diff --git a/models/db/search.go b/models/db/search.go index f5273cb6f6b..26e082756a5 100644 --- a/models/db/search.go +++ b/models/db/search.go @@ -27,3 +27,9 @@ const ( SearchOrderByForks SearchOrderBy = "num_forks ASC" SearchOrderByForksReverse SearchOrderBy = "num_forks DESC" ) + +const ( + // Which means a condition to filter the records which don't match any id. + // It's different from zero which means the condition could be ignored. + NoneID = -1 +) diff --git a/models/issues/issue.go b/models/issues/issue.go index 78cac900523..3ddc7992709 100644 --- a/models/issues/issue.go +++ b/models/issues/issue.go @@ -1251,6 +1251,8 @@ func (opts *IssuesOptions) setupSessionNoLimit(sess *xorm.Session) { if opts.ProjectID > 0 { sess.Join("INNER", "project_issue", "issue.id = project_issue.issue_id"). And("project_issue.project_id=?", opts.ProjectID) + } else if opts.ProjectID == db.NoneID { // show those that are in no project + sess.And(builder.NotIn("issue.id", builder.Select("issue_id").From("project_issue"))) } if opts.ProjectBoardID != 0 { diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 26217293a5e..f384056613c 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1306,7 +1306,8 @@ issues.filter_label_no_select = All labels issues.filter_milestone = Milestone issues.filter_milestone_no_select = All milestones issues.filter_project = Project -issues.filter_project_no_select = All projects +issues.filter_project_all = All projects +issues.filter_project_none = No project issues.filter_assignee = Assignee issues.filter_assginee_no_select = All assignees issues.filter_poster = Author diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index 5bff9e67f34..2193da5110e 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -363,16 +363,10 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti return 0 } - projects, _, err := project_model.FindProjects(ctx, project_model.SearchOptions{ - RepoID: repo.ID, - Type: project_model.TypeRepository, - IsClosed: util.OptionalBoolOf(isShowClosed), - }) - if err != nil { - ctx.ServerError("FindProjects", err) + retrieveProjects(ctx, repo) + if ctx.Written() { return } - ctx.Data["Projects"] = projects ctx.Data["IssueStats"] = issueStats ctx.Data["SelLabelIDs"] = labelIDs diff --git a/templates/repo/issue/list.tmpl b/templates/repo/issue/list.tmpl index 23bcc60f94f..cf2a2a6bbab 100644 --- a/templates/repo/issue/list.tmpl +++ b/templates/repo/issue/list.tmpl @@ -75,19 +75,41 @@ </div> <!-- Project --> - <div class="ui {{if not .Projects}}disabled{{end}} dropdown jump item"> + <div class="ui{{if not (or .OpenProjects .ClosedProjects)}} disabled{{end}} dropdown jump item"> <span class="text"> - {{.locale.Tr "repo.issues.filter_project"}} + {{.locale.Tr "repo.issues.filter_projects"}} {{svg "octicon-triangle-down" 14 "dropdown icon"}} </span> <div class="menu"> <div class="ui icon search input"> <i class="icon df ac jc">{{svg "octicon-search" 16}}</i> - <input type="text" placeholder="{{.locale.Tr "repo.issues.filter_project"}}"> + <input type="text" placeholder="{{.locale.Tr "repo.issues.filter_projects"}}"> </div> - <a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_project_no_select"}}</a> - {{range .Projects}} - <a class="{{if $.ProjectID}}{{if eq $.ProjectID .ID}}active selected{{end}}{{end}} item" href="{{$.Link}}?type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{$.MilestoneID}}&project={{.ID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.Title}}</a> + <a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_project_all"}}</a> + <a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&project=-1&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_project_none"}}</a> + {{if .OpenProjects}} + <div class="divider"></div> + <div class="header"> + {{.locale.Tr "repo.issues.new.open_projects"}} + </div> + {{range .OpenProjects}} + <a class="{{if $.ProjectID}}{{if eq $.ProjectID .ID}}active selected{{end}}{{end}} item" href="{{$.Link}}?type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{$.MilestoneID}}&project={{.ID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}"> + {{if .IsOrganizationProject}}{{svg "octicon-project-symlink" 18 "mr-3"}}{{else}}{{svg "octicon-project" 18 "mr-3"}}{{end}} + {{.Title}} + </a> + {{end}} + {{end}} + {{if .ClosedProjects}} + <div class="divider"></div> + <div class="header"> + {{.locale.Tr "repo.issues.new.closed_projects"}} + </div> + {{range .ClosedProjects}} + <a class="{{if $.ProjectID}}{{if eq $.ProjectID .ID}}active selected{{end}}{{end}} item" href="{{$.Link}}?type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{$.MilestoneID}}&project={{.ID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}"> + {{if .IsOrganizationProject}}{{svg "octicon-project-symlink" 18 "mr-3"}}{{else}}{{svg "octicon-project" 18 "mr-3"}}{{end}} + {{.Title}} + </a> + {{end}} {{end}} </div> </div> @@ -222,18 +244,38 @@ </div> <!-- Projects --> - <div class="ui {{if not .Projects}}disabled{{end}} dropdown jump item"> + <div class="ui{{if not (or .OpenProjects .ClosedProjects)}} disabled{{end}} dropdown jump item"> <span class="text"> {{.locale.Tr "repo.project_board"}} {{svg "octicon-triangle-down" 14 "dropdown icon"}} </span> <div class="menu"> <div class="item issue-action" data-element-id="0" data-url="{{$.Link}}/projects"> - {{.locale.Tr "repo.issues.new.no_projects"}} + {{.locale.Tr "repo.issues.new.clear_projects"}} </div> - {{range .Projects}} - <div class="item issue-action" data-element-id="{{.ID}}" data-url="{{$.RepoLink}}/issues/projects"> - {{.Title}} + {{if .OpenProjects}} + <div class="divider"></div> + <div class="header"> + {{.locale.Tr "repo.issues.new.open_projects"}} + </div> + {{range .OpenProjects}} + <div class="item issue-action" data-element-id="{{.ID}}" data-url="{{$.RepoLink}}/issues/projects"> + {{if .IsOrganizationProject}}{{svg "octicon-project-symlink" 18 "mr-3"}}{{else}}{{svg "octicon-project" 18 "mr-3"}}{{end}} + {{.Title}} + </div> + {{end}} + {{end}} + {{if .ClosedProjects}} + <div class="divider"></div> + <div class="header"> + {{.locale.Tr "repo.issues.new.closed_projects"}} + </div> + {{range .ClosedProjects}} + <div class="item issue-action" data-element-id="{{.ID}}" data-url="{{$.RepoLink}}/issues/projects"> + {{if .IsOrganizationProject}}{{svg "octicon-project-symlink" 18 "mr-3"}}{{else}}{{svg "octicon-project" 18 "mr-3"}}{{end}} + {{.Title}} + </div> + {{end}} </div> {{end}} </div>