Skip to content

Improve labels-list rendering #34846

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 36 commits into from
Jun 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
64bf916
Unify gap in issue sidebar labels-list
silverwind Jun 24, 2025
dff919f
enhancements
silverwind Jun 25, 2025
41f37c1
add comment
silverwind Jun 25, 2025
b616c25
fix bug
silverwind Jun 25, 2025
5c3e8ba
fix
wxiaoguang Jun 25, 2025
5902393
Merge branch 'main' into labels-list-gap
wxiaoguang Jun 25, 2025
e470d00
unify issue content right lists
wxiaoguang Jun 25, 2025
d9c97d9
adjust card avatar size
wxiaoguang Jun 25, 2025
039cdfe
fix issues
silverwind Jun 25, 2025
3bd9cbd
cleanup
silverwind Jun 25, 2025
7094383
Merge branch 'main' into labels-list-gap
silverwind Jun 25, 2025
a2a60c6
update comment
silverwind Jun 25, 2025
d7b255a
restore flex-wrap
silverwind Jun 25, 2025
5394119
use contain:paint instead of overflow:hidden, remove line-height
silverwind Jun 25, 2025
5a4841a
use different vertical gap
silverwind Jun 25, 2025
4162213
Merge branch 'main' into labels-list-gap
wxiaoguang Jun 26, 2025
46a6987
fix alignment and ellipsis
wxiaoguang Jun 26, 2025
f74c643
fine tune
wxiaoguang Jun 26, 2025
a7ddce1
fix card
wxiaoguang Jun 26, 2025
1466310
fix comment label list
wxiaoguang Jun 26, 2025
b890ad0
fix labels list page
wxiaoguang Jun 26, 2025
ef76480
add comment
wxiaoguang Jun 26, 2025
f8836e2
fine tune
wxiaoguang Jun 26, 2025
16b5f3e
align issue list checkbox
wxiaoguang Jun 26, 2025
7f2be67
fix labels
wxiaoguang Jun 26, 2025
b918300
fix label margin
wxiaoguang Jun 26, 2025
2efd951
move label code to "label.css"
wxiaoguang Jun 26, 2025
4085d02
clean up border radius
wxiaoguang Jun 26, 2025
0f28bc3
Merge branch 'main' into labels-list-gap
wxiaoguang Jun 26, 2025
4adf1e3
use "a" tag for label directly when render
wxiaoguang Jun 26, 2025
760e23b
fix lint
wxiaoguang Jun 26, 2025
8bedfb6
add --gap-label variable
silverwind Jun 26, 2025
eb2a642
fix typo
wxiaoguang Jun 26, 2025
43e7745
add tests
wxiaoguang Jun 26, 2025
0f1ea9a
improve gap variables
wxiaoguang Jun 27, 2025
15cf513
Merge branch 'main' into labels-list-gap
wxiaoguang Jun 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions modules/htmlutil/html.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ func HTMLFormat(s template.HTML, rawArgs ...any) template.HTML {
// for most basic types (including template.HTML which is safe), just do nothing and use it
case string:
args[i] = template.HTMLEscapeString(v)
case template.URL:
args[i] = template.HTMLEscapeString(string(v))
case fmt.Stringer:
args[i] = template.HTMLEscapeString(v.String())
default:
Expand Down
9 changes: 9 additions & 0 deletions modules/htmlutil/html_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@ import (
"github.com/stretchr/testify/assert"
)

type testStringer struct{}

func (t testStringer) String() string {
return "&StringMethod"
}

func TestHTMLFormat(t *testing.T) {
assert.Equal(t, template.HTML("<a>&lt; < 1</a>"), HTMLFormat("<a>%s %s %d</a>", "<", template.HTML("<"), 1))
assert.Equal(t, template.HTML("%!s(<nil>)"), HTMLFormat("%s", nil))
assert.Equal(t, template.HTML("&lt;&gt;"), HTMLFormat("%s", template.URL("<>")))
assert.Equal(t, template.HTML("&amp;StringMethod &amp;StringMethod"), HTMLFormat("%s %s", testStringer{}, &testStringer{}))
}
41 changes: 30 additions & 11 deletions modules/templates/util_render.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,23 @@ func (ut *RenderUtils) RenderIssueSimpleTitle(text string) template.HTML {
return ret
}

// RenderLabel renders a label
func (ut *RenderUtils) RenderLabelWithLink(label *issues_model.Label, link any) template.HTML {
var attrHref template.HTML
switch link.(type) {
case template.URL, string:
attrHref = htmlutil.HTMLFormat(`href="%s"`, link)
default:
panic(fmt.Sprintf("unexpected type %T for link", link))
}
return ut.renderLabelWithTag(label, "a", attrHref)
}

func (ut *RenderUtils) RenderLabel(label *issues_model.Label) template.HTML {
return ut.renderLabelWithTag(label, "span", "")
}

// RenderLabel renders a label
func (ut *RenderUtils) renderLabelWithTag(label *issues_model.Label, tagName, tagAttrs template.HTML) template.HTML {
locale := ut.ctx.Value(translation.ContextKey).(translation.Locale)
var extraCSSClasses string
textColor := util.ContrastColor(label.Color)
Expand All @@ -137,8 +152,8 @@ func (ut *RenderUtils) RenderLabel(label *issues_model.Label) template.HTML {

if labelScope == "" {
// Regular label
return htmlutil.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, ut.RenderEmoji(label.Name))
return htmlutil.HTMLFormat(`<%s %s class="ui label %s" style="color: %s !important; background-color: %s !important;" data-tooltip-content title="%s"><span class="gt-ellipsis">%s</span></%s>`,
tagName, tagAttrs, extraCSSClasses, textColor, label.Color, descriptionText, ut.RenderEmoji(label.Name), tagName)
}

// Scoped label
Expand All @@ -152,7 +167,7 @@ func (ut *RenderUtils) RenderLabel(label *issues_model.Label) template.HTML {
// 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.
// Compute the 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)

Expand All @@ -173,26 +188,29 @@ func (ut *RenderUtils) RenderLabel(label *issues_model.Label) template.HTML {

if label.ExclusiveOrder > 0 {
// <scope> | <label> | <order>
return htmlutil.HTMLFormat(`<span class="ui label %s scope-parent" data-tooltip-content title="%s">`+
return htmlutil.HTMLFormat(`<%s %s class="ui label %s scope-parent" data-tooltip-content title="%s">`+
`<div class="ui label scope-left" style="color: %s !important; background-color: %s !important">%s</div>`+
`<div class="ui label scope-middle" style="color: %s !important; background-color: %s !important">%s</div>`+
`<div class="ui label scope-right">%d</div>`+
`</span>`,
`</%s>`,
tagName, tagAttrs,
extraCSSClasses, descriptionText,
textColor, scopeColor, scopeHTML,
textColor, itemColor, itemHTML,
label.ExclusiveOrder)
label.ExclusiveOrder,
tagName)
}

// <scope> | <label>
return htmlutil.HTMLFormat(`<span class="ui label %s scope-parent" data-tooltip-content title="%s">`+
return htmlutil.HTMLFormat(`<%s %s class="ui label %s scope-parent" data-tooltip-content title="%s">`+
`<div class="ui label scope-left" style="color: %s !important; background-color: %s !important">%s</div>`+
`<div class="ui label scope-right" style="color: %s !important; background-color: %s !important">%s</div>`+
`</span>`,
`</%s>`,
tagName, tagAttrs,
extraCSSClasses, descriptionText,
textColor, scopeColor, scopeHTML,
textColor, itemColor, itemHTML,
)
tagName)
}

// RenderEmoji renders html text with emoji post processors
Expand Down Expand Up @@ -235,7 +253,8 @@ func (ut *RenderUtils) RenderLabels(labels []*issues_model.Label, repoLink strin
if label == nil {
continue
}
htmlCode += fmt.Sprintf(`<a href="%s?labels=%d">%s</a>`, baseLink, label.ID, ut.RenderLabel(label))
link := fmt.Sprintf("%s?labels=%d", baseLink, label.ID)
htmlCode += string(ut.RenderLabelWithLink(label, template.URL(link)))
}
htmlCode += "</span>"
return template.HTML(htmlCode)
Expand Down
11 changes: 11 additions & 0 deletions modules/templates/util_render_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,17 @@ func TestRenderLabels(t *testing.T) {
issue = &issues.Issue{IsPull: true}
expected = `/owner/repo/pulls?labels=123`
assert.Contains(t, ut.RenderLabels([]*issues.Label{label}, "/owner/repo", issue), expected)

expectedLabel := `<a href="&lt;&gt;" class="ui label " style="color: #fff !important; background-color: label-color !important;" data-tooltip-content title=""><span class="gt-ellipsis">label-name</span></a>`
assert.Equal(t, expectedLabel, string(ut.RenderLabelWithLink(label, "<>")))
assert.Equal(t, expectedLabel, string(ut.RenderLabelWithLink(label, template.URL("<>"))))

label = &issues.Label{ID: 123, Name: "</>", Exclusive: true}
expectedLabel = `<a href="" class="ui label scope-parent" data-tooltip-content title=""><div class="ui label scope-left" style="color: #fff !important; background-color: #000000 !important">&lt;</div><div class="ui label scope-right" style="color: #fff !important; background-color: #000000 !important">&gt;</div></a>`
assert.Equal(t, expectedLabel, string(ut.RenderLabelWithLink(label, "")))
label = &issues.Label{ID: 123, Name: "</>", Exclusive: true, ExclusiveOrder: 1}
expectedLabel = `<a href="" class="ui label scope-parent" data-tooltip-content title=""><div class="ui label scope-left" style="color: #fff !important; background-color: #000000 !important">&lt;</div><div class="ui label scope-middle" style="color: #fff !important; background-color: #000000 !important">&gt;</div><div class="ui label scope-right">1</div></a>`
assert.Equal(t, expectedLabel, string(ut.RenderLabelWithLink(label, "")))
}

func TestUserMention(t *testing.T) {
Expand Down
4 changes: 2 additions & 2 deletions templates/repo/home_sidebar_bottom.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
</a>
</div>
<div class="flex-item">
<div class="flex-item-icon">
{{svg "octicon-tag" 16}}
<div class="flex-item-leading">
<div class="tw-mt-1">{{svg "octicon-tag" 16}}</div>
</div>
<div class="flex-item-main">
<div class="flex-item-header">
Expand Down
15 changes: 10 additions & 5 deletions templates/repo/issue/card.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -63,16 +63,21 @@

{{if or .Labels .Assignees}}
<div class="issue-card-bottom">
<div class="labels-list">
{{range .Labels}}
<a target="_blank" href="{{$.Issue.Repo.Link}}/issues?labels={{.ID}}">{{ctx.RenderUtils.RenderLabel .}}</a>
{{/* the labels container must always be present, to help the assignees list right-aligned */}}
<div class="issue-card-bottom-part labels-list">
{{range $label := .Labels}}
{{$link := print $.Issue.Repo.Link "/issues"}}
{{$link = QueryBuild $link "labels" $label.ID}}
{{ctx.RenderUtils.RenderLabelWithLink $label $link}}
{{end}}
</div>
<div class="issue-card-assignees">
{{if .Assignees}}
<div class="issue-card-bottom-part tw-justify-end">
{{range .Assignees}}
<a target="_blank" href="{{.HomeLink}}" data-tooltip-content="{{ctx.Locale.Tr "repo.projects.column.assigned_to"}} {{.Name}}">{{ctx.AvatarUtils.Avatar . 28}}</a>
<a target="_blank" href="{{.HomeLink}}" data-tooltip-content="{{ctx.Locale.Tr "repo.projects.column.assigned_to"}} {{.Name}}">{{ctx.AvatarUtils.Avatar . 24}}</a>
{{end}}
</div>
{{end}}
</div>
{{end}}
{{end}}
6 changes: 2 additions & 4 deletions templates/repo/issue/labels/label_list.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
<div class="divider"></div>
{{end}}

<ul class="issue-label-list">
<ul class="issue-label-list muted-links">
{{$canEditLabel := and (not $.PageIsOrgSettingsLabels) (not $.Repository.IsArchived) (or $.CanWriteIssues $.CanWritePulls)}}
{{$canEditLabel = or $canEditLabel $.PageIsOrgSettingsLabels}}
{{range .Labels}}
Expand All @@ -42,9 +42,8 @@
<a class="open-issues" href="{{$.RepoLink}}/issues?labels={{.ID}}">{{svg "octicon-issue-opened"}} {{ctx.Locale.Tr "repo.issues.label_open_issues" .NumOpenIssues}}</a>
{{end}}
</div>
<div class="label-operation tw-flex">
<div class="label-operation">
{{template "repo/issue/labels/label_archived" .}}
<div class="tw-flex tw-ml-auto">
{{if $canEditLabel}}
<a class="edit-label-button" href="#"
data-label-id="{{.ID}}" data-label-name="{{.Name}}" data-label-color="{{.Color}}"
Expand All @@ -57,7 +56,6 @@
data-modal-confirm-content="{{ctx.Locale.Tr "repo.issues.label_deletion_desc"}}"
>{{svg "octicon-trash"}} {{ctx.Locale.Tr "repo.issues.label_delete"}}</a>
{{end}}
</div>
</div>
</li>
{{end}}
Expand Down
2 changes: 1 addition & 1 deletion templates/repo/issue/sidebar/assignee_list.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
</div>
</div>
</div>
<div class="ui list muted-links flex-items-block tw-flex tw-flex-col tw-gap-2">
<div class="ui relaxed list muted-links flex-items-block">
<span class="item empty-list {{if $issueAssignees}}tw-hidden{{end}}">{{ctx.Locale.Tr "repo.issues.new.no_assignees"}}</span>
{{range $issueAssignees}}
<a class="item" href="{{$pageMeta.RepoLink}}/{{if $pageMeta.IsPullRequest}}pulls{{else}}issues{{end}}?assignee={{.ID}}">
Expand Down
2 changes: 1 addition & 1 deletion templates/repo/issue/sidebar/label_list.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
</div>
</div>

<div class="ui list labels-list tw-my-2 tw-flex tw-gap-2">
<div class="ui list labels-list">
<span class="item empty-list {{if $data.SelectedLabelIDs}}tw-hidden{{end}}">{{ctx.Locale.Tr "repo.issues.new.no_label"}}</span>
{{range $data.AllLabels}}
{{if .IsChecked}}
Expand Down
2 changes: 1 addition & 1 deletion templates/repo/issue/sidebar/reviewer_list.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
</div>
</div>

<div class="ui relaxed list flex-items-block tw-my-4">
<div class="ui relaxed list flex-items-block">
<span class="item empty-list {{if or $data.OriginalReviews $data.CurrentPullReviewers}}tw-hidden{{end}}">
{{ctx.Locale.Tr "repo.issues.new.no_reviewers"}}
</span>
Expand Down
2 changes: 1 addition & 1 deletion templates/repo/issue/view_content/comments.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@
</div>
{{else if eq .Type 7}}
{{if or .AddedLabels .RemovedLabels}}
<div class="timeline-item event" id="{{.HashTag}}">
<div class="timeline-item event with-labels-list-inline" id="{{.HashTag}}">
<span class="badge">{{svg "octicon-tag"}}</span>
{{template "shared/user/avatarlink" dict "user" .Poster}}
<span class="text grey muted-links">
Expand Down
17 changes: 10 additions & 7 deletions templates/shared/issuelist.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@
{{range .Issues}}
<div class="flex-item">

<div class="flex-item-icon">
{{if $.CanWriteIssuesOrPulls}}
<input type="checkbox" autocomplete="off" class="issue-checkbox tw-mr-4" data-issue-id={{.ID}} aria-label="{{ctx.Locale.Tr "repo.issues.action_check"}} &quot;{{.Title}}&quot;">
{{end}}
{{template "shared/issueicon" .}}
<div class="flex-item-leading">
{{/* using some tw helpers is the only way to align the checkbox */}}
<div class="flex-text-inline tw-mt-[2px]">
{{if $.CanWriteIssuesOrPulls}}
<input type="checkbox" autocomplete="off" class="issue-checkbox tw-mr-[14px]" data-issue-id={{.ID}} aria-label="{{ctx.Locale.Tr "repo.issues.action_check"}} &quot;{{.Title}}&quot;">
{{end}}
{{template "shared/issueicon" .}}
</div>
</div>

<div class="flex-item-main">
Expand All @@ -19,7 +22,7 @@
{{template "repo/commit_statuses" dict "Status" (index $.CommitLastStatus .PullRequest.ID) "Statuses" (index $.CommitStatuses .PullRequest.ID)}}
{{end}}
{{end}}
<span class="labels-list tw-ml-1">
<span class="labels-list">
{{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}}">{{ctx.RenderUtils.RenderLabel .}}</a>
{{end}}
Expand All @@ -32,7 +35,7 @@
</div>
{{end}}
</div>
<div class="flex-item-body">
<div class="flex-item-body tw-mt-1">
<a class="index" href="{{if .Link}}{{.Link}}{{else}}{{$.Link}}/{{.Index}}{{end}}">
{{if eq $.listType "dashboard"}}
{{.Repo.FullName}}#{{.Index}}
Expand Down
11 changes: 8 additions & 3 deletions web_src/css/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@
/* z-index */
--z-index-modal: 1001; /* modal dialog, hard-coded from Fomantic modal.css */
--z-index-toast: 1002; /* should be larger than modal */

--font-size-label: 12px; /* font size of individual labels */

--gap-inline: 0.25rem; /* gap for inline texts and elements, for example: the spaces for sentence with labels, button text, etc */
--gap-block: 0.25rem; /* gap for element blocks, for example: spaces between buttons, menu image & title, header icon & title etc */
}

@media (min-width: 768px) and (max-width: 1200px) {
Expand Down Expand Up @@ -1093,7 +1098,7 @@ table th[data-sortt-desc] .svg {
.flex-text-inline {
display: inline-flex;
align-items: center;
gap: .25rem;
gap: var(--gap-inline);
vertical-align: middle;
min-width: 0; /* make ellipsis work */
}
Expand Down Expand Up @@ -1121,7 +1126,7 @@ table th[data-sortt-desc] .svg {
.flex-text-block {
display: flex;
align-items: center;
gap: .5rem;
gap: var(--gap-block);
min-width: 0;
}

Expand All @@ -1136,7 +1141,7 @@ the "!important" is necessary to override Fomantic UI menu item styles, meanwhil
.ui.dropdown .menu.flex-items-menu > .item:not(.hidden, .filtered, .tw-hidden) {
display: flex !important;
align-items: center;
gap: .5rem;
gap: var(--gap-block);
min-width: 0;
}
.ui.dropdown .menu.flex-items-menu > .item img,
Expand Down
Loading