loading up the forgejo repo on tangled to test page performance
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Make PR form use toast to show error message (#29545)

![image](https://github.com/go-gitea/gitea/assets/2114189/b7a14ed6-db89-4f21-a590-66cd33307233)

(cherry picked from commit 27deea7330f83ddb37c918afbb4159053d8847cb)

authored by

wxiaoguang and committed by
Earl Warren
221a2843 d509031e

+35 -30
+1 -1
routers/web/repo/branch.go
··· 231 231 if len(e.Message) == 0 { 232 232 ctx.Flash.Error(ctx.Tr("repo.editor.push_rejected_no_message")) 233 233 } else { 234 - flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{ 234 + flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{ 235 235 "Message": ctx.Tr("repo.editor.push_rejected"), 236 236 "Summary": ctx.Tr("repo.editor.push_rejected_summary"), 237 237 "Details": utils.SanitizeFlashErrorString(e.Message),
+4 -4
routers/web/repo/editor.go
··· 382 382 if len(errPushRej.Message) == 0 { 383 383 ctx.RenderWithErr(ctx.Tr("repo.editor.push_rejected_no_message"), tplEditFile, &form) 384 384 } else { 385 - flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{ 385 + flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{ 386 386 "Message": ctx.Tr("repo.editor.push_rejected"), 387 387 "Summary": ctx.Tr("repo.editor.push_rejected_summary"), 388 388 "Details": utils.SanitizeFlashErrorString(errPushRej.Message), ··· 394 394 ctx.RenderWithErr(flashError, tplEditFile, &form) 395 395 } 396 396 } else { 397 - flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{ 397 + flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{ 398 398 "Message": ctx.Tr("repo.editor.fail_to_update_file", form.TreePath), 399 399 "Summary": ctx.Tr("repo.editor.fail_to_update_file_summary"), 400 400 "Details": utils.SanitizeFlashErrorString(err.Error()), ··· 590 590 if len(errPushRej.Message) == 0 { 591 591 ctx.RenderWithErr(ctx.Tr("repo.editor.push_rejected_no_message"), tplDeleteFile, &form) 592 592 } else { 593 - flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{ 593 + flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{ 594 594 "Message": ctx.Tr("repo.editor.push_rejected"), 595 595 "Summary": ctx.Tr("repo.editor.push_rejected_summary"), 596 596 "Details": utils.SanitizeFlashErrorString(errPushRej.Message), ··· 797 797 if len(errPushRej.Message) == 0 { 798 798 ctx.RenderWithErr(ctx.Tr("repo.editor.push_rejected_no_message"), tplUploadFile, &form) 799 799 } else { 800 - flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{ 800 + flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{ 801 801 "Message": ctx.Tr("repo.editor.push_rejected"), 802 802 "Summary": ctx.Tr("repo.editor.push_rejected_summary"), 803 803 "Details": utils.SanitizeFlashErrorString(errPushRej.Message),
+8 -7
routers/web/repo/issue.go
··· 9 9 stdCtx "context" 10 10 "errors" 11 11 "fmt" 12 + "html/template" 12 13 "math/big" 13 14 "net/http" 14 15 "net/url" ··· 1022 1023 ctx.HTML(http.StatusOK, tplIssueNew) 1023 1024 } 1024 1025 1025 - func renderErrorOfTemplates(ctx *context.Context, errs map[string]error) string { 1026 + func renderErrorOfTemplates(ctx *context.Context, errs map[string]error) template.HTML { 1026 1027 var files []string 1027 1028 for k := range errs { 1028 1029 files = append(files, k) ··· 1034 1035 lines = append(lines, fmt.Sprintf("%s: %v", file, errs[file])) 1035 1036 } 1036 1037 1037 - flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{ 1038 + flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{ 1038 1039 "Message": ctx.Tr("repo.issues.choose.ignore_invalid_templates"), 1039 1040 "Summary": ctx.Tr("repo.issues.choose.invalid_templates", len(errs)), 1040 1041 "Details": utils.SanitizeFlashErrorString(strings.Join(lines, "\n")), 1041 1042 }) 1042 1043 if err != nil { 1043 1044 log.Debug("render flash error: %v", err) 1044 - flashError = ctx.Locale.TrString("repo.issues.choose.ignore_invalid_templates") 1045 + flashError = ctx.Locale.Tr("repo.issues.choose.ignore_invalid_templates") 1045 1046 } 1046 1047 return flashError 1047 1048 } ··· 3324 3325 return 3325 3326 } 3326 3327 3327 - html, err := ctx.RenderToString(tplReactions, map[string]any{ 3328 + html, err := ctx.RenderToHTML(tplReactions, map[string]any{ 3328 3329 "ctxData": ctx.Data, 3329 3330 "ActionURL": fmt.Sprintf("%s/issues/%d/reactions", ctx.Repo.RepoLink, issue.Index), 3330 3331 "Reactions": issue.Reactions.GroupByType(), ··· 3431 3432 return 3432 3433 } 3433 3434 3434 - html, err := ctx.RenderToString(tplReactions, map[string]any{ 3435 + html, err := ctx.RenderToHTML(tplReactions, map[string]any{ 3435 3436 "ctxData": ctx.Data, 3436 3437 "ActionURL": fmt.Sprintf("%s/comments/%d/reactions", ctx.Repo.RepoLink, comment.ID), 3437 3438 "Reactions": comment.Reactions.GroupByType(), ··· 3574 3575 return err 3575 3576 } 3576 3577 3577 - func attachmentsHTML(ctx *context.Context, attachments []*repo_model.Attachment, content string) string { 3578 - attachHTML, err := ctx.RenderToString(tplAttachment, map[string]any{ 3578 + func attachmentsHTML(ctx *context.Context, attachments []*repo_model.Attachment, content string) template.HTML { 3579 + attachHTML, err := ctx.RenderToHTML(tplAttachment, map[string]any{ 3579 3580 "ctxData": ctx.Data, 3580 3581 "Attachments": attachments, 3581 3582 "Content": content,
+7 -8
routers/web/repo/pull.go
··· 1159 1159 if err = pull_service.Update(ctx, issue.PullRequest, ctx.Doer, message, rebase); err != nil { 1160 1160 if models.IsErrMergeConflicts(err) { 1161 1161 conflictError := err.(models.ErrMergeConflicts) 1162 - flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{ 1162 + flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{ 1163 1163 "Message": ctx.Tr("repo.pulls.merge_conflict"), 1164 1164 "Summary": ctx.Tr("repo.pulls.merge_conflict_summary"), 1165 1165 "Details": utils.SanitizeFlashErrorString(conflictError.StdErr) + "<br>" + utils.SanitizeFlashErrorString(conflictError.StdOut), ··· 1173 1173 return 1174 1174 } else if models.IsErrRebaseConflicts(err) { 1175 1175 conflictError := err.(models.ErrRebaseConflicts) 1176 - flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{ 1176 + flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{ 1177 1177 "Message": ctx.Tr("repo.pulls.rebase_conflict", utils.SanitizeFlashErrorString(conflictError.CommitSHA)), 1178 1178 "Summary": ctx.Tr("repo.pulls.rebase_conflict_summary"), 1179 1179 "Details": utils.SanitizeFlashErrorString(conflictError.StdErr) + "<br>" + utils.SanitizeFlashErrorString(conflictError.StdOut), ··· 1305 1305 ctx.JSONError(ctx.Tr("repo.pulls.invalid_merge_option")) 1306 1306 } else if models.IsErrMergeConflicts(err) { 1307 1307 conflictError := err.(models.ErrMergeConflicts) 1308 - flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{ 1308 + flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{ 1309 1309 "Message": ctx.Tr("repo.editor.merge_conflict"), 1310 1310 "Summary": ctx.Tr("repo.editor.merge_conflict_summary"), 1311 1311 "Details": utils.SanitizeFlashErrorString(conflictError.StdErr) + "<br>" + utils.SanitizeFlashErrorString(conflictError.StdOut), ··· 1318 1318 ctx.JSONRedirect(issue.Link()) 1319 1319 } else if models.IsErrRebaseConflicts(err) { 1320 1320 conflictError := err.(models.ErrRebaseConflicts) 1321 - flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{ 1321 + flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{ 1322 1322 "Message": ctx.Tr("repo.pulls.rebase_conflict", utils.SanitizeFlashErrorString(conflictError.CommitSHA)), 1323 1323 "Summary": ctx.Tr("repo.pulls.rebase_conflict_summary"), 1324 1324 "Details": utils.SanitizeFlashErrorString(conflictError.StdErr) + "<br>" + utils.SanitizeFlashErrorString(conflictError.StdOut), ··· 1348 1348 if len(message) == 0 { 1349 1349 ctx.Flash.Error(ctx.Tr("repo.pulls.push_rejected_no_message")) 1350 1350 } else { 1351 - flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{ 1351 + flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{ 1352 1352 "Message": ctx.Tr("repo.pulls.push_rejected"), 1353 1353 "Summary": ctx.Tr("repo.pulls.push_rejected_summary"), 1354 1354 "Details": utils.SanitizeFlashErrorString(pushrejErr.Message), ··· 1525 1525 ctx.JSONError(ctx.Tr("repo.pulls.push_rejected_no_message")) 1526 1526 return 1527 1527 } 1528 - flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{ 1528 + flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{ 1529 1529 "Message": ctx.Tr("repo.pulls.push_rejected"), 1530 1530 "Summary": ctx.Tr("repo.pulls.push_rejected_summary"), 1531 1531 "Details": utils.SanitizeFlashErrorString(pushrejErr.Message), ··· 1534 1534 ctx.ServerError("CompareAndPullRequest.HTMLString", err) 1535 1535 return 1536 1536 } 1537 - ctx.Flash.Error(flashError) 1538 - ctx.JSONRedirect(ctx.Link + "?" + ctx.Req.URL.RawQuery) // FIXME: it's unfriendly, and will make the content lost 1537 + ctx.JSONError(flashError) 1539 1538 return 1540 1539 } 1541 1540 ctx.ServerError("NewPullRequest", err)
+5 -4
services/context/context_response.go
··· 6 6 import ( 7 7 "errors" 8 8 "fmt" 9 + "html/template" 9 10 "net" 10 11 "net/http" 11 12 "net/url" ··· 104 105 } 105 106 } 106 107 107 - // RenderToString renders the template content to a string 108 - func (ctx *Context) RenderToString(name base.TplName, data map[string]any) (string, error) { 108 + // RenderToHTML renders the template content to a HTML string 109 + func (ctx *Context) RenderToHTML(name base.TplName, data map[string]any) (template.HTML, error) { 109 110 var buf strings.Builder 110 - err := ctx.Render.HTML(&buf, http.StatusOK, string(name), data, ctx.TemplateContext) 111 - return buf.String(), err 111 + err := ctx.Render.HTML(&buf, 0, string(name), data, ctx.TemplateContext) 112 + return template.HTML(buf.String()), err 112 113 } 113 114 114 115 // RenderWithErr used for page has form validation but need to prompt error to users.
+8 -3
web_src/js/features/common-global.js
··· 91 91 } else { 92 92 window.location.reload(); 93 93 } 94 + return; 94 95 } else if (resp.status >= 400 && resp.status < 500) { 95 96 const data = await resp.json(); 96 97 // the code was quite messy, sometimes the backend uses "err", sometimes it uses "error", and even "user_error" 97 98 // but at the moment, as a new approach, we only use "errorMessage" here, backend can use JSONError() to respond. 98 - showErrorToast(data.errorMessage || `server error: ${resp.status}`); 99 + if (data.errorMessage) { 100 + showErrorToast(data.errorMessage, {useHtmlBody: data.renderFormat === 'html'}); 101 + } else { 102 + showErrorToast(`server error: ${resp.status}`); 103 + } 99 104 } else { 100 105 showErrorToast(`server error: ${resp.status}`); 101 106 } 102 107 } catch (e) { 103 108 console.error('error when doRequest', e); 104 - actionElem.classList.remove('is-loading', 'small-loading-icon'); 105 - showErrorToast(i18n.network_error); 109 + showErrorToast(`${i18n.network_error} ${e}`); 106 110 } 111 + actionElem.classList.remove('is-loading', 'small-loading-icon'); 107 112 } 108 113 109 114 async function formFetchAction(e) {
+2 -3
web_src/js/modules/toast.js
··· 21 21 }; 22 22 23 23 // See https://github.com/apvarun/toastify-js#api for options 24 - function showToast(message, level, {gravity, position, duration, ...other} = {}) { 24 + function showToast(message, level, {gravity, position, duration, useHtmlBody, ...other} = {}) { 25 25 const {icon, background, duration: levelDuration} = levels[level ?? 'info']; 26 - 27 26 const toast = Toastify({ 28 27 text: ` 29 28 <div class='toast-icon'>${svg(icon)}</div> 30 - <div class='toast-body'>${htmlEscape(message)}</div> 29 + <div class='toast-body'>${useHtmlBody ? message : htmlEscape(message)}</div> 31 30 <button class='toast-close'>${svg('octicon-x')}</button> 32 31 `, 33 32 escapeMarkup: false,