mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2025-08-15 23:56:50 +02:00
chore: move template context (#8663)
The template module now holds the **Template** context, this makes it possible for (render) function in the template module to access functions and share data between render functions. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8663 Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org> Reviewed-by: Lucas <sclu1034@noreply.codeberg.org> Co-authored-by: Gusted <postmaster@gusted.xyz> Co-committed-by: Gusted <postmaster@gusted.xyz>
This commit is contained in:
parent
61334f7982
commit
d4e4a2a1e3
16 changed files with 88 additions and 70 deletions
23
modules/templates/context.go
Normal file
23
modules/templates/context.go
Normal file
|
@ -0,0 +1,23 @@
|
|||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package templates
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"forgejo.org/modules/translation"
|
||||
)
|
||||
|
||||
type Context struct {
|
||||
context.Context
|
||||
Locale translation.Locale
|
||||
AvatarUtils *AvatarUtils
|
||||
Data map[string]any
|
||||
}
|
||||
|
||||
var _ context.Context = Context{}
|
||||
|
||||
func NewContext(ctx context.Context) *Context {
|
||||
return &Context{Context: ctx}
|
||||
}
|
18
modules/templates/context_test.go
Normal file
18
modules/templates/context_test.go
Normal file
|
@ -0,0 +1,18 @@
|
|||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
package templates
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestContext(t *testing.T) {
|
||||
type ctxKey struct{}
|
||||
|
||||
// Test that the original context is used for its context functions.
|
||||
ctx := NewContext(context.WithValue(t.Context(), ctxKey{}, "there"))
|
||||
assert.Equal(t, "there", ctx.Value(ctxKey{}))
|
||||
}
|
|
@ -22,7 +22,6 @@ import (
|
|||
"forgejo.org/modules/markup"
|
||||
"forgejo.org/modules/markup/markdown"
|
||||
"forgejo.org/modules/setting"
|
||||
"forgejo.org/modules/translation"
|
||||
"forgejo.org/modules/util"
|
||||
)
|
||||
|
||||
|
@ -145,7 +144,7 @@ func RenderRefIssueTitle(ctx context.Context, text string) template.HTML {
|
|||
|
||||
// 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 {
|
||||
func RenderLabel(ctx *Context, label *issues_model.Label) template.HTML {
|
||||
var (
|
||||
archivedCSSClass string
|
||||
textColor = util.ContrastColor(label.Color)
|
||||
|
@ -156,7 +155,7 @@ func RenderLabel(ctx context.Context, locale translation.Locale, label *issues_m
|
|||
|
||||
if label.IsArchived() {
|
||||
archivedCSSClass = "archived-label"
|
||||
description = locale.TrString("repo.issues.archived_label_description", description)
|
||||
description = ctx.Locale.TrString("repo.issues.archived_label_description", description)
|
||||
}
|
||||
|
||||
if labelScope == "" {
|
||||
|
@ -246,7 +245,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, isPull bool) template.HTML {
|
||||
func RenderLabels(ctx *Context, labels []*issues_model.Label, repoLink string, isPull bool) 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
|
||||
|
@ -259,7 +258,7 @@ func RenderLabels(ctx context.Context, locale translation.Locale, labels []*issu
|
|||
issuesOrPull = "pulls"
|
||||
}
|
||||
htmlCode += fmt.Sprintf("<a href='%s/%s?labels=%d' rel='nofollow'>%s</a> ",
|
||||
repoLink, issuesOrPull, label.ID, RenderLabel(ctx, locale, label))
|
||||
repoLink, issuesOrPull, label.ID, RenderLabel(ctx, label))
|
||||
}
|
||||
htmlCode += "</span>"
|
||||
return template.HTML(htmlCode)
|
||||
|
|
|
@ -223,23 +223,26 @@ func TestRenderLabels(t *testing.T) {
|
|||
labelMalicious := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 11})
|
||||
labelArchived := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 12})
|
||||
|
||||
rendered := RenderLabels(db.DefaultContext, tr, []*issues_model.Label{label}, "user2/repo1", false)
|
||||
ctx := NewContext(t.Context())
|
||||
ctx.Locale = tr
|
||||
|
||||
rendered := RenderLabels(ctx, []*issues_model.Label{label}, "user2/repo1", false)
|
||||
assert.Contains(t, rendered, "user2/repo1/issues?labels=1")
|
||||
assert.Contains(t, rendered, ">label1<")
|
||||
assert.Contains(t, rendered, "title='First label'")
|
||||
rendered = RenderLabels(db.DefaultContext, tr, []*issues_model.Label{label}, "user2/repo1", true)
|
||||
rendered = RenderLabels(ctx, []*issues_model.Label{label}, "user2/repo1", true)
|
||||
assert.Contains(t, rendered, "user2/repo1/pulls?labels=1")
|
||||
assert.Contains(t, rendered, ">label1<")
|
||||
rendered = RenderLabels(db.DefaultContext, tr, []*issues_model.Label{labelScoped}, "user2/repo1", false)
|
||||
rendered = RenderLabels(ctx, []*issues_model.Label{labelScoped}, "user2/repo1", false)
|
||||
assert.Contains(t, rendered, "user2/repo1/issues?labels=7")
|
||||
assert.Contains(t, rendered, ">scope<")
|
||||
assert.Contains(t, rendered, ">label1<")
|
||||
rendered = RenderLabels(db.DefaultContext, tr, []*issues_model.Label{labelMalicious}, "user2/repo1", false)
|
||||
rendered = RenderLabels(ctx, []*issues_model.Label{labelMalicious}, "user2/repo1", false)
|
||||
assert.Contains(t, rendered, "user2/repo1/issues?labels=11")
|
||||
assert.Contains(t, rendered, "> <script>malicious</script> <")
|
||||
assert.Contains(t, rendered, ">'?&<")
|
||||
assert.Contains(t, rendered, "title='Malicious label ' <script>malicious</script>'")
|
||||
rendered = RenderLabels(db.DefaultContext, tr, []*issues_model.Label{labelArchived}, "user2/repo1", false)
|
||||
rendered = RenderLabels(ctx, []*issues_model.Label{labelArchived}, "user2/repo1", false)
|
||||
assert.Contains(t, rendered, "user2/repo1/issues?labels=12")
|
||||
assert.Contains(t, rendered, ">archived label<><")
|
||||
assert.Contains(t, rendered, "title='repo.issues.archived_label_description'")
|
||||
|
|
|
@ -15,7 +15,6 @@ import (
|
|||
"forgejo.org/modules/templates"
|
||||
"forgejo.org/modules/web/middleware"
|
||||
"forgejo.org/modules/web/routing"
|
||||
"forgejo.org/services/context"
|
||||
)
|
||||
|
||||
const tplStatus500 base.TplName = "status/500"
|
||||
|
@ -36,8 +35,8 @@ func RenderPanicErrorPage(w http.ResponseWriter, req *http.Request, err any) {
|
|||
httpcache.SetCacheControlInHeader(w.Header(), 0, "no-transform")
|
||||
w.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions)
|
||||
|
||||
tmplCtx := context.TemplateContext{}
|
||||
tmplCtx["Locale"] = middleware.Locale(w, req)
|
||||
tmplCtx := templates.NewContext(req.Context())
|
||||
tmplCtx.Locale = middleware.Locale(w, req)
|
||||
ctxData := middleware.GetContextData(req.Context())
|
||||
|
||||
// This recovery handler could be called without Gitea's web context, so we shouldn't touch that context too much.
|
||||
|
|
|
@ -41,7 +41,7 @@ type Render interface {
|
|||
type Context struct {
|
||||
*Base
|
||||
|
||||
TemplateContext TemplateContext
|
||||
TemplateContext *templates.Context
|
||||
|
||||
Render Render
|
||||
PageData map[string]any // data used by JavaScript modules in one page, it's `window.config.pageData`
|
||||
|
@ -64,8 +64,6 @@ type Context struct {
|
|||
Package *Package
|
||||
}
|
||||
|
||||
type TemplateContext map[string]any
|
||||
|
||||
func init() {
|
||||
web.RegisterResponseStatusProvider[*Context](func(req *http.Request) web_types.ResponseStatusProvider {
|
||||
return req.Context().Value(WebContextKey).(*Context)
|
||||
|
@ -98,10 +96,11 @@ func GetValidateContext(req *http.Request) (ctx *ValidateContext) {
|
|||
return ctx
|
||||
}
|
||||
|
||||
func NewTemplateContextForWeb(ctx *Context) TemplateContext {
|
||||
tmplCtx := NewTemplateContext(ctx)
|
||||
tmplCtx["Locale"] = ctx.Locale
|
||||
tmplCtx["AvatarUtils"] = templates.NewAvatarUtils(ctx)
|
||||
func NewTemplateContextForWeb(ctx *Context) *templates.Context {
|
||||
tmplCtx := templates.NewContext(ctx)
|
||||
tmplCtx.Locale = ctx.Locale
|
||||
tmplCtx.AvatarUtils = templates.NewAvatarUtils(ctx)
|
||||
tmplCtx.Data = ctx.Data
|
||||
return tmplCtx
|
||||
}
|
||||
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package context
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
var _ context.Context = TemplateContext(nil)
|
||||
|
||||
func NewTemplateContext(ctx context.Context) TemplateContext {
|
||||
return TemplateContext{"_ctx": ctx}
|
||||
}
|
||||
|
||||
func (c TemplateContext) parentContext() context.Context {
|
||||
return c["_ctx"].(context.Context)
|
||||
}
|
||||
|
||||
func (c TemplateContext) Deadline() (deadline time.Time, ok bool) {
|
||||
return c.parentContext().Deadline()
|
||||
}
|
||||
|
||||
func (c TemplateContext) Done() <-chan struct{} {
|
||||
return c.parentContext().Done()
|
||||
}
|
||||
|
||||
func (c TemplateContext) Err() error {
|
||||
return c.parentContext().Err()
|
||||
}
|
||||
|
||||
func (c TemplateContext) Value(key any) any {
|
||||
return c.parentContext().Value(key)
|
||||
}
|
|
@ -65,7 +65,7 @@
|
|||
<div class="issue-card-bottom">
|
||||
<div class="labels-list">
|
||||
{{range .Labels}}
|
||||
<a target="_blank" href="{{$.Issue.Repo.Link}}/issues?labels={{.ID}}">{{RenderLabel ctx ctx.Locale .}}</a>
|
||||
<a target="_blank" href="{{$.Issue.Repo.Link}}/issues?labels={{.ID}}">{{RenderLabel ctx .}}</a>
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="issue-card-assignees">
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
{{end}}
|
||||
{{$previousExclusiveScope = $exclusiveScope}}
|
||||
<div class="item issue-action tw-flex tw-justify-between" data-action="toggle" data-element-id="{{.ID}}" data-url="{{$.RepoLink}}/issues/labels">
|
||||
{{if SliceUtils.Contains $.SelLabelIDs .ID}}{{if $exclusiveScope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}{{end}} {{RenderLabel $.Context ctx.Locale .}}
|
||||
{{if SliceUtils.Contains $.SelLabelIDs .ID}}{{if $exclusiveScope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}{{end}} {{RenderLabel ctx .}}
|
||||
{{template "repo/issue/labels/label_archived" .}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
|
|
@ -4,5 +4,5 @@
|
|||
href="{{.root.RepoLink}}/{{if or .root.IsPull .root.Issue.IsPull}}pulls{{else}}issues{{end}}?labels={{.label.ID}}"{{/* FIXME: use .root.Issue.Link or create .root.Link */}}
|
||||
rel="nofollow"
|
||||
>
|
||||
{{- RenderLabel $.Context ctx.Locale .label -}}
|
||||
{{- RenderLabel ctx .label -}}
|
||||
</a>
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
{{range .Labels}}
|
||||
<li class="item">
|
||||
<div class="label-title">
|
||||
{{RenderLabel $.Context ctx.Locale .}}
|
||||
{{RenderLabel ctx .}}
|
||||
{{if .Description}}<br><small class="desc">{{.Description | RenderEmoji $.Context}}</small>{{end}}
|
||||
</div>
|
||||
<div class="label-issues">
|
||||
|
@ -72,7 +72,7 @@
|
|||
{{range .OrgLabels}}
|
||||
<li class="item org-label">
|
||||
<div class="label-title">
|
||||
{{RenderLabel $.Context ctx.Locale .}}
|
||||
{{RenderLabel ctx .}}
|
||||
{{if .Description}}<br><small class="desc">{{.Description | RenderEmoji $.Context}}</small>{{end}}
|
||||
</div>
|
||||
<div class="label-issues">
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
<div class="divider"></div>
|
||||
{{end}}
|
||||
{{$previousExclusiveScope = $exclusiveScope}}
|
||||
<a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" {{if .IsArchived}}data-is-archived{{end}} data-id-selector="#label_{{.ID}}" data-scope="{{$exclusiveScope}}"><span class="octicon-check {{if not .IsChecked}}tw-invisible{{end}}">{{if $exclusiveScope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}</span> {{RenderLabel $.Context ctx.Locale .}}
|
||||
<a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" {{if .IsArchived}}data-is-archived{{end}} data-id-selector="#label_{{.ID}}" data-scope="{{$exclusiveScope}}"><span class="octicon-check {{if not .IsChecked}}tw-invisible{{end}}">{{if $exclusiveScope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}</span> {{RenderLabel ctx .}}
|
||||
{{if .Description}}<br><small class="desc">{{.Description | RenderEmoji $.Context}}</small>{{end}}
|
||||
<p class="archived-label-hint">{{template "repo/issue/labels/label_archived" .}}</p>
|
||||
</a>
|
||||
|
@ -34,7 +34,7 @@
|
|||
<div class="divider"></div>
|
||||
{{end}}
|
||||
{{$previousExclusiveScope = $exclusiveScope}}
|
||||
<a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" {{if .IsArchived}}data-is-archived{{end}} data-id-selector="#label_{{.ID}}" data-scope="{{$exclusiveScope}}"><span class="octicon-check {{if not .IsChecked}}tw-invisible{{end}}">{{if $exclusiveScope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}</span> {{RenderLabel $.Context ctx.Locale .}}
|
||||
<a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" {{if .IsArchived}}data-is-archived{{end}} data-id-selector="#label_{{.ID}}" data-scope="{{$exclusiveScope}}"><span class="octicon-check {{if not .IsChecked}}tw-invisible{{end}}">{{if $exclusiveScope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}</span> {{RenderLabel ctx .}}
|
||||
{{if .Description}}<br><small class="desc">{{.Description | RenderEmoji $.Context}}</small>{{end}}
|
||||
<p class="archived-label-hint">{{template "repo/issue/labels/label_archived" .}}</p>
|
||||
</a>
|
||||
|
|
|
@ -176,11 +176,11 @@
|
|||
<span class="text grey muted-links">
|
||||
{{template "shared/user/authorlink" .Poster}}
|
||||
{{if and .AddedLabels (not .RemovedLabels)}}
|
||||
{{ctx.Locale.TrN (len .AddedLabels) "repo.issues.add_label" "repo.issues.add_labels" (RenderLabels $.Context ctx.Locale .AddedLabels $.RepoLink .Issue.IsPull) $createdStr}}
|
||||
{{ctx.Locale.TrN (len .AddedLabels) "repo.issues.add_label" "repo.issues.add_labels" (RenderLabels ctx .AddedLabels $.RepoLink .Issue.IsPull) $createdStr}}
|
||||
{{else if and (not .AddedLabels) .RemovedLabels}}
|
||||
{{ctx.Locale.TrN (len .RemovedLabels) "repo.issues.remove_label" "repo.issues.remove_labels" (RenderLabels $.Context ctx.Locale .RemovedLabels $.RepoLink .Issue.IsPull) $createdStr}}
|
||||
{{ctx.Locale.TrN (len .RemovedLabels) "repo.issues.remove_label" "repo.issues.remove_labels" (RenderLabels ctx .RemovedLabels $.RepoLink .Issue.IsPull) $createdStr}}
|
||||
{{else}}
|
||||
{{ctx.Locale.Tr "repo.issues.add_remove_labels" (RenderLabels $.Context ctx.Locale .AddedLabels $.RepoLink .Issue.IsPull) (RenderLabels $.Context ctx.Locale .RemovedLabels $.RepoLink .Issue.IsPull) $createdStr}}
|
||||
{{ctx.Locale.Tr "repo.issues.add_remove_labels" (RenderLabels ctx .AddedLabels $.RepoLink .Issue.IsPull) (RenderLabels ctx .RemovedLabels $.RepoLink .Issue.IsPull) $createdStr}}
|
||||
{{end}}
|
||||
</span>
|
||||
</div>
|
||||
|
@ -742,11 +742,11 @@
|
|||
<li>
|
||||
<span class="badge">{{svg "octicon-tag" 20}}</span>
|
||||
{{if and .AddedLabels (not .RemovedLabels)}}
|
||||
{{ctx.Locale.TrN (len .AddedLabels) "repo.issues.add_label" "repo.issues.add_labels" (RenderLabels $.Context ctx.Locale .AddedLabels $.RepoLink .Issue.IsPull) ""}}
|
||||
{{ctx.Locale.TrN (len .AddedLabels) "repo.issues.add_label" "repo.issues.add_labels" (RenderLabels ctx .AddedLabels $.RepoLink .Issue.IsPull) ""}}
|
||||
{{else if and (not .AddedLabels) .RemovedLabels}}
|
||||
{{ctx.Locale.TrN (len .RemovedLabels) "repo.issues.remove_label" "repo.issues.remove_labels" (RenderLabels $.Context ctx.Locale .RemovedLabels $.RepoLink .Issue.IsPull) ""}}
|
||||
{{ctx.Locale.TrN (len .RemovedLabels) "repo.issues.remove_label" "repo.issues.remove_labels" (RenderLabels ctx .RemovedLabels $.RepoLink .Issue.IsPull) ""}}
|
||||
{{else}}
|
||||
{{ctx.Locale.Tr "repo.issues.add_remove_labels" (RenderLabels $.Context ctx.Locale .AddedLabels $.RepoLink .Issue.IsPull) (RenderLabels $.Context ctx.Locale .RemovedLabels $.RepoLink .Issue.IsPull) ""}}
|
||||
{{ctx.Locale.Tr "repo.issues.add_remove_labels" (RenderLabels ctx .AddedLabels $.RepoLink .Issue.IsPull) (RenderLabels ctx .RemovedLabels $.RepoLink .Issue.IsPull) ""}}
|
||||
{{end}}
|
||||
</li>
|
||||
{{end}}
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
{{end}}
|
||||
<span class="labels-list tw-ml-1">
|
||||
{{range .Labels}}
|
||||
<a href="?q={{$.Keyword}}&type={{$.ViewType}}&state={{$.State}}&labels={{.ID}}{{if ne $.listType "milestone"}}&milestone={{$.MilestoneID}}{{end}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}" rel="nofollow">{{RenderLabel $.Context ctx.Locale .}}</a>
|
||||
<a href="?q={{$.Keyword}}&type={{$.ViewType}}&state={{$.State}}&labels={{.ID}}{{if ne $.listType "milestone"}}&milestone={{$.MilestoneID}}{{end}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}" rel="nofollow">{{RenderLabel ctx .}}</a>
|
||||
{{end}}
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
{{svg "octicon-check"}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{RenderLabel $.Context ctx.Locale .}}
|
||||
{{RenderLabel ctx .}}
|
||||
<p class="tw-ml-auto">{{template "repo/issue/labels/label_archived" .}}</p>
|
||||
</a>
|
||||
{{end}}
|
||||
|
|
|
@ -1524,3 +1524,15 @@ func TestIssuePostersSearch(t *testing.T) {
|
|||
assert.EqualValues(t, 1, data.Results[0].UserID)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIssueTimelineLabels(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
req := NewRequest(t, "GET", "/user2/repo1/issues/1")
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
assert.NotContains(t, resp.Body.String(), `status-page-500`)
|
||||
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
filterLinks := htmlDoc.Find(".timeline .labels-list a")
|
||||
assert.Equal(t, 9, filterLinks.Length())
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue