* track labels changed on issue view & resolved #542 * add missing head comment & sort & fix refreshtags/v1.21.12.1
| @@ -7,6 +7,7 @@ package models | |||
| import ( | |||
| "errors" | |||
| "fmt" | |||
| "sort" | |||
| "strings" | |||
| "time" | |||
| @@ -103,11 +104,17 @@ func (issue *Issue) GetPullRequest() (pr *PullRequest, err error) { | |||
| return | |||
| } | |||
| func (issue *Issue) loadAttributes(e Engine) (err error) { | |||
| if err := issue.loadRepo(e); err != nil { | |||
| return err | |||
| func (issue *Issue) loadLabels(e Engine) (err error) { | |||
| if issue.Labels == nil { | |||
| issue.Labels, err = getLabelsByIssueID(e, issue.ID) | |||
| if err != nil { | |||
| return fmt.Errorf("getLabelsByIssueID [%d]: %v", issue.ID, err) | |||
| } | |||
| } | |||
| return nil | |||
| } | |||
| func (issue *Issue) loadPoster(e Engine) (err error) { | |||
| if issue.Poster == nil { | |||
| issue.Poster, err = getUserByID(e, issue.PosterID) | |||
| if err != nil { | |||
| @@ -120,12 +127,20 @@ func (issue *Issue) loadAttributes(e Engine) (err error) { | |||
| return | |||
| } | |||
| } | |||
| return | |||
| } | |||
| if issue.Labels == nil { | |||
| issue.Labels, err = getLabelsByIssueID(e, issue.ID) | |||
| if err != nil { | |||
| return fmt.Errorf("getLabelsByIssueID [%d]: %v", issue.ID, err) | |||
| } | |||
| func (issue *Issue) loadAttributes(e Engine) (err error) { | |||
| if err = issue.loadRepo(e); err != nil { | |||
| return | |||
| } | |||
| if err = issue.loadPoster(e); err != nil { | |||
| return | |||
| } | |||
| if err = issue.loadLabels(e); err != nil { | |||
| return | |||
| } | |||
| if issue.Milestone == nil && issue.MilestoneID > 0 { | |||
| @@ -289,13 +304,13 @@ func (issue *Issue) sendLabelUpdatedWebhook(doer *User) { | |||
| } | |||
| } | |||
| func (issue *Issue) addLabel(e *xorm.Session, label *Label) error { | |||
| return newIssueLabel(e, issue, label) | |||
| func (issue *Issue) addLabel(e *xorm.Session, label *Label, doer *User) error { | |||
| return newIssueLabel(e, issue, label, doer) | |||
| } | |||
| // AddLabel adds a new label to the issue. | |||
| func (issue *Issue) AddLabel(doer *User, label *Label) error { | |||
| if err := NewIssueLabel(issue, label); err != nil { | |||
| if err := NewIssueLabel(issue, label, doer); err != nil { | |||
| return err | |||
| } | |||
| @@ -303,13 +318,13 @@ func (issue *Issue) AddLabel(doer *User, label *Label) error { | |||
| return nil | |||
| } | |||
| func (issue *Issue) addLabels(e *xorm.Session, labels []*Label) error { | |||
| return newIssueLabels(e, issue, labels) | |||
| func (issue *Issue) addLabels(e *xorm.Session, labels []*Label, doer *User) error { | |||
| return newIssueLabels(e, issue, labels, doer) | |||
| } | |||
| // AddLabels adds a list of new labels to the issue. | |||
| func (issue *Issue) AddLabels(doer *User, labels []*Label) error { | |||
| if err := NewIssueLabels(issue, labels); err != nil { | |||
| if err := NewIssueLabels(issue, labels, doer); err != nil { | |||
| return err | |||
| } | |||
| @@ -329,8 +344,8 @@ func (issue *Issue) getLabels(e Engine) (err error) { | |||
| return nil | |||
| } | |||
| func (issue *Issue) removeLabel(e *xorm.Session, label *Label) error { | |||
| return deleteIssueLabel(e, issue, label) | |||
| func (issue *Issue) removeLabel(e *xorm.Session, doer *User, label *Label) error { | |||
| return deleteIssueLabel(e, doer, issue, label) | |||
| } | |||
| // RemoveLabel removes a label from issue by given ID. | |||
| @@ -345,7 +360,7 @@ func (issue *Issue) RemoveLabel(doer *User, label *Label) error { | |||
| return ErrLabelNotExist{} | |||
| } | |||
| if err := DeleteIssueLabel(issue, label); err != nil { | |||
| if err := DeleteIssueLabel(issue, doer, label); err != nil { | |||
| return err | |||
| } | |||
| @@ -353,13 +368,13 @@ func (issue *Issue) RemoveLabel(doer *User, label *Label) error { | |||
| return nil | |||
| } | |||
| func (issue *Issue) clearLabels(e *xorm.Session) (err error) { | |||
| func (issue *Issue) clearLabels(e *xorm.Session, doer *User) (err error) { | |||
| if err = issue.getLabels(e); err != nil { | |||
| return fmt.Errorf("getLabels: %v", err) | |||
| } | |||
| for i := range issue.Labels { | |||
| if err = issue.removeLabel(e, issue.Labels[i]); err != nil { | |||
| if err = issue.removeLabel(e, doer, issue.Labels[i]); err != nil { | |||
| return fmt.Errorf("removeLabel: %v", err) | |||
| } | |||
| } | |||
| @@ -386,7 +401,7 @@ func (issue *Issue) ClearLabels(doer *User) (err error) { | |||
| return ErrLabelNotExist{} | |||
| } | |||
| if err = issue.clearLabels(sess); err != nil { | |||
| if err = issue.clearLabels(sess, doer); err != nil { | |||
| return err | |||
| } | |||
| @@ -417,19 +432,75 @@ func (issue *Issue) ClearLabels(doer *User) (err error) { | |||
| return nil | |||
| } | |||
| type labelSorter []*Label | |||
| func (ts labelSorter) Len() int { | |||
| return len([]*Label(ts)) | |||
| } | |||
| func (ts labelSorter) Less(i, j int) bool { | |||
| return []*Label(ts)[i].ID < []*Label(ts)[j].ID | |||
| } | |||
| func (ts labelSorter) Swap(i, j int) { | |||
| []*Label(ts)[i], []*Label(ts)[j] = []*Label(ts)[j], []*Label(ts)[i] | |||
| } | |||
| // ReplaceLabels removes all current labels and add new labels to the issue. | |||
| // Triggers appropriate WebHooks, if any. | |||
| func (issue *Issue) ReplaceLabels(labels []*Label) (err error) { | |||
| func (issue *Issue) ReplaceLabels(labels []*Label, doer *User) (err error) { | |||
| sess := x.NewSession() | |||
| defer sessionRelease(sess) | |||
| if err = sess.Begin(); err != nil { | |||
| return err | |||
| } | |||
| if err = issue.clearLabels(sess); err != nil { | |||
| return fmt.Errorf("clearLabels: %v", err) | |||
| } else if err = issue.addLabels(sess, labels); err != nil { | |||
| return fmt.Errorf("addLabels: %v", err) | |||
| if err = issue.loadLabels(sess); err != nil { | |||
| return err | |||
| } | |||
| sort.Sort(labelSorter(labels)) | |||
| sort.Sort(labelSorter(issue.Labels)) | |||
| var toAdd, toRemove []*Label | |||
| for _, l := range labels { | |||
| var exist bool | |||
| for _, oriLabel := range issue.Labels { | |||
| if oriLabel.ID == l.ID { | |||
| exist = true | |||
| break | |||
| } | |||
| } | |||
| if !exist { | |||
| toAdd = append(toAdd, l) | |||
| } | |||
| } | |||
| for _, oriLabel := range issue.Labels { | |||
| var exist bool | |||
| for _, l := range labels { | |||
| if oriLabel.ID == l.ID { | |||
| exist = true | |||
| break | |||
| } | |||
| } | |||
| if !exist { | |||
| toRemove = append(toRemove, oriLabel) | |||
| } | |||
| } | |||
| if len(toAdd) > 0 { | |||
| if err = issue.addLabels(sess, toAdd, doer); err != nil { | |||
| return fmt.Errorf("addLabels: %v", err) | |||
| } | |||
| } | |||
| if len(toRemove) > 0 { | |||
| for _, l := range toRemove { | |||
| if err = issue.removeLabel(sess, doer, l); err != nil { | |||
| return fmt.Errorf("removeLabel: %v", err) | |||
| } | |||
| } | |||
| } | |||
| return sess.Commit() | |||
| @@ -731,13 +802,17 @@ func newIssue(e *xorm.Session, opts NewIssueOptions) (err error) { | |||
| return fmt.Errorf("find all labels [label_ids: %v]: %v", opts.LableIDs, err) | |||
| } | |||
| if err = opts.Issue.loadPoster(e); err != nil { | |||
| return err | |||
| } | |||
| for _, label := range labels { | |||
| // Silently drop invalid labels. | |||
| if label.RepoID != opts.Repo.ID { | |||
| continue | |||
| } | |||
| if err = opts.Issue.addLabel(e, label); err != nil { | |||
| if err = opts.Issue.addLabel(e, label, opts.Issue.Poster); err != nil { | |||
| return fmt.Errorf("addLabel [id: %d]: %v", label.ID, err) | |||
| } | |||
| } | |||
| @@ -36,6 +36,8 @@ const ( | |||
| CommentTypeCommentRef | |||
| // Reference from a pull request | |||
| CommentTypePullRef | |||
| // Labels changed | |||
| CommentTypeLabel | |||
| ) | |||
| // CommentTag defines comment tag type | |||
| @@ -57,6 +59,8 @@ type Comment struct { | |||
| Poster *User `xorm:"-"` | |||
| IssueID int64 `xorm:"INDEX"` | |||
| CommitID int64 | |||
| LabelID int64 | |||
| Label *Label `xorm:"-"` | |||
| Line int64 | |||
| Content string `xorm:"TEXT"` | |||
| RenderedContent string `xorm:"-"` | |||
| @@ -185,6 +189,21 @@ func (c *Comment) EventTag() string { | |||
| return "event-" + com.ToStr(c.ID) | |||
| } | |||
| // LoadLabel if comment.Type is CommentTypeLabel, then load Label | |||
| func (c *Comment) LoadLabel() error { | |||
| var label Label | |||
| has, err := x.ID(c.LabelID).Get(&label) | |||
| if err != nil { | |||
| return err | |||
| } else if !has { | |||
| return ErrLabelNotExist{ | |||
| LabelID: c.LabelID, | |||
| } | |||
| } | |||
| c.Label = &label | |||
| return nil | |||
| } | |||
| // MailParticipants sends new comment emails to repository watchers | |||
| // and mentioned people. | |||
| func (c *Comment) MailParticipants(e Engine, opType ActionType, issue *Issue) (err error) { | |||
| @@ -209,11 +228,16 @@ func (c *Comment) MailParticipants(e Engine, opType ActionType, issue *Issue) (e | |||
| } | |||
| func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err error) { | |||
| var LabelID int64 | |||
| if opts.Label != nil { | |||
| LabelID = opts.Label.ID | |||
| } | |||
| comment := &Comment{ | |||
| Type: opts.Type, | |||
| PosterID: opts.Doer.ID, | |||
| Poster: opts.Doer, | |||
| IssueID: opts.Issue.ID, | |||
| LabelID: LabelID, | |||
| CommitID: opts.CommitID, | |||
| CommitSHA: opts.CommitSHA, | |||
| Line: opts.LineNum, | |||
| @@ -223,6 +247,10 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err | |||
| return nil, err | |||
| } | |||
| if err = opts.Repo.getOwner(e); err != nil { | |||
| return nil, err | |||
| } | |||
| // Compose comment action, could be plain comment, close or reopen issue/pull request. | |||
| // This object will be used to notify watchers in the end of function. | |||
| act := &Action{ | |||
| @@ -324,12 +352,28 @@ func createStatusComment(e *xorm.Session, doer *User, repo *Repository, issue *I | |||
| }) | |||
| } | |||
| func createLabelComment(e *xorm.Session, doer *User, repo *Repository, issue *Issue, label *Label, add bool) (*Comment, error) { | |||
| var content string | |||
| if add { | |||
| content = "1" | |||
| } | |||
| return createComment(e, &CreateCommentOptions{ | |||
| Type: CommentTypeLabel, | |||
| Doer: doer, | |||
| Repo: repo, | |||
| Issue: issue, | |||
| Label: label, | |||
| Content: content, | |||
| }) | |||
| } | |||
| // CreateCommentOptions defines options for creating comment | |||
| type CreateCommentOptions struct { | |||
| Type CommentType | |||
| Doer *User | |||
| Repo *Repository | |||
| Issue *Issue | |||
| Label *Label | |||
| CommitID int64 | |||
| CommitSHA string | |||
| @@ -276,7 +276,7 @@ func HasIssueLabel(issueID, labelID int64) bool { | |||
| return hasIssueLabel(x, issueID, labelID) | |||
| } | |||
| func newIssueLabel(e *xorm.Session, issue *Issue, label *Label) (err error) { | |||
| func newIssueLabel(e *xorm.Session, issue *Issue, label *Label, doer *User) (err error) { | |||
| if _, err = e.Insert(&IssueLabel{ | |||
| IssueID: issue.ID, | |||
| LabelID: label.ID, | |||
| @@ -284,6 +284,14 @@ func newIssueLabel(e *xorm.Session, issue *Issue, label *Label) (err error) { | |||
| return err | |||
| } | |||
| if err = issue.loadRepo(e); err != nil { | |||
| return | |||
| } | |||
| if _, err = createLabelComment(e, doer, issue.Repo, issue, label, true); err != nil { | |||
| return err | |||
| } | |||
| label.NumIssues++ | |||
| if issue.IsClosed { | |||
| label.NumClosedIssues++ | |||
| @@ -292,7 +300,7 @@ func newIssueLabel(e *xorm.Session, issue *Issue, label *Label) (err error) { | |||
| } | |||
| // NewIssueLabel creates a new issue-label relation. | |||
| func NewIssueLabel(issue *Issue, label *Label) (err error) { | |||
| func NewIssueLabel(issue *Issue, label *Label, doer *User) (err error) { | |||
| if HasIssueLabel(issue.ID, label.ID) { | |||
| return nil | |||
| } | |||
| @@ -303,20 +311,20 @@ func NewIssueLabel(issue *Issue, label *Label) (err error) { | |||
| return err | |||
| } | |||
| if err = newIssueLabel(sess, issue, label); err != nil { | |||
| if err = newIssueLabel(sess, issue, label, doer); err != nil { | |||
| return err | |||
| } | |||
| return sess.Commit() | |||
| } | |||
| func newIssueLabels(e *xorm.Session, issue *Issue, labels []*Label) (err error) { | |||
| func newIssueLabels(e *xorm.Session, issue *Issue, labels []*Label, doer *User) (err error) { | |||
| for i := range labels { | |||
| if hasIssueLabel(e, issue.ID, labels[i].ID) { | |||
| continue | |||
| } | |||
| if err = newIssueLabel(e, issue, labels[i]); err != nil { | |||
| if err = newIssueLabel(e, issue, labels[i], doer); err != nil { | |||
| return fmt.Errorf("newIssueLabel: %v", err) | |||
| } | |||
| } | |||
| @@ -325,14 +333,14 @@ func newIssueLabels(e *xorm.Session, issue *Issue, labels []*Label) (err error) | |||
| } | |||
| // NewIssueLabels creates a list of issue-label relations. | |||
| func NewIssueLabels(issue *Issue, labels []*Label) (err error) { | |||
| func NewIssueLabels(issue *Issue, labels []*Label, doer *User) (err error) { | |||
| sess := x.NewSession() | |||
| defer sessionRelease(sess) | |||
| if err = sess.Begin(); err != nil { | |||
| return err | |||
| } | |||
| if err = newIssueLabels(sess, issue, labels); err != nil { | |||
| if err = newIssueLabels(sess, issue, labels, doer); err != nil { | |||
| return err | |||
| } | |||
| @@ -352,7 +360,7 @@ func GetIssueLabels(issueID int64) ([]*IssueLabel, error) { | |||
| return getIssueLabels(x, issueID) | |||
| } | |||
| func deleteIssueLabel(e *xorm.Session, issue *Issue, label *Label) (err error) { | |||
| func deleteIssueLabel(e *xorm.Session, doer *User, issue *Issue, label *Label) (err error) { | |||
| if _, err = e.Delete(&IssueLabel{ | |||
| IssueID: issue.ID, | |||
| LabelID: label.ID, | |||
| @@ -360,6 +368,14 @@ func deleteIssueLabel(e *xorm.Session, issue *Issue, label *Label) (err error) { | |||
| return err | |||
| } | |||
| if err = issue.loadRepo(e); err != nil { | |||
| return | |||
| } | |||
| if _, err = createLabelComment(e, doer, issue.Repo, issue, label, false); err != nil { | |||
| return err | |||
| } | |||
| label.NumIssues-- | |||
| if issue.IsClosed { | |||
| label.NumClosedIssues-- | |||
| @@ -368,14 +384,14 @@ func deleteIssueLabel(e *xorm.Session, issue *Issue, label *Label) (err error) { | |||
| } | |||
| // DeleteIssueLabel deletes issue-label relation. | |||
| func DeleteIssueLabel(issue *Issue, label *Label) (err error) { | |||
| func DeleteIssueLabel(issue *Issue, doer *User, label *Label) (err error) { | |||
| sess := x.NewSession() | |||
| defer sessionRelease(sess) | |||
| if err = sess.Begin(); err != nil { | |||
| return err | |||
| } | |||
| if err = deleteIssueLabel(sess, issue, label); err != nil { | |||
| if err = deleteIssueLabel(sess, doer, issue, label); err != nil { | |||
| return err | |||
| } | |||
| @@ -541,6 +541,8 @@ issues.label_templates.info = There aren't any labels yet. You can click on the | |||
| issues.label_templates.helper = Select a label set | |||
| issues.label_templates.use = Use this label set | |||
| issues.label_templates.fail_to_load_file = Failed to load label template file '%s': %v | |||
| issues.add_label_at = `added the <div class="ui label" style="color: %s; background-color: %s">%s</div> label %s` | |||
| issues.remove_label_at = `removed the <div class="ui label" style="color: %s; background-color: %s">%s</div> label %s` | |||
| issues.open_tab = %d Open | |||
| issues.close_tab = %d Closed | |||
| issues.filter_label = Label | |||
| @@ -501,6 +501,8 @@ issues.label_templates.info=此仓库还未创建任何标签,您可以通过 | |||
| issues.label_templates.helper=选择标签模板 | |||
| issues.label_templates.use=加载标签模板 | |||
| issues.label_templates.fail_to_load_file=加载标签模板文件 '%s' 时发生错误:%v | |||
| issues.add_label_at = ` %[4]s 添加了标签 <div class="ui label" style="color: %[1]s; background-color: %[2]s">%[3]s</div>` | |||
| issues.remove_label_at = ` %[4]s 删除了标签 <div class="ui label" style="color: %[1]s; background-color: %[2]s">%[3]s</div>` | |||
| issues.open_tab=%d 个开启中 | |||
| issues.close_tab=%d 个已关闭 | |||
| issues.filter_label=标签筛选 | |||
| @@ -108,6 +108,10 @@ function initCommentForm() { | |||
| }); | |||
| } | |||
| $('.select-label').dropdown('setting', 'onHide', function(){ | |||
| location.reload(); | |||
| }); | |||
| $labelMenu.find('.item:not(.no-select)').click(function () { | |||
| if ($(this).hasClass('checked')) { | |||
| $(this).removeClass('checked'); | |||
| @@ -98,7 +98,7 @@ func DeleteIssueLabel(ctx *context.APIContext) { | |||
| return | |||
| } | |||
| if err := models.DeleteIssueLabel(issue, label); err != nil { | |||
| if err := models.DeleteIssueLabel(issue, ctx.User, label); err != nil { | |||
| ctx.Error(500, "DeleteIssueLabel", err) | |||
| return | |||
| } | |||
| @@ -129,7 +129,7 @@ func ReplaceIssueLabels(ctx *context.APIContext, form api.IssueLabelsOption) { | |||
| return | |||
| } | |||
| if err := issue.ReplaceLabels(labels); err != nil { | |||
| if err := issue.ReplaceLabels(labels, ctx.User); err != nil { | |||
| ctx.Error(500, "ReplaceLabels", err) | |||
| return | |||
| } | |||
| @@ -34,8 +34,6 @@ const ( | |||
| tplIssueNew base.TplName = "repo/issue/new" | |||
| tplIssueView base.TplName = "repo/issue/view" | |||
| tplLabels base.TplName = "repo/issue/labels" | |||
| tplMilestone base.TplName = "repo/issue/milestones" | |||
| tplMilestoneNew base.TplName = "repo/issue/milestone_new" | |||
| tplMilestoneEdit base.TplName = "repo/issue/milestone_edit" | |||
| @@ -86,21 +84,6 @@ func MustAllowPulls(ctx *context.Context) { | |||
| } | |||
| } | |||
| // RetrieveLabels find all the labels of a repository | |||
| func RetrieveLabels(ctx *context.Context) { | |||
| labels, err := models.GetLabelsByRepoID(ctx.Repo.Repository.ID, ctx.Query("sort")) | |||
| if err != nil { | |||
| ctx.Handle(500, "RetrieveLabels.GetLabels", err) | |||
| return | |||
| } | |||
| for _, l := range labels { | |||
| l.CalOpenIssues() | |||
| } | |||
| ctx.Data["Labels"] = labels | |||
| ctx.Data["NumLabels"] = len(labels) | |||
| ctx.Data["SortType"] = ctx.Query("sort") | |||
| } | |||
| // Issues render issues page | |||
| func Issues(ctx *context.Context) { | |||
| isPullList := ctx.Params(":type") == "pulls" | |||
| @@ -629,6 +612,11 @@ func ViewIssue(ctx *context.Context) { | |||
| if !isAdded && !issue.IsPoster(comment.Poster.ID) { | |||
| participants = append(participants, comment.Poster) | |||
| } | |||
| } else if comment.Type == models.CommentTypeLabel { | |||
| if err = comment.LoadLabel(); err != nil { | |||
| ctx.Handle(500, "LoadLabel", err) | |||
| return | |||
| } | |||
| } | |||
| } | |||
| @@ -723,48 +711,6 @@ func UpdateIssueContent(ctx *context.Context) { | |||
| }) | |||
| } | |||
| // UpdateIssueLabel change issue's labels | |||
| func UpdateIssueLabel(ctx *context.Context) { | |||
| issue := getActionIssue(ctx) | |||
| if ctx.Written() { | |||
| return | |||
| } | |||
| if ctx.Query("action") == "clear" { | |||
| if err := issue.ClearLabels(ctx.User); err != nil { | |||
| ctx.Handle(500, "ClearLabels", err) | |||
| return | |||
| } | |||
| } else { | |||
| isAttach := ctx.Query("action") == "attach" | |||
| label, err := models.GetLabelByID(ctx.QueryInt64("id")) | |||
| if err != nil { | |||
| if models.IsErrLabelNotExist(err) { | |||
| ctx.Error(404, "GetLabelByID") | |||
| } else { | |||
| ctx.Handle(500, "GetLabelByID", err) | |||
| } | |||
| return | |||
| } | |||
| if isAttach && !issue.HasLabel(label.ID) { | |||
| if err = issue.AddLabel(ctx.User, label); err != nil { | |||
| ctx.Handle(500, "AddLabel", err) | |||
| return | |||
| } | |||
| } else if !isAttach && issue.HasLabel(label.ID) { | |||
| if err = issue.RemoveLabel(ctx.User, label); err != nil { | |||
| ctx.Handle(500, "RemoveLabel", err) | |||
| return | |||
| } | |||
| } | |||
| } | |||
| ctx.JSON(200, map[string]interface{}{ | |||
| "ok": true, | |||
| }) | |||
| } | |||
| // UpdateIssueMilestone change issue's milestone | |||
| func UpdateIssueMilestone(ctx *context.Context) { | |||
| issue := getActionIssue(ctx) | |||
| @@ -966,103 +912,6 @@ func DeleteComment(ctx *context.Context) { | |||
| ctx.Status(200) | |||
| } | |||
| // Labels render issue's labels page | |||
| func Labels(ctx *context.Context) { | |||
| ctx.Data["Title"] = ctx.Tr("repo.labels") | |||
| ctx.Data["PageIsIssueList"] = true | |||
| ctx.Data["PageIsLabels"] = true | |||
| ctx.Data["RequireMinicolors"] = true | |||
| ctx.Data["LabelTemplates"] = models.LabelTemplates | |||
| ctx.HTML(200, tplLabels) | |||
| } | |||
| // InitializeLabels init labels for a repository | |||
| func InitializeLabels(ctx *context.Context, form auth.InitializeLabelsForm) { | |||
| if ctx.HasError() { | |||
| ctx.Redirect(ctx.Repo.RepoLink + "/labels") | |||
| return | |||
| } | |||
| list, err := models.GetLabelTemplateFile(form.TemplateName) | |||
| if err != nil { | |||
| ctx.Flash.Error(ctx.Tr("repo.issues.label_templates.fail_to_load_file", form.TemplateName, err)) | |||
| ctx.Redirect(ctx.Repo.RepoLink + "/labels") | |||
| return | |||
| } | |||
| 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], | |||
| } | |||
| } | |||
| if err := models.NewLabels(labels...); err != nil { | |||
| ctx.Handle(500, "NewLabels", err) | |||
| return | |||
| } | |||
| ctx.Redirect(ctx.Repo.RepoLink + "/labels") | |||
| } | |||
| // NewLabel create new label for repository | |||
| func NewLabel(ctx *context.Context, form auth.CreateLabelForm) { | |||
| ctx.Data["Title"] = ctx.Tr("repo.labels") | |||
| ctx.Data["PageIsLabels"] = true | |||
| if ctx.HasError() { | |||
| ctx.Flash.Error(ctx.Data["ErrorMsg"].(string)) | |||
| ctx.Redirect(ctx.Repo.RepoLink + "/labels") | |||
| return | |||
| } | |||
| l := &models.Label{ | |||
| RepoID: ctx.Repo.Repository.ID, | |||
| Name: form.Title, | |||
| Color: form.Color, | |||
| } | |||
| if err := models.NewLabels(l); err != nil { | |||
| ctx.Handle(500, "NewLabel", err) | |||
| return | |||
| } | |||
| ctx.Redirect(ctx.Repo.RepoLink + "/labels") | |||
| } | |||
| // UpdateLabel update a label's name and color | |||
| func UpdateLabel(ctx *context.Context, form auth.CreateLabelForm) { | |||
| l, err := models.GetLabelByID(form.ID) | |||
| if err != nil { | |||
| switch { | |||
| case models.IsErrLabelNotExist(err): | |||
| ctx.Error(404) | |||
| default: | |||
| ctx.Handle(500, "UpdateLabel", err) | |||
| } | |||
| return | |||
| } | |||
| l.Name = form.Title | |||
| l.Color = form.Color | |||
| if err := models.UpdateLabel(l); err != nil { | |||
| ctx.Handle(500, "UpdateLabel", err) | |||
| return | |||
| } | |||
| ctx.Redirect(ctx.Repo.RepoLink + "/labels") | |||
| } | |||
| // DeleteLabel delete a label | |||
| func DeleteLabel(ctx *context.Context) { | |||
| if err := models.DeleteLabel(ctx.Repo.Repository.ID, ctx.QueryInt64("id")); err != nil { | |||
| ctx.Flash.Error("DeleteLabel: " + err.Error()) | |||
| } else { | |||
| ctx.Flash.Success(ctx.Tr("repo.issues.label_deletion_success")) | |||
| } | |||
| ctx.JSON(200, map[string]interface{}{ | |||
| "redirect": ctx.Repo.RepoLink + "/labels", | |||
| }) | |||
| return | |||
| } | |||
| // Milestones render milestones page | |||
| func Milestones(ctx *context.Context) { | |||
| ctx.Data["Title"] = ctx.Tr("repo.milestones") | |||
| @@ -0,0 +1,170 @@ | |||
| // Copyright 2017 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 repo | |||
| import ( | |||
| "code.gitea.io/gitea/models" | |||
| "code.gitea.io/gitea/modules/auth" | |||
| "code.gitea.io/gitea/modules/base" | |||
| "code.gitea.io/gitea/modules/context" | |||
| ) | |||
| const ( | |||
| tplLabels base.TplName = "repo/issue/labels" | |||
| ) | |||
| // Labels render issue's labels page | |||
| func Labels(ctx *context.Context) { | |||
| ctx.Data["Title"] = ctx.Tr("repo.labels") | |||
| ctx.Data["PageIsIssueList"] = true | |||
| ctx.Data["PageIsLabels"] = true | |||
| ctx.Data["RequireMinicolors"] = true | |||
| ctx.Data["LabelTemplates"] = models.LabelTemplates | |||
| ctx.HTML(200, tplLabels) | |||
| } | |||
| // InitializeLabels init labels for a repository | |||
| func InitializeLabels(ctx *context.Context, form auth.InitializeLabelsForm) { | |||
| if ctx.HasError() { | |||
| ctx.Redirect(ctx.Repo.RepoLink + "/labels") | |||
| return | |||
| } | |||
| list, err := models.GetLabelTemplateFile(form.TemplateName) | |||
| if err != nil { | |||
| ctx.Flash.Error(ctx.Tr("repo.issues.label_templates.fail_to_load_file", form.TemplateName, err)) | |||
| ctx.Redirect(ctx.Repo.RepoLink + "/labels") | |||
| return | |||
| } | |||
| 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], | |||
| } | |||
| } | |||
| if err := models.NewLabels(labels...); err != nil { | |||
| ctx.Handle(500, "NewLabels", err) | |||
| return | |||
| } | |||
| ctx.Redirect(ctx.Repo.RepoLink + "/labels") | |||
| } | |||
| // RetrieveLabels find all the labels of a repository | |||
| func RetrieveLabels(ctx *context.Context) { | |||
| labels, err := models.GetLabelsByRepoID(ctx.Repo.Repository.ID, ctx.Query("sort")) | |||
| if err != nil { | |||
| ctx.Handle(500, "RetrieveLabels.GetLabels", err) | |||
| return | |||
| } | |||
| for _, l := range labels { | |||
| l.CalOpenIssues() | |||
| } | |||
| ctx.Data["Labels"] = labels | |||
| ctx.Data["NumLabels"] = len(labels) | |||
| ctx.Data["SortType"] = ctx.Query("sort") | |||
| } | |||
| // NewLabel create new label for repository | |||
| func NewLabel(ctx *context.Context, form auth.CreateLabelForm) { | |||
| ctx.Data["Title"] = ctx.Tr("repo.labels") | |||
| ctx.Data["PageIsLabels"] = true | |||
| if ctx.HasError() { | |||
| ctx.Flash.Error(ctx.Data["ErrorMsg"].(string)) | |||
| ctx.Redirect(ctx.Repo.RepoLink + "/labels") | |||
| return | |||
| } | |||
| l := &models.Label{ | |||
| RepoID: ctx.Repo.Repository.ID, | |||
| Name: form.Title, | |||
| Color: form.Color, | |||
| } | |||
| if err := models.NewLabels(l); err != nil { | |||
| ctx.Handle(500, "NewLabel", err) | |||
| return | |||
| } | |||
| ctx.Redirect(ctx.Repo.RepoLink + "/labels") | |||
| } | |||
| // UpdateLabel update a label's name and color | |||
| func UpdateLabel(ctx *context.Context, form auth.CreateLabelForm) { | |||
| l, err := models.GetLabelByID(form.ID) | |||
| if err != nil { | |||
| switch { | |||
| case models.IsErrLabelNotExist(err): | |||
| ctx.Error(404) | |||
| default: | |||
| ctx.Handle(500, "UpdateLabel", err) | |||
| } | |||
| return | |||
| } | |||
| l.Name = form.Title | |||
| l.Color = form.Color | |||
| if err := models.UpdateLabel(l); err != nil { | |||
| ctx.Handle(500, "UpdateLabel", err) | |||
| return | |||
| } | |||
| ctx.Redirect(ctx.Repo.RepoLink + "/labels") | |||
| } | |||
| // DeleteLabel delete a label | |||
| func DeleteLabel(ctx *context.Context) { | |||
| if err := models.DeleteLabel(ctx.Repo.Repository.ID, ctx.QueryInt64("id")); err != nil { | |||
| ctx.Flash.Error("DeleteLabel: " + err.Error()) | |||
| } else { | |||
| ctx.Flash.Success(ctx.Tr("repo.issues.label_deletion_success")) | |||
| } | |||
| ctx.JSON(200, map[string]interface{}{ | |||
| "redirect": ctx.Repo.RepoLink + "/labels", | |||
| }) | |||
| return | |||
| } | |||
| // UpdateIssueLabel change issue's labels | |||
| func UpdateIssueLabel(ctx *context.Context) { | |||
| issue := getActionIssue(ctx) | |||
| if ctx.Written() { | |||
| return | |||
| } | |||
| if ctx.Query("action") == "clear" { | |||
| if err := issue.ClearLabels(ctx.User); err != nil { | |||
| ctx.Handle(500, "ClearLabels", err) | |||
| return | |||
| } | |||
| } else { | |||
| isAttach := ctx.Query("action") == "attach" | |||
| label, err := models.GetLabelByID(ctx.QueryInt64("id")) | |||
| if err != nil { | |||
| if models.IsErrLabelNotExist(err) { | |||
| ctx.Error(404, "GetLabelByID") | |||
| } else { | |||
| ctx.Handle(500, "GetLabelByID", err) | |||
| } | |||
| return | |||
| } | |||
| if isAttach && !issue.HasLabel(label.ID) { | |||
| if err = issue.AddLabel(ctx.User, label); err != nil { | |||
| ctx.Handle(500, "AddLabel", err) | |||
| return | |||
| } | |||
| } else if !isAttach && issue.HasLabel(label.ID) { | |||
| if err = issue.RemoveLabel(ctx.User, label); err != nil { | |||
| ctx.Handle(500, "RemoveLabel", err) | |||
| return | |||
| } | |||
| } | |||
| } | |||
| ctx.JSON(200, map[string]interface{}{ | |||
| "ok": true, | |||
| }) | |||
| } | |||
| @@ -58,7 +58,7 @@ | |||
| {{range .Issue.Comments}} | |||
| {{ $createdStr:= TimeSince .Created $.Lang }} | |||
| <!-- 0 = COMMENT, 1 = REOPEN, 2 = CLOSE, 3 = ISSUE_REF, 4 = COMMIT_REF, 5 = COMMENT_REF, 6 = PULL_REF --> | |||
| <!-- 0 = COMMENT, 1 = REOPEN, 2 = CLOSE, 3 = ISSUE_REF, 4 = COMMIT_REF, 5 = COMMENT_REF, 6 = PULL_REF, 7 = COMMENT_LABEL --> | |||
| {{if eq .Type 0}} | |||
| <div class="comment" id="{{.HashTag}}"> | |||
| <a class="avatar" {{if gt .Poster.ID 0}}href="{{.Poster.HomeLink}}"{{end}}> | |||
| @@ -144,6 +144,15 @@ | |||
| <span class="text grey">{{.Content | Str2html}}</span> | |||
| </div> | |||
| </div> | |||
| {{else if eq .Type 7}} | |||
| <div class="event"> | |||
| <span class="octicon octicon-primitive-dot"></span> | |||
| <a class="ui avatar image" href="{{.Poster.HomeLink}}"> | |||
| <img src="{{.Poster.RelAvatarLink}}"> | |||
| </a> | |||
| <span class="text grey"><a href="{{.Poster.HomeLink}}">{{.Poster.Name}}</a> | |||
| {{if .Content}}{{$.i18n.Tr "repo.issues.add_label_at" .Label.ForegroundColor .Label.Color .Label.Name $createdStr | Safe}}{{else}}{{$.i18n.Tr "repo.issues.remove_label_at" .Label.ForegroundColor .Label.Color .Label.Name $createdStr | Safe}}{{end}}</span> | |||
| </div> | |||
| {{end}} | |||
| {{end}} | |||