mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 21:28:11 +09:00 
			
		
		
		
	Add pages to view watched repos and subscribed issues/PRs (#17156)
Adds GitHub-like pages to view watched repos and subscribed issues/PRs This is my second try to fix this, but it is better than the first since it doesn't uses a filter option which could be slow when accessing `/issues` or `/pulls` and it shows both pulls and issues (the first try is #17053). Closes #16111 Replaces and closes #17053  Co-authored-by: Lauris BH <lauris@nix.lv> Co-authored-by: 6543 <6543@obermui.de> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
		| @@ -1186,6 +1186,7 @@ type IssuesOptions struct { //nolint | |||||||
| 	PosterID           int64 | 	PosterID           int64 | ||||||
| 	MentionedID        int64 | 	MentionedID        int64 | ||||||
| 	ReviewRequestedID  int64 | 	ReviewRequestedID  int64 | ||||||
|  | 	SubscriberID       int64 | ||||||
| 	MilestoneIDs       []int64 | 	MilestoneIDs       []int64 | ||||||
| 	ProjectID          int64 | 	ProjectID          int64 | ||||||
| 	ProjectBoardID     int64 | 	ProjectBoardID     int64 | ||||||
| @@ -1299,6 +1300,10 @@ func (opts *IssuesOptions) setupSessionNoLimit(sess *xorm.Session) { | |||||||
| 		applyReviewRequestedCondition(sess, opts.ReviewRequestedID) | 		applyReviewRequestedCondition(sess, opts.ReviewRequestedID) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if opts.SubscriberID > 0 { | ||||||
|  | 		applySubscribedCondition(sess, opts.SubscriberID) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	if len(opts.MilestoneIDs) > 0 { | 	if len(opts.MilestoneIDs) > 0 { | ||||||
| 		sess.In("issue.milestone_id", opts.MilestoneIDs) | 		sess.In("issue.milestone_id", opts.MilestoneIDs) | ||||||
| 	} | 	} | ||||||
| @@ -1463,6 +1468,36 @@ func applyReviewRequestedCondition(sess *xorm.Session, reviewRequestedID int64) | |||||||
| 			reviewRequestedID, ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest, reviewRequestedID) | 			reviewRequestedID, ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest, reviewRequestedID) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func applySubscribedCondition(sess *xorm.Session, subscriberID int64) *xorm.Session { | ||||||
|  | 	return sess.And( | ||||||
|  | 		builder. | ||||||
|  | 			NotIn("issue.id", | ||||||
|  | 				builder.Select("issue_id"). | ||||||
|  | 					From("issue_watch"). | ||||||
|  | 					Where(builder.Eq{"is_watching": false, "user_id": subscriberID}), | ||||||
|  | 			), | ||||||
|  | 	).And( | ||||||
|  | 		builder.Or( | ||||||
|  | 			builder.In("issue.id", builder. | ||||||
|  | 				Select("issue_id"). | ||||||
|  | 				From("issue_watch"). | ||||||
|  | 				Where(builder.Eq{"is_watching": true, "user_id": subscriberID}), | ||||||
|  | 			), | ||||||
|  | 			builder.In("issue.id", builder. | ||||||
|  | 				Select("issue_id"). | ||||||
|  | 				From("comment"). | ||||||
|  | 				Where(builder.Eq{"poster_id": subscriberID}), | ||||||
|  | 			), | ||||||
|  | 			builder.Eq{"issue.poster_id": subscriberID}, | ||||||
|  | 			builder.In("issue.repo_id", builder. | ||||||
|  | 				Select("id"). | ||||||
|  | 				From("watch"). | ||||||
|  | 				Where(builder.Eq{"user_id": subscriberID, "mode": true}), | ||||||
|  | 			), | ||||||
|  | 		), | ||||||
|  | 	) | ||||||
|  | } | ||||||
|  |  | ||||||
| // CountIssuesByRepo map from repoID to number of issues matching the options | // CountIssuesByRepo map from repoID to number of issues matching the options | ||||||
| func CountIssuesByRepo(opts *IssuesOptions) (map[int64]int64, error) { | func CountIssuesByRepo(opts *IssuesOptions) (map[int64]int64, error) { | ||||||
| 	e := db.GetEngine(db.DefaultContext) | 	e := db.GetEngine(db.DefaultContext) | ||||||
|   | |||||||
| @@ -3034,6 +3034,9 @@ pin = Pin notification | |||||||
| mark_as_read = Mark as read | mark_as_read = Mark as read | ||||||
| mark_as_unread = Mark as unread | mark_as_unread = Mark as unread | ||||||
| mark_all_as_read = Mark all as read | mark_all_as_read = Mark all as read | ||||||
|  | subscriptions = Subscriptions | ||||||
|  | watching = Watching | ||||||
|  | no_subscriptions = No subscriptions | ||||||
|  |  | ||||||
| [gpg] | [gpg] | ||||||
| default_key=Signed with default key | default_key=Signed with default key | ||||||
|   | |||||||
| @@ -13,16 +13,23 @@ import ( | |||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
| 	activities_model "code.gitea.io/gitea/models/activities" | 	activities_model "code.gitea.io/gitea/models/activities" | ||||||
|  | 	"code.gitea.io/gitea/models/db" | ||||||
|  | 	issues_model "code.gitea.io/gitea/models/issues" | ||||||
|  | 	repo_model "code.gitea.io/gitea/models/repo" | ||||||
| 	"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" | 	"code.gitea.io/gitea/modules/log" | ||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"code.gitea.io/gitea/modules/setting" | ||||||
| 	"code.gitea.io/gitea/modules/structs" | 	"code.gitea.io/gitea/modules/structs" | ||||||
|  | 	"code.gitea.io/gitea/modules/util" | ||||||
|  | 	issue_service "code.gitea.io/gitea/services/issue" | ||||||
|  | 	pull_service "code.gitea.io/gitea/services/pull" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| const ( | const ( | ||||||
| 	tplNotification    base.TplName = "user/notification/notification" | 	tplNotification              base.TplName = "user/notification/notification" | ||||||
| 	tplNotificationDiv base.TplName = "user/notification/notification_div" | 	tplNotificationDiv           base.TplName = "user/notification/notification_div" | ||||||
|  | 	tplNotificationSubscriptions base.TplName = "user/notification/notification_subscriptions" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // GetNotificationCount is the middleware that sets the notification count in the context | // GetNotificationCount is the middleware that sets the notification count in the context | ||||||
| @@ -197,6 +204,208 @@ func NotificationPurgePost(c *context.Context) { | |||||||
| 	c.Redirect(setting.AppSubURL+"/notifications", http.StatusSeeOther) | 	c.Redirect(setting.AppSubURL+"/notifications", http.StatusSeeOther) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // NotificationSubscriptions returns the list of subscribed issues | ||||||
|  | func NotificationSubscriptions(c *context.Context) { | ||||||
|  | 	page := c.FormInt("page") | ||||||
|  | 	if page < 1 { | ||||||
|  | 		page = 1 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	sortType := c.FormString("sort") | ||||||
|  | 	c.Data["SortType"] = sortType | ||||||
|  |  | ||||||
|  | 	state := c.FormString("state") | ||||||
|  | 	if !util.IsStringInSlice(state, []string{"all", "open", "closed"}, true) { | ||||||
|  | 		state = "all" | ||||||
|  | 	} | ||||||
|  | 	c.Data["State"] = state | ||||||
|  | 	var showClosed util.OptionalBool | ||||||
|  | 	switch state { | ||||||
|  | 	case "all": | ||||||
|  | 		showClosed = util.OptionalBoolNone | ||||||
|  | 	case "closed": | ||||||
|  | 		showClosed = util.OptionalBoolTrue | ||||||
|  | 	case "open": | ||||||
|  | 		showClosed = util.OptionalBoolFalse | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var issueTypeBool util.OptionalBool | ||||||
|  | 	issueType := c.FormString("issueType") | ||||||
|  | 	switch issueType { | ||||||
|  | 	case "issues": | ||||||
|  | 		issueTypeBool = util.OptionalBoolFalse | ||||||
|  | 	case "pulls": | ||||||
|  | 		issueTypeBool = util.OptionalBoolTrue | ||||||
|  | 	default: | ||||||
|  | 		issueTypeBool = util.OptionalBoolNone | ||||||
|  | 	} | ||||||
|  | 	c.Data["IssueType"] = issueType | ||||||
|  |  | ||||||
|  | 	var labelIDs []int64 | ||||||
|  | 	selectedLabels := c.FormString("labels") | ||||||
|  | 	c.Data["Labels"] = selectedLabels | ||||||
|  | 	if len(selectedLabels) > 0 && selectedLabels != "0" { | ||||||
|  | 		var err error | ||||||
|  | 		labelIDs, err = base.StringsToInt64s(strings.Split(selectedLabels, ",")) | ||||||
|  | 		if err != nil { | ||||||
|  | 			c.ServerError("StringsToInt64s", err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	count, err := issues_model.CountIssues(&issues_model.IssuesOptions{ | ||||||
|  | 		SubscriberID: c.Doer.ID, | ||||||
|  | 		IsClosed:     showClosed, | ||||||
|  | 		IsPull:       issueTypeBool, | ||||||
|  | 		LabelIDs:     labelIDs, | ||||||
|  | 	}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		c.ServerError("CountIssues", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	issues, err := issues_model.Issues(&issues_model.IssuesOptions{ | ||||||
|  | 		ListOptions: db.ListOptions{ | ||||||
|  | 			PageSize: setting.UI.IssuePagingNum, | ||||||
|  | 			Page:     page, | ||||||
|  | 		}, | ||||||
|  | 		SubscriberID: c.Doer.ID, | ||||||
|  | 		SortType:     sortType, | ||||||
|  | 		IsClosed:     showClosed, | ||||||
|  | 		IsPull:       issueTypeBool, | ||||||
|  | 		LabelIDs:     labelIDs, | ||||||
|  | 	}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		c.ServerError("Issues", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	commitStatuses, lastStatus, err := pull_service.GetIssuesAllCommitStatus(c, issues) | ||||||
|  | 	if err != nil { | ||||||
|  | 		c.ServerError("GetIssuesAllCommitStatus", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	c.Data["CommitLastStatus"] = lastStatus | ||||||
|  | 	c.Data["CommitStatuses"] = commitStatuses | ||||||
|  | 	c.Data["Issues"] = issues | ||||||
|  |  | ||||||
|  | 	c.Data["IssueRefEndNames"], c.Data["IssueRefURLs"] = issue_service.GetRefEndNamesAndURLs(issues, "") | ||||||
|  |  | ||||||
|  | 	commitStatus, err := pull_service.GetIssuesLastCommitStatus(c, issues) | ||||||
|  | 	if err != nil { | ||||||
|  | 		c.ServerError("GetIssuesLastCommitStatus", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	c.Data["CommitStatus"] = commitStatus | ||||||
|  |  | ||||||
|  | 	issueList := issues_model.IssueList(issues) | ||||||
|  | 	approvalCounts, err := issueList.GetApprovalCounts(c) | ||||||
|  | 	if err != nil { | ||||||
|  | 		c.ServerError("ApprovalCounts", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	c.Data["ApprovalCounts"] = func(issueID int64, typ string) int64 { | ||||||
|  | 		counts, ok := approvalCounts[issueID] | ||||||
|  | 		if !ok || len(counts) == 0 { | ||||||
|  | 			return 0 | ||||||
|  | 		} | ||||||
|  | 		reviewTyp := issues_model.ReviewTypeApprove | ||||||
|  | 		if typ == "reject" { | ||||||
|  | 			reviewTyp = issues_model.ReviewTypeReject | ||||||
|  | 		} else if typ == "waiting" { | ||||||
|  | 			reviewTyp = issues_model.ReviewTypeRequest | ||||||
|  | 		} | ||||||
|  | 		for _, count := range counts { | ||||||
|  | 			if count.Type == reviewTyp { | ||||||
|  | 				return count.Count | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		return 0 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	c.Data["Status"] = 1 | ||||||
|  | 	c.Data["Title"] = c.Tr("notification.subscriptions") | ||||||
|  |  | ||||||
|  | 	// redirect to last page if request page is more than total pages | ||||||
|  | 	pager := context.NewPagination(int(count), setting.UI.IssuePagingNum, page, 5) | ||||||
|  | 	if pager.Paginater.Current() < page { | ||||||
|  | 		c.Redirect(fmt.Sprintf("/notifications/subscriptions?page=%d", pager.Paginater.Current())) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	pager.AddParam(c, "sort", "SortType") | ||||||
|  | 	pager.AddParam(c, "state", "State") | ||||||
|  | 	c.Data["Page"] = pager | ||||||
|  |  | ||||||
|  | 	c.HTML(http.StatusOK, tplNotificationSubscriptions) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // NotificationWatching returns the list of watching repos | ||||||
|  | func NotificationWatching(c *context.Context) { | ||||||
|  | 	page := c.FormInt("page") | ||||||
|  | 	if page < 1 { | ||||||
|  | 		page = 1 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var orderBy db.SearchOrderBy | ||||||
|  | 	c.Data["SortType"] = c.FormString("sort") | ||||||
|  | 	switch c.FormString("sort") { | ||||||
|  | 	case "newest": | ||||||
|  | 		orderBy = db.SearchOrderByNewest | ||||||
|  | 	case "oldest": | ||||||
|  | 		orderBy = db.SearchOrderByOldest | ||||||
|  | 	case "recentupdate": | ||||||
|  | 		orderBy = db.SearchOrderByRecentUpdated | ||||||
|  | 	case "leastupdate": | ||||||
|  | 		orderBy = db.SearchOrderByLeastUpdated | ||||||
|  | 	case "reversealphabetically": | ||||||
|  | 		orderBy = db.SearchOrderByAlphabeticallyReverse | ||||||
|  | 	case "alphabetically": | ||||||
|  | 		orderBy = db.SearchOrderByAlphabetically | ||||||
|  | 	case "moststars": | ||||||
|  | 		orderBy = db.SearchOrderByStarsReverse | ||||||
|  | 	case "feweststars": | ||||||
|  | 		orderBy = db.SearchOrderByStars | ||||||
|  | 	case "mostforks": | ||||||
|  | 		orderBy = db.SearchOrderByForksReverse | ||||||
|  | 	case "fewestforks": | ||||||
|  | 		orderBy = db.SearchOrderByForks | ||||||
|  | 	default: | ||||||
|  | 		c.Data["SortType"] = "recentupdate" | ||||||
|  | 		orderBy = db.SearchOrderByRecentUpdated | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	repos, count, err := repo_model.SearchRepository(&repo_model.SearchRepoOptions{ | ||||||
|  | 		ListOptions: db.ListOptions{ | ||||||
|  | 			PageSize: setting.UI.User.RepoPagingNum, | ||||||
|  | 			Page:     page, | ||||||
|  | 		}, | ||||||
|  | 		Actor:              c.Doer, | ||||||
|  | 		Keyword:            c.FormTrim("q"), | ||||||
|  | 		OrderBy:            orderBy, | ||||||
|  | 		Private:            c.IsSigned, | ||||||
|  | 		WatchedByID:        c.Doer.ID, | ||||||
|  | 		Collaborate:        util.OptionalBoolFalse, | ||||||
|  | 		TopicOnly:          c.FormBool("topic"), | ||||||
|  | 		IncludeDescription: setting.UI.SearchRepoDescription, | ||||||
|  | 	}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		c.ServerError("ErrSearchRepository", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	total := int(count) | ||||||
|  | 	c.Data["Total"] = total | ||||||
|  | 	c.Data["Repos"] = repos | ||||||
|  |  | ||||||
|  | 	// redirect to last page if request page is more than total pages | ||||||
|  | 	pager := context.NewPagination(total, setting.UI.User.RepoPagingNum, page, 5) | ||||||
|  | 	pager.SetDefaultParams(c) | ||||||
|  | 	c.Data["Page"] = pager | ||||||
|  |  | ||||||
|  | 	c.Data["Status"] = 2 | ||||||
|  | 	c.Data["Title"] = c.Tr("notification.watching") | ||||||
|  |  | ||||||
|  | 	c.HTML(http.StatusOK, tplNotificationSubscriptions) | ||||||
|  | } | ||||||
|  |  | ||||||
| // NewAvailable returns the notification counts | // NewAvailable returns the notification counts | ||||||
| func NewAvailable(ctx *context.Context) { | func NewAvailable(ctx *context.Context) { | ||||||
| 	ctx.JSON(http.StatusOK, structs.NotificationCount{New: activities_model.CountUnread(ctx, ctx.Doer.ID)}) | 	ctx.JSON(http.StatusOK, structs.NotificationCount{New: activities_model.CountUnread(ctx, ctx.Doer.ID)}) | ||||||
|   | |||||||
| @@ -1269,6 +1269,8 @@ func RegisterRoutes(m *web.Route) { | |||||||
|  |  | ||||||
| 	m.Group("/notifications", func() { | 	m.Group("/notifications", func() { | ||||||
| 		m.Get("", user.Notifications) | 		m.Get("", user.Notifications) | ||||||
|  | 		m.Get("/subscriptions", user.NotificationSubscriptions) | ||||||
|  | 		m.Get("/watching", user.NotificationWatching) | ||||||
| 		m.Post("/status", user.NotificationStatusPost) | 		m.Post("/status", user.NotificationStatusPost) | ||||||
| 		m.Post("/purge", user.NotificationPurgePost) | 		m.Post("/purge", user.NotificationPurgePost) | ||||||
| 		m.Get("/new", user.NewAvailable) | 		m.Get("/new", user.NewAvailable) | ||||||
|   | |||||||
| @@ -171,6 +171,10 @@ | |||||||
| 							{{.locale.Tr "your_starred"}} | 							{{.locale.Tr "your_starred"}} | ||||||
| 						</a> | 						</a> | ||||||
| 					{{end}} | 					{{end}} | ||||||
|  | 					<a class="item" href="{{AppSubUrl}}/notifications/subscriptions"> | ||||||
|  | 						{{svg "octicon-bell"}} | ||||||
|  | 						{{.locale.Tr "notification.subscriptions"}}<!-- Subscriptions --> | ||||||
|  | 					</a> | ||||||
| 					<a class="{{if .PageIsUserSettings}}active{{end}} item" href="{{AppSubUrl}}/user/settings"> | 					<a class="{{if .PageIsUserSettings}}active{{end}} item" href="{{AppSubUrl}}/user/settings"> | ||||||
| 						{{svg "octicon-tools"}} | 						{{svg "octicon-tools"}} | ||||||
| 						{{.locale.Tr "your_settings"}}<!-- Your settings --> | 						{{.locale.Tr "your_settings"}}<!-- Your settings --> | ||||||
|   | |||||||
							
								
								
									
										79
									
								
								templates/user/notification/notification_subscriptions.tmpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								templates/user/notification/notification_subscriptions.tmpl
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,79 @@ | |||||||
|  | {{template "base/head" .}} | ||||||
|  | <div class="page-content user notification" id="notification_subscriptions" data-params="{{.Page.GetParams}}" data-sequence-number="{{.SequenceNumber}}"> | ||||||
|  | 	<div class="ui container"> | ||||||
|  | 		<div class="ui top attached tabular menu"> | ||||||
|  | 			<a href="{{AppSubUrl}}/notifications/subscriptions" class="{{if eq .Status 1}}active {{end}}item"> | ||||||
|  | 				{{.locale.Tr "notification.subscriptions"}} | ||||||
|  | 			</a> | ||||||
|  | 			<a href="{{AppSubUrl}}/notifications/watching" class="{{if eq .Status 2}}active {{end}}item"> | ||||||
|  | 				{{.locale.Tr "notification.watching"}} | ||||||
|  | 			</a> | ||||||
|  | 		</div> | ||||||
|  | 		<div class="ui bottom attached active tab segment"> | ||||||
|  | 			{{if eq .Status 1}} | ||||||
|  | 				<div id="issue-filters" class="ui stackable grid"> | ||||||
|  | 					<div class="six wide column"> | ||||||
|  | 						<div class="ui compact tiny menu"> | ||||||
|  | 							<a class="{{if eq .State "all"}}active {{end}}item" href="{{$.Link}}?sort={{$.SortType}}&state=all&issueType={{$.IssueType}}&labels={{$.Labels}}"> | ||||||
|  | 								{{.locale.Tr "all"}} | ||||||
|  | 							</a> | ||||||
|  | 							<a class="{{if eq .State "open"}}active {{end}}item" href="{{$.Link}}?sort={{$.SortType}}&state=open&issueType={{$.IssueType}}&labels={{$.Labels}}"> | ||||||
|  | 								{{svg "octicon-issue-opened" 16 "mr-3"}} | ||||||
|  | 								{{.locale.Tr "repo.issues.open_title"}} | ||||||
|  | 							</a> | ||||||
|  | 							<a class="{{if eq .State "closed"}}active {{end}}item" href="{{$.Link}}?sort={{$.SortType}}&state=closed&issueType={{$.IssueType}}&labels={{$.Labels}}"> | ||||||
|  | 								{{svg "octicon-issue-closed" 16 "mr-3"}} | ||||||
|  | 								{{.locale.Tr "repo.issues.closed_title"}} | ||||||
|  | 							</a> | ||||||
|  | 						</div> | ||||||
|  | 					</div> | ||||||
|  | 					<div class="seven wide right aligned right floated column"> | ||||||
|  | 						<div class="ui right aligned secondary filter stackable menu labels"> | ||||||
|  | 							<!-- Type --> | ||||||
|  | 								<div class="ui dropdown type jump item"> | ||||||
|  | 									<span class="text"> | ||||||
|  | 										{{.locale.Tr "repo.issues.filter_type"}} | ||||||
|  | 										{{svg "octicon-triangle-down" 14 "dropdown icon"}} | ||||||
|  | 									</span> | ||||||
|  | 									<div class="menu"> | ||||||
|  | 										<a class="{{if or (eq .IssueType "all") (not .IssueType)}}active {{end}}item" href="{{$.Link}}?sort={{$.SortType}}&state={{$.State}}&issueType=all&labels={{$.Labels}}">{{.locale.Tr "all"}}</a> | ||||||
|  | 										<a class="{{if eq .IssueType "issues"}}active {{end}}item" href="{{$.Link}}?sort={{$.SortType}}&state={{$.State}}&issueType=issues&labels={{$.Labels}}">{{.locale.Tr "issues"}}</a> | ||||||
|  | 										<a class="{{if eq .IssueType "pulls"}}active {{end}}item" href="{{$.Link}}?sort={{$.SortType}}&state={{$.State}}&issueType=pulls&labels={{$.Labels}}">{{.locale.Tr "pull_requests"}}</a> | ||||||
|  | 									</div> | ||||||
|  | 								</div> | ||||||
|  |  | ||||||
|  | 							<!-- Sort --> | ||||||
|  | 							<div class="ui dropdown type jump item"> | ||||||
|  | 								<span class="text"> | ||||||
|  | 									{{.locale.Tr "repo.issues.filter_sort"}} | ||||||
|  | 									{{svg "octicon-triangle-down" 14 "dropdown icon"}} | ||||||
|  | 								</span> | ||||||
|  | 								<div class="menu"> | ||||||
|  | 									<a class="{{if or (eq .SortType "latest") (not .SortType)}}active {{end}}item" href="{{$.Link}}?sort=latest&state={{$.State}}&issueType={{$.IssueType}}&labels={{$.Labels}}">{{.locale.Tr "repo.issues.filter_sort.latest"}}</a> | ||||||
|  | 									<a class="{{if eq .SortType "oldest"}}active {{end}}item" href="{{$.Link}}?sort=oldest&state={{$.State}}&issueType={{$.IssueType}}&labels={{$.Labels}}">{{.locale.Tr "repo.issues.filter_sort.oldest"}}</a> | ||||||
|  | 									<a class="{{if eq .SortType "recentupdate"}}active {{end}}item" href="{{$.Link}}?sort=recentupdate&state={{$.State}}&issueType={{$.IssueType}}&labels={{$.Labels}}">{{.locale.Tr "repo.issues.filter_sort.recentupdate"}}</a> | ||||||
|  | 									<a class="{{if eq .SortType "leastupdate"}}active {{end}}item" href="{{$.Link}}?sort=leastupdate&state={{$.State}}&issueType={{$.IssueType}}&labels={{$.Labels}}">{{.locale.Tr "repo.issues.filter_sort.leastupdate"}}</a> | ||||||
|  | 									<a class="{{if eq .SortType "mostcomment"}}active {{end}}item" href="{{$.Link}}?sort=mostcomment&state={{$.State}}&issueType={{$.IssueType}}&labels={{$.Labels}}">{{.locale.Tr "repo.issues.filter_sort.mostcomment"}}</a> | ||||||
|  | 									<a class="{{if eq .SortType "leastcomment"}}active {{end}}item" href="{{$.Link}}?sort=leastcomment&state={{$.State}}&issueType={{$.IssueType}}&labels={{$.Labels}}">{{.locale.Tr "repo.issues.filter_sort.leastcomment"}}</a> | ||||||
|  | 									<a class="{{if eq .SortType "nearduedate"}}active {{end}}item" href="{{$.Link}}?sort=nearduedate&state={{$.State}}&issueType={{$.IssueType}}&labels={{$.Labels}}">{{.locale.Tr "repo.issues.filter_sort.nearduedate"}}</a> | ||||||
|  | 									<a class="{{if eq .SortType "farduedate"}}active {{end}}item" href="{{$.Link}}?sort=farduedate&state={{$.State}}&issueType={{$.IssueType}}&labels={{$.Labels}}">{{.locale.Tr "repo.issues.filter_sort.farduedate"}}</a> | ||||||
|  | 								</div> | ||||||
|  | 							</div> | ||||||
|  | 						</div> | ||||||
|  | 					</div> | ||||||
|  | 				</div> | ||||||
|  | 				{{if eq (len .Issues) 0}} | ||||||
|  | 					<div class="ui divider"></div> | ||||||
|  | 					{{.locale.Tr "notification.no_subscriptions"}} | ||||||
|  | 				{{else}} | ||||||
|  | 					{{template "shared/issuelist" mergeinto . "listType" "dashboard"}} | ||||||
|  | 				{{end}} | ||||||
|  | 			{{else}} | ||||||
|  | 				{{template "explore/repo_search" .}} | ||||||
|  | 				{{template "explore/repo_list" .}} | ||||||
|  | 				{{template "base/paginate" .}} | ||||||
|  | 			{{end}} | ||||||
|  | 		</div> | ||||||
|  | 	</div> | ||||||
|  | </div> | ||||||
|  | {{template "base/footer" .}} | ||||||
		Reference in New Issue
	
	Block a user