From e68b9d00a6e05b3a941f63ffb696f91e554ac5ec Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 18 Oct 2024 20:33:49 +0200 Subject: Adding upstream version 9.0.3. Signed-off-by: Daniel Baumann --- modules/templates/util_render.go | 264 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 264 insertions(+) create mode 100644 modules/templates/util_render.go (limited to 'modules/templates/util_render.go') diff --git a/modules/templates/util_render.go b/modules/templates/util_render.go new file mode 100644 index 0000000..c53bdd8 --- /dev/null +++ b/modules/templates/util_render.go @@ -0,0 +1,264 @@ +// 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" + "code.gitea.io/gitea/modules/translation" + "code.gitea.io/gitea/modules/util" +) + +// RenderCommitMessage renders commit message with XSS-safe and special links. +func RenderCommitMessage(ctx context.Context, msg 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, + 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 RenderCodeBlock(template.HTML(msgLines[0])) +} + +// RenderCommitMessageLinkSubject renders commit message as a XSS-safe link to +// the provided default url, handling for special links without email to links. +func RenderCommitMessageLinkSubject(ctx context.Context, msg, 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, + DefaultLink: urlDefault, + Metas: metas, + }, template.HTMLEscapeString(msgLine)) + if err != nil { + log.Error("RenderCommitMessageSubject: %v", err) + return template.HTML("") + } + return RenderCodeBlock(template.HTML(renderedMessage)) +} + +// RenderCommitBody extracts the body of a commit message without its title. +func RenderCommitBody(ctx context.Context, msg string, metas map[string]string) template.HTML { + msgLine := strings.TrimSpace(msg) + lineEnd := strings.IndexByte(msgLine, '\n') + if lineEnd > 0 { + msgLine = msgLine[lineEnd+1:] + } else { + return "" + } + msgLine = strings.TrimLeftFunc(msgLine, unicode.IsSpace) + if len(msgLine) == 0 { + return "" + } + + renderedMessage, err := markup.RenderCommitMessage(&markup.RenderContext{ + Ctx: ctx, + 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 "" block, intended for issue and PR titles +func RenderCodeBlock(htmlEscapedTextToRender template.HTML) template.HTML { + htmlWithCodeTags := codeMatcher.ReplaceAllString(string(htmlEscapedTextToRender), `$1`) // replace with HTML tags + return template.HTML(htmlWithCodeTags) +} + +const ( + activeLabelOpacity = uint8(255) + archivedLabelOpacity = uint8(127) +) + +func GetLabelOpacityByte(isArchived bool) uint8 { + if isArchived { + return archivedLabelOpacity + } + return activeLabelOpacity +} + +// RenderIssueTitle renders issue/pull title with defined post processors +func RenderIssueTitle(ctx context.Context, text string, metas map[string]string) template.HTML { + renderedText, err := markup.RenderIssueTitle(&markup.RenderContext{ + Ctx: ctx, + Metas: metas, + }, template.HTMLEscapeString(text)) + if err != nil { + log.Error("RenderIssueTitle: %v", err) + return template.HTML("") + } + return template.HTML(renderedText) +} + +// RenderRefIssueTitle renders referenced issue/pull title with defined post processors +func RenderRefIssueTitle(ctx context.Context, text string) template.HTML { + renderedText, err := markup.RenderRefIssueTitle(&markup.RenderContext{Ctx: ctx}, template.HTMLEscapeString(text)) + if err != nil { + log.Error("RenderRefIssueTitle: %v", err) + return "" + } + + return template.HTML(renderedText) +} + +// RenderLabel renders a label +// locale is needed due to an import cycle with our context providing the `Tr` function +func RenderLabel(ctx context.Context, locale translation.Locale, label *issues_model.Label) template.HTML { + var ( + archivedCSSClass string + textColor = util.ContrastColor(label.Color) + labelScope = label.ExclusiveScope() + ) + + description := emoji.ReplaceAliases(template.HTMLEscapeString(label.Description)) + + if label.IsArchived() { + archivedCSSClass = "archived-label" + description = locale.TrString("repo.issues.archived_label_description", description) + } + + if labelScope == "" { + // Regular label + + labelColor := label.Color + hex.EncodeToString([]byte{GetLabelOpacityByte(label.IsArchived())}) + s := fmt.Sprintf("
%s
", + archivedCSSClass, textColor, labelColor, description, RenderEmoji(ctx, label.Name)) + return template.HTML(s) + } + + // Scoped label + scopeText := RenderEmoji(ctx, labelScope) + itemText := RenderEmoji(ctx, label.Name[len(labelScope)+1:]) + + // Make scope and item background colors slightly darker and lighter respectively. + // More contrast needed with higher luminance, empirically tweaked. + luminance := util.GetRelativeLuminance(label.Color) + 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) + + opacity := GetLabelOpacityByte(label.IsArchived()) + r, g, b := util.HexToRBGColor(label.Color) + 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)), + opacity, + } + 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)), + opacity, + } + + scopeColor := "#" + hex.EncodeToString(scopeBytes) + itemColor := "#" + hex.EncodeToString(itemBytes) + + s := fmt.Sprintf(""+ + "
%s
"+ + "
%s
"+ + "
", + archivedCSSClass, 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(`:%s:`, reaction, setting.StaticURLPrefix, url.PathEscape(reaction))) +} + +func RenderMarkdownToHtml(ctx context.Context, input string) template.HTML { //nolint:revive + output, err := markdown.RenderString(&markup.RenderContext{ + Ctx: ctx, + Metas: map[string]string{"mode": "document"}, + }, input) + if err != nil { + log.Error("RenderString: %v", err) + } + return output +} + +func RenderLabels(ctx context.Context, locale translation.Locale, labels []*issues_model.Label, repoLink string, isPull bool) template.HTML { + htmlCode := `` + for _, label := range labels { + // Protect against nil value in labels - shouldn't happen but would cause a panic if so + if label == nil { + continue + } + + issuesOrPull := "issues" + if isPull { + issuesOrPull = "pulls" + } + htmlCode += fmt.Sprintf("%s ", + repoLink, issuesOrPull, label.ID, RenderLabel(ctx, locale, label)) + } + htmlCode += "" + return template.HTML(htmlCode) +} -- cgit v1.2.3