1
1
mirror of https://github.com/go-gitea/gitea synced 2025-07-22 18:28:37 +00:00

Refactor template ctx and render utils (#32422)

Clean up the templates
This commit is contained in:
wxiaoguang
2024-11-05 14:04:26 +08:00
committed by GitHub
parent b068dbd40e
commit 4a469c8e1b
53 changed files with 281 additions and 205 deletions

View File

@@ -73,11 +73,6 @@ func NewFuncMap() template.FuncMap {
return fmt.Sprint(time.Since(startTime).Nanoseconds()/1e6) + "ms"
},
// for backward compatibility only, do not use them anymore
"TimeSince": timeSinceLegacy,
"TimeSinceUnix": timeSinceLegacy,
"DateTime": dateTimeLegacy,
// -----------------------------------------------------------------
// setting
"AppName": func() string {
@@ -156,18 +151,8 @@ func NewFuncMap() template.FuncMap {
// -----------------------------------------------------------------
// render
"RenderCommitMessage": RenderCommitMessage,
"RenderCommitMessageLinkSubject": renderCommitMessageLinkSubject,
"RenderCommitBody": renderCommitBody,
"RenderCodeBlock": renderCodeBlock,
"RenderIssueTitle": renderIssueTitle,
"RenderEmoji": renderEmoji,
"ReactionToEmoji": reactionToEmoji,
"RenderMarkdownToHtml": RenderMarkdownToHtml,
"RenderLabel": renderLabel,
"RenderLabels": RenderLabels,
"RenderCodeBlock": renderCodeBlock,
"ReactionToEmoji": reactionToEmoji,
// -----------------------------------------------------------------
// misc
@@ -179,6 +164,22 @@ func NewFuncMap() template.FuncMap {
"FilenameIsImage": filenameIsImage,
"TabSizeClass": tabSizeClass,
// for backward compatibility only, do not use them anymore
"TimeSince": timeSinceLegacy,
"TimeSinceUnix": timeSinceLegacy,
"DateTime": dateTimeLegacy,
"RenderEmoji": renderEmojiLegacy,
"RenderLabel": renderLabelLegacy,
"RenderLabels": renderLabelsLegacy,
"RenderIssueTitle": renderIssueTitleLegacy,
"RenderMarkdownToHtml": renderMarkdownToHtmlLegacy,
"RenderCommitMessage": renderCommitMessageLegacy,
"RenderCommitMessageLinkSubject": renderCommitMessageLinkSubjectLegacy,
"RenderCommitBody": renderCommitBodyLegacy,
}
}
@@ -296,3 +297,9 @@ func userThemeName(user *user_model.User) string {
}
return setting.UI.DefaultTheme
}
func panicIfDevOrTesting() {
if !setting.IsProd || setting.IsInTesting {
panic("legacy template functions are for backward compatibility only, do not use them in new code")
}
}

View File

@@ -12,7 +12,6 @@ import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/translation"
)
type DateUtils struct{}
@@ -54,23 +53,6 @@ func parseLegacy(datetime string) time.Time {
return t
}
func dateTimeLegacy(format string, datetime any, _ ...string) template.HTML {
if !setting.IsProd || setting.IsInTesting {
panic("dateTimeLegacy is for backward compatibility only, do not use it in new code")
}
if s, ok := datetime.(string); ok {
datetime = parseLegacy(s)
}
return dateTimeFormat(format, datetime)
}
func timeSinceLegacy(time any, _ translation.Locale) template.HTML {
if !setting.IsProd || setting.IsInTesting {
panic("timeSinceLegacy is for backward compatibility only, do not use it in new code")
}
return TimeSince(time)
}
func anyToTime(any any) (t time.Time, isZero bool) {
switch v := any.(type) {
case nil:

View File

@@ -0,0 +1,23 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package templates
import (
"html/template"
"code.gitea.io/gitea/modules/translation"
)
func dateTimeLegacy(format string, datetime any, _ ...string) template.HTML {
panicIfDevOrTesting()
if s, ok := datetime.(string); ok {
datetime = parseLegacy(s)
}
return dateTimeFormat(format, datetime)
}
func timeSinceLegacy(time any, _ translation.Locale) template.HTML {
panicIfDevOrTesting()
return TimeSince(time)
}

View File

@@ -24,13 +24,21 @@ import (
"code.gitea.io/gitea/modules/util"
)
type RenderUtils struct {
ctx context.Context
}
func NewRenderUtils(ctx context.Context) *RenderUtils {
return &RenderUtils{ctx: ctx}
}
// RenderCommitMessage renders commit message with XSS-safe and special links.
func RenderCommitMessage(ctx context.Context, msg string, metas map[string]string) template.HTML {
func (ut *RenderUtils) RenderCommitMessage(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,
Ctx: ut.ctx,
Metas: metas,
}, cleanMsg)
if err != nil {
@@ -44,9 +52,9 @@ func RenderCommitMessage(ctx context.Context, msg string, metas map[string]strin
return renderCodeBlock(template.HTML(msgLines[0]))
}
// renderCommitMessageLinkSubject renders commit message as a XSS-safe link to
// 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 {
func (ut *RenderUtils) RenderCommitMessageLinkSubject(msg, urlDefault string, metas map[string]string) template.HTML {
msgLine := strings.TrimLeftFunc(msg, unicode.IsSpace)
lineEnd := strings.IndexByte(msgLine, '\n')
if lineEnd > 0 {
@@ -60,7 +68,7 @@ func renderCommitMessageLinkSubject(ctx context.Context, msg, urlDefault string,
// 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,
Ctx: ut.ctx,
DefaultLink: urlDefault,
Metas: metas,
}, template.HTMLEscapeString(msgLine))
@@ -71,8 +79,8 @@ func renderCommitMessageLinkSubject(ctx context.Context, msg, urlDefault string,
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 {
// RenderCommitBody extracts the body of a commit message without its title.
func (ut *RenderUtils) RenderCommitBody(msg string, metas map[string]string) template.HTML {
msgLine := strings.TrimSpace(msg)
lineEnd := strings.IndexByte(msgLine, '\n')
if lineEnd > 0 {
@@ -86,7 +94,7 @@ func renderCommitBody(ctx context.Context, msg string, metas map[string]string)
}
renderedMessage, err := markup.RenderCommitMessage(&markup.RenderContext{
Ctx: ctx,
Ctx: ut.ctx,
Metas: metas,
}, template.HTMLEscapeString(msgLine))
if err != nil {
@@ -105,22 +113,22 @@ func renderCodeBlock(htmlEscapedTextToRender template.HTML) template.HTML {
return template.HTML(htmlWithCodeTags)
}
// renderIssueTitle renders issue/pull title with defined post processors
func renderIssueTitle(ctx context.Context, text string, metas map[string]string) template.HTML {
// RenderIssueTitle renders issue/pull title with defined post processors
func (ut *RenderUtils) RenderIssueTitle(text string, metas map[string]string) template.HTML {
renderedText, err := markup.RenderIssueTitle(&markup.RenderContext{
Ctx: ctx,
Ctx: ut.ctx,
Metas: metas,
}, template.HTMLEscapeString(text))
if err != nil {
log.Error("RenderIssueTitle: %v", err)
return template.HTML("")
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 {
// RenderLabel renders a label
func (ut *RenderUtils) RenderLabel(label *issues_model.Label) template.HTML {
locale := ut.ctx.Value(translation.ContextKey).(translation.Locale)
var extraCSSClasses string
textColor := util.ContrastColor(label.Color)
labelScope := label.ExclusiveScope()
@@ -134,12 +142,12 @@ func renderLabel(ctx context.Context, locale translation.Locale, label *issues_m
if labelScope == "" {
// Regular label
return HTMLFormat(`<div class="ui label %s" style="color: %s !important; background-color: %s !important;" data-tooltip-content title="%s">%s</div>`,
extraCSSClasses, textColor, label.Color, descriptionText, renderEmoji(ctx, label.Name))
extraCSSClasses, textColor, label.Color, descriptionText, ut.RenderEmoji(label.Name))
}
// Scoped label
scopeHTML := renderEmoji(ctx, labelScope)
itemHTML := renderEmoji(ctx, label.Name[len(labelScope)+1:])
scopeHTML := ut.RenderEmoji(labelScope)
itemHTML := ut.RenderEmoji(label.Name[len(labelScope)+1:])
// Make scope and item background colors slightly darker and lighter respectively.
// More contrast needed with higher luminance, empirically tweaked.
@@ -176,13 +184,12 @@ func renderLabel(ctx context.Context, locale translation.Locale, label *issues_m
textColor, itemColor, itemHTML)
}
// 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))
// RenderEmoji renders html text with emoji post processors
func (ut *RenderUtils) RenderEmoji(text string) template.HTML {
renderedText, err := markup.RenderEmoji(&markup.RenderContext{Ctx: ut.ctx}, template.HTMLEscapeString(text))
if err != nil {
log.Error("RenderEmoji: %v", err)
return template.HTML("")
return ""
}
return template.HTML(renderedText)
}
@@ -200,9 +207,9 @@ func reactionToEmoji(reaction string) template.HTML {
return template.HTML(fmt.Sprintf(`<img alt=":%s:" src="%s/assets/img/emoji/%s.png"></img>`, reaction, setting.StaticURLPrefix, url.PathEscape(reaction)))
}
func RenderMarkdownToHtml(ctx context.Context, input string) template.HTML { //nolint:revive
func (ut *RenderUtils) MarkdownToHtml(input string) template.HTML { //nolint:revive
output, err := markdown.RenderString(&markup.RenderContext{
Ctx: ctx,
Ctx: ut.ctx,
Metas: map[string]string{"mode": "document"},
}, input)
if err != nil {
@@ -211,7 +218,7 @@ func RenderMarkdownToHtml(ctx context.Context, input string) template.HTML { //n
return output
}
func RenderLabels(ctx context.Context, locale translation.Locale, labels []*issues_model.Label, repoLink string, issue *issues_model.Issue) template.HTML {
func (ut *RenderUtils) RenderLabels(labels []*issues_model.Label, repoLink string, issue *issues_model.Issue) template.HTML {
isPullRequest := issue != nil && issue.IsPull
baseLink := fmt.Sprintf("%s/%s", repoLink, util.Iif(isPullRequest, "pulls", "issues"))
htmlCode := `<span class="labels-list">`
@@ -220,7 +227,7 @@ func RenderLabels(ctx context.Context, locale translation.Locale, labels []*issu
if label == nil {
continue
}
htmlCode += fmt.Sprintf(`<a href="%s?labels=%d">%s</a>`, baseLink, label.ID, renderLabel(ctx, locale, label))
htmlCode += fmt.Sprintf(`<a href="%s?labels=%d">%s</a>`, baseLink, label.ID, ut.RenderLabel(label))
}
htmlCode += "</span>"
return template.HTML(htmlCode)

View File

@@ -0,0 +1,52 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package templates
import (
"context"
"html/template"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/modules/translation"
)
func renderEmojiLegacy(ctx context.Context, text string) template.HTML {
panicIfDevOrTesting()
return NewRenderUtils(ctx).RenderEmoji(text)
}
func renderLabelLegacy(ctx context.Context, locale translation.Locale, label *issues_model.Label) template.HTML {
panicIfDevOrTesting()
return NewRenderUtils(ctx).RenderLabel(label)
}
func renderLabelsLegacy(ctx context.Context, locale translation.Locale, labels []*issues_model.Label, repoLink string, issue *issues_model.Issue) template.HTML {
panicIfDevOrTesting()
return NewRenderUtils(ctx).RenderLabels(labels, repoLink, issue)
}
func renderMarkdownToHtmlLegacy(ctx context.Context, input string) template.HTML { //nolint:revive
panicIfDevOrTesting()
return NewRenderUtils(ctx).MarkdownToHtml(input)
}
func renderCommitMessageLegacy(ctx context.Context, msg string, metas map[string]string) template.HTML {
panicIfDevOrTesting()
return NewRenderUtils(ctx).RenderCommitMessage(msg, metas)
}
func renderCommitMessageLinkSubjectLegacy(ctx context.Context, msg, urlDefault string, metas map[string]string) template.HTML {
panicIfDevOrTesting()
return NewRenderUtils(ctx).RenderCommitMessageLinkSubject(msg, urlDefault, metas)
}
func renderIssueTitleLegacy(ctx context.Context, text string, metas map[string]string) template.HTML {
panicIfDevOrTesting()
return NewRenderUtils(ctx).RenderIssueTitle(text, metas)
}
func renderCommitBodyLegacy(ctx context.Context, msg string, metas map[string]string) template.HTML {
panicIfDevOrTesting()
return NewRenderUtils(ctx).RenderCommitBody(msg, metas)
}

View File

@@ -65,9 +65,14 @@ func TestMain(m *testing.M) {
os.Exit(m.Run())
}
func newTestRenderUtils() *RenderUtils {
ctx := context.Background()
ctx = context.WithValue(ctx, translation.ContextKey, &translation.MockLocale{})
return NewRenderUtils(ctx)
}
func TestRenderCommitBody(t *testing.T) {
type args struct {
ctx context.Context
msg string
metas map[string]string
}
@@ -79,7 +84,6 @@ func TestRenderCommitBody(t *testing.T) {
{
name: "multiple lines",
args: args{
ctx: context.Background(),
msg: "first line\nsecond line",
},
want: "second line",
@@ -87,7 +91,6 @@ func TestRenderCommitBody(t *testing.T) {
{
name: "multiple lines with leading newlines",
args: args{
ctx: context.Background(),
msg: "\n\n\n\nfirst line\nsecond line",
},
want: "second line",
@@ -95,15 +98,15 @@ func TestRenderCommitBody(t *testing.T) {
{
name: "multiple lines with trailing newlines",
args: args{
ctx: context.Background(),
msg: "first line\nsecond line\n\n\n",
},
want: "second line",
},
}
ut := newTestRenderUtils()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equalf(t, tt.want, renderCommitBody(tt.args.ctx, tt.args.msg, tt.args.metas), "RenderCommitBody(%v, %v, %v)", tt.args.ctx, tt.args.msg, tt.args.metas)
assert.Equalf(t, tt.want, ut.RenderCommitBody(tt.args.msg, tt.args.metas), "RenderCommitBody(%v, %v)", tt.args.msg, tt.args.metas)
})
}
@@ -127,19 +130,19 @@ com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
<a href="/user13/repo11/issues/123" class="ref-issue">#123</a>
space`
assert.EqualValues(t, expected, renderCommitBody(context.Background(), testInput(), testMetas))
assert.EqualValues(t, expected, newTestRenderUtils().RenderCommitBody(testInput(), testMetas))
}
func TestRenderCommitMessage(t *testing.T) {
expected := `space <a href="/mention-user" class="mention">@mention-user</a> `
assert.EqualValues(t, expected, RenderCommitMessage(context.Background(), testInput(), testMetas))
assert.EqualValues(t, expected, newTestRenderUtils().RenderCommitMessage(testInput(), testMetas))
}
func TestRenderCommitMessageLinkSubject(t *testing.T) {
expected := `<a href="https://example.com/link" class="default-link muted">space </a><a href="/mention-user" class="mention">@mention-user</a>`
assert.EqualValues(t, expected, renderCommitMessageLinkSubject(context.Background(), testInput(), "https://example.com/link", testMetas))
assert.EqualValues(t, expected, newTestRenderUtils().RenderCommitMessageLinkSubject(testInput(), "https://example.com/link", testMetas))
}
func TestRenderIssueTitle(t *testing.T) {
@@ -165,7 +168,7 @@ mail@domain.com
space<SPACE><SPACE>
`
expected = strings.ReplaceAll(expected, "<SPACE>", " ")
assert.EqualValues(t, expected, renderIssueTitle(context.Background(), testInput(), testMetas))
assert.EqualValues(t, expected, newTestRenderUtils().RenderIssueTitle(testInput(), testMetas))
}
func TestRenderMarkdownToHtml(t *testing.T) {
@@ -190,25 +193,23 @@ com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
#123
space</p>
`
assert.Equal(t, expected, string(RenderMarkdownToHtml(context.Background(), testInput())))
assert.Equal(t, expected, string(newTestRenderUtils().MarkdownToHtml(testInput())))
}
func TestRenderLabels(t *testing.T) {
ctx := context.Background()
locale := &translation.MockLocale{}
ut := newTestRenderUtils()
label := &issues.Label{ID: 123, Name: "label-name", Color: "label-color"}
issue := &issues.Issue{}
expected := `/owner/repo/issues?labels=123`
assert.Contains(t, RenderLabels(ctx, locale, []*issues.Label{label}, "/owner/repo", issue), expected)
assert.Contains(t, ut.RenderLabels([]*issues.Label{label}, "/owner/repo", issue), expected)
label = &issues.Label{ID: 123, Name: "label-name", Color: "label-color"}
issue = &issues.Issue{IsPull: true}
expected = `/owner/repo/pulls?labels=123`
assert.Contains(t, RenderLabels(ctx, locale, []*issues.Label{label}, "/owner/repo", issue), expected)
assert.Contains(t, ut.RenderLabels([]*issues.Label{label}, "/owner/repo", issue), expected)
}
func TestUserMention(t *testing.T) {
rendered := RenderMarkdownToHtml(context.Background(), "@no-such-user @mention-user @mention-user")
rendered := newTestRenderUtils().MarkdownToHtml("@no-such-user @mention-user @mention-user")
assert.EqualValues(t, `<p>@no-such-user <a href="/mention-user" rel="nofollow">@mention-user</a> <a href="/mention-user" rel="nofollow">@mention-user</a></p>`, strings.TrimSpace(string(rendered)))
}