* Add label descriptions * Add default descriptions to label templatetags/v1.21.12.1
| @@ -19,22 +19,24 @@ import ( | |||
| var labelColorPattern = regexp.MustCompile("#([a-fA-F0-9]{6})") | |||
| // 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) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("getRepoInitFile: %v", err) | |||
| } | |||
| 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++ { | |||
| line := strings.TrimSpace(lines[i]) | |||
| if len(line) == 0 { | |||
| continue | |||
| } | |||
| fields := strings.SplitN(line, " ", 2) | |||
| parts := strings.SplitN(line, ";", 2) | |||
| fields := strings.SplitN(parts[0], " ", 2) | |||
| if len(fields) != 2 { | |||
| return nil, fmt.Errorf("line is malformed: %s", line) | |||
| } | |||
| @@ -43,8 +45,14 @@ func GetLabelTemplateFile(name string) ([][2]string, error) { | |||
| 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]) | |||
| list = append(list, [2]string{fields[1], fields[0]}) | |||
| list = append(list, [3]string{fields[1], fields[0], description}) | |||
| } | |||
| return list, nil | |||
| @@ -55,6 +63,7 @@ type Label struct { | |||
| ID int64 `xorm:"pk autoincr"` | |||
| RepoID int64 `xorm:"INDEX"` | |||
| Name string | |||
| Description string | |||
| Color string `xorm:"VARCHAR(7)"` | |||
| NumIssues int | |||
| NumClosedIssues int | |||
| @@ -168,6 +168,8 @@ var migrations = []Migration{ | |||
| NewMigration("remove is_owner, num_teams columns from org_user", removeIsOwnerColumnFromOrgUser), | |||
| // v57 -> v58 | |||
| NewMigration("add closed_unix column for issues", addIssueClosedTime), | |||
| // v58 -> v59 | |||
| NewMigration("add label descriptions", addLabelsDescriptions), | |||
| } | |||
| // Migrate database to current version | |||
| @@ -0,0 +1,22 @@ | |||
| // 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 | |||
| } | |||
| @@ -310,9 +310,10 @@ func (f *CreateMilestoneForm) Validate(ctx *macaron.Context, errs binding.Errors | |||
| // CreateLabelForm form for creating label | |||
| 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 | |||
| @@ -1,7 +1,7 @@ | |||
| #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 | |||
| @@ -627,6 +627,7 @@ issues.no_ref = No Branch/Tag Specified | |||
| issues.create = Create Issue | |||
| issues.new_label = New Label | |||
| issues.new_label_placeholder = Label name… | |||
| issues.new_label_desc_placeholder = Description… | |||
| issues.create_label = Create Label | |||
| 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. | |||
| @@ -697,6 +698,7 @@ issues.edit = Edit | |||
| issues.cancel = Cancel | |||
| issues.save = Save | |||
| issues.label_title = Label name | |||
| issues.label_description = Label description | |||
| issues.label_color = Label color | |||
| issues.label_count = %d labels | |||
| issues.label_open_issues = %d open issues | |||
| @@ -491,6 +491,7 @@ function initRepository() { | |||
| $('.edit-label-button').click(function () { | |||
| $('#label-modal-id').val($(this).data('id')); | |||
| $('.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')); | |||
| $('.minicolors-swatch-color').css("background-color", $(this).data('color')); | |||
| $('.edit-label.modal').modal({ | |||
| @@ -134,6 +134,17 @@ | |||
| } | |||
| } | |||
| .select-label { | |||
| .item { | |||
| max-width: 250px; | |||
| overflow: hidden; | |||
| text-overflow: ellipsis; | |||
| } | |||
| .desc { | |||
| padding-left: 16px; | |||
| } | |||
| } | |||
| .ui.tabs { | |||
| &.container { | |||
| margin-top: 14px; | |||
| @@ -42,9 +42,10 @@ func InitializeLabels(ctx *context.Context, form auth.InitializeLabelsForm) { | |||
| labels := make([]*models.Label, len(list)) | |||
| for i := 0; i < len(list); i++ { | |||
| 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 { | |||
| @@ -81,9 +82,10 @@ func NewLabel(ctx *context.Context, form auth.CreateLabelForm) { | |||
| } | |||
| 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 { | |||
| ctx.ServerError("NewLabel", err) | |||
| @@ -106,6 +108,7 @@ func UpdateLabel(ctx *context.Context, form auth.CreateLabelForm) { | |||
| } | |||
| l.Name = form.Title | |||
| l.Description = form.Description | |||
| l.Color = form.Color | |||
| if err := models.UpdateLabel(l); err != nil { | |||
| ctx.ServerError("UpdateLabel", err) | |||
| @@ -14,11 +14,16 @@ | |||
| <form class="ui form" action="{{$.RepoLink}}/labels/new" method="post"> | |||
| {{.CsrfTokenHtml}} | |||
| <div class="ui grid"> | |||
| <div class="five wide column"> | |||
| <div class="three wide column"> | |||
| <div class="ui small input"> | |||
| <input class="new-label-input" name="title" placeholder="{{.i18n.Tr "repo.issues.new_label_placeholder"}}" autofocus required> | |||
| </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"> | |||
| <input class="color-picker" name="color" value="#70c24a" required> | |||
| </div> | |||
| @@ -85,14 +90,27 @@ | |||
| </div> | |||
| {{end}} | |||
| <div class="ui divider"></div> | |||
| {{range .Labels}} | |||
| <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> | |||
| {{end}} | |||
| </div> | |||
| @@ -129,11 +147,16 @@ | |||
| {{.CsrfTokenHtml}} | |||
| <input id="label-modal-id" name="id" type="hidden"> | |||
| <div class="ui grid"> | |||
| <div class="five wide column"> | |||
| <div class="three wide column"> | |||
| <div class="ui small input"> | |||
| <input class="new-label-input" name="title" placeholder="{{.i18n.Tr "repo.issues.new_label_placeholder"}}" autofocus required> | |||
| </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"> | |||
| <input class="color-picker" name="color" value="#70c24a" required> | |||
| </div> | |||
| @@ -191,7 +191,7 @@ | |||
| <a class="ui label" href="{{$.RepoLink}}/src/branch/{{.Ref}}">{{.Ref}}</a> | |||
| {{end}} | |||
| {{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}} | |||
| {{if .NumComments}} | |||
| @@ -10,7 +10,8 @@ | |||
| <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> | |||
| {{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}} | |||
| </div> | |||
| </div> | |||
| @@ -18,7 +19,7 @@ | |||
| <span class="no-select item {{if .HasSelectedLabel}}hide{{end}}">{{.i18n.Tr "repo.issues.new.no_label"}}</span> | |||
| {{range .Labels}} | |||
| <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> | |||
| {{end}} | |||
| @@ -71,7 +71,7 @@ | |||
| especially on mobile views. */}} | |||
| <span style="line-height: 2.5"> | |||
| {{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}} | |||
| </span> | |||
| {{end}} | |||