Skip to content

Commit 741bd19

Browse files
committed
fix
1 parent cd7bf77 commit 741bd19

File tree

5 files changed

+197
-82
lines changed

5 files changed

+197
-82
lines changed

modules/templates/helper.go

+66
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ func NewFuncMap() template.FuncMap {
4242
"HTMLFormat": htmlutil.HTMLFormat,
4343
"HTMLEscape": htmlEscape,
4444
"QueryEscape": queryEscape,
45+
"QueryBuild": queryBuild,
4546
"JSEscape": jsEscapeSafe,
4647
"SanitizeHTML": SanitizeHTML,
4748
"URLJoin": util.URLJoin,
@@ -293,6 +294,71 @@ func timeEstimateString(timeSec any) string {
293294
return util.TimeEstimateString(v)
294295
}
295296

297+
type QueryString string
298+
299+
func queryBuild(a ...any) QueryString {
300+
var s string
301+
if len(a)%2 == 1 {
302+
if v, ok := a[0].(string); ok {
303+
if v == "" || (v[0] != '?' && v[0] != '&') {
304+
panic("queryBuild: invalid argument")
305+
}
306+
s = v
307+
} else if v, ok := a[0].(QueryString); ok {
308+
s = string(v)
309+
} else {
310+
panic("queryBuild: invalid argument")
311+
}
312+
}
313+
for i := len(a) % 2; i < len(a); i += 2 {
314+
k, ok := a[i].(string)
315+
if !ok {
316+
panic("queryBuild: invalid argument")
317+
}
318+
var v string
319+
if va, ok := a[i+1].(string); ok {
320+
v = va
321+
} else if a[i+1] != nil {
322+
v = fmt.Sprint(a[i+1])
323+
}
324+
// pos1 to pos2 is the "k=v&" part, "&" is optional
325+
pos1 := strings.Index(s, "&"+k+"=")
326+
if pos1 != -1 {
327+
pos1++
328+
} else {
329+
pos1 = strings.Index(s, "?"+k+"=")
330+
if pos1 != -1 {
331+
pos1++
332+
} else if strings.HasPrefix(s, k+"=") {
333+
pos1 = 0
334+
}
335+
}
336+
pos2 := len(s)
337+
if pos1 == -1 {
338+
pos1 = len(s)
339+
} else {
340+
pos2 = pos1 + 1
341+
for pos2 < len(s) && s[pos2-1] != '&' {
342+
pos2++
343+
}
344+
}
345+
if v != "" {
346+
sep := ""
347+
hasPrefixSep := pos1 == 0 || (pos1 <= len(s) && (s[pos1-1] == '?' || s[pos1-1] == '&'))
348+
if !hasPrefixSep {
349+
sep = "&"
350+
}
351+
s = s[:pos1] + sep + k + "=" + url.QueryEscape(v) + "&" + s[pos2:]
352+
} else {
353+
s = s[:pos1] + s[pos2:]
354+
}
355+
}
356+
if s != "" && s != "&" && s[len(s)-1] == '&' {
357+
s = s[:len(s)-1]
358+
}
359+
return QueryString(s)
360+
}
361+
296362
func panicIfDevOrTesting() {
297363
if !setting.IsProd || setting.IsInTesting {
298364
panic("legacy template functions are for backward compatibility only, do not use them in new code")

modules/templates/helper_test.go

+47
Original file line numberDiff line numberDiff line change
@@ -102,3 +102,50 @@ func TestTemplateTruthy(t *testing.T) {
102102
}
103103
assert.True(t, truthyCount != 0 && truthyCount != len(cases))
104104
}
105+
106+
func TestQueryBuild(t *testing.T) {
107+
t.Run("construct", func(t *testing.T) {
108+
assert.Equal(t, "", string(queryBuild()))
109+
assert.Equal(t, "a=1&b=true", string(queryBuild("a", 1, "b", "true")))
110+
assert.Equal(t, "?k=1", string(queryBuild("?", "k", 1)))
111+
assert.Equal(t, "?a=b&k=1", string(queryBuild("?a=b", "k", 1)))
112+
assert.Equal(t, "&k=1", string(queryBuild("&", "k", 1)))
113+
assert.Equal(t, "&a=b&k=1", string(queryBuild("&a=b", "k", 1)))
114+
})
115+
t.Run("replace", func(t *testing.T) {
116+
assert.Equal(t, "a=1&c=d&e=f", string(queryBuild(QueryString("a=b&c=d&e=f"), "a", 1)))
117+
assert.Equal(t, "a=b&c=1&e=f", string(queryBuild(QueryString("a=b&c=d&e=f"), "c", 1)))
118+
assert.Equal(t, "a=b&c=d&e=1", string(queryBuild(QueryString("a=b&c=d&e=f"), "e", 1)))
119+
assert.Equal(t, "a=b&c=d&e=f&k=1", string(queryBuild(QueryString("a=b&c=d&e=f"), "k", 1)))
120+
})
121+
t.Run("replace-?", func(t *testing.T) {
122+
assert.Equal(t, "?a=1&c=d&e=f", string(queryBuild(QueryString("?a=b&c=d&e=f"), "a", 1)))
123+
assert.Equal(t, "?a=b&c=1&e=f", string(queryBuild(QueryString("?a=b&c=d&e=f"), "c", 1)))
124+
assert.Equal(t, "?a=b&c=d&e=1", string(queryBuild(QueryString("?a=b&c=d&e=f"), "e", 1)))
125+
assert.Equal(t, "?a=b&c=d&e=f&k=1", string(queryBuild(QueryString("?a=b&c=d&e=f"), "k", 1)))
126+
})
127+
t.Run("replace-&", func(t *testing.T) {
128+
assert.Equal(t, "&a=1&c=d&e=f", string(queryBuild(QueryString("&a=b&c=d&e=f"), "a", 1)))
129+
assert.Equal(t, "&a=b&c=1&e=f", string(queryBuild(QueryString("&a=b&c=d&e=f"), "c", 1)))
130+
assert.Equal(t, "&a=b&c=d&e=1", string(queryBuild(QueryString("&a=b&c=d&e=f"), "e", 1)))
131+
assert.Equal(t, "&a=b&c=d&e=f&k=1", string(queryBuild(QueryString("&a=b&c=d&e=f"), "k", 1)))
132+
})
133+
t.Run("delete", func(t *testing.T) {
134+
assert.Equal(t, "c=d&e=f", string(queryBuild(QueryString("a=b&c=d&e=f"), "a", "")))
135+
assert.Equal(t, "a=b&e=f", string(queryBuild(QueryString("a=b&c=d&e=f"), "c", "")))
136+
assert.Equal(t, "a=b&c=d", string(queryBuild(QueryString("a=b&c=d&e=f"), "e", "")))
137+
assert.Equal(t, "a=b&c=d&e=f", string(queryBuild(QueryString("a=b&c=d&e=f"), "k", "")))
138+
})
139+
t.Run("delete-?", func(t *testing.T) {
140+
assert.Equal(t, "?c=d&e=f", string(queryBuild(QueryString("?a=b&c=d&e=f"), "a", "")))
141+
assert.Equal(t, "?a=b&e=f", string(queryBuild(QueryString("?a=b&c=d&e=f"), "c", "")))
142+
assert.Equal(t, "?a=b&c=d", string(queryBuild(QueryString("?a=b&c=d&e=f"), "e", "")))
143+
assert.Equal(t, "?a=b&c=d&e=f", string(queryBuild(QueryString("?a=b&c=d&e=f"), "k", "")))
144+
})
145+
t.Run("delete-&", func(t *testing.T) {
146+
assert.Equal(t, "&c=d&e=f", string(queryBuild(QueryString("&a=b&c=d&e=f"), "a", "")))
147+
assert.Equal(t, "&a=b&e=f", string(queryBuild(QueryString("&a=b&c=d&e=f"), "c", "")))
148+
assert.Equal(t, "&a=b&c=d", string(queryBuild(QueryString("&a=b&c=d&e=f"), "e", "")))
149+
assert.Equal(t, "&a=b&c=d&e=f", string(queryBuild(QueryString("&a=b&c=d&e=f"), "k", "")))
150+
})
151+
}

routers/web/user/home.go

+36-32
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
"code.gitea.io/gitea/modules/markup/markdown"
3232
"code.gitea.io/gitea/modules/optional"
3333
"code.gitea.io/gitea/modules/setting"
34+
"code.gitea.io/gitea/modules/util"
3435
"code.gitea.io/gitea/routers/web/feed"
3536
"code.gitea.io/gitea/services/context"
3637
feed_service "code.gitea.io/gitea/services/feed"
@@ -375,16 +376,8 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
375376
return
376377
}
377378

378-
var (
379-
viewType string
380-
sortType = ctx.FormString("sort")
381-
filterMode int
382-
)
383-
384379
// Default to recently updated, unlike repository issues list
385-
if sortType == "" {
386-
sortType = "recentupdate"
387-
}
380+
sortType := util.IfZero(ctx.FormString("sort"), "recentupdate")
388381

389382
// --------------------------------------------------------------------------------
390383
// Distinguish User from Organization.
@@ -399,7 +392,8 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
399392

400393
// TODO: distinguish during routing
401394

402-
viewType = ctx.FormString("type")
395+
viewType := ctx.FormString("type")
396+
var filterMode int
403397
switch viewType {
404398
case "assigned":
405399
filterMode = issues_model.FilterModeAssign
@@ -443,6 +437,12 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
443437
Team: team,
444438
User: ctx.Doer,
445439
}
440+
// Get filter by author id & assignee id
441+
// FIXME: this feature doesn't work at the moment, because frontend can't use a "user-remote-search" dropdown directly
442+
// the existing "/posters" handlers doesn't work for this case, it is unable to list the related users correctly.
443+
// In the future, we need something like github: "author:user1" to accept usernames directly.
444+
opts.PosterID, _ = strconv.ParseInt(ctx.FormString("poster"), 10, 64)
445+
opts.AssigneeID, _ = strconv.ParseInt(ctx.FormString("assignee"), 10, 64)
446446

447447
isFuzzy := ctx.FormBool("fuzzy")
448448

@@ -573,8 +573,21 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
573573
// -------------------------------
574574
// Fill stats to post to ctx.Data.
575575
// -------------------------------
576-
issueStats, err := getUserIssueStats(ctx, ctxUser, filterMode, issue_indexer.ToSearchOptions(keyword, opts).Copy(
577-
func(o *issue_indexer.SearchOptions) { o.IsFuzzyKeyword = isFuzzy },
576+
issueStats, err := getUserIssueStats(ctx, filterMode, issue_indexer.ToSearchOptions(keyword, opts).Copy(
577+
func(o *issue_indexer.SearchOptions) {
578+
o.IsFuzzyKeyword = isFuzzy
579+
// If the doer is the same as the context user, which means the doer is viewing his own dashboard,
580+
// it's not enough to show the repos that the doer owns or has been explicitly granted access to,
581+
// because the doer may create issues or be mentioned in any public repo.
582+
// So we need search issues in all public repos.
583+
o.AllPublic = ctx.Doer.ID == ctxUser.ID
584+
// TODO: to make it work with poster/assignee filter
585+
o.AssigneeID = nil
586+
o.PosterID = nil
587+
o.MentionID = nil
588+
o.ReviewRequestedID = nil
589+
o.ReviewedID = nil
590+
},
578591
))
579592
if err != nil {
580593
ctx.ServerError("getUserIssueStats", err)
@@ -630,6 +643,8 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
630643
ctx.Data["IsShowClosed"] = isShowClosed
631644
ctx.Data["SelectLabels"] = selectedLabels
632645
ctx.Data["IsFuzzy"] = isFuzzy
646+
ctx.Data["SearchFilterPosterID"] = util.Iif[any](opts.PosterID != 0, opts.PosterID, nil)
647+
ctx.Data["SearchFilterAssigneeID"] = util.Iif[any](opts.AssigneeID != 0, opts.AssigneeID, nil)
633648

634649
if isShowClosed {
635650
ctx.Data["State"] = "closed"
@@ -643,7 +658,13 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
643658
pager.AddParamString("sort", sortType)
644659
pager.AddParamString("state", fmt.Sprint(ctx.Data["State"]))
645660
pager.AddParamString("labels", selectedLabels)
646-
pager.AddParamString("fuzzy", fmt.Sprintf("%v", isFuzzy))
661+
pager.AddParamString("fuzzy", fmt.Sprint(isFuzzy))
662+
if opts.PosterID != 0 {
663+
pager.AddParamString("poster", fmt.Sprint(opts.PosterID))
664+
}
665+
if opts.AssigneeID != 0 {
666+
pager.AddParamString("assignee", fmt.Sprint(opts.AssigneeID))
667+
}
647668
ctx.Data["Page"] = pager
648669

649670
ctx.HTML(http.StatusOK, tplIssues)
@@ -768,27 +789,10 @@ func UsernameSubRoute(ctx *context.Context) {
768789
}
769790
}
770791

771-
func getUserIssueStats(ctx *context.Context, ctxUser *user_model.User, filterMode int, opts *issue_indexer.SearchOptions) (*issues_model.IssueStats, error) {
792+
func getUserIssueStats(ctx *context.Context, filterMode int, opts *issue_indexer.SearchOptions) (ret *issues_model.IssueStats, err error) {
793+
ret = &issues_model.IssueStats{}
772794
doerID := ctx.Doer.ID
773795

774-
opts = opts.Copy(func(o *issue_indexer.SearchOptions) {
775-
// If the doer is the same as the context user, which means the doer is viewing his own dashboard,
776-
// it's not enough to show the repos that the doer owns or has been explicitly granted access to,
777-
// because the doer may create issues or be mentioned in any public repo.
778-
// So we need search issues in all public repos.
779-
o.AllPublic = doerID == ctxUser.ID
780-
o.AssigneeID = nil
781-
o.PosterID = nil
782-
o.MentionID = nil
783-
o.ReviewRequestedID = nil
784-
o.ReviewedID = nil
785-
})
786-
787-
var (
788-
err error
789-
ret = &issues_model.IssueStats{}
790-
)
791-
792796
{
793797
openClosedOpts := opts.Copy()
794798
switch filterMode {

templates/user/dashboard/issues.tmpl

+22-19
Original file line numberDiff line numberDiff line change
@@ -4,45 +4,48 @@
44
<div class="ui container">
55
{{template "base/alert" .}}
66
<div class="flex-container">
7+
{{$queryLink := QueryBuild "?" "type" $.ViewType "sort" $.SortType "state" $.State "q" $.Keyword "fuzzy" $.IsFuzzy}}
78
<div class="flex-container-nav">
89
<div class="ui secondary vertical filter menu tw-bg-transparent">
9-
<a class="{{if eq .ViewType "your_repositories"}}active{{end}} item" href="?type=your_repositories&sort={{$.SortType}}&state={{.State}}&q={{$.Keyword}}&fuzzy={{.IsFuzzy}}">
10+
<a class="{{if eq .ViewType "your_repositories"}}active{{end}} item" href="{{QueryBuild $queryLink "type" "your_repositories"}}">
1011
{{ctx.Locale.Tr "home.issues.in_your_repos"}}
1112
<strong>{{CountFmt .IssueStats.YourRepositoriesCount}}</strong>
1213
</a>
13-
<a class="{{if eq .ViewType "assigned"}}active{{end}} item" href="?type=assigned&sort={{$.SortType}}&state={{.State}}&q={{$.Keyword}}&fuzzy={{.IsFuzzy}}">
14+
<a class="{{if eq .ViewType "assigned"}}active{{end}} item" href="{{QueryBuild $queryLink "type" "assigned"}}">
1415
{{ctx.Locale.Tr "repo.issues.filter_type.assigned_to_you"}}
1516
<strong>{{CountFmt .IssueStats.AssignCount}}</strong>
1617
</a>
17-
<a class="{{if eq .ViewType "created_by"}}active{{end}} item" href="?type=created_by&sort={{$.SortType}}&state={{.State}}&q={{$.Keyword}}&fuzzy={{.IsFuzzy}}">
18+
<a class="{{if eq .ViewType "created_by"}}active{{end}} item" href="{{QueryBuild $queryLink "type" "created_by"}}">
1819
{{ctx.Locale.Tr "repo.issues.filter_type.created_by_you"}}
1920
<strong>{{CountFmt .IssueStats.CreateCount}}</strong>
2021
</a>
2122
{{if .PageIsPulls}}
22-
<a class="{{if eq .ViewType "review_requested"}}active{{end}} item" href="?type=review_requested&sort={{$.SortType}}&state={{.State}}&q={{$.Keyword}}&fuzzy={{.IsFuzzy}}">
23+
<a class="{{if eq .ViewType "review_requested"}}active{{end}} item" href="{{QueryBuild $queryLink "type" "review_requested"}}">
2324
{{ctx.Locale.Tr "repo.issues.filter_type.review_requested"}}
2425
<strong>{{CountFmt .IssueStats.ReviewRequestedCount}}</strong>
2526
</a>
26-
<a class="{{if eq .ViewType "reviewed_by"}}active{{end}} item" href="?type=reviewed_by&sort={{$.SortType}}&state={{.State}}&q={{$.Keyword}}&fuzzy={{.IsFuzzy}}">
27+
<a class="{{if eq .ViewType "reviewed_by"}}active{{end}} item" href="{{QueryBuild $queryLink "type" "reviewed_by"}}">
2728
{{ctx.Locale.Tr "repo.issues.filter_type.reviewed_by_you"}}
2829
<strong>{{CountFmt .IssueStats.ReviewedCount}}</strong>
2930
</a>
3031
{{end}}
31-
<a class="{{if eq .ViewType "mentioned"}}active{{end}} item" href="?type=mentioned&sort={{$.SortType}}&state={{.State}}&q={{$.Keyword}}&fuzzy={{.IsFuzzy}}">
32+
<a class="{{if eq .ViewType "mentioned"}}active{{end}} item" href="{{QueryBuild $queryLink "type" "mentioned"}}">
3233
{{ctx.Locale.Tr "repo.issues.filter_type.mentioning_you"}}
3334
<strong>{{CountFmt .IssueStats.MentionCount}}</strong>
3435
</a>
3536
</div>
3637
</div>
38+
39+
{{$queryLinkWithFilter := QueryBuild $queryLink "poster" $.SearchFilterPosterID "assignee" $.SearchFilterAssigneeID}}
3740
<div class="flex-container-main content">
3841
<div class="list-header">
39-
<div class="small-menu-items ui compact tiny menu list-header-toggle">
40-
<a class="item{{if not .IsShowClosed}} active{{end}}" href="?type={{$.ViewType}}&sort={{$.SortType}}&state=open&q={{$.Keyword}}&fuzzy={{.IsFuzzy}}">
41-
{{svg "octicon-issue-opened" 16 "tw-mr-2"}}
42+
<div class="small-menu-items ui compact tiny menu list-header-toggle flex-items-block">
43+
<a class="item{{if not .IsShowClosed}} active{{end}}" href="{{QueryBuild $queryLink "state" "open"}}">
44+
{{svg "octicon-issue-opened"}}
4245
{{ctx.Locale.PrettyNumber .IssueStats.OpenCount}}&nbsp;{{ctx.Locale.Tr "repo.issues.open_title"}}
4346
</a>
44-
<a class="item{{if .IsShowClosed}} active{{end}}" href="?type={{$.ViewType}}&sort={{$.SortType}}&state=closed&q={{$.Keyword}}&fuzzy={{.IsFuzzy}}">
45-
{{svg "octicon-issue-closed" 16 "tw-mr-2"}}
47+
<a class="item{{if .IsShowClosed}} active{{end}}" href="{{QueryBuild $queryLink "state" "closed"}}">
48+
{{svg "octicon-issue-closed"}}
4649
{{ctx.Locale.PrettyNumber .IssueStats.ClosedCount}}&nbsp;{{ctx.Locale.Tr "repo.issues.closed_title"}}
4750
</a>
4851
</div>
@@ -61,14 +64,14 @@
6164
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
6265
</span>
6366
<div class="menu">
64-
<a class="{{if eq .SortType "recentupdate"}}active {{end}}item" href="?type={{$.ViewType}}&sort=recentupdate&state={{$.State}}&q={{$.Keyword}}&fuzzy={{.IsFuzzy}}">{{ctx.Locale.Tr "repo.issues.filter_sort.recentupdate"}}</a>
65-
<a class="{{if eq .SortType "leastupdate"}}active {{end}}item" href="?type={{$.ViewType}}&sort=leastupdate&state={{$.State}}&q={{$.Keyword}}&fuzzy={{.IsFuzzy}}">{{ctx.Locale.Tr "repo.issues.filter_sort.leastupdate"}}</a>
66-
<a class="{{if or (eq .SortType "latest") (not .SortType)}}active {{end}}item" href="?type={{$.ViewType}}&sort=latest&state={{$.State}}&q={{$.Keyword}}&fuzzy={{.IsFuzzy}}">{{ctx.Locale.Tr "repo.issues.filter_sort.latest"}}</a>
67-
<a class="{{if eq .SortType "oldest"}}active {{end}}item" href="?type={{$.ViewType}}&sort=oldest&state={{$.State}}&q={{$.Keyword}}&fuzzy={{.IsFuzzy}}">{{ctx.Locale.Tr "repo.issues.filter_sort.oldest"}}</a>
68-
<a class="{{if eq .SortType "mostcomment"}}active {{end}}item" href="?type={{$.ViewType}}&sort=mostcomment&state={{$.State}}&q={{$.Keyword}}&fuzzy={{.IsFuzzy}}">{{ctx.Locale.Tr "repo.issues.filter_sort.mostcomment"}}</a>
69-
<a class="{{if eq .SortType "leastcomment"}}active {{end}}item" href="?type={{$.ViewType}}&sort=leastcomment&state={{$.State}}&q={{$.Keyword}}&fuzzy={{.IsFuzzy}}">{{ctx.Locale.Tr "repo.issues.filter_sort.leastcomment"}}</a>
70-
<a class="{{if eq .SortType "nearduedate"}}active {{end}}item" href="?type={{$.ViewType}}&sort=nearduedate&state={{$.State}}&q={{$.Keyword}}&fuzzy={{.IsFuzzy}}">{{ctx.Locale.Tr "repo.issues.filter_sort.nearduedate"}}</a>
71-
<a class="{{if eq .SortType "farduedate"}}active {{end}}item" href="?type={{$.ViewType}}&sort=farduedate&state={{$.State}}&q={{$.Keyword}}&fuzzy={{.IsFuzzy}}">{{ctx.Locale.Tr "repo.issues.filter_sort.farduedate"}}</a>
67+
<a class="{{if eq .SortType "recentupdate"}}active {{end}}item" href="{{QueryBuild $queryLinkWithFilter "sort" "recentupdate"}}">{{ctx.Locale.Tr "repo.issues.filter_sort.recentupdate"}}</a>
68+
<a class="{{if eq .SortType "leastupdate"}}active {{end}}item" href="{{QueryBuild $queryLinkWithFilter "sort" "leastupdate"}}">{{ctx.Locale.Tr "repo.issues.filter_sort.leastupdate"}}</a>
69+
<a class="{{if eq .SortType "latest"}}active {{end}}item" href="{{QueryBuild $queryLinkWithFilter "sort" "latest"}}">{{ctx.Locale.Tr "repo.issues.filter_sort.latest"}}</a>
70+
<a class="{{if eq .SortType "oldest"}}active {{end}}item" href="{{QueryBuild $queryLinkWithFilter "sort" "oldest"}}">{{ctx.Locale.Tr "repo.issues.filter_sort.oldest"}}</a>
71+
<a class="{{if eq .SortType "mostcomment"}}active {{end}}item" href="{{QueryBuild $queryLinkWithFilter "sort" "mostcomment"}}">{{ctx.Locale.Tr "repo.issues.filter_sort.mostcomment"}}</a>
72+
<a class="{{if eq .SortType "leastcomment"}}active {{end}}item" href="{{QueryBuild $queryLinkWithFilter "sort" "leastcomment"}}">{{ctx.Locale.Tr "repo.issues.filter_sort.leastcomment"}}</a>
73+
<a class="{{if eq .SortType "nearduedate"}}active {{end}}item" href="{{QueryBuild $queryLinkWithFilter "sort" "nearduedate"}}">{{ctx.Locale.Tr "repo.issues.filter_sort.nearduedate"}}</a>
74+
<a class="{{if eq .SortType "farduedate"}}active {{end}}item" href="{{QueryBuild $queryLinkWithFilter "sort" "farduedate"}}">{{ctx.Locale.Tr "repo.issues.filter_sort.farduedate"}}</a>
7275
</div>
7376
</div>
7477
</div>

0 commit comments

Comments
 (0)