mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-11-30 06:07:55 +01: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 ![Screenshot](https://user-images.githubusercontent.com/80460567/134782937-3112f7da-425a-45b6-9511-5c9695aee896.png) 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:
parent
3b6a7e5c8a
commit
08609d439d
6 changed files with 334 additions and 2 deletions
|
@ -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" .}}
|
Loading…
Reference in a new issue