+
{{template "base/alert" .}}
{{if .Repository.IsArchived}}
@@ -16,112 +17,9 @@
{{template "repo/code/recently_pushed_new_branches" .}}
- {{$treeNamesLen := len .TreeNames}}
- {{$isTreePathRoot := eq $treeNamesLen 0}}
- {{$showSidebar := and $isTreePathRoot (not .HideRepoInfo) (not .IsBlame)}}
('.repo-view-file-tree-container');
+ const repoViewContent = document.querySelector('.repo-view-content');
+ if (!sidebar || !repoViewContent) return;
+
+ registerGlobalEventFunc('click', 'onRepoViewFileTreeToggle', toggleSidebar);
+
+ const fileTree = sidebar.querySelector('#view-file-tree');
+ createApp(ViewFileTree, {
+ repoLink: fileTree.getAttribute('data-repo-link'),
+ treePath: fileTree.getAttribute('data-tree-path'),
+ currentRefNameSubURL: fileTree.getAttribute('data-current-ref-name-sub-url'),
+ }).mount(fileTree);
+}
diff --git a/web_src/js/index.ts b/web_src/js/index.ts
index 2e44ef826a4..839a160168c 100644
--- a/web_src/js/index.ts
+++ b/web_src/js/index.ts
@@ -64,6 +64,7 @@ import {initFootLanguageMenu, initGlobalDropdown, initGlobalInput, initGlobalTab
import {initGlobalButtonClickOnEnter, initGlobalButtons, initGlobalDeleteButton} from './features/common-button.ts';
import {initGlobalComboMarkdownEditor, initGlobalEnterQuickSubmit, initGlobalFormDirtyLeaveConfirm} from './features/common-form.ts';
import {callInitFunctions} from './modules/init.ts';
+import {initRepoViewFileTree} from './features/repo-view-file-tree.ts';
initGiteaFomantic();
initSubmitEventPolyfill();
@@ -139,6 +140,7 @@ onDomReady(() => {
initRepoRelease,
initRepoReleaseNew,
initRepoTopicBar,
+ initRepoViewFileTree,
initRepoWikiForm,
initRepository,
initRepositoryActionView,
diff --git a/web_src/js/svg.ts b/web_src/js/svg.ts
index 8316cbcf85e..7b377e1ab43 100644
--- a/web_src/js/svg.ts
+++ b/web_src/js/svg.ts
@@ -29,6 +29,7 @@ import octiconFile from '../../public/assets/img/svg/octicon-file.svg';
import octiconFileDirectoryFill from '../../public/assets/img/svg/octicon-file-directory-fill.svg';
import octiconFileDirectoryOpenFill from '../../public/assets/img/svg/octicon-file-directory-open-fill.svg';
import octiconFileSubmodule from '../../public/assets/img/svg/octicon-file-submodule.svg';
+import octiconFileSymlinkFile from '../../public/assets/img/svg/octicon-file-symlink-file.svg';
import octiconFilter from '../../public/assets/img/svg/octicon-filter.svg';
import octiconGear from '../../public/assets/img/svg/octicon-gear.svg';
import octiconGitBranch from '../../public/assets/img/svg/octicon-git-branch.svg';
@@ -107,6 +108,7 @@ const svgs = {
'octicon-file-directory-fill': octiconFileDirectoryFill,
'octicon-file-directory-open-fill': octiconFileDirectoryOpenFill,
'octicon-file-submodule': octiconFileSubmodule,
+ 'octicon-file-symlink-file': octiconFileSymlinkFile,
'octicon-filter': octiconFilter,
'octicon-gear': octiconGear,
'octicon-git-branch': octiconGitBranch,
- {{template "repo/sub_menu" .}}
-
{{if $showSidebar}}
diff --git a/templates/repo/view.tmpl b/templates/repo/view.tmpl
new file mode 100644
index 00000000000..c3d562003dc
--- /dev/null
+++ b/templates/repo/view.tmpl
@@ -0,0 +1,29 @@
+{{template "base/head" .}}
+
-
- {{if .IsViewFile}}
- {{template "repo/view_file" .}}
- {{else if .IsBlame}}
- {{template "repo/blame" .}}
- {{else}}{{/* IsViewDirectory */}}
- {{if $isTreePathRoot}}
- {{template "repo/code/upstream_diverging_info" .}}
- {{end}}
- {{template "repo/view_list" .}}
- {{if and .ReadmeExist (or .IsMarkup .IsPlainText)}}
- {{template "repo/view_file" .}}
- {{end}}
- {{end}}
+ {{template "repo/view_content" .}}
- {{- /* for repo home (default branch) and /owner/repo/src/{RefType}/{RefShortName} */ -}}
- {{- template "repo/branch_dropdown" dict
- "Repository" .Repository
- "ShowTabBranches" true
- "ShowTabTags" true
- "CurrentRefType" .RefFullName.RefType
- "CurrentRefShortName" .RefFullName.ShortName
- "CurrentTreePath" .TreePath
- "RefLinkTemplate" "{RepoLink}/src/{RefType}/{RefShortName}/{TreePath}"
- "AllowCreateNewRef" .CanCreateBranch
- "ShowViewAllRefsEntry" true
- -}}
- {{if and .CanCompareOrPull .RefFullName.IsBranch (not .Repository.IsArchived)}}
- {{$cmpBranch := ""}}
- {{if ne .Repository.ID .BaseRepo.ID}}
- {{$cmpBranch = printf "%s/%s:" (.Repository.OwnerName|PathEscape) (.Repository.Name|PathEscape)}}
- {{end}}
- {{$cmpBranch = print $cmpBranch (.BranchName|PathEscapeSegments)}}
- {{$compareLink := printf "%s/compare/%s...%s" .BaseRepo.Link (.BaseRepo.DefaultBranch|PathEscapeSegments) $cmpBranch}}
-
- {{svg "octicon-git-pull-request"}}
-
- {{end}}
-
-
- {{if $isTreePathRoot}}
- {{ctx.Locale.Tr "repo.find_file.go_to_file"}}
- {{end}}
-
- {{if and .CanWriteCode .RefFullName.IsBranch (not .Repository.IsMirror) (not .Repository.IsArchived) (not .IsViewFile)}}
-
- {{end}}
-
- {{if and $isTreePathRoot .Repository.IsTemplate}}
-
- {{ctx.Locale.Tr "repo.use_template"}}
-
- {{end}}
-
- {{if not $isTreePathRoot}}
- {{$treeNameIdxLast := Eval $treeNamesLen "-" 1}}
-
- {{StringUtils.EllipsisString .Repository.Name 30}}
- {{- range $i, $v := .TreeNames -}}
- /
- {{- if eq $i $treeNameIdxLast -}}
- {{$v}}
-
- {{- else -}}
- {{$p := index $.Paths $i}}{{$v}}
- {{- end -}}
- {{- end -}}
-
- {{end}}
-
-
-
-
- {{if $isTreePathRoot}}
- {{template "repo/clone_panel" .}}
- {{end}}
- {{if and (not $isTreePathRoot) (not .IsViewFile) (not .IsBlame)}}{{/* IsViewDirectory (not home), TODO: split the templates, avoid using "if" tricks */}}
-
- {{svg "octicon-history" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.file_history"}}
-
- {{end}}
-
-
+ {{template "repo/header" .}}
+
+{{template "base/footer" .}}
diff --git a/templates/repo/view_content.tmpl b/templates/repo/view_content.tmpl
new file mode 100644
index 00000000000..06e9f8515ca
--- /dev/null
+++ b/templates/repo/view_content.tmpl
@@ -0,0 +1,111 @@
+{{$isTreePathRoot := not .TreeNames}}
+
+{{template "repo/sub_menu" .}}
+
+ {{template "base/alert" .}}
+
+ {{if .Repository.IsArchived}}
+
+
+ {{if .Repository.ArchivedUnix.IsZero}}
+ {{ctx.Locale.Tr "repo.archive.title"}}
+ {{else}}
+ {{ctx.Locale.Tr "repo.archive.title_date" (DateUtils.AbsoluteLong .Repository.ArchivedUnix)}}
+ {{end}}
+
+ {{end}}
+
+ {{template "repo/code/recently_pushed_new_branches" .}}
+
+
+
+
+ {{template "repo/view_file_tree" .}}
+
+
+ {{template "repo/view_content" .}}
+
+
+
+{{if .IsViewFile}}
+ {{template "repo/view_file" .}}
+{{else if .IsBlame}}
+ {{template "repo/blame" .}}
+{{else}}{{/* IsViewDirectory */}}
+ {{if $isTreePathRoot}}
+ {{template "repo/code/upstream_diverging_info" .}}
+ {{end}}
+ {{template "repo/view_list" .}}
+ {{if and .ReadmeExist (or .IsMarkup .IsPlainText)}}
+ {{template "repo/view_file" .}}
+ {{end}}
+{{end}}
diff --git a/templates/repo/view_file_tree.tmpl b/templates/repo/view_file_tree.tmpl
new file mode 100644
index 00000000000..6e5d504a472
--- /dev/null
+++ b/templates/repo/view_file_tree.tmpl
@@ -0,0 +1,15 @@
+
+ {{if not $isTreePathRoot}}
+
+ {{end}}
+
+ {{template "repo/branch_dropdown" dict
+ "Repository" .Repository
+ "ShowTabBranches" true
+ "ShowTabTags" true
+ "CurrentRefType" .RefFullName.RefType
+ "CurrentRefShortName" .RefFullName.ShortName
+ "CurrentTreePath" .TreePath
+ "RefLinkTemplate" "{RepoLink}/src/{RefType}/{RefShortName}/{TreePath}"
+ "AllowCreateNewRef" .CanCreateBranch
+ "ShowViewAllRefsEntry" true
+ }}
+
+ {{if and .CanCompareOrPull .RefFullName.IsBranch (not .Repository.IsArchived)}}
+ {{$cmpBranch := ""}}
+ {{if ne .Repository.ID .BaseRepo.ID}}
+ {{$cmpBranch = printf "%s/%s:" (.Repository.OwnerName|PathEscape) (.Repository.Name|PathEscape)}}
+ {{end}}
+ {{$cmpBranch = print $cmpBranch (.BranchName|PathEscapeSegments)}}
+ {{$compareLink := printf "%s/compare/%s...%s" .BaseRepo.Link (.BaseRepo.DefaultBranch|PathEscapeSegments) $cmpBranch}}
+
+ {{svg "octicon-git-pull-request"}}
+
+ {{end}}
+
+
+ {{if $isTreePathRoot}}
+ {{ctx.Locale.Tr "repo.find_file.go_to_file"}}
+ {{end}}
+
+ {{if and .CanWriteCode .RefFullName.IsBranch (not .Repository.IsMirror) (not .Repository.IsArchived) (not .IsViewFile)}}
+
+ {{end}}
+
+ {{if and $isTreePathRoot .Repository.IsTemplate}}
+
+ {{ctx.Locale.Tr "repo.use_template"}}
+
+ {{end}}
+
+ {{if not $isTreePathRoot}}
+ {{$treeNameIdxLast := Eval (len .TreeNames) "-" 1}}
+
+ {{StringUtils.EllipsisString .Repository.Name 30}}
+ {{- range $i, $v := .TreeNames -}}
+ /
+ {{- if eq $i $treeNameIdxLast -}}
+ {{$v}}
+
+ {{- else -}}
+ {{$p := index $.Paths $i}}{{$v}}
+ {{- end -}}
+ {{- end -}}
+
+ {{end}}
+
+
+
+
+ {{if $isTreePathRoot}}
+ {{template "repo/clone_panel" .}}
+ {{end}}
+ {{if and (not $isTreePathRoot) (not .IsViewFile) (not .IsBlame)}}{{/* IsViewDirectory (not home), TODO: split the templates, avoid using "if" tricks */}}
+
+ {{svg "octicon-history" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.file_history"}}
+
+ {{end}}
+
+
+
+ Files
+
+
+{{/* TODO: Dynamically move components such as refSelector and createPR here */}}
+
diff --git a/web_src/css/repo/home.css b/web_src/css/repo/home.css
index 96551979ea2..219be77adb4 100644
--- a/web_src/css/repo/home.css
+++ b/web_src/css/repo/home.css
@@ -49,6 +49,21 @@
}
}
+.repo-view-container {
+ display: flex;
+ gap: var(--page-spacing);
+}
+
+.repo-view-container .repo-view-file-tree-container {
+ flex: 0 1 15%;
+ min-width: 0;
+ max-height: 100vh;
+}
+
+.repo-view-content {
+ flex: 1;
+}
+
.language-stats {
display: flex;
gap: 2px;
diff --git a/web_src/js/components/ViewFileTree.vue b/web_src/js/components/ViewFileTree.vue
new file mode 100644
index 00000000000..1820c47e7aa
--- /dev/null
+++ b/web_src/js/components/ViewFileTree.vue
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/web_src/js/components/ViewFileTreeItem.vue b/web_src/js/components/ViewFileTreeItem.vue
new file mode 100644
index 00000000000..4dffc86a1b1
--- /dev/null
+++ b/web_src/js/components/ViewFileTreeItem.vue
@@ -0,0 +1,156 @@
+
+
+
+
+
+
+
+
+
+ {{ item.entryName }}
+
+
+
+
+
+
+ {{ item.entryName }}
+
+
+
+
+
+
+ {{ item.entryName }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ item.entryName }}
+
+
+
+
+
+
diff --git a/web_src/js/features/repo-view-file-tree.ts b/web_src/js/features/repo-view-file-tree.ts
new file mode 100644
index 00000000000..f52b64cc51d
--- /dev/null
+++ b/web_src/js/features/repo-view-file-tree.ts
@@ -0,0 +1,37 @@
+import {createApp} from 'vue';
+import {toggleElem} from '../utils/dom.ts';
+import {POST} from '../modules/fetch.ts';
+import ViewFileTree from '../components/ViewFileTree.vue';
+import {registerGlobalEventFunc} from '../modules/observer.ts';
+
+const {appSubUrl} = window.config;
+
+async function toggleSidebar(btn: HTMLElement) {
+ const elToggleShow = document.querySelector('.repo-view-file-tree-toggle-show');
+ const elFileTreeContainer = document.querySelector('.repo-view-file-tree-container');
+ const shouldShow = btn.getAttribute('data-toggle-action') === 'show';
+ toggleElem(elFileTreeContainer, shouldShow);
+ toggleElem(elToggleShow, !shouldShow);
+
+ // FIXME: need to remove "full height" style from parent element
+
+ if (!elFileTreeContainer.hasAttribute('data-user-is-signed-in')) return;
+ await POST(`${appSubUrl}/user/settings/update_preferences`, {
+ data: {codeViewShowFileTree: shouldShow},
+ });
+}
+
+export async function initRepoViewFileTree() {
+ const sidebar = document.querySelector