Skip to content

Commit 9172675

Browse files
committed
Label display improvements for scopes and consistency
* Show scoped label as "Scope > Name", with scope prefix slightly dimmed * Show label with background color also in assignment and filter menus for consistency, in particular for scoped label rendering. * Tweak light/dark color text detection slight, to make it white on some background colors that I found otherwise hard to read. * Don't use exactly black/white text colors to look a bit nicer * Show label in labels editing list the same size as elsewhere, and without tag icon. To give a better preview of how it will actually look. * Increase height of menus to show more labels (and projects, milestones, ..). Showing only 3-4 labels as before leads to a lot of scrolling. * Refactor code so label rendering is done in a single helper function.
1 parent dd45362 commit 9172675

File tree

14 files changed

+64
-77
lines changed

14 files changed

+64
-77
lines changed

Diff for: models/issues/label.go

+12-39
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ package issues
77
import (
88
"context"
99
"fmt"
10-
"html/template"
11-
"math"
1210
"regexp"
1311
"strconv"
1412
"strings"
@@ -163,49 +161,24 @@ func (label *Label) BelongsToRepo() bool {
163161
return label.RepoID > 0
164162
}
165163

166-
// SrgbToLinear converts a component of an sRGB color to its linear intensity
167-
// See: https://en.wikipedia.org/wiki/SRGB#The_reverse_transformation_(sRGB_to_CIE_XYZ)
168-
func SrgbToLinear(color uint8) float64 {
169-
flt := float64(color) / 255
170-
if flt <= 0.04045 {
171-
return flt / 12.92
172-
}
173-
return math.Pow((flt+0.055)/1.055, 2.4)
174-
}
175-
176-
// Luminance returns the luminance of an sRGB color
177-
func Luminance(color uint32) float64 {
178-
r := SrgbToLinear(uint8(0xFF & (color >> 16)))
179-
g := SrgbToLinear(uint8(0xFF & (color >> 8)))
180-
b := SrgbToLinear(uint8(0xFF & color))
181-
182-
// luminance ratios for sRGB
183-
return 0.2126*r + 0.7152*g + 0.0722*b
184-
}
185-
186-
// LuminanceThreshold is the luminance at which white and black appear to have the same contrast
187-
// i.e. x such that 1.05 / (x + 0.05) = (x + 0.05) / 0.05
188-
// i.e. math.Sqrt(1.05*0.05) - 0.05
189-
const LuminanceThreshold float64 = 0.179
190-
191-
// ForegroundColor calculates the text color for labels based
192-
// on their background color.
193-
func (label *Label) ForegroundColor() template.CSS {
164+
// Determine if label text should be light or dark to be readable on
165+
// background color.
166+
func (label *Label) UseLightTextColor() bool {
194167
if strings.HasPrefix(label.Color, "#") {
195168
if color, err := strconv.ParseUint(label.Color[1:], 16, 64); err == nil {
196-
// NOTE: see web_src/js/components/ContextPopup.vue for similar implementation
197-
luminance := Luminance(uint32(color))
169+
// sRGB color space luminance
170+
r := float64(uint8(0xFF & (uint32(color) >> 16)))
171+
g := float64(uint8(0xFF & (uint32(color) >> 8)))
172+
b := float64(uint8(0xFF & uint32(color)))
198173

199-
// prefer white or black based upon contrast
200-
if luminance < LuminanceThreshold {
201-
return template.CSS("#fff")
202-
}
203-
return template.CSS("#000")
174+
luminance := (0.299*r + 0.587*g + 0.114*b) / 255
175+
176+
// NOTE: see web_src/js/components/ContextPopup.vue for similar implementation
177+
return luminance < 0.5
204178
}
205179
}
206180

207-
// default to black
208-
return template.CSS("#000")
181+
return false
209182
}
210183

211184
// Return scope substring of label name, or empty string if none exists.

Diff for: models/issues/label_test.go

+3-4
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
package issues_test
55

66
import (
7-
"html/template"
87
"testing"
98

109
"code.gitea.io/gitea/models/db"
@@ -25,13 +24,13 @@ func TestLabel_CalOpenIssues(t *testing.T) {
2524
assert.EqualValues(t, 2, label.NumOpenIssues)
2625
}
2726

28-
func TestLabel_ForegroundColor(t *testing.T) {
27+
func TestLabel_TextColor(t *testing.T) {
2928
assert.NoError(t, unittest.PrepareTestDatabase())
3029
label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1})
31-
assert.Equal(t, template.CSS("#000"), label.ForegroundColor())
30+
assert.False(t, label.UseLightTextColor())
3231

3332
label = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 2})
34-
assert.Equal(t, template.CSS("#fff"), label.ForegroundColor())
33+
assert.True(t, label.UseLightTextColor())
3534
}
3635

