Skip to content

Commit 0792c38

Browse files
committed
docs: let a section's redirect stub still group and title its section
Eight of the twelve docs sections were missing from the sidebar's grouping: admin, cloud, contribute, device-agent, hardware, install, migration and user. They appeared under a catch-all "Other" heading, titled by raw path segment ("user", "admin"), and the Device Agent, FlowFuse Cloud and Contributing groups came out empty and were filtered away, so they did not render at all. The cause: each of those section index pages is a `layout: redirect` stub pointing at the section's introduction page, and buildDocsNav dropped every page carrying `redirect`. Dropping them was right as far as linking goes - a sidebar entry pointing at a URL that 301s makes nuxt-link-checker's `redirects` inspection flag every docs page - but it also threw away the navGroup, navGroupOrder, navTitle and navOrder those pages carry. With the index gone, the section node was built only from the paths of its children, which have no group of their own. So the frontmatter is kept and applied to the node the children create, and only the link is withheld. `link: false` marks such a node as a label rather than a destination; navigationMenu omits `to` for it. The node keeps its own path, which is what the menu's auto-expand and the breadcrumb trail match on, so both now work through a stubbed section and breadcrumbs read "Using FlowFuse" rather than "user". A stub with no pages beneath it still contributes nothing, so leaf redirects like docs/community-support.md do not become dead, unclickable labels. This is a fix to the nav builder only. Every group name, order and title it now honours was already declared in FlowFuse/flowfuse; nothing changes there.
1 parent fba0972 commit 0792c38

4 files changed

Lines changed: 136 additions & 3 deletions

File tree

‎nuxt/composables/useDocsNav.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ export interface DocsNavNode {
1010
group?: string
1111
groupOrder?: number
1212
order: number
13+
// false when this section's index page is a redirect stub - see nuxt/lib/docs-nav.mjs.
14+
link?: boolean
1315
children: DocsNavNode[]
1416
}
1517

‎nuxt/lib/docs-nav.mjs‎

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,33 @@
1010

1111
import { findPageBreadcrumb } from '@nuxt/content/utils'
1212

