fix(code search): various ui/ux improvements raised by user research (#4332)
various improvements from [user research](https://codeberg.org/forgejo/user-research/src/branch/main/interviews/2024-06/issues%28search%2Cfeatures%29%2Cselfhosting%2CCI%2Caccessibility%2Ccodesearch.md) - filenames are links to the respective files fixes: `fold menu: user clicked and expected to view file, instead the file collapsed` - refactor(searchfile.tmpl): ordered lists with lines grouped together instead of table fixes: `multiple matches per file are "merged", only visible in the code lines (no visual separation)` - feat: display fuzzy as "Either" when using git-grep which is much more accurate than "fuzzy" git-grep does not support fuzzy searching, in this context selecting fuzzy searching worked similar to an OR of white space separated keywords fixes: `typo doesn't bring results in fuzzy search` --- <details> <summary>Before</summary>  </details> <details> <summary>After</summary>  </details> Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/4332 Reviewed-by: 0ko <0ko@noreply.codeberg.org> Co-authored-by: Shiny Nematoda <snematoda.751k2@aleeas.com> Co-committed-by: Shiny Nematoda <snematoda.751k2@aleeas.com>
This commit is contained in:
		
					parent
					
						
							
								2d85d51879
							
						
					
				
			
			
				commit
				
					
						928f188689
					
				
			
		
					 7 changed files with 88 additions and 24 deletions
				
			
		| 
						 | 
				
			
			@ -164,6 +164,8 @@ search = Search...
 | 
			
		|||
type_tooltip = Search type
 | 
			
		||||
fuzzy = Fuzzy
 | 
			
		||||
fuzzy_tooltip = Include results that also match the search term closely
 | 
			
		||||
union = Union
 | 
			
		||||
union_tooltip = Include results that match any of the whitespace seperated keywords
 | 
			
		||||
exact = Exact
 | 
			
		||||
exact_tooltip = Include only results that match the exact search term
 | 
			
		||||
repo_kind = Search repos...
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,7 +26,10 @@
 | 
			
		|||
							- {{.Filename}}
 | 
			
		||||
						</span>
 | 
			
		||||
					{{else}}
 | 
			
		||||
						<span class="file tw-flex-1 tw-ml-2">{{.Filename}}</span>
 | 
			
		||||
						<span class="file tw-flex-1 tw-ml-2">
 | 
			
		||||
							<a rel="nofollow" class="muted file-link" href="{{$repo.Link}}/src/commit/{{$result.CommitID | PathEscape}}/{{.Filename | PathEscapeSegments}}">{{/*
 | 
			
		||||
							*/}}{{.Filename}}</a>
 | 
			
		||||
						</span>
 | 
			
		||||
					{{end}}
 | 
			
		||||
					<a role="button" class="ui basic tiny button" rel="nofollow" href="{{$repo.Link}}/src/commit/{{$result.CommitID | PathEscape}}/{{.Filename | PathEscapeSegments}}">{{ctx.Locale.Tr "repo.diff.view_file"}}</a>
 | 
			
		||||
				</h4>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,11 @@
 | 
			
		|||
<form class="ui form ignore-dirty">
 | 
			
		||||
	{{template "shared/search/combo_fuzzy" dict "Value" .Keyword "Disabled" .CodeIndexerUnavailable "IsFuzzy" .IsFuzzy "Placeholder" (ctx.Locale.Tr "search.code_kind")}}
 | 
			
		||||
	{{template "shared/search/combo_fuzzy"
 | 
			
		||||
		dict
 | 
			
		||||
			"Value" .Keyword
 | 
			
		||||
			"Disabled" .CodeIndexerUnavailable
 | 
			
		||||
			"IsFuzzy" .IsFuzzy
 | 
			
		||||
			"Placeholder" (ctx.Locale.Tr "search.code_kind")
 | 
			
		||||
			"CodeIndexerDisabled" $.CodeIndexerDisabled}}
 | 
			
		||||
</form>
 | 
			
		||||
<div class="divider"></div>
 | 
			
		||||
<div class="ui user list">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,10 +1,15 @@
 | 
			
		|||
{{/* Value - value of the search field (for search results page) */}}
 | 
			
		||||
{{/* Disabled (optional) - if search field/button has to be disabled */}}
 | 
			
		||||
{{/* Placeholder (optional) - placeholder text to be used */}}
 | 
			
		||||
{{/* IsFuzzy - state of the fuzzy search toggle */}}
 | 
			
		||||
{{/* IsFuzzy - state of the fuzzy/union search toggle */}}
 | 
			
		||||
{{/* CodeIndexerDisabled (optional) - if the performed search is done using git-grep */}}
 | 
			
		||||
{{/* Tooltip (optional) - a tooltip to be displayed on button hover */}}
 | 
			
		||||
<div class="ui small fluid action input">
 | 
			
		||||
	{{template "shared/search/input" dict "Value" .Value "Disabled" .Disabled "Placeholder" .Placeholder}}
 | 
			
		||||
	{{template "shared/search/fuzzy" dict "Disabled" .Disabled "IsFuzzy" .IsFuzzy}}
 | 
			
		||||
	{{template "shared/search/fuzzy"
 | 
			
		||||
		dict
 | 
			
		||||
			"Disabled" .Disabled
 | 
			
		||||
			"IsFuzzy" .IsFuzzy
 | 
			
		||||
			"CodeIndexerDisabled" .CodeIndexerDisabled}}
 | 
			
		||||
	{{template "shared/search/button" dict "Disabled" .Disabled "Tooltip" .Tooltip}}
 | 
			
		||||
</div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,10 +1,21 @@
 | 
			
		|||
{{/* Disabled (optional) - if dropdown has to be disabled */}}
 | 
			
		||||
{{/* IsFuzzy - state of the fuzzy search toggle */}}
 | 
			
		||||
<div class="ui small dropdown selection {{if .Disabled}} disabled{{end}}" data-tooltip-content="{{ctx.Locale.Tr "search.type_tooltip"}}">
 | 
			
		||||
<div class="ui small dropdown selection {{if .Disabled}} disabled{{end}}" data-tooltip-content="{{ctx.Locale.Tr "search.type_tooltip"}}" data-test-tag="fuzzy-dropdown">
 | 
			
		||||
	{{$fuzzyType := "fuzzy"}}
 | 
			
		||||
	{{if .CodeIndexerDisabled}}
 | 
			
		||||
		{{$fuzzyType = "union"}}
 | 
			
		||||
	{{end}}
 | 
			
		||||
	<input name="fuzzy" type="hidden"{{if .Disabled}} disabled{{end}} value="{{.IsFuzzy}}">{{svg "octicon-triangle-down" 14 "dropdown icon"}}
 | 
			
		||||
	<div class="text">{{if .IsFuzzy}}{{ctx.Locale.Tr "search.fuzzy"}}{{else}}{{ctx.Locale.Tr "search.exact"}}{{end}}</div>
 | 
			
		||||
	<div class="text">{{/*
 | 
			
		||||
		if code indexer is disabled display fuzzy as union
 | 
			
		||||
	*/}}{{if .IsFuzzy}}{{/*
 | 
			
		||||
		*/}}{{ctx.Locale.Tr (printf "search.%s" $fuzzyType)}}{{/*
 | 
			
		||||
	*/}}{{else}}{{/*
 | 
			
		||||
		*/}}{{ctx.Locale.Tr "search.exact"}}{{/*
 | 
			
		||||
	*/}}{{end}}</div>
 | 
			
		||||
	<div class="menu">
 | 
			
		||||
		<div class="item" data-value="true" data-tooltip-content="{{ctx.Locale.Tr "search.fuzzy_tooltip"}}">{{ctx.Locale.Tr "search.fuzzy"}}</div>
 | 
			
		||||
		<div class="item" data-value="true" data-tooltip-content="{{ctx.Locale.Tr (printf "search.%s_tooltip" $fuzzyType)}}">{{/*
 | 
			
		||||
		*/}}{{ctx.Locale.Tr (printf "search.%s" $fuzzyType)}}</div>
 | 
			
		||||
		<div class="item" data-value="false" data-tooltip-content="{{ctx.Locale.Tr "search.exact_tooltip"}}">{{ctx.Locale.Tr "search.exact"}}</div>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,14 +1,25 @@
 | 
			
		|||
<div class="file-body file-code code-view">
 | 
			
		||||
	<table>
 | 
			
		||||
		<tbody>
 | 
			
		||||
			{{range .SearchResult.Lines}}
 | 
			
		||||
				<tr>
 | 
			
		||||
					<td class="lines-num">
 | 
			
		||||
						<a href="{{$.RepoLink}}/src/commit/{{PathEscape $.SearchResult.CommitID}}/{{PathEscapeSegments $.SearchResult.Filename}}#L{{.Num}}"><span>{{.Num}}</span></a>
 | 
			
		||||
					</td>
 | 
			
		||||
					<td class="lines-code chroma"><code class="code-inner">{{.FormattedContent}}</code></td>
 | 
			
		||||
				</tr>
 | 
			
		||||
			{{end}}
 | 
			
		||||
		</tbody>
 | 
			
		||||
	</table>
 | 
			
		||||
	<div>
 | 
			
		||||
		<ol class="tw-p-0 tw-m-0">
 | 
			
		||||
		{{/* if the expected line number does not match
 | 
			
		||||
		     the actual line number end the ordered list
 | 
			
		||||
		     and begin a new one */}}
 | 
			
		||||
		{{$expNum := 0}}
 | 
			
		||||
		{{range .SearchResult.Lines}}
 | 
			
		||||
		{{if and (gt $expNum 0) (ne .Num $expNum)}}
 | 
			
		||||
		</ol>
 | 
			
		||||
		<ol class="tw-p-0 tw-m-0 tw-pt-2 tw-mt-2 tw-border-t-4 tw-border-secondary">
 | 
			
		||||
		{{end}}
 | 
			
		||||
			<li value="{{.Num}}" class="tw-grid tw-grid-cols-[minmax(50px,_1%)_auto] tw-list-none">
 | 
			
		||||
				<div class="lines-num">
 | 
			
		||||
					<a href="{{$.RepoLink}}/src/commit/{{PathEscape $.SearchResult.CommitID}}/{{PathEscapeSegments $.SearchResult.Filename}}#L{{.Num}}">
 | 
			
		||||
						{{.Num}}
 | 
			
		||||
					</a>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div class="lines-code chroma"><code class="code-inner">{{.FormattedContent}}</code></div>
 | 
			
		||||
			</li>
 | 
			
		||||
			{{$expNum = Eval .Num "+" 1}}
 | 
			
		||||
		{{end}}
 | 
			
		||||
		</ol>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,11 +20,20 @@ import (
 | 
			
		|||
)
 | 
			
		||||
 | 
			
		||||
func resultFilenames(t testing.TB, doc *HTMLDoc) []string {
 | 
			
		||||
	filenameSelections := doc.Find(".repository.search").Find("details.repo-search-result").Find(".header").Find("span.file")
 | 
			
		||||
	result := make([]string, filenameSelections.Length())
 | 
			
		||||
	filenameSelections.Each(func(i int, selection *goquery.Selection) {
 | 
			
		||||
		result[i] = selection.Text()
 | 
			
		||||
	resultSelections := doc.
 | 
			
		||||
		Find(".repository.search").
 | 
			
		||||
		Find("details.repo-search-result")
 | 
			
		||||
 | 
			
		||||
	result := make([]string, resultSelections.Length())
 | 
			
		||||
	resultSelections.Each(func(i int, selection *goquery.Selection) {
 | 
			
		||||
		assert.True(t, resultSelections.Find("div ol li").Length() > 0)
 | 
			
		||||
		result[i] = selection.
 | 
			
		||||
			Find(".header").
 | 
			
		||||
			Find("span.file a.file-link").
 | 
			
		||||
			First().
 | 
			
		||||
			Text()
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	return result
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -77,7 +86,7 @@ func testSearchRepo(t *testing.T, indexer bool) {
 | 
			
		|||
		testSearch(t, "/user2/glob/search?q=file4&page=1", []string{"x/b.txt", "a.txt"}, indexer)
 | 
			
		||||
		testSearch(t, "/user2/glob/search?q=file5&page=1", []string{"x/b.txt", "a.txt"}, indexer)
 | 
			
		||||
	} else {
 | 
			
		||||
		// fuzzy search: OR of all the keywords
 | 
			
		||||
		// fuzzy search: Union/OR of all the keywords
 | 
			
		||||
		// when indexer is disabled
 | 
			
		||||
		testSearch(t, "/user2/glob/search?q=file3+file1&page=1", []string{"a.txt", "x/b.txt"}, indexer)
 | 
			
		||||
		testSearch(t, "/user2/glob/search?q=file4&page=1", []string{}, indexer)
 | 
			
		||||
| 
						 | 
				
			
			@ -102,6 +111,23 @@ func testSearch(t *testing.T, url string, expected []string, indexer bool) {
 | 
			
		|||
	branchDropdown := container.Find(".js-branch-tag-selector")
 | 
			
		||||
	assert.EqualValues(t, indexer, len(branchDropdown.Nodes) == 0)
 | 
			
		||||
 | 
			
		||||
	// if indexer is disabled "fuzzy" should be displayed as "union"
 | 
			
		||||
	expectedFuzzy := "Fuzzy"
 | 
			
		||||
	if !indexer {
 | 
			
		||||
		expectedFuzzy = "Union"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fuzzyDropdown := container.Find(".ui.dropdown[data-test-tag=fuzzy-dropdown]")
 | 
			
		||||
	actualFuzzyText := fuzzyDropdown.Find(".menu .item[data-value=true]").First().Text()
 | 
			
		||||
	assert.EqualValues(t, expectedFuzzy, actualFuzzyText)
 | 
			
		||||
 | 
			
		||||
	if fuzzyDropdown.
 | 
			
		||||
		Find("input[name=fuzzy][value=true]").
 | 
			
		||||
		Length() != 0 {
 | 
			
		||||
		actualFuzzyText = fuzzyDropdown.Find("div.text").First().Text()
 | 
			
		||||
		assert.EqualValues(t, expectedFuzzy, actualFuzzyText)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	filenames := resultFilenames(t, doc)
 | 
			
		||||
	assert.EqualValues(t, expected, filenames)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue