Skip to content

Commit 3513f74

Browse files
committed
Exclusive scoped labels custom rendering
* Display scoped label prefix and suffix with slightly darker and lighter background color respectively, and a slanted edge between them similar to the `/` symbol. * In menus exclusive labels are grouped with a divider line.
1 parent d8f108d commit 3513f74

File tree

6 files changed

+112
-3
lines changed

6 files changed

+112
-3
lines changed

modules/templates/helper.go

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@ package templates
77
import (
88
"bytes"
99
"context"
10+
"encoding/hex"
1011
"errors"
1112
"fmt"
1213
"html"
1314
"html/template"
15+
"math"
1416
"mime"
1517
"net/url"
1618
"path/filepath"
@@ -803,15 +805,63 @@ func RenderIssueTitle(ctx context.Context, text, urlPrefix string, metas map[str
803805

804806
// RenderLabel renders a label
805807
func RenderLabel(label *issues_model.Label) string {
808+
labelScope := label.ExclusiveScope()
809+
806810
textColor := "#111"
807811
if label.UseLightTextColor() {
808812
textColor = "#eee"
809813
}
810814

811815
description := emoji.ReplaceAliases(label.Description)
812816

813-
return fmt.Sprintf("<div class='ui label' style='color: %s !important; background-color: %s !important' title='%s'>%s</div>",
814-
textColor, label.Color, description, RenderEmoji(label.Name))
817+
if labelScope == "" {
818+
// Regular label
819+
return fmt.Sprintf("<div class='ui label' style='color: %s !important; background-color: %s !important' title='%s'>%s</div>",
820+
textColor, label.Color, description, RenderEmoji(label.Name))
821+
}
822+
823+
// Scoped label
824+
scopeText := RenderEmoji(labelScope)
825+
itemText := RenderEmoji(label.Name[len(labelScope)+1:])
826+
827+
itemColor := label.Color
828+
scopeColor := label.Color
829+
if r, g, b, err := label.ColorRGB(); err == nil {
830+
// Make scope and item background colors slightly darker and lighter respectively.
831+
// More contrast needed with higher luminance, empirically tweaked.
832+
luminance := (0.299*r + 0.587*g + 0.114*b) / 255
833+
contrast := 0.01 + luminance*0.06
834+
// Ensure we add the same amount of contrast also near 0 and 1.
835+
darken := contrast + math.Max(luminance+contrast-1.0, 0.0)
836+
lighten := contrast + math.Max(contrast-luminance, 0.0)
837+
// Compute factor to keep RGB values proportional.
838+
darkenFactor := math.Max(luminance-darken, 0.0) / math.Max(luminance, 1.0/255.0)
839+
lightenFactor := math.Min(luminance+lighten, 1.0) / math.Max(luminance, 1.0/255.0)
840+
841+
scopeBytes := []byte{
842+
uint8(math.Min(math.Round(r*darkenFactor), 255)),
843+
uint8(math.Min(math.Round(g*darkenFactor), 255)),
844+
uint8(math.Min(math.Round(b*darkenFactor), 255)),
845+
}
846+
itemBytes := []byte{
847+
uint8(math.Min(math.Round(r*lightenFactor), 255)),
848+
uint8(math.Min(math.Round(g*lightenFactor), 255)),
849+
uint8(math.Min(math.Round(b*lightenFactor), 255)),
850+
}
851+
852+
itemColor = "#" + hex.EncodeToString(itemBytes)
853+
scopeColor = "#" + hex.EncodeToString(scopeBytes)
854+
}
855+
856+
return fmt.Sprintf("<span class='ui label scope-parent' title='%s'>"+
857+
"<div class='ui label scope-left' style='color: %s !important; background-color: %s !important'>%s</div>"+
858+
"<div class='ui label scope-middle' style='background: linear-gradient(-80deg, %s 48%%, %s 52%% 0%%);'>&nbsp;</div>"+
859+
"<div class='ui label scope-right' style='color: %s !important; background-color: %s !important''>%s</div>"+
860+
"</span>",
861+
description,
862+
textColor, scopeColor, scopeText,
863+
itemColor, scopeColor,
864+
textColor, itemColor, itemText)
815865
}
816866

817867
// RenderEmoji renders html text with emoji post processors

templates/repo/issue/list.tmpl

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,13 @@
5050
</div>
5151
<span class="info">{{.locale.Tr "repo.issues.filter_label_exclude" | Safe}}</span>
5252
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_label_no_select"}}</a>
53+
{{$previousExclusiveScope := "_no_scope"}}
5354
{{range .Labels}}
5455
{{$exclusiveScope := .ExclusiveScope}}
56+
{{if and (ne $previousExclusiveScope "_no_scope") (ne $previousExclusiveScope $exclusiveScope)}}
57+
<div class="ui divider"></div>
58+
{{end}}
59+
{{$previousExclusiveScope = $exclusiveScope}}
5560
<a class="item label-filter-item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.QueryString}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}" data-label-id="{{.ID}}">{{if .IsExcluded}}{{svg "octicon-circle-slash"}}{{else if .IsSelected}}{{if $exclusiveScope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}{{end}} {{RenderLabel .}}</a>
5661
{{end}}
5762
</div>
@@ -218,8 +223,13 @@
218223
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
219224
</span>
220225
<div class="menu">
226+
{{$previousExclusiveScope := "_no_scope"}}
221227
{{range .Labels}}
222228
{{$exclusiveScope := .ExclusiveScope}}
229+
{{if and (ne $previousExclusiveScope "_no_scope") (ne $previousExclusiveScope $exclusiveScope)}}
230+
<div class="ui divider"></div>
231+
{{end}}
232+
{{$previousExclusiveScope = $exclusiveScope}}
223233
<div class="item issue-action" data-action="toggle" data-element-id="{{.ID}}" data-url="{{$.RepoLink}}/issues/labels">
224234
{{if contain $.SelLabelIDs .ID}}{{if $exclusiveScope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}{{end}} {{RenderLabel .}}
225235
</div>

templates/repo/issue/new_form.tmpl

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,15 +53,25 @@
5353
{{end}}
5454
<div class="no-select item">{{.locale.Tr "repo.issues.new.clear_labels"}}</div>
5555
{{if or .Labels .OrgLabels}}
56+
{{$previousExclusiveScope := "_no_scope"}}
5657
{{range .Labels}}
5758
{{$exclusiveScope := .ExclusiveScope}}
59+
{{if and (ne $previousExclusiveScope "_no_scope") (ne $previousExclusiveScope $exclusiveScope)}}
60+
<div class="ui divider"></div>
61+
{{end}}
62+
{{$previousExclusiveScope = $exclusiveScope}}
5863
<a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" data-id-selector="#label_{{.ID}}" data-scope="{{$exclusiveScope}}"><span class="octicon-check {{if not .IsChecked}}invisible{{end}}">{{if $exclusiveScope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}</span>&nbsp;&nbsp;{{RenderLabel .}}
5964
{{if .Description}}<br><small class="desc">{{.Description | RenderEmoji}}</small>{{end}}</a>
6065
{{end}}
6166

6267
<div class="ui divider"></div>
68+
{{$previousExclusiveScope := "_no_scope"}}
6369
{{range .OrgLabels}}
6470
{{$exclusiveScope := .ExclusiveScope}}
71+
{{if and (ne $previousExclusiveScope "_no_scope") (ne $previousExclusiveScope $exclusiveScope)}}
72+
<div class="ui divider"></div>
73+
{{end}}
74+
{{$previousExclusiveScope = $exclusiveScope}}
6575
<a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" data-id-selector="#label_{{.ID}}" data-scope="{{$exclusiveScope}}"><span class="octicon-check {{if not .IsChecked}}invisible{{end}}">{{if $exclusiveScope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}</span>&nbsp;&nbsp;{{RenderLabel .}}
6676
{{if .Description}}<br><small class="desc">{{.Description | RenderEmoji}}</small>{{end}}</a>
6777
{{end}}

templates/repo/issue/view_content/sidebar.tmpl

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,14 +123,24 @@
123123
{{end}}
124124
<div class="no-select item">{{.locale.Tr "repo.issues.new.clear_labels"}}</div>
125125
{{if or .Labels .OrgLabels}}
126+
{{$previousExclusiveScope := "_no_scope"}}
126127
{{range .Labels}}
127128
{{$exclusiveScope := .ExclusiveScope}}
129+
{{if and (ne $previousExclusiveScope "_no_scope") (ne $previousExclusiveScope $exclusiveScope)}}
130+
<div class="ui divider"></div>
131+
{{end}}
132+
{{$previousExclusiveScope = $exclusiveScope}}
128133
<a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" data-id-selector="#label_{{.ID}}" data-scope="{{$exclusiveScope}}"><span class="octicon-check {{if not .IsChecked}}invisible{{end}}">{{if $exclusiveScope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}</span>&nbsp;&nbsp;{{RenderLabel .}}
129134
{{if .Description}}<br><small class="desc">{{.Description | RenderEmoji}}</small>{{end}}</a>
130135
{{end}}
131136
<div class="ui divider"></div>
137+
{{$previousExclusiveScope := "_no_scope"}}
132138
{{range .OrgLabels}}
133139
{{$exclusiveScope := .ExclusiveScope}}
140+
{{if and (ne $previousExclusiveScope "_no_scope") (ne $previousExclusiveScope $exclusiveScope)}}
141+
<div class="ui divider"></div>
142+
{{end}}
143+
{{$previousExclusiveScope = $exclusiveScope}}
134144
<a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" data-id-selector="#label_{{.ID}}" data-scope="{{$exclusiveScope}}"><span class="octicon-check {{if not .IsChecked}}invisible{{end}}">{{if $exclusiveScope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}</span>&nbsp;&nbsp;{{RenderLabel .}}
135145
{{if .Description}}<br><small class="desc">{{.Description | RenderEmoji}}</small>{{end}}</a>
136146
{{end}}

templates/swagger/v1_json.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21000,4 +21000,4 @@
2100021000
"TOTPHeader": []
2100121001
}
2100221002
]
21003-
}
21003+
}

web_src/less/_repository.less

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2849,6 +2849,35 @@
28492849
line-height: 1.3em; // there is a `font-size: 1.25em` for inside emoji, so here the line-height needs to be larger slightly
28502850
}
28512851

2852+
// Scoped labels with different colors on left and right, and slanted divider in the middle
2853+
.scope-parent {
2854+
background: none !important;
2855+
padding: 0 !important;
2856+
}
2857+
2858+
.ui.label.scope-left {
2859+
border-bottom-right-radius: 0;
2860+
border-top-right-radius: 0;
2861+
padding-right: 0;
2862+
margin-right: 0;
2863+
}
2864+
2865+
.ui.label.scope-middle {
2866+
width: 12px;
2867+
border-radius: 0;
2868+
padding-left: 0;
2869+
padding-right: 0;
2870+
margin-left: 0;
2871+
margin-right: 0;
2872+
}
2873+
2874+
.ui.label.scope-right {
2875+
border-bottom-left-radius: 0;
2876+
border-top-left-radius: 0;
2877+
padding-left: 0;
2878+
margin-left: 0;
2879+
}
2880+
28522881
.repo-button-row {
28532882
margin-bottom: 10px;
28542883
}

0 commit comments

Comments
 (0)