diff options
Diffstat (limited to 'modules')
-rw-r--r-- | modules/migration/label.go | 1 | ||||
-rw-r--r-- | modules/structs/issue_label.go | 9 | ||||
-rw-r--r-- | modules/templates/helper.go | 70 |
3 files changed, 77 insertions, 3 deletions
diff --git a/modules/migration/label.go b/modules/migration/label.go index 38f0eb10da..4927be3c0b 100644 --- a/modules/migration/label.go +++ b/modules/migration/label.go @@ -9,4 +9,5 @@ type Label struct { Name string `json:"name"` Color string `json:"color"` Description string `json:"description"` + Exclusive bool `json:"exclusive"` } diff --git a/modules/structs/issue_label.go b/modules/structs/issue_label.go index 5c622797f4..5bb6cc3b84 100644 --- a/modules/structs/issue_label.go +++ b/modules/structs/issue_label.go @@ -9,6 +9,8 @@ package structs type Label struct { ID int64 `json:"id"` Name string `json:"name"` + // example: false + Exclusive bool `json:"exclusive"` // example: 00aabb Color string `json:"color"` Description string `json:"description"` @@ -19,6 +21,8 @@ type Label struct { type CreateLabelOption struct { // required:true Name string `json:"name" binding:"Required"` + // example: false + Exclusive bool `json:"exclusive"` // required:true // example: #00aabb Color string `json:"color" binding:"Required"` @@ -27,7 +31,10 @@ type CreateLabelOption struct { // EditLabelOption options for editing a label type EditLabelOption struct { - Name *string `json:"name"` + Name *string `json:"name"` + // example: false + Exclusive *bool `json:"exclusive"` + // example: #00aabb Color *string `json:"color"` Description *string `json:"description"` } diff --git a/modules/templates/helper.go b/modules/templates/helper.go index 8f8f565c1f..4ffd0a5dee 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -7,10 +7,12 @@ package templates import ( "bytes" "context" + "encoding/hex" "errors" "fmt" "html" "html/template" + "math" "mime" "net/url" "path/filepath" @@ -382,6 +384,9 @@ func NewFuncMap() []template.FuncMap { // the table is NOT sorted with this header return "" }, + "RenderLabel": func(label *issues_model.Label) template.HTML { + return template.HTML(RenderLabel(label)) + }, "RenderLabels": func(labels []*issues_model.Label, repoLink string) template.HTML { htmlCode := `<span class="labels-list">` for _, label := range labels { @@ -389,8 +394,8 @@ func NewFuncMap() []template.FuncMap { if label == nil { continue } - htmlCode += fmt.Sprintf("<a href='%s/issues?labels=%d' class='ui label' style='color: %s !important; background-color: %s !important' title='%s'>%s</a> ", - repoLink, label.ID, label.ForegroundColor(), label.Color, html.EscapeString(label.Description), RenderEmoji(label.Name)) + htmlCode += fmt.Sprintf("<a href='%s/issues?labels=%d'>%s</a> ", + repoLink, label.ID, RenderLabel(label)) } htmlCode += "</span>" return template.HTML(htmlCode) @@ -801,6 +806,67 @@ func RenderIssueTitle(ctx context.Context, text, urlPrefix string, metas map[str return template.HTML(renderedText) } +// RenderLabel renders a label +func RenderLabel(label *issues_model.Label) string { + labelScope := label.ExclusiveScope() + + textColor := "#111" + if label.UseLightTextColor() { + textColor = "#eee" + } + + description := emoji.ReplaceAliases(template.HTMLEscapeString(label.Description)) + + if labelScope == "" { + // Regular label + return fmt.Sprintf("<div class='ui label' style='color: %s !important; background-color: %s !important' title='%s'>%s</div>", + textColor, label.Color, description, RenderEmoji(label.Name)) + } + + // Scoped label + scopeText := RenderEmoji(labelScope) + itemText := RenderEmoji(label.Name[len(labelScope)+1:]) + + itemColor := label.Color + scopeColor := label.Color + if r, g, b, err := label.ColorRGB(); err == nil { + // Make scope and item background colors slightly darker and lighter respectively. + // More contrast needed with higher luminance, empirically tweaked. + luminance := (0.299*r + 0.587*g + 0.114*b) / 255 + contrast := 0.01 + luminance*0.06 + // 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. + 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) + + scopeBytes := []byte{ + uint8(math.Min(math.Round(r*darkenFactor), 255)), + uint8(math.Min(math.Round(g*darkenFactor), 255)), + uint8(math.Min(math.Round(b*darkenFactor), 255)), + } + itemBytes := []byte{ + uint8(math.Min(math.Round(r*lightenFactor), 255)), + uint8(math.Min(math.Round(g*lightenFactor), 255)), + uint8(math.Min(math.Round(b*lightenFactor), 255)), + } + + itemColor = "#" + hex.EncodeToString(itemBytes) + scopeColor = "#" + hex.EncodeToString(scopeBytes) + } + + return fmt.Sprintf("<span class='ui label scope-parent' 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='background: linear-gradient(-80deg, %s 48%%, %s 52%% 0%%);'> </div>"+ + "<div class='ui label scope-right' style='color: %s !important; background-color: %s !important''>%s</div>"+ + "</span>", + description, + textColor, scopeColor, scopeText, + itemColor, scopeColor, + textColor, itemColor, itemText) +} + // RenderEmoji renders html text with emoji post processors func RenderEmoji(text string) template.HTML { renderedText, err := markup.RenderEmoji(template.HTMLEscapeString(text)) |