diff options
-rw-r--r-- | cmd/web.go | 11 | ||||
-rw-r--r-- | models/issue.go | 10 | ||||
-rw-r--r-- | models/issue_test.go | 16 | ||||
-rw-r--r-- | options/locale/locale_en-US.ini | 7 | ||||
-rw-r--r-- | public/css/index.css | 3 | ||||
-rw-r--r-- | public/js/index.js | 80 | ||||
-rw-r--r-- | public/less/_repository.less | 4 | ||||
-rw-r--r-- | routers/repo/issue.go | 93 | ||||
-rw-r--r-- | routers/repo/issue_label.go | 56 | ||||
-rw-r--r-- | templates/repo/issue/list.tmpl | 206 | ||||
-rw-r--r-- | templates/repo/issue/view_content.tmpl | 8 |
11 files changed, 363 insertions, 131 deletions
diff --git a/cmd/web.go b/cmd/web.go index af32592d0b..0410ad5190 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -467,16 +467,15 @@ func runWeb(ctx *cli.Context) error { Post(bindIgnErr(auth.CreateIssueForm{}), repo.NewIssuePost) m.Group("/:index", func() { - m.Post("/label", repo.UpdateIssueLabel) - m.Post("/milestone", repo.UpdateIssueMilestone) - m.Post("/assignee", repo.UpdateIssueAssignee) - }, reqRepoWriter) - - m.Group("/:index", func() { m.Post("/title", repo.UpdateIssueTitle) m.Post("/content", repo.UpdateIssueContent) m.Combo("/comments").Post(bindIgnErr(auth.CreateCommentForm{}), repo.NewComment) }) + + m.Post("/labels", repo.UpdateIssueLabel, reqRepoWriter) + m.Post("/milestone", repo.UpdateIssueMilestone, reqRepoWriter) + m.Post("/assignee", repo.UpdateIssueAssignee, reqRepoWriter) + m.Post("/status", repo.UpdateIssueStatus, reqRepoWriter) }) m.Group("/comments/:id", func() { m.Post("", repo.UpdateCommentContent) diff --git a/models/issue.go b/models/issue.go index eab494bdea..347598300e 100644 --- a/models/issue.go +++ b/models/issue.go @@ -1002,6 +1002,16 @@ func GetIssueByID(id int64) (*Issue, error) { return getIssueByID(x, id) } +func getIssuesByIDs(e Engine, issueIDs []int64) ([]*Issue, error) { + issues := make([]*Issue, 0, 10) + return issues, e.In("id", issueIDs).Find(&issues) +} + +// GetIssuesByIDs return issues with the given IDs. +func GetIssuesByIDs(issueIDs []int64) ([]*Issue, error) { + return getIssuesByIDs(x, issueIDs) +} + // IssuesOptions represents options of an issue. type IssuesOptions struct { RepoID int64 diff --git a/models/issue_test.go b/models/issue_test.go index a6da80917e..7c80258c2b 100644 --- a/models/issue_test.go +++ b/models/issue_test.go @@ -42,3 +42,19 @@ func TestIssueAPIURL(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://try.gitea.io/api/v1/repos/user2/repo1/issues/1", issue.APIURL()) } + +func TestGetIssuesByIDs(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + testSuccess := func(expectedIssueIDs []int64, nonExistentIssueIDs []int64) { + issues, err := GetIssuesByIDs(append(expectedIssueIDs, nonExistentIssueIDs...)) + assert.NoError(t, err) + actualIssueIDs := make([]int64, len(issues)) + for i, issue := range issues { + actualIssueIDs[i] = issue.ID + } + assert.Equal(t, expectedIssueIDs, actualIssueIDs) + + } + testSuccess([]int64{1, 2, 3}, []int64{}) + testSuccess([]int64{1, 2, 3}, []int64{NonexistentID}) +} diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index d39589bd30..ad75f28c97 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -583,6 +583,13 @@ issues.filter_sort.recentupdate = Recently updated issues.filter_sort.leastupdate = Least recently updated issues.filter_sort.mostcomment = Most commented issues.filter_sort.leastcomment = Least commented +issues.action_open = Open +issues.action_close = Close +issues.action_label = Label +issues.action_milestone = Milestone +issues.action_milestone_no_select = No milestone +issues.action_assignee = Assignee +issues.action_assignee_no_select = No assignee issues.opened_by = opened %[1]s by <a href="%[2]s">%[3]s</a> issues.opened_by_fake = opened %[1]s by %[2]s issues.previous = Previous diff --git a/public/css/index.css b/public/css/index.css index 54a0c23727..e840659a21 100644 --- a/public/css/index.css +++ b/public/css/index.css @@ -2270,6 +2270,9 @@ footer .ui.language .menu { #search-user-box .results .item img { margin-right: 8px; } +.issue-actions { + display: none; +} .issue.list { list-style: none; padding-top: 15px; diff --git a/public/js/index.js b/public/js/index.js index 98a0efff47..e139d16748 100644 --- a/public/js/index.js +++ b/public/js/index.js @@ -87,6 +87,20 @@ function initEditForm() { } +function updateIssuesMeta(url, action, issueIds, elementId, afterSuccess) { + $.ajax({ + type: "POST", + url: url, + data: { + "_csrf": csrf, + "action": action, + "issue_ids": issueIds, + "id": elementId + }, + success: afterSuccess + }) +} + function initCommentForm() { if ($('.comment.form').length == 0) { return @@ -100,14 +114,6 @@ function initCommentForm() { var $labelMenu = $('.select-label .menu'); var hasLabelUpdateAction = $labelMenu.data('action') == 'update'; - function updateIssueMeta(url, action, id) { - $.post(url, { - "_csrf": csrf, - "action": action, - "id": id - }); - } - $('.select-label').dropdown('setting', 'onHide', function(){ if (hasLabelUpdateAction) { location.reload(); @@ -119,13 +125,23 @@ function initCommentForm() { $(this).removeClass('checked'); $(this).find('.octicon').removeClass('octicon-check'); if (hasLabelUpdateAction) { - updateIssueMeta($labelMenu.data('update-url'), "detach", $(this).data('id')); + updateIssuesMeta( + $labelMenu.data('update-url'), + "detach", + $labelMenu.data('issue-id'), + $(this).data('id') + ); } } else { $(this).addClass('checked'); $(this).find('.octicon').addClass('octicon-check'); if (hasLabelUpdateAction) { - updateIssueMeta($labelMenu.data('update-url'), "attach", $(this).data('id')); + updateIssuesMeta( + $labelMenu.data('update-url'), + "attach", + $labelMenu.data('issue-id'), + $(this).data('id') + ); } } @@ -148,7 +164,12 @@ function initCommentForm() { }); $labelMenu.find('.no-select.item').click(function () { if (hasLabelUpdateAction) { - updateIssueMeta($labelMenu.data('update-url'), "clear", ''); + updateIssuesMeta( + $labelMenu.data('update-url'), + "clear", + $labelMenu.data('issue-id'), + "" + ); } $(this).parent().find('.item').each(function () { @@ -181,7 +202,12 @@ function initCommentForm() { $(this).addClass('selected active'); if (hasUpdateAction) { - updateIssueMeta($menu.data('update-url'), '', $(this).data('id')); + updateIssuesMeta( + $menu.data('update-url'), + "", + $menu.data('issue-id'), + $(this).data('id') + ); } switch (input_id) { case '#milestone_id': @@ -202,7 +228,12 @@ function initCommentForm() { }); if (hasUpdateAction) { - updateIssueMeta($menu.data('update-url'), '', ''); + updateIssuesMeta( + $menu.data('update-url'), + "", + $menu.data('issue-id'), + $(this).data('id') + ); } $list.find('.selected').html(''); @@ -1431,6 +1462,29 @@ $(document).ready(function () { }); $('.markdown').autolink(); + $('.issue-checkbox').click(function() { + var numChecked = $('.issue-checkbox').children('input:checked').length; + if (numChecked > 0) { + $('.issue-filters').hide(); + $('.issue-actions').show(); + } else { + $('.issue-filters').show(); + $('.issue-actions').hide(); + } + }); + + $('.issue-action').click(function () { + var action = this.dataset.action + var elementId = this.dataset.elementId + var issueIDs = $('.issue-checkbox').children('input:checked').map(function() { + return this.dataset.issueId; + }).get().join(); + var url = this.dataset.url + updateIssuesMeta(url, action, issueIDs, elementId, function() { + location.reload(); + }); + }); + buttonsClickOnEnter(); searchUsers(); searchRepositories(); diff --git a/public/less/_repository.less b/public/less/_repository.less index fcf6efda27..2009847670 100644 --- a/public/less/_repository.less +++ b/public/less/_repository.less @@ -1261,6 +1261,10 @@ } } +.issue-actions { + display: none; +} + .issue.list { list-style: none; padding-top: 15px; diff --git a/routers/repo/issue.go b/routers/repo/issue.go index a06f21b859..0a723d755b 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -10,6 +10,7 @@ import ( "fmt" "io" "io/ioutil" + "strconv" "strings" "time" @@ -644,6 +645,28 @@ func getActionIssue(ctx *context.Context) *models.Issue { return issue } +func getActionIssues(ctx *context.Context) []*models.Issue { + commaSeparatedIssueIDs := ctx.Query("issue_ids") + if len(commaSeparatedIssueIDs) == 0 { + return nil + } + issueIDs := make([]int64, 0, 10) + for _, stringIssueID := range strings.Split(commaSeparatedIssueIDs, ",") { + issueID, err := strconv.ParseInt(stringIssueID, 10, 64) + if err != nil { + ctx.Handle(500, "ParseInt", err) + return nil + } + issueIDs = append(issueIDs, issueID) + } + issues, err := models.GetIssuesByIDs(issueIDs) + if err != nil { + ctx.Handle(500, "GetIssuesByIDs", err) + return nil + } + return issues +} + // UpdateIssueTitle change issue's title func UpdateIssueTitle(ctx *context.Context) { issue := getActionIssue(ctx) @@ -697,25 +720,22 @@ func UpdateIssueContent(ctx *context.Context) { // UpdateIssueMilestone change issue's milestone func UpdateIssueMilestone(ctx *context.Context) { - issue := getActionIssue(ctx) + issues := getActionIssues(ctx) if ctx.Written() { return } - oldMilestoneID := issue.MilestoneID milestoneID := ctx.QueryInt64("id") - if oldMilestoneID == milestoneID { - ctx.JSON(200, map[string]interface{}{ - "ok": true, - }) - return - } - - // Not check for invalid milestone id and give responsibility to owners. - issue.MilestoneID = milestoneID - if err := models.ChangeMilestoneAssign(issue, ctx.User, oldMilestoneID); err != nil { - ctx.Handle(500, "ChangeMilestoneAssign", err) - return + for _, issue := range issues { + oldMilestoneID := issue.MilestoneID + if oldMilestoneID == milestoneID { + continue + } + issue.MilestoneID = milestoneID + if err := models.ChangeMilestoneAssign(issue, ctx.User, oldMilestoneID); err != nil { + ctx.Handle(500, "ChangeMilestoneAssign", err) + return + } } ctx.JSON(200, map[string]interface{}{ @@ -725,24 +745,53 @@ func UpdateIssueMilestone(ctx *context.Context) { // UpdateIssueAssignee change issue's assignee func UpdateIssueAssignee(ctx *context.Context) { - issue := getActionIssue(ctx) + issues := getActionIssues(ctx) if ctx.Written() { return } assigneeID := ctx.QueryInt64("id") - if issue.AssigneeID == assigneeID { - ctx.JSON(200, map[string]interface{}{ - "ok": true, - }) - return + for _, issue := range issues { + if issue.AssigneeID == assigneeID { + continue + } + if err := issue.ChangeAssignee(ctx.User, assigneeID); err != nil { + ctx.Handle(500, "ChangeAssignee", err) + return + } } + ctx.JSON(200, map[string]interface{}{ + "ok": true, + }) +} - if err := issue.ChangeAssignee(ctx.User, assigneeID); err != nil { - ctx.Handle(500, "ChangeAssignee", err) +// UpdateIssueStatus change issue's status +func UpdateIssueStatus(ctx *context.Context) { + issues := getActionIssues(ctx) + if ctx.Written() { return } + var isClosed bool + switch action := ctx.Query("action"); action { + case "open": + isClosed = false + case "close": + isClosed = true + default: + log.Warn("Unrecognized action: %s", action) + } + + if _, err := models.IssueList(issues).LoadRepositories(); err != nil { + ctx.Handle(500, "LoadRepositories", err) + return + } + for _, issue := range issues { + if err := issue.ChangeStatus(ctx.User, issue.Repo, isClosed); err != nil { + ctx.Handle(500, "ChangeStatus", err) + return + } + } ctx.JSON(200, map[string]interface{}{ "ok": true, }) diff --git a/routers/repo/issue_label.go b/routers/repo/issue_label.go index 6792947669..966c2c1c53 100644 --- a/routers/repo/issue_label.go +++ b/routers/repo/issue_label.go @@ -9,6 +9,7 @@ import ( "code.gitea.io/gitea/modules/auth" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/log" ) const ( @@ -129,18 +130,20 @@ func DeleteLabel(ctx *context.Context) { // UpdateIssueLabel change issue's labels func UpdateIssueLabel(ctx *context.Context) { - issue := getActionIssue(ctx) + issues := getActionIssues(ctx) if ctx.Written() { return } - if ctx.Query("action") == "clear" { - if err := issue.ClearLabels(ctx.User); err != nil { - ctx.Handle(500, "ClearLabels", err) - return + switch action := ctx.Query("action"); action { + case "clear": + for _, issue := range issues { + if err := issue.ClearLabels(ctx.User); err != nil { + ctx.Handle(500, "ClearLabels", err) + return + } } - } else { - isAttach := ctx.Query("action") == "attach" + case "attach", "detach", "toggle": label, err := models.GetLabelByID(ctx.QueryInt64("id")) if err != nil { if models.IsErrLabelNotExist(err) { @@ -151,17 +154,40 @@ func UpdateIssueLabel(ctx *context.Context) { return } - if isAttach && !issue.HasLabel(label.ID) { - if err = issue.AddLabel(ctx.User, label); err != nil { - ctx.Handle(500, "AddLabel", err) - return + if action == "toggle" { + anyHaveLabel := false + for _, issue := range issues { + if issue.HasLabel(label.ID) { + anyHaveLabel = true + break + } } - } else if !isAttach && issue.HasLabel(label.ID) { - if err = issue.RemoveLabel(ctx.User, label); err != nil { - ctx.Handle(500, "RemoveLabel", err) - return + if anyHaveLabel { + action = "detach" + } else { + action = "attach" + } + } + + if action == "attach" { + for _, issue := range issues { + if err = issue.AddLabel(ctx.User, label); err != nil { + ctx.Handle(500, "AddLabel", err) + return + } + } + } else { + for _, issue := range issues { + if err = issue.RemoveLabel(ctx.User, label); err != nil { + ctx.Handle(500, "RemoveLabel", err) + return + } } } + default: + log.Warn("Unrecognized action: %s", action) + ctx.Error(500) + return } ctx.JSON(200, map[string]interface{}{ diff --git a/templates/repo/issue/list.tmpl b/templates/repo/issue/list.tmpl index 1b060cb231..863ae6a74d 100644 --- a/templates/repo/issue/list.tmpl +++ b/templates/repo/issue/list.tmpl @@ -14,86 +14,147 @@ </div> </div> <div class="ui divider"></div> - <div class="ui tiny basic status buttons"> - <a class="ui {{if not .IsShowClosed}}green active{{end}} basic button" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state=open&labels={{.SelectLabels}}&milestone={{.MilestoneID}}&assignee={{.AssigneeID}}"> - <i class="octicon octicon-issue-opened"></i> - {{.i18n.Tr "repo.issues.open_tab" .IssueStats.OpenCount}} - </a> - <a class="ui {{if .IsShowClosed}}red active{{end}} basic button" href="{{$.Link}}?q={{$.Keyword}}&type={{.ViewType}}&sort={{$.SortType}}&state=closed&labels={{.SelectLabels}}&milestone={{.MilestoneID}}&assignee={{.AssigneeID}}"> - <i class="octicon octicon-issue-closed"></i> - {{.i18n.Tr "repo.issues.close_tab" .IssueStats.ClosedCount}} - </a> - </div> - <div class="ui right floated secondary filter menu"> - <!-- Label --> - <div class="ui {{if not .Labels}}disabled{{end}} dropdown jump item"> - <span class="text"> - {{.i18n.Tr "repo.issues.filter_label"}} - <i class="dropdown icon"></i> - </span> - <div class="menu"> - <a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}">{{.i18n.Tr "repo.issues.filter_label_no_select"}}</a> - {{range .Labels}} - <a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.ID}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}"><span class="octicon {{if eq $.SelectLabels .ID}}octicon-check{{end}}"></span><span class="label color" style="background-color: {{.Color}}"></span> {{.Name | Sanitize}}</a> - {{end}} - </div> + <div class="issue-filters"> + <div class="ui tiny basic status buttons"> + <a class="ui {{if not .IsShowClosed}}green active{{end}} basic button" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state=open&labels={{.SelectLabels}}&milestone={{.MilestoneID}}&assignee={{.AssigneeID}}"> + <i class="octicon octicon-issue-opened"></i> + {{.i18n.Tr "repo.issues.open_tab" .IssueStats.OpenCount}} + </a> + <a class="ui {{if .IsShowClosed}}red active{{end}} basic button" href="{{$.Link}}?q={{$.Keyword}}&type={{.ViewType}}&sort={{$.SortType}}&state=closed&labels={{.SelectLabels}}&milestone={{.MilestoneID}}&assignee={{.AssigneeID}}"> + <i class="octicon octicon-issue-closed"></i> + {{.i18n.Tr "repo.issues.close_tab" .IssueStats.ClosedCount}} + </a> </div> + <div class="ui right floated secondary filter menu"> + <!-- Label --> + <div class="ui {{if not .Labels}}disabled{{end}} dropdown jump item"> + <span class="text"> + {{.i18n.Tr "repo.issues.filter_label"}} + <i class="dropdown icon"></i> + </span> + <div class="menu"> + <a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}">{{.i18n.Tr "repo.issues.filter_label_no_select"}}</a> + {{range .Labels}} + <a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.ID}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}"><span class="octicon {{if eq $.SelectLabels .ID}}octicon-check{{end}}"></span><span class="label color" style="background-color: {{.Color}}"></span> {{.Name | Sanitize}}</a> + {{end}} + </div> + </div> - <!-- Milestone --> - <div class="ui {{if not .Milestones}}disabled{{end}} dropdown jump item"> - <span class="text"> - {{.i18n.Tr "repo.issues.filter_milestone"}} - <i class="dropdown icon"></i> - </span> - <div class="menu"> - <a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&assignee={{$.AssigneeID}}">{{.i18n.Tr "repo.issues.filter_milestone_no_select"}}</a> - {{range .Milestones}} - <a class="{{if eq $.MilestoneID .ID}}active selected{{end}} item" href="{{$.Link}}?type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{.ID}}&assignee={{$.AssigneeID}}">{{.Name | Sanitize}}</a> - {{end}} + <!-- Milestone --> + <div class="ui {{if not .Milestones}}disabled{{end}} dropdown jump item"> + <span class="text"> + {{.i18n.Tr "repo.issues.filter_milestone"}} + <i class="dropdown icon"></i> + </span> + <div class="menu"> + <a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&assignee={{$.AssigneeID}}">{{.i18n.Tr "repo.issues.filter_milestone_no_select"}}</a> + {{range .Milestones}} + <a class="{{if eq $.MilestoneID .ID}}active selected{{end}} item" href="{{$.Link}}?type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{.ID}}&assignee={{$.AssigneeID}}">{{.Name | Sanitize}}</a> + {{end}} + </div> </div> - </div> - <!-- Assignee --> - <div class="ui {{if not .Assignees}}disabled{{end}} dropdown jump item"> - <span class="text"> - {{.i18n.Tr "repo.issues.filter_assignee"}} - <i class="dropdown icon"></i> - </span> - <div class="menu"> - <a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}">{{.i18n.Tr "repo.issues.filter_assginee_no_select"}}</a> - {{range .Assignees}} - <a class="{{if eq $.AssigneeID .ID}}active selected{{end}} item" href="{{$.Link}}?type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{$.MilestoneID}}&assignee={{.ID}}"><img src="{{.RelAvatarLink}}"> {{.Name}}</a> - {{end}} + <!-- Assignee --> + <div class="ui {{if not .Assignees}}disabled{{end}} dropdown jump item"> + <span class="text"> + {{.i18n.Tr "repo.issues.filter_assignee"}} + <i class="dropdown icon"></i> + </span> + <div class="menu"> + <a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}">{{.i18n.Tr "repo.issues.filter_assginee_no_select"}}</a> + {{range .Assignees}} + <a class="{{if eq $.AssigneeID .ID}}active selected{{end}} item" href="{{$.Link}}?type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{$.MilestoneID}}&assignee={{.ID}}"><img src="{{.RelAvatarLink}}"> {{.Name}}</a> + {{end}} + </div> + </div> + + <!-- Type --> + <div class="ui dropdown type jump item"> + <span class="text"> + {{.i18n.Tr "repo.issues.filter_type"}} + <i class="dropdown icon"></i> + </span> + <div class="menu"> + <a class="{{if eq .ViewType "all"}}active{{end}} item" href="{{$.Link}}?q={{$.Keyword}}&type=all&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}">{{.i18n.Tr "repo.issues.filter_type.all_issues"}}</a> + <a class="{{if eq .ViewType "assigned"}}active{{end}} item" href="{{$.Link}}?q={{$.Keyword}}&type=assigned&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}">{{.i18n.Tr "repo.issues.filter_type.assigned_to_you"}}</a> + <a class="{{if eq .ViewType "created_by"}}active{{end}} item" href="{{$.Link}}?q={{$.Keyword}}&type=created_by&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}">{{.i18n.Tr "repo.issues.filter_type.created_by_you"}}</a> + <a class="{{if eq .ViewType "mentioned"}}active{{end}} item" href="{{$.Link}}?q={{$.Keyword}}&type=mentioned&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}">{{.i18n.Tr "repo.issues.filter_type.mentioning_you"}}</a> + </div> </div> - </div> - <!-- Type --> - <div class="ui dropdown type jump item"> - <span class="text"> - {{.i18n.Tr "repo.issues.filter_type"}} - <i class="dropdown icon"></i> - </span> - <div class="menu"> - <a class="{{if eq .ViewType "all"}}active{{end}} item" href="{{$.Link}}?q={{$.Keyword}}&type=all&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}">{{.i18n.Tr "repo.issues.filter_type.all_issues"}}</a> - <a class="{{if eq .ViewType "assigned"}}active{{end}} item" href="{{$.Link}}?q={{$.Keyword}}&type=assigned&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}">{{.i18n.Tr "repo.issues.filter_type.assigned_to_you"}}</a> - <a class="{{if eq .ViewType "created_by"}}active{{end}} item" href="{{$.Link}}?q={{$.Keyword}}&type=created_by&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}">{{.i18n.Tr "repo.issues.filter_type.created_by_you"}}</a> - <a class="{{if eq .ViewType "mentioned"}}active{{end}} item" href="{{$.Link}}?q={{$.Keyword}}&type=mentioned&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}">{{.i18n.Tr "repo.issues.filter_type.mentioning_you"}}</a> + <!-- Sort --> + <div class="ui dropdown type jump item"> + <span class="text"> + {{.i18n.Tr "repo.issues.filter_sort"}} + <i class="dropdown icon"></i> + </span> + <div class="menu"> + <a class="{{if or (eq .SortType "latest") (not .SortType)}}active{{end}} item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort=latest&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}">{{.i18n.Tr "repo.issues.filter_sort.latest"}}</a> + <a class="{{if eq .SortType "oldest"}}active{{end}} item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort=oldest&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}">{{.i18n.Tr "repo.issues.filter_sort.oldest"}}</a> + <a class="{{if eq .SortType "recentupdate"}}active{{end}} item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort=recentupdate&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}">{{.i18n.Tr "repo.issues.filter_sort.recentupdate"}}</a> + <a class="{{if eq .SortType "leastupdate"}}active{{end}} item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort=leastupdate&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}">{{.i18n.Tr "repo.issues.filter_sort.leastupdate"}}</a> + <a class="{{if eq .SortType "mostcomment"}}active{{end}} item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort=mostcomment&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}">{{.i18n.Tr "repo.issues.filter_sort.mostcomment"}}</a> + <a class="{{if eq .SortType "leastcomment"}}active{{end}} item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort=leastcomment&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}">{{.i18n.Tr "repo.issues.filter_sort.leastcomment"}}</a> + </div> </div> </div> + </div> + <div class="issue-actions"> + <div class="ui basic status buttons"> + <div class="ui green active basic button issue-action" data-action="open" data-url="{{$.Link}}/status">{{.i18n.Tr "repo.issues.action_open"}}</div> + <div class="ui red active basic button issue-action" data-action="close" data-url="{{$.Link}}/status">{{.i18n.Tr "repo.issues.action_close"}}</div> + </div> - <!-- Sort --> - <div class="ui dropdown type jump item"> - <span class="text"> - {{.i18n.Tr "repo.issues.filter_sort"}} - <i class="dropdown icon"></i> - </span> - <div class="menu"> - <a class="{{if or (eq .SortType "latest") (not .SortType)}}active{{end}} item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort=latest&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}">{{.i18n.Tr "repo.issues.filter_sort.latest"}}</a> - <a class="{{if eq .SortType "oldest"}}active{{end}} item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort=oldest&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}">{{.i18n.Tr "repo.issues.filter_sort.oldest"}}</a> - <a class="{{if eq .SortType "recentupdate"}}active{{end}} item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort=recentupdate&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}">{{.i18n.Tr "repo.issues.filter_sort.recentupdate"}}</a> - <a class="{{if eq .SortType "leastupdate"}}active{{end}} item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort=leastupdate&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}">{{.i18n.Tr "repo.issues.filter_sort.leastupdate"}}</a> - <a class="{{if eq .SortType "mostcomment"}}active{{end}} item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort=mostcomment&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}">{{.i18n.Tr "repo.issues.filter_sort.mostcomment"}}</a> - <a class="{{if eq .SortType "leastcomment"}}active{{end}} item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort=leastcomment&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}">{{.i18n.Tr "repo.issues.filter_sort.leastcomment"}}</a> + <div class="ui secondary filter menu floated right"> + <!-- Labels --> + <div class="ui {{if not .Labels}}disabled{{end}} dropdown jump item"> + <span class="text"> + {{.i18n.Tr "repo.issues.action_label"}} + <i class="dropdown icon"></i> + </span> + <div class="menu"> + {{range .Labels}} + <div class="item issue-action" data-action="toggle" data-element-id="{{.ID}}" data-url="{{$.Link}}/labels"> + <span class="octicon {{if eq $.SelectLabels .ID}}octicon-check{{end}}"></span><span class="label color" style="background-color: {{.Color}}"></span> {{.Name | Sanitize}} + </div> + {{end}} + </div> + </div> + + <!-- Milestone --> + <div class="ui {{if not .Milestones}}disabled{{end}} dropdown jump item"> + <span class="text"> + {{.i18n.Tr "repo.issues.action_milestone"}} + <i class="dropdown icon"></i> + </span> + <div class="menu"> + <div class="item issue-action" data-element-id="0" data-url="{{$.Link}}/milestone"> + {{.i18n.Tr "repo.issues.action_milestone_no_select"}} + </div> + {{range .Milestones}} + <div class="item issue-action" data-element-id="{{.ID}}" data-url="{{$.Link}}/milestone"> + {{.Name | Sanitize}} + </div> + {{end}} + </div> + </div> + + <!-- Assignee --> + <div class="ui {{if not .Assignees}}disabled{{end}} dropdown jump item"> + <span class="text"> + {{.i18n.Tr "repo.issues.action_assignee"}} + <i class="dropdown icon"></i> + </span> + <div class="menu"> + <div class="item issue-action" data-element-id="0" data-url="{{$.Link}}/assignee"> + {{.i18n.Tr "repo.issues.action_assignee_no_select"}} + </div> + {{range .Assignees}} + <div class="item issue-action" data-element-id="{{.ID}}" data-url="{{$.Link}}/assignee"> + <img src="{{.RelAvatarLink}}"> {{.Name}} + </div> + {{end}} + </div> </div> </div> </div> @@ -102,6 +163,9 @@ {{range .Issues}} {{ $timeStr:= TimeSince .Created $.Lang }} <li class="item"> + <div class="ui checkbox issue-checkbox"> + <input type="checkbox" data-issue-id={{.ID}}></input> + </div> <div class="ui {{if .IsRead}}black{{else}}green{{end}} label">#{{.Index}}</div> <a class="title has-emoji" href="{{$.Link}}/{{.Index}}">{{.Title}}</a> diff --git a/templates/repo/issue/view_content.tmpl b/templates/repo/issue/view_content.tmpl index 42e9f01c0b..4e35614dbb 100644 --- a/templates/repo/issue/view_content.tmpl +++ b/templates/repo/issue/view_content.tmpl @@ -311,7 +311,7 @@ <strong>{{.i18n.Tr "repo.issues.new.labels"}}</strong> <span class="octicon octicon-gear"></span> </span> - <div class="filter menu" data-action="update" data-update-url="{{$.RepoLink}}/issues/{{$.Issue.Index}}/label"> + <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> @@ -335,7 +335,7 @@ <strong>{{.i18n.Tr "repo.issues.new.milestone"}}</strong> <span class="octicon octicon-gear"></span> </span> - <div class="menu" data-action="update" data-update-url="{{$.RepoLink}}/issues/{{$.Issue.Index}}/milestone"> + <div class="menu" data-action="update" data-issue-id="{{$.Issue.ID}}" data-update-url="{{$.RepoLink}}/issues/milestone"> <div class="no-select item">{{.i18n.Tr "repo.issues.new.clear_milestone"}}</div> {{if .OpenMilestones}} <div class="divider"></div> @@ -376,7 +376,7 @@ <strong>{{.i18n.Tr "repo.issues.new.assignee"}}</strong> <span class="octicon octicon-gear"></span> </span> - <div class="menu" data-action="update" data-update-url="{{$.RepoLink}}/issues/{{$.Issue.Index}}/assignee"> + <div class="menu" data-action="update" data-issue-id="{{$.Issue.ID}}" data-update-url="{{$.RepoLink}}/issues/assignee"> <div class="no-select item">{{.i18n.Tr "repo.issues.new.clear_assignee"}}</div> {{range .Assignees}} <div class="item" data-id="{{.ID}}" data-href="{{$.RepoLink}}/issues?assignee={{.ID}}" data-avatar="{{.RelAvatarLink}}"><img src="{{.RelAvatarLink}}"> {{.Name}}</div> @@ -442,4 +442,4 @@ {{.i18n.Tr "repo.branch.delete_notices_2" .HeadTarget | Safe}}<br> </div> {{template "base/delete_modal_actions" .}} -</div>
\ No newline at end of file +</div> |