Skip to content

Commit d23fff3

Browse files
committed
refactor: Lazily load file list for tree search and localize no results message.
1 parent 17858b6 commit d23fff3

File tree

6 files changed

+42
-21
lines changed

6 files changed

+42
-21
lines changed

services/repository/files/delete.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
// Copyright 2025 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
14
package files
25

36
import (
47
"context"
5-
"fmt"
8+
"errors"
69

710
repo_model "code.gitea.io/gitea/models/repo"
811
user_model "code.gitea.io/gitea/models/user"
@@ -24,7 +27,7 @@ type DeleteRepoFileOptions struct {
2427
// DeleteRepoFile deletes a file or directory in the given repository
2528
func DeleteRepoFile(ctx context.Context, repo *repo_model.Repository, doer *user_model.User, opts *DeleteRepoFileOptions) (*structs.FilesResponse, error) {
2629
if opts.TreePath == "" {
27-
return nil, fmt.Errorf("path cannot be empty")
30+
return nil, errors.New("path cannot be empty")
2831
}
2932

3033
// If no branch name is set, assume the default branch

services/repository/files/delete_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// Copyright 2025 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
14
package files
25

36
import (

templates/repo/view_file_tree.tmpl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,5 @@
1717
data-tree-path="{{$.TreePath}}"
1818
data-current-ref-name-sub-url="{{.RefTypeNameSubURL}}"
1919
data-tree-list-url="{{.RepoLink}}/tree-list/{{.RefTypeNameSubURL}}"
20+
data-no-results-text="{{ctx.Locale.Tr "repo.find_file.no_matching"}}"
2021
></div>

web_src/css/repo/file-actions.css

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,19 @@
11
/* Repository file actions dropdown */
22
.ui.dropdown.repo-add-file > .menu,
33
.ui.dropdown.repo-file-actions-dropdown > .menu {
4-
margin-top: 4px !important;
4+
margin-top: 4px;
55
}
66

77
.ui.dropdown.repo-file-actions-dropdown > .menu {
88
min-width: 200px;
99
}
1010

11-
.ui.dropdown.repo-file-actions-dropdown > .menu > .divider {
12-
margin: 0.5rem 0;
13-
}
14-
1511
.ui.dropdown.repo-file-actions-dropdown > .menu > .item.danger,
1612
.ui.dropdown.repo-file-actions-dropdown > .menu > .item.danger svg {
17-
color: var(--color-red) !important;
13+
color: var(--color-red);
1814
}
1915

20-
.ui.dropdown.repo-file-actions-dropdown > .menu > .item.danger:hover,
21-
.ui.dropdown.repo-file-actions-dropdown > .menu > .item.danger:hover svg {
22-
color: var(--color-red) !important;
23-
background: var(--color-red-badge-hover-bg) !important;
16+
.ui.dropdown.repo-file-actions-dropdown > .menu > .item.danger:hover {
17+
color: var(--color-red);
18+
background: var(--color-red-badge-hover-bg);
2419
}

web_src/js/components/ViewFileTree.vue

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,14 @@ const searchResults = useTemplateRef('searchResults');
1212
const searchQuery = ref('');
1313
const allFiles = ref<string[]>([]);
1414
const selectedIndex = ref(0);
15+
const isLoadingFileList = ref(false);
16+
const hasLoadedFileList = ref(false);
1517
1618
const props = defineProps({
1719
repoLink: {type: String, required: true},
1820
treePath: {type: String, required: true},
1921
currentRefNameSubURL: {type: String, required: true},
22+
noResultsText: {type: String, required: true},
2023
});
2124
2225
const store = createViewFileTreeStore(props);
@@ -26,7 +29,7 @@ const filteredFiles = computed(() => {
2629
return filterRepoFilesWeighted(allFiles.value, searchQuery.value);
2730
});
2831
29-
const treeLink = computed(() => `${props.repoLink}/src/${props.currentRefNameSubURL}`);
32+
const treeLink = computed(() => `${props.repoLink}/src/${pathEscapeSegments(props.currentRefNameSubURL)}`);
3033
3134
let searchInputElement: HTMLInputElement | null = null;
3235
@@ -90,19 +93,33 @@ const handleClickOutside = (e: MouseEvent) => {
9093
}
9194
};
9295
96+
const loadFileListForSearch = async () => {
97+
if (hasLoadedFileList.value || isLoadingFileList.value) return;
98+
99+
isLoadingFileList.value = true;
100+
try {
101+
const treeListUrl = elRoot.value.closest('#view-file-tree')?.getAttribute('data-tree-list-url');
102+
if (treeListUrl) {
103+
const response = await GET(treeListUrl);
104+
allFiles.value = await response.json();
105+
hasLoadedFileList.value = true;
106+
}
107+
} finally {
108+
isLoadingFileList.value = false;
109+
}
110+
};
111+
112+
const handleSearchFocus = () => {
113+
loadFileListForSearch();
114+
};
115+
93116
onMounted(async () => {
94117
store.rootFiles = await store.loadChildren('', props.treePath);
95118
elRoot.value.closest('.is-loading')?.classList?.remove('is-loading');
96119
97-
// Load all files for search
98-
const treeListUrl = elRoot.value.closest('#view-file-tree')?.getAttribute('data-tree-list-url');
99-
if (treeListUrl) {
100-
const response = await GET(treeListUrl);
101-
allFiles.value = await response.json();
102-
}
103-
104120
searchInputElement = document.querySelector('#file-tree-search');
105121
if (searchInputElement) {
122+
searchInputElement.addEventListener('focus', handleSearchFocus);
106123
searchInputElement.addEventListener('input', handleSearchInput);
107124
searchInputElement.addEventListener('keydown', handleKeyDown);
108125
document.addEventListener('click', handleClickOutside);
@@ -129,6 +146,7 @@ watch(searchQuery, async () => {
129146
130147
onUnmounted(() => {
131148
if (searchInputElement) {
149+
searchInputElement.removeEventListener('focus', handleSearchFocus);
132150
searchInputElement.removeEventListener('input', handleSearchInput);
133151
searchInputElement.removeEventListener('keydown', handleKeyDown);
134152
document.removeEventListener('click', handleClickOutside);
@@ -168,7 +186,7 @@ function handleSearchResultClick(filePath: string) {
168186
<div class="file-tree-no-results-content">
169187
<!-- eslint-disable-next-line vue/no-v-html -->
170188
<span v-html="svg('octicon-search', 24)"/>
171-
<span>No matching files found</span>
189+
<span>{{ props.noResultsText }}</span>
172190
</div>
173191
</div>
174192
</Teleport>

web_src/js/features/repo-view-file-tree.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,6 @@ export async function initRepoViewFileTree() {
3333
repoLink: fileTree.getAttribute('data-repo-link'),
3434
treePath: fileTree.getAttribute('data-tree-path'),
3535
currentRefNameSubURL: fileTree.getAttribute('data-current-ref-name-sub-url'),
36+
noResultsText: fileTree.getAttribute('data-no-results-text'),
3637
}).mount(fileTree);
3738
}

0 commit comments

Comments
 (0)