From 2bcf950b78519f81b4ade3f9cab32b6e4a200268 Mon Sep 17 00:00:00 2001 From: McMichalK Date: Sun, 23 Aug 2026 18:31:22 +0200 Subject: [PATCH] feat(diff): Add search and extension filter to diff sidebar (#37068) Adds a search box and a file-extension filter to the pull request diff sidebar, so reviewers can narrow a large diff down to the files they care about. Both filters apply to the file tree and to the diff itself. The extension menu follows GitHub: extensions sorted alphabetically, dotfiles and extension-less files in their own buckets, and the selection kept in the same `file-filters[]` query parameter, so a filtered view is shareable and survives a reload. The menu can list every extension in a diff, so `createTippy` gains an opt-in `limitSizeToViewport` option that caps a popup to the space left in the viewport and scrolls its content. Popups that do not ask for it are unchanged. Closes https://github.com/go-gitea/gitea/issues/27256 Signed-off-by: silverwind Signed-off-by: wxiaoguang Co-authored-by: silverwind Co-authored-by: Claude (Opus 4.7) Co-authored-by: Copilot Co-authored-by: Nicolas Co-authored-by: wxiaoguang --- options/locale/locale_en-US.json | 15 +- routers/web/repo/treelist.go | 4 + routers/web/repo/treelist_test.go | 15 ++ templates/repo/diff/box.tmpl | 36 +++- tests/e2e/external-render.test.ts | 6 +- tests/e2e/file-view-render.test.ts | 8 +- tests/e2e/pr-create.test.ts | 4 +- tests/e2e/pr-diff-filter.test.ts | 87 +++++++++ tests/e2e/pr-review.test.ts | 4 +- tests/e2e/pull-merge-box.test.ts | 4 +- tests/e2e/utils.ts | 27 ++- web_src/css/helpers.css | 18 ++ web_src/css/modules/tippy.css | 18 +- web_src/css/repo.css | 21 ++- web_src/js/components/DiffCommitSelector.vue | 2 +- .../js/components/DiffFileExtensionFilter.vue | 168 +++++++++++++++++ web_src/js/components/DiffFileTree.vue | 126 ++++++++++++- .../js/components/DiffFileTreeItem.test.ts | 23 +++ web_src/js/components/DiffFileTreeItem.vue | 30 ++- web_src/js/features/repo-diff-filetree.ts | 5 +- web_src/js/features/repo-diff.ts | 6 +- web_src/js/modules/diff-file.test.ts | 177 +++++++++++++----- web_src/js/modules/diff-file.ts | 168 ++++++++++++++++- web_src/js/modules/tippy.test.ts | 16 ++ web_src/js/modules/tippy.ts | 47 ++++- web_src/js/svg.ts | 2 + web_src/js/utils.test.ts | 3 + web_src/js/utils.ts | 7 +- 28 files changed, 914 insertions(+), 133 deletions(-) create mode 100644 tests/e2e/pr-diff-filter.test.ts create mode 100644 web_src/js/components/DiffFileExtensionFilter.vue create mode 100644 web_src/js/components/DiffFileTreeItem.test.ts create mode 100644 web_src/js/modules/tippy.test.ts diff --git a/options/locale/locale_en-US.json b/options/locale/locale_en-US.json index a0b0b3eb5bc..f5b7ed1268b 100644 --- a/options/locale/locale_en-US.json +++ b/options/locale/locale_en-US.json @@ -2597,9 +2597,9 @@ "repo.diff.file_byte_size": "Size", "repo.diff.file_suppressed": "File diff suppressed because it is too large", "repo.diff.file_suppressed_line_too_long": "File diff suppressed because one or more lines are too long", - "repo.diff.too_many_files": "Some files were not shown because too many files have changed in this diff", - "repo.diff.show_more": "Show More", - "repo.diff.load": "Load Diff", + "repo.diff.too_many_files": "Loaded %[1]d of %[2]d files, more files were not shown because too many files have changed in this diff.", + "repo.diff.show_more": "Show more", + "repo.diff.load": "Load diff", "repo.diff.generated": "Generated", "repo.diff.vendored": "Vendored", "repo.diff.comment.add_line_comment": "Add line comment", @@ -2627,6 +2627,15 @@ "repo.diff.has_escaped": "This line has hidden Unicode characters", "repo.diff.show_file_tree": "Show file tree", "repo.diff.hide_file_tree": "Hide file tree", + "repo.diff.filter_files": "Filter files…", + "repo.diff.filter_files_clear": "Clear filter", + "repo.diff.filter_by_file_extension": "Filter by file extension", + "repo.diff.file_extensions": "File extensions", + "repo.diff.no_file_extension": "No extension", + "repo.diff.dotfile_extension": "Dotfiles", + "repo.diff.all_file_extensions": "All extensions", + "repo.diff.no_files_matched": "No files matched your search", + "repo.diff.show_more_matching": "Show more - %[1]d matching below", "repo.diff.submodule_added": "Submodule %[1]s added at %[2]s", "repo.diff.submodule_deleted": "Submodule %[1]s deleted from %[2]s", "repo.diff.submodule_updated": "Submodule %[1]s updated: %[2]s", diff --git a/routers/web/repo/treelist.go b/routers/web/repo/treelist.go index 206660ad5c7..d75d31444a6 100644 --- a/routers/web/repo/treelist.go +++ b/routers/web/repo/treelist.go @@ -63,6 +63,7 @@ func isExcludedEntry(entry *git.TreeEntry) bool { // WebDiffFileItem is used by frontend, check the field names in frontend before changing type WebDiffFileItem struct { FullName string + OldFullName string DisplayName string NameHash string DiffStatus string @@ -110,6 +111,9 @@ func transformDiffTreeForWeb(renderedIconPool *fileicon.RenderedIconPool, diffTr for _, file := range diffTree.Files { item := &WebDiffFileItem{FullName: file.HeadPath, DiffStatus: file.Status} + if file.BasePath != file.HeadPath { + item.OldFullName = file.BasePath + } item.IsViewed = filesViewedState[item.FullName] == pull_model.Viewed item.NameHash = git.HashFilePathForWebUI(item.FullName) item.FileIcon = fileicon.RenderEntryIconHTML(renderedIconPool, &fileicon.EntryInfo{BaseName: path.Base(file.HeadPath), EntryMode: file.HeadMode}) diff --git a/routers/web/repo/treelist_test.go b/routers/web/repo/treelist_test.go index be496933e86..171b32a66a3 100644 --- a/routers/web/repo/treelist_test.go +++ b/routers/web/repo/treelist_test.go @@ -28,6 +28,12 @@ func TestTransformDiffTreeForWeb(t *testing.T) { HeadPath: "file1", HeadMode: git.EntryModeBlob, }, + { + Status: "renamed", + BasePath: "file2-old", + HeadPath: "file2", + HeadMode: git.EntryModeBlob, + }, }}, map[string]pull_model.ViewedState{ "dir-a/dir-a-x/file-deep": pull_model.Viewed, }) @@ -62,6 +68,15 @@ func TestTransformDiffTreeForWeb(t *testing.T) { DiffStatus: "added", FileIcon: mockIconForFile(`svg-mfi-file`), }, + { + EntryMode: "", + DisplayName: "file2", + FullName: "file2", + OldFullName: "file2-old", + NameHash: "cb99b709a1978bd205ab9dfd4c5aaa1fc91c7523", + DiffStatus: "renamed", + FileIcon: mockIconForFile(`svg-mfi-file`), + }, }, }, }, ret) diff --git a/templates/repo/diff/box.tmpl b/templates/repo/diff/box.tmpl index 4ed7fe9ab7e..52204947d7c 100644 --- a/templates/repo/diff/box.tmpl +++ b/templates/repo/diff/box.tmpl @@ -35,7 +35,9 @@ {{template "repo/diff/whitespace_dropdown" .}} {{template "repo/diff/options_dropdown" .}} {{if .PageIsPullFiles}} -
+
{{/* the following will be replaced by vue component, but this avoids any loading artifacts till the vue component is initialized */}}