mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 16:40:24 +08:00 
			
		
		
		
	Batch updates for issues (#926)
This commit is contained in:
		
				
					committed by
					
						
						Kim "BKC" Carlbäcker
					
				
			
			
				
	
			
			
			
						parent
						
							021904e4e6
						
					
				
				
					commit
					09fe4a2ae9
				
			
							
								
								
									
										11
									
								
								cmd/web.go
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								cmd/web.go
									
									
									
									
									
								
							@@ -466,17 +466,16 @@ func runWeb(ctx *cli.Context) error {
 | 
				
			|||||||
			m.Combo("/new", repo.MustEnableIssues).Get(context.RepoRef(), repo.NewIssue).
 | 
								m.Combo("/new", repo.MustEnableIssues).Get(context.RepoRef(), repo.NewIssue).
 | 
				
			||||||
				Post(bindIgnErr(auth.CreateIssueForm{}), repo.NewIssuePost)
 | 
									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.Group("/:index", func() {
 | 
				
			||||||
				m.Post("/title", repo.UpdateIssueTitle)
 | 
									m.Post("/title", repo.UpdateIssueTitle)
 | 
				
			||||||
				m.Post("/content", repo.UpdateIssueContent)
 | 
									m.Post("/content", repo.UpdateIssueContent)
 | 
				
			||||||
				m.Combo("/comments").Post(bindIgnErr(auth.CreateCommentForm{}), repo.NewComment)
 | 
									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.Group("/comments/:id", func() {
 | 
				
			||||||
			m.Post("", repo.UpdateCommentContent)
 | 
								m.Post("", repo.UpdateCommentContent)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1002,6 +1002,16 @@ func GetIssueByID(id int64) (*Issue, error) {
 | 
				
			|||||||
	return getIssueByID(x, id)
 | 
						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.
 | 
					// IssuesOptions represents options of an issue.
 | 
				
			||||||
type IssuesOptions struct {
 | 
					type IssuesOptions struct {
 | 
				
			||||||
	RepoID      int64
 | 
						RepoID      int64
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -42,3 +42,19 @@ func TestIssueAPIURL(t *testing.T) {
 | 
				
			|||||||
	assert.NoError(t, err)
 | 
						assert.NoError(t, err)
 | 
				
			||||||
	assert.Equal(t, "https://try.gitea.io/api/v1/repos/user2/repo1/issues/1", issue.APIURL())
 | 
						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})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -583,6 +583,13 @@ issues.filter_sort.recentupdate = Recently updated
 | 
				
			|||||||
issues.filter_sort.leastupdate = Least recently updated
 | 
					issues.filter_sort.leastupdate = Least recently updated
 | 
				
			||||||
issues.filter_sort.mostcomment = Most commented
 | 
					issues.filter_sort.mostcomment = Most commented
 | 
				
			||||||
issues.filter_sort.leastcomment = Least 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 = opened %[1]s by <a href="%[2]s">%[3]s</a>
 | 
				
			||||||
issues.opened_by_fake = opened %[1]s by %[2]s
 | 
					issues.opened_by_fake = opened %[1]s by %[2]s
 | 
				
			||||||
issues.previous = Previous
 | 
					issues.previous = Previous
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2270,6 +2270,9 @@ footer .ui.language .menu {
 | 
				
			|||||||
#search-user-box .results .item img {
 | 
					#search-user-box .results .item img {
 | 
				
			||||||
  margin-right: 8px;
 | 
					  margin-right: 8px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					.issue-actions {
 | 
				
			||||||
 | 
					  display: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
.issue.list {
 | 
					.issue.list {
 | 
				
			||||||
  list-style: none;
 | 
					  list-style: none;
 | 
				
			||||||
  padding-top: 15px;
 | 
					  padding-top: 15px;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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() {
 | 
					function initCommentForm() {
 | 
				
			||||||
    if ($('.comment.form').length == 0) {
 | 
					    if ($('.comment.form').length == 0) {
 | 
				
			||||||
        return
 | 
					        return
 | 
				
			||||||
@@ -100,14 +114,6 @@ function initCommentForm() {
 | 
				
			|||||||
    var $labelMenu = $('.select-label .menu');
 | 
					    var $labelMenu = $('.select-label .menu');
 | 
				
			||||||
    var hasLabelUpdateAction = $labelMenu.data('action') == 'update';
 | 
					    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(){
 | 
					    $('.select-label').dropdown('setting', 'onHide', function(){
 | 
				
			||||||
        if (hasLabelUpdateAction) {
 | 
					        if (hasLabelUpdateAction) {
 | 
				
			||||||
            location.reload();
 | 
					            location.reload();
 | 
				
			||||||
@@ -119,13 +125,23 @@ function initCommentForm() {
 | 
				
			|||||||
            $(this).removeClass('checked');
 | 
					            $(this).removeClass('checked');
 | 
				
			||||||
            $(this).find('.octicon').removeClass('octicon-check');
 | 
					            $(this).find('.octicon').removeClass('octicon-check');
 | 
				
			||||||
            if (hasLabelUpdateAction) {
 | 
					            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 {
 | 
					        } else {
 | 
				
			||||||
            $(this).addClass('checked');
 | 
					            $(this).addClass('checked');
 | 
				
			||||||
            $(this).find('.octicon').addClass('octicon-check');
 | 
					            $(this).find('.octicon').addClass('octicon-check');
 | 
				
			||||||
            if (hasLabelUpdateAction) {
 | 
					            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 () {
 | 
					    $labelMenu.find('.no-select.item').click(function () {
 | 
				
			||||||
        if (hasLabelUpdateAction) {
 | 
					        if (hasLabelUpdateAction) {
 | 
				
			||||||
            updateIssueMeta($labelMenu.data('update-url'), "clear", '');
 | 
					            updateIssuesMeta(
 | 
				
			||||||
 | 
					                $labelMenu.data('update-url'),
 | 
				
			||||||
 | 
					                "clear",
 | 
				
			||||||
 | 
					                $labelMenu.data('issue-id'),
 | 
				
			||||||
 | 
					                ""
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $(this).parent().find('.item').each(function () {
 | 
					        $(this).parent().find('.item').each(function () {
 | 
				
			||||||
@@ -181,7 +202,12 @@ function initCommentForm() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            $(this).addClass('selected active');
 | 
					            $(this).addClass('selected active');
 | 
				
			||||||
            if (hasUpdateAction) {
 | 
					            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) {
 | 
					            switch (input_id) {
 | 
				
			||||||
                case '#milestone_id':
 | 
					                case '#milestone_id':
 | 
				
			||||||
@@ -202,7 +228,12 @@ function initCommentForm() {
 | 
				
			|||||||
            });
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (hasUpdateAction) {
 | 
					            if (hasUpdateAction) {
 | 
				
			||||||
                updateIssueMeta($menu.data('update-url'), '', '');
 | 
					                updateIssuesMeta(
 | 
				
			||||||
 | 
					                    $menu.data('update-url'),
 | 
				
			||||||
 | 
					                    "",
 | 
				
			||||||
 | 
					                    $menu.data('issue-id'),
 | 
				
			||||||
 | 
					                    $(this).data('id')
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            $list.find('.selected').html('');
 | 
					            $list.find('.selected').html('');
 | 
				
			||||||
@@ -1431,6 +1462,29 @@ $(document).ready(function () {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
    $('.markdown').autolink();
 | 
					    $('.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();
 | 
					    buttonsClickOnEnter();
 | 
				
			||||||
    searchUsers();
 | 
					    searchUsers();
 | 
				
			||||||
    searchRepositories();
 | 
					    searchRepositories();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1261,6 +1261,10 @@
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.issue-actions {
 | 
				
			||||||
 | 
					    display: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.issue.list {
 | 
					.issue.list {
 | 
				
			||||||
	list-style: none;
 | 
						list-style: none;
 | 
				
			||||||
	padding-top: 15px;
 | 
						padding-top: 15px;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,6 +10,7 @@ import (
 | 
				
			|||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"io"
 | 
						"io"
 | 
				
			||||||
	"io/ioutil"
 | 
						"io/ioutil"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -644,6 +645,28 @@ func getActionIssue(ctx *context.Context) *models.Issue {
 | 
				
			|||||||
	return 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
 | 
					// UpdateIssueTitle change issue's title
 | 
				
			||||||
func UpdateIssueTitle(ctx *context.Context) {
 | 
					func UpdateIssueTitle(ctx *context.Context) {
 | 
				
			||||||
	issue := getActionIssue(ctx)
 | 
						issue := getActionIssue(ctx)
 | 
				
			||||||
@@ -697,25 +720,22 @@ func UpdateIssueContent(ctx *context.Context) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// UpdateIssueMilestone change issue's milestone
 | 
					// UpdateIssueMilestone change issue's milestone
 | 
				
			||||||
func UpdateIssueMilestone(ctx *context.Context) {
 | 
					func UpdateIssueMilestone(ctx *context.Context) {
 | 
				
			||||||
	issue := getActionIssue(ctx)
 | 
						issues := getActionIssues(ctx)
 | 
				
			||||||
	if ctx.Written() {
 | 
						if ctx.Written() {
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	oldMilestoneID := issue.MilestoneID
 | 
					 | 
				
			||||||
	milestoneID := ctx.QueryInt64("id")
 | 
						milestoneID := ctx.QueryInt64("id")
 | 
				
			||||||
	if oldMilestoneID == milestoneID {
 | 
						for _, issue := range issues {
 | 
				
			||||||
		ctx.JSON(200, map[string]interface{}{
 | 
							oldMilestoneID := issue.MilestoneID
 | 
				
			||||||
			"ok": true,
 | 
							if oldMilestoneID == milestoneID {
 | 
				
			||||||
		})
 | 
								continue
 | 
				
			||||||
		return
 | 
							}
 | 
				
			||||||
	}
 | 
							issue.MilestoneID = milestoneID
 | 
				
			||||||
 | 
							if err := models.ChangeMilestoneAssign(issue, ctx.User, oldMilestoneID); err != nil {
 | 
				
			||||||
	// Not check for invalid milestone id and give responsibility to owners.
 | 
								ctx.Handle(500, "ChangeMilestoneAssign", err)
 | 
				
			||||||
	issue.MilestoneID = milestoneID
 | 
								return
 | 
				
			||||||
	if err := models.ChangeMilestoneAssign(issue, ctx.User, oldMilestoneID); err != nil {
 | 
							}
 | 
				
			||||||
		ctx.Handle(500, "ChangeMilestoneAssign", err)
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ctx.JSON(200, map[string]interface{}{
 | 
						ctx.JSON(200, map[string]interface{}{
 | 
				
			||||||
@@ -725,24 +745,53 @@ func UpdateIssueMilestone(ctx *context.Context) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// UpdateIssueAssignee change issue's assignee
 | 
					// UpdateIssueAssignee change issue's assignee
 | 
				
			||||||
func UpdateIssueAssignee(ctx *context.Context) {
 | 
					func UpdateIssueAssignee(ctx *context.Context) {
 | 
				
			||||||
	issue := getActionIssue(ctx)
 | 
						issues := getActionIssues(ctx)
 | 
				
			||||||
	if ctx.Written() {
 | 
						if ctx.Written() {
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	assigneeID := ctx.QueryInt64("id")
 | 
						assigneeID := ctx.QueryInt64("id")
 | 
				
			||||||
	if issue.AssigneeID == assigneeID {
 | 
						for _, issue := range issues {
 | 
				
			||||||
		ctx.JSON(200, map[string]interface{}{
 | 
							if issue.AssigneeID == assigneeID {
 | 
				
			||||||
			"ok": true,
 | 
								continue
 | 
				
			||||||
		})
 | 
							}
 | 
				
			||||||
 | 
							if err := issue.ChangeAssignee(ctx.User, assigneeID); err != nil {
 | 
				
			||||||
 | 
								ctx.Handle(500, "ChangeAssignee", err)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						ctx.JSON(200, map[string]interface{}{
 | 
				
			||||||
 | 
							"ok": true,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// UpdateIssueStatus change issue's status
 | 
				
			||||||
 | 
					func UpdateIssueStatus(ctx *context.Context) {
 | 
				
			||||||
 | 
						issues := getActionIssues(ctx)
 | 
				
			||||||
 | 
						if ctx.Written() {
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err := issue.ChangeAssignee(ctx.User, assigneeID); err != nil {
 | 
						var isClosed bool
 | 
				
			||||||
		ctx.Handle(500, "ChangeAssignee", err)
 | 
						switch action := ctx.Query("action"); action {
 | 
				
			||||||
		return
 | 
						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{}{
 | 
						ctx.JSON(200, map[string]interface{}{
 | 
				
			||||||
		"ok": true,
 | 
							"ok": true,
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,6 +9,7 @@ import (
 | 
				
			|||||||
	"code.gitea.io/gitea/modules/auth"
 | 
						"code.gitea.io/gitea/modules/auth"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/base"
 | 
						"code.gitea.io/gitea/modules/base"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/context"
 | 
						"code.gitea.io/gitea/modules/context"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/log"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const (
 | 
					const (
 | 
				
			||||||
@@ -129,18 +130,20 @@ func DeleteLabel(ctx *context.Context) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// UpdateIssueLabel change issue's labels
 | 
					// UpdateIssueLabel change issue's labels
 | 
				
			||||||
func UpdateIssueLabel(ctx *context.Context) {
 | 
					func UpdateIssueLabel(ctx *context.Context) {
 | 
				
			||||||
	issue := getActionIssue(ctx)
 | 
						issues := getActionIssues(ctx)
 | 
				
			||||||
	if ctx.Written() {
 | 
						if ctx.Written() {
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if ctx.Query("action") == "clear" {
 | 
						switch action := ctx.Query("action"); action {
 | 
				
			||||||
		if err := issue.ClearLabels(ctx.User); err != nil {
 | 
						case "clear":
 | 
				
			||||||
			ctx.Handle(500, "ClearLabels", err)
 | 
							for _, issue := range issues {
 | 
				
			||||||
			return
 | 
								if err := issue.ClearLabels(ctx.User); err != nil {
 | 
				
			||||||
 | 
									ctx.Handle(500, "ClearLabels", err)
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	} else {
 | 
						case "attach", "detach", "toggle":
 | 
				
			||||||
		isAttach := ctx.Query("action") == "attach"
 | 
					 | 
				
			||||||
		label, err := models.GetLabelByID(ctx.QueryInt64("id"))
 | 
							label, err := models.GetLabelByID(ctx.QueryInt64("id"))
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			if models.IsErrLabelNotExist(err) {
 | 
								if models.IsErrLabelNotExist(err) {
 | 
				
			||||||
@@ -151,17 +154,40 @@ func UpdateIssueLabel(ctx *context.Context) {
 | 
				
			|||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if isAttach && !issue.HasLabel(label.ID) {
 | 
							if action == "toggle" {
 | 
				
			||||||
			if err = issue.AddLabel(ctx.User, label); err != nil {
 | 
								anyHaveLabel := false
 | 
				
			||||||
				ctx.Handle(500, "AddLabel", err)
 | 
								for _, issue := range issues {
 | 
				
			||||||
				return
 | 
									if issue.HasLabel(label.ID) {
 | 
				
			||||||
 | 
										anyHaveLabel = true
 | 
				
			||||||
 | 
										break
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		} else if !isAttach && issue.HasLabel(label.ID) {
 | 
								if anyHaveLabel {
 | 
				
			||||||
			if err = issue.RemoveLabel(ctx.User, label); err != nil {
 | 
									action = "detach"
 | 
				
			||||||
				ctx.Handle(500, "RemoveLabel", err)
 | 
								} else {
 | 
				
			||||||
				return
 | 
									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{}{
 | 
						ctx.JSON(200, map[string]interface{}{
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,86 +14,147 @@
 | 
				
			|||||||
			</div>
 | 
								</div>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
		<div class="ui divider"></div>
 | 
							<div class="ui divider"></div>
 | 
				
			||||||
		<div class="ui tiny basic status buttons">
 | 
							<div class="issue-filters">
 | 
				
			||||||
			<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}}">
 | 
								<div class="ui tiny basic status buttons">
 | 
				
			||||||
				<i class="octicon octicon-issue-opened"></i>
 | 
									<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}}">
 | 
				
			||||||
				{{.i18n.Tr "repo.issues.open_tab" .IssueStats.OpenCount}}
 | 
										<i class="octicon octicon-issue-opened"></i>
 | 
				
			||||||
			</a>
 | 
										{{.i18n.Tr "repo.issues.open_tab" .IssueStats.OpenCount}}
 | 
				
			||||||
			<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}}">
 | 
									</a>
 | 
				
			||||||
				<i class="octicon octicon-issue-closed"></i>
 | 
									<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}}">
 | 
				
			||||||
				{{.i18n.Tr "repo.issues.close_tab" .IssueStats.ClosedCount}}
 | 
										<i class="octicon octicon-issue-closed"></i>
 | 
				
			||||||
			</a>
 | 
										{{.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}}
 | 
				
			||||||
 | 
										</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}}
 | 
				
			||||||
 | 
										</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>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									<!-- 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>
 | 
				
			||||||
		<div class="ui right floated secondary filter menu">
 | 
							<div class="issue-actions">
 | 
				
			||||||
			<!-- Label -->
 | 
								<div class="ui basic status buttons">
 | 
				
			||||||
			<div class="ui {{if not .Labels}}disabled{{end}} dropdown jump item">
 | 
									<div class="ui green active basic button issue-action" data-action="open" data-url="{{$.Link}}/status">{{.i18n.Tr "repo.issues.action_open"}}</div>
 | 
				
			||||||
				<span class="text">
 | 
									<div class="ui red active basic button issue-action" data-action="close" data-url="{{$.Link}}/status">{{.i18n.Tr "repo.issues.action_close"}}</div>
 | 
				
			||||||
					{{.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>
 | 
								</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			<!-- Milestone -->
 | 
								<div class="ui secondary filter menu floated right">
 | 
				
			||||||
			<div class="ui {{if not .Milestones}}disabled{{end}} dropdown jump item">
 | 
									<!-- Labels -->
 | 
				
			||||||
				<span class="text">
 | 
									<div class="ui {{if not .Labels}}disabled{{end}} dropdown jump item">
 | 
				
			||||||
					{{.i18n.Tr "repo.issues.filter_milestone"}}
 | 
										<span class="text">
 | 
				
			||||||
					<i class="dropdown icon"></i>
 | 
											{{.i18n.Tr "repo.issues.action_label"}}
 | 
				
			||||||
				</span>
 | 
											<i class="dropdown icon"></i>
 | 
				
			||||||
				<div class="menu">
 | 
										</span>
 | 
				
			||||||
					<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>
 | 
										<div class="menu">
 | 
				
			||||||
					{{range .Milestones}}
 | 
											{{range .Labels}}
 | 
				
			||||||
						<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>
 | 
												<div class="item issue-action" data-action="toggle" data-element-id="{{.ID}}" data-url="{{$.Link}}/labels">
 | 
				
			||||||
					{{end}}
 | 
													<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>
 | 
									</div>
 | 
				
			||||||
			</div>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
			<!-- Assignee -->
 | 
									<!-- Milestone -->
 | 
				
			||||||
			<div class="ui {{if not .Assignees}}disabled{{end}} dropdown jump item">
 | 
									<div class="ui {{if not .Milestones}}disabled{{end}} dropdown jump item">
 | 
				
			||||||
				<span class="text">
 | 
										<span class="text">
 | 
				
			||||||
					{{.i18n.Tr "repo.issues.filter_assignee"}}
 | 
											{{.i18n.Tr "repo.issues.action_milestone"}}
 | 
				
			||||||
					<i class="dropdown icon"></i>
 | 
											<i class="dropdown icon"></i>
 | 
				
			||||||
				</span>
 | 
										</span>
 | 
				
			||||||
				<div class="menu">
 | 
										<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>
 | 
											<div class="item issue-action" data-element-id="0" data-url="{{$.Link}}/milestone">
 | 
				
			||||||
					{{range .Assignees}}
 | 
											  {{.i18n.Tr "repo.issues.action_milestone_no_select"}}
 | 
				
			||||||
						<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>
 | 
											</div>
 | 
				
			||||||
					{{end}}
 | 
											{{range .Milestones}}
 | 
				
			||||||
 | 
												<div class="item issue-action" data-element-id="{{.ID}}" data-url="{{$.Link}}/milestone">
 | 
				
			||||||
 | 
													{{.Name | Sanitize}}
 | 
				
			||||||
 | 
												</div>
 | 
				
			||||||
 | 
											{{end}}
 | 
				
			||||||
 | 
										</div>
 | 
				
			||||||
				</div>
 | 
									</div>
 | 
				
			||||||
			</div>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
			<!-- Type -->
 | 
									<!-- Assignee -->
 | 
				
			||||||
			<div class="ui dropdown type jump item">
 | 
									<div class="ui {{if not .Assignees}}disabled{{end}} dropdown jump item">
 | 
				
			||||||
				<span class="text">
 | 
										<span class="text">
 | 
				
			||||||
					{{.i18n.Tr "repo.issues.filter_type"}}
 | 
											{{.i18n.Tr "repo.issues.action_assignee"}}
 | 
				
			||||||
					<i class="dropdown icon"></i>
 | 
											<i class="dropdown icon"></i>
 | 
				
			||||||
				</span>
 | 
										</span>
 | 
				
			||||||
				<div class="menu">
 | 
										<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>
 | 
											<div class="item issue-action" data-element-id="0" data-url="{{$.Link}}/assignee">
 | 
				
			||||||
					<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>
 | 
												{{.i18n.Tr "repo.issues.action_assignee_no_select"}}
 | 
				
			||||||
					<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>
 | 
											</div>
 | 
				
			||||||
					<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>
 | 
											{{range .Assignees}}
 | 
				
			||||||
				</div>
 | 
												<div class="item issue-action" data-element-id="{{.ID}}" data-url="{{$.Link}}/assignee">
 | 
				
			||||||
			</div>
 | 
													<img src="{{.RelAvatarLink}}"> {{.Name}}
 | 
				
			||||||
 | 
												</div>
 | 
				
			||||||
			<!-- Sort -->
 | 
											{{end}}
 | 
				
			||||||
			<div class="ui dropdown type jump item">
 | 
										</div>
 | 
				
			||||||
				<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>
 | 
							</div>
 | 
				
			||||||
@@ -102,6 +163,9 @@
 | 
				
			|||||||
			{{range .Issues}}
 | 
								{{range .Issues}}
 | 
				
			||||||
				{{ $timeStr:= TimeSince .Created $.Lang }}
 | 
									{{ $timeStr:= TimeSince .Created $.Lang }}
 | 
				
			||||||
				<li class="item">
 | 
									<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>
 | 
										<div class="ui {{if .IsRead}}black{{else}}green{{end}} label">#{{.Index}}</div>
 | 
				
			||||||
					<a class="title has-emoji" href="{{$.Link}}/{{.Index}}">{{.Title}}</a>
 | 
										<a class="title has-emoji" href="{{$.Link}}/{{.Index}}">{{.Title}}</a>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -311,7 +311,7 @@
 | 
				
			|||||||
					<strong>{{.i18n.Tr "repo.issues.new.labels"}}</strong>
 | 
										<strong>{{.i18n.Tr "repo.issues.new.labels"}}</strong>
 | 
				
			||||||
					<span class="octicon octicon-gear"></span>
 | 
										<span class="octicon octicon-gear"></span>
 | 
				
			||||||
				</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>
 | 
										<div class="no-select item">{{.i18n.Tr "repo.issues.new.clear_labels"}}</div>
 | 
				
			||||||
					{{range .Labels}}
 | 
										{{range .Labels}}
 | 
				
			||||||
						<a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" data-id-selector="#label_{{.ID}}"><span class="octicon {{if .IsChecked}}octicon-check{{end}}"></span><span class="label color" style="background-color: {{.Color}}"></span> {{.Name}}</a>
 | 
											<a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" data-id-selector="#label_{{.ID}}"><span class="octicon {{if .IsChecked}}octicon-check{{end}}"></span><span class="label color" style="background-color: {{.Color}}"></span> {{.Name}}</a>
 | 
				
			||||||
@@ -335,7 +335,7 @@
 | 
				
			|||||||
					<strong>{{.i18n.Tr "repo.issues.new.milestone"}}</strong>
 | 
										<strong>{{.i18n.Tr "repo.issues.new.milestone"}}</strong>
 | 
				
			||||||
					<span class="octicon octicon-gear"></span>
 | 
										<span class="octicon octicon-gear"></span>
 | 
				
			||||||
				</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>
 | 
										<div class="no-select item">{{.i18n.Tr "repo.issues.new.clear_milestone"}}</div>
 | 
				
			||||||
					{{if .OpenMilestones}}
 | 
										{{if .OpenMilestones}}
 | 
				
			||||||
						<div class="divider"></div>
 | 
											<div class="divider"></div>
 | 
				
			||||||
@@ -376,7 +376,7 @@
 | 
				
			|||||||
					<strong>{{.i18n.Tr "repo.issues.new.assignee"}}</strong>
 | 
										<strong>{{.i18n.Tr "repo.issues.new.assignee"}}</strong>
 | 
				
			||||||
					<span class="octicon octicon-gear"></span>
 | 
										<span class="octicon octicon-gear"></span>
 | 
				
			||||||
				</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>
 | 
										<div class="no-select item">{{.i18n.Tr "repo.issues.new.clear_assignee"}}</div>
 | 
				
			||||||
					{{range .Assignees}}
 | 
										{{range .Assignees}}
 | 
				
			||||||
						<div class="item" data-id="{{.ID}}" data-href="{{$.RepoLink}}/issues?assignee={{.ID}}" data-avatar="{{.RelAvatarLink}}"><img src="{{.RelAvatarLink}}"> {{.Name}}</div>
 | 
											<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>
 | 
							{{.i18n.Tr "repo.branch.delete_notices_2" .HeadTarget | Safe}}<br>
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
	{{template "base/delete_modal_actions" .}}
 | 
						{{template "base/delete_modal_actions" .}}
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user