3736
func TestLabel_Scope(t *testing.T) {

Diff for: modules/templates/helper.go

+29-2
Original file line numberDiff line numberDiff line change
@@ -379,15 +379,18 @@ func NewFuncMap() []template.FuncMap {
379379
// the table is NOT sorted with this header
380380
return ""
381381
},
382+
"RenderLabel": func(label *issues_model.Label) template.HTML {
383+
return template.HTML(RenderLabel(label))
384+
},
382385
"RenderLabels": func(labels []*issues_model.Label, repoLink string) template.HTML {
383386
htmlCode := `<span class="labels-list">`
384387
for _, label := range labels {
385388
// Protect against nil value in labels - shouldn't happen but would cause a panic if so
386389
if label == nil {
387390
continue
388391
}
389-
htmlCode += fmt.Sprintf("<a href='%s/issues?labels=%d' class='ui label' style='color: %s !important; background-color: %s !important' title='%s'>%s</a> ",
390-
repoLink, label.ID, label.ForegroundColor(), label.Color, html.EscapeString(label.Description), RenderEmoji(label.Name))
392+
htmlCode += fmt.Sprintf("<a href='%s/issues?labels=%d'>%s</a> ",
393+
repoLink, label.ID, RenderLabel(label))
391394
}
392395
htmlCode += "</span>"
393396
return template.HTML(htmlCode)
@@ -795,6 +798,30 @@ func RenderIssueTitle(ctx context.Context, text, urlPrefix string, metas map[str
795798
return template.HTML(renderedText)
796799
}
797800

801+
// RenderLabel renders a label
802+
func RenderLabel(label *issues_model.Label) string {
803+
labelScope := label.Scope()
804+
805+
textColor := "#000"
806+
scopeTextColor := "#444"
807+
if label.UseLightTextColor() {
808+
textColor = "#ddd"
809+
scopeTextColor = "#bbb"
810+
}
811+
812+
var text string
813+
if labelScope == "" {
814+
text = string(RenderEmoji(label.Name))
815+
} else {
816+
scopeText := strings.ReplaceAll(labelScope, "::", " \u203A ")
817+
itemText := label.Name[len(labelScope):]
818+
text = fmt.Sprintf("<span style='color: %s !important;'>%s</span> %s", scopeTextColor, RenderEmoji(scopeText), RenderEmoji(itemText))
819+
}
820+
821+
return fmt.Sprintf("<div class='ui label' style='color: %s!important; background-color: %s !important' title='%s'>%s</div>",
822+
textColor, label.Color, emoji.ReplaceAliases(label.Description), text)
823+
}
824+
798825
// RenderEmoji renders html text with emoji post processors
799826
func RenderEmoji(text string) template.HTML {
800827
renderedText, err := markup.RenderEmoji(template.HTMLEscapeString(text))

Diff for: templates/projects/view.tmpl

+1-1
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@
234234
{{if or .Labels .Assignees}}
235235
<div class="extra content labels-list p-0 pt-2">
236236
{{range .Labels}}
237-
<a class="ui label" target="_blank" href="{{$.RepoLink}}/issues?labels={{.ID}}" style="color: {{.ForegroundColor}}; background-color: {{.Color}};" title="{{.Description | RenderEmojiPlain}}">{{.Name | RenderEmoji}}</a>
237+
<a target="_blank" href="{{$.RepoLink}}/issues?labels={{.ID}}">{{RenderLabel .}}</a>
238238
{{end}}
239239
<div class="right floated">
240240
{{range .Assignees}}

Diff for: templates/repo/issue/labels/label.tmpl

+2-4
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
<a
2-
class="ui label item {{if not .label.IsChecked}}hide{{end}}"
2+
class="item {{if not .label.IsChecked}}hide{{end}}"
33
id="label_{{.label.ID}}"
44
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 */}}
5-
style="color: {{.label.ForegroundColor}}; background-color: {{.label.Color}}"
6-
title="{{.label.Description | RenderEmojiPlain}}"
75
>
8-
{{.label.Name | RenderEmoji}}
6+
{{RenderLabel .label}}
97
</a>

Diff for: templates/repo/issue/labels/label_list.tmpl

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
<li class="item">
3232
<div class="ui grid middle aligned">
3333
<div class="four wide column">
34-
<div class="ui label" style="color: {{.ForegroundColor}}; background-color: {{.Color}}">{{svg "octicon-tag"}} {{.Name | RenderEmoji}}</div>
34+
{{RenderLabel .}}
3535
</div>
3636
<div class="six wide column">
3737
<div class="ui">
@@ -74,7 +74,7 @@
7474
<li class="item">
7575
<div class="ui grid middle aligned">
7676
<div class="three wide column">
77-
<div class="ui label" style="color: {{.ForegroundColor}}; background-color: {{.Color}}">{{svg "octicon-tag"}} {{.Name | RenderEmoji}}</div>
77+
{{RenderLabel .}}
7878
</div>
7979
<div class="seven wide column">
8080
<div class="ui">

Diff for: templates/repo/issue/list.tmpl

+2-2
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
<div class="ui divider"></div>
5959
{{end}}
6060
{{$previousScope = $scope}}
61-
<a class="item label-filter-item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.QueryString}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}" data-label-id="{{.ID}}">{{if .IsExcluded}}{{svg "octicon-circle-slash"}}{{else if .IsSelected}}{{if $scope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}{{end}}<span class="label color" style="background-color: {{.Color}}"></span> {{.Name | RenderEmoji}}</a>
61+
<a class="item label-filter-item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.QueryString}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}" data-label-id="{{.ID}}">{{if .IsExcluded}}{{svg "octicon-circle-slash"}}{{else if .IsSelected}}{{if $scope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}{{end}} {{RenderLabel .}}</a>
6262
{{end}}
6363
</div>
6464
</div>
@@ -192,7 +192,7 @@
192192
{{end}}
193193
{{$previousScope = $scope}}
194194
<div class="item issue-action" data-action="toggle" data-element-id="{{.ID}}" data-url="{{$.RepoLink}}/issues/labels">
195-
{{if contain $.SelLabelIDs .ID}}{{if $scope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}{{end}}<span class="label color" style="background-color: {{.Color}}"></span> {{.Name | RenderEmoji}}
195+
{{if contain $.SelLabelIDs .ID}}{{if $scope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}{{end}} {{RenderLabel .}}
196196
</div>
197197
{{end}}
198198
</div>

Diff for: templates/repo/issue/milestone_issues.tmpl

+2-2
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
<span class="info">{{.locale.Tr "repo.issues.filter_label_exclude" | Safe}}</span>
5959
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_label_no_select"}}</a>
6060
{{range .Labels}}
61-
<a class="item label-filter-item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.QueryString}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}" data-label-id="{{.ID}}">{{if .IsExcluded}}{{svg "octicon-circle-slash"}}{{else if contain $.SelLabelIDs .ID}}{{svg "octicon-check"}}{{end}}<span class="label color" style="background-color: {{.Color}}"></span> {{.Name | RenderEmoji}}</a>
61+
<a class="item label-filter-item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.QueryString}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}" data-label-id="{{.ID}}">{{if .IsExcluded}}{{svg "octicon-circle-slash"}}{{else if contain $.SelLabelIDs .ID}}{{svg "octicon-check"}}{{end}} {{RenderLabel .}}</a>
6262
{{end}}
6363
</div>
6464
</div>
@@ -161,7 +161,7 @@
161161
<div class="menu">
162162
{{range .Labels}}
163163
<div class="item issue-action" data-action="toggle" data-element-id="{{.ID}}" data-url="{{$.RepoLink}}/issues/labels">
164-
{{if contain $.SelLabelIDs .ID}}{{svg "octicon-check"}}{{end}}<span class="label color" style="background-color: {{.Color}}"></span> {{.Name | RenderEmoji}}
164+
{{if contain $.SelLabelIDs .ID}}{{svg "octicon-check"}}{{end}} {{RenderLabel .}}
165165
</div>
166166
{{end}}
167167
</div>

Diff for: templates/repo/issue/new_form.tmpl

+2-2
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@
6060
<div class="ui divider"></div>
6161
{{end}}
6262
{{$previousScope = $scope}}
63-
<a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" data-id-selector="#label_{{.ID}}" data-scope="{{.Scope}}"><span class="octicon-check {{if not .IsChecked}}invisible{{end}}">{{if $scope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}</span><span class="label color" style="background-color: {{.Color}}"></span> {{.Name | RenderEmoji}}
63+
<a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" data-id-selector="#label_{{.ID}}" data-scope="{{.Scope}}"><span class="octicon-check {{if not .IsChecked}}invisible{{end}}">{{if $scope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}</span>&nbsp;&nbsp;{{RenderLabel .}}
6464
{{if .Description}}<br><small class="desc">{{.Description | RenderEmoji}}</small>{{end}}</a>
6565
{{end}}
6666

@@ -72,7 +72,7 @@
7272
<div class="ui divider"></div>
7373
{{end}}
7474
{{$previousScope = $scope}}
75-
<a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" data-id-selector="#label_{{.ID}}" data-scope="{{.Scope}}"><span class="octicon-check {{if not .IsChecked}}invisible{{end}}">{{if $scope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}</span><span class="label color" style="background-color: {{.Color}}"></span> {{.Name | RenderEmoji}}
75+
<a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" data-id-selector="#label_{{.ID}}" data-scope="{{.Scope}}"><span class="octicon-check {{if not .IsChecked}}invisible{{end}}">{{if $scope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}</span>&nbsp;&nbsp;{{RenderLabel .}}
7676
{{if .Description}}<br><small class="desc">{{.Description | RenderEmoji}}</small>{{end}}</a>
7777
{{end}}
7878
{{else}}

Diff for: templates/repo/issue/view_content/sidebar.tmpl

+2-2
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@
130130
<div class="ui divider"></div>
131131
{{end}}
132132
{{$previousScope = $scope}}
133-
<a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" data-id-selector="#label_{{.ID}}" data-scope="{{$scope}}"><span class="octicon-check {{if not .IsChecked}}invisible{{end}}">{{if $scope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}</span><span class="label color" style="background-color: {{.Color}}"></span> {{.Name | RenderEmoji}}
133+
<a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" data-id-selector="#label_{{.ID}}" data-scope="{{$scope}}"><span class="octicon-check {{if not .IsChecked}}invisible{{end}}">{{if $scope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}</span>&nbsp;&nbsp;{{RenderLabel .}}
134134
{{if .Description}}<br><small class="desc">{{.Description | RenderEmoji}}</small>{{end}}</a>
135135
{{end}}
136136
<div class="ui divider"></div>
@@ -141,7 +141,7 @@
141141
<div class="ui divider"></div>
142142
{{end}}
143143
{{$previousScope = $scope}}
144-
<a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" data-id-selector="#label_{{.ID}}" data-scope="{{$scope}}"><span class="octicon-check {{if not .IsChecked}}invisible{{end}}">{{if $scope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}</span><span class="label color" style="background-color: {{.Color}}"></span> {{.Name | RenderEmoji}}
144+
<a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" data-id-selector="#label_{{.ID}}" data-scope="{{$scope}}"><span class="octicon-check {{if not .IsChecked}}invisible{{end}}">{{if $scope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}</span>&nbsp;&nbsp;{{RenderLabel .}}
145145
{{if .Description}}<br><small class="desc">{{.Description | RenderEmoji}}</small>{{end}}</a>
146146
{{end}}
147147
{{else}}

Diff for: templates/repo/projects/view.tmpl

+1-1
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@
238238
{{if or .Labels .Assignees}}
239239
<div class="extra content labels-list p-0 pt-2">
240240
{{range .Labels}}
241-
<a class="ui label" target="_blank" href="{{$.RepoLink}}/issues?labels={{.ID}}" style="color: {{.ForegroundColor}}; background-color: {{.Color}};" title="{{.Description | RenderEmojiPlain}}">{{.Name | RenderEmoji}}</a>
241+
<a target="_blank" href="{{$.RepoLink}}/issues?labels={{.ID}}">{{RenderLabel .}}</a>
242242
{{end}}
243243
<div class="right floated">
244244
{{range .Assignees}}

Diff for: templates/shared/issuelist.tmpl

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
{{end}}
4444
<span class="labels-list ml-2">
4545
{{range .Labels}}
46-
<a class="ui label" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&state={{$.State}}&labels={{.ID}}{{if ne $.listType "milestone"}}&milestone={{$.MilestoneID}}{{end}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}" style="color: {{.ForegroundColor}}; background-color: {{.Color}}" title="{{.Description | RenderEmojiPlain}}">{{.Name | RenderEmoji}}</a>
46+
<a href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&state={{$.State}}&labels={{.ID}}{{if ne $.listType "milestone"}}&milestone={{$.MilestoneID}}{{end}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{RenderLabel .}}</a>
4747
{{end}}
4848
</span>
4949
</div>

Diff for: web_src/less/_base.less

+1-5
Original file line numberDiff line numberDiff line change
@@ -2529,7 +2529,7 @@ table th[data-sortt-desc] {
25292529
border-top: none;
25302530

25312531
a {
2532-
font-size: 15px;
2532+
font-size: 12px;
25332533
padding-top: 5px;
25342534
padding-right: 10px;
25352535
color: var(--color-text-light);
@@ -2542,10 +2542,6 @@ table th[data-sortt-desc] {
25422542
margin-right: 30px;
25432543
}
25442544
}
2545-
2546-
.ui.label {
2547-
font-size: 1em;
2548-
}
25492545
}
25502546

25512547
.item:last-child {

Diff for: web_src/less/_repository.less

+4-10
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@
9292
.metas {
9393
.menu {
9494
overflow-x: auto;
95-
max-height: 300px;
95+
max-height: 500px;
9696
}
9797

9898
.ui.list {
@@ -155,12 +155,6 @@
155155
}
156156

157157
.filter.menu {
158-
.label.color {
159-
border-radius: 3px;
160-
margin-left: 15px;
161-
padding: 0 8px;
162-
}
163-
164158
&.labels {
165159
.label-filter .menu .info {
166160
display: inline-block;
@@ -181,7 +175,7 @@
181175
}
182176

183177
.menu {
184-
max-height: 300px;
178+
max-height: 500px;
185179
overflow-x: auto;
186180
right: 0 !important;
187181
left: auto !important;
@@ -190,7 +184,7 @@
190184

191185
.select-label {
192186
.desc {
193-
padding-left: 16px;
187+
padding-left: 23px;
194188
}
195189
}
196190

@@ -607,7 +601,7 @@
607601
min-width: 220px;
608602

609603
.filter.menu {
610-
max-height: 300px;
604+
max-height: 500px;
611605
overflow-x: auto;
612606
}
613607
}

0 commit comments

Comments
 (0)