From 181645f4edac217522e2aaa730a98e472ac60c90 Mon Sep 17 00:00:00 2001 From: Kerwin Bryant Date: Thu, 12 Dec 2024 15:20:28 +0000 Subject: [PATCH 01/92] add tree sidebar to file view --- routers/web/repo/blame.go | 5 + routers/web/repo/file.go | 45 +++++++ routers/web/repo/repo.go | 18 +++ routers/web/repo/view_home.go | 5 + routers/web/web.go | 5 + templates/repo/home.tmpl | 20 ++- templates/repo/view_file_tree_sidebar.tmpl | 65 ++++++++++ web_src/css/repo/home.css | 43 +++++++ web_src/js/components/ViewFileTree.vue | 25 ++++ web_src/js/components/ViewFileTreeItem.vue | 119 ++++++++++++++++++ .../features/repo-view-file-tree-sidebar.ts | 87 +++++++++++++ web_src/js/index.ts | 2 + 12 files changed, 436 insertions(+), 3 deletions(-) create mode 100644 routers/web/repo/file.go create mode 100644 templates/repo/view_file_tree_sidebar.tmpl create mode 100644 web_src/js/components/ViewFileTree.vue create mode 100644 web_src/js/components/ViewFileTreeItem.vue create mode 100644 web_src/js/features/repo-view-file-tree-sidebar.ts diff --git a/routers/web/repo/blame.go b/routers/web/repo/blame.go index ad790875136c3..2bdaeefb88c61 100644 --- a/routers/web/repo/blame.go +++ b/routers/web/repo/blame.go @@ -46,6 +46,11 @@ func RefBlame(ctx *context.Context) { return } + // ctx.Data["RepoPreferences"] = ctx.Session.Get("repoPreferences") + ctx.Data["RepoPreferences"] = &preferencesForm{ + ShowFileViewTreeSidebar: true, + } + branchLink := ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL() treeLink := branchLink rawLink := ctx.Repo.RepoLink + "/raw/" + ctx.Repo.BranchNameSubURL() diff --git a/routers/web/repo/file.go b/routers/web/repo/file.go new file mode 100644 index 0000000000000..60e7cb24b74da --- /dev/null +++ b/routers/web/repo/file.go @@ -0,0 +1,45 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Copyright 2018 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package repo + +import ( + "net/http" + + "code.gitea.io/gitea/models/unit" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/services/context" + files_service "code.gitea.io/gitea/services/repository/files" +) + +// canReadFiles returns true if repository is readable and user has proper access level. +func canReadFiles(r *context.Repository) bool { + return r.Permission.CanRead(unit.TypeCode) +} + +// GetContents Get the metadata and contents (if a file) of an entry in a repository, or a list of entries if a dir +func GetContents(ctx *context.Context) { + if !canReadFiles(ctx.Repo) { + ctx.NotFound("Invalid FilePath", nil) + return + } + + treePath := ctx.PathParam("*") + ref := ctx.FormTrim("ref") + + if fileList, err := files_service.GetContentsOrList(ctx, ctx.Repo.Repository, treePath, ref); err != nil { + if git.IsErrNotExist(err) { + ctx.NotFound("GetContentsOrList", err) + return + } + ctx.ServerError("Repo.GitRepo.GetCommit", err) + } else { + ctx.JSON(http.StatusOK, fileList) + } +} + +// GetContentsList Get the metadata of all the entries of the root dir +func GetContentsList(ctx *context.Context) { + GetContents(ctx) +} diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go index f5e59b0357b02..fbd3c835518c9 100644 --- a/routers/web/repo/repo.go +++ b/routers/web/repo/repo.go @@ -22,6 +22,7 @@ import ( "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/cache" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/optional" repo_module "code.gitea.io/gitea/modules/repository" @@ -758,3 +759,20 @@ func PrepareBranchList(ctx *context.Context) { } ctx.Data["Branches"] = brs } + +type preferencesForm struct { + ShowFileViewTreeSidebar bool `json:"show_file_view_tree_sidebar"` +} + +func UpdatePreferences(ctx *context.Context) { + form := &preferencesForm{} + if err := json.NewDecoder(ctx.Req.Body).Decode(&form); err != nil { + ctx.ServerError("DecodePreferencesForm", err) + return + } + // if err := ctx.Session.Set("repoPreferences", form); err != nil { + // ctx.ServerError("Session.Set", err) + // return + // } + ctx.JSONOK() +} diff --git a/routers/web/repo/view_home.go b/routers/web/repo/view_home.go index b318c4a621a60..707387f8e5cc4 100644 --- a/routers/web/repo/view_home.go +++ b/routers/web/repo/view_home.go @@ -305,6 +305,11 @@ func Home(ctx *context.Context) { return } + // ctx.Data["RepoPreferences"] = ctx.Session.Get("repoPreferences") + ctx.Data["RepoPreferences"] = &preferencesForm{ + ShowFileViewTreeSidebar: true, + } + title := ctx.Repo.Repository.Owner.Name + "/" + ctx.Repo.Repository.Name if len(ctx.Repo.Repository.Description) > 0 { title += ": " + ctx.Repo.Repository.Description diff --git a/routers/web/web.go b/routers/web/web.go index 72ee47bb4c962..bf8c4306bf314 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -987,6 +987,7 @@ func registerRoutes(m *web.Router) { m.Get("/migrate", repo.Migrate) m.Post("/migrate", web.Bind(forms.MigrateRepoForm{}), repo.MigratePost) m.Get("/search", repo.SearchRepo) + m.Put("/preferences", repo.UpdatePreferences) }, reqSignIn) // end "/repo": create, migrate, search @@ -1161,6 +1162,10 @@ func registerRoutes(m *web.Router) { m.Get("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.TreeList) m.Get("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.TreeList) }) + m.Group("/contents", func() { + m.Get("", repo.GetContentsList) + m.Get("/*", repo.GetContents) + }) m.Get("/compare", repo.MustBeNotEmpty, repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.CompareDiff) m.Combo("/compare/*", repo.MustBeNotEmpty, repo.SetEditorconfigIfExists). Get(repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.CompareDiff). diff --git a/templates/repo/home.tmpl b/templates/repo/home.tmpl index 4e6d375b51710..89d2442afafc4 100644 --- a/templates/repo/home.tmpl +++ b/templates/repo/home.tmpl @@ -19,11 +19,24 @@ {{$treeNamesLen := len .TreeNames}} {{$isTreePathRoot := eq $treeNamesLen 0}} {{$showSidebar := $isTreePathRoot}} -
+ {{$hasTreeSidebar := not $isTreePathRoot}} + {{$showTreeSidebar := .RepoPreferences.ShowFileViewTreeSidebar}} + {{$hideTreeSidebar := not $showTreeSidebar}} + {{$hasAndShowTreeSidebar := and $hasTreeSidebar $showTreeSidebar}} +
+ {{if $hasTreeSidebar}} +
{{template "repo/view_file_tree_sidebar" .}}
+ {{end}} +
{{template "repo/sub_menu" .}}
+ {{if $hasTreeSidebar}} + + {{end}} {{$branchDropdownCurrentRefType := "branch"}} {{$branchDropdownCurrentRefShortName := .BranchName}} {{if .IsViewTag}} @@ -40,6 +53,7 @@ "RefLinkTemplate" "{RepoLink}/src/{RefType}/{RefShortName}/{TreePath}" "AllowCreateNewRef" .CanCreateBranch "ShowViewAllRefsEntry" true + "ContainerClasses" (Iif $hasAndShowTreeSidebar "tw-hidden" "") }} {{if and .CanCompareOrPull .IsViewBranch (not .Repository.IsArchived)}} {{$cmpBranch := ""}} @@ -48,7 +62,7 @@ {{end}} {{$cmpBranch = print $cmpBranch (.BranchName|PathEscapeSegments)}} {{$compareLink := printf "%s/compare/%s...%s" .BaseRepo.Link (.BaseRepo.DefaultBranch|PathEscapeSegments) $cmpBranch}} - {{svg "octicon-git-pull-request"}} @@ -60,7 +74,7 @@ {{end}} {{if and .CanWriteCode .IsViewBranch (not .Repository.IsMirror) (not .Repository.IsArchived) (not .IsViewFile)}} - + Files +
+ +
+
+
+ {{svg "octicon-sync" 16 "job-status-rotate"}} +
+
diff --git a/web_src/css/repo/home.css b/web_src/css/repo/home.css index ca5b432804fe4..71ccbaf8f227f 100644 --- a/web_src/css/repo/home.css +++ b/web_src/css/repo/home.css @@ -48,6 +48,49 @@ } } +.repo-grid-tree-sidebar { + display: grid; + grid-template-columns: 300px auto; + grid-template-rows: auto auto 1fr; +} + +.repo-grid-tree-sidebar .repo-home-filelist { + min-width: 0; + grid-column: 2; + grid-row: 1 / 4; +} + +.repo-grid-tree-sidebar .repo-view-file-tree-sidebar { + display: flex; + flex-direction: column; + gap: 0.25em; +} + +.repo-grid-tree-sidebar .view-file-tree-sidebar-top { + display: flex; + flex-direction: column; + gap: 0.25em; +} + +.repo-grid-tree-sidebar .view-file-tree-sidebar-top .button { + padding: 6px 10px !important; + height: 30px; + flex-shrink: 0; + margin: 0; +} + +.repo-grid-tree-sidebar .view-file-tree-sidebar-top .sidebar-ref { + display: flex; + gap: 0.25em; +} + +@media (max-width: 767.98px) { + .repo-grid-tree-sidebar { + grid-template-columns: auto; + grid-template-rows: auto auto auto; + } +} + .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 0000000000000..605314027ce83 --- /dev/null +++ b/web_src/js/components/ViewFileTree.vue @@ -0,0 +1,25 @@ + + + + + diff --git a/web_src/js/components/ViewFileTreeItem.vue b/web_src/js/components/ViewFileTreeItem.vue new file mode 100644 index 0000000000000..0df09613bc0f1 --- /dev/null +++ b/web_src/js/components/ViewFileTreeItem.vue @@ -0,0 +1,119 @@ + + + + diff --git a/web_src/js/features/repo-view-file-tree-sidebar.ts b/web_src/js/features/repo-view-file-tree-sidebar.ts new file mode 100644 index 0000000000000..26a9d0111b118 --- /dev/null +++ b/web_src/js/features/repo-view-file-tree-sidebar.ts @@ -0,0 +1,87 @@ +import {createApp} from 'vue'; +import {toggleElem} from '../utils/dom.ts'; +import {GET, PUT} from '../modules/fetch.ts'; +import ViewFileTree from '../components/ViewFileTree.vue'; + +async function toggleSidebar(visibility) { + const sidebarEl = document.querySelector('.repo-view-file-tree-sidebar'); + const showBtnEl = document.querySelector('.show-tree-sidebar-button'); + const refSelectorEl = document.querySelector('.repo-home-filelist .js-branch-tag-selector'); + const newPrBtnEl = document.querySelector('.repo-home-filelist #new-pull-request'); + const addFileEl = document.querySelector('.repo-home-filelist .add-file-dropdown'); + const containerClassList = sidebarEl.parentElement.classList; + containerClassList.toggle('repo-grid-tree-sidebar', visibility); + containerClassList.toggle('repo-grid-filelist-only', !visibility); + toggleElem(sidebarEl, visibility); + toggleElem(showBtnEl, !visibility); + toggleElem(refSelectorEl, !visibility); + toggleElem(newPrBtnEl, !visibility); + if (addFileEl) { + toggleElem(addFileEl, !visibility); + } + + // save to session + await PUT('/repo/preferences', { + data: { + show_file_view_tree_sidebar: visibility, + }, + }); +} + +async function loadChildren(item?) { + const el = document.querySelector('#view-file-tree'); + const apiBaseUrl = el.getAttribute('data-api-base-url'); + const response = await GET(`${apiBaseUrl}/contents/${item ? item.path : ''}`); + const json = await response.json(); + if (json instanceof Array) { + return json.map((i) => ({ + name: i.name, + isFile: i.type === 'file', + htmlUrl: i.html_url, + path: i.path, + })); + } + return null; +} + +async function loadRecursive(treePath) { + let root = null; + let parent = null; + let parentPath = ''; + for (const i of (`/${treePath}`).split('/')) { + const path = `${parentPath}${parentPath ? '/' : ''}${i}`; + const result = await loadChildren({path}); + if (root === null) { + root = result; + parent = root; + } else { + parent = parent.find((item) => item.path === path); + parent.children = result; + parent = result; + } + parentPath = path; + } + return root; +} + +export async function initViewFileTreeSidebar() { + const sidebarElement = document.querySelector('.repo-view-file-tree-sidebar'); + if (!sidebarElement) return; + + document.querySelector('.show-tree-sidebar-button').addEventListener('click', () => { + toggleSidebar(true); + }); + + document.querySelector('.hide-tree-sidebar-button').addEventListener('click', () => { + toggleSidebar(false); + }); + + const fileTree = document.querySelector('#view-file-tree'); + const treePath = fileTree.getAttribute('data-tree-path'); + + const files = await loadRecursive(treePath); + + fileTree.classList.remove('center'); + const fileTreeView = createApp(ViewFileTree, {files, selectedItem: treePath, loadChildren}); + fileTreeView.mount(fileTree); +} diff --git a/web_src/js/index.ts b/web_src/js/index.ts index 51d8c96fbdfbf..4602e22ffa5e9 100644 --- a/web_src/js/index.ts +++ b/web_src/js/index.ts @@ -33,6 +33,7 @@ import { } from './features/repo-issue.ts'; import {initRepoEllipsisButton, initCommitStatuses} from './features/repo-commit.ts'; import {initRepoTopicBar} from './features/repo-home.ts'; +import {initViewFileTreeSidebar} from './features/repo-view-file-tree-sidebar.ts'; import {initAdminCommon} from './features/admin/common.ts'; import {initRepoTemplateSearch} from './features/repo-template.ts'; import {initRepoCodeView} from './features/repo-code.ts'; @@ -195,6 +196,7 @@ onDomReady(() => { initRepoReleaseNew, initRepoTemplateSearch, initRepoTopicBar, + initViewFileTreeSidebar, initRepoWikiForm, initRepository, initRepositoryActionView, From c4e7f0c11929c5c0581792cedbee3dec66437b7b Mon Sep 17 00:00:00 2001 From: Kerwin Bryant Date: Fri, 13 Dec 2024 08:08:41 +0000 Subject: [PATCH 02/92] add tree sidebar to file view --- templates/repo/home.tmpl | 14 +++++++------- web_src/js/components/ViewFileTree.vue | 5 +++-- web_src/js/components/ViewFileTreeItem.vue | 15 ++++++++------- .../js/features/repo-view-file-tree-sidebar.ts | 13 +++++++++++-- 4 files changed, 29 insertions(+), 18 deletions(-) diff --git a/templates/repo/home.tmpl b/templates/repo/home.tmpl index 89d2442afafc4..f5c76a026bc6f 100644 --- a/templates/repo/home.tmpl +++ b/templates/repo/home.tmpl @@ -1,4 +1,11 @@ {{template "base/head" .}} +{{$treeNamesLen := len .TreeNames}} +{{$isTreePathRoot := eq $treeNamesLen 0}} +{{$showSidebar := $isTreePathRoot}} +{{$hasTreeSidebar := not $isTreePathRoot}} +{{$showTreeSidebar := .RepoPreferences.ShowFileViewTreeSidebar}} +{{$hideTreeSidebar := not $showTreeSidebar}} +{{$hasAndShowTreeSidebar := and $hasTreeSidebar $showTreeSidebar}}
{{template "repo/header" .}}
@@ -16,13 +23,6 @@ {{template "repo/code/recently_pushed_new_branches" .}} - {{$treeNamesLen := len .TreeNames}} - {{$isTreePathRoot := eq $treeNamesLen 0}} - {{$showSidebar := $isTreePathRoot}} - {{$hasTreeSidebar := not $isTreePathRoot}} - {{$showTreeSidebar := .RepoPreferences.ShowFileViewTreeSidebar}} - {{$hideTreeSidebar := not $showTreeSidebar}} - {{$hasAndShowTreeSidebar := and $hasTreeSidebar $showTreeSidebar}}
{{if $hasTreeSidebar}}
{{template "repo/view_file_tree_sidebar" .}}
diff --git a/web_src/js/components/ViewFileTree.vue b/web_src/js/components/ViewFileTree.vue index 605314027ce83..3337f6e9e553e 100644 --- a/web_src/js/components/ViewFileTree.vue +++ b/web_src/js/components/ViewFileTree.vue @@ -3,15 +3,16 @@ import ViewFileTreeItem from './ViewFileTreeItem.vue'; defineProps<{ files: any, - selectedItem: string, + selectedItem: any, loadChildren: any, + loadContent: any; }>(); diff --git a/web_src/js/components/ViewFileTreeItem.vue b/web_src/js/components/ViewFileTreeItem.vue index 0df09613bc0f1..db2e888fddfdf 100644 --- a/web_src/js/components/ViewFileTreeItem.vue +++ b/web_src/js/components/ViewFileTreeItem.vue @@ -12,13 +12,14 @@ type Item = { const props = defineProps<{ item: Item, + loadContent: any; loadChildren: any; - selectedItem?: string; + selectedItem?: any; }>(); const isLoading = ref(false); -const collapsed = ref(!props.item.children); const children = ref(props.item.children); +const collapsed = ref(!props.item.children); const doLoadChildren = async () => { collapsed.value = !collapsed.value; @@ -32,11 +33,11 @@ const doLoadChildren = async () => { const doLoadDirContent = () => { doLoadChildren(); - window.location.href = props.item.htmlUrl; + props.loadContent(props.item); }; const doLoadFileContent = () => { - window.location.href = props.item.htmlUrl; + props.loadContent(props.item); }; @@ -44,7 +45,7 @@ const doLoadFileContent = () => {
@@ -54,7 +55,7 @@ const doLoadFileContent = () => {
@@ -66,7 +67,7 @@ const doLoadFileContent = () => {
- +
From 2d644fb4cea501063294afe9451229ea1795185d Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 14 Jan 2025 21:47:59 -0800 Subject: [PATCH 57/92] make template simpler --- templates/repo/home.tmpl | 15 +++++---------- templates/repo/home_content.tmpl | 11 ++++------- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/templates/repo/home.tmpl b/templates/repo/home.tmpl index 07141256c7beb..290e80064c574 100644 --- a/templates/repo/home.tmpl +++ b/templates/repo/home.tmpl @@ -1,13 +1,8 @@ {{template "base/head" .}} -{{$treeNamesLen := len .TreeNames}} -{{$isTreePathRoot := eq $treeNamesLen 0}} -{{$showSidebar := and $isTreePathRoot (not .HideRepoInfo) (not .IsBlame)}} -{{$hasTreeSidebar := not $isTreePathRoot}} -{{$showTreeSidebar := .RepoPreferences.ShowFileViewTreeSidebar}} -{{$hideTreeSidebar := not $showTreeSidebar}} +{{$showSidebar := and (not .TreeNames) (not .HideRepoInfo) (not .IsBlame)}}
{{template "repo/header" .}} -
+
{{template "base/alert" .}} {{if .Repository.IsArchived}} @@ -22,9 +17,9 @@ {{template "repo/code/recently_pushed_new_branches" .}} -
- {{if $hasTreeSidebar}} -
{{template "repo/view_file_tree_sidebar" .}}
+
+ {{if .TreeNames}} +
{{template "repo/view_file_tree_sidebar" .}}
{{end}}
diff --git a/templates/repo/home_content.tmpl b/templates/repo/home_content.tmpl index 881a9de1a4d66..291ceb50cdca7 100644 --- a/templates/repo/home_content.tmpl +++ b/templates/repo/home_content.tmpl @@ -1,14 +1,11 @@ -{{$treeNamesLen := len .TreeNames}} -{{$isTreePathRoot := eq $treeNamesLen 0}} +{{$isTreePathRoot := not .TreeNames}} {{$showSidebar := and $isTreePathRoot (not .HideRepoInfo) (not .IsBlame)}} -{{$hasTreeSidebar := not $isTreePathRoot}} -{{$showTreeSidebar := .RepoPreferences.ShowFileViewTreeSidebar}} {{template "repo/sub_menu" .}}
- {{if $hasTreeSidebar}} - {{end}} @@ -58,7 +55,7 @@ {{end}} {{if not $isTreePathRoot}} - {{$treeNameIdxLast := Eval $treeNamesLen "-" 1}} + {{$treeNameIdxLast := Eval (len .TreeNames) "-" 1}} {{StringUtils.EllipsisString .Repository.Name 30}} {{- range $i, $v := .TreeNames -}} From bb8297919178719c4085e0e9285727c41e4a2576 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 14 Jan 2025 21:57:42 -0800 Subject: [PATCH 58/92] remove duplicated code --- routers/web/repo/blame.go | 13 +------------ routers/web/repo/view_home.go | 28 ++++++++++++++++------------ 2 files changed, 17 insertions(+), 24 deletions(-) diff --git a/routers/web/repo/blame.go b/routers/web/repo/blame.go index 31a6a577728a7..808d2cb052c6b 100644 --- a/routers/web/repo/blame.go +++ b/routers/web/repo/blame.go @@ -46,18 +46,7 @@ func RefBlame(ctx *context.Context) { return } - showFileViewTreeSidebar := true - if ctx.Doer != nil { - v, err := user_model.GetUserSetting(ctx, ctx.Doer.ID, user_model.SettingsKeyShowFileViewTreeSidebar, "true") - if err != nil { - log.Error("GetUserSetting: %v", err) - } else { - showFileViewTreeSidebar, _ = strconv.ParseBool(v) - } - } - ctx.Data["RepoPreferences"] = &preferencesForm{ - ShowFileViewTreeSidebar: showFileViewTreeSidebar, - } + prepareHomeTreeSideBarSwitch(ctx) branchLink := ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL() treeLink := branchLink diff --git a/routers/web/repo/view_home.go b/routers/web/repo/view_home.go index 0c5a20eff7d42..39da84b1e9dfc 100644 --- a/routers/web/repo/view_home.go +++ b/routers/web/repo/view_home.go @@ -312,6 +312,21 @@ func handleRepoHomeFeed(ctx *context.Context) bool { return false } +func prepareHomeTreeSideBarSwitch(ctx *context.Context) { + showFileViewTreeSidebar := true + if ctx.Doer != nil { + v, err := user_model.GetUserSetting(ctx, ctx.Doer.ID, user_model.SettingsKeyShowFileViewTreeSidebar, "true") + if err != nil { + log.Error("GetUserSetting: %v", err) + } else { + showFileViewTreeSidebar, _ = strconv.ParseBool(v) + } + } + ctx.Data["RepoPreferences"] = &preferencesForm{ + ShowFileViewTreeSidebar: showFileViewTreeSidebar, + } +} + // Home render repository home page func Home(ctx *context.Context) { if handleRepoHomeFeed(ctx) { @@ -325,18 +340,7 @@ func Home(ctx *context.Context) { return } - showFileViewTreeSidebar := true - if ctx.Doer != nil { - v, err := user_model.GetUserSetting(ctx, ctx.Doer.ID, user_model.SettingsKeyShowFileViewTreeSidebar, "true") - if err != nil { - log.Error("GetUserSetting: %v", err) - } else { - showFileViewTreeSidebar, _ = strconv.ParseBool(v) - } - } - ctx.Data["RepoPreferences"] = &preferencesForm{ - ShowFileViewTreeSidebar: showFileViewTreeSidebar, - } + prepareHomeTreeSideBarSwitch(ctx) title := ctx.Repo.Repository.Owner.Name + "/" + ctx.Repo.Repository.Name if len(ctx.Repo.Repository.Description) > 0 { From 717991640291fd9ea151eb7f14e35ad252f1fd7c Mon Sep 17 00:00:00 2001 From: Kerwin Bryant Date: Thu, 16 Jan 2025 01:22:25 +0000 Subject: [PATCH 59/92] fix --- web_src/css/repo/home.css | 3 +++ web_src/js/features/repo-view-file-tree-sidebar.ts | 2 ++ 2 files changed, 5 insertions(+) diff --git a/web_src/css/repo/home.css b/web_src/css/repo/home.css index db171e7974b29..d8ca1339f98c7 100644 --- a/web_src/css/repo/home.css +++ b/web_src/css/repo/home.css @@ -73,6 +73,9 @@ gap: 8px; max-height: 100vh; overflow: hidden; + position: sticky; + top: 14px; + z-index: 8; } .repo-grid-tree-sidebar .view-file-tree-sidebar-top { diff --git a/web_src/js/features/repo-view-file-tree-sidebar.ts b/web_src/js/features/repo-view-file-tree-sidebar.ts index 8bec3d06ae255..22f7978f3053d 100644 --- a/web_src/js/features/repo-view-file-tree-sidebar.ts +++ b/web_src/js/features/repo-view-file-tree-sidebar.ts @@ -2,6 +2,7 @@ import {createApp, ref} from 'vue'; import {toggleElem} from '../utils/dom.ts'; import {GET, PUT} from '../modules/fetch.ts'; import ViewFileTree from '../components/ViewFileTree.vue'; +import {initMarkupContent} from '../markup/content.ts'; import {initTargetRepoBranchTagSelector} from './repo-legacy.ts'; import {initTargetDropdown} from './common-page.ts'; import {initTargetRepoEllipsisButton} from './repo-commit.ts'; @@ -58,6 +59,7 @@ function reloadContentScript(contentEl: Element) { contentEl.querySelector('.show-tree-sidebar-button').addEventListener('click', () => { toggleSidebar(true, document.querySelector('.repo-view-file-tree-sidebar').hasAttribute('data-is-signed')); }); + initMarkupContent(); initTargetButtons(contentEl); initTargetDropdown(contentEl); initTargetPdfViewer(contentEl); From a0f69ec1a1f7bf0c2e1ccaad1dd556d6ede04717 Mon Sep 17 00:00:00 2001 From: Kerwin Bryant Date: Thu, 20 Feb 2025 06:13:11 +0000 Subject: [PATCH 60/92] fix --- routers/web/repo/tree.go | 3 ++- web_src/js/components/ViewFileTreeItem.vue | 6 +++--- .../js/features/repo-view-file-tree-sidebar.ts | 16 ++++++++-------- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/routers/web/repo/tree.go b/routers/web/repo/tree.go index 78aa5537f61db..b4d214e4d050b 100644 --- a/routers/web/repo/tree.go +++ b/routers/web/repo/tree.go @@ -4,6 +4,7 @@ package repo import ( + "errors" "net/http" "code.gitea.io/gitea/modules/base" @@ -58,7 +59,7 @@ func Tree(ctx *context.Context) { recursive := ctx.FormBool("recursive") if ctx.Repo.RefFullName == "" { - ctx.Error(http.StatusBadRequest, "RefFullName", "ref_name is invalid") + ctx.ServerError("RefFullName", errors.New("ref_name is invalid")) return } diff --git a/web_src/js/components/ViewFileTreeItem.vue b/web_src/js/components/ViewFileTreeItem.vue index cbfe68ac8e573..9240ba45f5def 100644 --- a/web_src/js/components/ViewFileTreeItem.vue +++ b/web_src/js/components/ViewFileTreeItem.vue @@ -25,7 +25,7 @@ const doLoadChildren = async () => { collapsed.value = !collapsed.value; if (!collapsed.value && props.loadChildren) { isLoading.value = true; - const _children = await props.loadChildren(props.item); + const _children = await props.loadChildren(props.item.path); children.value = _children; isLoading.value = false; } @@ -33,11 +33,11 @@ const doLoadChildren = async () => { const doLoadDirContent = () => { doLoadChildren(); - props.loadContent(props.item); + props.loadContent(props.item.path); }; const doLoadFileContent = () => { - props.loadContent(props.item); + props.loadContent(props.item.path); }; const doGotoSubModule = () => { diff --git a/web_src/js/features/repo-view-file-tree-sidebar.ts b/web_src/js/features/repo-view-file-tree-sidebar.ts index 22f7978f3053d..d7fdb3f2d2a57 100644 --- a/web_src/js/features/repo-view-file-tree-sidebar.ts +++ b/web_src/js/features/repo-view-file-tree-sidebar.ts @@ -10,7 +10,7 @@ import {initTargetPdfViewer} from '../render/pdf.ts'; import {initTargetButtons} from './common-button.ts'; import {initTargetCopyContent} from './copycontent.ts'; -async function toggleSidebar(visibility, isSigned) { +async function toggleSidebar(visibility: boolean, isSigned: boolean) { const sidebarEl = document.querySelector('.repo-view-file-tree-sidebar'); const showBtnEl = document.querySelector('.show-tree-sidebar-button'); const containerClassList = sidebarEl.parentElement.classList; @@ -29,11 +29,11 @@ async function toggleSidebar(visibility, isSigned) { }); } -async function loadChildren(item, recursive?: boolean) { +async function loadChildren(path: string, recursive?: boolean) { const fileTree = document.querySelector('#view-file-tree'); const apiBaseUrl = fileTree.getAttribute('data-api-base-url'); const refTypeNameSubURL = fileTree.getAttribute('data-current-ref-type-name-sub-url'); - const response = await GET(`${apiBaseUrl}/tree/${refTypeNameSubURL}/${item ? item.path : ''}?recursive=${recursive ?? false}`); + const response = await GET(`${apiBaseUrl}/tree/${refTypeNameSubURL}/${path ?? ''}?recursive=${recursive ?? false}`); const json = await response.json(); if (json instanceof Array) { return json.map((i) => ({ @@ -90,12 +90,12 @@ export async function initViewFileTreeSidebar() { const selectedItem = ref(treePath); - const files = await loadChildren({path: treePath}, true); + const files = await loadChildren(treePath, true); fileTree.classList.remove('is-loading'); - const fileTreeView = createApp(ViewFileTree, {files, selectedItem, loadChildren, loadContent: (item) => { - window.history.pushState(null, null, `${baseUrl}/src${refString}/${item.path}`); - selectedItem.value = item.path; + const fileTreeView = createApp(ViewFileTree, {files, selectedItem, loadChildren, loadContent: (path: string) => { + window.history.pushState(null, null, `${baseUrl}/src${refString}/${path}`); + selectedItem.value = path; loadContent(); }}); fileTreeView.mount(fileTree); @@ -106,7 +106,7 @@ export async function initViewFileTreeSidebar() { }); } -function extractPath(url) { +function extractPath(url: string) { // Create a URL object const urlObj = new URL(url); From bf83b2a1e51d825fbbf5b251f486303a0f0c8f66 Mon Sep 17 00:00:00 2001 From: Kerwin Bryant Date: Mon, 3 Mar 2025 06:04:34 +0000 Subject: [PATCH 61/92] Revert some changes, and plan to complete the adaptation of the function in a new way of global-*. --- web_src/js/features/common-button.ts | 10 +++----- web_src/js/features/copycontent.ts | 6 +---- web_src/js/features/repo-commit.ts | 6 +---- web_src/js/features/repo-legacy.ts | 14 +++++------ .../features/repo-view-file-tree-sidebar.ts | 24 +++++++++---------- web_src/js/render/pdf.ts | 6 +---- 6 files changed, 25 insertions(+), 41 deletions(-) diff --git a/web_src/js/features/common-button.ts b/web_src/js/features/common-button.ts index 284c590fca62d..7aebdd8dd5be5 100644 --- a/web_src/js/features/common-button.ts +++ b/web_src/js/features/common-button.ts @@ -160,11 +160,7 @@ export function initGlobalButtons(): void { // There are a few cancel buttons in non-modal forms, and there are some dynamically created forms (eg: the "Edit Issue Content") addDelegatedEventListener(document, 'click', 'form button.ui.cancel.button', (_ /* el */, e) => e.preventDefault()); - initTargetButtons(document); -} - -export function initTargetButtons(target: ParentNode): void { - queryElems(target, '.show-panel', (el) => el.addEventListener('click', onShowPanelClick)); - queryElems(target, '.hide-panel', (el) => el.addEventListener('click', onHidePanelClick)); - queryElems(target, '.show-modal', (el) => el.addEventListener('click', onShowModalClick)); + queryElems(document, '.show-panel', (el) => el.addEventListener('click', onShowPanelClick)); + queryElems(document, '.hide-panel', (el) => el.addEventListener('click', onHidePanelClick)); + queryElems(document, '.show-modal', (el) => el.addEventListener('click', onShowModalClick)); } diff --git a/web_src/js/features/copycontent.ts b/web_src/js/features/copycontent.ts index 0bc14c3bff5af..4bc9281a35e4f 100644 --- a/web_src/js/features/copycontent.ts +++ b/web_src/js/features/copycontent.ts @@ -6,11 +6,7 @@ import {GET} from '../modules/fetch.ts'; const {i18n} = window.config; export function initCopyContent() { - initTargetCopyContent(document); -} - -export function initTargetCopyContent(target: ParentNode) { - const btn = target.querySelector('#copy-content'); + const btn = document.querySelector('#copy-content'); if (!btn || btn.classList.contains('disabled')) return; btn.addEventListener('click', async () => { diff --git a/web_src/js/features/repo-commit.ts b/web_src/js/features/repo-commit.ts index f0a2f878d84f6..8994a57f4a8af 100644 --- a/web_src/js/features/repo-commit.ts +++ b/web_src/js/features/repo-commit.ts @@ -2,11 +2,7 @@ import {createTippy} from '../modules/tippy.ts'; import {toggleElem} from '../utils/dom.ts'; export function initRepoEllipsisButton() { - initTargetRepoEllipsisButton(document); -} - -export function initTargetRepoEllipsisButton(target: ParentNode) { - for (const button of target.querySelectorAll('.js-toggle-commit-body')) { + for (const button of document.querySelectorAll('.js-toggle-commit-body')) { button.addEventListener('click', function (e) { e.preventDefault(); const expanded = this.getAttribute('aria-expanded') === 'true'; diff --git a/web_src/js/features/repo-legacy.ts b/web_src/js/features/repo-legacy.ts index 90568edca83e9..a1b53c1f41327 100644 --- a/web_src/js/features/repo-legacy.ts +++ b/web_src/js/features/repo-legacy.ts @@ -20,6 +20,12 @@ import {initRepoNew} from './repo-new.ts'; import {createApp} from 'vue'; import RepoBranchTagSelector from '../components/RepoBranchTagSelector.vue'; +function initRepoBranchTagSelector(selector: string) { + for (const elRoot of document.querySelectorAll(selector)) { + createApp(RepoBranchTagSelector, {elRoot}).mount(elRoot); + } +} + export function initBranchSelectorTabs() { const elSelectBranches = document.querySelectorAll('.ui.dropdown.select-branch'); for (const elSelectBranch of elSelectBranches) { @@ -32,17 +38,11 @@ export function initBranchSelectorTabs() { } } -export function initTargetRepoBranchTagSelector(target: ParentNode, selector: string = '.js-branch-tag-selector') { - for (const elRoot of target.querySelectorAll(selector)) { - createApp(RepoBranchTagSelector, {elRoot}).mount(elRoot); - } -} - export function initRepository() { const pageContent = document.querySelector('.page-content.repository'); if (!pageContent) return; - initTargetRepoBranchTagSelector(document); + initRepoBranchTagSelector('.js-branch-tag-selector'); initRepoCommentFormAndSidebar(); // Labels diff --git a/web_src/js/features/repo-view-file-tree-sidebar.ts b/web_src/js/features/repo-view-file-tree-sidebar.ts index 3f82882390f07..33933563c9246 100644 --- a/web_src/js/features/repo-view-file-tree-sidebar.ts +++ b/web_src/js/features/repo-view-file-tree-sidebar.ts @@ -2,12 +2,12 @@ import {createApp, ref} from 'vue'; import {toggleElem} from '../utils/dom.ts'; import {GET, PUT} from '../modules/fetch.ts'; import ViewFileTree from '../components/ViewFileTree.vue'; -import {initMarkupContent} from '../markup/content.ts'; -import {initTargetRepoBranchTagSelector} from './repo-legacy.ts'; -import {initTargetRepoEllipsisButton} from './repo-commit.ts'; -import {initTargetPdfViewer} from '../render/pdf.ts'; -import {initTargetButtons} from './common-button.ts'; -import {initTargetCopyContent} from './copycontent.ts'; +// import {initMarkupContent} from '../markup/content.ts'; +// import {initTargetRepoBranchTagSelector} from './repo-legacy.ts'; +// import {initTargetRepoEllipsisButton} from './repo-commit.ts'; +// import {initTargetPdfViewer} from '../render/pdf.ts'; +// import {initTargetButtons} from './common-button.ts'; +// import {initTargetCopyContent} from './copycontent.ts'; async function toggleSidebar(visibility: boolean, isSigned: boolean) { const sidebarEl = document.querySelector('.repo-view-file-tree-sidebar'); @@ -58,13 +58,13 @@ function reloadContentScript(contentEl: Element) { contentEl.querySelector('.show-tree-sidebar-button').addEventListener('click', () => { toggleSidebar(true, document.querySelector('.repo-view-file-tree-sidebar').hasAttribute('data-is-signed')); }); - initMarkupContent(); - initTargetButtons(contentEl); + // initMarkupContent(); + // initTargetButtons(contentEl); // initTargetDropdown(contentEl); - initTargetPdfViewer(contentEl); - initTargetRepoBranchTagSelector(contentEl); - initTargetRepoEllipsisButton(contentEl); - initTargetCopyContent(contentEl); + // initTargetPdfViewer(contentEl); + // initTargetRepoBranchTagSelector(contentEl); + // initTargetRepoEllipsisButton(contentEl); + // initTargetCopyContent(contentEl); } export async function initViewFileTreeSidebar() { diff --git a/web_src/js/render/pdf.ts b/web_src/js/render/pdf.ts index 5bed6f7bab842..f31f161e6e8e2 100644 --- a/web_src/js/render/pdf.ts +++ b/web_src/js/render/pdf.ts @@ -1,11 +1,7 @@ import {htmlEscape} from 'escape-goat'; export async function initPdfViewer() { - initTargetPdfViewer(document); -} - -export async function initTargetPdfViewer(target: ParentNode) { - const els = target.querySelectorAll('.pdf-content'); + const els = document.querySelectorAll('.pdf-content'); if (!els.length) return; const pdfobject = await import(/* webpackChunkName: "pdfobject" */'pdfobject'); From 2d5a570faa58e8160513954f3098ef793e3c6212 Mon Sep 17 00:00:00 2001 From: Kerwin Bryant Date: Tue, 4 Mar 2025 00:29:38 +0000 Subject: [PATCH 62/92] fix --- web_src/js/features/repo-view-file-tree-sidebar.ts | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/web_src/js/features/repo-view-file-tree-sidebar.ts b/web_src/js/features/repo-view-file-tree-sidebar.ts index 33933563c9246..858f640f198b9 100644 --- a/web_src/js/features/repo-view-file-tree-sidebar.ts +++ b/web_src/js/features/repo-view-file-tree-sidebar.ts @@ -2,12 +2,6 @@ import {createApp, ref} from 'vue'; import {toggleElem} from '../utils/dom.ts'; import {GET, PUT} from '../modules/fetch.ts'; import ViewFileTree from '../components/ViewFileTree.vue'; -// import {initMarkupContent} from '../markup/content.ts'; -// import {initTargetRepoBranchTagSelector} from './repo-legacy.ts'; -// import {initTargetRepoEllipsisButton} from './repo-commit.ts'; -// import {initTargetPdfViewer} from '../render/pdf.ts'; -// import {initTargetButtons} from './common-button.ts'; -// import {initTargetCopyContent} from './copycontent.ts'; async function toggleSidebar(visibility: boolean, isSigned: boolean) { const sidebarEl = document.querySelector('.repo-view-file-tree-sidebar'); @@ -58,13 +52,6 @@ function reloadContentScript(contentEl: Element) { contentEl.querySelector('.show-tree-sidebar-button').addEventListener('click', () => { toggleSidebar(true, document.querySelector('.repo-view-file-tree-sidebar').hasAttribute('data-is-signed')); }); - // initMarkupContent(); - // initTargetButtons(contentEl); - // initTargetDropdown(contentEl); - // initTargetPdfViewer(contentEl); - // initTargetRepoBranchTagSelector(contentEl); - // initTargetRepoEllipsisButton(contentEl); - // initTargetCopyContent(contentEl); } export async function initViewFileTreeSidebar() { From 9fc2ba3705266ec4909897f77a0808340064efc8 Mon Sep 17 00:00:00 2001 From: Kerwin Bryant Date: Tue, 4 Mar 2025 00:51:31 +0000 Subject: [PATCH 63/92] Refactor initViewFileTreeSidebar using the new registerGlobalInitFunc --- templates/repo/home.tmpl | 2 +- .../features/repo-view-file-tree-sidebar.ts | 81 +++++++++---------- 2 files changed, 40 insertions(+), 43 deletions(-) diff --git a/templates/repo/home.tmpl b/templates/repo/home.tmpl index 290e80064c574..7e16dd6549246 100644 --- a/templates/repo/home.tmpl +++ b/templates/repo/home.tmpl @@ -19,7 +19,7 @@
{{if .TreeNames}} -
{{template "repo/view_file_tree_sidebar" .}}
+
{{template "repo/view_file_tree_sidebar" .}}
{{end}}
diff --git a/web_src/js/features/repo-view-file-tree-sidebar.ts b/web_src/js/features/repo-view-file-tree-sidebar.ts index 858f640f198b9..a730cae3e9c35 100644 --- a/web_src/js/features/repo-view-file-tree-sidebar.ts +++ b/web_src/js/features/repo-view-file-tree-sidebar.ts @@ -1,10 +1,10 @@ import {createApp, ref} from 'vue'; import {toggleElem} from '../utils/dom.ts'; import {GET, PUT} from '../modules/fetch.ts'; +import {registerGlobalInitFunc} from '../modules/observer.ts'; import ViewFileTree from '../components/ViewFileTree.vue'; -async function toggleSidebar(visibility: boolean, isSigned: boolean) { - const sidebarEl = document.querySelector('.repo-view-file-tree-sidebar'); +async function toggleSidebar(sidebarEl: HTMLElement, visibility: boolean) { const showBtnEl = document.querySelector('.show-tree-sidebar-button'); const containerClassList = sidebarEl.parentElement.classList; containerClassList.toggle('repo-grid-tree-sidebar', visibility); @@ -12,7 +12,7 @@ async function toggleSidebar(visibility: boolean, isSigned: boolean) { toggleElem(sidebarEl, visibility); toggleElem(showBtnEl, !visibility); - if (!isSigned) return; + if (!sidebarEl.hasAttribute('data-is-signed')) return; // save to session await PUT('/repo/preferences', { @@ -40,55 +40,52 @@ async function loadChildren(path: string, recursive?: boolean) { return null; } -async function loadContent() { +async function loadContent(sidebarEl: HTMLElement) { // load content by path (content based on home_content.tmpl) const response = await GET(`${window.location.href}?only_content=true`); const contentEl = document.querySelector('.repo-home-filelist'); contentEl.innerHTML = await response.text(); - reloadContentScript(contentEl); + reloadContentScript(sidebarEl, contentEl); } -function reloadContentScript(contentEl: Element) { +function reloadContentScript(sidebarEl: HTMLElement, contentEl: Element) { contentEl.querySelector('.show-tree-sidebar-button').addEventListener('click', () => { - toggleSidebar(true, document.querySelector('.repo-view-file-tree-sidebar').hasAttribute('data-is-signed')); + toggleSidebar(sidebarEl, true); }); } -export async function initViewFileTreeSidebar() { - const sidebarElement = document.querySelector('.repo-view-file-tree-sidebar'); - if (!sidebarElement) return; - - const isSigned = sidebarElement.hasAttribute('data-is-signed'); - - document.querySelector('.hide-tree-sidebar-button').addEventListener('click', () => { - toggleSidebar(false, isSigned); - }); - document.querySelector('.repo-home-filelist .show-tree-sidebar-button').addEventListener('click', () => { - toggleSidebar(true, isSigned); - }); - - const fileTree = document.querySelector('#view-file-tree'); - const baseUrl = fileTree.getAttribute('data-api-base-url'); - const treePath = fileTree.getAttribute('data-tree-path'); - const refType = fileTree.getAttribute('data-current-ref-type'); - const refName = fileTree.getAttribute('data-current-ref-short-name'); - const refString = (refType ? (`/${refType}`) : '') + (refName ? (`/${refName}`) : ''); - - const selectedItem = ref(treePath); - - const files = await loadChildren(treePath, true); - - fileTree.classList.remove('is-loading'); - const fileTreeView = createApp(ViewFileTree, {files, selectedItem, loadChildren, loadContent: (path: string) => { - window.history.pushState(null, null, `${baseUrl}/src${refString}/${path}`); - selectedItem.value = path; - loadContent(); - }}); - fileTreeView.mount(fileTree); - - window.addEventListener('popstate', () => { - selectedItem.value = extractPath(window.location.href); - loadContent(); +export function initViewFileTreeSidebar() { + registerGlobalInitFunc('initViewFileTreeSidebar', async (el: HTMLElement) => { + document.querySelector('.hide-tree-sidebar-button').addEventListener('click', () => { + toggleSidebar(el, false); + }); + document.querySelector('.repo-home-filelist .show-tree-sidebar-button').addEventListener('click', () => { + toggleSidebar(el, true); + }); + + const fileTree = document.querySelector('#view-file-tree'); + const baseUrl = fileTree.getAttribute('data-api-base-url'); + const treePath = fileTree.getAttribute('data-tree-path'); + const refType = fileTree.getAttribute('data-current-ref-type'); + const refName = fileTree.getAttribute('data-current-ref-short-name'); + const refString = (refType ? (`/${refType}`) : '') + (refName ? (`/${refName}`) : ''); + + const selectedItem = ref(treePath); + + const files = await loadChildren(treePath, true); + + fileTree.classList.remove('is-loading'); + const fileTreeView = createApp(ViewFileTree, {files, selectedItem, loadChildren, loadContent: (path: string) => { + window.history.pushState(null, null, `${baseUrl}/src${refString}/${path}`); + selectedItem.value = path; + loadContent(el); + }}); + fileTreeView.mount(fileTree); + + window.addEventListener('popstate', () => { + selectedItem.value = extractPath(window.location.href); + loadContent(el); + }); }); } From a212ff6bb01b9f4049a0e76a029f315c89c0b3af Mon Sep 17 00:00:00 2001 From: Kerwin Bryant Date: Tue, 4 Mar 2025 07:11:45 +0000 Subject: [PATCH 64/92] fix --- .../features/repo-view-file-tree-sidebar.ts | 33 +++++-------------- 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/web_src/js/features/repo-view-file-tree-sidebar.ts b/web_src/js/features/repo-view-file-tree-sidebar.ts index a730cae3e9c35..a31c835a43dba 100644 --- a/web_src/js/features/repo-view-file-tree-sidebar.ts +++ b/web_src/js/features/repo-view-file-tree-sidebar.ts @@ -26,7 +26,7 @@ async function loadChildren(path: string, recursive?: boolean) { const fileTree = document.querySelector('#view-file-tree'); const apiBaseUrl = fileTree.getAttribute('data-api-base-url'); const refTypeNameSubURL = fileTree.getAttribute('data-current-ref-type-name-sub-url'); - const response = await GET(`${apiBaseUrl}/tree/${refTypeNameSubURL}/${path ?? ''}?recursive=${recursive ?? false}`); + const response = await GET(`${apiBaseUrl}/tree/${refTypeNameSubURL}/${encodeURIComponent(path ?? '')}?recursive=${recursive ?? false}`); const json = await response.json(); if (json instanceof Array) { return json.map((i) => ({ @@ -49,7 +49,7 @@ async function loadContent(sidebarEl: HTMLElement) { } function reloadContentScript(sidebarEl: HTMLElement, contentEl: Element) { - contentEl.querySelector('.show-tree-sidebar-button').addEventListener('click', () => { + contentEl.querySelector('.show-tree-sidebar-button')?.addEventListener('click', () => { toggleSidebar(sidebarEl, true); }); } @@ -70,41 +70,26 @@ export function initViewFileTreeSidebar() { const refName = fileTree.getAttribute('data-current-ref-short-name'); const refString = (refType ? (`/${refType}`) : '') + (refName ? (`/${refName}`) : ''); - const selectedItem = ref(treePath); + const selectedItem = ref(getSelectedPath(refString)); const files = await loadChildren(treePath, true); fileTree.classList.remove('is-loading'); const fileTreeView = createApp(ViewFileTree, {files, selectedItem, loadChildren, loadContent: (path: string) => { - window.history.pushState(null, null, `${baseUrl}/src${refString}/${path}`); - selectedItem.value = path; + selectedItem.value = getSelectedPath(refString, `${baseUrl}/src${refString}/${path}`); + window.history.pushState(null, null, `${baseUrl}/src${refString}/${encodeURIComponent(path)}`); loadContent(el); }}); fileTreeView.mount(fileTree); window.addEventListener('popstate', () => { - selectedItem.value = extractPath(window.location.href); + selectedItem.value = getSelectedPath(refString); loadContent(el); }); }); } -function extractPath(url: string) { - // Create a URL object - const urlObj = new URL(url); - - // Get the pathname part - const path = urlObj.pathname; - - // Define a regular expression to match "/{param1}/{param2}/src/{branch}/{main}/" - const regex = /^\/[^/]+\/[^/]+\/src\/[^/]+\/[^/]+\//; - - // Use RegExp#exec() method to match the path - const match = regex.exec(path); - if (match) { - return path.substring(match[0].length); - } - - // If the path does not match, return the original path - return path; +function getSelectedPath(ref: string, url?: string) { + const path = url ?? (new URL(window.location.href).pathname); + return path.substring(path.indexOf(ref) + ref.length + 1); } From 605c5d4a072840fdf8f1a78f9a476cd1ec7d5fa3 Mon Sep 17 00:00:00 2001 From: Kerwin Bryant Date: Tue, 4 Mar 2025 07:25:43 +0000 Subject: [PATCH 65/92] fix --- web_src/js/components/ViewFileTreeItem.vue | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/web_src/js/components/ViewFileTreeItem.vue b/web_src/js/components/ViewFileTreeItem.vue index 9240ba45f5def..ec2660c8acad1 100644 --- a/web_src/js/components/ViewFileTreeItem.vue +++ b/web_src/js/components/ViewFileTreeItem.vue @@ -25,9 +25,12 @@ const doLoadChildren = async () => { collapsed.value = !collapsed.value; if (!collapsed.value && props.loadChildren) { isLoading.value = true; - const _children = await props.loadChildren(props.item.path); - children.value = _children; - isLoading.value = false; + try { + const _children = await props.loadChildren(props.item.path); + children.value = _children; + } finally { + isLoading.value = false; + } } }; From 3cbcd95e6a5f6067287f7866ee5dbd760470b611 Mon Sep 17 00:00:00 2001 From: Kerwin Bryant Date: Tue, 4 Mar 2025 08:59:50 +0000 Subject: [PATCH 66/92] fix --- .../features/repo-view-file-tree-sidebar.ts | 48 ++++++++++--------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/web_src/js/features/repo-view-file-tree-sidebar.ts b/web_src/js/features/repo-view-file-tree-sidebar.ts index a31c835a43dba..e57cd4d7a6141 100644 --- a/web_src/js/features/repo-view-file-tree-sidebar.ts +++ b/web_src/js/features/repo-view-file-tree-sidebar.ts @@ -5,7 +5,7 @@ import {registerGlobalInitFunc} from '../modules/observer.ts'; import ViewFileTree from '../components/ViewFileTree.vue'; async function toggleSidebar(sidebarEl: HTMLElement, visibility: boolean) { - const showBtnEl = document.querySelector('.show-tree-sidebar-button'); + const showBtnEl = sidebarEl.parentElement.querySelector('.show-tree-sidebar-button'); const containerClassList = sidebarEl.parentElement.classList; containerClassList.toggle('repo-grid-tree-sidebar', visibility); containerClassList.toggle('repo-grid-filelist-only', !visibility); @@ -22,28 +22,30 @@ async function toggleSidebar(sidebarEl: HTMLElement, visibility: boolean) { }); } -async function loadChildren(path: string, recursive?: boolean) { - const fileTree = document.querySelector('#view-file-tree'); - const apiBaseUrl = fileTree.getAttribute('data-api-base-url'); - const refTypeNameSubURL = fileTree.getAttribute('data-current-ref-type-name-sub-url'); - const response = await GET(`${apiBaseUrl}/tree/${refTypeNameSubURL}/${encodeURIComponent(path ?? '')}?recursive=${recursive ?? false}`); - const json = await response.json(); - if (json instanceof Array) { - return json.map((i) => ({ - name: i.name, - type: i.type, - path: i.path, - sub_module_url: i.sub_module_url, - children: i.children, - })); - } - return null; +function childrenLoader(sidebarEl: HTMLElement) { + return async (path: string, recursive?: boolean) => { + const fileTree = sidebarEl.querySelector('#view-file-tree'); + const apiBaseUrl = fileTree.getAttribute('data-api-base-url'); + const refTypeNameSubURL = fileTree.getAttribute('data-current-ref-type-name-sub-url'); + const response = await GET(`${apiBaseUrl}/tree/${refTypeNameSubURL}/${encodeURIComponent(path ?? '')}?recursive=${recursive ?? false}`); + const json = await response.json(); + if (json instanceof Array) { + return json.map((i) => ({ + name: i.name, + type: i.type, + path: i.path, + sub_module_url: i.sub_module_url, + children: i.children, + })); + } + return null; + }; } async function loadContent(sidebarEl: HTMLElement) { // load content by path (content based on home_content.tmpl) const response = await GET(`${window.location.href}?only_content=true`); - const contentEl = document.querySelector('.repo-home-filelist'); + const contentEl = sidebarEl.parentElement.querySelector('.repo-home-filelist'); contentEl.innerHTML = await response.text(); reloadContentScript(sidebarEl, contentEl); } @@ -56,14 +58,14 @@ function reloadContentScript(sidebarEl: HTMLElement, contentEl: Element) { export function initViewFileTreeSidebar() { registerGlobalInitFunc('initViewFileTreeSidebar', async (el: HTMLElement) => { - document.querySelector('.hide-tree-sidebar-button').addEventListener('click', () => { + el.querySelector('.hide-tree-sidebar-button').addEventListener('click', () => { toggleSidebar(el, false); }); - document.querySelector('.repo-home-filelist .show-tree-sidebar-button').addEventListener('click', () => { + el.parentElement.querySelector('.repo-home-filelist .show-tree-sidebar-button').addEventListener('click', () => { toggleSidebar(el, true); }); - const fileTree = document.querySelector('#view-file-tree'); + const fileTree = el.querySelector('#view-file-tree'); const baseUrl = fileTree.getAttribute('data-api-base-url'); const treePath = fileTree.getAttribute('data-tree-path'); const refType = fileTree.getAttribute('data-current-ref-type'); @@ -72,10 +74,10 @@ export function initViewFileTreeSidebar() { const selectedItem = ref(getSelectedPath(refString)); - const files = await loadChildren(treePath, true); + const files = await childrenLoader(el)(treePath, true); fileTree.classList.remove('is-loading'); - const fileTreeView = createApp(ViewFileTree, {files, selectedItem, loadChildren, loadContent: (path: string) => { + const fileTreeView = createApp(ViewFileTree, {files, selectedItem, loadChildren: childrenLoader(el), loadContent: (path: string) => { selectedItem.value = getSelectedPath(refString, `${baseUrl}/src${refString}/${path}`); window.history.pushState(null, null, `${baseUrl}/src${refString}/${encodeURIComponent(path)}`); loadContent(el); From 6f2de3ea5e30522a5a24ed3ac0e8b6ea7c6a01f6 Mon Sep 17 00:00:00 2001 From: Kerwin Bryant Date: Tue, 4 Mar 2025 11:21:39 +0000 Subject: [PATCH 67/92] fix --- web_src/js/features/repo-view-file-tree-sidebar.ts | 11 +++++++---- web_src/js/globals.d.ts | 1 + 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/web_src/js/features/repo-view-file-tree-sidebar.ts b/web_src/js/features/repo-view-file-tree-sidebar.ts index e57cd4d7a6141..3069584db33cb 100644 --- a/web_src/js/features/repo-view-file-tree-sidebar.ts +++ b/web_src/js/features/repo-view-file-tree-sidebar.ts @@ -84,10 +84,13 @@ export function initViewFileTreeSidebar() { }}); fileTreeView.mount(fileTree); - window.addEventListener('popstate', () => { - selectedItem.value = getSelectedPath(refString); - loadContent(el); - }); + if (!window.popstateListenerForViewFilePageAdded) { + window.addEventListener('popstate', () => { + selectedItem.value = getSelectedPath(refString); + loadContent(el); + }); + window.popstateListenerForViewFilePageAdded = true; + } }); } diff --git a/web_src/js/globals.d.ts b/web_src/js/globals.d.ts index 0c540ac296fc7..e1ccde79380ed 100644 --- a/web_src/js/globals.d.ts +++ b/web_src/js/globals.d.ts @@ -74,5 +74,6 @@ interface Window { turnstile: any, hcaptcha: any, codeEditors: any[], + popstateListenerForViewFilePageAdded: boolean, updateCloneStates: () => void, } From 31835f463806236fca5253a9740fee7702062b49 Mon Sep 17 00:00:00 2001 From: Kerwin Bryant Date: Wed, 5 Mar 2025 02:53:11 +0000 Subject: [PATCH 68/92] fix --- web_src/js/features/repo-view-file-tree-sidebar.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/web_src/js/features/repo-view-file-tree-sidebar.ts b/web_src/js/features/repo-view-file-tree-sidebar.ts index 3069584db33cb..ea14355c60475 100644 --- a/web_src/js/features/repo-view-file-tree-sidebar.ts +++ b/web_src/js/features/repo-view-file-tree-sidebar.ts @@ -27,7 +27,7 @@ function childrenLoader(sidebarEl: HTMLElement) { const fileTree = sidebarEl.querySelector('#view-file-tree'); const apiBaseUrl = fileTree.getAttribute('data-api-base-url'); const refTypeNameSubURL = fileTree.getAttribute('data-current-ref-type-name-sub-url'); - const response = await GET(`${apiBaseUrl}/tree/${refTypeNameSubURL}/${encodeURIComponent(path ?? '')}?recursive=${recursive ?? false}`); + const response = await GET(encodeURI(`${apiBaseUrl}/tree/${refTypeNameSubURL}/${path ?? ''}?recursive=${recursive ?? false}`)); const json = await response.json(); if (json instanceof Array) { return json.map((i) => ({ @@ -78,8 +78,8 @@ export function initViewFileTreeSidebar() { fileTree.classList.remove('is-loading'); const fileTreeView = createApp(ViewFileTree, {files, selectedItem, loadChildren: childrenLoader(el), loadContent: (path: string) => { - selectedItem.value = getSelectedPath(refString, `${baseUrl}/src${refString}/${path}`); - window.history.pushState(null, null, `${baseUrl}/src${refString}/${encodeURIComponent(path)}`); + window.history.pushState(null, null, encodeURI(`${baseUrl}/src${refString}/${path}`)); + selectedItem.value = path; loadContent(el); }}); fileTreeView.mount(fileTree); @@ -94,7 +94,7 @@ export function initViewFileTreeSidebar() { }); } -function getSelectedPath(ref: string, url?: string) { - const path = url ?? (new URL(window.location.href).pathname); +function getSelectedPath(ref: string) { + const path = decodeURI(new URL(window.location.href).pathname); return path.substring(path.indexOf(ref) + ref.length + 1); } From 3bc3cd8ab5d7769aaf74400861f73b90e8f62911 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Wed, 5 Mar 2025 17:04:28 +0800 Subject: [PATCH 69/92] no window var --- web_src/js/features/repo-view-file-tree-sidebar.ts | 5 +++-- web_src/js/globals.d.ts | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/web_src/js/features/repo-view-file-tree-sidebar.ts b/web_src/js/features/repo-view-file-tree-sidebar.ts index ea14355c60475..98ebfee24f446 100644 --- a/web_src/js/features/repo-view-file-tree-sidebar.ts +++ b/web_src/js/features/repo-view-file-tree-sidebar.ts @@ -57,6 +57,7 @@ function reloadContentScript(sidebarEl: HTMLElement, contentEl: Element) { } export function initViewFileTreeSidebar() { + let popstateListenerForViewFilePageAdded = false; registerGlobalInitFunc('initViewFileTreeSidebar', async (el: HTMLElement) => { el.querySelector('.hide-tree-sidebar-button').addEventListener('click', () => { toggleSidebar(el, false); @@ -84,12 +85,12 @@ export function initViewFileTreeSidebar() { }}); fileTreeView.mount(fileTree); - if (!window.popstateListenerForViewFilePageAdded) { + if (!popstateListenerForViewFilePageAdded) { window.addEventListener('popstate', () => { selectedItem.value = getSelectedPath(refString); loadContent(el); }); - window.popstateListenerForViewFilePageAdded = true; + popstateListenerForViewFilePageAdded = true; } }); } diff --git a/web_src/js/globals.d.ts b/web_src/js/globals.d.ts index e1ccde79380ed..0c540ac296fc7 100644 --- a/web_src/js/globals.d.ts +++ b/web_src/js/globals.d.ts @@ -74,6 +74,5 @@ interface Window { turnstile: any, hcaptcha: any, codeEditors: any[], - popstateListenerForViewFilePageAdded: boolean, updateCloneStates: () => void, } From b9eef420e4a1d2e36e9279131706f3b0ef38e5e0 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Thu, 6 Mar 2025 17:41:41 +0800 Subject: [PATCH 70/92] fix --- templates/repo/home_content.tmpl | 2 +- templates/repo/view_file_tree_sidebar.tmpl | 17 +++++++------- web_src/css/repo/home.css | 23 ------------------- .../features/repo-view-file-tree-sidebar.ts | 2 ++ 4 files changed, 11 insertions(+), 33 deletions(-) diff --git a/templates/repo/home_content.tmpl b/templates/repo/home_content.tmpl index 291ceb50cdca7..e2d78274668cc 100644 --- a/templates/repo/home_content.tmpl +++ b/templates/repo/home_content.tmpl @@ -6,7 +6,7 @@
{{if not $isTreePathRoot}} {{end}} {{template "repo/home_branch_dropdown" (dict "ctxData" .)}} diff --git a/templates/repo/view_file_tree_sidebar.tmpl b/templates/repo/view_file_tree_sidebar.tmpl index 48b8edfaa5235..4a5f829e5a7c2 100644 --- a/templates/repo/view_file_tree_sidebar.tmpl +++ b/templates/repo/view_file_tree_sidebar.tmpl @@ -1,13 +1,12 @@ -
- - +
+ + Files
-
+ + +
Date: Thu, 6 Mar 2025 17:56:51 +0800 Subject: [PATCH 71/92] dead code --- templates/repo/home_content.tmpl | 1 - 1 file changed, 1 deletion(-) diff --git a/templates/repo/home_content.tmpl b/templates/repo/home_content.tmpl index e2d78274668cc..5bf3d67d0c3ff 100644 --- a/templates/repo/home_content.tmpl +++ b/templates/repo/home_content.tmpl @@ -1,5 +1,4 @@ {{$isTreePathRoot := not .TreeNames}} -{{$showSidebar := and $isTreePathRoot (not .HideRepoInfo) (not .IsBlame)}} {{template "repo/sub_menu" .}}
From 46ab64ba497ec83bd2fc1d19012f596b87cc0058 Mon Sep 17 00:00:00 2001 From: Kerwin Bryant Date: Mon, 10 Mar 2025 02:14:59 +0000 Subject: [PATCH 72/92] Revert: [Refactor initViewFileTreeSidebar using the new registerGlobalInitFunc] --- templates/repo/home.tmpl | 2 +- .../features/repo-view-file-tree-sidebar.ts | 60 +++++++++---------- 2 files changed, 29 insertions(+), 33 deletions(-) diff --git a/templates/repo/home.tmpl b/templates/repo/home.tmpl index 7e16dd6549246..290e80064c574 100644 --- a/templates/repo/home.tmpl +++ b/templates/repo/home.tmpl @@ -19,7 +19,7 @@
{{if .TreeNames}} -
{{template "repo/view_file_tree_sidebar" .}}
+
{{template "repo/view_file_tree_sidebar" .}}
{{end}}
diff --git a/web_src/js/features/repo-view-file-tree-sidebar.ts b/web_src/js/features/repo-view-file-tree-sidebar.ts index dd1e8d6b4ada7..246a8b40ca10f 100644 --- a/web_src/js/features/repo-view-file-tree-sidebar.ts +++ b/web_src/js/features/repo-view-file-tree-sidebar.ts @@ -1,7 +1,6 @@ import {createApp, ref} from 'vue'; import {toggleElem} from '../utils/dom.ts'; import {GET, PUT} from '../modules/fetch.ts'; -import {registerGlobalInitFunc} from '../modules/observer.ts'; import ViewFileTree from '../components/ViewFileTree.vue'; async function toggleSidebar(sidebarEl: HTMLElement, visibility: boolean) { @@ -58,42 +57,39 @@ function reloadContentScript(sidebarEl: HTMLElement, contentEl: Element) { }); } -export function initViewFileTreeSidebar() { - let popstateListenerForViewFilePageAdded = false; - registerGlobalInitFunc('initViewFileTreeSidebar', async (el: HTMLElement) => { - el.querySelector('.hide-tree-sidebar-button').addEventListener('click', () => { - toggleSidebar(el, false); - }); - el.parentElement.querySelector('.repo-home-filelist .show-tree-sidebar-button').addEventListener('click', () => { - toggleSidebar(el, true); - }); +export async function initViewFileTreeSidebar() { + const sidebarEl = document.querySelector('.repo-view-file-tree-sidebar'); + if (!sidebarEl || !(sidebarEl instanceof HTMLElement)) return; - const fileTree = el.querySelector('#view-file-tree'); - const baseUrl = fileTree.getAttribute('data-api-base-url'); - const treePath = fileTree.getAttribute('data-tree-path'); - const refType = fileTree.getAttribute('data-current-ref-type'); - const refName = fileTree.getAttribute('data-current-ref-short-name'); - const refString = (refType ? (`/${refType}`) : '') + (refName ? (`/${refName}`) : ''); + sidebarEl.querySelector('.hide-tree-sidebar-button').addEventListener('click', () => { + toggleSidebar(sidebarEl, false); + }); + sidebarEl.parentElement.querySelector('.repo-home-filelist .show-tree-sidebar-button').addEventListener('click', () => { + toggleSidebar(sidebarEl, true); + }); - const selectedItem = ref(getSelectedPath(refString)); + const fileTree = sidebarEl.querySelector('#view-file-tree'); + const baseUrl = fileTree.getAttribute('data-api-base-url'); + const treePath = fileTree.getAttribute('data-tree-path'); + const refType = fileTree.getAttribute('data-current-ref-type'); + const refName = fileTree.getAttribute('data-current-ref-short-name'); + const refString = (refType ? (`/${refType}`) : '') + (refName ? (`/${refName}`) : ''); - const files = await childrenLoader(el)(treePath, true); + const selectedItem = ref(getSelectedPath(refString)); - fileTree.classList.remove('is-loading'); - const fileTreeView = createApp(ViewFileTree, {files, selectedItem, loadChildren: childrenLoader(el), loadContent: (path: string) => { - window.history.pushState(null, null, encodeURI(`${baseUrl}/src${refString}/${path}`)); - selectedItem.value = path; - loadContent(el); - }}); - fileTreeView.mount(fileTree); + const files = await childrenLoader(sidebarEl)(treePath, true); - if (!popstateListenerForViewFilePageAdded) { - window.addEventListener('popstate', () => { - selectedItem.value = getSelectedPath(refString); - loadContent(el); - }); - popstateListenerForViewFilePageAdded = true; - } + fileTree.classList.remove('is-loading'); + const fileTreeView = createApp(ViewFileTree, {files, selectedItem, loadChildren: childrenLoader(sidebarEl), loadContent: (path: string) => { + window.history.pushState(null, null, encodeURI(`${baseUrl}/src${refString}/${path}`)); + selectedItem.value = path; + loadContent(sidebarEl); + }}); + fileTreeView.mount(fileTree); + + window.addEventListener('popstate', () => { + selectedItem.value = getSelectedPath(refString); + loadContent(sidebarEl); }); } From 1211fcf75d133f86556360c8aa4989e1c12b9373 Mon Sep 17 00:00:00 2001 From: Kerwin Bryant Date: Mon, 10 Mar 2025 02:40:45 +0000 Subject: [PATCH 73/92] fix --- web_src/js/features/repo-view-file-tree-sidebar.ts | 7 ++++--- web_src/js/utils/url.ts | 4 ++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/web_src/js/features/repo-view-file-tree-sidebar.ts b/web_src/js/features/repo-view-file-tree-sidebar.ts index 246a8b40ca10f..f135b547a57b4 100644 --- a/web_src/js/features/repo-view-file-tree-sidebar.ts +++ b/web_src/js/features/repo-view-file-tree-sidebar.ts @@ -1,5 +1,6 @@ import {createApp, ref} from 'vue'; import {toggleElem} from '../utils/dom.ts'; +import {pathEscapeSegments, pathUnescapeSegments} from '../utils/url.ts'; import {GET, PUT} from '../modules/fetch.ts'; import ViewFileTree from '../components/ViewFileTree.vue'; @@ -28,7 +29,7 @@ function childrenLoader(sidebarEl: HTMLElement) { const fileTree = sidebarEl.querySelector('#view-file-tree'); const apiBaseUrl = fileTree.getAttribute('data-api-base-url'); const refTypeNameSubURL = fileTree.getAttribute('data-current-ref-type-name-sub-url'); - const response = await GET(encodeURI(`${apiBaseUrl}/tree/${refTypeNameSubURL}/${path ?? ''}?recursive=${recursive ?? false}`)); + const response = await GET(`${apiBaseUrl}/tree/${refTypeNameSubURL}/${pathEscapeSegments(path ?? '')}?recursive=${recursive ?? false}`); const json = await response.json(); if (json instanceof Array) { return json.map((i) => ({ @@ -81,7 +82,7 @@ export async function initViewFileTreeSidebar() { fileTree.classList.remove('is-loading'); const fileTreeView = createApp(ViewFileTree, {files, selectedItem, loadChildren: childrenLoader(sidebarEl), loadContent: (path: string) => { - window.history.pushState(null, null, encodeURI(`${baseUrl}/src${refString}/${path}`)); + window.history.pushState(null, null, `${baseUrl}/src${refString}/${pathEscapeSegments(path)}`); selectedItem.value = path; loadContent(sidebarEl); }}); @@ -94,6 +95,6 @@ export async function initViewFileTreeSidebar() { } function getSelectedPath(ref: string) { - const path = decodeURI(new URL(window.location.href).pathname); + const path = pathUnescapeSegments(new URL(window.location.href).pathname); return path.substring(path.indexOf(ref) + ref.length + 1); } diff --git a/web_src/js/utils/url.ts b/web_src/js/utils/url.ts index a7d61c5e837b7..98f51d6adc9da 100644 --- a/web_src/js/utils/url.ts +++ b/web_src/js/utils/url.ts @@ -2,6 +2,10 @@ export function pathEscapeSegments(s: string): string { return s.split('/').map(encodeURIComponent).join('/'); } +export function pathUnescapeSegments(s: string): string { + return s.split('/').map(decodeURIComponent).join('/'); +} + function stripSlash(url: string): string { return url.endsWith('/') ? url.slice(0, -1) : url; } From ce6afd3debe1ac52d8b810dd5329cb9967b57bd6 Mon Sep 17 00:00:00 2001 From: Kerwin Bryant Date: Mon, 10 Mar 2025 02:43:40 +0000 Subject: [PATCH 74/92] fix --- web_src/js/features/repo-view-file-tree-sidebar.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/web_src/js/features/repo-view-file-tree-sidebar.ts b/web_src/js/features/repo-view-file-tree-sidebar.ts index f135b547a57b4..7957266727591 100644 --- a/web_src/js/features/repo-view-file-tree-sidebar.ts +++ b/web_src/js/features/repo-view-file-tree-sidebar.ts @@ -4,13 +4,13 @@ import {pathEscapeSegments, pathUnescapeSegments} from '../utils/url.ts'; import {GET, PUT} from '../modules/fetch.ts'; import ViewFileTree from '../components/ViewFileTree.vue'; -async function toggleSidebar(sidebarEl: HTMLElement, visibility: boolean) { +async function toggleSidebar(sidebarEl: HTMLElement, shouldShow: boolean) { const showBtnEl = sidebarEl.parentElement.querySelector('.show-tree-sidebar-button'); const containerClassList = sidebarEl.parentElement.classList; - containerClassList.toggle('repo-grid-tree-sidebar', visibility); - containerClassList.toggle('repo-grid-filelist-only', !visibility); - toggleElem(sidebarEl, visibility); - toggleElem(showBtnEl, !visibility); + containerClassList.toggle('repo-grid-tree-sidebar', shouldShow); + containerClassList.toggle('repo-grid-filelist-only', !shouldShow); + toggleElem(sidebarEl, shouldShow); + toggleElem(showBtnEl, !shouldShow); // FIXME: need to remove "full height" style from parent element @@ -19,7 +19,7 @@ async function toggleSidebar(sidebarEl: HTMLElement, visibility: boolean) { // save to session await PUT('/repo/preferences', { data: { - show_file_view_tree_sidebar: visibility, + show_file_view_tree_sidebar: shouldShow, }, }); } From 94033723f1bef0668e51e5f4169acbdecb5e9ccf Mon Sep 17 00:00:00 2001 From: Kerwin Bryant Date: Mon, 10 Mar 2025 06:34:17 +0000 Subject: [PATCH 75/92] fix --- web_src/js/features/repo-view-file-tree-sidebar.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/web_src/js/features/repo-view-file-tree-sidebar.ts b/web_src/js/features/repo-view-file-tree-sidebar.ts index 7957266727591..bfa4bfc326975 100644 --- a/web_src/js/features/repo-view-file-tree-sidebar.ts +++ b/web_src/js/features/repo-view-file-tree-sidebar.ts @@ -4,6 +4,8 @@ import {pathEscapeSegments, pathUnescapeSegments} from '../utils/url.ts'; import {GET, PUT} from '../modules/fetch.ts'; import ViewFileTree from '../components/ViewFileTree.vue'; +const {appSubUrl} = window.config; + async function toggleSidebar(sidebarEl: HTMLElement, shouldShow: boolean) { const showBtnEl = sidebarEl.parentElement.querySelector('.show-tree-sidebar-button'); const containerClassList = sidebarEl.parentElement.classList; @@ -17,7 +19,7 @@ async function toggleSidebar(sidebarEl: HTMLElement, shouldShow: boolean) { if (!sidebarEl.hasAttribute('data-is-signed')) return; // save to session - await PUT('/repo/preferences', { + await PUT(`${appSubUrl}/repo/preferences`, { data: { show_file_view_tree_sidebar: shouldShow, }, From 3b41bea9a29bb195f9fdb03ad55b317a6ee45e67 Mon Sep 17 00:00:00 2001 From: Kerwin Bryant Date: Mon, 10 Mar 2025 07:11:12 +0000 Subject: [PATCH 76/92] Split the original repo/home.tmpl into two templates: home and view. --- routers/web/repo/blame.go | 4 +-- routers/web/repo/view.go | 3 ++- routers/web/repo/view_home.go | 4 ++- templates/repo/home.tmpl | 8 ++---- templates/repo/view.tmpl | 27 +++++++++++++++++++ .../{home_content.tmpl => view_content.tmpl} | 0 web_src/css/repo/home.css | 20 ++++++++++---- .../features/repo-view-file-tree-sidebar.ts | 8 +++--- 8 files changed, 55 insertions(+), 19 deletions(-) create mode 100644 templates/repo/view.tmpl rename templates/repo/{home_content.tmpl => view_content.tmpl} (100%) diff --git a/routers/web/repo/blame.go b/routers/web/repo/blame.go index 1c67a4002067c..1d872ba7c6a1b 100644 --- a/routers/web/repo/blame.go +++ b/routers/web/repo/blame.go @@ -95,9 +95,9 @@ func RefBlame(ctx *context.Context) { var tplName templates.TplName if ctx.FormBool("only_content") { - tplName = tplRepoHomeContent + tplName = tplRepoViewContent } else { - tplName = tplRepoHome + tplName = tplRepoView } if fileSize >= setting.UI.MaxDisplayFileSize { diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index 4d172c9c2046f..6ed5801d10fa7 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -49,7 +49,8 @@ import ( const ( tplRepoEMPTY templates.TplName = "repo/empty" tplRepoHome templates.TplName = "repo/home" - tplRepoHomeContent templates.TplName = "repo/home_content" + tplRepoView templates.TplName = "repo/view" + tplRepoViewContent templates.TplName = "repo/view_content" tplRepoViewList templates.TplName = "repo/view_list" tplWatchers templates.TplName = "repo/watchers" tplForks templates.TplName = "repo/forks" diff --git a/routers/web/repo/view_home.go b/routers/web/repo/view_home.go index e8dba14a64761..ba52a871389d3 100644 --- a/routers/web/repo/view_home.go +++ b/routers/web/repo/view_home.go @@ -430,7 +430,9 @@ func Home(ctx *context.Context) { } if ctx.FormBool("only_content") { - ctx.HTML(http.StatusOK, tplRepoHomeContent) + ctx.HTML(http.StatusOK, tplRepoViewContent) + } else if len(treeNames) != 0 { + ctx.HTML(http.StatusOK, tplRepoView) } else { ctx.HTML(http.StatusOK, tplRepoHome) } diff --git a/templates/repo/home.tmpl b/templates/repo/home.tmpl index 290e80064c574..f86b90502df28 100644 --- a/templates/repo/home.tmpl +++ b/templates/repo/home.tmpl @@ -17,13 +17,9 @@ {{template "repo/code/recently_pushed_new_branches" .}} -
- {{if .TreeNames}} -
{{template "repo/view_file_tree_sidebar" .}}
- {{end}} - +
- {{template "repo/home_content" .}} + {{template "repo/view_content" .}}
{{if $showSidebar}} diff --git a/templates/repo/view.tmpl b/templates/repo/view.tmpl new file mode 100644 index 0000000000000..f28dd30f54aaf --- /dev/null +++ b/templates/repo/view.tmpl @@ -0,0 +1,27 @@ +{{template "base/head" .}} +
+ {{template "repo/header" .}} +
+ {{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_sidebar" .}}
+
+ {{template "repo/view_content" .}} +
+
+
+
+{{template "base/footer" .}} diff --git a/templates/repo/home_content.tmpl b/templates/repo/view_content.tmpl similarity index 100% rename from templates/repo/home_content.tmpl rename to templates/repo/view_content.tmpl diff --git a/web_src/css/repo/home.css b/web_src/css/repo/home.css index 11fc7a263c399..f81dba8b81d34 100644 --- a/web_src/css/repo/home.css +++ b/web_src/css/repo/home.css @@ -49,13 +49,19 @@ } } -.repo-grid-tree-sidebar { +.repo-view-with-sidebar { display: grid; grid-template-columns: 300px auto; grid-template-rows: auto auto 1fr; } -.repo-grid-tree-sidebar .repo-home-filelist { +.repo-view-content { + min-width: 0; + grid-column: 1; + grid-row: 1 / 4; +} + +.repo-view-with-sidebar .repo-view-content { min-width: 0; grid-column: 2; grid-row: 1 / 4; @@ -66,7 +72,7 @@ aspect-ratio: 5.415; /* the size is about 790 x 145 */ } -.repo-grid-tree-sidebar .repo-view-file-tree-sidebar { +.repo-view-with-sidebar .repo-view-file-tree-sidebar { display: flex; flex-direction: column; gap: 8px; @@ -77,15 +83,19 @@ z-index: 8; } -.repo-grid-tree-sidebar .repo-button-row { +.repo-view-with-sidebar .repo-button-row { margin-top: 0 !important; } @media (max-width: 767.98px) { - .repo-grid-tree-sidebar { + .repo-view-with-sidebar { grid-template-columns: auto; grid-template-rows: auto auto auto; } + .repo-view-content { + grid-column: 1; + grid-row: 2; + } } .language-stats { diff --git a/web_src/js/features/repo-view-file-tree-sidebar.ts b/web_src/js/features/repo-view-file-tree-sidebar.ts index bfa4bfc326975..7c1bb4a316aab 100644 --- a/web_src/js/features/repo-view-file-tree-sidebar.ts +++ b/web_src/js/features/repo-view-file-tree-sidebar.ts @@ -9,8 +9,8 @@ const {appSubUrl} = window.config; async function toggleSidebar(sidebarEl: HTMLElement, shouldShow: boolean) { const showBtnEl = sidebarEl.parentElement.querySelector('.show-tree-sidebar-button'); const containerClassList = sidebarEl.parentElement.classList; - containerClassList.toggle('repo-grid-tree-sidebar', shouldShow); - containerClassList.toggle('repo-grid-filelist-only', !shouldShow); + containerClassList.toggle('repo-view-with-sidebar', shouldShow); + containerClassList.toggle('repo-view-content-only', !shouldShow); toggleElem(sidebarEl, shouldShow); toggleElem(showBtnEl, !shouldShow); @@ -49,7 +49,7 @@ function childrenLoader(sidebarEl: HTMLElement) { async function loadContent(sidebarEl: HTMLElement) { // load content by path (content based on home_content.tmpl) const response = await GET(`${window.location.href}?only_content=true`); - const contentEl = sidebarEl.parentElement.querySelector('.repo-home-filelist'); + const contentEl = sidebarEl.parentElement.querySelector('.repo-view-content'); contentEl.innerHTML = await response.text(); reloadContentScript(sidebarEl, contentEl); } @@ -67,7 +67,7 @@ export async function initViewFileTreeSidebar() { sidebarEl.querySelector('.hide-tree-sidebar-button').addEventListener('click', () => { toggleSidebar(sidebarEl, false); }); - sidebarEl.parentElement.querySelector('.repo-home-filelist .show-tree-sidebar-button').addEventListener('click', () => { + sidebarEl.parentElement.querySelector('.repo-view-content .show-tree-sidebar-button').addEventListener('click', () => { toggleSidebar(sidebarEl, true); }); From e7613605552b499dc2ed2a0476a54038cdffa5c5 Mon Sep 17 00:00:00 2001 From: Kerwin Bryant Date: Mon, 10 Mar 2025 07:34:15 +0000 Subject: [PATCH 77/92] Change the layout of the view page from CSS Grid to Flexbox --- templates/repo/view.tmpl | 2 +- web_src/css/repo/home.css | 28 +++++++++------------------- 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/templates/repo/view.tmpl b/templates/repo/view.tmpl index f28dd30f54aaf..6073b91914aef 100644 --- a/templates/repo/view.tmpl +++ b/templates/repo/view.tmpl @@ -16,7 +16,7 @@ {{template "repo/code/recently_pushed_new_branches" .}} -
+
{{template "repo/view_file_tree_sidebar" .}}
{{template "repo/view_content" .}} diff --git a/web_src/css/repo/home.css b/web_src/css/repo/home.css index f81dba8b81d34..222a5082f72a2 100644 --- a/web_src/css/repo/home.css +++ b/web_src/css/repo/home.css @@ -49,23 +49,14 @@ } } -.repo-view-with-sidebar { - display: grid; - grid-template-columns: 300px auto; - grid-template-rows: auto auto 1fr; +.repo-view-container { + display: flex; + flex-wrap: wrap; + gap: var(--page-spacing); } .repo-view-content { - min-width: 0; - grid-column: 1; - grid-row: 1 / 4; -} - -.repo-view-with-sidebar .repo-view-content { - min-width: 0; - grid-column: 2; - grid-row: 1 / 4; - margin-left: 1rem; + flex: 1; } #view-file-tree.is-loading { @@ -73,6 +64,7 @@ } .repo-view-with-sidebar .repo-view-file-tree-sidebar { + flex: 0 1 15%; display: flex; flex-direction: column; gap: 8px; @@ -83,18 +75,16 @@ z-index: 8; } -.repo-view-with-sidebar .repo-button-row { +.repo-view-container .repo-button-row { margin-top: 0 !important; } @media (max-width: 767.98px) { .repo-view-with-sidebar { - grid-template-columns: auto; - grid-template-rows: auto auto auto; + flex: 1 1 0; } .repo-view-content { - grid-column: 1; - grid-row: 2; + flex: 1 1 0; } } From daa8282d4ae22989c6a91e6fe55cc913a089a6e9 Mon Sep 17 00:00:00 2001 From: Kerwin Bryant Date: Tue, 11 Mar 2025 01:30:37 +0000 Subject: [PATCH 78/92] fix --- services/repository/files/tree.go | 157 ++++++++++++++++-------------- 1 file changed, 82 insertions(+), 75 deletions(-) diff --git a/services/repository/files/tree.go b/services/repository/files/tree.go index a3e8af44e7d74..cfbc68a9f677a 100644 --- a/services/repository/files/tree.go +++ b/services/repository/files/tree.go @@ -189,26 +189,27 @@ func sortTreeViewNodes(nodes []*TreeViewNode) { /* Example 1: (path: /) - GET /repo/name/tree/ - - resp: - [{ - "name": "d1", - "isFile": false, - "path": "d1" - },{ - "name": "d2", - "isFile": false, - "path": "d2" - },{ - "name": "d3", - "isFile": false, - "path": "d3" - },{ - "name": "f1", - "isFile": true, - "path": "f1" - },] + GET /repo/name/tree/ + + resp: + [{ + "name": "d1", + "type": "commit", + "path": "d1", + "sub_module_url": "https://gitea.com/gitea/awesome-gitea/tree/887fe27678dced0bd682923b30b2d979575d35d6" + },{ + "name": "d2", + "type": "symlink", + "path": "d2" + },{ + "name": "d3", + "type": "tree", + "path": "d3" + },{ + "name": "f1", + "type": "blob", + "path": "f1" + },] Example 2: (path: d3) @@ -216,7 +217,7 @@ Example 2: (path: d3) resp: [{ "name": "d3d1", - "isFile": false, + "type": "tree", "path": "d3/d3d1" }] @@ -226,11 +227,11 @@ Example 3: (path: d3/d3d1) resp: [{ "name": "d3d1f1", - "isFile": true, + "type": "blob", "path": "d3/d3d1/d3d1f1" },{ - "name": "d3d1f1", - "isFile": true, + "name": "d3d1f2", + "type": "blob", "path": "d3/d3d1/d3d1f2" }] */ @@ -262,7 +263,8 @@ func GetTreeList(ctx context.Context, repo *repo_model.Repository, gitRepo *git. return nil, err } - // If the entry is a file, we return a FileContentResponse object + // If the entry is a file, an exception will be thrown. + // This is because this interface is specifically designed for expanding folders and only supports the retrieval and return of the file list within a folder. if entry.Type() != "tree" { return nil, fmt.Errorf("%s is not a tree", treePath) } @@ -287,7 +289,8 @@ func GetTreeList(ctx context.Context, repo *repo_model.Repository, gitRepo *git. subTreePath := path.Join(treePath, e.Name()) if strings.Contains(e.Name(), "/") { - mapTree[path.Dir(e.Name())] = append(mapTree[path.Dir(e.Name())], &TreeViewNode{ + dirName := path.Dir(e.Name()) + mapTree[dirName] = append(mapTree[dirName], &TreeViewNode{ Name: path.Base(e.Name()), Type: entryModeString(e.Mode()), Path: subTreePath, @@ -320,46 +323,48 @@ Example 1: (path: /) GET /repo/name/tree/?recursive=true resp: [{ - "name": "d1", - "isFile": false, - "path": "d1" - },{ - "name": "d2", - "isFile": false, - "path": "d2" - },{ - "name": "d3", - "isFile": false, - "path": "d3" - },{ - "name": "f1", - "isFile": true, - "path": "f1" - },] + "name": "d1", + "type": "commit", + "path": "d1", + "sub_module_url": "https://gitea.com/gitea/awesome-gitea/tree/887fe27678dced0bd682923b30b2d979575d35d6" + },{ + "name": "d2", + "type": "symlink", + "path": "d2" + },{ + "name": "d3", + "type": "tree", + "path": "d3" + },{ + "name": "f1", + "type": "blob", + "path": "f1" + },] Example 2: (path: d3) GET /repo/name/tree/d3?recursive=true resp: [{ - "name": "d1", - "isFile": false, - "path": "d1" - },{ - "name": "d2", - "isFile": false, - "path": "d2" - },{ + "name": "d1", + "type": "commit", + "path": "d1", + "sub_module_url": "https://gitea.com/gitea/awesome-gitea/tree/887fe27678dced0bd682923b30b2d979575d35d6" + },{ + "name": "d2", + "type": "symlink", + "path": "d2" + },{ "name": "d3", - "isFile": false, + "type": "tree", "path": "d3", "children": [{ "name": "d3d1", - "isFile": false, + "type": "tree", "path": "d3/d3d1" }] },{ "name": "f1", - "isFile": true, + "type": "blob", "path": "f1" },] @@ -367,34 +372,35 @@ Example 3: (path: d3/d3d1) GET /repo/name/tree/d3/d3d1?recursive=true resp: [{ - "name": "d1", - "isFile": false, - "path": "d1" - },{ - "name": "d2", - "isFile": false, - "path": "d2" - },{ + "name": "d1", + "type": "commit", + "path": "d1", + "sub_module_url": "https://gitea.com/gitea/awesome-gitea/tree/887fe27678dced0bd682923b30b2d979575d35d6" + },{ + "name": "d2", + "type": "symlink", + "path": "d2" + },{ "name": "d3", - "isFile": false, + "type": "tree", "path": "d3", "children": [{ "name": "d3d1", - "isFile": false, + "type": "tree", "path": "d3/d3d1", "children": [{ "name": "d3d1f1", - "isFile": true, + "type": "blob", "path": "d3/d3d1/d3d1f1" },{ - "name": "d3d1f1", - "isFile": true, + "name": "d3d1f2", + "type": "blob", "path": "d3/d3d1/d3d1f2" }] }] },{ "name": "f1", - "isFile": true, + "type": "blob", "path": "f1" },] @@ -402,25 +408,26 @@ Example 4: (path: d2/d2f1) GET /repo/name/tree/d2/d2f1?recursive=true resp: [{ - "name": "d1", - "isFile": false, - "path": "d1" - },{ + "name": "d1", + "type": "commit", + "path": "d1", + "sub_module_url": "https://gitea.com/gitea/awesome-gitea/tree/887fe27678dced0bd682923b30b2d979575d35d6" + },{ "name": "d2", - "isFile": false, + "type": "tree", "path": "d2", "children": [{ "name": "d2f1", - "isFile": true, + "type": "blob", "path": "d2/d2f1" }] },{ "name": "d3", - "isFile": false, + "type": "tree", "path": "d3" },{ "name": "f1", - "isFile": true, + "type": "blob", "path": "f1" },] */ From 4eac5029181f9a84a68d4affbc0184b1f08c0074 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 14 Mar 2025 16:44:53 -0700 Subject: [PATCH 79/92] Some improvements --- routers/web/repo/tree.go | 2 +- services/repository/files/tree.go | 14 +++++--------- services/repository/files/tree_test.go | 11 +++-------- 3 files changed, 9 insertions(+), 18 deletions(-) diff --git a/routers/web/repo/tree.go b/routers/web/repo/tree.go index 4b8211c9575d1..b4090667ee449 100644 --- a/routers/web/repo/tree.go +++ b/routers/web/repo/tree.go @@ -98,7 +98,7 @@ func Tree(ctx *context.Context) { var results []*files_service.TreeViewNode var err error if !recursive { - results, err = files_service.GetTreeList(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, ctx.Repo.TreePath, ctx.Repo.RefFullName, false) + results, err = files_service.GetTreeList(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, ctx.Repo.TreePath, ctx.Repo.RefFullName) } else { results, err = files_service.GetTreeInformation(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, ctx.Repo.TreePath, ctx.Repo.RefFullName) } diff --git a/services/repository/files/tree.go b/services/repository/files/tree.go index cfbc68a9f677a..2367f5dc10cdf 100644 --- a/services/repository/files/tree.go +++ b/services/repository/files/tree.go @@ -235,7 +235,7 @@ Example 3: (path: d3/d3d1) "path": "d3/d3d1/d3d1f2" }] */ -func GetTreeList(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, treePath string, ref git.RefName, recursive bool) ([]*TreeViewNode, error) { +func GetTreeList(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, treePath string, ref git.RefName) ([]*TreeViewNode, error) { if repo.IsEmpty { return nil, nil } @@ -244,7 +244,7 @@ func GetTreeList(ctx context.Context, repo *repo_model.Repository, gitRepo *git. } // Check that the path given in opts.treePath is valid (not a git path) - cleanTreePath := CleanUploadFileName(treePath) + cleanTreePath := util.PathJoinRel(treePath) if cleanTreePath == "" && treePath != "" { return nil, ErrFilenameInvalid{ Path: treePath, @@ -273,12 +273,8 @@ func GetTreeList(ctx context.Context, repo *repo_model.Repository, gitRepo *git. if err != nil { return nil, err } - var entries git.Entries - if recursive { - entries, err = gitTree.ListEntriesRecursiveFast() - } else { - entries, err = gitTree.ListEntries() - } + + entries, err := gitTree.ListEntries() if err != nil { return nil, err } @@ -440,7 +436,7 @@ func GetTreeInformation(ctx context.Context, repo *repo_model.Repository, gitRep } // Check that the path given in opts.treePath is valid (not a git path) - cleanTreePath := CleanUploadFileName(treePath) + cleanTreePath := util.PathJoinRel(treePath) if cleanTreePath == "" && treePath != "" { return nil, ErrFilenameInvalid{ Path: treePath, diff --git a/services/repository/files/tree_test.go b/services/repository/files/tree_test.go index 3f774bb268a64..a3b7eb33d7342 100644 --- a/services/repository/files/tree_test.go +++ b/services/repository/files/tree_test.go @@ -63,7 +63,7 @@ func Test_GetTreeList(t *testing.T) { refName := git.RefNameFromBranch(ctx1.Repo.Repository.DefaultBranch) - treeList, err := GetTreeList(ctx1, ctx1.Repo.Repository, ctx1.Repo.GitRepo, "", refName, true) + treeList, err := GetTreeList(ctx1, ctx1.Repo.Repository, ctx1.Repo.GitRepo, "", refName) assert.NoError(t, err) assert.Len(t, treeList, 1) assert.EqualValues(t, "README.md", treeList[0].Name) @@ -80,19 +80,14 @@ func Test_GetTreeList(t *testing.T) { refName = git.RefNameFromBranch(ctx2.Repo.Repository.DefaultBranch) - treeList, err = GetTreeList(ctx2, ctx2.Repo.Repository, ctx2.Repo.GitRepo, "", refName, true) + treeList, err = GetTreeList(ctx2, ctx2.Repo.Repository, ctx2.Repo.GitRepo, "", refName) assert.NoError(t, err) assert.Len(t, treeList, 2) assert.EqualValues(t, "doc", treeList[0].Name) assert.EqualValues(t, "doc", treeList[0].Path) assert.EqualValues(t, "tree", treeList[0].Type) - assert.Len(t, treeList[0].Children, 1) - - assert.EqualValues(t, "doc.md", treeList[0].Children[0].Name) - assert.EqualValues(t, "doc/doc.md", treeList[0].Children[0].Path) - assert.EqualValues(t, "blob", treeList[0].Children[0].Type) - assert.Empty(t, treeList[0].Children[0].Children) + assert.Empty(t, treeList[0].Children) assert.EqualValues(t, "README.md", treeList[1].Name) assert.EqualValues(t, "README.md", treeList[1].Path) From b1dde04807b0d27922a849387f00056b62611d59 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sat, 15 Mar 2025 11:09:59 +0800 Subject: [PATCH 80/92] clean up --- routers/web/repo/blame.go | 59 +++++++---------------- routers/web/repo/{tree.go => treelist.go} | 0 templates/repo/view_content.tmpl | 16 +++--- 3 files changed, 26 insertions(+), 49 deletions(-) rename routers/web/repo/{tree.go => treelist.go} (100%) diff --git a/routers/web/repo/blame.go b/routers/web/repo/blame.go index a433d4c358bfd..efd85b9452798 100644 --- a/routers/web/repo/blame.go +++ b/routers/web/repo/blame.go @@ -41,63 +41,39 @@ type blameRow struct { // RefBlame render blame page func RefBlame(ctx *context.Context) { - fileName := ctx.Repo.TreePath - if len(fileName) == 0 { + ctx.Data["PageIsViewCode"] = true + ctx.Data["IsBlame"] = true + + // Get current entry user currently looking at. + if ctx.Repo.TreePath == "" { ctx.NotFound(nil) return } - - prepareHomeTreeSideBarSwitch(ctx) - - branchLink := ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL() - treeLink := branchLink - rawLink := ctx.Repo.RepoLink + "/raw/" + ctx.Repo.RefTypeNameSubURL() - - if len(ctx.Repo.TreePath) > 0 { - treeLink += "/" + util.PathEscapeSegments(ctx.Repo.TreePath) - } - - var treeNames []string - paths := make([]string, 0, 5) - if len(ctx.Repo.TreePath) > 0 { - treeNames = strings.Split(ctx.Repo.TreePath, "/") - for i := range treeNames { - paths = append(paths, strings.Join(treeNames[:i+1], "/")) - } - - ctx.Data["HasParentPath"] = true - if len(paths)-2 >= 0 { - ctx.Data["ParentPath"] = "/" + paths[len(paths)-1] - } - } - - // Get current entry user currently looking at. entry, err := ctx.Repo.Commit.GetTreeEntryByPath(ctx.Repo.TreePath) if err != nil { HandleGitError(ctx, "Repo.Commit.GetTreeEntryByPath", err) return } - blob := entry.Blob() + treeNames := strings.Split(ctx.Repo.TreePath, "/") + var paths []string + for i := range treeNames { + paths = append(paths, strings.Join(treeNames[:i+1], "/")) + } ctx.Data["Paths"] = paths - ctx.Data["TreeLink"] = treeLink ctx.Data["TreeNames"] = treeNames - ctx.Data["BranchLink"] = branchLink - - ctx.Data["RawFileLink"] = rawLink + "/" + util.PathEscapeSegments(ctx.Repo.TreePath) - ctx.Data["PageIsViewCode"] = true - - ctx.Data["IsBlame"] = true + ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL() + ctx.Data["RawFileLink"] = ctx.Repo.RepoLink + "/raw/" + ctx.Repo.RefTypeNameSubURL() + "/" + util.PathEscapeSegments(ctx.Repo.TreePath) + blob := entry.Blob() fileSize := blob.Size() ctx.Data["FileSize"] = fileSize ctx.Data["FileName"] = blob.Name() - var tplName templates.TplName - if ctx.FormBool("only_content") { - tplName = tplRepoViewContent - } else { + tplName := tplRepoViewContent + if !ctx.FormBool("only_content") { + prepareHomeTreeSideBarSwitch(ctx) tplName = tplRepoView } @@ -114,8 +90,7 @@ func RefBlame(ctx *context.Context) { } bypassBlameIgnore, _ := strconv.ParseBool(ctx.FormString("bypass-blame-ignore")) - - result, err := performBlame(ctx, ctx.Repo.Repository, ctx.Repo.Commit, fileName, bypassBlameIgnore) + result, err := performBlame(ctx, ctx.Repo.Repository, ctx.Repo.Commit, ctx.Repo.TreePath, bypassBlameIgnore) if err != nil { ctx.NotFound(err) return diff --git a/routers/web/repo/tree.go b/routers/web/repo/treelist.go similarity index 100% rename from routers/web/repo/tree.go rename to routers/web/repo/treelist.go diff --git a/templates/repo/view_content.tmpl b/templates/repo/view_content.tmpl index 5bf3d67d0c3ff..ad03da97ac1df 100644 --- a/templates/repo/view_content.tmpl +++ b/templates/repo/view_content.tmpl @@ -3,13 +3,15 @@ {{template "repo/sub_menu" .}}
- {{if not $isTreePathRoot}} - - {{end}} - {{template "repo/home_branch_dropdown" (dict "ctxData" .)}} - {{if and .CanCompareOrPull .RefFullName.IsBranch (not .Repository.IsArchived)}} + {{if not $isTreePathRoot}} + + {{end}} + + {{template "repo/home_branch_dropdown" (dict "ctxData" .)}} + + {{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)}} From 14f914c13301092eb7a300aa95e48798f4097542 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sat, 15 Mar 2025 11:34:54 +0800 Subject: [PATCH 81/92] clean up --- models/user/setting_keys.go | 6 +++-- modules/git/parse_nogogit.go | 2 +- routers/web/repo/repo.go | 20 -------------- routers/web/repo/view_home.go | 10 +++---- routers/web/user/setting/settings.go | 26 +++++++++++++++++++ routers/web/web.go | 2 +- templates/repo/view.tmpl | 4 +-- templates/repo/view_content.tmpl | 2 +- .../features/repo-view-file-tree-sidebar.ts | 6 ++--- 9 files changed, 42 insertions(+), 36 deletions(-) create mode 100644 routers/web/user/setting/settings.go diff --git a/models/user/setting_keys.go b/models/user/setting_keys.go index ae09086db99b7..2c2ed078beabb 100644 --- a/models/user/setting_keys.go +++ b/models/user/setting_keys.go @@ -10,6 +10,7 @@ const ( SettingsKeyDiffWhitespaceBehavior = "diff.whitespace_behaviour" // SettingsKeyShowOutdatedComments is the setting key wether or not to show outdated comments in PRs SettingsKeyShowOutdatedComments = "comment_code.show_outdated" + // UserActivityPubPrivPem is user's private key UserActivityPubPrivPem = "activitypub.priv_pem" // UserActivityPubPubPem is user's public key @@ -17,6 +18,7 @@ const ( // SignupIP is the IP address that the user signed up with SignupIP = "signup.ip" // SignupUserAgent is the user agent that the user signed up with - SignupUserAgent = "signup.user_agent" - SettingsKeyShowFileViewTreeSidebar = "tree.show_file_view_tree_sidebar" + SignupUserAgent = "signup.user_agent" + + SettingsKeyCodeViewShowFileTree = "code_view.show_file_tree" ) diff --git a/modules/git/parse_nogogit.go b/modules/git/parse_nogogit.go index 676bb3c76c09f..78a016288986a 100644 --- a/modules/git/parse_nogogit.go +++ b/modules/git/parse_nogogit.go @@ -19,7 +19,7 @@ func ParseTreeEntries(data []byte) ([]*TreeEntry, error) { return parseTreeEntries(data, nil) } -// parseTreeEntries FIXME this function's design is not right, it should make the caller read all data into memory +// parseTreeEntries FIXME this function's design is not right, it should not make the caller read all data into memory func parseTreeEntries(data []byte, ptree *Tree) ([]*TreeEntry, error) { entries := make([]*TreeEntry, 0, bytes.Count(data, []byte{'\n'})+1) for pos := 0; pos < len(data); { diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go index f5f4a081c5059..73baf683ed577 100644 --- a/routers/web/repo/repo.go +++ b/routers/web/repo/repo.go @@ -9,7 +9,6 @@ import ( "fmt" "net/http" "slices" - "strconv" "strings" "code.gitea.io/gitea/models/db" @@ -21,7 +20,6 @@ import ( user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/cache" "code.gitea.io/gitea/modules/git" - "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/optional" repo_module "code.gitea.io/gitea/modules/repository" @@ -650,21 +648,3 @@ func PrepareBranchList(ctx *context.Context) { } ctx.Data["Branches"] = brs } - -type preferencesForm struct { - ShowFileViewTreeSidebar bool `json:"show_file_view_tree_sidebar"` -} - -func UpdatePreferences(ctx *context.Context) { - form := &preferencesForm{} - if err := json.NewDecoder(ctx.Req.Body).Decode(&form); err != nil { - ctx.ServerError("DecodePreferencesForm", err) - return - } - if err := user_model.SetUserSetting(ctx, ctx.Doer.ID, user_model.SettingsKeyShowFileViewTreeSidebar, - strconv.FormatBool(form.ShowFileViewTreeSidebar)); err != nil { - log.Error("SetUserSetting: %v", err) - } - - ctx.JSONOK() -} diff --git a/routers/web/repo/view_home.go b/routers/web/repo/view_home.go index cd716e65b245d..d538406035c7d 100644 --- a/routers/web/repo/view_home.go +++ b/routers/web/repo/view_home.go @@ -331,18 +331,16 @@ func handleRepoHomeFeed(ctx *context.Context) bool { } func prepareHomeTreeSideBarSwitch(ctx *context.Context) { - showFileViewTreeSidebar := true + showFileTree := true if ctx.Doer != nil { - v, err := user_model.GetUserSetting(ctx, ctx.Doer.ID, user_model.SettingsKeyShowFileViewTreeSidebar, "true") + v, err := user_model.GetUserSetting(ctx, ctx.Doer.ID, user_model.SettingsKeyCodeViewShowFileTree, "true") if err != nil { log.Error("GetUserSetting: %v", err) } else { - showFileViewTreeSidebar, _ = strconv.ParseBool(v) + showFileTree, _ = strconv.ParseBool(v) } } - ctx.Data["RepoPreferences"] = &preferencesForm{ - ShowFileViewTreeSidebar: showFileViewTreeSidebar, - } + ctx.Data["UserSettingCodeViewShowFileTree"] = showFileTree } // Home render repository home page diff --git a/routers/web/user/setting/settings.go b/routers/web/user/setting/settings.go new file mode 100644 index 0000000000000..111931633da4f --- /dev/null +++ b/routers/web/user/setting/settings.go @@ -0,0 +1,26 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package setting + +import ( + "net/http" + "strconv" + + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/json" + "code.gitea.io/gitea/services/context" +) + +func UpdatePreferences(ctx *context.Context) { + type preferencesForm struct { + CodeViewShowFileTree bool `json:"codeViewShowFileTree"` + } + form := &preferencesForm{} + if err := json.NewDecoder(ctx.Req.Body).Decode(&form); err != nil { + ctx.HTTPError(http.StatusBadRequest, "json decode failed") + return + } + _ = user_model.SetUserSetting(ctx, ctx.Doer.ID, user_model.SettingsKeyCodeViewShowFileTree, strconv.FormatBool(form.CodeViewShowFileTree)) + ctx.JSONOK() +} diff --git a/routers/web/web.go b/routers/web/web.go index a8e1539c660ce..feb72d186e3ab 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -580,6 +580,7 @@ func registerRoutes(m *web.Router) { m.Group("/user/settings", func() { m.Get("", user_setting.Profile) m.Post("", web.Bind(forms.UpdateProfileForm{}), user_setting.ProfilePost) + m.Post("/update_preferences", user_setting.UpdatePreferences) m.Get("/change_password", auth.MustChangePassword) m.Post("/change_password", web.Bind(forms.MustChangePasswordForm{}), auth.MustChangePasswordPost) m.Post("/avatar", web.Bind(forms.AvatarForm{}), user_setting.AvatarPost) @@ -1001,7 +1002,6 @@ func registerRoutes(m *web.Router) { m.Get("/migrate", repo.Migrate) m.Post("/migrate", web.Bind(forms.MigrateRepoForm{}), repo.MigratePost) m.Get("/search", repo.SearchRepo) - m.Put("/preferences", repo.UpdatePreferences) }, reqSignIn) // end "/repo": create, migrate, search diff --git a/templates/repo/view.tmpl b/templates/repo/view.tmpl index 6073b91914aef..8395139e8b747 100644 --- a/templates/repo/view.tmpl +++ b/templates/repo/view.tmpl @@ -16,8 +16,8 @@ {{template "repo/code/recently_pushed_new_branches" .}} -
-
{{template "repo/view_file_tree_sidebar" .}}
+
+
{{template "repo/view_file_tree_sidebar" .}}
{{template "repo/view_content" .}}
diff --git a/templates/repo/view_content.tmpl b/templates/repo/view_content.tmpl index ad03da97ac1df..3cbf64ae7000c 100644 --- a/templates/repo/view_content.tmpl +++ b/templates/repo/view_content.tmpl @@ -4,7 +4,7 @@
{{if not $isTreePathRoot}} - {{end}} diff --git a/web_src/js/features/repo-view-file-tree-sidebar.ts b/web_src/js/features/repo-view-file-tree-sidebar.ts index 7c1bb4a316aab..a0c091686a7b8 100644 --- a/web_src/js/features/repo-view-file-tree-sidebar.ts +++ b/web_src/js/features/repo-view-file-tree-sidebar.ts @@ -1,7 +1,7 @@ import {createApp, ref} from 'vue'; import {toggleElem} from '../utils/dom.ts'; import {pathEscapeSegments, pathUnescapeSegments} from '../utils/url.ts'; -import {GET, PUT} from '../modules/fetch.ts'; +import {GET, POST} from '../modules/fetch.ts'; import ViewFileTree from '../components/ViewFileTree.vue'; const {appSubUrl} = window.config; @@ -19,9 +19,9 @@ async function toggleSidebar(sidebarEl: HTMLElement, shouldShow: boolean) { if (!sidebarEl.hasAttribute('data-is-signed')) return; // save to session - await PUT(`${appSubUrl}/repo/preferences`, { + await POST(`${appSubUrl}/user/settings/update_preferences`, { data: { - show_file_view_tree_sidebar: shouldShow, + codeViewShowFileTree: shouldShow, }, }); } From b63ceb3600d1cbbd413c2ef0e2b703522f2e6b48 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sat, 15 Mar 2025 12:34:58 +0800 Subject: [PATCH 82/92] fix tree node --- routers/web/repo/treelist.go | 4 +- services/contexttest/context_tests.go | 24 +- services/repository/files/tree.go | 336 ++----------------------- services/repository/files/tree_test.go | 161 +++--------- 4 files changed, 78 insertions(+), 447 deletions(-) diff --git a/routers/web/repo/treelist.go b/routers/web/repo/treelist.go index b4090667ee449..47d6ab23f2f1e 100644 --- a/routers/web/repo/treelist.go +++ b/routers/web/repo/treelist.go @@ -98,9 +98,9 @@ func Tree(ctx *context.Context) { var results []*files_service.TreeViewNode var err error if !recursive { - results, err = files_service.GetTreeList(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, ctx.Repo.TreePath, ctx.Repo.RefFullName) + results, err = files_service.GetTreeViewNodes(ctx, ctx.Repo.Commit, ctx.Repo.TreePath, "") } else { - results, err = files_service.GetTreeInformation(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, ctx.Repo.TreePath, ctx.Repo.RefFullName) + results, err = files_service.GetTreeViewNodes(ctx, ctx.Repo.Commit, "", ctx.Repo.TreePath) } if err != nil { ctx.ServerError("GetTreeInformation", err) diff --git a/services/contexttest/context_tests.go b/services/contexttest/context_tests.go index 98b8bdd63edb4..d4531dd71909f 100644 --- a/services/contexttest/context_tests.go +++ b/services/contexttest/context_tests.go @@ -20,6 +20,7 @@ import ( "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/cache" + git2 "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/reqctx" "code.gitea.io/gitea/modules/session" @@ -30,6 +31,7 @@ import ( "github.com/go-chi/chi/v5" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func mockRequest(t *testing.T, reqPath string) *http.Request { @@ -106,13 +108,13 @@ func MockPrivateContext(t *testing.T, reqPath string) (*context.PrivateContext, // LoadRepo load a repo into a test context. func LoadRepo(t *testing.T, ctx gocontext.Context, repoID int64) { var doer *user_model.User - repo := &context.Repository{} + var repo *context.Repository switch ctx := ctx.(type) { case *context.Context: - ctx.Repo = repo + repo = ctx.Repo doer = ctx.Doer case *context.APIContext: - ctx.Repo = repo + repo = ctx.Repo doer = ctx.Doer default: assert.FailNow(t, "context is not *context.Context or *context.APIContext") @@ -140,15 +142,17 @@ func LoadRepoCommit(t *testing.T, ctx gocontext.Context) { } gitRepo, err := gitrepo.OpenRepository(ctx, repo.Repository) - assert.NoError(t, err) + require.NoError(t, err) defer gitRepo.Close() - branch, err := gitRepo.GetHEADBranch() - assert.NoError(t, err) - assert.NotNil(t, branch) - if branch != nil { - repo.Commit, err = gitRepo.GetBranchCommit(branch.Name) - assert.NoError(t, err) + + if repo.RefFullName == "" { + repo.RefFullName = git2.RefNameFromBranch(repo.Repository.DefaultBranch) + } + if repo.RefFullName.IsPull() { + repo.BranchName = repo.RefFullName.ShortName() } + repo.Commit, err = gitRepo.GetCommit(repo.RefFullName.String()) + require.NoError(t, err) } // LoadUser load a user into a test context diff --git a/services/repository/files/tree.go b/services/repository/files/tree.go index 2367f5dc10cdf..f1634a9a24c6d 100644 --- a/services/repository/files/tree.go +++ b/services/repository/files/tree.go @@ -186,328 +186,38 @@ func sortTreeViewNodes(nodes []*TreeViewNode) { }) } -/* -Example 1: (path: /) - - GET /repo/name/tree/ - - resp: - [{ - "name": "d1", - "type": "commit", - "path": "d1", - "sub_module_url": "https://gitea.com/gitea/awesome-gitea/tree/887fe27678dced0bd682923b30b2d979575d35d6" - },{ - "name": "d2", - "type": "symlink", - "path": "d2" - },{ - "name": "d3", - "type": "tree", - "path": "d3" - },{ - "name": "f1", - "type": "blob", - "path": "f1" - },] - -Example 2: (path: d3) - - GET /repo/name/tree/d3 - resp: - [{ - "name": "d3d1", - "type": "tree", - "path": "d3/d3d1" - }] - -Example 3: (path: d3/d3d1) - - GET /repo/name/tree/d3/d3d1 - resp: - [{ - "name": "d3d1f1", - "type": "blob", - "path": "d3/d3d1/d3d1f1" - },{ - "name": "d3d1f2", - "type": "blob", - "path": "d3/d3d1/d3d1f2" - }] -*/ -func GetTreeList(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, treePath string, ref git.RefName) ([]*TreeViewNode, error) { - if repo.IsEmpty { - return nil, nil - } - if ref == "" { - ref = git.RefNameFromBranch(repo.DefaultBranch) - } - - // Check that the path given in opts.treePath is valid (not a git path) - cleanTreePath := util.PathJoinRel(treePath) - if cleanTreePath == "" && treePath != "" { - return nil, ErrFilenameInvalid{ - Path: treePath, - } - } - treePath = cleanTreePath - - // Get the commit object for the ref - commit, err := gitRepo.GetCommit(ref.String()) - if err != nil { - return nil, err - } - - entry, err := commit.GetTreeEntryByPath(treePath) - if err != nil { - return nil, err - } - - // If the entry is a file, an exception will be thrown. - // This is because this interface is specifically designed for expanding folders and only supports the retrieval and return of the file list within a folder. - if entry.Type() != "tree" { - return nil, fmt.Errorf("%s is not a tree", treePath) - } - - gitTree, err := commit.SubTree(treePath) - if err != nil { - return nil, err - } - - entries, err := gitTree.ListEntries() +func listTreeNodes(ctx context.Context, commit *git.Commit, tree *git.Tree, treePath, subPath string) ([]*TreeViewNode, error) { + entries, err := tree.ListEntries() if err != nil { return nil, err } - var treeViewNodes []*TreeViewNode - mapTree := make(map[string][]*TreeViewNode) - for _, e := range entries { - subTreePath := path.Join(treePath, e.Name()) - - if strings.Contains(e.Name(), "/") { - dirName := path.Dir(e.Name()) - mapTree[dirName] = append(mapTree[dirName], &TreeViewNode{ - Name: path.Base(e.Name()), - Type: entryModeString(e.Mode()), - Path: subTreePath, - }) - } else { - treeViewNodes = append(treeViewNodes, &TreeViewNode{ - Name: e.Name(), - Type: entryModeString(e.Mode()), - Path: subTreePath, - }) - } - } - - for _, node := range treeViewNodes { - if node.Type == "tree" { - node.Children = mapTree[node.Path] - sortTreeViewNodes(node.Children) + subPathDirName, subPathRemaining, _ := strings.Cut(subPath, "/") + nodes := make([]*TreeViewNode, 0, len(entries)) + for _, entry := range entries { + node := newTreeViewNodeFromEntry(ctx, commit, treePath, entry) + nodes = append(nodes, node) + if entry.IsDir() && subPathDirName == entry.Name() { + subTreePath := treePath + "/" + node.Name + if subTreePath[0] == '/' { + subTreePath = subTreePath[1:] + } + subNodes, err := listTreeNodes(ctx, commit, entry.Tree(), subTreePath, subPathRemaining) + if err != nil { + log.Error("listTreeNodes: %v", err) + } else { + node.Children = subNodes + } } } - - sortTreeViewNodes(treeViewNodes) - - return treeViewNodes, nil + sortTreeViewNodes(nodes) + return nodes, nil } -// GetTreeInformation returns the first level directories and files and all the trees of the path to treePath. -// If treePath is a directory, list all subdirectories and files of treePath. -/* -Example 1: (path: /) - GET /repo/name/tree/?recursive=true - resp: - [{ - "name": "d1", - "type": "commit", - "path": "d1", - "sub_module_url": "https://gitea.com/gitea/awesome-gitea/tree/887fe27678dced0bd682923b30b2d979575d35d6" - },{ - "name": "d2", - "type": "symlink", - "path": "d2" - },{ - "name": "d3", - "type": "tree", - "path": "d3" - },{ - "name": "f1", - "type": "blob", - "path": "f1" - },] - -Example 2: (path: d3) - GET /repo/name/tree/d3?recursive=true - resp: - [{ - "name": "d1", - "type": "commit", - "path": "d1", - "sub_module_url": "https://gitea.com/gitea/awesome-gitea/tree/887fe27678dced0bd682923b30b2d979575d35d6" - },{ - "name": "d2", - "type": "symlink", - "path": "d2" - },{ - "name": "d3", - "type": "tree", - "path": "d3", - "children": [{ - "name": "d3d1", - "type": "tree", - "path": "d3/d3d1" - }] - },{ - "name": "f1", - "type": "blob", - "path": "f1" - },] - -Example 3: (path: d3/d3d1) - GET /repo/name/tree/d3/d3d1?recursive=true - resp: - [{ - "name": "d1", - "type": "commit", - "path": "d1", - "sub_module_url": "https://gitea.com/gitea/awesome-gitea/tree/887fe27678dced0bd682923b30b2d979575d35d6" - },{ - "name": "d2", - "type": "symlink", - "path": "d2" - },{ - "name": "d3", - "type": "tree", - "path": "d3", - "children": [{ - "name": "d3d1", - "type": "tree", - "path": "d3/d3d1", - "children": [{ - "name": "d3d1f1", - "type": "blob", - "path": "d3/d3d1/d3d1f1" - },{ - "name": "d3d1f2", - "type": "blob", - "path": "d3/d3d1/d3d1f2" - }] - }] - },{ - "name": "f1", - "type": "blob", - "path": "f1" - },] - -Example 4: (path: d2/d2f1) - GET /repo/name/tree/d2/d2f1?recursive=true - resp: - [{ - "name": "d1", - "type": "commit", - "path": "d1", - "sub_module_url": "https://gitea.com/gitea/awesome-gitea/tree/887fe27678dced0bd682923b30b2d979575d35d6" - },{ - "name": "d2", - "type": "tree", - "path": "d2", - "children": [{ - "name": "d2f1", - "type": "blob", - "path": "d2/d2f1" - }] - },{ - "name": "d3", - "type": "tree", - "path": "d3" - },{ - "name": "f1", - "type": "blob", - "path": "f1" - },] -*/ -func GetTreeInformation(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, treePath string, ref git.RefName) ([]*TreeViewNode, error) { - if repo.IsEmpty { - return nil, nil - } - if ref == "" { - ref = git.RefNameFromBranch(repo.DefaultBranch) - } - - // Check that the path given in opts.treePath is valid (not a git path) - cleanTreePath := util.PathJoinRel(treePath) - if cleanTreePath == "" && treePath != "" { - return nil, ErrFilenameInvalid{ - Path: treePath, - } - } - treePath = cleanTreePath - - // Get the commit object for the ref - commit, err := gitRepo.GetCommit(ref.String()) - if err != nil { - return nil, err - } - - // get root entries - rootEntries, err := commit.ListEntries() - if err != nil { - return nil, err - } - - dir := treePath - if dir != "" { - lastDirEntry, err := commit.GetTreeEntryByPath(treePath) - if err != nil { - return nil, err - } - if lastDirEntry.IsRegular() { - // path.Dir cannot correctly handle .xxx file - dir, _ = path.Split(treePath) - dir = strings.TrimRight(dir, "/") - } - } - - treeViewNodes := make([]*TreeViewNode, 0, len(rootEntries)) - fields := strings.Split(dir, "/") - var parentNode *TreeViewNode - for _, entry := range rootEntries { - node := newTreeViewNodeFromEntry(ctx, commit, "", entry) - treeViewNodes = append(treeViewNodes, node) - if dir != "" && fields[0] == entry.Name() { - parentNode = node - } - } - - sortTreeViewNodes(treeViewNodes) - if dir == "" || parentNode == nil { - return treeViewNodes, nil - } - - for i := 1; i < len(fields); i++ { - parentNode.Children = []*TreeViewNode{ - { - Name: fields[i], - Type: "tree", - Path: path.Join(fields[:i+1]...), - }, - } - parentNode = parentNode.Children[0] - } - - tree, err := commit.Tree.SubTree(dir) - if err != nil { - return nil, err - } - entries, err := tree.ListEntries() +func GetTreeViewNodes(ctx context.Context, commit *git.Commit, treePath, subPath string) ([]*TreeViewNode, error) { + entry, err := commit.GetTreeEntryByPath(treePath) if err != nil { return nil, err } - - for _, entry := range entries { - parentNode.Children = append(parentNode.Children, newTreeViewNodeFromEntry(ctx, commit, dir, entry)) - } - sortTreeViewNodes(parentNode.Children) - return treeViewNodes, nil + return listTreeNodes(ctx, commit, entry.Tree(), treePath, subPath) } diff --git a/services/repository/files/tree_test.go b/services/repository/files/tree_test.go index a3b7eb33d7342..236d040db34b5 100644 --- a/services/repository/files/tree_test.go +++ b/services/repository/files/tree_test.go @@ -52,133 +52,50 @@ func TestGetTreeBySHA(t *testing.T) { assert.EqualValues(t, expectedTree, tree) } -func Test_GetTreeList(t *testing.T) { +func Test_GetTreeViewNodes(t *testing.T) { unittest.PrepareTestEnv(t) - ctx1, _ := contexttest.MockContext(t, "user2/repo1") - contexttest.LoadRepo(t, ctx1, 1) - contexttest.LoadRepoCommit(t, ctx1) - contexttest.LoadUser(t, ctx1, 2) - contexttest.LoadGitRepo(t, ctx1) - defer ctx1.Repo.GitRepo.Close() - - refName := git.RefNameFromBranch(ctx1.Repo.Repository.DefaultBranch) - - treeList, err := GetTreeList(ctx1, ctx1.Repo.Repository, ctx1.Repo.GitRepo, "", refName) - assert.NoError(t, err) - assert.Len(t, treeList, 1) - assert.EqualValues(t, "README.md", treeList[0].Name) - assert.EqualValues(t, "README.md", treeList[0].Path) - assert.EqualValues(t, "blob", treeList[0].Type) - assert.Empty(t, treeList[0].Children) - - ctx2, _ := contexttest.MockContext(t, "org3/repo3") - contexttest.LoadRepo(t, ctx2, 3) - contexttest.LoadRepoCommit(t, ctx2) - contexttest.LoadUser(t, ctx2, 2) - contexttest.LoadGitRepo(t, ctx2) - defer ctx2.Repo.GitRepo.Close() - - refName = git.RefNameFromBranch(ctx2.Repo.Repository.DefaultBranch) - - treeList, err = GetTreeList(ctx2, ctx2.Repo.Repository, ctx2.Repo.GitRepo, "", refName) - assert.NoError(t, err) - assert.Len(t, treeList, 2) - - assert.EqualValues(t, "doc", treeList[0].Name) - assert.EqualValues(t, "doc", treeList[0].Path) - assert.EqualValues(t, "tree", treeList[0].Type) - assert.Empty(t, treeList[0].Children) - - assert.EqualValues(t, "README.md", treeList[1].Name) - assert.EqualValues(t, "README.md", treeList[1].Path) - assert.EqualValues(t, "blob", treeList[1].Type) - assert.Empty(t, treeList[1].Children) -} - -func Test_GetTreeInformation(t *testing.T) { - unittest.PrepareTestEnv(t) - ctx1, _ := contexttest.MockContext(t, "user2/repo1") - contexttest.LoadRepo(t, ctx1, 1) - contexttest.LoadRepoCommit(t, ctx1) - contexttest.LoadUser(t, ctx1, 2) - contexttest.LoadGitRepo(t, ctx1) - defer ctx1.Repo.GitRepo.Close() - - refName := git.RefNameFromBranch(ctx1.Repo.Repository.DefaultBranch) - - treeList, err := GetTreeInformation(ctx1, ctx1.Repo.Repository, ctx1.Repo.GitRepo, "", refName) - assert.NoError(t, err) - assert.Len(t, treeList, 1) - assert.EqualValues(t, "README.md", treeList[0].Name) - assert.EqualValues(t, "README.md", treeList[0].Path) - assert.EqualValues(t, "blob", treeList[0].Type) - assert.Empty(t, treeList[0].Children) - - treeList, err = GetTreeInformation(ctx1, ctx1.Repo.Repository, ctx1.Repo.GitRepo, "README.md", refName) - assert.NoError(t, err) - assert.Len(t, treeList, 1) - assert.EqualValues(t, "README.md", treeList[0].Name) - assert.EqualValues(t, "README.md", treeList[0].Path) - assert.EqualValues(t, "blob", treeList[0].Type) - assert.Empty(t, treeList[0].Children) - - ctx2, _ := contexttest.MockContext(t, "org3/repo3") - contexttest.LoadRepo(t, ctx2, 3) - contexttest.LoadRepoCommit(t, ctx2) - contexttest.LoadUser(t, ctx2, 2) - contexttest.LoadGitRepo(t, ctx2) - defer ctx2.Repo.GitRepo.Close() - - refName = git.RefNameFromBranch(ctx2.Repo.Repository.DefaultBranch) + ctx, _ := contexttest.MockContext(t, "user2/repo1") + ctx.Repo.RefFullName = git.RefNameFromBranch("sub-home-md-img-check") + contexttest.LoadRepo(t, ctx, 1) + contexttest.LoadRepoCommit(t, ctx) + contexttest.LoadUser(t, ctx, 2) + contexttest.LoadGitRepo(t, ctx) + defer ctx.Repo.GitRepo.Close() - treeList, err = GetTreeInformation(ctx2, ctx2.Repo.Repository, ctx2.Repo.GitRepo, "", refName) + treeNodes, err := GetTreeViewNodes(ctx, ctx.Repo.Commit, "", "") assert.NoError(t, err) - assert.Len(t, treeList, 2) - - assert.EqualValues(t, "doc", treeList[0].Name) - assert.EqualValues(t, "doc", treeList[0].Path) - assert.EqualValues(t, "tree", treeList[0].Type) - assert.Empty(t, treeList[0].Children) - - assert.EqualValues(t, "README.md", treeList[1].Name) - assert.EqualValues(t, "README.md", treeList[1].Path) - assert.EqualValues(t, "blob", treeList[1].Type) - assert.Empty(t, treeList[1].Children) + assert.Equal(t, []*TreeViewNode{ + { + Name: "docs", + Type: "tree", + Path: "docs", + }, + }, treeNodes) - treeList, err = GetTreeInformation(ctx2, ctx2.Repo.Repository, ctx2.Repo.GitRepo, "doc", refName) + treeNodes, err = GetTreeViewNodes(ctx, ctx.Repo.Commit, "", "docs/README.md") assert.NoError(t, err) - assert.Len(t, treeList, 2) - assert.EqualValues(t, "doc", treeList[0].Name) - assert.EqualValues(t, "doc", treeList[0].Path) - assert.EqualValues(t, "tree", treeList[0].Type) - assert.Len(t, treeList[0].Children, 1) - - assert.EqualValues(t, "doc.md", treeList[0].Children[0].Name) - assert.EqualValues(t, "doc/doc.md", treeList[0].Children[0].Path) - assert.EqualValues(t, "blob", treeList[0].Children[0].Type) - assert.Empty(t, treeList[0].Children[0].Children) - - assert.EqualValues(t, "README.md", treeList[1].Name) - assert.EqualValues(t, "README.md", treeList[1].Path) - assert.EqualValues(t, "blob", treeList[1].Type) - assert.Empty(t, treeList[1].Children) + assert.Equal(t, []*TreeViewNode{ + { + Name: "docs", + Type: "tree", + Path: "docs", + Children: []*TreeViewNode{ + { + Name: "README.md", + Type: "blob", + Path: "docs/README.md", + }, + }, + }, + }, treeNodes) - treeList, err = GetTreeInformation(ctx2, ctx2.Repo.Repository, ctx2.Repo.GitRepo, "doc/doc.md", refName) + treeNodes, err = GetTreeViewNodes(ctx, ctx.Repo.Commit, "docs", "README.md") assert.NoError(t, err) - assert.Len(t, treeList, 2) - - assert.EqualValues(t, "doc", treeList[0].Name) - assert.EqualValues(t, "doc", treeList[0].Path) - assert.EqualValues(t, "tree", treeList[0].Type) - assert.Len(t, treeList[0].Children, 1) - - assert.EqualValues(t, "doc.md", treeList[0].Children[0].Name) - assert.EqualValues(t, "doc/doc.md", treeList[0].Children[0].Path) - assert.EqualValues(t, "blob", treeList[0].Children[0].Type) - assert.Empty(t, treeList[0].Children[0].Children) - - assert.EqualValues(t, "README.md", treeList[1].Name) - assert.EqualValues(t, "README.md", treeList[1].Path) - assert.EqualValues(t, "blob", treeList[1].Type) - assert.Empty(t, treeList[1].Children) + assert.Equal(t, []*TreeViewNode{ + { + Name: "README.md", + Type: "blob", + Path: "docs/README.md", + }, + }, treeNodes) } From 692c62ae920ee27452fdb23b69c93d427aab05fd Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sat, 15 Mar 2025 12:46:24 +0800 Subject: [PATCH 83/92] clean up --- routers/web/repo/treelist.go | 22 ++++--------------- routers/web/web.go | 6 ++--- .../features/repo-view-file-tree-sidebar.ts | 19 +++++----------- 3 files changed, 12 insertions(+), 35 deletions(-) diff --git a/routers/web/repo/treelist.go b/routers/web/repo/treelist.go index 47d6ab23f2f1e..ab74741e6183a 100644 --- a/routers/web/repo/treelist.go +++ b/routers/web/repo/treelist.go @@ -4,7 +4,6 @@ package repo import ( - "errors" "net/http" pull_model "code.gitea.io/gitea/models/pull" @@ -87,24 +86,11 @@ func transformDiffTreeForUI(diffTree *gitdiff.DiffTree, filesViewedState map[str return files } -func Tree(ctx *context.Context) { - recursive := ctx.FormBool("recursive") - - if ctx.Repo.RefFullName == "" { - ctx.ServerError("RefFullName", errors.New("ref_name is invalid")) - return - } - - var results []*files_service.TreeViewNode - var err error - if !recursive { - results, err = files_service.GetTreeViewNodes(ctx, ctx.Repo.Commit, ctx.Repo.TreePath, "") - } else { - results, err = files_service.GetTreeViewNodes(ctx, ctx.Repo.Commit, "", ctx.Repo.TreePath) - } +func TreeViewNodes(ctx *context.Context) { + results, err := files_service.GetTreeViewNodes(ctx, ctx.Repo.Commit, ctx.Repo.TreePath, ctx.FormString("sub_path")) if err != nil { - ctx.ServerError("GetTreeInformation", err) + ctx.ServerError("GetTreeViewNodes", err) return } - ctx.JSON(http.StatusOK, results) + ctx.JSON(http.StatusOK, map[string]any{"fileTreeNodes": results}) } diff --git a/routers/web/web.go b/routers/web/web.go index feb72d186e3ab..2c5d63ed3231d 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -1177,9 +1177,9 @@ func registerRoutes(m *web.Router) { m.Get("/commit/*", context.RepoRefByType(git.RefTypeCommit), repo.TreeList) }) m.Group("/tree", func() { - m.Get("/branch/*", context.RepoRefByType(git.RefTypeBranch), repo.Tree) - m.Get("/tag/*", context.RepoRefByType(git.RefTypeTag), repo.Tree) - m.Get("/commit/*", context.RepoRefByType(git.RefTypeCommit), repo.Tree) + m.Get("/branch/*", context.RepoRefByType(git.RefTypeBranch), repo.TreeViewNodes) + m.Get("/tag/*", context.RepoRefByType(git.RefTypeTag), repo.TreeViewNodes) + m.Get("/commit/*", context.RepoRefByType(git.RefTypeCommit), repo.TreeViewNodes) }) m.Get("/compare", repo.MustBeNotEmpty, repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.CompareDiff) m.Combo("/compare/*", repo.MustBeNotEmpty, repo.SetEditorconfigIfExists). diff --git a/web_src/js/features/repo-view-file-tree-sidebar.ts b/web_src/js/features/repo-view-file-tree-sidebar.ts index a0c091686a7b8..cea83c724b27e 100644 --- a/web_src/js/features/repo-view-file-tree-sidebar.ts +++ b/web_src/js/features/repo-view-file-tree-sidebar.ts @@ -27,22 +27,13 @@ async function toggleSidebar(sidebarEl: HTMLElement, shouldShow: boolean) { } function childrenLoader(sidebarEl: HTMLElement) { - return async (path: string, recursive?: boolean) => { + return async (treePath: string, subPath: string = '') => { const fileTree = sidebarEl.querySelector('#view-file-tree'); - const apiBaseUrl = fileTree.getAttribute('data-api-base-url'); + const baseUrl = fileTree.getAttribute('data-api-base-url'); const refTypeNameSubURL = fileTree.getAttribute('data-current-ref-type-name-sub-url'); - const response = await GET(`${apiBaseUrl}/tree/${refTypeNameSubURL}/${pathEscapeSegments(path ?? '')}?recursive=${recursive ?? false}`); + const response = await GET(`${baseUrl}/tree/${refTypeNameSubURL}/${pathEscapeSegments(treePath)}?sub_path=${encodeURIComponent(subPath)}`); const json = await response.json(); - if (json instanceof Array) { - return json.map((i) => ({ - name: i.name, - type: i.type, - path: i.path, - sub_module_url: i.sub_module_url, - children: i.children, - })); - } - return null; + return json.fileTreeNodes ?? null; }; } @@ -80,7 +71,7 @@ export async function initViewFileTreeSidebar() { const selectedItem = ref(getSelectedPath(refString)); - const files = await childrenLoader(sidebarEl)(treePath, true); + const files = await childrenLoader(sidebarEl)('', treePath); fileTree.classList.remove('is-loading'); const fileTreeView = createApp(ViewFileTree, {files, selectedItem, loadChildren: childrenLoader(sidebarEl), loadContent: (path: string) => { From 40b61f34e2e262b143c12d1381a9ae8c2f64d992 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sat, 15 Mar 2025 13:38:58 +0800 Subject: [PATCH 84/92] clean up --- routers/web/web.go | 2 +- templates/repo/view_file_tree_sidebar.tmpl | 6 +- web_src/js/components/ViewFileTree.vue | 47 +++++++++-- web_src/js/components/ViewFileTreeItem.vue | 27 +++---- .../features/repo-view-file-tree-sidebar.ts | 77 ++++--------------- web_src/js/utils/url.ts | 4 - 6 files changed, 67 insertions(+), 96 deletions(-) diff --git a/routers/web/web.go b/routers/web/web.go index 2c5d63ed3231d..f4bd3ef4bce99 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -1176,7 +1176,7 @@ func registerRoutes(m *web.Router) { m.Get("/tag/*", context.RepoRefByType(git.RefTypeTag), repo.TreeList) m.Get("/commit/*", context.RepoRefByType(git.RefTypeCommit), repo.TreeList) }) - m.Group("/tree", func() { + m.Group("/tree-view", func() { m.Get("/branch/*", context.RepoRefByType(git.RefTypeBranch), repo.TreeViewNodes) m.Get("/tag/*", context.RepoRefByType(git.RefTypeTag), repo.TreeViewNodes) m.Get("/commit/*", context.RepoRefByType(git.RefTypeCommit), repo.TreeViewNodes) diff --git a/templates/repo/view_file_tree_sidebar.tmpl b/templates/repo/view_file_tree_sidebar.tmpl index 4a5f829e5a7c2..31aa9e52d92e4 100644 --- a/templates/repo/view_file_tree_sidebar.tmpl +++ b/templates/repo/view_file_tree_sidebar.tmpl @@ -8,10 +8,8 @@
diff --git a/web_src/js/components/ViewFileTree.vue b/web_src/js/components/ViewFileTree.vue index 3337f6e9e553e..0ed6d4dd5bba1 100644 --- a/web_src/js/components/ViewFileTree.vue +++ b/web_src/js/components/ViewFileTree.vue @@ -1,18 +1,49 @@ diff --git a/web_src/js/components/ViewFileTreeItem.vue b/web_src/js/components/ViewFileTreeItem.vue index ec2660c8acad1..f6a198a19e56f 100644 --- a/web_src/js/components/ViewFileTreeItem.vue +++ b/web_src/js/components/ViewFileTreeItem.vue @@ -26,8 +26,7 @@ const doLoadChildren = async () => { if (!collapsed.value && props.loadChildren) { isLoading.value = true; try { - const _children = await props.loadChildren(props.item.path); - children.value = _children; + children.value = await props.loadChildren(props.item.path); } finally { isLoading.value = false; } @@ -51,7 +50,7 @@ const doGotoSubModule = () => {