summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--cmd/web.go11
-rw-r--r--models/issue.go10
-rw-r--r--models/issue_test.go16
-rw-r--r--options/locale/locale_en-US.ini7
-rw-r--r--public/css/index.css3
-rw-r--r--public/js/index.js80
-rw-r--r--public/less/_repository.less4
-rw-r--r--routers/repo/issue.go93
-rw-r--r--routers/repo/issue_label.go56
-rw-r--r--templates/repo/issue/list.tmpl206
-rw-r--r--templates/repo/issue/view_content.tmpl8
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>