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"
|
||||||
"forgejo.org/modules/markup/markdown"
|
"forgejo.org/modules/markup/markdown"
|
||||||
"forgejo.org/modules/setting"
|
"forgejo.org/modules/setting"
|
||||||
"forgejo.org/modules/translation"
|
|
||||||
"forgejo.org/modules/util"
|
"forgejo.org/modules/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -145,7 +144,7 @@ func RenderRefIssueTitle(ctx context.Context, text string) template.HTML {
|
||||||
|
|
||||||
// RenderLabel renders a label
|
// RenderLabel renders a label
|
||||||
// locale is needed due to an import cycle with our context providing the `Tr` function
|
// 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 (
|
var (
|
||||||
archivedCSSClass string
|
archivedCSSClass string
|
||||||
textColor = util.ContrastColor(label.Color)
|
textColor = util.ContrastColor(label.Color)
|
||||||
|
@ -156,7 +155,7 @@ func RenderLabel(ctx context.Context, locale translation.Locale, label *issues_m
|
||||||
|
|
||||||
if label.IsArchived() {
|
if label.IsArchived() {
|
||||||
archivedCSSClass = "archived-label"
|
archivedCSSClass = "archived-label"
|
||||||
description = locale.TrString("repo.issues.archived_label_description", description)
|
description = ctx.Locale.TrString("repo.issues.archived_label_description", description)
|
||||||
}
|
}
|
||||||
|
|
||||||
if labelScope == "" {
|
if labelScope == "" {
|
||||||
|
@ -246,7 +245,7 @@ func RenderMarkdownToHtml(ctx context.Context, input string) template.HTML { //n
|
||||||
return output
|
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">`
|
htmlCode := `<span class="labels-list">`
|
||||||
for _, label := range labels {
|
for _, label := range labels {
|
||||||
// Protect against nil value in labels - shouldn't happen but would cause a panic if so
|
// 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"
|
issuesOrPull = "pulls"
|
||||||
}
|
}
|
||||||
htmlCode += fmt.Sprintf("<a href='%s/%s?labels=%d' rel='nofollow'>%s</a> ",
|
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>"
|
htmlCode += "</span>"
|
||||||
return template.HTML(htmlCode)
|
return template.HTML(htmlCode)
|
||||||
|
|
|
@ -223,23 +223,26 @@ func TestRenderLabels(t *testing.T) {
|
||||||
labelMalicious := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 11})
|
labelMalicious := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 11})
|
||||||
labelArchived := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 12})
|
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, "user2/repo1/issues?labels=1")
|
||||||
assert.Contains(t, rendered, ">label1<")
|
assert.Contains(t, rendered, ">label1<")
|
||||||
assert.Contains(t, rendered, "title='First label'")
|
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, "user2/repo1/pulls?labels=1")
|
||||||
assert.Contains(t, rendered, ">label1<")
|
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, "user2/repo1/issues?labels=7")
|
||||||
assert.Contains(t, rendered, ">scope<")
|
assert.Contains(t, rendered, ">scope<")
|
||||||
assert.Contains(t, rendered, ">label1<")
|
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, "user2/repo1/issues?labels=11")
|
||||||
assert.Contains(t, rendered, "> <script>malicious</script> <")
|
assert.Contains(t, rendered, "> <script>malicious</script> <")
|
||||||
assert.Contains(t, rendered, ">'?&<")
|
assert.Contains(t, rendered, ">'?&<")
|
||||||
assert.Contains(t, rendered, "title='Malicious label ' <script>malicious</script>'")
|
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, "user2/repo1/issues?labels=12")
|
||||||
assert.Contains(t, rendered, ">archived label<><")
|
assert.Contains(t, rendered, ">archived label<><")
|
||||||
assert.Contains(t, rendered, "title='repo.issues.archived_label_description'")
|
assert.Contains(t, rendered, "title='repo.issues.archived_label_description'")
|
||||||
|
|
|
@ -15,7 +15,6 @@ import (
|
||||||
"forgejo.org/modules/templates"
|
"forgejo.org/modules/templates"
|
||||||
"forgejo.org/modules/web/middleware"
|
"forgejo.org/modules/web/middleware"
|
||||||
"forgejo.org/modules/web/routing"
|
"forgejo.org/modules/web/routing"
|
||||||
"forgejo.org/services/context"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const tplStatus500 base.TplName = "status/500"
|
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")
|
httpcache.SetCacheControlInHeader(w.Header(), 0, "no-transform")
|
||||||
w.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions)
|
w.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions)
|
||||||
|
|
||||||
tmplCtx := context.TemplateContext{}
|
tmplCtx := templates.NewContext(req.Context())
|
||||||
tmplCtx["Locale"] = middleware.Locale(w, req)
|
tmplCtx.Locale = middleware.Locale(w, req)
|
||||||
ctxData := middleware.GetContextData(req.Context())
|
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.
|
// 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 {
|
type Context struct {
|
||||||
*Base
|
*Base
|
||||||
|
|
||||||
TemplateContext TemplateContext
|
TemplateContext *templates.Context
|
||||||
|
|
||||||
Render Render
|
Render Render
|
||||||
PageData map[string]any // data used by JavaScript modules in one page, it's `window.config.pageData`
|
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
|
Package *Package
|
||||||
}
|
}
|
||||||
|
|
||||||
type TemplateContext map[string]any
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
web.RegisterResponseStatusProvider[*Context](func(req *http.Request) web_types.ResponseStatusProvider {
|
web.RegisterResponseStatusProvider[*Context](func(req *http.Request) web_types.ResponseStatusProvider {
|
||||||
return req.Context().Value(WebContextKey).(*Context)
|
return req.Context().Value(WebContextKey).(*Context)
|
||||||
|
@ -98,10 +96,11 @@ func GetValidateContext(req *http.Request) (ctx *ValidateContext) {
|
||||||
return ctx
|
return ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTemplateContextForWeb(ctx *Context) TemplateContext {
|
func NewTemplateContextForWeb(ctx *Context) *templates.Context {
|
||||||
tmplCtx := NewTemplateContext(ctx)
|
tmplCtx := templates.NewContext(ctx)
|
||||||
tmplCtx["Locale"] = ctx.Locale
|
tmplCtx.Locale = ctx.Locale
|
||||||
tmplCtx["AvatarUtils"] = templates.NewAvatarUtils(ctx)
|
tmplCtx.AvatarUtils = templates.NewAvatarUtils(ctx)
|
||||||
|
tmplCtx.Data = ctx.Data
|
||||||
return tmplCtx
|
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="issue-card-bottom">
|
||||||
<div class="labels-list">
|
<div class="labels-list">
|
||||||
{{range .Labels}}
|
{{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}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
<div class="issue-card-assignees">
|
<div class="issue-card-assignees">
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
{{$previousExclusiveScope = $exclusiveScope}}
|
{{$previousExclusiveScope = $exclusiveScope}}
|
||||||
<div class="item issue-action tw-flex tw-justify-between" data-action="toggle" data-element-id="{{.ID}}" data-url="{{$.RepoLink}}/issues/labels">
|
<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" .}}
|
{{template "repo/issue/labels/label_archived" .}}
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{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 */}}
|
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"
|
rel="nofollow"
|
||||||
>
|
>
|
||||||
{{- RenderLabel $.Context ctx.Locale .label -}}
|
{{- RenderLabel ctx .label -}}
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
{{range .Labels}}
|
{{range .Labels}}
|
||||||
<li class="item">
|
<li class="item">
|
||||||
<div class="label-title">
|
<div class="label-title">
|
||||||
{{RenderLabel $.Context ctx.Locale .}}
|
{{RenderLabel ctx .}}
|
||||||
{{if .Description}}<br><small class="desc">{{.Description | RenderEmoji $.Context}}</small>{{end}}
|
{{if .Description}}<br><small class="desc">{{.Description | RenderEmoji $.Context}}</small>{{end}}
|
||||||
</div>
|
</div>
|
||||||
<div class="label-issues">
|
<div class="label-issues">
|
||||||
|
@ -72,7 +72,7 @@
|
||||||
{{range .OrgLabels}}
|
{{range .OrgLabels}}
|
||||||
<li class="item org-label">
|
<li class="item org-label">
|
||||||
<div class="label-title">
|
<div class="label-title">
|
||||||
{{RenderLabel $.Context ctx.Locale .}}
|
{{RenderLabel ctx .}}
|
||||||
{{if .Description}}<br><small class="desc">{{.Description | RenderEmoji $.Context}}</small>{{end}}
|
{{if .Description}}<br><small class="desc">{{.Description | RenderEmoji $.Context}}</small>{{end}}
|
||||||
</div>
|
</div>
|
||||||
<div class="label-issues">
|
<div class="label-issues">
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{$previousExclusiveScope = $exclusiveScope}}
|
{{$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}}
|
{{if .Description}}<br><small class="desc">{{.Description | RenderEmoji $.Context}}</small>{{end}}
|
||||||
<p class="archived-label-hint">{{template "repo/issue/labels/label_archived" .}}</p>
|
<p class="archived-label-hint">{{template "repo/issue/labels/label_archived" .}}</p>
|
||||||
</a>
|
</a>
|
||||||
|
@ -34,7 +34,7 @@
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{$previousExclusiveScope = $exclusiveScope}}
|
{{$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}}
|
{{if .Description}}<br><small class="desc">{{.Description | RenderEmoji $.Context}}</small>{{end}}
|
||||||
<p class="archived-label-hint">{{template "repo/issue/labels/label_archived" .}}</p>
|
<p class="archived-label-hint">{{template "repo/issue/labels/label_archived" .}}</p>
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -176,11 +176,11 @@
|
||||||
<span class="text grey muted-links">
|
<span class="text grey muted-links">
|
||||||
{{template "shared/user/authorlink" .Poster}}
|
{{template "shared/user/authorlink" .Poster}}
|
||||||
{{if and .AddedLabels (not .RemovedLabels)}}
|
{{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}}
|
{{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}}
|
{{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}}
|
{{end}}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -742,11 +742,11 @@
|
||||||
<li>
|
<li>
|
||||||
<span class="badge">{{svg "octicon-tag" 20}}</span>
|
<span class="badge">{{svg "octicon-tag" 20}}</span>
|
||||||
{{if and .AddedLabels (not .RemovedLabels)}}
|
{{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}}
|
{{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}}
|
{{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}}
|
{{end}}
|
||||||
</li>
|
</li>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
<span class="labels-list tw-ml-1">
|
<span class="labels-list tw-ml-1">
|
||||||
{{range .Labels}}
|
{{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}}
|
{{end}}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -42,7 +42,7 @@
|
||||||
{{svg "octicon-check"}}
|
{{svg "octicon-check"}}
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
{{RenderLabel $.Context ctx.Locale .}}
|
{{RenderLabel ctx .}}
|
||||||
<p class="tw-ml-auto">{{template "repo/issue/labels/label_archived" .}}</p>
|
<p class="tw-ml-auto">{{template "repo/issue/labels/label_archived" .}}</p>
|
||||||
</a>
|
</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
|
@ -1524,3 +1524,15 @@ func TestIssuePostersSearch(t *testing.T) {
|
||||||
assert.EqualValues(t, 1, data.Results[0].UserID)
|
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