From 50bf7087fa64c01b78384364c05819f1a8700918 Mon Sep 17 00:00:00 2001 From: bytedream <me@bytedream.dev> Date: Thu, 29 May 2025 01:59:43 +0200 Subject: [PATCH 01/17] Open new tab when clicking file tree with mouse wheel --- web_src/js/components/ViewFileTree.vue | 6 +++++- web_src/js/components/ViewFileTreeItem.vue | 21 ++++++++++++--------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/web_src/js/components/ViewFileTree.vue b/web_src/js/components/ViewFileTree.vue index c692142792754..bdbf71a04e975 100644 --- a/web_src/js/components/ViewFileTree.vue +++ b/web_src/js/components/ViewFileTree.vue @@ -37,8 +37,12 @@ async function loadViewContent(url: string) { document.querySelector('.repo-view-content').innerHTML = await response.text(); } -async function navigateTreeView(treePath: string) { +async function navigateTreeView(treePath: string, newTab: boolean) { const url = `${props.repoLink}/src/${props.currentRefNameSubURL}/${pathEscapeSegments(treePath)}`; + if (newTab) { + window.open(url, '_blank'); + return; + } window.history.pushState({treePath, url}, null, url); selectedItem.value = treePath; await loadViewContent(url); diff --git a/web_src/js/components/ViewFileTreeItem.vue b/web_src/js/components/ViewFileTreeItem.vue index c39fa1f4aef16..bb3ab8fbeb40a 100644 --- a/web_src/js/components/ViewFileTreeItem.vue +++ b/web_src/js/components/ViewFileTreeItem.vue @@ -14,7 +14,7 @@ type Item = { const props = defineProps<{ item: Item, - navigateViewContent:(treePath: string) => void, + navigateViewContent:(treePath: string, newTab: boolean) => void, loadChildren:(treePath: string, subPath?: string) => Promise<Item[]>, selectedItem?: string, }>(); @@ -35,13 +35,13 @@ const doLoadChildren = async () => { } }; -const doLoadDirContent = () => { - doLoadChildren(); - props.navigateViewContent(props.item.fullPath); +const doLoadDirContent = (newTab: boolean) => { + if (!newTab) doLoadChildren(); + props.navigateViewContent(props.item.fullPath, newTab); }; -const doLoadFileContent = () => { - props.navigateViewContent(props.item.fullPath); +const doLoadFileContent = (newTab: boolean) => { + props.navigateViewContent(props.item.fullPath, newTab); }; const doGotoSubModule = () => { @@ -67,7 +67,8 @@ const doGotoSubModule = () => { v-else-if="item.entryMode === 'symlink'" class="tree-item type-symlink" :class="{'selected': selectedItem === item.fullPath}" :title="item.entryName" - @click.stop="doLoadFileContent" + @click.stop="doLoadFileContent(false)" + @auxclick.stop="doLoadFileContent(true)" > <!-- symlink --> <div class="item-content"> @@ -80,7 +81,8 @@ const doGotoSubModule = () => { v-else-if="item.entryMode !== 'tree'" class="tree-item type-file" :class="{'selected': selectedItem === item.fullPath}" :title="item.entryName" - @click.stop="doLoadFileContent" + @click.stop="doLoadFileContent(false)" + @auxclick.stop="doLoadFileContent(true)" > <!-- file --> <div class="item-content"> @@ -93,7 +95,8 @@ const doGotoSubModule = () => { v-else class="tree-item type-directory" :class="{'selected': selectedItem === item.fullPath}" :title="item.entryName" - @click.stop="doLoadDirContent" + @click.stop="doLoadDirContent(false)" + @auxclick.stop="doLoadDirContent(true)" > <!-- directory --> <div class="item-toggle"> From 83e925a6051c860f57623a81fbd2028d7f1547fb Mon Sep 17 00:00:00 2001 From: bytedream <me@bytedream.dev> Date: Thu, 29 May 2025 02:20:57 +0200 Subject: [PATCH 02/17] Use own function instead --- web_src/js/components/ViewFileTree.vue | 2 +- web_src/js/components/ViewFileTreeItem.vue | 28 ++++++++++++---------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/web_src/js/components/ViewFileTree.vue b/web_src/js/components/ViewFileTree.vue index bdbf71a04e975..0e1ea7f59d3b0 100644 --- a/web_src/js/components/ViewFileTree.vue +++ b/web_src/js/components/ViewFileTree.vue @@ -37,7 +37,7 @@ async function loadViewContent(url: string) { document.querySelector('.repo-view-content').innerHTML = await response.text(); } -async function navigateTreeView(treePath: string, newTab: boolean) { +async function navigateTreeView(treePath: string, newTab?: boolean) { const url = `${props.repoLink}/src/${props.currentRefNameSubURL}/${pathEscapeSegments(treePath)}`; if (newTab) { window.open(url, '_blank'); diff --git a/web_src/js/components/ViewFileTreeItem.vue b/web_src/js/components/ViewFileTreeItem.vue index bb3ab8fbeb40a..5b2a4e577bfa9 100644 --- a/web_src/js/components/ViewFileTreeItem.vue +++ b/web_src/js/components/ViewFileTreeItem.vue @@ -14,7 +14,7 @@ type Item = { const props = defineProps<{ item: Item, - navigateViewContent:(treePath: string, newTab: boolean) => void, + navigateViewContent:(treePath: string, newTab?: boolean) => void, loadChildren:(treePath: string, subPath?: string) => Promise<Item[]>, selectedItem?: string, }>(); @@ -35,13 +35,17 @@ const doLoadChildren = async () => { } }; -const doLoadDirContent = (newTab: boolean) => { - if (!newTab) doLoadChildren(); - props.navigateViewContent(props.item.fullPath, newTab); +const doLoadDirContent = () => { + doLoadChildren(); + props.navigateViewContent(props.item.fullPath); }; -const doLoadFileContent = (newTab: boolean) => { - props.navigateViewContent(props.item.fullPath, newTab); +const doLoadFileContent = () => { + props.navigateViewContent(props.item.fullPath); +}; + +const doOpenContentInNewTab = () => { + props.navigateViewContent(props.item.fullPath, true); }; const doGotoSubModule = () => { @@ -67,8 +71,8 @@ const doGotoSubModule = () => { v-else-if="item.entryMode === 'symlink'" class="tree-item type-symlink" :class="{'selected': selectedItem === item.fullPath}" :title="item.entryName" - @click.stop="doLoadFileContent(false)" - @auxclick.stop="doLoadFileContent(true)" + @click.stop="doLoadFileContent" + @auxclick.stop="doOpenContentInNewTab" > <!-- symlink --> <div class="item-content"> @@ -81,8 +85,8 @@ const doGotoSubModule = () => { v-else-if="item.entryMode !== 'tree'" class="tree-item type-file" :class="{'selected': selectedItem === item.fullPath}" :title="item.entryName" - @click.stop="doLoadFileContent(false)" - @auxclick.stop="doLoadFileContent(true)" + @click.stop="doLoadFileContent" + @auxclick.stop="doOpenContentInNewTab" > <!-- file --> <div class="item-content"> @@ -95,8 +99,8 @@ const doGotoSubModule = () => { v-else class="tree-item type-directory" :class="{'selected': selectedItem === item.fullPath}" :title="item.entryName" - @click.stop="doLoadDirContent(false)" - @auxclick.stop="doLoadDirContent(true)" + @click.stop="doLoadDirContent" + @auxclick.stop="doOpenContentInNewTab" > <!-- directory --> <div class="item-toggle"> From 91069cf5382365362f94a5608e282f6993514afa Mon Sep 17 00:00:00 2001 From: bytedream <me@bytedream.dev> Date: Sun, 15 Jun 2025 16:21:58 +0200 Subject: [PATCH 03/17] use click.middle instead of auxclick --- web_src/js/components/ViewFileTreeItem.vue | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web_src/js/components/ViewFileTreeItem.vue b/web_src/js/components/ViewFileTreeItem.vue index 5b2a4e577bfa9..cd1969e58c653 100644 --- a/web_src/js/components/ViewFileTreeItem.vue +++ b/web_src/js/components/ViewFileTreeItem.vue @@ -72,7 +72,7 @@ const doGotoSubModule = () => { :class="{'selected': selectedItem === item.fullPath}" :title="item.entryName" @click.stop="doLoadFileContent" - @auxclick.stop="doOpenContentInNewTab" + @click.middle.stop="doOpenContentInNewTab" > <!-- symlink --> <div class="item-content"> @@ -86,7 +86,7 @@ const doGotoSubModule = () => { :class="{'selected': selectedItem === item.fullPath}" :title="item.entryName" @click.stop="doLoadFileContent" - @auxclick.stop="doOpenContentInNewTab" + @click.middle.stop="doOpenContentInNewTab" > <!-- file --> <div class="item-content"> @@ -100,7 +100,7 @@ const doGotoSubModule = () => { :class="{'selected': selectedItem === item.fullPath}" :title="item.entryName" @click.stop="doLoadDirContent" - @auxclick.stop="doOpenContentInNewTab" + @click.middle.stop="doOpenContentInNewTab" > <!-- directory --> <div class="item-toggle"> From 2b5e11981656ed4c0db17232dc786ba71838143d Mon Sep 17 00:00:00 2001 From: bytedream <me@bytedream.dev> Date: Mon, 16 Jun 2025 12:36:24 +0200 Subject: [PATCH 04/17] also check if ctrl key was pressed --- web_src/js/components/ViewFileTreeItem.vue | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/web_src/js/components/ViewFileTreeItem.vue b/web_src/js/components/ViewFileTreeItem.vue index cd1969e58c653..8ffe3e464f68a 100644 --- a/web_src/js/components/ViewFileTreeItem.vue +++ b/web_src/js/components/ViewFileTreeItem.vue @@ -40,12 +40,12 @@ const doLoadDirContent = () => { props.navigateViewContent(props.item.fullPath); }; -const doLoadFileContent = () => { - props.navigateViewContent(props.item.fullPath); -}; - -const doOpenContentInNewTab = () => { - props.navigateViewContent(props.item.fullPath, true); +const doLoadFileContent = (event: MouseEvent) => { + // open the file in a new tab if either + // - the auxiliary button (mouse wheel button) is the origin of the click + // - the ctrl key was pressed while clicking + const openNewTab = event.button === 1 || event.ctrlKey; + props.navigateViewContent(props.item.fullPath, openNewTab); }; const doGotoSubModule = () => { @@ -72,7 +72,7 @@ const doGotoSubModule = () => { :class="{'selected': selectedItem === item.fullPath}" :title="item.entryName" @click.stop="doLoadFileContent" - @click.middle.stop="doOpenContentInNewTab" + @click.middle.stop="doLoadFileContent" > <!-- symlink --> <div class="item-content"> @@ -86,7 +86,7 @@ const doGotoSubModule = () => { :class="{'selected': selectedItem === item.fullPath}" :title="item.entryName" @click.stop="doLoadFileContent" - @click.middle.stop="doOpenContentInNewTab" + @click.middle.stop="doLoadFileContent" > <!-- file --> <div class="item-content"> @@ -100,7 +100,7 @@ const doGotoSubModule = () => { :class="{'selected': selectedItem === item.fullPath}" :title="item.entryName" @click.stop="doLoadDirContent" - @click.middle.stop="doOpenContentInNewTab" + @click.middle.stop="doLoadFileContent" > <!-- directory --> <div class="item-toggle"> From 06ac1c9f1371c321b4850cc967cdbf750b9d640d Mon Sep 17 00:00:00 2001 From: bytedream <me@bytedream.dev> Date: Mon, 16 Jun 2025 12:38:14 +0200 Subject: [PATCH 05/17] update comment --- web_src/js/components/ViewFileTreeItem.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_src/js/components/ViewFileTreeItem.vue b/web_src/js/components/ViewFileTreeItem.vue index 8ffe3e464f68a..696d234e10f18 100644 --- a/web_src/js/components/ViewFileTreeItem.vue +++ b/web_src/js/components/ViewFileTreeItem.vue @@ -42,7 +42,7 @@ const doLoadDirContent = () => { const doLoadFileContent = (event: MouseEvent) => { // open the file in a new tab if either - // - the auxiliary button (mouse wheel button) is the origin of the click + // - the auxiliary button (usually the mouse wheel button) is the origin of the click // - the ctrl key was pressed while clicking const openNewTab = event.button === 1 || event.ctrlKey; props.navigateViewContent(props.item.fullPath, openNewTab); From 10d947114617c395b787c874d65f0d029982a2a3 Mon Sep 17 00:00:00 2001 From: bytedream <me@bytedream.dev> Date: Mon, 16 Jun 2025 13:53:48 +0200 Subject: [PATCH 06/17] check meta key in addition to ctrl --- web_src/js/components/ViewFileTreeItem.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web_src/js/components/ViewFileTreeItem.vue b/web_src/js/components/ViewFileTreeItem.vue index 696d234e10f18..8cd9d36b57295 100644 --- a/web_src/js/components/ViewFileTreeItem.vue +++ b/web_src/js/components/ViewFileTreeItem.vue @@ -43,8 +43,8 @@ const doLoadDirContent = () => { const doLoadFileContent = (event: MouseEvent) => { // open the file in a new tab if either // - the auxiliary button (usually the mouse wheel button) is the origin of the click - // - the ctrl key was pressed while clicking - const openNewTab = event.button === 1 || event.ctrlKey; + // - the ctrl key or meta key (for mac support) was pressed while clicking + const openNewTab = event.button === 1 || event.ctrlKey || event.metaKey; props.navigateViewContent(props.item.fullPath, openNewTab); }; From 9c4a8e2da7cd73cd605c700c8953b7a18c85c6aa Mon Sep 17 00:00:00 2001 From: bytedream <me@bytedream.dev> Date: Mon, 16 Jun 2025 14:02:46 +0200 Subject: [PATCH 07/17] add aux click check to doLoadDirContent --- web_src/js/components/ViewFileTreeItem.vue | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/web_src/js/components/ViewFileTreeItem.vue b/web_src/js/components/ViewFileTreeItem.vue index 8cd9d36b57295..42c1978a185cd 100644 --- a/web_src/js/components/ViewFileTreeItem.vue +++ b/web_src/js/components/ViewFileTreeItem.vue @@ -35,15 +35,16 @@ const doLoadChildren = async () => { } }; -const doLoadDirContent = () => { - doLoadChildren(); - props.navigateViewContent(props.item.fullPath); +const doLoadDirContent = (event: MouseEvent) => { + // open the directory in a new tab if either + // - the auxiliary button (usually the mouse wheel button) is the origin of the click + // - the ctrl key or meta key (for mac support) was pressed while clicking + const openNewTab = event.button === 1 || event.ctrlKey || event.metaKey; + if (!openNewTab) doLoadChildren(); + props.navigateViewContent(props.item.fullPath, openNewTab); }; const doLoadFileContent = (event: MouseEvent) => { - // open the file in a new tab if either - // - the auxiliary button (usually the mouse wheel button) is the origin of the click - // - the ctrl key or meta key (for mac support) was pressed while clicking const openNewTab = event.button === 1 || event.ctrlKey || event.metaKey; props.navigateViewContent(props.item.fullPath, openNewTab); }; @@ -100,7 +101,7 @@ const doGotoSubModule = () => { :class="{'selected': selectedItem === item.fullPath}" :title="item.entryName" @click.stop="doLoadDirContent" - @click.middle.stop="doLoadFileContent" + @click.middle.stop="doLoadDirContent" > <!-- directory --> <div class="item-toggle"> From 9915581f4ce8111ddef27bb4765fdae6bed2a018 Mon Sep 17 00:00:00 2001 From: bytedream <me@bytedream.dev> Date: Mon, 16 Jun 2025 14:46:54 +0200 Subject: [PATCH 08/17] add sub module aux click support --- web_src/js/components/ViewFileTreeItem.vue | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/web_src/js/components/ViewFileTreeItem.vue b/web_src/js/components/ViewFileTreeItem.vue index 42c1978a185cd..769888b651b07 100644 --- a/web_src/js/components/ViewFileTreeItem.vue +++ b/web_src/js/components/ViewFileTreeItem.vue @@ -49,7 +49,12 @@ const doLoadFileContent = (event: MouseEvent) => { props.navigateViewContent(props.item.fullPath, openNewTab); }; -const doGotoSubModule = () => { +const doGotoSubModule = (event: MouseEvent) => { + const openNewTab = event.button === 1 || event.ctrlKey || event.metaKey; + if (openNewTab) { + window.open(props.item.submoduleUrl, '_blank'); + return; + } location.href = props.item.submoduleUrl; }; </script> @@ -60,6 +65,7 @@ const doGotoSubModule = () => { v-if="item.entryMode === 'commit'" class="tree-item type-submodule" :title="item.entryName" @click.stop="doGotoSubModule" + @click.middle.stop="doGotoSubModule" > <!-- submodule --> <div class="item-content"> From ee2f52a1c3bf463bda3cfb061c5c44b257626925 Mon Sep 17 00:00:00 2001 From: bytedream <me@bytedream.dev> Date: Wed, 18 Jun 2025 18:04:57 +0200 Subject: [PATCH 09/17] use `a` for file tree files --- web_src/js/components/ViewFileTree.vue | 14 ++--- web_src/js/components/ViewFileTreeItem.vue | 67 +++++++++++----------- 2 files changed, 40 insertions(+), 41 deletions(-) diff --git a/web_src/js/components/ViewFileTree.vue b/web_src/js/components/ViewFileTree.vue index 0e1ea7f59d3b0..b1e57752acbd5 100644 --- a/web_src/js/components/ViewFileTree.vue +++ b/web_src/js/components/ViewFileTree.vue @@ -37,17 +37,17 @@ async function loadViewContent(url: string) { document.querySelector('.repo-view-content').innerHTML = await response.text(); } -async function navigateTreeView(treePath: string, newTab?: boolean) { - const url = `${props.repoLink}/src/${props.currentRefNameSubURL}/${pathEscapeSegments(treePath)}`; - if (newTab) { - window.open(url, '_blank'); - return; - } +async function navigateTreeView(treePath: string) { + const url = getWebUrl(treePath); window.history.pushState({treePath, url}, null, url); selectedItem.value = treePath; await loadViewContent(url); } +function getWebUrl(treePath: string) { + return `${props.repoLink}/src/${props.currentRefNameSubURL}/${pathEscapeSegments(treePath)}`; +} + onMounted(async () => { selectedItem.value = props.treePath; files.value = await loadChildren('', props.treePath); @@ -62,7 +62,7 @@ onMounted(async () => { <template> <div class="view-file-tree-items" ref="elRoot"> <!-- only render the tree if we're visible. in many cases this is something that doesn't change very often --> - <ViewFileTreeItem v-for="item in files" :key="item.name" :item="item" :selected-item="selectedItem" :navigate-view-content="navigateTreeView" :load-children="loadChildren"/> + <ViewFileTreeItem v-for="item in files" :key="item.name" :item="item" :selected-item="selectedItem" :get-web-url="getWebUrl" :navigate-view-content="navigateTreeView" :load-children="loadChildren"/> </div> </template> diff --git a/web_src/js/components/ViewFileTreeItem.vue b/web_src/js/components/ViewFileTreeItem.vue index 769888b651b07..9474141be5759 100644 --- a/web_src/js/components/ViewFileTreeItem.vue +++ b/web_src/js/components/ViewFileTreeItem.vue @@ -14,8 +14,9 @@ type Item = { const props = defineProps<{ item: Item, - navigateViewContent:(treePath: string, newTab?: boolean) => void, + navigateViewContent:(treePath: string) => void, loadChildren:(treePath: string, subPath?: string) => Promise<Item[]>, + getWebUrl:(treePath: string) => string, selectedItem?: string, }>(); @@ -23,7 +24,11 @@ const isLoading = ref(false); const children = ref(props.item.children); const collapsed = ref(!props.item.children); -const doLoadChildren = async () => { +const doLoadChildren = async (e?: MouseEvent) => { + // the event is only not undefined if the user explicitly clicked on the directory item toggle. the preventDefault + // stops the event from bubbling up and causing a directory content load + e?.preventDefault(); + collapsed.value = !collapsed.value; if (!collapsed.value && props.loadChildren) { isLoading.value = true; @@ -35,37 +40,29 @@ const doLoadChildren = async () => { } }; -const doLoadDirContent = (event: MouseEvent) => { - // open the directory in a new tab if either - // - the auxiliary button (usually the mouse wheel button) is the origin of the click - // - the ctrl key or meta key (for mac support) was pressed while clicking - const openNewTab = event.button === 1 || event.ctrlKey || event.metaKey; - if (!openNewTab) doLoadChildren(); - props.navigateViewContent(props.item.fullPath, openNewTab); -}; +const doLoadDirContent = (e: MouseEvent) => { + // only load the directory content without a window refresh if the user didn't press any special key + if (e.button !== 0 || e.ctrlKey || e.metaKey || e.altKey || e.shiftKey) return; + e.preventDefault(); -const doLoadFileContent = (event: MouseEvent) => { - const openNewTab = event.button === 1 || event.ctrlKey || event.metaKey; - props.navigateViewContent(props.item.fullPath, openNewTab); + doLoadChildren(); + props.navigateViewContent(props.item.fullPath); }; -const doGotoSubModule = (event: MouseEvent) => { - const openNewTab = event.button === 1 || event.ctrlKey || event.metaKey; - if (openNewTab) { - window.open(props.item.submoduleUrl, '_blank'); - return; - } - location.href = props.item.submoduleUrl; +const doLoadFileContent = (e: MouseEvent) => { + if (e.button !== 0 || e.ctrlKey || e.metaKey || e.altKey || e.shiftKey) return; + e.preventDefault(); + + props.navigateViewContent(props.item.fullPath); }; </script> <!--title instead of tooltip above as the tooltip needs too much work with the current methods, i.e. not being loaded or staying open for "too long"--> <template> - <div + <a v-if="item.entryMode === 'commit'" class="tree-item type-submodule" :title="item.entryName" - @click.stop="doGotoSubModule" - @click.middle.stop="doGotoSubModule" + :href="getWebUrl(item.fullPath)" > <!-- submodule --> <div class="item-content"> @@ -73,13 +70,13 @@ const doGotoSubModule = (event: MouseEvent) => { <span class="tw-contents" v-html="item.entryIcon"/> <span class="gt-ellipsis tw-flex-1">{{ item.entryName }}</span> </div> - </div> - <div + </a> + <a v-else-if="item.entryMode === 'symlink'" class="tree-item type-symlink" :class="{'selected': selectedItem === item.fullPath}" :title="item.entryName" + :href="getWebUrl(item.fullPath)" @click.stop="doLoadFileContent" - @click.middle.stop="doLoadFileContent" > <!-- symlink --> <div class="item-content"> @@ -87,13 +84,13 @@ const doGotoSubModule = (event: MouseEvent) => { <span class="tw-contents" v-html="item.entryIcon"/> <span class="gt-ellipsis tw-flex-1">{{ item.entryName }}</span> </div> - </div> - <div + </a> + <a v-else-if="item.entryMode !== 'tree'" class="tree-item type-file" :class="{'selected': selectedItem === item.fullPath}" :title="item.entryName" + :href="getWebUrl(item.fullPath)" @click.stop="doLoadFileContent" - @click.middle.stop="doLoadFileContent" > <!-- file --> <div class="item-content"> @@ -101,13 +98,13 @@ const doGotoSubModule = (event: MouseEvent) => { <span class="tw-contents" v-html="item.entryIcon"/> <span class="gt-ellipsis tw-flex-1">{{ item.entryName }}</span> </div> - </div> - <div + </a> + <a v-else class="tree-item type-directory" :class="{'selected': selectedItem === item.fullPath}" :title="item.entryName" + :href="getWebUrl(item.fullPath)" @click.stop="doLoadDirContent" - @click.middle.stop="doLoadDirContent" > <!-- directory --> <div class="item-toggle"> @@ -119,10 +116,10 @@ const doGotoSubModule = (event: MouseEvent) => { <span class="tw-contents" v-html="(!collapsed && item.entryIconOpen) ? item.entryIconOpen : item.entryIcon"/> <span class="gt-ellipsis">{{ item.entryName }}</span> </div> - </div> + </a> <div v-if="children?.length" v-show="!collapsed" class="sub-items"> - <ViewFileTreeItem v-for="childItem in children" :key="childItem.entryName" :item="childItem" :selected-item="selectedItem" :navigate-view-content="navigateViewContent" :load-children="loadChildren"/> + <ViewFileTreeItem v-for="childItem in children" :key="childItem.entryName" :item="childItem" :selected-item="selectedItem" :get-web-url="getWebUrl" :navigate-view-content="navigateViewContent" :load-children="loadChildren"/> </div> </template> <style scoped> @@ -145,6 +142,8 @@ const doGotoSubModule = (event: MouseEvent) => { } .tree-item { + color: inherit; + text-decoration: inherit; display: grid; grid-template-columns: 16px 1fr; grid-template-areas: "toggle content"; From 0c8c45fdb6d9ac2df466a5c334f6f761323c6daf Mon Sep 17 00:00:00 2001 From: bytedream <me@bytedream.dev> Date: Wed, 18 Jun 2025 18:51:32 +0200 Subject: [PATCH 10/17] use class to disable default link styling --- web_src/js/components/ViewFileTreeItem.vue | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/web_src/js/components/ViewFileTreeItem.vue b/web_src/js/components/ViewFileTreeItem.vue index 9474141be5759..b9d9d15f0d697 100644 --- a/web_src/js/components/ViewFileTreeItem.vue +++ b/web_src/js/components/ViewFileTreeItem.vue @@ -60,7 +60,7 @@ const doLoadFileContent = (e: MouseEvent) => { <!--title instead of tooltip above as the tooltip needs too much work with the current methods, i.e. not being loaded or staying open for "too long"--> <template> <a - v-if="item.entryMode === 'commit'" class="tree-item type-submodule" + v-if="item.entryMode === 'commit'" class="tree-item type-submodule silenced" :title="item.entryName" :href="getWebUrl(item.fullPath)" > @@ -72,7 +72,7 @@ const doLoadFileContent = (e: MouseEvent) => { </div> </a> <a - v-else-if="item.entryMode === 'symlink'" class="tree-item type-symlink" + v-else-if="item.entryMode === 'symlink'" class="tree-item type-symlink silenced" :class="{'selected': selectedItem === item.fullPath}" :title="item.entryName" :href="getWebUrl(item.fullPath)" @@ -86,7 +86,7 @@ const doLoadFileContent = (e: MouseEvent) => { </div> </a> <a - v-else-if="item.entryMode !== 'tree'" class="tree-item type-file" + v-else-if="item.entryMode !== 'tree'" class="tree-item type-file silenced" :class="{'selected': selectedItem === item.fullPath}" :title="item.entryName" :href="getWebUrl(item.fullPath)" @@ -100,7 +100,7 @@ const doLoadFileContent = (e: MouseEvent) => { </div> </a> <a - v-else class="tree-item type-directory" + v-else class="tree-item type-directory silenced" :class="{'selected': selectedItem === item.fullPath}" :title="item.entryName" :href="getWebUrl(item.fullPath)" @@ -142,8 +142,6 @@ const doLoadFileContent = (e: MouseEvent) => { } .tree-item { - color: inherit; - text-decoration: inherit; display: grid; grid-template-columns: 16px 1fr; grid-template-areas: "toggle content"; From e2655f3df71a7ffe9914b546b2287516b47fd00d Mon Sep 17 00:00:00 2001 From: bytedream <me@bytedream.dev> Date: Wed, 18 Jun 2025 18:53:09 +0200 Subject: [PATCH 11/17] move plain click check to utils --- web_src/js/components/ViewFileTreeItem.vue | 5 +++-- web_src/js/utils/dom.ts | 5 +++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/web_src/js/components/ViewFileTreeItem.vue b/web_src/js/components/ViewFileTreeItem.vue index b9d9d15f0d697..6c4481748b75a 100644 --- a/web_src/js/components/ViewFileTreeItem.vue +++ b/web_src/js/components/ViewFileTreeItem.vue @@ -1,5 +1,6 @@ <script lang="ts" setup> import {SvgIcon} from '../svg.ts'; +import {isPlainClick} from '../utils/dom.ts'; import {ref} from 'vue'; type Item = { @@ -42,7 +43,7 @@ const doLoadChildren = async (e?: MouseEvent) => { const doLoadDirContent = (e: MouseEvent) => { // only load the directory content without a window refresh if the user didn't press any special key - if (e.button !== 0 || e.ctrlKey || e.metaKey || e.altKey || e.shiftKey) return; + if (!isPlainClick(e)) return; e.preventDefault(); doLoadChildren(); @@ -50,7 +51,7 @@ const doLoadDirContent = (e: MouseEvent) => { }; const doLoadFileContent = (e: MouseEvent) => { - if (e.button !== 0 || e.ctrlKey || e.metaKey || e.altKey || e.shiftKey) return; + if (!isPlainClick(e)) return; e.preventDefault(); props.navigateViewContent(props.item.fullPath); diff --git a/web_src/js/utils/dom.ts b/web_src/js/utils/dom.ts index 8f758bf9accff..6a5baa188809b 100644 --- a/web_src/js/utils/dom.ts +++ b/web_src/js/utils/dom.ts @@ -369,3 +369,8 @@ export function addDelegatedEventListener<T extends HTMLElement, E extends Event listener(elem as T, e as E); }, options); } + +// Check if a click is done without any special mouse or key inputs that would trigger special browser behavior. +export function isPlainClick(e: MouseEvent) { + return e.button === 0 && !e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey; +} From 35ab11f333be0bd021f676a06c23b7e6d7380165 Mon Sep 17 00:00:00 2001 From: bytedream <me@bytedream.dev> Date: Wed, 18 Jun 2025 18:54:19 +0200 Subject: [PATCH 12/17] mark mouse event explicitly null --- web_src/js/components/ViewFileTreeItem.vue | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web_src/js/components/ViewFileTreeItem.vue b/web_src/js/components/ViewFileTreeItem.vue index 6c4481748b75a..32d637c4afd88 100644 --- a/web_src/js/components/ViewFileTreeItem.vue +++ b/web_src/js/components/ViewFileTreeItem.vue @@ -25,8 +25,8 @@ const isLoading = ref(false); const children = ref(props.item.children); const collapsed = ref(!props.item.children); -const doLoadChildren = async (e?: MouseEvent) => { - // the event is only not undefined if the user explicitly clicked on the directory item toggle. the preventDefault +const doLoadChildren = async (e: MouseEvent | null) => { + // the event is only not null if the user explicitly clicked on the directory item toggle. the preventDefault // stops the event from bubbling up and causing a directory content load e?.preventDefault(); @@ -46,7 +46,7 @@ const doLoadDirContent = (e: MouseEvent) => { if (!isPlainClick(e)) return; e.preventDefault(); - doLoadChildren(); + doLoadChildren(null); props.navigateViewContent(props.item.fullPath); }; From ed23d70b621893df5fd6c35d3fbd7e985358020b Mon Sep 17 00:00:00 2001 From: bytedream <me@bytedream.dev> Date: Wed, 18 Jun 2025 18:55:53 +0200 Subject: [PATCH 13/17] update docs --- web_src/js/utils/dom.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_src/js/utils/dom.ts b/web_src/js/utils/dom.ts index 6a5baa188809b..7ed0d73406e31 100644 --- a/web_src/js/utils/dom.ts +++ b/web_src/js/utils/dom.ts @@ -370,7 +370,7 @@ export function addDelegatedEventListener<T extends HTMLElement, E extends Event }, options); } -// Check if a click is done without any special mouse or key inputs that would trigger special browser behavior. +/** Returns whether a click event is a left-click without any modifiers held */ export function isPlainClick(e: MouseEvent) { return e.button === 0 && !e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey; } From 156de3091d27a61a98a051491af93f7580801f65 Mon Sep 17 00:00:00 2001 From: wxiaoguang <wxiaoguang@gmail.com> Date: Thu, 19 Jun 2025 13:08:35 +0800 Subject: [PATCH 14/17] refactor --- web_src/js/components/DiffFileTree.vue | 2 +- web_src/js/components/ViewFileTree.vue | 50 +++------------------- web_src/js/components/ViewFileTreeItem.vue | 31 +++++++------- web_src/js/components/ViewFileTreeStore.ts | 44 +++++++++++++++++++ 4 files changed, 66 insertions(+), 61 deletions(-) create mode 100644 web_src/js/components/ViewFileTreeStore.ts diff --git a/web_src/js/components/DiffFileTree.vue b/web_src/js/components/DiffFileTree.vue index 5426a672cbe1e..981d10c1c16fc 100644 --- a/web_src/js/components/DiffFileTree.vue +++ b/web_src/js/components/DiffFileTree.vue @@ -60,8 +60,8 @@ function updateState(visible: boolean) { </script> <template> + <!-- only render the tree if we're visible. in many cases this is something that doesn't change very often --> <div v-if="store.fileTreeIsVisible" class="diff-file-tree-items"> - <!-- only render the tree if we're visible. in many cases this is something that doesn't change very often --> <DiffFileTreeItem v-for="item in store.diffFileTree.TreeRoot.Children" :key="item.FullName" :item="item"/> </div> </template> diff --git a/web_src/js/components/ViewFileTree.vue b/web_src/js/components/ViewFileTree.vue index b1e57752acbd5..d56082415980a 100644 --- a/web_src/js/components/ViewFileTree.vue +++ b/web_src/js/components/ViewFileTree.vue @@ -1,9 +1,7 @@ <script lang="ts" setup> import ViewFileTreeItem from './ViewFileTreeItem.vue'; import {onMounted, ref} from 'vue'; -import {pathEscapeSegments} from '../utils/url.ts'; -import {GET} from '../modules/fetch.ts'; -import {createElementFromHTML} from '../utils/dom.ts'; +import {createViewFileTreeStore} from './ViewFileTreeStore.ts'; const elRoot = ref<HTMLElement | null>(null); @@ -13,56 +11,20 @@ const props = defineProps({ currentRefNameSubURL: {type: String, required: true}, }); -const files = ref([]); -const selectedItem = ref(''); - -async function loadChildren(treePath: string, subPath: string = '') { - const response = await GET(`${props.repoLink}/tree-view/${props.currentRefNameSubURL}/${pathEscapeSegments(treePath)}?sub_path=${encodeURIComponent(subPath)}`); - const json = await response.json(); - const poolSvgs = []; - for (const [svgId, svgContent] of Object.entries(json.renderedIconPool ?? {})) { - if (!document.querySelector(`.global-svg-icon-pool #${svgId}`)) poolSvgs.push(svgContent); - } - if (poolSvgs.length) { - const svgContainer = createElementFromHTML('<div class="global-svg-icon-pool tw-hidden"></div>'); - svgContainer.innerHTML = poolSvgs.join(''); - document.body.append(svgContainer); - } - return json.fileTreeNodes ?? null; -} - -async function loadViewContent(url: string) { - url = url.includes('?') ? url.replace('?', '?only_content=true') : `${url}?only_content=true`; - const response = await GET(url); - document.querySelector('.repo-view-content').innerHTML = await response.text(); -} - -async function navigateTreeView(treePath: string) { - const url = getWebUrl(treePath); - window.history.pushState({treePath, url}, null, url); - selectedItem.value = treePath; - await loadViewContent(url); -} - -function getWebUrl(treePath: string) { - return `${props.repoLink}/src/${props.currentRefNameSubURL}/${pathEscapeSegments(treePath)}`; -} - +const store = createViewFileTreeStore(props); onMounted(async () => { - selectedItem.value = props.treePath; - files.value = await loadChildren('', props.treePath); + store.rootFiles = await store.loadChildren('', props.treePath); elRoot.value.closest('.is-loading')?.classList?.remove('is-loading'); window.addEventListener('popstate', (e) => { - selectedItem.value = e.state?.treePath || ''; - if (e.state?.url) loadViewContent(e.state.url); + store.selectedItem = e.state?.treePath || ''; + if (e.state?.url) store.loadViewContent(e.state.url); }); }); </script> <template> <div class="view-file-tree-items" ref="elRoot"> - <!-- only render the tree if we're visible. in many cases this is something that doesn't change very often --> - <ViewFileTreeItem v-for="item in files" :key="item.name" :item="item" :selected-item="selectedItem" :get-web-url="getWebUrl" :navigate-view-content="navigateTreeView" :load-children="loadChildren"/> + <ViewFileTreeItem v-for="item in store.rootFiles" :key="item.name" :item="item" :store="store"/> </div> </template> diff --git a/web_src/js/components/ViewFileTreeItem.vue b/web_src/js/components/ViewFileTreeItem.vue index 32d637c4afd88..931fc8d881cb9 100644 --- a/web_src/js/components/ViewFileTreeItem.vue +++ b/web_src/js/components/ViewFileTreeItem.vue @@ -2,6 +2,7 @@ import {SvgIcon} from '../svg.ts'; import {isPlainClick} from '../utils/dom.ts'; import {ref} from 'vue'; +import {type createViewFileTreeStore} from './ViewFileTreeStore.ts'; type Item = { entryName: string; @@ -15,12 +16,10 @@ type Item = { const props = defineProps<{ item: Item, - navigateViewContent:(treePath: string) => void, - loadChildren:(treePath: string, subPath?: string) => Promise<Item[]>, - getWebUrl:(treePath: string) => string, - selectedItem?: string, + store: ReturnType<typeof createViewFileTreeStore> }>(); +const store = props.store; const isLoading = ref(false); const children = ref(props.item.children); const collapsed = ref(!props.item.children); @@ -31,10 +30,10 @@ const doLoadChildren = async (e: MouseEvent | null) => { e?.preventDefault(); collapsed.value = !collapsed.value; - if (!collapsed.value && props.loadChildren) { + if (!collapsed.value) { isLoading.value = true; try { - children.value = await props.loadChildren(props.item.fullPath); + children.value = await store.loadChildren(props.item.fullPath); } finally { isLoading.value = false; } @@ -47,14 +46,14 @@ const doLoadDirContent = (e: MouseEvent) => { e.preventDefault(); doLoadChildren(null); - props.navigateViewContent(props.item.fullPath); + store.navigateTreeView(props.item.fullPath); }; const doLoadFileContent = (e: MouseEvent) => { if (!isPlainClick(e)) return; e.preventDefault(); - props.navigateViewContent(props.item.fullPath); + store.navigateTreeView(props.item.fullPath); }; </script> @@ -63,7 +62,7 @@ const doLoadFileContent = (e: MouseEvent) => { <a v-if="item.entryMode === 'commit'" class="tree-item type-submodule silenced" :title="item.entryName" - :href="getWebUrl(item.fullPath)" + :href="store.getWebUrl(item.fullPath)" > <!-- submodule --> <div class="item-content"> @@ -74,9 +73,9 @@ const doLoadFileContent = (e: MouseEvent) => { </a> <a v-else-if="item.entryMode === 'symlink'" class="tree-item type-symlink silenced" - :class="{'selected': selectedItem === item.fullPath}" + :class="{'selected': store.selectedItem === item.fullPath}" :title="item.entryName" - :href="getWebUrl(item.fullPath)" + :href="store.getWebUrl(item.fullPath)" @click.stop="doLoadFileContent" > <!-- symlink --> @@ -88,9 +87,9 @@ const doLoadFileContent = (e: MouseEvent) => { </a> <a v-else-if="item.entryMode !== 'tree'" class="tree-item type-file silenced" - :class="{'selected': selectedItem === item.fullPath}" + :class="{'selected': store.selectedItem === item.fullPath}" :title="item.entryName" - :href="getWebUrl(item.fullPath)" + :href="store.getWebUrl(item.fullPath)" @click.stop="doLoadFileContent" > <!-- file --> @@ -102,9 +101,9 @@ const doLoadFileContent = (e: MouseEvent) => { </a> <a v-else class="tree-item type-directory silenced" - :class="{'selected': selectedItem === item.fullPath}" + :class="{'selected': store.selectedItem === item.fullPath}" :title="item.entryName" - :href="getWebUrl(item.fullPath)" + :href="store.getWebUrl(item.fullPath)" @click.stop="doLoadDirContent" > <!-- directory --> @@ -120,7 +119,7 @@ const doLoadFileContent = (e: MouseEvent) => { </a> <div v-if="children?.length" v-show="!collapsed" class="sub-items"> - <ViewFileTreeItem v-for="childItem in children" :key="childItem.entryName" :item="childItem" :selected-item="selectedItem" :get-web-url="getWebUrl" :navigate-view-content="navigateViewContent" :load-children="loadChildren"/> + <ViewFileTreeItem v-for="childItem in children" :key="childItem.entryName" :item="childItem" :store="store"/> </div> </template> <style scoped> diff --git a/web_src/js/components/ViewFileTreeStore.ts b/web_src/js/components/ViewFileTreeStore.ts new file mode 100644 index 0000000000000..6f249ee19d0ed --- /dev/null +++ b/web_src/js/components/ViewFileTreeStore.ts @@ -0,0 +1,44 @@ +import {reactive} from 'vue'; +import {GET} from '../modules/fetch.ts'; +import {pathEscapeSegments} from '../utils/url.ts'; +import {createElementFromHTML} from '../utils/dom.ts'; + +export function createViewFileTreeStore(props: { repoLink: string, treePath: string, currentRefNameSubURL: string}) { + const store = reactive({ + rootFiles: [], + selectedItem: props.treePath, + + async loadChildren(treePath: string, subPath: string = '') { + const response = await GET(`${props.repoLink}/tree-view/${props.currentRefNameSubURL}/${pathEscapeSegments(treePath)}?sub_path=${encodeURIComponent(subPath)}`); + const json = await response.json(); + const poolSvgs = []; + for (const [svgId, svgContent] of Object.entries(json.renderedIconPool ?? {})) { + if (!document.querySelector(`.global-svg-icon-pool #${svgId}`)) poolSvgs.push(svgContent); + } + if (poolSvgs.length) { + const svgContainer = createElementFromHTML('<div class="global-svg-icon-pool tw-hidden"></div>'); + svgContainer.innerHTML = poolSvgs.join(''); + document.body.append(svgContainer); + } + return json.fileTreeNodes ?? null; + }, + + async loadViewContent(url: string) { + url = url.includes('?') ? url.replace('?', '?only_content=true') : `${url}?only_content=true`; + const response = await GET(url); + document.querySelector('.repo-view-content').innerHTML = await response.text(); + }, + + async navigateTreeView(treePath: string) { + const url = store.getWebUrl(treePath); + window.history.pushState({treePath, url}, null, url); + store.selectedItem = treePath; + await store.loadViewContent(url); + }, + + getWebUrl(treePath: string) { + return `${props.repoLink}/src/${props.currentRefNameSubURL}/${pathEscapeSegments(treePath)}`; + }, + }); + return store; +} From 55195776d4b555123cf781687c2d532f0ca04f38 Mon Sep 17 00:00:00 2001 From: wxiaoguang <wxiaoguang@gmail.com> Date: Thu, 19 Jun 2025 13:29:14 +0800 Subject: [PATCH 15/17] refactor --- web_src/js/components/ViewFileTreeItem.vue | 77 +++++----------------- 1 file changed, 16 insertions(+), 61 deletions(-) diff --git a/web_src/js/components/ViewFileTreeItem.vue b/web_src/js/components/ViewFileTreeItem.vue index 931fc8d881cb9..60c5fc6d6e049 100644 --- a/web_src/js/components/ViewFileTreeItem.vue +++ b/web_src/js/components/ViewFileTreeItem.vue @@ -6,7 +6,7 @@ import {type createViewFileTreeStore} from './ViewFileTreeStore.ts'; type Item = { entryName: string; - entryMode: string; + entryMode: 'blob' | 'exec' | 'tree' | 'commit' | 'symlink' | 'unknown'; entryIcon: string; entryIconOpen: string; fullPath: string; @@ -24,11 +24,7 @@ const isLoading = ref(false); const children = ref(props.item.children); const collapsed = ref(!props.item.children); -const doLoadChildren = async (e: MouseEvent | null) => { - // the event is only not null if the user explicitly clicked on the directory item toggle. the preventDefault - // stops the event from bubbling up and causing a directory content load - e?.preventDefault(); - +const doLoadChildren = async () => { collapsed.value = !collapsed.value; if (!collapsed.value) { isLoading.value = true; @@ -40,74 +36,33 @@ const doLoadChildren = async (e: MouseEvent | null) => { } }; -const doLoadDirContent = (e: MouseEvent) => { - // only load the directory content without a window refresh if the user didn't press any special key +const onItemClick = (e: MouseEvent) => { + // only handle the click event with page partial reloading if the user didn't press any special key + // let browsers handle special keys like "Ctrl+Click" if (!isPlainClick(e)) return; e.preventDefault(); - - doLoadChildren(null); + if (props.item.entryMode === 'tree') doLoadChildren(); store.navigateTreeView(props.item.fullPath); }; -const doLoadFileContent = (e: MouseEvent) => { - if (!isPlainClick(e)) return; - e.preventDefault(); - - store.navigateTreeView(props.item.fullPath); -}; </script> -<!--title instead of tooltip above as the tooltip needs too much work with the current methods, i.e. not being loaded or staying open for "too long"--> <template> <a - v-if="item.entryMode === 'commit'" class="tree-item type-submodule silenced" - :title="item.entryName" - :href="store.getWebUrl(item.fullPath)" - > - <!-- submodule --> - <div class="item-content"> - <!-- eslint-disable-next-line vue/no-v-html --> - <span class="tw-contents" v-html="item.entryIcon"/> - <span class="gt-ellipsis tw-flex-1">{{ item.entryName }}</span> - </div> - </a> - <a - v-else-if="item.entryMode === 'symlink'" class="tree-item type-symlink silenced" - :class="{'selected': store.selectedItem === item.fullPath}" - :title="item.entryName" - :href="store.getWebUrl(item.fullPath)" - @click.stop="doLoadFileContent" - > - <!-- symlink --> - <div class="item-content"> - <!-- eslint-disable-next-line vue/no-v-html --> - <span class="tw-contents" v-html="item.entryIcon"/> - <span class="gt-ellipsis tw-flex-1">{{ item.entryName }}</span> - </div> - </a> - <a - v-else-if="item.entryMode !== 'tree'" class="tree-item type-file silenced" - :class="{'selected': store.selectedItem === item.fullPath}" - :title="item.entryName" - :href="store.getWebUrl(item.fullPath)" - @click.stop="doLoadFileContent" - > - <!-- file --> - <div class="item-content"> - <!-- eslint-disable-next-line vue/no-v-html --> - <span class="tw-contents" v-html="item.entryIcon"/> - <span class="gt-ellipsis tw-flex-1">{{ item.entryName }}</span> - </div> - </a> - <a - v-else class="tree-item type-directory silenced" - :class="{'selected': store.selectedItem === item.fullPath}" + class="tree-item silenced" + :class="{ + 'selected': store.selectedItem === item.fullPath, + 'type-submodule': item.entryMode === 'commit', + 'type-directory': item.entryMode === 'tree', + 'type-symlink': item.entryMode === 'symlink', + 'type-file': item.entryMode === 'blob' || item.entryMode === 'exec', + }" :title="item.entryName" :href="store.getWebUrl(item.fullPath)" - @click.stop="doLoadDirContent" + @click.stop="onItemClick" > <!-- directory --> - <div class="item-toggle"> + <div v-if="item.entryMode === 'tree'" class="item-toggle"> <SvgIcon v-if="isLoading" name="octicon-sync" class="circular-spin"/> <SvgIcon v-else :name="collapsed ? 'octicon-chevron-right' : 'octicon-chevron-down'" @click.stop="doLoadChildren"/> </div> From ef3873c4a7035f62eff4f53c077fb75a051cd563 Mon Sep 17 00:00:00 2001 From: wxiaoguang <wxiaoguang@gmail.com> Date: Thu, 19 Jun 2025 13:33:22 +0800 Subject: [PATCH 16/17] refactor --- web_src/js/components/ViewFileTreeItem.vue | 4 ++-- web_src/js/components/ViewFileTreeStore.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/web_src/js/components/ViewFileTreeItem.vue b/web_src/js/components/ViewFileTreeItem.vue index 60c5fc6d6e049..8ba2421907e80 100644 --- a/web_src/js/components/ViewFileTreeItem.vue +++ b/web_src/js/components/ViewFileTreeItem.vue @@ -58,10 +58,9 @@ const onItemClick = (e: MouseEvent) => { 'type-file': item.entryMode === 'blob' || item.entryMode === 'exec', }" :title="item.entryName" - :href="store.getWebUrl(item.fullPath)" + :href="store.buildTreePathWebUrl(item.fullPath)" @click.stop="onItemClick" > - <!-- directory --> <div v-if="item.entryMode === 'tree'" class="item-toggle"> <SvgIcon v-if="isLoading" name="octicon-sync" class="circular-spin"/> <SvgIcon v-else :name="collapsed ? 'octicon-chevron-right' : 'octicon-chevron-down'" @click.stop="doLoadChildren"/> @@ -77,6 +76,7 @@ const onItemClick = (e: MouseEvent) => { <ViewFileTreeItem v-for="childItem in children" :key="childItem.entryName" :item="childItem" :store="store"/> </div> </template> + <style scoped> .sub-items { display: flex; diff --git a/web_src/js/components/ViewFileTreeStore.ts b/web_src/js/components/ViewFileTreeStore.ts index 6f249ee19d0ed..13e2753c94018 100644 --- a/web_src/js/components/ViewFileTreeStore.ts +++ b/web_src/js/components/ViewFileTreeStore.ts @@ -30,13 +30,13 @@ export function createViewFileTreeStore(props: { repoLink: string, treePath: str }, async navigateTreeView(treePath: string) { - const url = store.getWebUrl(treePath); + const url = store.buildTreePathWebUrl(treePath); window.history.pushState({treePath, url}, null, url); store.selectedItem = treePath; await store.loadViewContent(url); }, - getWebUrl(treePath: string) { + buildTreePathWebUrl(treePath: string) { return `${props.repoLink}/src/${props.currentRefNameSubURL}/${pathEscapeSegments(treePath)}`; }, }); From f01e51fd366995967d0385724ed128b2cde9ed03 Mon Sep 17 00:00:00 2001 From: bytedream <me@bytedream.dev> Date: Thu, 19 Jun 2025 19:49:44 +0200 Subject: [PATCH 17/17] prevent page reload on subtree load --- web_src/js/components/ViewFileTreeItem.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_src/js/components/ViewFileTreeItem.vue b/web_src/js/components/ViewFileTreeItem.vue index 8ba2421907e80..4a7569e92139a 100644 --- a/web_src/js/components/ViewFileTreeItem.vue +++ b/web_src/js/components/ViewFileTreeItem.vue @@ -63,7 +63,7 @@ const onItemClick = (e: MouseEvent) => { > <div v-if="item.entryMode === 'tree'" class="item-toggle"> <SvgIcon v-if="isLoading" name="octicon-sync" class="circular-spin"/> - <SvgIcon v-else :name="collapsed ? 'octicon-chevron-right' : 'octicon-chevron-down'" @click.stop="doLoadChildren"/> + <SvgIcon v-else :name="collapsed ? 'octicon-chevron-right' : 'octicon-chevron-down'" @click.stop.prevent="doLoadChildren"/> </div> <div class="item-content"> <!-- eslint-disable-next-line vue/no-v-html -->