@f5-sales-demo/docs-theme 4.11.4 → 4.11.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -119,3 +119,79 @@ describe('buildSubcategorySidebar without subcategories', () => {
|
|
|
119
119
|
]);
|
|
120
120
|
});
|
|
121
121
|
});
|
|
122
|
+
|
|
123
|
+
describe('authored sidebar metadata', () => {
|
|
124
|
+
async function fixture(docs: Record<string, string>) {
|
|
125
|
+
const root = await mkdtemp(path.join(tmpdir(), 'docs-theme-metadata-'));
|
|
126
|
+
workspaces.push(root);
|
|
127
|
+
for (const [file, metadata] of Object.entries(docs)) {
|
|
128
|
+
const destination = path.join(root, file);
|
|
129
|
+
await mkdir(path.dirname(destination), { recursive: true });
|
|
130
|
+
await writeFile(destination, `---\ntitle: Article title\n${metadata}\n---\nContent.\n`);
|
|
131
|
+
}
|
|
132
|
+
return buildSubcategorySidebar(root);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
it('uses authored root and nested index labels without Overview translations', async () => {
|
|
136
|
+
const sidebar = await fixture({
|
|
137
|
+
'index.mdx': 'sidebar:\n label: Home\n order: 0',
|
|
138
|
+
'plugins/index.mdx': 'sidebar:\n label: Plugin catalog\n order: 1',
|
|
139
|
+
'plugins/tool.mdx': 'sidebar:\n label: Review documents',
|
|
140
|
+
});
|
|
141
|
+
expect(sidebar).toEqual([
|
|
142
|
+
{ label: 'Home', link: '/' },
|
|
143
|
+
{
|
|
144
|
+
label: 'Plugin catalog',
|
|
145
|
+
collapsed: true,
|
|
146
|
+
items: [
|
|
147
|
+
{ label: 'Plugin catalog', slug: 'plugins' },
|
|
148
|
+
{ label: 'Review documents', slug: 'plugins/tool' },
|
|
149
|
+
],
|
|
150
|
+
},
|
|
151
|
+
]);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('omits hidden root and nested pages while keeping children of hidden indexes', async () => {
|
|
155
|
+
const sidebar = await fixture({
|
|
156
|
+
'index.mdx': 'sidebar:\n hidden: true',
|
|
157
|
+
'hidden.mdx': 'sidebar:\n hidden: true',
|
|
158
|
+
'plugins/index.mdx': 'sidebar:\n label: Plugins\n hidden: true',
|
|
159
|
+
'plugins/hidden.mdx': 'sidebar:\n hidden: true',
|
|
160
|
+
'plugins/deep/index.mdx': 'sidebar:\n hidden: true',
|
|
161
|
+
'plugins/deep/visible.mdx': '',
|
|
162
|
+
'empty/index.mdx': 'sidebar:\n hidden: true',
|
|
163
|
+
});
|
|
164
|
+
expect(sidebar).toEqual([
|
|
165
|
+
{
|
|
166
|
+
label: 'Plugins',
|
|
167
|
+
collapsed: true,
|
|
168
|
+
translations: expect.any(Object),
|
|
169
|
+
items: [{ label: 'Article title', collapsed: true, items: [{ slug: 'plugins/deep/visible' }] }],
|
|
170
|
+
},
|
|
171
|
+
]);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('honors explicit order and alphabetic fallback independently of filenames', async () => {
|
|
175
|
+
const sidebar = await fixture({
|
|
176
|
+
'index.mdx': 'sidebar:\n label: Home\n order: 2',
|
|
177
|
+
'last.mdx': 'sidebar:\n label: First\n order: 1',
|
|
178
|
+
'alpha.mdx': 'sidebar:\n label: Zebra',
|
|
179
|
+
'zebra.mdx': 'sidebar:\n label: Alpha',
|
|
180
|
+
});
|
|
181
|
+
expect(sidebar).toEqual([
|
|
182
|
+
{ label: 'First', slug: 'last' },
|
|
183
|
+
{ label: 'Home', link: '/' },
|
|
184
|
+
{ label: 'Alpha', slug: 'zebra' },
|
|
185
|
+
{ label: 'Zebra', slug: 'alpha' },
|
|
186
|
+
]);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('ignores blank labels and nonboolean hidden values', async () => {
|
|
190
|
+
const sidebar = await fixture({
|
|
191
|
+
'index.mdx': 'sidebar:\n label: " "\n hidden: "true"',
|
|
192
|
+
'page.mdx': 'sidebar:\n label: " "\n hidden: false',
|
|
193
|
+
});
|
|
194
|
+
expect(sidebar?.[0]).toMatchObject({ label: 'Overview', link: '/', translations: expect.any(Object) });
|
|
195
|
+
expect(sidebar?.[1]).toEqual({ slug: 'page' });
|
|
196
|
+
});
|
|
197
|
+
});
|
|
@@ -84,7 +84,14 @@ function kebabToTitleCase(kebab: string): string {
|
|
|
84
84
|
.join(' ');
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
|
|
87
|
+
interface NavigationMetadata {
|
|
88
|
+
title: string;
|
|
89
|
+
label?: string;
|
|
90
|
+
hidden: boolean;
|
|
91
|
+
order: number | undefined;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function readNavigationMetadata(filePath: string, fallbackTitle: string): NavigationMetadata {
|
|
88
95
|
try {
|
|
89
96
|
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
90
97
|
const frontmatter = matter(raw).data as Record<string, unknown>;
|
|
@@ -103,15 +110,24 @@ function readNavigationMetadata(filePath: string, fallbackTitle: string): { titl
|
|
|
103
110
|
Number.isFinite(sidebar.order)
|
|
104
111
|
? sidebar.order
|
|
105
112
|
: undefined;
|
|
106
|
-
|
|
113
|
+
const label =
|
|
114
|
+
typeof sidebar === 'object' &&
|
|
115
|
+
sidebar !== null &&
|
|
116
|
+
'label' in sidebar &&
|
|
117
|
+
typeof sidebar.label === 'string' &&
|
|
118
|
+
sidebar.label.trim()
|
|
119
|
+
? sidebar.label.trim()
|
|
120
|
+
: undefined;
|
|
121
|
+
const hidden = typeof sidebar === 'object' && sidebar !== null && 'hidden' in sidebar && sidebar.hidden === true;
|
|
122
|
+
return { title, label, hidden, order };
|
|
107
123
|
} catch {
|
|
108
|
-
return { title: fallbackTitle, order: undefined };
|
|
124
|
+
return { title: fallbackTitle, hidden: false, order: undefined };
|
|
109
125
|
}
|
|
110
126
|
}
|
|
111
127
|
|
|
112
128
|
function compareOrderedSidebarItems(a: OrderedSidebarItem, b: OrderedSidebarItem): number {
|
|
113
129
|
const orderDifference = (a.order ?? Number.POSITIVE_INFINITY) - (b.order ?? Number.POSITIVE_INFINITY);
|
|
114
|
-
if (orderDifference !== 0) return orderDifference;
|
|
130
|
+
if (!Number.isNaN(orderDifference) && orderDifference !== 0) return orderDifference;
|
|
115
131
|
const labelDifference = a.sortLabel.localeCompare(b.sortLabel);
|
|
116
132
|
return labelDifference !== 0 ? labelDifference : a.sortPath.localeCompare(b.sortPath);
|
|
117
133
|
}
|
|
@@ -131,7 +147,7 @@ function buildDirectorySidebar(dirPath: string, scanDir: string): OrderedSidebar
|
|
|
131
147
|
const indexPath = indexEntry ? path.join(dirPath, indexEntry.name) : undefined;
|
|
132
148
|
const metadata = indexPath
|
|
133
149
|
? readNavigationMetadata(indexPath, fallbackLabel)
|
|
134
|
-
: { title: fallbackLabel, order: undefined };
|
|
150
|
+
: { title: fallbackLabel, hidden: false, order: undefined };
|
|
135
151
|
const children: OrderedSidebarItem[] = [];
|
|
136
152
|
|
|
137
153
|
for (const entry of entries) {
|
|
@@ -149,36 +165,38 @@ function buildDirectorySidebar(dirPath: string, scanDir: string): OrderedSidebar
|
|
|
149
165
|
const slug = filePathToSlug(relativePath).replace(/^\/|\/$/g, '');
|
|
150
166
|
const fallbackTitle = path.basename(entry.name, path.extname(entry.name)).replace(/[-_]/g, ' ');
|
|
151
167
|
const page = readNavigationMetadata(fullPath, fallbackTitle);
|
|
168
|
+
if (page.hidden) continue;
|
|
152
169
|
children.push({
|
|
153
|
-
item: { slug },
|
|
170
|
+
item: { slug, ...(page.label ? { label: page.label } : {}) },
|
|
154
171
|
order: page.order,
|
|
155
|
-
sortLabel: page.title,
|
|
172
|
+
sortLabel: page.label ?? page.title,
|
|
156
173
|
sortPath: relativePath,
|
|
157
174
|
});
|
|
158
175
|
}
|
|
159
176
|
|
|
160
177
|
children.sort(compareOrderedSidebarItems);
|
|
161
178
|
const items: SidebarItem[] = [];
|
|
162
|
-
if (indexPath) {
|
|
179
|
+
if (indexPath && !metadata.hidden) {
|
|
163
180
|
items.push({
|
|
164
|
-
label: 'Overview',
|
|
181
|
+
label: metadata.label ?? 'Overview',
|
|
165
182
|
slug: filePathToSlug(path.relative(scanDir, indexPath)).replace(/^\/|\/$/g, ''),
|
|
166
|
-
translations: sidebarTranslations.Overview,
|
|
183
|
+
...(!metadata.label ? { translations: sidebarTranslations.Overview } : {}),
|
|
167
184
|
});
|
|
168
185
|
}
|
|
169
186
|
items.push(...children.map((child) => child.item));
|
|
170
187
|
if (items.length === 0) return undefined;
|
|
171
188
|
|
|
172
|
-
const
|
|
189
|
+
const groupLabel = metadata.label ?? metadata.title;
|
|
190
|
+
const translations = sidebarTranslations[groupLabel as keyof typeof sidebarTranslations];
|
|
173
191
|
return {
|
|
174
192
|
item: {
|
|
175
|
-
label:
|
|
193
|
+
label: groupLabel,
|
|
176
194
|
collapsed: true,
|
|
177
195
|
...(translations ? { translations } : {}),
|
|
178
196
|
items,
|
|
179
197
|
},
|
|
180
198
|
order: metadata.order,
|
|
181
|
-
sortLabel:
|
|
199
|
+
sortLabel: groupLabel,
|
|
182
200
|
sortPath: relativeDir,
|
|
183
201
|
};
|
|
184
202
|
}
|
|
@@ -225,6 +243,7 @@ export function buildSubcategorySidebar(contentDir: string): SidebarItem[] | und
|
|
|
225
243
|
const docs: DocEntry[] = [];
|
|
226
244
|
let hasAnySubcategory = false;
|
|
227
245
|
let hasOverview = false;
|
|
246
|
+
let overviewMetadata: NavigationMetadata = { title: 'Overview', hidden: false, order: undefined };
|
|
228
247
|
|
|
229
248
|
for (const filePath of files) {
|
|
230
249
|
const relativePath = path.relative(scanDir, filePath).replace(/\\/g, '/');
|
|
@@ -232,7 +251,8 @@ export function buildSubcategorySidebar(contentDir: string): SidebarItem[] | und
|
|
|
232
251
|
|
|
233
252
|
// Track overview page separately
|
|
234
253
|
if (slug === '/') {
|
|
235
|
-
|
|
254
|
+
overviewMetadata = readNavigationMetadata(filePath, 'Overview');
|
|
255
|
+
hasOverview = !overviewMetadata.hidden;
|
|
236
256
|
continue;
|
|
237
257
|
}
|
|
238
258
|
|
|
@@ -262,7 +282,9 @@ export function buildSubcategorySidebar(contentDir: string): SidebarItem[] | und
|
|
|
262
282
|
} else {
|
|
263
283
|
title = path.basename(filePath, path.extname(filePath)).replace(/[-_]/g, ' ');
|
|
264
284
|
}
|
|
265
|
-
|
|
285
|
+
const navigation = readNavigationMetadata(filePath, title);
|
|
286
|
+
if (navigation.hidden) continue;
|
|
287
|
+
title = navigation.label ?? cleanPageTitle(title, docType);
|
|
266
288
|
|
|
267
289
|
const subcategory =
|
|
268
290
|
typeof frontmatter.subcategory === 'string' && frontmatter.subcategory.trim()
|
|
@@ -298,18 +320,25 @@ export function buildSubcategorySidebar(contentDir: string): SidebarItem[] | und
|
|
|
298
320
|
const slug = filePathToSlug(entry.name).replace(/^\/|\/$/g, '');
|
|
299
321
|
const fallbackTitle = path.basename(entry.name, path.extname(entry.name)).replace(/[-_]/g, ' ');
|
|
300
322
|
const page = readNavigationMetadata(fullPath, fallbackTitle);
|
|
301
|
-
|
|
323
|
+
if (page.hidden) continue;
|
|
324
|
+
orderedItems.push({
|
|
325
|
+
item: { slug, ...(page.label ? { label: page.label } : {}) },
|
|
326
|
+
order: page.order,
|
|
327
|
+
sortLabel: page.label ?? page.title,
|
|
328
|
+
sortPath: entry.name,
|
|
329
|
+
});
|
|
302
330
|
}
|
|
303
331
|
|
|
304
332
|
if (hasOverview) {
|
|
305
|
-
const
|
|
306
|
-
const overview = overviewEntry
|
|
307
|
-
? readNavigationMetadata(path.join(scanDir, overviewEntry.name), 'Overview')
|
|
308
|
-
: { title: 'Overview', order: undefined };
|
|
333
|
+
const overview = overviewMetadata;
|
|
309
334
|
orderedItems.push({
|
|
310
|
-
item: {
|
|
335
|
+
item: {
|
|
336
|
+
label: overview.label ?? 'Overview',
|
|
337
|
+
link: '/',
|
|
338
|
+
...(!overview.label ? { translations: sidebarTranslations.Overview } : {}),
|
|
339
|
+
},
|
|
311
340
|
order: overview.order ?? Number.NEGATIVE_INFINITY,
|
|
312
|
-
sortLabel: overview.title,
|
|
341
|
+
sortLabel: overview.label ?? overview.title,
|
|
313
342
|
sortPath: '',
|
|
314
343
|
});
|
|
315
344
|
}
|
|
@@ -332,7 +361,11 @@ export function buildSubcategorySidebar(contentDir: string): SidebarItem[] | und
|
|
|
332
361
|
|
|
333
362
|
// 1. Overview link
|
|
334
363
|
if (hasOverview) {
|
|
335
|
-
sidebar.push({
|
|
364
|
+
sidebar.push({
|
|
365
|
+
label: overviewMetadata.label ?? 'Overview',
|
|
366
|
+
link: '/',
|
|
367
|
+
...(!overviewMetadata.label ? { translations: sidebarTranslations.Overview } : {}),
|
|
368
|
+
});
|
|
336
369
|
}
|
|
337
370
|
|
|
338
371
|
// 2. Guides group
|