@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.
Files changed (62) hide show
  1. package/lib/.tsbuildinfo +1 -1
  2. package/lib/categoryGeneratedIndex.d.ts +12 -0
  3. package/lib/categoryGeneratedIndex.js +37 -0
  4. package/lib/cli.js +5 -23
  5. package/lib/docs.d.ts +22 -2
  6. package/lib/docs.js +71 -29
  7. package/lib/index.js +34 -62
  8. package/lib/options.js +2 -0
  9. package/lib/props.js +35 -6
  10. package/lib/routes.d.ts +27 -0
  11. package/lib/routes.js +105 -0
  12. package/lib/sidebars/generator.d.ts +2 -1
  13. package/lib/sidebars/generator.js +55 -13
  14. package/lib/sidebars/index.d.ts +5 -4
  15. package/lib/sidebars/index.js +18 -9
  16. package/lib/sidebars/normalization.d.ts +8 -3
  17. package/lib/sidebars/normalization.js +36 -17
  18. package/lib/sidebars/processor.d.ts +5 -3
  19. package/lib/sidebars/processor.js +33 -18
  20. package/lib/sidebars/types.d.ts +43 -2
  21. package/lib/sidebars/utils.d.ts +18 -6
  22. package/lib/sidebars/utils.js +149 -24
  23. package/lib/sidebars/validation.d.ts +2 -0
  24. package/lib/sidebars/validation.js +44 -8
  25. package/lib/slug.d.ts +4 -3
  26. package/lib/slug.js +26 -14
  27. package/lib/translations.js +51 -7
  28. package/lib/types.d.ts +18 -3
  29. package/package.json +8 -8
  30. package/src/__tests__/__fixtures__/versioned-site/versioned_sidebars/version-1.0.1-sidebars.json +2 -2
  31. package/src/__tests__/__snapshots__/cli.test.ts.snap +48 -106
  32. package/src/__tests__/__snapshots__/index.test.ts.snap +279 -28
  33. package/src/__tests__/__snapshots__/translations.test.ts.snap +45 -0
  34. package/src/__tests__/docs.test.ts +122 -7
  35. package/src/__tests__/index.test.ts +27 -1
  36. package/src/__tests__/options.test.ts +2 -0
  37. package/src/__tests__/slug.test.ts +127 -20
  38. package/src/__tests__/translations.test.ts +7 -0
  39. package/src/categoryGeneratedIndex.ts +57 -0
  40. package/src/cli.ts +5 -35
  41. package/src/docs.ts +103 -45
  42. package/src/index.ts +55 -93
  43. package/src/options.ts +4 -0
  44. package/src/plugin-content-docs.d.ts +71 -8
  45. package/src/props.ts +48 -9
  46. package/src/routes.ts +173 -0
  47. package/src/sidebars/__tests__/__snapshots__/index.test.ts.snap +21 -6
  48. package/src/sidebars/__tests__/generator.test.ts +105 -1
  49. package/src/sidebars/__tests__/index.test.ts +26 -24
  50. package/src/sidebars/__tests__/processor.test.ts +110 -19
  51. package/src/sidebars/__tests__/utils.test.ts +320 -20
  52. package/src/sidebars/__tests__/validation.test.ts +105 -0
  53. package/src/sidebars/generator.ts +82 -19
  54. package/src/sidebars/index.ts +23 -13
  55. package/src/sidebars/normalization.ts +47 -23
  56. package/src/sidebars/processor.ts +57 -27
  57. package/src/sidebars/types.ts +64 -3
  58. package/src/sidebars/utils.ts +217 -42
  59. package/src/sidebars/validation.ts +52 -8
  60. package/src/slug.ts +32 -17
  61. package/src/translations.ts +74 -8
  62. package/src/types.ts +22 -5
@@ -16,8 +16,15 @@ import type {
16
16
  SidebarCategoriesShorthand,
17
17
  SidebarItemConfig,
18
18
  } from './types';
19
- import {mapValues, difference} from 'lodash';
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
- function collectRecursive(item: SidebarItem): Item[] {
49
- const currentItemsCollected: Item[] =
50
- item.type === type ? [item as Item] : [];
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, (sidebar) =>
77
- collectSidebarDocItems(sidebar).map((docItem) => docItem.id),
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 function createSidebarsUtils(sidebars: Sidebars): {
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: (docId: string) => {
85
- sidebarName: string | undefined;
86
- previousId: string | undefined;
87
- nextId: string | undefined;
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 getDocNavigation(docId: string): {
108
- sidebarName: string | undefined;
109
- previousId: string | undefined;
110
- nextId: string | undefined;
111
- } {
112
- const sidebarName = getSidebarNameByDocId(docId);
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 docIds = sidebarNameToDocIds[sidebarName];
115
- const currentIndex = docIds.indexOf(docId);
116
- const {previous, next} = getElementsAround(docIds, currentIndex);
117
- return {
118
- sidebarName,
119
- previousId: previous,
120
- nextId: next,
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
- sidebarName: undefined,
125
- previousId: undefined,
126
- nextId: undefined,
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: 'subcategory',
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
- dirName,
24
+ source,
25
+ sourceDirName,
24
26
  stripDirNumberPrefixes = true,
25
27
  numberPrefixParser = DefaultNumberPrefixParser,
26
28
  }: {
27
29
  baseID: string;
28
30
  frontmatterSlug?: string;
29
- dirName: string;
31
+ source: DocMetadataBase['slug'];
32
+ sourceDirName: DocMetadataBase['sourceDirName'];
30
33
  stripDirNumberPrefixes?: boolean;
31
34
  numberPrefixParser?: NumberPrefixParser;
32
35
  }): string {
33
- const baseSlug = frontmatterSlug || baseID;
34
- let slug: string;
35
- if (baseSlug.startsWith('/')) {
36
- slug = baseSlug;
37
- } else {
36
+ function getDirNameSlug(): string {
38
37
  const dirNameStripped = stripDirNumberPrefixes
39
- ? stripPathNumberPrefixes(dirName, numberPrefixParser)
40
- : dirName;
38
+ ? stripPathNumberPrefixes(sourceDirName, numberPrefixParser)
39
+ : sourceDirName;
41
40
  const resolveDirname =
42
- dirName === '.'
41
+ sourceDirName === '.'
43
42
  ? '/'
44
43
  : addLeadingSlash(addTrailingSlash(dirNameStripped));
45
- slug = resolvePathname(baseSlug, resolveDirname);
44
+ return resolveDirname;
46
45
  }
47
46
 
48
- if (!isValidPathname(slug)) {
49
- throw new Error(
50
- `We couldn't compute a valid slug for document with id "${baseID}" in "${dirName}" directory.
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 slug;
78
+ return ensureValidSlug(computeSlug());
64
79
  }
@@ -6,7 +6,12 @@
6
6
  */
7
7
 
8
8
  import type {LoadedVersion, LoadedContent} from './types';
9
- import type {Sidebar, Sidebars} from './sidebars/types';
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
- const categoryContent: TranslationFileContent = chain(categories)
101
- .keyBy((category) => `sidebar.${sidebarName}.category.${category.label}`)
102
- .mapValues((category) => ({
103
- message: category.label,
104
- description: `The label for category ${category.label} in sidebar ${sidebarName}`,
105
- }))
106
- .value();
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 = {