* Add label descriptions * Add default descriptions to label templatetags/v1.5.0-dev
var labelColorPattern = regexp.MustCompile("#([a-fA-F0-9]{6})") | var labelColorPattern = regexp.MustCompile("#([a-fA-F0-9]{6})") | ||||
// GetLabelTemplateFile loads the label template file by given name, | // GetLabelTemplateFile loads the label template file by given name, | ||||
// then parses and returns a list of name-color pairs. | |||||
func GetLabelTemplateFile(name string) ([][2]string, error) { | |||||
// then parses and returns a list of name-color pairs and optionally description. | |||||
func GetLabelTemplateFile(name string) ([][3]string, error) { | |||||
data, err := getRepoInitFile("label", name) | data, err := getRepoInitFile("label", name) | ||||
if err != nil { | if err != nil { | ||||
return nil, fmt.Errorf("getRepoInitFile: %v", err) | return nil, fmt.Errorf("getRepoInitFile: %v", err) | ||||
} | } | ||||
lines := strings.Split(string(data), "\n") | lines := strings.Split(string(data), "\n") | ||||
list := make([][2]string, 0, len(lines)) | |||||
list := make([][3]string, 0, len(lines)) | |||||
for i := 0; i < len(lines); i++ { | for i := 0; i < len(lines); i++ { | ||||
line := strings.TrimSpace(lines[i]) | line := strings.TrimSpace(lines[i]) | ||||
if len(line) == 0 { | if len(line) == 0 { | ||||
continue | continue | ||||
} | } | ||||
fields := strings.SplitN(line, " ", 2) | |||||
parts := strings.SplitN(line, ";", 2) | |||||
fields := strings.SplitN(parts[0], " ", 2) | |||||
if len(fields) != 2 { | if len(fields) != 2 { | ||||
return nil, fmt.Errorf("line is malformed: %s", line) | return nil, fmt.Errorf("line is malformed: %s", line) | ||||
} | } | ||||
return nil, fmt.Errorf("bad HTML color code in line: %s", line) | return nil, fmt.Errorf("bad HTML color code in line: %s", line) | ||||
} | } | ||||
var description string | |||||
if len(parts) > 1 { | |||||
description = strings.TrimSpace(parts[1]) | |||||
} | |||||
fields[1] = strings.TrimSpace(fields[1]) | fields[1] = strings.TrimSpace(fields[1]) | ||||
list = append(list, [2]string{fields[1], fields[0]}) | |||||
list = append(list, [3]string{fields[1], fields[0], description}) | |||||
} | } | ||||
return list, nil | return list, nil | ||||
ID int64 `xorm:"pk autoincr"` | ID int64 `xorm:"pk autoincr"` | ||||
RepoID int64 `xorm:"INDEX"` | RepoID int64 `xorm:"INDEX"` | ||||
Name string | Name string | ||||
Description string | |||||
Color string `xorm:"VARCHAR(7)"` | Color string `xorm:"VARCHAR(7)"` | ||||
NumIssues int | NumIssues int | ||||
NumClosedIssues int | NumClosedIssues int |
NewMigration("remove is_owner, num_teams columns from org_user", removeIsOwnerColumnFromOrgUser), | NewMigration("remove is_owner, num_teams columns from org_user", removeIsOwnerColumnFromOrgUser), | ||||
// v57 -> v58 | // v57 -> v58 | ||||
NewMigration("add closed_unix column for issues", addIssueClosedTime), | NewMigration("add closed_unix column for issues", addIssueClosedTime), | ||||
// v58 -> v59 | |||||
NewMigration("add label descriptions", addLabelsDescriptions), | |||||
} | } | ||||
// Migrate database to current version | // Migrate database to current version |
// Copyright 2018 The Gitea Authors. All rights reserved. | |||||
// Use of this source code is governed by a MIT-style | |||||
// license that can be found in the LICENSE file. | |||||
package migrations | |||||
import ( | |||||
"fmt" | |||||
"github.com/go-xorm/xorm" | |||||
) | |||||
func addLabelsDescriptions(x *xorm.Engine) error { | |||||
type Label struct { | |||||
Description string | |||||
} | |||||
if err := x.Sync2(new(Label)); err != nil { | |||||
return fmt.Errorf("Sync2: %v", err) | |||||
} | |||||
return nil | |||||
} |
// CreateLabelForm form for creating label | // CreateLabelForm form for creating label | ||||
type CreateLabelForm struct { | type CreateLabelForm struct { | ||||
ID int64 | |||||
Title string `binding:"Required;MaxSize(50)" locale:"repo.issues.label_name"` | |||||
Color string `binding:"Required;Size(7)" locale:"repo.issues.label_color"` | |||||
ID int64 | |||||
Title string `binding:"Required;MaxSize(50)" locale:"repo.issues.label_title"` | |||||
Description string `binding:"MaxSize(200)" locale:"repo.issues.label_description"` | |||||
Color string `binding:"Required;Size(7)" locale:"repo.issues.label_color"` | |||||
} | } | ||||
// Validate validates the fields | // Validate validates the fields |
#ee0701 bug | |||||
#cccccc duplicate | |||||
#84b6eb enhancement | |||||
#128a0c help wanted | |||||
#e6e6e6 invalid | |||||
#cc317c question | |||||
#ffffff wontfix | |||||
#ee0701 bug ; Something is not working | |||||
#cccccc duplicate ; This issue or pull request already exists | |||||
#84b6eb enhancement ; New feature | |||||
#128a0c help wanted ; Need some help | |||||
#e6e6e6 invalid ; Something is wrong | |||||
#cc317c question ; More information is needed | |||||
#ffffff wontfix ; This won't be fixed |
issues.create = Create Issue | issues.create = Create Issue | ||||
issues.new_label = New Label | issues.new_label = New Label | ||||
issues.new_label_placeholder = Label name… | issues.new_label_placeholder = Label name… | ||||
issues.new_label_desc_placeholder = Description… | |||||
issues.create_label = Create Label | issues.create_label = Create Label | ||||
issues.label_templates.title = Load a predefined set of labels | issues.label_templates.title = Load a predefined set of labels | ||||
issues.label_templates.info = There are not any labels yet. You can click on the "New Label" button above to create one or use a predefined set below. | issues.label_templates.info = There are not any labels yet. You can click on the "New Label" button above to create one or use a predefined set below. | ||||
issues.cancel = Cancel | issues.cancel = Cancel | ||||
issues.save = Save | issues.save = Save | ||||
issues.label_title = Label name | issues.label_title = Label name | ||||
issues.label_description = Label description | |||||
issues.label_color = Label color | issues.label_color = Label color | ||||
issues.label_count = %d labels | issues.label_count = %d labels | ||||
issues.label_open_issues = %d open issues | issues.label_open_issues = %d open issues |
$('.edit-label-button').click(function () { | $('.edit-label-button').click(function () { | ||||
$('#label-modal-id').val($(this).data('id')); | $('#label-modal-id').val($(this).data('id')); | ||||
$('.edit-label .new-label-input').val($(this).data('title')); | $('.edit-label .new-label-input').val($(this).data('title')); | ||||
$('.edit-label .new-label-desc-input').val($(this).data('description')); | |||||
$('.edit-label .color-picker').val($(this).data('color')); | $('.edit-label .color-picker').val($(this).data('color')); | ||||
$('.minicolors-swatch-color').css("background-color", $(this).data('color')); | $('.minicolors-swatch-color').css("background-color", $(this).data('color')); | ||||
$('.edit-label.modal').modal({ | $('.edit-label.modal').modal({ |
} | } | ||||
} | } | ||||
.select-label { | |||||
.item { | |||||
max-width: 250px; | |||||
overflow: hidden; | |||||
text-overflow: ellipsis; | |||||
} | |||||
.desc { | |||||
padding-left: 16px; | |||||
} | |||||
} | |||||
.ui.tabs { | .ui.tabs { | ||||
&.container { | &.container { | ||||
margin-top: 14px; | margin-top: 14px; |
labels := make([]*models.Label, len(list)) | labels := make([]*models.Label, len(list)) | ||||
for i := 0; i < len(list); i++ { | for i := 0; i < len(list); i++ { | ||||
labels[i] = &models.Label{ | labels[i] = &models.Label{ | ||||
RepoID: ctx.Repo.Repository.ID, | |||||
Name: list[i][0], | |||||
Color: list[i][1], | |||||
RepoID: ctx.Repo.Repository.ID, | |||||
Name: list[i][0], | |||||
Description: list[i][2], | |||||
Color: list[i][1], | |||||
} | } | ||||
} | } | ||||
if err := models.NewLabels(labels...); err != nil { | if err := models.NewLabels(labels...); err != nil { | ||||
} | } | ||||
l := &models.Label{ | l := &models.Label{ | ||||
RepoID: ctx.Repo.Repository.ID, | |||||
Name: form.Title, | |||||
Color: form.Color, | |||||
RepoID: ctx.Repo.Repository.ID, | |||||
Name: form.Title, | |||||
Description: form.Description, | |||||
Color: form.Color, | |||||
} | } | ||||
if err := models.NewLabel(l); err != nil { | if err := models.NewLabel(l); err != nil { | ||||
ctx.ServerError("NewLabel", err) | ctx.ServerError("NewLabel", err) | ||||
} | } | ||||
l.Name = form.Title | l.Name = form.Title | ||||
l.Description = form.Description | |||||
l.Color = form.Color | l.Color = form.Color | ||||
if err := models.UpdateLabel(l); err != nil { | if err := models.UpdateLabel(l); err != nil { | ||||
ctx.ServerError("UpdateLabel", err) | ctx.ServerError("UpdateLabel", err) |
<form class="ui form" action="{{$.RepoLink}}/labels/new" method="post"> | <form class="ui form" action="{{$.RepoLink}}/labels/new" method="post"> | ||||
{{.CsrfTokenHtml}} | {{.CsrfTokenHtml}} | ||||
<div class="ui grid"> | <div class="ui grid"> | ||||
<div class="five wide column"> | |||||
<div class="three wide column"> | |||||
<div class="ui small input"> | <div class="ui small input"> | ||||
<input class="new-label-input" name="title" placeholder="{{.i18n.Tr "repo.issues.new_label_placeholder"}}" autofocus required> | <input class="new-label-input" name="title" placeholder="{{.i18n.Tr "repo.issues.new_label_placeholder"}}" autofocus required> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
<div class="five wide column"> | |||||
<div class="ui small fluid input"> | |||||
<input class="new-label-desc-input" name="description" placeholder="{{.i18n.Tr "repo.issues.new_label_desc_placeholder"}}"> | |||||
</div> | |||||
</div> | |||||
<div class="color picker column"> | <div class="color picker column"> | ||||
<input class="color-picker" name="color" value="#70c24a" required> | <input class="color-picker" name="color" value="#70c24a" required> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
{{end}} | {{end}} | ||||
<div class="ui divider"></div> | |||||
{{range .Labels}} | {{range .Labels}} | ||||
<li class="item"> | <li class="item"> | ||||
<div class="ui label" style="color: {{.ForegroundColor}}; background-color: {{.Color}}"><i class="octicon octicon-tag"></i> {{.Name}}</div> | |||||
{{if $.IsRepositoryWriter}} | |||||
<a class="ui right delete-button" href="#" data-url="{{$.RepoLink}}/labels/delete" data-id="{{.ID}}"><i class="octicon octicon-trashcan"></i> {{$.i18n.Tr "repo.issues.label_delete"}}</a> | |||||
<a class="ui right edit-label-button" href="#" data-id={{.ID}} data-title={{.Name}} data-color={{.Color}}><i class="octicon octicon-pencil"></i> {{$.i18n.Tr "repo.issues.label_edit"}}</a> | |||||
{{end}} | |||||
<a class="ui right open-issues" href="{{$.RepoLink}}/issues?labels={{.ID}}"><i class="octicon octicon-issue-opened"></i> {{$.i18n.Tr "repo.issues.label_open_issues" .NumOpenIssues}}</a> | |||||
<div class="ui grid"> | |||||
<div class="three wide column"> | |||||
<div class="ui label" style="color: {{.ForegroundColor}}; background-color: {{.Color}}"><i class="octicon octicon-tag"></i> {{.Name}}</div> | |||||
</div> | |||||
<div class="seven wide column"> | |||||
{{.Description}} | |||||
</div> | |||||
<div class="three wide column"> | |||||
<a class="ui right open-issues" href="{{$.RepoLink}}/issues?labels={{.ID}}"><i class="octicon octicon-issue-opened"></i> {{$.i18n.Tr "repo.issues.label_open_issues" .NumOpenIssues}}</a> | |||||
</div> | |||||
<div class="three wide column"> | |||||
{{if $.IsRepositoryWriter}} | |||||
<a class="ui right delete-button" href="#" data-url="{{$.RepoLink}}/labels/delete" data-id="{{.ID}}"><i class="octicon octicon-trashcan"></i> {{$.i18n.Tr "repo.issues.label_delete"}}</a> | |||||
<a class="ui right edit-label-button" href="#" data-id="{{.ID}}" data-title="{{.Name}}" data-description="{{.Description}}" data-color={{.Color}}><i class="octicon octicon-pencil"></i> {{$.i18n.Tr "repo.issues.label_edit"}}</a> | |||||
{{end}} | |||||
</div> | |||||
</div> | |||||
</li> | </li> | ||||
{{end}} | {{end}} | ||||
</div> | </div> | ||||
{{.CsrfTokenHtml}} | {{.CsrfTokenHtml}} | ||||
<input id="label-modal-id" name="id" type="hidden"> | <input id="label-modal-id" name="id" type="hidden"> | ||||
<div class="ui grid"> | <div class="ui grid"> | ||||
<div class="five wide column"> | |||||
<div class="three wide column"> | |||||
<div class="ui small input"> | <div class="ui small input"> | ||||
<input class="new-label-input" name="title" placeholder="{{.i18n.Tr "repo.issues.new_label_placeholder"}}" autofocus required> | <input class="new-label-input" name="title" placeholder="{{.i18n.Tr "repo.issues.new_label_placeholder"}}" autofocus required> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
<div class="five wide column"> | |||||
<div class="ui small fluid input"> | |||||
<input class="new-label-desc-input" name="description" placeholder="{{.i18n.Tr "repo.issues.new_label_desc_placeholder"}}"> | |||||
</div> | |||||
</div> | |||||
<div class="color picker column"> | <div class="color picker column"> | ||||
<input class="color-picker" name="color" value="#70c24a" required> | <input class="color-picker" name="color" value="#70c24a" required> | ||||
</div> | </div> |
<a class="ui label" href="{{$.RepoLink}}/src/branch/{{.Ref}}">{{.Ref}}</a> | <a class="ui label" href="{{$.RepoLink}}/src/branch/{{.Ref}}">{{.Ref}}</a> | ||||
{{end}} | {{end}} | ||||
{{range .Labels}} | {{range .Labels}} | ||||
<a class="ui label" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&state={{$.State}}&labels={{.ID}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}" style="color: {{.ForegroundColor}}; background-color: {{.Color}}">{{.Name}}</a> | |||||
<a class="ui label" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&state={{$.State}}&labels={{.ID}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}" style="color: {{.ForegroundColor}}; background-color: {{.Color}}" title="{{.Description}}">{{.Name}}</a> | |||||
{{end}} | {{end}} | ||||
{{if .NumComments}} | {{if .NumComments}} |
<div class="filter menu" data-action="update" data-issue-id="{{$.Issue.ID}}" data-update-url="{{$.RepoLink}}/issues/labels"> | <div class="filter menu" data-action="update" data-issue-id="{{$.Issue.ID}}" data-update-url="{{$.RepoLink}}/issues/labels"> | ||||
<div class="no-select item">{{.i18n.Tr "repo.issues.new.clear_labels"}}</div> | <div class="no-select item">{{.i18n.Tr "repo.issues.new.clear_labels"}}</div> | ||||
{{range .Labels}} | {{range .Labels}} | ||||
<a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" data-id-selector="#label_{{.ID}}"><span class="octicon {{if .IsChecked}}octicon-check{{end}}"></span><span class="label color" style="background-color: {{.Color}}"></span> {{.Name}}</a> | |||||
<a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" data-id-selector="#label_{{.ID}}"><span class="octicon {{if .IsChecked}}octicon-check{{end}}"></span><span class="label color" style="background-color: {{.Color}}"></span> {{.Name}} | |||||
{{if .Description }}<br><small class="desc">{{.Description}}</small>{{end}}</a> | |||||
{{end}} | {{end}} | ||||
</div> | </div> | ||||
</div> | </div> | ||||
<span class="no-select item {{if .HasSelectedLabel}}hide{{end}}">{{.i18n.Tr "repo.issues.new.no_label"}}</span> | <span class="no-select item {{if .HasSelectedLabel}}hide{{end}}">{{.i18n.Tr "repo.issues.new.no_label"}}</span> | ||||
{{range .Labels}} | {{range .Labels}} | ||||
<div class="item"> | <div class="item"> | ||||
<a class="ui label {{if not .IsChecked}}hide{{end}}" id="label_{{.ID}}" href="{{$.RepoLink}}/issues?labels={{.ID}}" style="color: {{.ForegroundColor}}; background-color: {{.Color}}">{{.Name}}</a> | |||||
<a class="ui label {{if not .IsChecked}}hide{{end}}" id="label_{{.ID}}" href="{{$.RepoLink}}/issues?labels={{.ID}}" style="color: {{.ForegroundColor}}; background-color: {{.Color}}" title="{{.Description}}">{{.Name}}</a> | |||||
</div> | </div> | ||||
{{end}} | {{end}} |
especially on mobile views. */}} | especially on mobile views. */}} | ||||
<span style="line-height: 2.5"> | <span style="line-height: 2.5"> | ||||
{{range .}} | {{range .}} | ||||
<a class="ui label" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&state={{$.State}}&labels={{.ID}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}" style="color: {{.ForegroundColor}}; background-color: {{.Color}}">{{.Name}}</a> | |||||
<a class="ui label" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&state={{$.State}}&labels={{.ID}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}" style="color: {{.ForegroundColor}}; background-color: {{.Color}}" title="{{.Description}}">{{.Name}}</a> | |||||
{{end}} | {{end}} | ||||
</span> | </span> | ||||
{{end}} | {{end}} |