From 5f757af622a0956d95f5117c8557f71a6bb922ab Mon Sep 17 00:00:00 2001 From: bytedream Date: Mon, 27 Oct 2025 16:36:30 +0100 Subject: [PATCH 1/7] remove html repo file path stripping --- routers/web/repo/view_file.go | 2 +- routers/web/repo/view_home.go | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/routers/web/repo/view_file.go b/routers/web/repo/view_file.go index 8bb9fb62a786e..ea3920439d426 100644 --- a/routers/web/repo/view_file.go +++ b/routers/web/repo/view_file.go @@ -172,7 +172,7 @@ func prepareFileView(ctx *context.Context, entry *git.TreeEntry) { blob := entry.Blob() - ctx.Data["Title"] = ctx.Tr("repo.file.title", ctx.Repo.Repository.Name+"/"+path.Base(ctx.Repo.TreePath), ctx.Repo.RefFullName.ShortName()) + ctx.Data["Title"] = ctx.Tr("repo.file.title", ctx.Repo.Repository.Name+"/"+ctx.Repo.TreePath, ctx.Repo.RefFullName.ShortName()) ctx.Data["FileIsSymlink"] = entry.IsLink() ctx.Data["FileTreePath"] = ctx.Repo.TreePath ctx.Data["RawFileLink"] = ctx.Repo.RepoLink + "/raw/" + ctx.Repo.RefTypeNameSubURL() + "/" + util.PathEscapeSegments(ctx.Repo.TreePath) diff --git a/routers/web/repo/view_home.go b/routers/web/repo/view_home.go index 6b161df392c42..17043055e5ff9 100644 --- a/routers/web/repo/view_home.go +++ b/routers/web/repo/view_home.go @@ -7,7 +7,6 @@ import ( "errors" "fmt" "net/http" - "path" "strconv" "strings" "time" @@ -146,7 +145,7 @@ func prepareToRenderDirectory(ctx *context.Context) { if ctx.Repo.TreePath != "" { ctx.Data["HideRepoInfo"] = true - ctx.Data["Title"] = ctx.Tr("repo.file.title", ctx.Repo.Repository.Name+"/"+path.Base(ctx.Repo.TreePath), ctx.Repo.RefFullName.ShortName()) + ctx.Data["Title"] = ctx.Tr("repo.file.title", ctx.Repo.Repository.Name+"/"+ctx.Repo.TreePath, ctx.Repo.RefFullName.ShortName()) } subfolder, readmeFile, err := findReadmeFileInEntries(ctx, ctx.Repo.TreePath, entries, true) From 754535318c3da5ca92dbfd91a2149b2f05c4a367 Mon Sep 17 00:00:00 2001 From: bytedream Date: Mon, 27 Oct 2025 16:37:32 +0100 Subject: [PATCH 2/7] update title when changing file via file tree --- templates/repo/view_file_tree.tmpl | 1 + web_src/js/components/ViewFileTree.vue | 1 + web_src/js/components/ViewFileTreeStore.ts | 11 ++++++++++- web_src/js/features/repo-view-file-tree.ts | 1 + 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/templates/repo/view_file_tree.tmpl b/templates/repo/view_file_tree.tmpl index 8aed05f346940..1e737d09cdfbc 100644 --- a/templates/repo/view_file_tree.tmpl +++ b/templates/repo/view_file_tree.tmpl @@ -10,6 +10,7 @@ {{/* TODO: Dynamically move components such as refSelector and createPR here */}}
diff --git a/web_src/js/components/ViewFileTree.vue b/web_src/js/components/ViewFileTree.vue index 1f90f9258670c..1bd52d7435b83 100644 --- a/web_src/js/components/ViewFileTree.vue +++ b/web_src/js/components/ViewFileTree.vue @@ -7,6 +7,7 @@ const elRoot = useTemplateRef('elRoot'); const props = defineProps({ repoLink: {type: String, required: true}, + repoName: {type: String, required: true}, treePath: {type: String, required: true}, currentRefNameSubURL: {type: String, required: true}, }); diff --git a/web_src/js/components/ViewFileTreeStore.ts b/web_src/js/components/ViewFileTreeStore.ts index 0e16b566509a1..63b585c743da5 100644 --- a/web_src/js/components/ViewFileTreeStore.ts +++ b/web_src/js/components/ViewFileTreeStore.ts @@ -4,7 +4,7 @@ import {pathEscapeSegments} from '../utils/url.ts'; import {createElementFromHTML} from '../utils/dom.ts'; import {html} from '../utils/html.ts'; -export function createViewFileTreeStore(props: {repoLink: string, treePath: string, currentRefNameSubURL: string}) { +export function createViewFileTreeStore(props: {repoLink: string, repoName: string, treePath: string, currentRefNameSubURL: string}) { const store = reactive({ rootFiles: [], selectedItem: props.treePath, @@ -32,14 +32,23 @@ export function createViewFileTreeStore(props: {repoLink: string, treePath: stri async navigateTreeView(treePath: string) { const url = store.buildTreePathWebUrl(treePath); + const title = store.buildTitle(store.selectedItem, treePath); window.history.pushState({treePath, url}, null, url); store.selectedItem = treePath; await store.loadViewContent(url); + document.title = title; }, buildTreePathWebUrl(treePath: string) { return `${props.repoLink}/src/${props.currentRefNameSubURL}/${pathEscapeSegments(treePath)}`; }, + + buildTitle(oldTreePath: string, treePath: string) { + // the title always starts with "/" + const oldPrefixLength = props.repoName.length + 1 + oldTreePath.length; + const titleSuffix = document.title.substring(oldPrefixLength); + return `${props.repoName}/${treePath}${titleSuffix}`; + }, }); return store; } diff --git a/web_src/js/features/repo-view-file-tree.ts b/web_src/js/features/repo-view-file-tree.ts index f52b64cc51d19..785ec35a5a4c5 100644 --- a/web_src/js/features/repo-view-file-tree.ts +++ b/web_src/js/features/repo-view-file-tree.ts @@ -31,6 +31,7 @@ export async function initRepoViewFileTree() { const fileTree = sidebar.querySelector('#view-file-tree'); createApp(ViewFileTree, { repoLink: fileTree.getAttribute('data-repo-link'), + repoName: fileTree.getAttribute('data-repo-name'), treePath: fileTree.getAttribute('data-tree-path'), currentRefNameSubURL: fileTree.getAttribute('data-current-ref-name-sub-url'), }).mount(fileTree); From c3ecaed9541cc5fa496f5409e7119573dbdcea5f Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Wed, 29 Oct 2025 20:09:49 +0800 Subject: [PATCH 3/7] fix --- templates/repo/view_content.tmpl | 1 + templates/repo/view_file_tree.tmpl | 1 - web_src/js/components/ViewFileTree.vue | 1 - web_src/js/components/ViewFileTreeStore.ts | 20 +++++++------------- web_src/js/features/repo-view-file-tree.ts | 1 - 5 files changed, 8 insertions(+), 16 deletions(-) diff --git a/templates/repo/view_content.tmpl b/templates/repo/view_content.tmpl index 3ba04a9974940..f1cc811847004 100644 --- a/templates/repo/view_content.tmpl +++ b/templates/repo/view_content.tmpl @@ -1,5 +1,6 @@ {{$isTreePathRoot := not .TreeNames}} +
{{template "repo/sub_menu" .}}
diff --git a/templates/repo/view_file_tree.tmpl b/templates/repo/view_file_tree.tmpl index 1e737d09cdfbc..8aed05f346940 100644 --- a/templates/repo/view_file_tree.tmpl +++ b/templates/repo/view_file_tree.tmpl @@ -10,7 +10,6 @@ {{/* TODO: Dynamically move components such as refSelector and createPR here */}}
diff --git a/web_src/js/components/ViewFileTree.vue b/web_src/js/components/ViewFileTree.vue index 1bd52d7435b83..1f90f9258670c 100644 --- a/web_src/js/components/ViewFileTree.vue +++ b/web_src/js/components/ViewFileTree.vue @@ -7,7 +7,6 @@ const elRoot = useTemplateRef('elRoot'); const props = defineProps({ repoLink: {type: String, required: true}, - repoName: {type: String, required: true}, treePath: {type: String, required: true}, currentRefNameSubURL: {type: String, required: true}, }); diff --git a/web_src/js/components/ViewFileTreeStore.ts b/web_src/js/components/ViewFileTreeStore.ts index 63b585c743da5..2e78eb8b8b3e4 100644 --- a/web_src/js/components/ViewFileTreeStore.ts +++ b/web_src/js/components/ViewFileTreeStore.ts @@ -4,7 +4,7 @@ import {pathEscapeSegments} from '../utils/url.ts'; import {createElementFromHTML} from '../utils/dom.ts'; import {html} from '../utils/html.ts'; -export function createViewFileTreeStore(props: {repoLink: string, repoName: string, treePath: string, currentRefNameSubURL: string}) { +export function createViewFileTreeStore(props: {repoLink: string, treePath: string, currentRefNameSubURL: string}) { const store = reactive({ rootFiles: [], selectedItem: props.treePath, @@ -25,30 +25,24 @@ export function createViewFileTreeStore(props: {repoLink: string, repoName: stri }, async loadViewContent(url: string) { - url = url.includes('?') ? url.replace('?', '?only_content=true') : `${url}?only_content=true`; - const response = await GET(url); - document.querySelector('.repo-view-content').innerHTML = await response.text(); + const u = new URL(url, window.origin); + u.searchParams.set('only_content', '1'); + const response = await GET(u.href); + const elViewContent = document.querySelector('.repo-view-content'); + elViewContent.innerHTML = await response.text(); + document.title = elViewContent.querySelector('.repo-view-content-data').getAttribute('data-document-title'); }, async navigateTreeView(treePath: string) { const url = store.buildTreePathWebUrl(treePath); - const title = store.buildTitle(store.selectedItem, treePath); window.history.pushState({treePath, url}, null, url); store.selectedItem = treePath; await store.loadViewContent(url); - document.title = title; }, buildTreePathWebUrl(treePath: string) { return `${props.repoLink}/src/${props.currentRefNameSubURL}/${pathEscapeSegments(treePath)}`; }, - - buildTitle(oldTreePath: string, treePath: string) { - // the title always starts with "/" - const oldPrefixLength = props.repoName.length + 1 + oldTreePath.length; - const titleSuffix = document.title.substring(oldPrefixLength); - return `${props.repoName}/${treePath}${titleSuffix}`; - }, }); return store; } diff --git a/web_src/js/features/repo-view-file-tree.ts b/web_src/js/features/repo-view-file-tree.ts index 785ec35a5a4c5..f52b64cc51d19 100644 --- a/web_src/js/features/repo-view-file-tree.ts +++ b/web_src/js/features/repo-view-file-tree.ts @@ -31,7 +31,6 @@ export async function initRepoViewFileTree() { const fileTree = sidebar.querySelector('#view-file-tree'); createApp(ViewFileTree, { repoLink: fileTree.getAttribute('data-repo-link'), - repoName: fileTree.getAttribute('data-repo-name'), treePath: fileTree.getAttribute('data-tree-path'), currentRefNameSubURL: fileTree.getAttribute('data-current-ref-name-sub-url'), }).mount(fileTree); From 309307afba1db0ee5894a69baa99ada59760e935 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Wed, 29 Oct 2025 20:39:19 +0800 Subject: [PATCH 4/7] fix --- modules/web/middleware/data.go | 2 ++ services/context/repo.go | 1 + templates/base/head.tmpl | 2 +- templates/repo/view_content.tmpl | 2 +- web_src/js/components/ViewFileTreeStore.ts | 4 +++- 5 files changed, 8 insertions(+), 3 deletions(-) diff --git a/modules/web/middleware/data.go b/modules/web/middleware/data.go index a47da0f836b36..41fb1e7e6f64d 100644 --- a/modules/web/middleware/data.go +++ b/modules/web/middleware/data.go @@ -22,6 +22,8 @@ func GetContextData(c context.Context) reqctx.ContextData { func CommonTemplateContextData() reqctx.ContextData { return reqctx.ContextData{ + "PageTitleCommon": setting.AppName, + "IsLandingPageOrganizations": setting.LandingPageURL == setting.LandingPageOrganizations, "ShowRegistrationButton": setting.Service.ShowRegistrationButton, diff --git a/services/context/repo.go b/services/context/repo.go index 0ff1c7ea0337c..cfbfb33ab9ed1 100644 --- a/services/context/repo.go +++ b/services/context/repo.go @@ -537,6 +537,7 @@ func RepoAssignment(ctx *Context) { } ctx.Data["Title"] = repo.Owner.Name + "/" + repo.Name + ctx.Data["PageTitleCommon"] = repo.Name + " - " + setting.AppName ctx.Data["Repository"] = repo ctx.Data["Owner"] = ctx.Repo.Repository.Owner ctx.Data["CanWriteCode"] = ctx.Repo.CanWrite(unit_model.TypeCode) diff --git a/templates/base/head.tmpl b/templates/base/head.tmpl index 62bc625bda669..a3ea4c454d7c4 100644 --- a/templates/base/head.tmpl +++ b/templates/base/head.tmpl @@ -2,7 +2,7 @@ - {{if .Title}}{{.Title}} - {{end}}{{if .Repository.Name}}{{.Repository.Name}} - {{end}}{{AppName}} + {{if .Title}}{{.Title}} - {{end}}{{.PageTitleCommon}} {{if .ManifestData}}{{end}} diff --git a/templates/repo/view_content.tmpl b/templates/repo/view_content.tmpl index f1cc811847004..66e4fffcb9b19 100644 --- a/templates/repo/view_content.tmpl +++ b/templates/repo/view_content.tmpl @@ -1,6 +1,6 @@ {{$isTreePathRoot := not .TreeNames}} -
+
{{template "repo/sub_menu" .}}
diff --git a/web_src/js/components/ViewFileTreeStore.ts b/web_src/js/components/ViewFileTreeStore.ts index 2e78eb8b8b3e4..fd37b409920da 100644 --- a/web_src/js/components/ViewFileTreeStore.ts +++ b/web_src/js/components/ViewFileTreeStore.ts @@ -30,7 +30,9 @@ export function createViewFileTreeStore(props: {repoLink: string, treePath: stri const response = await GET(u.href); const elViewContent = document.querySelector('.repo-view-content'); elViewContent.innerHTML = await response.text(); - document.title = elViewContent.querySelector('.repo-view-content-data').getAttribute('data-document-title'); + const t1 = elViewContent.querySelector('.repo-view-content-data').getAttribute('data-document-title'); + const t2 = elViewContent.querySelector('.repo-view-content-data').getAttribute('data-document-title-common'); + document.title = `${t1} - ${t2}`; // follow the format in head.tmpl: ... }, async navigateTreeView(treePath: string) { From eda0b30f970f772894b1937532103182816d3ab4 Mon Sep 17 00:00:00 2001 From: silverwind Date: Wed, 29 Oct 2025 13:46:17 +0100 Subject: [PATCH 5/7] Update web_src/js/components/ViewFileTreeStore.ts Signed-off-by: silverwind --- web_src/js/components/ViewFileTreeStore.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_src/js/components/ViewFileTreeStore.ts b/web_src/js/components/ViewFileTreeStore.ts index fd37b409920da..013d83e0005fa 100644 --- a/web_src/js/components/ViewFileTreeStore.ts +++ b/web_src/js/components/ViewFileTreeStore.ts @@ -26,7 +26,7 @@ export function createViewFileTreeStore(props: {repoLink: string, treePath: stri async loadViewContent(url: string) { const u = new URL(url, window.origin); - u.searchParams.set('only_content', '1'); + u.searchParams.set('only_content', 'true'); const response = await GET(u.href); const elViewContent = document.querySelector('.repo-view-content'); elViewContent.innerHTML = await response.text(); From 3e1593aa0350a357671baf7109855a525ca4fa23 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Wed, 29 Oct 2025 20:48:57 +0800 Subject: [PATCH 6/7] only query once --- web_src/js/components/ViewFileTreeStore.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/web_src/js/components/ViewFileTreeStore.ts b/web_src/js/components/ViewFileTreeStore.ts index 013d83e0005fa..6f46d12e9bf41 100644 --- a/web_src/js/components/ViewFileTreeStore.ts +++ b/web_src/js/components/ViewFileTreeStore.ts @@ -30,8 +30,9 @@ export function createViewFileTreeStore(props: {repoLink: string, treePath: stri const response = await GET(u.href); const elViewContent = document.querySelector('.repo-view-content'); elViewContent.innerHTML = await response.text(); - const t1 = elViewContent.querySelector('.repo-view-content-data').getAttribute('data-document-title'); - const t2 = elViewContent.querySelector('.repo-view-content-data').getAttribute('data-document-title-common'); + const elViewContentData = elViewContent.querySelector('.repo-view-content-data'); + const t1 = elViewContentData.getAttribute('data-document-title'); + const t2 = elViewContentData.getAttribute('data-document-title-common'); document.title = `${t1} - ${t2}`; // follow the format in head.tmpl: ... }, From 497012d303c1dff8a0bcf3115e418a794b600b91 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Wed, 29 Oct 2025 20:50:34 +0800 Subject: [PATCH 7/7] handling edge case --- web_src/js/components/ViewFileTreeStore.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/web_src/js/components/ViewFileTreeStore.ts b/web_src/js/components/ViewFileTreeStore.ts index 6f46d12e9bf41..61d9168aa6c3a 100644 --- a/web_src/js/components/ViewFileTreeStore.ts +++ b/web_src/js/components/ViewFileTreeStore.ts @@ -31,6 +31,7 @@ export function createViewFileTreeStore(props: {repoLink: string, treePath: stri const elViewContent = document.querySelector('.repo-view-content'); elViewContent.innerHTML = await response.text(); const elViewContentData = elViewContent.querySelector('.repo-view-content-data'); + if (!elViewContentData) return; // if error occurs, there is no such element const t1 = elViewContentData.getAttribute('data-document-title'); const t2 = elViewContentData.getAttribute('data-document-title-common'); document.title = `${t1} - ${t2}`; // follow the format in head.tmpl: ...