13+
const stripSlash = path => (path.endsWith('/') ? path.slice(0, -1) : path) || '/'
14+
15+
/**
16+
* Give each node the frontmatter of the redirect stub sitting at its path, if there is one.
17+
*
18+
* `link: false` marks it as a label rather than a destination: nuxt/utils/navigationMenu.ts
19+
* omits `to` for those, so the branch is still titled, grouped and expandable without the
20+
* sidebar ever pointing at a URL that 301s. The node keeps its own path, which is what the
21+
* menu's auto-expand and the breadcrumb trail match on.
22+
*
23+
* @param {Array<{path: string, title: string, group?: string, groupOrder?: number, order: number, children: Array}>} nodes
24+
* @param {Map<string, {navTitle?: string|null, title?: string|null, navGroup?: string|null, navGroupOrder?: number|null, navOrder?: number|null}>} stubs
25+
*/
26+
function applyStubFrontmatter (nodes, stubs) {
27+
for (const node of nodes) {
28+
const stub = stubs.get(stripSlash(node.path))
29+
if (stub) {
30+
node.title = stub.navTitle || stub.title || node.title
31+
if (stub.navGroup != null) node.group = stub.navGroup
32+
if (stub.navGroupOrder != null) node.groupOrder = stub.navGroupOrder
33+
if (stub.navOrder != null) node.order = stub.navOrder
34+
node.link = false
35+
}
36+
applyStubFrontmatter(node.children, stubs)
37+
}
38+
}
39+
1340
/**
1441
* @param {Array<{path: string, title?: string|null, navTitle?: string|null, navOrder?: number|null, navGroup?: string|null, navGroupOrder?: number|null, redirect?: {to: string}|null}>} pages
1542
*/
@@ -19,8 +46,24 @@ export function buildDocsNav (pages) {
1946
// A page whose only purpose is `redirect: { to }` (e.g. FlowFuse/flowfuse's
2047
// docs/admin/licensing.md and docs/community-support.md) has no content of its own to
2148
// link to from the sidebar. Rendering it as a nav entry means every single docs page
22-
// gets flagged by nuxt-link-checker's `redirects` inspection, so it's left out.
23-
const linkable = pages.filter(page => !page.redirect)
49+
// gets flagged by nuxt-link-checker's `redirects` inspection, so it never becomes a
50+
// link target.
51+
//
52+
// Its frontmatter still matters though. Most section index pages in FlowFuse/flowfuse
53+
// are redirect stubs (docs/user, docs/install, docs/admin, docs/cloud, docs/device-agent,
54+
// docs/hardware, docs/migration, docs/contribute), and each one carries the navGroup,
55+
// navGroupOrder, navTitle and navOrder that its whole section is grouped, labelled and
56+
// ranked by. Discarding the page discarded that too, so those sections were built only
57+
// from the paths of their children: no group (they fell into "Other"), and titled by raw
58+
// path segment ("user", "admin"). Three groups had every member stubbed and vanished
59+
// entirely. So the metadata is kept here and applied to the node the children create,
60+
// and only the link is withheld.
61+
const linkable = []
62+
const stubs = new Map()
63+
for (const page of pages) {
64+
if (page.redirect) stubs.set(stripSlash(page.path), page)
65+
else linkable.push(page)
66+
}
2467

2568
const sorted = [...linkable].sort((a, b) => {
2669
const depthA = a.path.split('/').filter(Boolean).length
@@ -76,6 +119,9 @@ export function buildDocsNav (pages) {
76119
}
77120

78121
const root = toDocsNavNodes(tree)
122+
// Before grouping and sorting, which both read these fields.
123+
applyStubFrontmatter(root, stubs)
124+
79125
const docsRoot = root.find(n => n.path === '/docs')
80126
if (!docsRoot) return []
81127

‎nuxt/lib/docs-nav.test.mjs‎

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,3 +126,84 @@ test('findDocsBreadcrumb returns nothing for an unknown path', () => {
126126
const nav = buildDocsNav([section('/docs/user', { group: 'User Manuals', groupOrder: 1 })])
127127
assert.deepEqual(findDocsBreadcrumb(nav, '/docs/nonexistent'), [])
128128
})
129+
130+
// Most section index pages in FlowFuse/flowfuse are `layout: redirect` stubs pointing at
131+
// the section's introduction page. They still carry the frontmatter the section is
132+
// grouped, labelled and ranked by.
133+
function redirectStub (path, { group, groupOrder, order, navTitle } = {}) {
134+
return { ...section(path, { group, groupOrder, order, navTitle }), redirect: { to: path + '/introduction' } }
135+
}
136+
137+
test('a redirect stub still groups, titles and ranks the section its children create', () => {
138+
const nav = buildDocsNav([
139+
redirectStub('/docs/user', { group: 'User Manuals', groupOrder: 1, order: 1, navTitle: 'Using FlowFuse' }),
140+
section('/docs/user/introduction', { order: 1, navTitle: 'Introduction' }),
141+
section('/docs/user/concepts', { order: 2, navTitle: 'Concepts' }),
142+
])
143+
144+
assert.deepEqual(names(nav), ['User Manuals'])
145+
const user = nav[0].children[0]
146+
assert.equal(user.title, 'Using FlowFuse')
147+
assert.equal(user.path, '/docs/user')
148+
assert.deepEqual(user.children.map(c => c.title), ['Introduction', 'Concepts'])
149+
})
150+
151+
test('a redirect stub is not a link target, so the sidebar never points at a 301', () => {
152+
const nav = buildDocsNav([
153+
redirectStub('/docs/user', { group: 'User Manuals', groupOrder: 1, navTitle: 'Using FlowFuse' }),
154+
section('/docs/user/introduction', { order: 1, navTitle: 'Introduction' }),
155+
])
156+
157+
assert.equal(nav[0].children[0].link, false)
158+
// A real page in the same position stays linkable.
159+
const real = buildDocsNav([section('/docs/quick-start', { group: 'G', groupOrder: 1 })])
160+
assert.notEqual(real[0].children[0].link, false)
161+
})
162+
163+
test('a group whose sections are all redirect stubs still renders', () => {
164+
// Device Agent, FlowFuse Cloud and Contributing each had every member stubbed, so the
165+
// group came out empty and was filtered away entirely.
166+
const nav = buildDocsNav([
167+
redirectStub('/docs/device-agent', { group: 'Device Agent', groupOrder: 2, order: 1, navTitle: 'Device Agent' }),
168+
section('/docs/device-agent/quickstart', { order: 1, navTitle: 'Quickstart' }),
169+
redirectStub('/docs/hardware', { group: 'Device Agent', groupOrder: 2, order: 2, navTitle: 'Hardware Guides' }),
170+
section('/docs/hardware/raspbian', { order: 1, navTitle: 'Raspberry Pi' }),
171+
])
172+
173+
assert.deepEqual(names(nav), ['Device Agent'])
174+
assert.deepEqual(nav[0].children.map(c => c.title), ['Device Agent', 'Hardware Guides'])
175+
})
176+
177+
test('a stubbed section no longer falls into Other titled by its path segment', () => {
178+
const nav = buildDocsNav([
179+
redirectStub('/docs/admin', { group: 'Self-Hosted', groupOrder: 4, order: 4, navTitle: 'Administering FlowFuse' }),
180+
section('/docs/admin/introduction', { order: 1, navTitle: 'Introduction' }),
181+
section('/docs/quick-start', { group: 'Self-Hosted', groupOrder: 4, order: 1, navTitle: 'Quick Start' }),
182+
])
183+
184+
assert.deepEqual(names(nav), ['Self-Hosted'])
185+
assert.deepEqual(nav[0].children.map(c => c.title), ['Quick Start', 'Administering FlowFuse'])
186+
})
187+
188+
test('a redirect stub with no pages beneath it contributes nothing', () => {
189+
// docs/admin/licensing.md and docs/community-support.md are leaf redirects with no
190+
// children; they should not appear as dead, unclickable labels.
191+
const nav = buildDocsNav([
192+
section('/docs/debugging', { group: 'Support', groupOrder: 5, navTitle: 'Debugging' }),
193+
redirectStub('/docs/community-support', { group: 'Support', groupOrder: 5 }),
194+
])
195+
196+
assert.deepEqual(nav[0].children.map(c => c.title), ['Debugging'])
197+
})
198+
199+
test('breadcrumbs through a stubbed section use its real title', () => {
200+
const nav = buildDocsNav([
201+
redirectStub('/docs/user', { group: 'User Manuals', groupOrder: 1, navTitle: 'Using FlowFuse' }),
202+
section('/docs/user/concepts', { order: 1, navTitle: 'Concepts' }),
203+
])
204+
205+
assert.deepEqual(
206+
findDocsBreadcrumb(nav, '/docs/user/concepts').map(c => c.title),
207+
['Using FlowFuse', 'Concepts'],
208+
)
209+
})

‎nuxt/utils/navigationMenu.ts‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ export interface MenuTreeNode {
77
title: string
88
path: string
99
icon?: string
10+
// false for a branch whose index page only redirects (see nuxt/lib/docs-nav.mjs): it
11+
// still titles, groups and expands the branch, but must not be a link target. `path`
12+
// stays set either way - auto-expand below matches on it.
13+
link?: boolean
1014
children?: MenuTreeNode[]
1115
}
1216

@@ -22,7 +26,7 @@ export function buildNavigationMenuItems(nodes: MenuTreeNode[], currentPath: str
2226
const children = node.children?.length ? buildNavigationMenuItems(node.children, currentPath) : undefined
2327
return {
2428
label: node.title,
25-
to: node.path,
29+
...(node.link === false ? {} : { to: node.path }),
2630
icon: node.icon,
2731
defaultOpen: children ? isOrContains(node.path, currentPath) : undefined,
2832
children,

0 commit comments

Comments
 (0)