From 63423bbd850aa598ece78c9905a23d4f05aeb515 Mon Sep 17 00:00:00 2001 From: Daniel Duma Date: Sat, 27 Dec 2025 21:31:09 +0300 Subject: [PATCH] feat(breadcrumbs): show Title in breadcrumbs, fall back to file names - Added a new state property for breadcrumbs in Vuex store. - Updated page component to accept initial breadcrumbs as a prop and store them in Vuex. - Implemented breadcrumb generation logic in the server controller to build breadcrumb paths based on the page structure. - Modified the Pug template to pass the generated breadcrumbs to the page component. This enhances navigation by providing users with contextual links to their current location within the site. --- client/store/page.js | 2 ++ client/themes/default/components/page.vue | 19 +++++++------- server/controllers/common.js | 31 ++++++++++++++++++++++- server/views/page.pug | 3 ++- 4 files changed, 43 insertions(+), 12 deletions(-) diff --git a/client/store/page.js b/client/store/page.js index 92979647b8..d1b990c3a3 100644 --- a/client/store/page.js +++ b/client/store/page.js @@ -1,3 +1,4 @@ + import { make } from 'vuex-pathify' const state = { @@ -16,6 +17,7 @@ const state = { updatedAt: '', editor: '', mode: '', + breadcrumbs: [], scriptJs: '', scriptCss: '', effectivePermissions: { diff --git a/client/themes/default/components/page.vue b/client/themes/default/components/page.vue index 0d1f6473fa..fc3ecf21a7 100644 --- a/client/themes/default/components/page.vue +++ b/client/themes/default/components/page.vue @@ -489,6 +489,10 @@ export default { filename: { type: String, default: '' + }, + initialBreadcrumbs: { + type: String, + default: '' } }, data() { @@ -528,6 +532,7 @@ export default { commentsCount: get('page/commentsCount'), commentsPerms: get('page/effectivePermissions@comments'), editShortcutsObj: get('page/editShortcuts'), + breadcrumbs: get('page/breadcrumbs'), rating: { get () { return 3.5 @@ -536,15 +541,6 @@ export default { } }, - breadcrumbs() { - return [{ path: '/', name: 'Home' }].concat(_.reduce(this.path.split('/'), (result, value, key) => { - result.push({ - path: _.get(_.last(result), 'path', `/${this.locale}`) + `/${value}`, - name: value - }) - return result - }, [])) - }, pageUrl () { return window.location.href }, upBtnPosition () { if (this.$vuetify.breakpoint.mdAndUp) { @@ -598,6 +594,9 @@ export default { if (this.editShortcuts) { this.$store.set('page/editShortcuts', JSON.parse(Buffer.from(this.editShortcuts, 'base64').toString())) } + if (this.initialBreadcrumbs) { + this.$store.set('page/breadcrumbs', JSON.parse(Buffer.from(this.initialBreadcrumbs, 'base64').toString())) + } this.$store.set('page/mode', 'view') }, @@ -789,4 +788,4 @@ export default { } } - + \ No newline at end of file diff --git a/server/controllers/common.js b/server/controllers/common.js index dec6fa3aa7..a1a53f7ed1 100644 --- a/server/controllers/common.js +++ b/server/controllers/common.js @@ -478,6 +478,32 @@ router.get('/*', async (req, res, next) => { }) } + // -> Build breadcrumbs + const breadcrumbPaths = [] + let currentBreadcrumbPath = '' + for (const part of page.path.split('/')) { + currentBreadcrumbPath = currentBreadcrumbPath ? `${currentBreadcrumbPath}/${part}` : part + breadcrumbPaths.push(currentBreadcrumbPath) + } + const breadcrumbTitles = _.fromPairs((await WIKI.models.knex.table('pageTree') + .where('localeCode', page.localeCode) + .whereIn('path', breadcrumbPaths) + .select('path', 'title')).map(t => [t.path, t.title])) + + const breadcrumbs = [{ path: '/', name: 'Home' }] + let currentPath = '' + for (const part of page.path.split('/')) { + currentPath = currentPath ? `${currentPath}/${part}` : part + breadcrumbs.push({ + path: `/${page.localeCode}/${currentPath}`, + name: breadcrumbTitles[currentPath] || part + }) + } + // Use the actual page title for the last segment if available + if (page.title && page.title !== 'Untitled Page') { + breadcrumbs[breadcrumbs.length - 1].name = page.title + } + // -> Build sidebar navigation let sdi = 1 const sidebar = (await WIKI.models.navigation.getTree({ cache: true, locale: pageArgs.locale, groups: req.user.groups })).map(n => ({ @@ -518,6 +544,7 @@ router.get('/*', async (req, res, next) => { page, sidebar, injectCode, + breadcrumbs, isAuthenticated: req.user && req.user.id !== 2 }) } else { @@ -529,6 +556,7 @@ router.get('/*', async (req, res, next) => { // -> Inject comments variables const commentTmpl = { codeTemplate: WIKI.data.commentProvider.codeTemplate, + providerKey: WIKI.data.commentProvider.key, head: WIKI.data.commentProvider.head, body: WIKI.data.commentProvider.body, main: WIKI.data.commentProvider.main @@ -553,6 +581,7 @@ router.get('/*', async (req, res, next) => { page, sidebar, injectCode, + breadcrumbs, comments: commentTmpl, effectivePermissions, pageFilename @@ -581,4 +610,4 @@ router.get('/*', async (req, res, next) => { } }) -module.exports = router +module.exports = router \ No newline at end of file diff --git a/server/views/page.pug b/server/views/page.pug index 32649fbe74..47063f3806 100644 --- a/server/views/page.pug +++ b/server/views/page.pug @@ -21,7 +21,7 @@ block body author-name=page.authorName :author-id=page.authorId editor=page.editorKey - :is-published=page.isPublished.toString() + :is-published=(page.isPublished ? 'true' : 'false') toc=Buffer.from(page.toc).toString('base64') :page-id=page.id sidebar=Buffer.from(JSON.stringify(sidebar)).toString('base64') @@ -30,6 +30,7 @@ block body effective-permissions=Buffer.from(JSON.stringify(effectivePermissions)).toString('base64') comments-external=comments.codeTemplate edit-shortcuts=Buffer.from(JSON.stringify(config.editShortcuts)).toString('base64') + initial-breadcrumbs=Buffer.from(JSON.stringify(breadcrumbs)).toString('base64') filename=pageFilename ) template(slot='contents')