diff options
author | wxiaoguang <wxiaoguang@gmail.com> | 2023-04-29 20:02:29 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-04-29 08:02:29 -0400 |
commit | 241b74f6c536c1d7de3b4e79e552bf1a3264cc6d (patch) | |
tree | b6e482d056eeb1d00ca48de9161dc1123f6bbaf4 /modules/templates/util_render.go | |
parent | 5a5ab8ef5ac5fbdb893707933f06ff6bcd8e834a (diff) | |
download | gitea-241b74f6c536c1d7de3b4e79e552bf1a3264cc6d.tar.gz gitea-241b74f6c536c1d7de3b4e79e552bf1a3264cc6d.zip |
Improve template helper (#24417)
It seems that we really need the "context function" soon. So we should
clean up the helper functions first.
Major changes:
* Improve StringUtils and add JsonUtils
* Remove one-time-use helper functions like CompareLink
* Move other code (no change) to util_avatar/util_render/util_misc (no
need to propose changes for them)
I have tested the changed templates:
![image](https://user-images.githubusercontent.com/2114189/235283862-608dbf6b-2da3-4d06-8157-b523ca93edb4.png)
![image](https://user-images.githubusercontent.com/2114189/235283888-1dfc0471-e622-4d64-9d76-7859819580d3.png)
![image](https://user-images.githubusercontent.com/2114189/235283903-d559f14d-4abb-4a50-915f-2b9cbc381a7a.png)
![image](https://user-images.githubusercontent.com/2114189/235283955-b7b5adea-aca3-4758-b38a-3aae3f7c6048.png)
---------
Co-authored-by: Giteabot <teabot@gitea.io>
Diffstat (limited to 'modules/templates/util_render.go')
-rw-r--r-- | modules/templates/util_render.go | 254 |
1 files changed, 254 insertions, 0 deletions
diff --git a/modules/templates/util_render.go b/modules/templates/util_render.go new file mode 100644 index 0000000000..a59ddd3f17 --- /dev/null +++ b/modules/templates/util_render.go @@ -0,0 +1,254 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package templates + +import ( + "context" + "encoding/hex" + "fmt" + "html/template" + "math" + "net/url" + "regexp" + "strings" + "unicode" + + issues_model "code.gitea.io/gitea/models/issues" + "code.gitea.io/gitea/modules/emoji" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/markup" + "code.gitea.io/gitea/modules/markup/markdown" + "code.gitea.io/gitea/modules/setting" +) + +// RenderCommitMessage renders commit message with XSS-safe and special links. +func RenderCommitMessage(ctx context.Context, msg, urlPrefix string, metas map[string]string) template.HTML { + return RenderCommitMessageLink(ctx, msg, urlPrefix, "", metas) +} + +// RenderCommitMessageLink renders commit message as a XXS-safe link to the provided +// default url, handling for special links. +func RenderCommitMessageLink(ctx context.Context, msg, urlPrefix, urlDefault string, metas map[string]string) template.HTML { + cleanMsg := template.HTMLEscapeString(msg) + // we can safely assume that it will not return any error, since there + // shouldn't be any special HTML. + fullMessage, err := markup.RenderCommitMessage(&markup.RenderContext{ + Ctx: ctx, + URLPrefix: urlPrefix, + DefaultLink: urlDefault, + Metas: metas, + }, cleanMsg) + if err != nil { + log.Error("RenderCommitMessage: %v", err) + return "" + } + msgLines := strings.Split(strings.TrimSpace(fullMessage), "\n") + if len(msgLines) == 0 { + return template.HTML("") + } + return template.HTML(msgLines[0]) +} + +// RenderCommitMessageLinkSubject renders commit message as a XXS-safe link to +// the provided default url, handling for special links without email to links. +func RenderCommitMessageLinkSubject(ctx context.Context, msg, urlPrefix, urlDefault string, metas map[string]string) template.HTML { + msgLine := strings.TrimLeftFunc(msg, unicode.IsSpace) + lineEnd := strings.IndexByte(msgLine, '\n') + if lineEnd > 0 { + msgLine = msgLine[:lineEnd] + } + msgLine = strings.TrimRightFunc(msgLine, unicode.IsSpace) + if len(msgLine) == 0 { + return template.HTML("") + } + + // we can safely assume that it will not return any error, since there + // shouldn't be any special HTML. + renderedMessage, err := markup.RenderCommitMessageSubject(&markup.RenderContext{ + Ctx: ctx, + URLPrefix: urlPrefix, + DefaultLink: urlDefault, + Metas: metas, + }, template.HTMLEscapeString(msgLine)) + if err != nil { + log.Error("RenderCommitMessageSubject: %v", err) + return template.HTML("") + } + return template.HTML(renderedMessage) +} + +// RenderCommitBody extracts the body of a commit message without its title. +func RenderCommitBody(ctx context.Context, msg, urlPrefix string, metas map[string]string) template.HTML { + msgLine := strings.TrimRightFunc(msg, unicode.IsSpace) + lineEnd := strings.IndexByte(msgLine, '\n') + if lineEnd > 0 { + msgLine = msgLine[lineEnd+1:] + } else { + return template.HTML("") + } + msgLine = strings.TrimLeftFunc(msgLine, unicode.IsSpace) + if len(msgLine) == 0 { + return template.HTML("") + } + + renderedMessage, err := markup.RenderCommitMessage(&markup.RenderContext{ + Ctx: ctx, + URLPrefix: urlPrefix, + Metas: metas, + }, template.HTMLEscapeString(msgLine)) + if err != nil { + log.Error("RenderCommitMessage: %v", err) + return "" + } + return template.HTML(renderedMessage) +} + +// Match text that is between back ticks. +var codeMatcher = regexp.MustCompile("`([^`]+)`") + +// RenderCodeBlock renders "`…`" as highlighted "<code>" block. +// Intended for issue and PR titles, these containers should have styles for "<code>" elements +func RenderCodeBlock(htmlEscapedTextToRender template.HTML) template.HTML { + htmlWithCodeTags := codeMatcher.ReplaceAllString(string(htmlEscapedTextToRender), "<code>$1</code>") // replace with HTML <code> tags + return template.HTML(htmlWithCodeTags) +} + +// RenderIssueTitle renders issue/pull title with defined post processors +func RenderIssueTitle(ctx context.Context, text, urlPrefix string, metas map[string]string) template.HTML { + renderedText, err := markup.RenderIssueTitle(&markup.RenderContext{ + Ctx: ctx, + URLPrefix: urlPrefix, + Metas: metas, + }, template.HTMLEscapeString(text)) + if err != nil { + log.Error("RenderIssueTitle: %v", err) + return template.HTML("") + } + return template.HTML(renderedText) +} + +// RenderLabel renders a label +func RenderLabel(ctx context.Context, label *issues_model.Label) template.HTML { + labelScope := label.ExclusiveScope() + + textColor := "#111" + if label.UseLightTextColor() { + textColor = "#eee" + } + + description := emoji.ReplaceAliases(template.HTMLEscapeString(label.Description)) + + if labelScope == "" { + // Regular label + s := fmt.Sprintf("<div class='ui label' style='color: %s !important; background-color: %s !important' title='%s'>%s</div>", + textColor, label.Color, description, RenderEmoji(ctx, label.Name)) + return template.HTML(s) + } + + // Scoped label + scopeText := RenderEmoji(ctx, labelScope) + itemText := RenderEmoji(ctx, label.Name[len(labelScope)+1:]) + + itemColor := label.Color + scopeColor := label.Color + if r, g, b, err := label.ColorRGB(); err == nil { + // Make scope and item background colors slightly darker and lighter respectively. + // More contrast needed with higher luminance, empirically tweaked. + luminance := (0.299*r + 0.587*g + 0.114*b) / 255 + contrast := 0.01 + luminance*0.03 + // Ensure we add the same amount of contrast also near 0 and 1. + darken := contrast + math.Max(luminance+contrast-1.0, 0.0) + lighten := contrast + math.Max(contrast-luminance, 0.0) + // Compute factor to keep RGB values proportional. + darkenFactor := math.Max(luminance-darken, 0.0) / math.Max(luminance, 1.0/255.0) + lightenFactor := math.Min(luminance+lighten, 1.0) / math.Max(luminance, 1.0/255.0) + + scopeBytes := []byte{ + uint8(math.Min(math.Round(r*darkenFactor), 255)), + uint8(math.Min(math.Round(g*darkenFactor), 255)), + uint8(math.Min(math.Round(b*darkenFactor), 255)), + } + itemBytes := []byte{ + uint8(math.Min(math.Round(r*lightenFactor), 255)), + uint8(math.Min(math.Round(g*lightenFactor), 255)), + uint8(math.Min(math.Round(b*lightenFactor), 255)), + } + + itemColor = "#" + hex.EncodeToString(itemBytes) + scopeColor = "#" + hex.EncodeToString(scopeBytes) + } + + s := fmt.Sprintf("<span class='ui label scope-parent' title='%s'>"+ + "<div class='ui label scope-left' style='color: %s !important; background-color: %s !important'>%s</div>"+ + "<div class='ui label scope-right' style='color: %s !important; background-color: %s !important''>%s</div>"+ + "</span>", + description, + textColor, scopeColor, scopeText, + textColor, itemColor, itemText) + return template.HTML(s) +} + +// RenderEmoji renders html text with emoji post processors +func RenderEmoji(ctx context.Context, text string) template.HTML { + renderedText, err := markup.RenderEmoji(&markup.RenderContext{Ctx: ctx}, + template.HTMLEscapeString(text)) + if err != nil { + log.Error("RenderEmoji: %v", err) + return template.HTML("") + } + return template.HTML(renderedText) +} + +// ReactionToEmoji renders emoji for use in reactions +func ReactionToEmoji(reaction string) template.HTML { + val := emoji.FromCode(reaction) + if val != nil { + return template.HTML(val.Emoji) + } + val = emoji.FromAlias(reaction) + if val != nil { + return template.HTML(val.Emoji) + } + return template.HTML(fmt.Sprintf(`<img alt=":%s:" src="%s/assets/img/emoji/%s.png"></img>`, reaction, setting.StaticURLPrefix, url.PathEscape(reaction))) +} + +// RenderNote renders the contents of a git-notes file as a commit message. +func RenderNote(ctx context.Context, msg, urlPrefix string, metas map[string]string) template.HTML { + cleanMsg := template.HTMLEscapeString(msg) + fullMessage, err := markup.RenderCommitMessage(&markup.RenderContext{ + Ctx: ctx, + URLPrefix: urlPrefix, + Metas: metas, + }, cleanMsg) + if err != nil { + log.Error("RenderNote: %v", err) + return "" + } + return template.HTML(fullMessage) +} + +func RenderMarkdownToHtml(ctx context.Context, input string) template.HTML { //nolint:revive + output, err := markdown.RenderString(&markup.RenderContext{ + Ctx: ctx, + URLPrefix: setting.AppSubURL, + }, input) + if err != nil { + log.Error("RenderString: %v", err) + } + return template.HTML(output) +} + +func RenderLabels(ctx context.Context, labels []*issues_model.Label, repoLink string) template.HTML { + htmlCode := `<span class="labels-list">` + for _, label := range labels { + // Protect against nil value in labels - shouldn't happen but would cause a panic if so + if label == nil { + continue + } + htmlCode += fmt.Sprintf("<a href='%s/issues?labels=%d'>%s</a> ", + repoLink, label.ID, RenderLabel(ctx, label)) + } + htmlCode += "</span>" + return template.HTML(htmlCode) +} |