loading up the forgejo repo on tangled to test page performance
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(issue search): query string for boolean operators and phrase search (#6952)

closes #6909

related to forgejo/design#14

# Description

Adds the following boolean operators for issues when using an indexer (with minor caveats)

- `+term`: `term` MUST be present for any result
- `-term`: negation; exclude results that contain `term`
- `"this is a term"`: matches the exact phrase `this is a term`

In all cases the special characters may be escaped by the prefix `\`

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/6952
Reviewed-by: 0ko <0ko@noreply.codeberg.org>
Reviewed-by: Otto <otto@codeberg.org>
Co-authored-by: Shiny Nematoda <snematoda.751k2@aleeas.com>
Co-committed-by: Shiny Nematoda <snematoda.751k2@aleeas.com>

authored by

Shiny Nematoda
Shiny Nematoda
and committed by
Earl Warren
cddf608c eaa641c2

+450 -191
+24 -17
modules/indexer/issues/bleve/bleve.go
··· 156 156 var queries []query.Query 157 157 158 158 if options.Keyword != "" { 159 - if options.IsFuzzyKeyword { 160 - fuzziness := 1 161 - if kl := len(options.Keyword); kl > 3 { 162 - fuzziness = 2 163 - } else if kl < 2 { 164 - fuzziness = 0 159 + tokens, err := options.Tokens() 160 + if err != nil { 161 + return nil, err 162 + } 163 + q := bleve.NewBooleanQuery() 164 + for _, token := range tokens { 165 + fuzziness := 0 166 + if token.Fuzzy { 167 + // TODO: replace with "auto" after bleve update 168 + fuzziness = min(len(token.Term)/4, 2) 169 + } 170 + innerQ := bleve.NewDisjunctionQuery( 171 + inner_bleve.MatchPhraseQuery(token.Term, "title", issueIndexerAnalyzer, fuzziness), 172 + inner_bleve.MatchPhraseQuery(token.Term, "content", issueIndexerAnalyzer, fuzziness), 173 + inner_bleve.MatchPhraseQuery(token.Term, "comments", issueIndexerAnalyzer, fuzziness)) 174 + 175 + switch token.Kind { 176 + case internal.BoolOptMust: 177 + q.AddMust(innerQ) 178 + case internal.BoolOptShould: 179 + q.AddShould(innerQ) 180 + case internal.BoolOptNot: 181 + q.AddMustNot(innerQ) 165 182 } 166 - queries = append(queries, bleve.NewDisjunctionQuery([]query.Query{ 167 - inner_bleve.MatchQuery(options.Keyword, "title", issueIndexerAnalyzer, fuzziness), 168 - inner_bleve.MatchQuery(options.Keyword, "content", issueIndexerAnalyzer, fuzziness), 169 - inner_bleve.MatchQuery(options.Keyword, "comments", issueIndexerAnalyzer, fuzziness), 170 - }...)) 171 - } else { 172 - queries = append(queries, bleve.NewDisjunctionQuery([]query.Query{ 173 - inner_bleve.MatchPhraseQuery(options.Keyword, "title", issueIndexerAnalyzer, 0), 174 - inner_bleve.MatchPhraseQuery(options.Keyword, "content", issueIndexerAnalyzer, 0), 175 - inner_bleve.MatchPhraseQuery(options.Keyword, "comments", issueIndexerAnalyzer, 0), 176 - }...)) 177 183 } 184 + queries = append(queries, q) 178 185 } 179 186 180 187 if len(options.RepoIDs) > 0 || options.AllPublic {
+26 -4
modules/indexer/issues/elasticsearch/elasticsearch.go
··· 23 23 // Reference: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-multi-match-query.html#multi-match-types 24 24 esMultiMatchTypeBestFields = "best_fields" 25 25 esMultiMatchTypePhrasePrefix = "phrase_prefix" 26 + 27 + // fuzziness options 28 + // Reference: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/common-options.html#fuzziness 29 + esFuzzyAuto = "AUTO" 26 30 ) 27 31 28 32 var _ internal.Indexer = &Indexer{} ··· 145 149 query := elastic.NewBoolQuery() 146 150 147 151 if options.Keyword != "" { 148 - searchType := esMultiMatchTypePhrasePrefix 149 - if options.IsFuzzyKeyword { 150 - searchType = esMultiMatchTypeBestFields 152 + q := elastic.NewBoolQuery() 153 + tokens, err := options.Tokens() 154 + if err != nil { 155 + return nil, err 151 156 } 157 + for _, token := range tokens { 158 + innerQ := elastic.NewMultiMatchQuery(token.Term, "title", "content", "comments") 159 + if token.Fuzzy { 160 + // If the term is not a phrase use fuzziness set to AUTO 161 + innerQ = innerQ.Type(esMultiMatchTypeBestFields).Fuzziness(esFuzzyAuto) 162 + } else { 163 + innerQ = innerQ.Type(esMultiMatchTypePhrasePrefix) 164 + } 152 165 153 - query.Must(elastic.NewMultiMatchQuery(options.Keyword, "title", "content", "comments").Type(searchType)) 166 + switch token.Kind { 167 + case internal.BoolOptMust: 168 + q.Must(innerQ) 169 + case internal.BoolOptShould: 170 + q.Should(innerQ) 171 + case internal.BoolOptNot: 172 + q.MustNot(innerQ) 173 + } 174 + } 175 + query.Must(q) 154 176 } 155 177 156 178 if len(options.RepoIDs) > 0 {
-2
modules/indexer/issues/internal/model.go
··· 74 74 type SearchOptions struct { 75 75 Keyword string // keyword to search 76 76 77 - IsFuzzyKeyword bool // if false the levenshtein distance is 0 78 - 79 77 RepoIDs []int64 // repository IDs which the issues belong to 80 78 AllPublic bool // if include all public repositories 81 79
+112
modules/indexer/issues/internal/qstring.go
··· 1 + // Copyright 2025 The Forgejo Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package internal 5 + 6 + import ( 7 + "io" 8 + "strings" 9 + ) 10 + 11 + type BoolOpt int 12 + 13 + const ( 14 + BoolOptMust BoolOpt = iota 15 + BoolOptShould 16 + BoolOptNot 17 + ) 18 + 19 + type Token struct { 20 + Term string 21 + Kind BoolOpt 22 + Fuzzy bool 23 + } 24 + 25 + type Tokenizer struct { 26 + in *strings.Reader 27 + } 28 + 29 + func (t *Tokenizer) next() (tk Token, err error) { 30 + var ( 31 + sb strings.Builder 32 + r rune 33 + ) 34 + tk.Kind = BoolOptShould 35 + tk.Fuzzy = true 36 + 37 + // skip all leading white space 38 + for { 39 + if r, _, err = t.in.ReadRune(); err == nil && r == ' ' { 40 + //nolint:staticcheck,wastedassign // SA4006 the variable is used after the loop 41 + r, _, err = t.in.ReadRune() 42 + continue 43 + } 44 + break 45 + } 46 + if err != nil { 47 + return tk, err 48 + } 49 + 50 + // check for +/- op, increment to the next rune in both cases 51 + switch r { 52 + case '+': 53 + tk.Kind = BoolOptMust 54 + r, _, err = t.in.ReadRune() 55 + case '-': 56 + tk.Kind = BoolOptNot 57 + r, _, err = t.in.ReadRune() 58 + } 59 + if err != nil { 60 + return tk, err 61 + } 62 + 63 + // parse the string, escaping special characters 64 + for esc := false; err == nil; r, _, err = t.in.ReadRune() { 65 + if esc { 66 + if !strings.ContainsRune("+-\\\"", r) { 67 + sb.WriteRune('\\') 68 + } 69 + sb.WriteRune(r) 70 + esc = false 71 + continue 72 + } 73 + switch r { 74 + case '\\': 75 + esc = true 76 + case '"': 77 + if !tk.Fuzzy { 78 + goto nextEnd 79 + } 80 + tk.Fuzzy = false 81 + case ' ', '\t': 82 + if tk.Fuzzy { 83 + goto nextEnd 84 + } 85 + sb.WriteRune(r) 86 + default: 87 + sb.WriteRune(r) 88 + } 89 + } 90 + nextEnd: 91 + 92 + tk.Term = sb.String() 93 + if err == io.EOF { 94 + err = nil 95 + } // do not consider EOF as an error at the end 96 + return tk, err 97 + } 98 + 99 + // Tokenize the keyword 100 + func (o *SearchOptions) Tokens() (tokens []Token, err error) { 101 + in := strings.NewReader(o.Keyword) 102 + it := Tokenizer{in: in} 103 + 104 + for token, err := it.next(); err == nil; token, err = it.next() { 105 + tokens = append(tokens, token) 106 + } 107 + if err != nil && err != io.EOF { 108 + return nil, err 109 + } 110 + 111 + return tokens, nil 112 + }
+171
modules/indexer/issues/internal/qstring_test.go
··· 1 + // Copyright 2025 The Forgejo Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package internal 5 + 6 + import ( 7 + "testing" 8 + 9 + "github.com/stretchr/testify/assert" 10 + "github.com/stretchr/testify/require" 11 + ) 12 + 13 + type testIssueQueryStringOpt struct { 14 + Keyword string 15 + Results []Token 16 + } 17 + 18 + var testOpts = []testIssueQueryStringOpt{ 19 + { 20 + Keyword: "Hello", 21 + Results: []Token{ 22 + { 23 + Term: "Hello", 24 + Fuzzy: true, 25 + Kind: BoolOptShould, 26 + }, 27 + }, 28 + }, 29 + { 30 + Keyword: "Hello World", 31 + Results: []Token{ 32 + { 33 + Term: "Hello", 34 + Fuzzy: true, 35 + Kind: BoolOptShould, 36 + }, 37 + { 38 + Term: "World", 39 + Fuzzy: true, 40 + Kind: BoolOptShould, 41 + }, 42 + }, 43 + }, 44 + { 45 + Keyword: "+Hello +World", 46 + Results: []Token{ 47 + { 48 + Term: "Hello", 49 + Fuzzy: true, 50 + Kind: BoolOptMust, 51 + }, 52 + { 53 + Term: "World", 54 + Fuzzy: true, 55 + Kind: BoolOptMust, 56 + }, 57 + }, 58 + }, 59 + { 60 + Keyword: "+Hello World", 61 + Results: []Token{ 62 + { 63 + Term: "Hello", 64 + Fuzzy: true, 65 + Kind: BoolOptMust, 66 + }, 67 + { 68 + Term: "World", 69 + Fuzzy: true, 70 + Kind: BoolOptShould, 71 + }, 72 + }, 73 + }, 74 + { 75 + Keyword: "+Hello -World", 76 + Results: []Token{ 77 + { 78 + Term: "Hello", 79 + Fuzzy: true, 80 + Kind: BoolOptMust, 81 + }, 82 + { 83 + Term: "World", 84 + Fuzzy: true, 85 + Kind: BoolOptNot, 86 + }, 87 + }, 88 + }, 89 + { 90 + Keyword: "\"Hello World\"", 91 + Results: []Token{ 92 + { 93 + Term: "Hello World", 94 + Fuzzy: false, 95 + Kind: BoolOptShould, 96 + }, 97 + }, 98 + }, 99 + { 100 + Keyword: "+\"Hello World\"", 101 + Results: []Token{ 102 + { 103 + Term: "Hello World", 104 + Fuzzy: false, 105 + Kind: BoolOptMust, 106 + }, 107 + }, 108 + }, 109 + { 110 + Keyword: "-\"Hello World\"", 111 + Results: []Token{ 112 + { 113 + Term: "Hello World", 114 + Fuzzy: false, 115 + Kind: BoolOptNot, 116 + }, 117 + }, 118 + }, 119 + { 120 + Keyword: "\"+Hello -World\"", 121 + Results: []Token{ 122 + { 123 + Term: "+Hello -World", 124 + Fuzzy: false, 125 + Kind: BoolOptShould, 126 + }, 127 + }, 128 + }, 129 + { 130 + Keyword: "\\+Hello", // \+Hello => +Hello 131 + Results: []Token{ 132 + { 133 + Term: "+Hello", 134 + Fuzzy: true, 135 + Kind: BoolOptShould, 136 + }, 137 + }, 138 + }, 139 + { 140 + Keyword: "\\\\Hello", // \\Hello => \Hello 141 + Results: []Token{ 142 + { 143 + Term: "\\Hello", 144 + Fuzzy: true, 145 + Kind: BoolOptShould, 146 + }, 147 + }, 148 + }, 149 + { 150 + Keyword: "\\\"Hello", // \"Hello => "Hello 151 + Results: []Token{ 152 + { 153 + Term: "\"Hello", 154 + Fuzzy: true, 155 + Kind: BoolOptShould, 156 + }, 157 + }, 158 + }, 159 + } 160 + 161 + func TestIssueQueryString(t *testing.T) { 162 + var opt SearchOptions 163 + for _, res := range testOpts { 164 + t.Run(opt.Keyword, func(t *testing.T) { 165 + opt.Keyword = res.Keyword 166 + tokens, err := opt.Tokens() 167 + require.NoError(t, err) 168 + assert.Equal(t, res.Results, tokens) 169 + }) 170 + } 171 + }
+16 -3
modules/indexer/issues/internal/tests/tests.go
··· 132 132 ExpectedTotal: 3, 133 133 }, 134 134 { 135 + Name: "Keyword Exclude", 136 + ExtraData: []*internal.IndexerData{ 137 + {ID: 1000, Title: "hi hello world"}, 138 + {ID: 1001, Content: "hi hello world"}, 139 + {ID: 1002, Comments: []string{"hello", "hello world"}}, 140 + }, 141 + SearchOptions: &internal.SearchOptions{ 142 + Keyword: "hello world -hi", 143 + SortBy: internal.SortByCreatedDesc, 144 + }, 145 + ExpectedIDs: []int64{1002}, 146 + ExpectedTotal: 1, 147 + }, 148 + { 135 149 Name: "Keyword Fuzzy", 136 150 ExtraData: []*internal.IndexerData{ 137 151 {ID: 1000, Title: "hi hello world"}, ··· 139 153 {ID: 1002, Comments: []string{"hi", "hello world"}}, 140 154 }, 141 155 SearchOptions: &internal.SearchOptions{ 142 - Keyword: "hello world", 143 - SortBy: internal.SortByCreatedDesc, 144 - IsFuzzyKeyword: true, 156 + Keyword: "hello world", 157 + SortBy: internal.SortByCreatedDesc, 145 158 }, 146 159 ExpectedIDs: []int64{1002, 1001, 1000}, 147 160 ExpectedTotal: 3,
+28 -12
modules/indexer/issues/meilisearch/meilisearch.go
··· 232 232 limit = 1 233 233 } 234 234 235 - keyword := options.Keyword 236 - if !options.IsFuzzyKeyword { 237 - // to make it non fuzzy ("typo tolerance" in meilisearch terms), we have to quote the keyword(s) 238 - // https://www.meilisearch.com/docs/reference/api/search#phrase-search 239 - keyword = doubleQuoteKeyword(keyword) 235 + var keywords []string 236 + if options.Keyword != "" { 237 + tokens, err := options.Tokens() 238 + if err != nil { 239 + return nil, err 240 + } 241 + for _, token := range tokens { 242 + if !token.Fuzzy { 243 + // to make it a phrase search, we have to quote the keyword(s) 244 + // https://www.meilisearch.com/docs/reference/api/search#phrase-search 245 + token.Term = doubleQuoteKeyword(token.Term) 246 + } 247 + 248 + // internal.BoolOptShould (Default, requires no modifications) 249 + // internal.BoolOptMust (Not supported by meilisearch) 250 + if token.Kind == internal.BoolOptNot { 251 + token.Term = "-" + token.Term 252 + } 253 + keywords = append(keywords, token.Term) 254 + } 240 255 } 241 256 242 - searchRes, err := b.inner.Client.Index(b.inner.VersionedIndexName()).Search(keyword, &meilisearch.SearchRequest{ 243 - Filter: query.Statement(), 244 - Limit: int64(limit), 245 - Offset: int64(skip), 246 - Sort: sortBy, 247 - MatchingStrategy: meilisearch.All, 248 - }) 257 + searchRes, err := b.inner.Client.Index(b.inner.VersionedIndexName()). 258 + Search(strings.Join(keywords, " "), &meilisearch.SearchRequest{ 259 + Filter: query.Statement(), 260 + Limit: int64(limit), 261 + Offset: int64(skip), 262 + Sort: sortBy, 263 + MatchingStrategy: meilisearch.All, 264 + }) 249 265 if err != nil { 250 266 return nil, err 251 267 }
+8 -16
routers/web/repo/issue.go
··· 204 204 keyword = "" 205 205 } 206 206 207 - isFuzzy := ctx.FormOptionalBool("fuzzy").ValueOrDefault(true) 208 - 209 207 var mileIDs []int64 210 208 if milestoneID > 0 || milestoneID == db.NoConditionID { // -1 to get those issues which have no any milestone assigned 211 209 mileIDs = []int64{milestoneID} ··· 226 224 IssueIDs: nil, 227 225 } 228 226 if keyword != "" { 229 - allIssueIDs, err := issueIDsFromSearch(ctx, keyword, isFuzzy, statsOpts) 227 + allIssueIDs, err := issueIDsFromSearch(ctx, keyword, statsOpts) 230 228 if err != nil { 231 229 if issue_indexer.IsAvailable(ctx) { 232 230 ctx.ServerError("issueIDsFromSearch", err) ··· 294 292 295 293 var issues issues_model.IssueList 296 294 { 297 - ids, err := issueIDsFromSearch(ctx, keyword, isFuzzy, &issues_model.IssuesOptions{ 295 + ids, err := issueIDsFromSearch(ctx, keyword, &issues_model.IssuesOptions{ 298 296 Paginator: &db.ListOptions{ 299 297 Page: pager.Paginater.Current(), 300 298 PageSize: setting.UI.IssuePagingNum, ··· 458 456 ctx.Data["OpenCount"] = issueStats.OpenCount 459 457 ctx.Data["ClosedCount"] = issueStats.ClosedCount 460 458 ctx.Data["AllCount"] = issueStats.AllCount 461 - linkStr := "?q=%s&type=%s&sort=%s&state=%s&labels=%s&milestone=%d&project=%d&assignee=%d&poster=%d&fuzzy=%t&archived=%t" 459 + linkStr := "?q=%s&type=%s&sort=%s&state=%s&labels=%s&milestone=%d&project=%d&assignee=%d&poster=%d&archived=%t" 462 460 ctx.Data["AllStatesLink"] = fmt.Sprintf(linkStr, 463 461 url.QueryEscape(keyword), url.QueryEscape(viewType), url.QueryEscape(sortType), "all", url.QueryEscape(selectLabels), 464 - milestoneID, projectID, assigneeID, posterID, isFuzzy, archived) 462 + milestoneID, projectID, assigneeID, posterID, archived) 465 463 ctx.Data["OpenLink"] = fmt.Sprintf(linkStr, 466 464 url.QueryEscape(keyword), url.QueryEscape(viewType), url.QueryEscape(sortType), "open", url.QueryEscape(selectLabels), 467 - milestoneID, projectID, assigneeID, posterID, isFuzzy, archived) 465 + milestoneID, projectID, assigneeID, posterID, archived) 468 466 ctx.Data["ClosedLink"] = fmt.Sprintf(linkStr, 469 467 url.QueryEscape(keyword), url.QueryEscape(viewType), url.QueryEscape(sortType), "closed", url.QueryEscape(selectLabels), 470 - milestoneID, projectID, assigneeID, posterID, isFuzzy, archived) 468 + milestoneID, projectID, assigneeID, posterID, archived) 471 469 ctx.Data["SelLabelIDs"] = labelIDs 472 470 ctx.Data["SelectLabels"] = selectLabels 473 471 ctx.Data["ViewType"] = viewType ··· 476 474 ctx.Data["ProjectID"] = projectID 477 475 ctx.Data["AssigneeID"] = assigneeID 478 476 ctx.Data["PosterID"] = posterID 479 - ctx.Data["IsFuzzy"] = isFuzzy 480 477 ctx.Data["Keyword"] = keyword 481 478 ctx.Data["IsShowClosed"] = isShowClosed 482 479 switch { ··· 499 496 pager.AddParam(ctx, "assignee", "AssigneeID") 500 497 pager.AddParam(ctx, "poster", "PosterID") 501 498 pager.AddParam(ctx, "archived", "ShowArchivedLabels") 502 - pager.AddParam(ctx, "fuzzy", "IsFuzzy") 503 499 504 500 ctx.Data["Page"] = pager 505 501 } 506 502 507 - func issueIDsFromSearch(ctx *context.Context, keyword string, fuzzy bool, opts *issues_model.IssuesOptions) ([]int64, error) { 508 - ids, _, err := issue_indexer.SearchIssues(ctx, issue_indexer.ToSearchOptions(keyword, opts).Copy( 509 - func(o *issue_indexer.SearchOptions) { 510 - o.IsFuzzyKeyword = fuzzy 511 - }, 512 - )) 503 + func issueIDsFromSearch(ctx *context.Context, keyword string, opts *issues_model.IssuesOptions) ([]int64, error) { 504 + ids, _, err := issue_indexer.SearchIssues(ctx, issue_indexer.ToSearchOptions(keyword, opts)) 513 505 if err != nil { 514 506 return nil, fmt.Errorf("SearchIssues: %w", err) 515 507 }
+2 -10
routers/web/user/home.go
··· 463 463 User: ctx.Doer, 464 464 } 465 465 466 - isFuzzy := ctx.FormOptionalBool("fuzzy").ValueOrDefault(true) 467 - 468 466 // Search all repositories which 469 467 // 470 468 // As user: ··· 594 592 // USING FINAL STATE OF opts FOR A QUERY. 595 593 var issues issues_model.IssueList 596 594 { 597 - issueIDs, _, err := issue_indexer.SearchIssues(ctx, issue_indexer.ToSearchOptions(keyword, opts).Copy( 598 - func(o *issue_indexer.SearchOptions) { o.IsFuzzyKeyword = isFuzzy }, 599 - )) 595 + issueIDs, _, err := issue_indexer.SearchIssues(ctx, issue_indexer.ToSearchOptions(keyword, opts)) 600 596 if err != nil { 601 597 ctx.ServerError("issueIDsFromSearch", err) 602 598 return ··· 622 618 // ------------------------------- 623 619 // Fill stats to post to ctx.Data. 624 620 // ------------------------------- 625 - issueStats, err := getUserIssueStats(ctx, ctxUser, filterMode, issue_indexer.ToSearchOptions(keyword, opts).Copy( 626 - func(o *issue_indexer.SearchOptions) { o.IsFuzzyKeyword = isFuzzy }, 627 - )) 621 + issueStats, err := getUserIssueStats(ctx, ctxUser, filterMode, issue_indexer.ToSearchOptions(keyword, opts)) 628 622 if err != nil { 629 623 ctx.ServerError("getUserIssueStats", err) 630 624 return ··· 679 673 ctx.Data["IsShowClosed"] = isShowClosed 680 674 ctx.Data["SelectLabels"] = selectedLabels 681 675 ctx.Data["PageIsOrgIssues"] = org != nil 682 - ctx.Data["IsFuzzy"] = isFuzzy 683 676 684 677 if isShowClosed { 685 678 ctx.Data["State"] = "closed" ··· 695 688 pager.AddParam(ctx, "labels", "SelectLabels") 696 689 pager.AddParam(ctx, "milestone", "MilestoneID") 697 690 pager.AddParam(ctx, "assignee", "AssigneeID") 698 - pager.AddParam(ctx, "fuzzy", "IsFuzzy") 699 691 ctx.Data["Page"] = pager 700 692 701 693 ctx.HTML(http.StatusOK, tplIssues)
+20 -20
templates/repo/issue/filter_list.tmpl
··· 14 14 <input type="text" placeholder="{{ctx.Locale.Tr "repo.issues.filter_milestone"}}"> 15 15 </div> 16 16 <div class="divider"></div> 17 - <a rel="nofollow" class="{{if not $.MilestoneID}}active selected {{end}}item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone=0&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}&fuzzy={{$.IsFuzzy}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_milestone_all"}}</a> 18 - <a rel="nofollow" class="{{if $.MilestoneID}}{{if eq $.MilestoneID -1}}active selected {{end}}{{end}}item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone=-1&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}&fuzzy={{$.IsFuzzy}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_milestone_none"}}</a> 17 + <a rel="nofollow" class="{{if not $.MilestoneID}}active selected {{end}}item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone=0&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_milestone_all"}}</a> 18 + <a rel="nofollow" class="{{if $.MilestoneID}}{{if eq $.MilestoneID -1}}active selected {{end}}{{end}}item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone=-1&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_milestone_none"}}</a> 19 19 {{if .OpenMilestones}} 20 20 <div class="divider"></div> 21 21 <div class="header">{{ctx.Locale.Tr "repo.issues.filter_milestone_open"}}</div> 22 22 {{range .OpenMilestones}} 23 - <a rel="nofollow" class="{{if $.MilestoneID}}{{if eq $.MilestoneID .ID}}active selected {{end}}{{end}}item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{.ID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}&fuzzy={{$.IsFuzzy}}{{if $.ShowArchivedLabels}}&archived=true{{end}}"> 23 + <a rel="nofollow" class="{{if $.MilestoneID}}{{if eq $.MilestoneID .ID}}active selected {{end}}{{end}}item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{.ID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}"> 24 24 {{svg "octicon-milestone" 16 "mr-2"}} 25 25 {{.Name}} 26 26 </a> ··· 30 30 <div class="divider"></div> 31 31 <div class="header">{{ctx.Locale.Tr "repo.issues.filter_milestone_closed"}}</div> 32 32 {{range .ClosedMilestones}} 33 - <a rel="nofollow" class="{{if $.MilestoneID}}{{if eq $.MilestoneID .ID}}active selected {{end}}{{end}}item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{.ID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}&fuzzy={{$.IsFuzzy}}{{if $.ShowArchivedLabels}}&archived=true{{end}}"> 33 + <a rel="nofollow" class="{{if $.MilestoneID}}{{if eq $.MilestoneID .ID}}active selected {{end}}{{end}}item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{.ID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}"> 34 34 {{svg "octicon-milestone" 16 "mr-2"}} 35 35 {{.Name}} 36 36 </a> ··· 51 51 <i class="icon">{{svg "octicon-search" 16}}</i> 52 52 <input type="text" placeholder="{{ctx.Locale.Tr "repo.issues.filter_project"}}"> 53 53 </div> 54 - <a rel="nofollow" class="{{if not .ProjectID}}active selected {{end}}item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project=&assignee={{$.AssigneeID}}&poster={{$.PosterID}}&fuzzy={{$.IsFuzzy}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_project_all"}}</a> 55 - <a rel="nofollow" class="{{if eq .ProjectID -1}}active selected {{end}}item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project=-1&assignee={{$.AssigneeID}}&poster={{$.PosterID}}&fuzzy={{$.IsFuzzy}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_project_none"}}</a> 54 + <a rel="nofollow" class="{{if not .ProjectID}}active selected {{end}}item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project=&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_project_all"}}</a> 55 + <a rel="nofollow" class="{{if eq .ProjectID -1}}active selected {{end}}item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project=-1&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_project_none"}}</a> 56 56 {{if .OpenProjects}} 57 57 <div class="divider"></div> 58 58 <div class="header"> 59 59 {{ctx.Locale.Tr "repo.issues.new.open_projects"}} 60 60 </div> 61 61 {{range .OpenProjects}} 62 - <a rel="nofollow" class="{{if $.ProjectID}}{{if eq $.ProjectID .ID}}active selected{{end}}{{end}} item tw-flex" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{$.MilestoneID}}&project={{.ID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}&fuzzy={{$.IsFuzzy}}{{if $.ShowArchivedLabels}}&archived=true{{end}}"> 62 + <a rel="nofollow" class="{{if $.ProjectID}}{{if eq $.ProjectID .ID}}active selected{{end}}{{end}} item tw-flex" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{$.MilestoneID}}&project={{.ID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}"> 63 63 {{svg .IconName 18 "tw-mr-2 tw-shrink-0"}}<span class="gt-ellipsis">{{.Title}}</span> 64 64 </a> 65 65 {{end}} ··· 70 70 {{ctx.Locale.Tr "repo.issues.new.closed_projects"}} 71 71 </div> 72 72 {{range .ClosedProjects}} 73 - <a rel="nofollow" class="{{if $.ProjectID}}{{if eq $.ProjectID .ID}}active selected{{end}}{{end}} item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{$.MilestoneID}}&project={{.ID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}&fuzzy={{$.IsFuzzy}}{{if $.ShowArchivedLabels}}&archived=true{{end}}"> 73 + <a rel="nofollow" class="{{if $.ProjectID}}{{if eq $.ProjectID .ID}}active selected{{end}}{{end}} item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{$.MilestoneID}}&project={{.ID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}"> 74 74 {{svg .IconName 18 "tw-mr-2"}}{{.Title}} 75 75 </a> 76 76 {{end}} ··· 82 82 <div class="list-header-author ui dropdown jump item user-remote-search" data-tooltip-content="{{ctx.Locale.Tr "repo.author_search_tooltip"}}" 83 83 data-search-url="{{if .Milestone}}{{$.RepoLink}}/issues/posters{{else}}{{$.Link}}/posters{{end}}" 84 84 data-selected-user-id="{{$.PosterID}}" 85 - data-action-jump-url="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&fuzzy={{$.IsFuzzy}}&poster={user_id}{{if $.ShowArchivedLabels}}&archived=true{{end}}" 85 + data-action-jump-url="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={user_id}{{if $.ShowArchivedLabels}}&archived=true{{end}}" 86 86 > 87 87 <span class="text"> 88 88 {{ctx.Locale.Tr "repo.issues.filter_poster"}} ··· 108 108 <i class="icon">{{svg "octicon-search" 16}}</i> 109 109 <input type="text" placeholder="{{ctx.Locale.Tr "repo.issues.filter_assignee"}}"> 110 110 </div> 111 - <a rel="nofollow" class="{{if not .AssigneeID}}active selected {{end}}item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee=&poster={{$.PosterID}}&fuzzy={{$.IsFuzzy}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_assginee_no_select"}}</a> 112 - <a rel="nofollow" class="{{if eq .AssigneeID -1}}active selected {{end}}item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee=-1&poster={{$.PosterID}}&fuzzy={{$.IsFuzzy}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_assginee_no_assignee"}}</a> 111 + <a rel="nofollow" class="{{if not .AssigneeID}}active selected {{end}}item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee=&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_assginee_no_select"}}</a> 112 + <a rel="nofollow" class="{{if eq .AssigneeID -1}}active selected {{end}}item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee=-1&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_assginee_no_assignee"}}</a> 113 113 <div class="divider"></div> 114 114 {{range .Assignees}} 115 - <a rel="nofollow" class="{{if eq $.AssigneeID .ID}}active selected{{end}} item tw-flex" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{.ID}}&poster={{$.PosterID}}&fuzzy={{$.IsFuzzy}}{{if $.ShowArchivedLabels}}&archived=true{{end}}"> 115 + <a rel="nofollow" class="{{if eq $.AssigneeID .ID}}active selected{{end}} item tw-flex" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{.ID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}"> 116 116 {{ctx.AvatarUtils.Avatar . 20}}{{template "repo/search_name" .}} 117 117 </a> 118 118 {{end}} ··· 127 127 </span> 128 128 {{svg "octicon-triangle-down" 14 "dropdown icon"}} 129 129 <div class="menu"> 130 - <a rel="nofollow" class="{{if eq .ViewType "all"}}active {{end}}item" href="?q={{$.Keyword}}&type=all&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}&fuzzy={{$.IsFuzzy}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_type.all_issues"}}</a> 131 - <a rel="nofollow" class="{{if eq .ViewType "assigned"}}active {{end}}item" href="?q={{$.Keyword}}&type=assigned&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}&fuzzy={{$.IsFuzzy}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_type.assigned_to_you"}}</a> 132 - <a rel="nofollow" class="{{if eq .ViewType "created_by"}}active {{end}}item" href="?q={{$.Keyword}}&type=created_by&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}&fuzzy={{$.IsFuzzy}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_type.created_by_you"}}</a> 130 + <a rel="nofollow" class="{{if eq .ViewType "all"}}active {{end}}item" href="?q={{$.Keyword}}&type=all&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_type.all_issues"}}</a> 131 + <a rel="nofollow" class="{{if eq .ViewType "assigned"}}active {{end}}item" href="?q={{$.Keyword}}&type=assigned&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_type.assigned_to_you"}}</a> 132 + <a rel="nofollow" class="{{if eq .ViewType "created_by"}}active {{end}}item" href="?q={{$.Keyword}}&type=created_by&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_type.created_by_you"}}</a> 133 133 {{if .PageIsPullList}} 134 - <a rel="nofollow" class="{{if eq .ViewType "review_requested"}}active {{end}}item" href="?q={{$.Keyword}}&type=review_requested&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}&fuzzy={{$.IsFuzzy}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_type.review_requested"}}</a> 135 - <a rel="nofollow" class="{{if eq .ViewType "reviewed_by"}}active {{end}}item" href="?q={{$.Keyword}}&type=reviewed_by&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}&fuzzy={{$.IsFuzzy}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_type.reviewed_by_you"}}</a> 134 + <a rel="nofollow" class="{{if eq .ViewType "review_requested"}}active {{end}}item" href="?q={{$.Keyword}}&type=review_requested&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_type.review_requested"}}</a> 135 + <a rel="nofollow" class="{{if eq .ViewType "reviewed_by"}}active {{end}}item" href="?q={{$.Keyword}}&type=reviewed_by&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_type.reviewed_by_you"}}</a> 136 136 {{end}} 137 - <a rel="nofollow" class="{{if eq .ViewType "mentioned"}}active {{end}}item" href="?q={{$.Keyword}}&type=mentioned&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}&fuzzy={{$.IsFuzzy}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_type.mentioning_you"}}</a> 137 + <a rel="nofollow" class="{{if eq .ViewType "mentioned"}}active {{end}}item" href="?q={{$.Keyword}}&type=mentioned&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_type.mentioning_you"}}</a> 138 138 </div> 139 139 </div> 140 140 {{end}} ··· 146 146 </span> 147 147 {{svg "octicon-triangle-down" 14 "dropdown icon"}} 148 148 <div class="menu"> 149 - <a rel="nofollow" class="{{if or (eq .SortType "relevance") (not .SortType)}}active {{end}}item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort=relevency&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}&fuzzy={{$.IsFuzzy}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_sort.relevance"}}</a> 149 + <a rel="nofollow" class="{{if or (eq .SortType "relevance") (not .SortType)}}active {{end}}item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort=relevency&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_sort.relevance"}}</a> 150 150 {{$o := .}} 151 151 {{range $opt := StringUtils.Make "latest" "oldest" "recentupdate" "leastupdate" "mostcomment" "leastcomment" "nearduedate" "farduedate"}} 152 152 {{$text := ctx.Locale.Tr (printf "repo.issues.filter_sort.%s" $opt)}} 153 - <a rel="nofollow" class="{{if eq $o.SortType $opt}}active {{end}}item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$opt}}&state={{$.State}}&labels={{$o.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}&fuzzy={{$.IsFuzzy}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{$text}}</a> 153 + <a rel="nofollow" class="{{if eq $o.SortType $opt}}active {{end}}item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$opt}}&state={{$.State}}&labels={{$o.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{$text}}</a> 154 154 {{end}} 155 155 </div> 156 156 </div>
+3 -3
templates/repo/issue/search.tmpl
··· 10 10 <input type="hidden" name="poster" value="{{$.PosterID}}"> 11 11 {{end}} 12 12 {{if .PageIsPullList}} 13 - {{template "shared/search/combo_fuzzy" dict "Value" .Keyword "IsFuzzy" .IsFuzzy "Placeholder" (ctx.Locale.Tr "search.pull_kind") "Tooltip" (ctx.Locale.Tr "explore.go_to")}} 13 + {{template "shared/search/combo" dict "Value" .Keyword "Placeholder" (ctx.Locale.Tr "search.pull_kind") "Tooltip" (ctx.Locale.Tr "explore.go_to")}} 14 14 {{else if .PageIsMilestones}} 15 - {{template "shared/search/combo_fuzzy" dict "Value" .Keyword "IsFuzzy" .IsFuzzy "Placeholder" (ctx.Locale.Tr "search.milestone_kind") "Tooltip" (ctx.Locale.Tr "explore.go_to")}} 15 + {{template "shared/search/combo" dict "Value" .Keyword "Placeholder" (ctx.Locale.Tr "search.milestone_kind") "Tooltip" (ctx.Locale.Tr "explore.go_to")}} 16 16 {{else}} 17 - {{template "shared/search/combo_fuzzy" dict "Value" .Keyword "IsFuzzy" .IsFuzzy "Placeholder" (ctx.Locale.Tr "search.issue_kind") "Tooltip" (ctx.Locale.Tr "explore.go_to")}} 17 + {{template "shared/search/combo" dict "Value" .Keyword "Placeholder" (ctx.Locale.Tr "search.issue_kind") "Tooltip" (ctx.Locale.Tr "explore.go_to")}} 18 18 {{end}} 19 19 </div> 20 20 </form>
+1 -1
templates/shared/issuelist.tmpl
··· 21 21 {{end}} 22 22 <span class="labels-list tw-ml-1"> 23 23 {{range .Labels}} 24 - <a href="?q={{$.Keyword}}&type={{$.ViewType}}&state={{$.State}}&labels={{.ID}}{{if ne $.listType "milestone"}}&milestone={{$.MilestoneID}}{{end}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}&fuzzy={{$.IsFuzzy}}{{if $.ShowArchivedLabels}}&archived=true{{end}}" rel="nofollow">{{RenderLabel $.Context ctx.Locale .}}</a> 24 + <a href="?q={{$.Keyword}}&type={{$.ViewType}}&state={{$.State}}&labels={{.ID}}{{if ne $.listType "milestone"}}&milestone={{$.MilestoneID}}{{end}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}" rel="nofollow">{{RenderLabel $.Context ctx.Locale .}}</a> 25 25 {{end}} 26 26 </span> 27 27 </div>
+3 -3
templates/shared/label_filter.tmpl
··· 23 23 </div> 24 24 <span class="info">{{ctx.Locale.Tr "repo.issues.filter_label_exclude"}}</span> 25 25 <div class="divider"></div> 26 - <a rel="nofollow" class="{{if .AllLabels}}active selected {{end}}item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels=&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}&fuzzy={{$.IsFuzzy}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_label_no_select"}}</a> 27 - <a rel="nofollow" class="{{if .NoLabel}}active selected {{end}}item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels=0&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}&fuzzy={{$.IsFuzzy}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_label_select_no_label"}}</a> 26 + <a rel="nofollow" class="{{if .AllLabels}}active selected {{end}}item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels=&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_label_no_select"}}</a> 27 + <a rel="nofollow" class="{{if .NoLabel}}active selected {{end}}item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels=0&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.Locale.Tr "repo.issues.filter_label_select_no_label"}}</a> 28 28 {{$previousExclusiveScope := "_no_scope"}} 29 29 {{range .Labels}} 30 30 {{$exclusiveScope := .ExclusiveScope}} ··· 32 32 <div class="divider"></div> 33 33 {{end}} 34 34 {{$previousExclusiveScope = $exclusiveScope}} 35 - <a rel="nofollow" class="item label-filter-item tw-flex tw-items-center" {{if .IsArchived}}data-is-archived{{end}} href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&labels={{.QueryString}}&state={{$.State}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}&fuzzy={{$.IsFuzzy}}{{if $.ShowArchivedLabels}}&archived=true{{end}}" data-label-id="{{.ID}}"> 35 + <a rel="nofollow" class="item label-filter-item tw-flex tw-items-center" {{if .IsArchived}}data-is-archived{{end}} href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&labels={{.QueryString}}&state={{$.State}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}" data-label-id="{{.ID}}"> 36 36 {{if .IsExcluded}} 37 37 {{svg "octicon-circle-slash"}} 38 38 {{else if .IsSelected}}
+5 -1
templates/shared/search/combo.tmpl
··· 3 3 {{/* Placeholder (optional) - placeholder text to be used */}} 4 4 {{/* Tooltip (optional) - a tooltip to be displayed on button hover */}} 5 5 <div class="ui small fluid action input"> 6 - {{template "shared/search/input" dict "Value" .Value "Disabled" .Disabled "Placeholder" .Placeholder}} 6 + {{template "shared/search/input" 7 + dict 8 + "Value" .Value 9 + "Disabled" .Disabled 10 + "Placeholder" .Placeholder}} 7 11 {{template "shared/search/button" dict "Disabled" .Disabled "Tooltip" .Tooltip}} 8 12 </div>
-13
templates/shared/search/combo_fuzzy.tmpl
··· 1 - {{/* Value - value of the search field (for search results page) */}} 2 - {{/* Disabled (optional) - if search field/button has to be disabled */}} 3 - {{/* Placeholder (optional) - placeholder text to be used */}} 4 - {{/* IsFuzzy - state of the fuzzy/union search toggle */}} 5 - {{/* Tooltip (optional) - a tooltip to be displayed on button hover */}} 6 - <div class="ui small fluid action input"> 7 - {{template "shared/search/input" dict "Value" .Value "Disabled" .Disabled "Placeholder" .Placeholder}} 8 - {{template "shared/search/fuzzy" 9 - dict 10 - "Disabled" .Disabled 11 - "IsFuzzy" .IsFuzzy}} 12 - {{template "shared/search/button" dict "Disabled" .Disabled "Tooltip" .Tooltip}} 13 - </div>
-15
templates/shared/search/fuzzy.tmpl
··· 1 - {{/* Disabled (optional) - if dropdown has to be disabled */}} 2 - {{/* IsFuzzy - state of the fuzzy search toggle */}} 3 - <div class="ui small dropdown selection {{if .Disabled}} disabled{{end}}" data-tooltip-content="{{ctx.Locale.Tr "search.type_tooltip"}}"> 4 - <input name="fuzzy" type="hidden"{{if .Disabled}} disabled{{end}} value="{{.IsFuzzy}}">{{svg "octicon-triangle-down" 14 "dropdown icon"}} 5 - <div class="text">{{if .IsFuzzy}}{{/* 6 - */}}{{ctx.Locale.Tr "search.fuzzy"}}{{/* 7 - */}}{{else}}{{/* 8 - */}}{{ctx.Locale.Tr "search.exact"}}{{/* 9 - */}}{{end}}</div> 10 - <div class="menu"> 11 - <div class="item" data-value="true" data-tooltip-content="{{ctx.Locale.Tr "search.fuzzy_tooltip"}}">{{/* 12 - */}}{{ctx.Locale.Tr "search.fuzzy"}}</div> 13 - <div class="item" data-value="false" data-tooltip-content="{{ctx.Locale.Tr "search.exact_tooltip"}}">{{ctx.Locale.Tr "search.exact"}}</div> 14 - </div> 15 - </div>
+17 -18
templates/user/dashboard/issues.tmpl
··· 5 5 {{template "base/alert" .}} 6 6 <div class="list-header"> 7 7 <div class="switch list-header-toggle"> 8 - <a class="item{{if not .IsShowClosed}} active{{end}}" href="?type={{$.ViewType}}&sort={{$.SortType}}&state=open&labels={{.SelectLabels}}&q={{$.Keyword}}&fuzzy={{.IsFuzzy}}"> 8 + <a class="item{{if not .IsShowClosed}} active{{end}}" href="?type={{$.ViewType}}&sort={{$.SortType}}&state=open&labels={{.SelectLabels}}&q={{$.Keyword}}"> 9 9 {{svg "octicon-issue-opened" 16 "tw-mr-2"}} 10 10 {{ctx.Locale.PrettyNumber .IssueStats.OpenCount}}&nbsp;{{ctx.Locale.Tr "repo.issues.open_title"}} 11 11 </a> 12 - <a class="item{{if .IsShowClosed}} active{{end}}" href="?type={{$.ViewType}}&sort={{$.SortType}}&state=closed&labels={{.SelectLabels}}&q={{$.Keyword}}&fuzzy={{.IsFuzzy}}"> 12 + <a class="item{{if .IsShowClosed}} active{{end}}" href="?type={{$.ViewType}}&sort={{$.SortType}}&state=closed&labels={{.SelectLabels}}&q={{$.Keyword}}"> 13 13 {{svg "octicon-issue-closed" 16 "tw-mr-2"}} 14 14 {{ctx.Locale.PrettyNumber .IssueStats.ClosedCount}}&nbsp;{{ctx.Locale.Tr "repo.issues.closed_title"}} 15 15 </a> ··· 20 20 <input type="hidden" name="sort" value="{{$.SortType}}"> 21 21 <input type="hidden" name="state" value="{{$.State}}"> 22 22 {{if .PageIsPulls}} 23 - {{template "shared/search/combo_fuzzy" dict "Value" $.Keyword "IsFuzzy" $.IsFuzzy "Placeholder" (ctx.Locale.Tr "search.pull_kind") "Tooltip" (ctx.Locale.Tr "explore.go_to")}} 23 + {{template "shared/search/combo" dict "Value" $.Keyword "Placeholder" (ctx.Locale.Tr "search.pull_kind") "Tooltip" (ctx.Locale.Tr "explore.go_to")}} 24 24 {{else}} 25 - {{template "shared/search/combo_fuzzy" dict "Value" $.Keyword "IsFuzzy" $.IsFuzzy "Placeholder" (ctx.Locale.Tr "search.issue_kind") "Tooltip" (ctx.Locale.Tr "explore.go_to")}} 25 + {{template "shared/search/combo" dict "Value" $.Keyword "Placeholder" (ctx.Locale.Tr "search.issue_kind") "Tooltip" (ctx.Locale.Tr "explore.go_to")}} 26 26 {{end}} 27 27 </div> 28 28 </form> ··· 38 38 {{svg "octicon-triangle-down" 14 "dropdown icon"}} 39 39 </span> 40 40 <div class="ui menu"> 41 - <a class="{{if eq .ViewType "created_by"}}active{{end}} item" href="?type=created_by&sort={{$.SortType}}&state={{.State}}&q={{$.Keyword}}&fuzzy={{.IsFuzzy}}"> 41 + <a class="{{if eq .ViewType "created_by"}}active{{end}} item" href="?type=created_by&sort={{$.SortType}}&state={{.State}}&q={{$.Keyword}}"> 42 42 <div class="ui circular mini label tw-ml-0">{{CountFmt .IssueStats.CreateCount}}</div> 43 43 {{ctx.Locale.Tr "repo.issues.filter_type.created_by_you"}} 44 44 </a> 45 - <a class="{{if eq .ViewType "your_repositories"}}active{{end}} item" href="?type=your_repositories&sort={{$.SortType}}&state={{.State}}&q={{$.Keyword}}&fuzzy={{.IsFuzzy}}"> 45 + <a class="{{if eq .ViewType "your_repositories"}}active{{end}} item" href="?type=your_repositories&sort={{$.SortType}}&state={{.State}}&q={{$.Keyword}}"> 46 46 <div class="ui circular mini label tw-ml-0">{{CountFmt .IssueStats.YourRepositoriesCount}}</div> 47 47 {{ctx.Locale.Tr "home.issues.in_your_repos"}} 48 48 </a> 49 - <a class="{{if eq .ViewType "assigned"}}active{{end}} item" href="?type=assigned&sort={{$.SortType}}&state={{.State}}&q={{$.Keyword}}&fuzzy={{.IsFuzzy}}"> 49 + <a class="{{if eq .ViewType "assigned"}}active{{end}} item" href="?type=assigned&sort={{$.SortType}}&state={{.State}}&q={{$.Keyword}}"> 50 50 <div class="ui circular mini label tw-ml-0">{{CountFmt .IssueStats.AssignCount}}</div> 51 51 {{ctx.Locale.Tr "repo.issues.filter_type.assigned_to_you"}} 52 52 </a> 53 53 {{if .PageIsPulls}} 54 - <a class="{{if eq .ViewType "review_requested"}}active{{end}} item" href="?type=review_requested&sort={{$.SortType}}&state={{.State}}&q={{$.Keyword}}&fuzzy={{.IsFuzzy}}"> 54 + <a class="{{if eq .ViewType "review_requested"}}active{{end}} item" href="?type=review_requested&sort={{$.SortType}}&state={{.State}}&q={{$.Keyword}}"> 55 55 <div class="ui circular mini label tw-ml-0">{{CountFmt .IssueStats.ReviewRequestedCount}}</div> 56 56 {{ctx.Locale.Tr "repo.issues.filter_type.review_requested"}} 57 57 </a> 58 - <a class="{{if eq .ViewType "reviewed_by"}}active{{end}} item" href="?type=reviewed_by&sort={{$.SortType}}&state={{.State}}&q={{$.Keyword}}&fuzzy={{.IsFuzzy}}"> 58 + <a class="{{if eq .ViewType "reviewed_by"}}active{{end}} item" href="?type=reviewed_by&sort={{$.SortType}}&state={{.State}}&q={{$.Keyword}}"> 59 59 <div class="ui circular mini label tw-ml-0">{{CountFmt .IssueStats.ReviewedCount}}</div> 60 60 {{ctx.Locale.Tr "repo.issues.filter_type.reviewed_by_you"}} 61 61 </a> 62 62 {{end}} 63 - <a class="{{if eq .ViewType "mentioned"}}active{{end}} item" href="?type=mentioned&sort={{$.SortType}}&state={{.State}}&q={{$.Keyword}}&fuzzy={{.IsFuzzy}}"> 63 + <a class="{{if eq .ViewType "mentioned"}}active{{end}} item" href="?type=mentioned&sort={{$.SortType}}&state={{.State}}&q={{$.Keyword}}"> 64 64 <div class="ui circular mini label tw-ml-0">{{CountFmt .IssueStats.MentionCount}}</div> 65 65 {{ctx.Locale.Tr "repo.issues.filter_type.mentioning_you"}} 66 66 </a> ··· 73 73 {{svg "octicon-triangle-down" 14 "dropdown icon"}} 74 74 </span> 75 75 <div class="menu"> 76 - <a class="{{if eq .SortType "recentupdate"}}active {{end}}item" href="?type={{$.ViewType}}&sort=recentupdate&state={{$.State}}&labels={{.SelectLabels}}&q={{$.Keyword}}&fuzzy={{.IsFuzzy}}">{{ctx.Locale.Tr "repo.issues.filter_sort.recentupdate"}}</a> 77 - <a class="{{if eq .SortType "leastupdate"}}active {{end}}item" href="?type={{$.ViewType}}&sort=leastupdate&state={{$.State}}&labels={{.SelectLabels}}&q={{$.Keyword}}&fuzzy={{.IsFuzzy}}">{{ctx.Locale.Tr "repo.issues.filter_sort.leastupdate"}}</a> 78 - <a class="{{if or (eq .SortType "latest") (not .SortType)}}active {{end}}item" href="?type={{$.ViewType}}&sort=latest&state={{$.State}}&labels={{.SelectLabels}}&q={{$.Keyword}}&fuzzy={{.IsFuzzy}}">{{ctx.Locale.Tr "repo.issues.filter_sort.latest"}}</a> 79 - <a class="{{if eq .SortType "oldest"}}active {{end}}item" href="?type={{$.ViewType}}&sort=oldest&state={{$.State}}&labels={{.SelectLabels}}&q={{$.Keyword}}&fuzzy={{.IsFuzzy}}">{{ctx.Locale.Tr "repo.issues.filter_sort.oldest"}}</a> 80 - <a class="{{if eq .SortType "mostcomment"}}active {{end}}item" href="?type={{$.ViewType}}&sort=mostcomment&state={{$.State}}&labels={{.SelectLabels}}&q={{$.Keyword}}&fuzzy={{.IsFuzzy}}">{{ctx.Locale.Tr "repo.issues.filter_sort.mostcomment"}}</a> 81 - <a class="{{if eq .SortType "leastcomment"}}active {{end}}item" href="?type={{$.ViewType}}&sort=leastcomment&state={{$.State}}&labels={{.SelectLabels}}&q={{$.Keyword}}&fuzzy={{.IsFuzzy}}">{{ctx.Locale.Tr "repo.issues.filter_sort.leastcomment"}}</a> 82 - <a class="{{if eq .SortType "nearduedate"}}active {{end}}item" href="?type={{$.ViewType}}&sort=nearduedate&state={{$.State}}&labels={{.SelectLabels}}&q={{$.Keyword}}&fuzzy={{.IsFuzzy}}">{{ctx.Locale.Tr "repo.issues.filter_sort.nearduedate"}}</a> 83 - <a class="{{if eq .SortType "farduedate"}}active {{end}}item" href="?type={{$.ViewType}}&sort=farduedate&state={{$.State}}&labels={{.SelectLabels}}&q={{$.Keyword}}&fuzzy={{.IsFuzzy}}">{{ctx.Locale.Tr "repo.issues.filter_sort.farduedate"}}</a> 76 + {{$o := .}} 77 + {{range $opt := StringUtils.Make "recentupdate" "leastupdate" "latest" "oldest" "mostcomment" "leastcomment" "nearduedate" "farduedate"}} 78 + {{$text := ctx.Locale.Tr (printf "repo.issues.filter_sort.%s" $opt)}} 79 + <a class="{{if or (eq $o.SortType $opt) (and (eq $opt "latest") (not $o.SortType))}}active {{end}}item" href="?type={{$.ViewType}}&sort={{$opt}}&state={{$.State}}&labels={{$o.SelectLabels}}&q={{$.Keyword}}">{{ 80 + $text 81 + }}</a> 82 + {{end}} 84 83 </div> 85 84 </div> 86 85 </div>
+14 -16
tests/integration/issue_test.go
··· 138 138 }) 139 139 140 140 // keyword: 'firstt' 141 - // should not match when fuzzy searching is disabled 142 - req = NewRequestf(t, "GET", "%s/issues?q=%st&fuzzy=false", repo.Link(), keyword) 141 + // should not match when using phrase search 142 + req = NewRequestf(t, "GET", "%s/issues?q=\"%st\"", repo.Link(), keyword) 143 143 resp = MakeRequest(t, req, http.StatusOK) 144 144 htmlDoc = NewHTMLParser(t, resp.Body) 145 145 issuesSelection = getIssuesSelection(t, htmlDoc) 146 146 assert.EqualValues(t, 0, issuesSelection.Length()) 147 147 148 - // should match as 'first' when fuzzy seaeching is enabled 149 - for _, fmt := range []string{"%s/issues?q=%st&fuzzy=true", "%s/issues?q=%st"} { 150 - req = NewRequestf(t, "GET", fmt, repo.Link(), keyword) 151 - resp = MakeRequest(t, req, http.StatusOK) 152 - htmlDoc = NewHTMLParser(t, resp.Body) 153 - issuesSelection = getIssuesSelection(t, htmlDoc) 154 - assert.EqualValues(t, 1, issuesSelection.Length()) 155 - issuesSelection.Each(func(_ int, selection *goquery.Selection) { 156 - issue := getIssue(t, repo.ID, selection) 157 - assert.False(t, issue.IsClosed) 158 - assert.False(t, issue.IsPull) 159 - assertMatch(t, issue, keyword) 160 - }) 161 - } 148 + // should match as 'first' when using a standard query 149 + req = NewRequestf(t, "GET", "%s/issues?q=%st", repo.Link(), keyword) 150 + resp = MakeRequest(t, req, http.StatusOK) 151 + htmlDoc = NewHTMLParser(t, resp.Body) 152 + issuesSelection = getIssuesSelection(t, htmlDoc) 153 + assert.EqualValues(t, 1, issuesSelection.Length()) 154 + issuesSelection.Each(func(_ int, selection *goquery.Selection) { 155 + issue := getIssue(t, repo.ID, selection) 156 + assert.False(t, issue.IsClosed) 157 + assert.False(t, issue.IsPull) 158 + assertMatch(t, issue, keyword) 159 + }) 162 160 } 163 161 164 162 func TestViewIssuesSearchOptions(t *testing.T) {
-37
tests/integration/repo_test.go
··· 1120 1120 assert.Contains(t, href, "&project=") 1121 1121 assert.Contains(t, href, "&assignee=") 1122 1122 assert.Contains(t, href, "&poster=") 1123 - assert.Contains(t, href, "&fuzzy=") 1124 1123 }) 1125 1124 assert.True(t, called) 1126 1125 }) ··· 1145 1144 assert.Contains(t, href, "&project=") 1146 1145 assert.Contains(t, href, "&assignee=") 1147 1146 assert.Contains(t, href, "&poster=") 1148 - assert.Contains(t, href, "&fuzzy=") 1149 - }) 1150 - assert.True(t, called) 1151 - }) 1152 - 1153 - t.Run("Fuzzy", func(t *testing.T) { 1154 - defer tests.PrintCurrentTest(t)() 1155 - 1156 - req := NewRequest(t, "GET", "/user2/repo1/issues?fuzzy=false") 1157 - resp := MakeRequest(t, req, http.StatusOK) 1158 - htmlDoc := NewHTMLParser(t, resp.Body) 1159 - 1160 - called := false 1161 - htmlDoc.Find("#issue-filters a[href^='?']").Each(func(_ int, s *goquery.Selection) { 1162 - called = true 1163 - href, _ := s.Attr("href") 1164 - assert.Contains(t, href, "?q=&") 1165 - assert.Contains(t, href, "&type=") 1166 - assert.Contains(t, href, "&sort=") 1167 - assert.Contains(t, href, "&state=") 1168 - assert.Contains(t, href, "&labels=") 1169 - assert.Contains(t, href, "&milestone=") 1170 - assert.Contains(t, href, "&project=") 1171 - assert.Contains(t, href, "&assignee=") 1172 - assert.Contains(t, href, "&poster=") 1173 - assert.Contains(t, href, "&fuzzy=false") 1174 1147 }) 1175 1148 assert.True(t, called) 1176 1149 }) ··· 1195 1168 assert.Contains(t, href, "&project=") 1196 1169 assert.Contains(t, href, "&assignee=") 1197 1170 assert.Contains(t, href, "&poster=") 1198 - assert.Contains(t, href, "&fuzzy=") 1199 1171 }) 1200 1172 assert.True(t, called) 1201 1173 }) ··· 1220 1192 assert.Contains(t, href, "&project=") 1221 1193 assert.Contains(t, href, "&assignee=") 1222 1194 assert.Contains(t, href, "&poster=") 1223 - assert.Contains(t, href, "&fuzzy=") 1224 1195 }) 1225 1196 assert.True(t, called) 1226 1197 }) ··· 1245 1216 assert.Contains(t, href, "&project=") 1246 1217 assert.Contains(t, href, "&assignee=") 1247 1218 assert.Contains(t, href, "&poster=") 1248 - assert.Contains(t, href, "&fuzzy=") 1249 1219 }) 1250 1220 assert.True(t, called) 1251 1221 }) ··· 1270 1240 assert.Contains(t, href, "&project=") 1271 1241 assert.Contains(t, href, "&assignee=") 1272 1242 assert.Contains(t, href, "&poster=") 1273 - assert.Contains(t, href, "&fuzzy=") 1274 1243 }) 1275 1244 assert.True(t, called) 1276 1245 }) ··· 1295 1264 assert.Contains(t, href, "&project=") 1296 1265 assert.Contains(t, href, "&assignee=") 1297 1266 assert.Contains(t, href, "&poster=") 1298 - assert.Contains(t, href, "&fuzzy=") 1299 1267 }) 1300 1268 assert.True(t, called) 1301 1269 }) ··· 1320 1288 assert.Contains(t, href, "&project=1") 1321 1289 assert.Contains(t, href, "&assignee=") 1322 1290 assert.Contains(t, href, "&poster=") 1323 - assert.Contains(t, href, "&fuzzy=") 1324 1291 }) 1325 1292 assert.True(t, called) 1326 1293 }) ··· 1345 1312 assert.Contains(t, href, "&project=") 1346 1313 assert.Contains(t, href, "&assignee=1") 1347 1314 assert.Contains(t, href, "&poster=") 1348 - assert.Contains(t, href, "&fuzzy=") 1349 1315 }) 1350 1316 assert.True(t, called) 1351 1317 }) ··· 1370 1336 assert.Contains(t, href, "&project=") 1371 1337 assert.Contains(t, href, "&assignee=") 1372 1338 assert.Contains(t, href, "&poster=1") 1373 - assert.Contains(t, href, "&fuzzy=") 1374 1339 }) 1375 1340 assert.True(t, called) 1376 1341 }) ··· 1395 1360 assert.Contains(t, href, "&project=") 1396 1361 assert.Contains(t, href, "&assignee=") 1397 1362 assert.Contains(t, href, "&poster=") 1398 - assert.Contains(t, href, "&fuzzy=") 1399 1363 }) 1400 1364 assert.True(t, called) 1401 1365 }) ··· 1420 1384 assert.Contains(t, href, "&project=") 1421 1385 assert.Contains(t, href, "&assignee=") 1422 1386 assert.Contains(t, href, "&poster=") 1423 - assert.Contains(t, href, "&fuzzy=") 1424 1387 assert.Contains(t, href, "&archived=true") 1425 1388 }) 1426 1389 assert.True(t, called)