@docusaurus/plugin-content-docs 0.0.0-4240 → 0.0.0-4244
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/lib/.tsbuildinfo +1 -1
- package/lib/categoryGeneratedIndex.d.ts +12 -0
- package/lib/categoryGeneratedIndex.js +37 -0
- package/lib/cli.js +5 -23
- package/lib/docs.d.ts +22 -2
- package/lib/docs.js +71 -29
- package/lib/index.js +34 -62
- package/lib/options.js +2 -0
- package/lib/props.js +35 -6
- package/lib/routes.d.ts +27 -0
- package/lib/routes.js +105 -0
- package/lib/sidebars/generator.d.ts +2 -1
- package/lib/sidebars/generator.js +55 -13
- package/lib/sidebars/index.d.ts +5 -4
- package/lib/sidebars/index.js +18 -9
- package/lib/sidebars/normalization.d.ts +8 -3
- package/lib/sidebars/normalization.js +36 -17
- package/lib/sidebars/processor.d.ts +5 -3
- package/lib/sidebars/processor.js +33 -18
- package/lib/sidebars/types.d.ts +43 -2
- package/lib/sidebars/utils.d.ts +18 -6
- package/lib/sidebars/utils.js +149 -24
- package/lib/sidebars/validation.d.ts +2 -0
- package/lib/sidebars/validation.js +44 -8
- package/lib/slug.d.ts +4 -3
- package/lib/slug.js +26 -14
- package/lib/translations.js +51 -7
- package/lib/types.d.ts +18 -3
- package/package.json +8 -8
- package/src/__tests__/__fixtures__/versioned-site/versioned_sidebars/version-1.0.1-sidebars.json +2 -2
- package/src/__tests__/__snapshots__/cli.test.ts.snap +48 -106
- package/src/__tests__/__snapshots__/index.test.ts.snap +279 -28
- package/src/__tests__/__snapshots__/translations.test.ts.snap +45 -0
- package/src/__tests__/docs.test.ts +122 -7
- package/src/__tests__/index.test.ts +27 -1
- package/src/__tests__/options.test.ts +2 -0
- package/src/__tests__/slug.test.ts +127 -20
- package/src/__tests__/translations.test.ts +7 -0
- package/src/categoryGeneratedIndex.ts +57 -0
- package/src/cli.ts +5 -35
- package/src/docs.ts +103 -45
- package/src/index.ts +55 -93
- package/src/options.ts +4 -0
- package/src/plugin-content-docs.d.ts +71 -8
- package/src/props.ts +48 -9
- package/src/routes.ts +173 -0
- package/src/sidebars/__tests__/__snapshots__/index.test.ts.snap +21 -6
- package/src/sidebars/__tests__/generator.test.ts +105 -1
- package/src/sidebars/__tests__/index.test.ts +26 -24
- package/src/sidebars/__tests__/processor.test.ts +110 -19
- package/src/sidebars/__tests__/utils.test.ts +320 -20
- package/src/sidebars/__tests__/validation.test.ts +105 -0
- package/src/sidebars/generator.ts +82 -19
- package/src/sidebars/index.ts +23 -13
- package/src/sidebars/normalization.ts +47 -23
- package/src/sidebars/processor.ts +57 -27
- package/src/sidebars/types.ts +64 -3
- package/src/sidebars/utils.ts +217 -42
- package/src/sidebars/validation.ts +52 -8
- package/src/slug.ts +32 -17
- package/src/translations.ts +74 -8
- package/src/types.ts +22 -5
package/src/sidebars/utils.ts
CHANGED
|
@@ -16,8 +16,15 @@ import type {
|
|
|
16
16
|
SidebarCategoriesShorthand,
|
|
17
17
|
SidebarItemConfig,
|
|
18
18
|
} from './types';
|
|
19
|
-
|
|
19
|
+
|
|
20
|
+
import {mapValues, difference, uniq} from 'lodash';
|
|
20
21
|
import {getElementsAround, toMessageRelativeFilePath} from '@docusaurus/utils';
|
|
22
|
+
import {DocMetadataBase, DocNavLink} from '../types';
|
|
23
|
+
import {
|
|
24
|
+
SidebarItemCategoryWithGeneratedIndex,
|
|
25
|
+
SidebarItemCategoryWithLink,
|
|
26
|
+
SidebarNavigationItem,
|
|
27
|
+
} from './types';
|
|
21
28
|
|
|
22
29
|
export function isCategoriesShorthand(
|
|
23
30
|
item: SidebarItemConfig,
|
|
@@ -41,21 +48,24 @@ export function transformSidebarItems(
|
|
|
41
48
|
return sidebar.map(transformRecursive);
|
|
42
49
|
}
|
|
43
50
|
|
|
51
|
+
// Flatten sidebar items into a single flat array (containing categories/docs on the same level)
|
|
52
|
+
// /!\ order matters (useful for next/prev nav), top categories appear before their child elements
|
|
53
|
+
function flattenSidebarItems(items: SidebarItem[]): SidebarItem[] {
|
|
54
|
+
function flattenRecursive(item: SidebarItem): SidebarItem[] {
|
|
55
|
+
return item.type === 'category'
|
|
56
|
+
? [item, ...item.items.flatMap(flattenRecursive)]
|
|
57
|
+
: [item];
|
|
58
|
+
}
|
|
59
|
+
return items.flatMap(flattenRecursive);
|
|
60
|
+
}
|
|
61
|
+
|
|
44
62
|
function collectSidebarItemsOfType<
|
|
45
63
|
Type extends SidebarItemType,
|
|
46
64
|
Item extends SidebarItem & {type: SidebarItemType},
|
|
47
65
|
>(type: Type, sidebar: Sidebar): Item[] {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const childItemsCollected: Item[] =
|
|
53
|
-
item.type === 'category' ? item.items.flatMap(collectRecursive) : [];
|
|
54
|
-
|
|
55
|
-
return [...currentItemsCollected, ...childItemsCollected];
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return sidebar.flatMap(collectRecursive);
|
|
66
|
+
return flattenSidebarItems(sidebar).filter(
|
|
67
|
+
(item) => item.type === type,
|
|
68
|
+
) as Item[];
|
|
59
69
|
}
|
|
60
70
|
|
|
61
71
|
export function collectSidebarDocItems(sidebar: Sidebar): SidebarItemDoc[] {
|
|
@@ -70,25 +80,72 @@ export function collectSidebarLinks(sidebar: Sidebar): SidebarItemLink[] {
|
|
|
70
80
|
return collectSidebarItemsOfType('link', sidebar);
|
|
71
81
|
}
|
|
72
82
|
|
|
83
|
+
// /!\ docId order matters for navigation!
|
|
84
|
+
export function collectSidebarDocIds(sidebar: Sidebar): string[] {
|
|
85
|
+
return flattenSidebarItems(sidebar).flatMap((item) => {
|
|
86
|
+
if (item.type === 'category') {
|
|
87
|
+
return item.link?.type === 'doc' ? [item.link.id] : [];
|
|
88
|
+
}
|
|
89
|
+
if (item.type === 'doc') {
|
|
90
|
+
return [item.id];
|
|
91
|
+
}
|
|
92
|
+
return [];
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function collectSidebarNavigation(
|
|
97
|
+
sidebar: Sidebar,
|
|
98
|
+
): SidebarNavigationItem[] {
|
|
99
|
+
return flattenSidebarItems(sidebar).flatMap((item) => {
|
|
100
|
+
if (item.type === 'category' && item.link) {
|
|
101
|
+
return [item as SidebarNavigationItem];
|
|
102
|
+
}
|
|
103
|
+
if (item.type === 'doc') {
|
|
104
|
+
return [item];
|
|
105
|
+
}
|
|
106
|
+
return [];
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
73
110
|
export function collectSidebarsDocIds(
|
|
74
111
|
sidebars: Sidebars,
|
|
75
112
|
): Record<string, string[]> {
|
|
76
|
-
return mapValues(sidebars,
|
|
77
|
-
|
|
78
|
-
|
|
113
|
+
return mapValues(sidebars, collectSidebarDocIds);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function collectSidebarsNavigations(
|
|
117
|
+
sidebars: Sidebars,
|
|
118
|
+
): Record<string, SidebarNavigationItem[]> {
|
|
119
|
+
return mapValues(sidebars, collectSidebarNavigation);
|
|
79
120
|
}
|
|
80
121
|
|
|
81
|
-
export
|
|
122
|
+
export type SidebarNavigation = {
|
|
123
|
+
sidebarName: string | undefined;
|
|
124
|
+
previous: SidebarNavigationItem | undefined;
|
|
125
|
+
next: SidebarNavigationItem | undefined;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// A convenient and performant way to query the sidebars content
|
|
129
|
+
export type SidebarsUtils = {
|
|
130
|
+
sidebars: Sidebars;
|
|
82
131
|
getFirstDocIdOfFirstSidebar: () => string | undefined;
|
|
83
132
|
getSidebarNameByDocId: (docId: string) => string | undefined;
|
|
84
|
-
getDocNavigation: (
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
133
|
+
getDocNavigation: (
|
|
134
|
+
unversionedId: string,
|
|
135
|
+
versionedId: string,
|
|
136
|
+
) => SidebarNavigation;
|
|
137
|
+
getCategoryGeneratedIndexList: () => SidebarItemCategoryWithGeneratedIndex[];
|
|
138
|
+
getCategoryGeneratedIndexNavigation: (
|
|
139
|
+
categoryGeneratedIndexPermalink: string,
|
|
140
|
+
) => SidebarNavigation;
|
|
141
|
+
|
|
89
142
|
checkSidebarsDocIds: (validDocIds: string[], sidebarFilePath: string) => void;
|
|
90
|
-
}
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
export function createSidebarsUtils(sidebars: Sidebars): SidebarsUtils {
|
|
91
146
|
const sidebarNameToDocIds = collectSidebarsDocIds(sidebars);
|
|
147
|
+
const sidebarNameToNavigationItems = collectSidebarsNavigations(sidebars);
|
|
148
|
+
|
|
92
149
|
// Reverse mapping
|
|
93
150
|
const docIdToSidebarName = Object.fromEntries(
|
|
94
151
|
Object.entries(sidebarNameToDocIds).flatMap(([sidebarName, docIds]) =>
|
|
@@ -104,27 +161,91 @@ export function createSidebarsUtils(sidebars: Sidebars): {
|
|
|
104
161
|
return docIdToSidebarName[docId];
|
|
105
162
|
}
|
|
106
163
|
|
|
107
|
-
function
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
164
|
+
function emptySidebarNavigation(): SidebarNavigation {
|
|
165
|
+
return {
|
|
166
|
+
sidebarName: undefined,
|
|
167
|
+
previous: undefined,
|
|
168
|
+
next: undefined,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function getDocNavigation(
|
|
173
|
+
unversionedId: string,
|
|
174
|
+
versionedId: string,
|
|
175
|
+
): SidebarNavigation {
|
|
176
|
+
// TODO legacy id retro-compatibility!
|
|
177
|
+
let docId = unversionedId;
|
|
178
|
+
let sidebarName = getSidebarNameByDocId(docId);
|
|
179
|
+
if (!sidebarName) {
|
|
180
|
+
docId = versionedId;
|
|
181
|
+
sidebarName = getSidebarNameByDocId(docId);
|
|
182
|
+
}
|
|
183
|
+
|
|
113
184
|
if (sidebarName) {
|
|
114
|
-
const
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
185
|
+
const navigationItems = sidebarNameToNavigationItems[sidebarName];
|
|
186
|
+
const currentItemIndex = navigationItems.findIndex((item) => {
|
|
187
|
+
if (item.type === 'doc') {
|
|
188
|
+
return item.id === docId;
|
|
189
|
+
}
|
|
190
|
+
if (item.type === 'category' && item.link.type === 'doc') {
|
|
191
|
+
return item.link.id === docId;
|
|
192
|
+
}
|
|
193
|
+
return false;
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
const {previous, next} = getElementsAround(
|
|
197
|
+
navigationItems,
|
|
198
|
+
currentItemIndex,
|
|
199
|
+
);
|
|
200
|
+
return {sidebarName, previous, next};
|
|
122
201
|
} else {
|
|
123
|
-
return
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
202
|
+
return emptySidebarNavigation();
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function getCategoryGeneratedIndexList(): SidebarItemCategoryWithGeneratedIndex[] {
|
|
207
|
+
return Object.values(sidebarNameToNavigationItems)
|
|
208
|
+
.flat()
|
|
209
|
+
.flatMap((item) => {
|
|
210
|
+
if (item.type === 'category' && item.link.type === 'generated-index') {
|
|
211
|
+
return [item as SidebarItemCategoryWithGeneratedIndex];
|
|
212
|
+
}
|
|
213
|
+
return [];
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// We identity the category generated index by its permalink (should be unique)
|
|
218
|
+
// More reliable than using object identity
|
|
219
|
+
function getCategoryGeneratedIndexNavigation(
|
|
220
|
+
categoryGeneratedIndexPermalink: string,
|
|
221
|
+
): SidebarNavigation {
|
|
222
|
+
function isCurrentCategoryGeneratedIndexItem(
|
|
223
|
+
item: SidebarNavigationItem,
|
|
224
|
+
): boolean {
|
|
225
|
+
return (
|
|
226
|
+
item.type === 'category' &&
|
|
227
|
+
item.link?.type === 'generated-index' &&
|
|
228
|
+
item.link.permalink === categoryGeneratedIndexPermalink
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const sidebarName = Object.entries(sidebarNameToNavigationItems).find(
|
|
233
|
+
([, navigationItems]) =>
|
|
234
|
+
navigationItems.find(isCurrentCategoryGeneratedIndexItem),
|
|
235
|
+
)?.[0];
|
|
236
|
+
|
|
237
|
+
if (sidebarName) {
|
|
238
|
+
const navigationItems = sidebarNameToNavigationItems[sidebarName];
|
|
239
|
+
const currentItemIndex = navigationItems.findIndex(
|
|
240
|
+
isCurrentCategoryGeneratedIndexItem,
|
|
241
|
+
);
|
|
242
|
+
const {previous, next} = getElementsAround(
|
|
243
|
+
navigationItems,
|
|
244
|
+
currentItemIndex,
|
|
245
|
+
);
|
|
246
|
+
return {sidebarName, previous, next};
|
|
247
|
+
} else {
|
|
248
|
+
return emptySidebarNavigation();
|
|
128
249
|
}
|
|
129
250
|
}
|
|
130
251
|
|
|
@@ -140,15 +261,69 @@ These sidebar document ids do not exist:
|
|
|
140
261
|
- ${invalidSidebarDocIds.sort().join('\n- ')}
|
|
141
262
|
|
|
142
263
|
Available document ids are:
|
|
143
|
-
- ${validDocIds.sort().join('\n- ')}`,
|
|
264
|
+
- ${uniq(validDocIds).sort().join('\n- ')}`,
|
|
144
265
|
);
|
|
145
266
|
}
|
|
146
267
|
}
|
|
147
268
|
|
|
148
269
|
return {
|
|
270
|
+
sidebars,
|
|
149
271
|
getFirstDocIdOfFirstSidebar,
|
|
150
272
|
getSidebarNameByDocId,
|
|
151
273
|
getDocNavigation,
|
|
274
|
+
getCategoryGeneratedIndexList,
|
|
275
|
+
getCategoryGeneratedIndexNavigation,
|
|
152
276
|
checkSidebarsDocIds,
|
|
153
277
|
};
|
|
154
278
|
}
|
|
279
|
+
|
|
280
|
+
export function toDocNavigationLink(doc: DocMetadataBase): DocNavLink {
|
|
281
|
+
const {
|
|
282
|
+
title,
|
|
283
|
+
permalink,
|
|
284
|
+
frontMatter: {
|
|
285
|
+
pagination_label: paginationLabel,
|
|
286
|
+
sidebar_label: sidebarLabel,
|
|
287
|
+
},
|
|
288
|
+
} = doc;
|
|
289
|
+
return {title: paginationLabel ?? sidebarLabel ?? title, permalink};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export function toNavigationLink(
|
|
293
|
+
navigationItem: SidebarNavigationItem | undefined,
|
|
294
|
+
docsById: Record<string, DocMetadataBase>,
|
|
295
|
+
): DocNavLink | undefined {
|
|
296
|
+
function getDocById(docId: string) {
|
|
297
|
+
const doc = docsById[docId];
|
|
298
|
+
if (!doc) {
|
|
299
|
+
throw new Error(
|
|
300
|
+
`Can't create navigation link: no doc found with id=${docId}`,
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
return doc;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function handleCategory(category: SidebarItemCategoryWithLink): DocNavLink {
|
|
307
|
+
if (category.link.type === 'doc') {
|
|
308
|
+
return toDocNavigationLink(getDocById(category.link.id));
|
|
309
|
+
} else if (category.link.type === 'generated-index') {
|
|
310
|
+
return {
|
|
311
|
+
title: category.label,
|
|
312
|
+
permalink: category.link.permalink,
|
|
313
|
+
};
|
|
314
|
+
} else {
|
|
315
|
+
throw new Error('unexpected category link type');
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (!navigationItem) {
|
|
319
|
+
return undefined;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (navigationItem.type === 'doc') {
|
|
323
|
+
return toDocNavigationLink(getDocById(navigationItem.id));
|
|
324
|
+
} else if (navigationItem.type === 'category') {
|
|
325
|
+
return handleCategory(navigationItem);
|
|
326
|
+
} else {
|
|
327
|
+
throw new Error('unexpected navigation item');
|
|
328
|
+
}
|
|
329
|
+
}
|
|
@@ -14,9 +14,13 @@ import type {
|
|
|
14
14
|
SidebarItemDoc,
|
|
15
15
|
SidebarItemLink,
|
|
16
16
|
SidebarItemCategoryConfig,
|
|
17
|
+
SidebarItemCategoryLink,
|
|
17
18
|
SidebarsConfig,
|
|
19
|
+
SidebarItemCategoryLinkDoc,
|
|
20
|
+
SidebarItemCategoryLinkGeneratedIndex,
|
|
18
21
|
} from './types';
|
|
19
22
|
import {isCategoriesShorthand} from './utils';
|
|
23
|
+
import {CategoryMetadataFile} from './generator';
|
|
20
24
|
|
|
21
25
|
const sidebarItemBaseSchema = Joi.object<SidebarItemBase>({
|
|
22
26
|
className: Joi.string(),
|
|
@@ -48,6 +52,36 @@ const sidebarItemLinkSchema = sidebarItemBaseSchema.append<SidebarItemLink>({
|
|
|
48
52
|
.messages({'any.unknown': '"label" must be a string'}),
|
|
49
53
|
});
|
|
50
54
|
|
|
55
|
+
const sidebarItemCategoryLinkSchema = Joi.object<SidebarItemCategoryLink>()
|
|
56
|
+
.when('.type', {
|
|
57
|
+
switch: [
|
|
58
|
+
{
|
|
59
|
+
is: 'doc',
|
|
60
|
+
then: Joi.object<SidebarItemCategoryLinkDoc>({
|
|
61
|
+
type: 'doc',
|
|
62
|
+
id: Joi.string().required(),
|
|
63
|
+
}),
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
is: 'generated-index',
|
|
67
|
+
then: Joi.object<SidebarItemCategoryLinkGeneratedIndex>({
|
|
68
|
+
type: 'generated-index',
|
|
69
|
+
slug: Joi.string().optional(),
|
|
70
|
+
// permalink: Joi.string().optional(), // No, this one is not in the user config, only in the normalized version
|
|
71
|
+
title: Joi.string().optional(),
|
|
72
|
+
description: Joi.string().optional(),
|
|
73
|
+
}),
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
is: Joi.string().required(),
|
|
77
|
+
then: Joi.forbidden().messages({
|
|
78
|
+
'any.unknown': 'Unknown sidebar category link type "{.type}".',
|
|
79
|
+
}),
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
})
|
|
83
|
+
.id('sidebarCategoryLinkSchema');
|
|
84
|
+
|
|
51
85
|
const sidebarItemCategorySchema =
|
|
52
86
|
sidebarItemBaseSchema.append<SidebarItemCategoryConfig>({
|
|
53
87
|
type: 'category',
|
|
@@ -58,6 +92,7 @@ const sidebarItemCategorySchema =
|
|
|
58
92
|
items: Joi.array()
|
|
59
93
|
.required()
|
|
60
94
|
.messages({'any.unknown': '"items" must be an array'}), // .items(Joi.link('#sidebarItemSchema')),
|
|
95
|
+
link: sidebarItemCategoryLinkSchema,
|
|
61
96
|
collapsed: Joi.boolean().messages({
|
|
62
97
|
'any.unknown': '"collapsed" must be a boolean',
|
|
63
98
|
}),
|
|
@@ -77,14 +112,7 @@ const sidebarItemSchema: Joi.Schema<SidebarItemConfig> = Joi.object()
|
|
|
77
112
|
{is: 'autogenerated', then: sidebarItemAutogeneratedSchema},
|
|
78
113
|
{is: 'category', then: sidebarItemCategorySchema},
|
|
79
114
|
{
|
|
80
|
-
is:
|
|
81
|
-
then: Joi.forbidden().messages({
|
|
82
|
-
'any.unknown':
|
|
83
|
-
'Docusaurus v2: "subcategory" has been renamed as "category".',
|
|
84
|
-
}),
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
is: Joi.string().required(),
|
|
115
|
+
is: Joi.any().required(),
|
|
88
116
|
then: Joi.forbidden().messages({
|
|
89
117
|
'any.unknown': 'Unknown sidebar item type "{.type}".',
|
|
90
118
|
}),
|
|
@@ -105,6 +133,7 @@ function validateSidebarItem(item: unknown): asserts item is SidebarItemConfig {
|
|
|
105
133
|
);
|
|
106
134
|
} else {
|
|
107
135
|
Joi.assert(item, sidebarItemSchema);
|
|
136
|
+
|
|
108
137
|
if ((item as SidebarItemCategoryConfig).type === 'category') {
|
|
109
138
|
(item as SidebarItemCategoryConfig).items.forEach(validateSidebarItem);
|
|
110
139
|
}
|
|
@@ -122,3 +151,18 @@ export function validateSidebars(
|
|
|
122
151
|
}
|
|
123
152
|
});
|
|
124
153
|
}
|
|
154
|
+
|
|
155
|
+
const categoryMetadataFileSchema = Joi.object<CategoryMetadataFile>({
|
|
156
|
+
label: Joi.string(),
|
|
157
|
+
position: Joi.number(),
|
|
158
|
+
collapsed: Joi.boolean(),
|
|
159
|
+
collapsible: Joi.boolean(),
|
|
160
|
+
className: Joi.string(),
|
|
161
|
+
link: sidebarItemCategoryLinkSchema,
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
export function validateCategoryMetadataFile(
|
|
165
|
+
unsafeContent: unknown,
|
|
166
|
+
): CategoryMetadataFile {
|
|
167
|
+
return Joi.attempt(unsafeContent, categoryMetadataFileSchema);
|
|
168
|
+
}
|
package/src/slug.ts
CHANGED
|
@@ -15,39 +15,52 @@ import {
|
|
|
15
15
|
DefaultNumberPrefixParser,
|
|
16
16
|
stripPathNumberPrefixes,
|
|
17
17
|
} from './numberPrefix';
|
|
18
|
-
import type {NumberPrefixParser} from './types';
|
|
18
|
+
import type {DocMetadataBase, NumberPrefixParser} from './types';
|
|
19
|
+
import {isConventionalDocIndex} from './docs';
|
|
19
20
|
|
|
20
21
|
export default function getSlug({
|
|
21
22
|
baseID,
|
|
22
23
|
frontmatterSlug,
|
|
23
|
-
|
|
24
|
+
source,
|
|
25
|
+
sourceDirName,
|
|
24
26
|
stripDirNumberPrefixes = true,
|
|
25
27
|
numberPrefixParser = DefaultNumberPrefixParser,
|
|
26
28
|
}: {
|
|
27
29
|
baseID: string;
|
|
28
30
|
frontmatterSlug?: string;
|
|
29
|
-
|
|
31
|
+
source: DocMetadataBase['slug'];
|
|
32
|
+
sourceDirName: DocMetadataBase['sourceDirName'];
|
|
30
33
|
stripDirNumberPrefixes?: boolean;
|
|
31
34
|
numberPrefixParser?: NumberPrefixParser;
|
|
32
35
|
}): string {
|
|
33
|
-
|
|
34
|
-
let slug: string;
|
|
35
|
-
if (baseSlug.startsWith('/')) {
|
|
36
|
-
slug = baseSlug;
|
|
37
|
-
} else {
|
|
36
|
+
function getDirNameSlug(): string {
|
|
38
37
|
const dirNameStripped = stripDirNumberPrefixes
|
|
39
|
-
? stripPathNumberPrefixes(
|
|
40
|
-
:
|
|
38
|
+
? stripPathNumberPrefixes(sourceDirName, numberPrefixParser)
|
|
39
|
+
: sourceDirName;
|
|
41
40
|
const resolveDirname =
|
|
42
|
-
|
|
41
|
+
sourceDirName === '.'
|
|
43
42
|
? '/'
|
|
44
43
|
: addLeadingSlash(addTrailingSlash(dirNameStripped));
|
|
45
|
-
|
|
44
|
+
return resolveDirname;
|
|
46
45
|
}
|
|
47
46
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
function computeSlug(): string {
|
|
48
|
+
if (frontmatterSlug?.startsWith('/')) {
|
|
49
|
+
return frontmatterSlug;
|
|
50
|
+
} else {
|
|
51
|
+
const dirNameSlug = getDirNameSlug();
|
|
52
|
+
if (!frontmatterSlug && isConventionalDocIndex({source, sourceDirName})) {
|
|
53
|
+
return dirNameSlug;
|
|
54
|
+
}
|
|
55
|
+
const baseSlug = frontmatterSlug || baseID;
|
|
56
|
+
return resolvePathname(baseSlug, getDirNameSlug());
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function ensureValidSlug(slug: string): string {
|
|
61
|
+
if (!isValidPathname(slug)) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
`We couldn't compute a valid slug for document with id "${baseID}" in "${sourceDirName}" directory.
|
|
51
64
|
The slug we computed looks invalid: ${slug}.
|
|
52
65
|
Maybe your slug frontmatter is incorrect or you use weird chars in the file path?
|
|
53
66
|
By using the slug frontmatter, you should be able to fix this error, by using the slug of your choice:
|
|
@@ -57,8 +70,10 @@ Example =>
|
|
|
57
70
|
slug: /my/customDocPath
|
|
58
71
|
---
|
|
59
72
|
`,
|
|
60
|
-
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
return slug;
|
|
61
76
|
}
|
|
62
77
|
|
|
63
|
-
return
|
|
78
|
+
return ensureValidSlug(computeSlug());
|
|
64
79
|
}
|
package/src/translations.ts
CHANGED
|
@@ -6,7 +6,12 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type {LoadedVersion, LoadedContent} from './types';
|
|
9
|
-
import type {
|
|
9
|
+
import type {
|
|
10
|
+
Sidebar,
|
|
11
|
+
SidebarItemCategory,
|
|
12
|
+
SidebarItemCategoryLink,
|
|
13
|
+
Sidebars,
|
|
14
|
+
} from './sidebars/types';
|
|
10
15
|
|
|
11
16
|
import {chain, mapValues, keyBy} from 'lodash';
|
|
12
17
|
import {
|
|
@@ -21,6 +26,7 @@ import type {
|
|
|
21
26
|
} from '@docusaurus/types';
|
|
22
27
|
import {mergeTranslations} from '@docusaurus/utils';
|
|
23
28
|
import {CURRENT_VERSION_NAME} from './constants';
|
|
29
|
+
import {TranslationMessage} from '@docusaurus/types';
|
|
24
30
|
|
|
25
31
|
function getVersionFileName(versionName: string): string {
|
|
26
32
|
if (versionName === CURRENT_VERSION_NAME) {
|
|
@@ -96,14 +102,48 @@ function getSidebarTranslationFileContent(
|
|
|
96
102
|
sidebar: Sidebar,
|
|
97
103
|
sidebarName: string,
|
|
98
104
|
): TranslationFileContent {
|
|
105
|
+
type TranslationMessageEntry = [string, TranslationMessage];
|
|
106
|
+
|
|
99
107
|
const categories = collectSidebarCategories(sidebar);
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
.
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
108
|
+
|
|
109
|
+
const categoryContent: TranslationFileContent = Object.fromEntries(
|
|
110
|
+
categories.flatMap((category) => {
|
|
111
|
+
const entries: TranslationMessageEntry[] = [];
|
|
112
|
+
|
|
113
|
+
entries.push([
|
|
114
|
+
`sidebar.${sidebarName}.category.${category.label}`,
|
|
115
|
+
{
|
|
116
|
+
message: category.label,
|
|
117
|
+
description: `The label for category ${category.label} in sidebar ${sidebarName}`,
|
|
118
|
+
},
|
|
119
|
+
]);
|
|
120
|
+
|
|
121
|
+
if (category.link) {
|
|
122
|
+
if (category.link.type === 'generated-index') {
|
|
123
|
+
if (category.link.title) {
|
|
124
|
+
entries.push([
|
|
125
|
+
`sidebar.${sidebarName}.category.${category.label}.link.generated-index.title`,
|
|
126
|
+
{
|
|
127
|
+
message: category.link.title,
|
|
128
|
+
description: `The generated-index page title for category ${category.label} in sidebar ${sidebarName}`,
|
|
129
|
+
},
|
|
130
|
+
]);
|
|
131
|
+
}
|
|
132
|
+
if (category.link.description) {
|
|
133
|
+
entries.push([
|
|
134
|
+
`sidebar.${sidebarName}.category.${category.label}.link.generated-index.description`,
|
|
135
|
+
{
|
|
136
|
+
message: category.link.description,
|
|
137
|
+
description: `The generated-index page description for category ${category.label} in sidebar ${sidebarName}`,
|
|
138
|
+
},
|
|
139
|
+
]);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return entries;
|
|
145
|
+
}),
|
|
146
|
+
);
|
|
107
147
|
|
|
108
148
|
const links = collectSidebarLinks(sidebar);
|
|
109
149
|
const linksContent: TranslationFileContent = chain(links)
|
|
@@ -126,13 +166,39 @@ function translateSidebar({
|
|
|
126
166
|
sidebarName: string;
|
|
127
167
|
sidebarsTranslations: TranslationFileContent;
|
|
128
168
|
}): Sidebar {
|
|
169
|
+
function transformSidebarCategoryLink(
|
|
170
|
+
category: SidebarItemCategory,
|
|
171
|
+
): SidebarItemCategoryLink | undefined {
|
|
172
|
+
if (!category.link) {
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
if (category.link.type === 'generated-index') {
|
|
176
|
+
const title =
|
|
177
|
+
sidebarsTranslations[
|
|
178
|
+
`sidebar.${sidebarName}.category.${category.label}.link.generated-index.title`
|
|
179
|
+
]?.message ?? category.link.title;
|
|
180
|
+
const description =
|
|
181
|
+
sidebarsTranslations[
|
|
182
|
+
`sidebar.${sidebarName}.category.${category.label}.link.generated-index.description`
|
|
183
|
+
]?.message ?? category.link.description;
|
|
184
|
+
return {
|
|
185
|
+
...category.link,
|
|
186
|
+
title,
|
|
187
|
+
description,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
return category.link;
|
|
191
|
+
}
|
|
192
|
+
|
|
129
193
|
return transformSidebarItems(sidebar, (item) => {
|
|
130
194
|
if (item.type === 'category') {
|
|
195
|
+
const link = transformSidebarCategoryLink(item);
|
|
131
196
|
return {
|
|
132
197
|
...item,
|
|
133
198
|
label:
|
|
134
199
|
sidebarsTranslations[`sidebar.${sidebarName}.category.${item.label}`]
|
|
135
200
|
?.message ?? item.label,
|
|
201
|
+
...(link && {link}),
|
|
136
202
|
};
|
|
137
203
|
}
|
|
138
204
|
if (item.type === 'link') {
|
package/src/types.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
/// <reference types="@docusaurus/module-type-aliases" />
|
|
9
9
|
|
|
10
10
|
import type {RemarkAndRehypePluginOptions} from '@docusaurus/mdx-loader';
|
|
11
|
-
import type {Tag, FrontMatterTag} from '@docusaurus/utils';
|
|
11
|
+
import type {Tag, FrontMatterTag, Slugger} from '@docusaurus/utils';
|
|
12
12
|
import type {
|
|
13
13
|
BrokenMarkdownLink as IBrokenMarkdownLink,
|
|
14
14
|
ContentPaths,
|
|
@@ -86,6 +86,11 @@ export type SidebarOptions = {
|
|
|
86
86
|
sidebarCollapsed: boolean;
|
|
87
87
|
};
|
|
88
88
|
|
|
89
|
+
export type NormalizeSidebarsParams = SidebarOptions & {
|
|
90
|
+
version: VersionMetadata;
|
|
91
|
+
categoryLabelSlugger: Slugger;
|
|
92
|
+
};
|
|
93
|
+
|
|
89
94
|
export type PluginOptions = MetadataOptions &
|
|
90
95
|
PathOptions &
|
|
91
96
|
VersionsOptions &
|
|
@@ -98,6 +103,7 @@ export type PluginOptions = MetadataOptions &
|
|
|
98
103
|
docItemComponent: string;
|
|
99
104
|
docTagDocListComponent: string;
|
|
100
105
|
docTagsListComponent: string;
|
|
106
|
+
docCategoryGeneratedIndexComponent: string;
|
|
101
107
|
admonitions: Record<string, unknown>;
|
|
102
108
|
disableVersioning: boolean;
|
|
103
109
|
includeCurrentVersion: boolean;
|
|
@@ -135,14 +141,14 @@ export type DocFrontMatter = {
|
|
|
135
141
|
};
|
|
136
142
|
|
|
137
143
|
export type DocMetadataBase = LastUpdateData & {
|
|
144
|
+
id: string; // TODO legacy versioned id => try to remove
|
|
145
|
+
unversionedId: string; // TODO new unversioned id => try to rename to "id"
|
|
138
146
|
version: VersionName;
|
|
139
|
-
unversionedId: string;
|
|
140
|
-
id: string;
|
|
141
147
|
isDocsHomePage: boolean;
|
|
142
148
|
title: string;
|
|
143
149
|
description: string;
|
|
144
|
-
source: string;
|
|
145
|
-
sourceDirName: string; // relative to the docs folder (can be ".")
|
|
150
|
+
source: string; // @site aliased source => "@site/docs/folder/subFolder/subSubFolder/myDoc.md"
|
|
151
|
+
sourceDirName: string; // relative to the versioned docs folder (can be ".") => "folder/subFolder/subSubFolder"
|
|
146
152
|
slug: string;
|
|
147
153
|
permalink: string;
|
|
148
154
|
sidebarPosition?: number;
|
|
@@ -162,6 +168,16 @@ export type DocMetadata = DocMetadataBase & {
|
|
|
162
168
|
next?: DocNavLink;
|
|
163
169
|
};
|
|
164
170
|
|
|
171
|
+
export type CategoryGeneratedIndexMetadata = {
|
|
172
|
+
title: string;
|
|
173
|
+
description?: string;
|
|
174
|
+
slug: string;
|
|
175
|
+
permalink: string;
|
|
176
|
+
sidebar: string;
|
|
177
|
+
previous?: DocNavLink;
|
|
178
|
+
next?: DocNavLink;
|
|
179
|
+
};
|
|
180
|
+
|
|
165
181
|
export type SourceToPermalink = {
|
|
166
182
|
[source: string]: string;
|
|
167
183
|
};
|
|
@@ -180,6 +196,7 @@ export type LoadedVersion = VersionMetadata & {
|
|
|
180
196
|
mainDocId: string;
|
|
181
197
|
docs: DocMetadata[];
|
|
182
198
|
sidebars: Sidebars;
|
|
199
|
+
categoryGeneratedIndices: CategoryGeneratedIndexMetadata[];
|
|
183
200
|
};
|
|
184
201
|
|
|
185
202
|
export type LoadedContent = {
|