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