@docusaurus/plugin-content-docs 2.0.0-beta.1 → 2.0.0-beta.10

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 (140) 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.d.ts +2 -2
  5. package/lib/cli.js +12 -34
  6. package/lib/client/docsClientUtils.d.ts +1 -4
  7. package/lib/client/docsClientUtils.js +21 -31
  8. package/lib/docFrontMatter.d.ts +1 -1
  9. package/lib/docFrontMatter.js +10 -6
  10. package/lib/docs.d.ts +25 -3
  11. package/lib/docs.js +125 -38
  12. package/lib/globalData.d.ts +1 -1
  13. package/lib/index.d.ts +1 -1
  14. package/lib/index.js +104 -138
  15. package/lib/lastUpdate.js +9 -10
  16. package/lib/markdown/index.d.ts +3 -6
  17. package/lib/markdown/index.js +3 -3
  18. package/lib/markdown/linkify.js +2 -2
  19. package/lib/numberPrefix.d.ts +1 -1
  20. package/lib/options.d.ts +3 -3
  21. package/lib/options.js +49 -17
  22. package/lib/props.d.ts +7 -2
  23. package/lib/props.js +61 -9
  24. package/lib/routes.d.ts +27 -0
  25. package/lib/routes.js +105 -0
  26. package/lib/{sidebarItemsGenerator.d.ts → sidebars/generator.d.ts} +5 -2
  27. package/lib/sidebars/generator.js +216 -0
  28. package/lib/sidebars/index.d.ts +15 -0
  29. package/lib/sidebars/index.js +73 -0
  30. package/lib/sidebars/normalization.d.ts +14 -0
  31. package/lib/sidebars/normalization.js +77 -0
  32. package/lib/sidebars/processor.d.ts +18 -0
  33. package/lib/sidebars/processor.js +85 -0
  34. package/lib/sidebars/types.d.ts +127 -0
  35. package/lib/sidebars/types.js +8 -0
  36. package/lib/sidebars/utils.d.ts +35 -0
  37. package/lib/sidebars/utils.js +228 -0
  38. package/lib/sidebars/validation.d.ts +10 -0
  39. package/lib/sidebars/validation.js +138 -0
  40. package/lib/slug.d.ts +4 -3
  41. package/lib/slug.js +27 -15
  42. package/lib/tags.d.ts +8 -0
  43. package/lib/tags.js +20 -0
  44. package/lib/theme/hooks/useDocs.js +24 -21
  45. package/lib/translations.d.ts +2 -2
  46. package/lib/translations.js +71 -29
  47. package/lib/types.d.ts +52 -62
  48. package/lib/versions.d.ts +3 -3
  49. package/lib/versions.js +76 -24
  50. package/package.json +22 -20
  51. package/src/__tests__/__fixtures__/simple-site/docs/_partials/somePartial.md +3 -0
  52. package/src/__tests__/__fixtures__/simple-site/docs/_partials/subfolder/somePartial.md +3 -0
  53. package/src/__tests__/__fixtures__/simple-site/docs/_somePartial.md +3 -0
  54. package/src/__tests__/__fixtures__/simple-site/docs/foo/baz.md +5 -0
  55. package/src/__tests__/__fixtures__/simple-site/docs/hello.md +2 -0
  56. package/src/__tests__/__fixtures__/simple-site/docs/rootAbsoluteSlug.md +2 -0
  57. package/src/__tests__/__fixtures__/simple-site/docs/rootRelativeSlug.md +2 -0
  58. package/src/__tests__/__fixtures__/simple-site/docs/rootResolvedSlug.md +2 -0
  59. package/src/__tests__/__fixtures__/simple-site/docs/rootTryToEscapeSlug.md +2 -0
  60. package/src/__tests__/__fixtures__/simple-site/sidebars.json +15 -1
  61. package/src/__tests__/__fixtures__/site-with-doc-label/docs/hello-1.md +1 -0
  62. package/src/__tests__/__fixtures__/versioned-site/docs/foo/bar.md +6 -0
  63. package/src/__tests__/__fixtures__/versioned-site/docs/hello.md +3 -0
  64. package/src/__tests__/__fixtures__/versioned-site/i18n/en/docusaurus-plugin-content-docs/version-1.0.0/hello.md +3 -0
  65. package/src/__tests__/__fixtures__/versioned-site/i18n/fr/docusaurus-plugin-content-docs/version-1.0.0/hello.md +3 -0
  66. package/src/__tests__/__fixtures__/versioned-site/versioned_docs/version-1.0.0/hello.md +3 -0
  67. package/src/__tests__/__fixtures__/versioned-site/versioned_docs/version-1.0.1/_partials/somePartial.md +3 -0
  68. package/src/__tests__/__fixtures__/versioned-site/versioned_docs/version-1.0.1/_partials/subfolder/somePartial.md +3 -0
  69. package/src/__tests__/__fixtures__/versioned-site/versioned_docs/version-1.0.1/_somePartial.md +3 -0
  70. package/src/__tests__/__fixtures__/versioned-site/versioned_docs/version-1.0.1/hello.md +3 -0
  71. package/src/__tests__/__fixtures__/versioned-site/versioned_sidebars/version-1.0.1-sidebars.json +2 -2
  72. package/src/__tests__/__snapshots__/cli.test.ts.snap +48 -73
  73. package/src/__tests__/__snapshots__/docs.test.ts.snap +140 -0
  74. package/src/__tests__/__snapshots__/index.test.ts.snap +753 -112
  75. package/src/__tests__/__snapshots__/translations.test.ts.snap +45 -18
  76. package/src/__tests__/cli.test.ts +15 -11
  77. package/src/__tests__/docFrontMatter.test.ts +195 -40
  78. package/src/__tests__/docs.test.ts +311 -150
  79. package/src/__tests__/index.test.ts +112 -69
  80. package/src/__tests__/lastUpdate.test.ts +3 -2
  81. package/src/__tests__/options.test.ts +48 -4
  82. package/src/__tests__/props.test.ts +62 -0
  83. package/src/__tests__/slug.test.ts +127 -20
  84. package/src/__tests__/translations.test.ts +7 -2
  85. package/src/__tests__/versions.test.ts +93 -67
  86. package/src/categoryGeneratedIndex.ts +57 -0
  87. package/src/cli.ts +8 -41
  88. package/src/client/__tests__/docsClientUtils.test.ts +4 -5
  89. package/src/client/docsClientUtils.ts +19 -41
  90. package/{types.d.ts → src/deps.d.ts} +0 -0
  91. package/src/docFrontMatter.ts +13 -7
  92. package/src/docs.ts +158 -29
  93. package/src/globalData.ts +6 -1
  94. package/src/index.ts +134 -179
  95. package/src/lastUpdate.ts +10 -9
  96. package/src/markdown/index.ts +8 -12
  97. package/src/numberPrefix.ts +5 -3
  98. package/src/options.ts +59 -28
  99. package/src/plugin-content-docs.d.ts +179 -35
  100. package/src/props.ts +91 -16
  101. package/src/routes.ts +173 -0
  102. package/src/{__tests__ → sidebars/__tests__}/__fixtures__/sidebars/sidebars-category-shorthand.js +0 -0
  103. package/src/{__tests__ → sidebars/__tests__}/__fixtures__/sidebars/sidebars-category-wrong-items.json +0 -0
  104. package/src/{__tests__ → sidebars/__tests__}/__fixtures__/sidebars/sidebars-category-wrong-label.json +0 -0
  105. package/src/{__tests__ → sidebars/__tests__}/__fixtures__/sidebars/sidebars-category.js +0 -0
  106. package/src/{__tests__ → sidebars/__tests__}/__fixtures__/sidebars/sidebars-collapsed-first-level.json +0 -0
  107. package/src/{__tests__ → sidebars/__tests__}/__fixtures__/sidebars/sidebars-collapsed.json +0 -0
  108. package/src/{__tests__ → sidebars/__tests__}/__fixtures__/sidebars/sidebars-doc-id-not-string.json +0 -0
  109. package/src/{__tests__ → sidebars/__tests__}/__fixtures__/sidebars/sidebars-first-level-not-category.js +0 -0
  110. package/src/{__tests__ → sidebars/__tests__}/__fixtures__/sidebars/sidebars-link-wrong-href.json +0 -0
  111. package/src/{__tests__ → sidebars/__tests__}/__fixtures__/sidebars/sidebars-link-wrong-label.json +0 -0
  112. package/src/{__tests__ → sidebars/__tests__}/__fixtures__/sidebars/sidebars-link.json +0 -0
  113. package/src/{__tests__ → sidebars/__tests__}/__fixtures__/sidebars/sidebars-unknown-type.json +0 -0
  114. package/src/{__tests__ → sidebars/__tests__}/__fixtures__/sidebars/sidebars-wrong-field.json +0 -0
  115. package/src/{__tests__ → sidebars/__tests__}/__fixtures__/sidebars/sidebars.json +0 -0
  116. package/src/{__tests__/__snapshots__/sidebars.test.ts.snap → sidebars/__tests__/__snapshots__/index.test.ts.snap} +36 -6
  117. package/src/{__tests__/sidebarItemsGenerator.test.ts → sidebars/__tests__/generator.test.ts} +143 -18
  118. package/src/sidebars/__tests__/index.test.ts +204 -0
  119. package/src/sidebars/__tests__/processor.test.ts +237 -0
  120. package/src/sidebars/__tests__/utils.test.ts +695 -0
  121. package/src/sidebars/__tests__/validation.test.ts +105 -0
  122. package/src/sidebars/generator.ts +310 -0
  123. package/src/sidebars/index.ts +94 -0
  124. package/src/sidebars/normalization.ts +112 -0
  125. package/src/sidebars/processor.ts +154 -0
  126. package/src/sidebars/types.ts +211 -0
  127. package/src/sidebars/utils.ts +329 -0
  128. package/src/sidebars/validation.ts +168 -0
  129. package/src/slug.ts +32 -17
  130. package/src/tags.ts +19 -0
  131. package/src/theme/hooks/useDocs.ts +5 -1
  132. package/src/translations.ts +103 -47
  133. package/src/types.ts +67 -105
  134. package/src/versions.ts +117 -21
  135. package/lib/sidebarItemsGenerator.js +0 -211
  136. package/lib/sidebars.d.ts +0 -43
  137. package/lib/sidebars.js +0 -319
  138. package/src/__tests__/sidebars.test.ts +0 -639
  139. package/src/sidebarItemsGenerator.ts +0 -307
  140. package/src/sidebars.ts +0 -506
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ import {validateSidebars, validateCategoryMetadataFile} from '../validation';
9
+ import {CategoryMetadataFile} from '../generator';
10
+ import {SidebarsConfig} from '../types';
11
+
12
+ describe('validateSidebars', () => {
13
+ // TODO add more tests
14
+
15
+ // TODO it seems many error cases are not validated properly
16
+ // and error messages are quite bad
17
+ test('throw for bad value', async () => {
18
+ expect(() => validateSidebars({sidebar: [{type: 42}]}))
19
+ .toThrowErrorMatchingInlineSnapshot(`
20
+ "{
21
+ \\"type\\": 42,
22
+ \\"undefined\\" [1]: -- missing --
23
+ }
24
+ 
25
+ [1] Unknown sidebar item type \\"42\\"."
26
+ `);
27
+ });
28
+
29
+ test('accept empty object', async () => {
30
+ const sidebars: SidebarsConfig = {};
31
+ validateSidebars(sidebars);
32
+ });
33
+
34
+ test('accept valid values', async () => {
35
+ const sidebars: SidebarsConfig = {
36
+ sidebar1: [
37
+ {type: 'doc', id: 'doc1'},
38
+ {type: 'doc', id: 'doc2'},
39
+ {
40
+ type: 'category',
41
+ label: 'Category',
42
+ items: [{type: 'doc', id: 'doc3'}],
43
+ },
44
+ ],
45
+ };
46
+ validateSidebars(sidebars);
47
+ });
48
+ });
49
+
50
+ describe('validateCategoryMetadataFile', () => {
51
+ // TODO add more tests
52
+
53
+ test('throw for bad value', async () => {
54
+ expect(() =>
55
+ validateCategoryMetadataFile(42),
56
+ ).toThrowErrorMatchingInlineSnapshot(
57
+ `"\\"value\\" must be of type object"`,
58
+ );
59
+ });
60
+
61
+ test('accept empty object', async () => {
62
+ const content: CategoryMetadataFile = {};
63
+ expect(validateCategoryMetadataFile(content)).toEqual(content);
64
+ });
65
+
66
+ test('accept valid values', async () => {
67
+ const content: CategoryMetadataFile = {
68
+ className: 'className',
69
+ label: 'Category Label',
70
+ link: {
71
+ type: 'generated-index',
72
+ slug: 'slug',
73
+ title: 'title',
74
+ description: 'description',
75
+ },
76
+ collapsible: true,
77
+ collapsed: true,
78
+ position: 3,
79
+ };
80
+ expect(validateCategoryMetadataFile(content)).toEqual(content);
81
+ });
82
+
83
+ test('rejects permalink', async () => {
84
+ const content: CategoryMetadataFile = {
85
+ className: 'className',
86
+ label: 'Category Label',
87
+ link: {
88
+ type: 'generated-index',
89
+ slug: 'slug',
90
+ // @ts-expect-error: rejected on purpose
91
+ permalink: 'somePermalink',
92
+ title: 'title',
93
+ description: 'description',
94
+ },
95
+ collapsible: true,
96
+ collapsed: true,
97
+ position: 3,
98
+ };
99
+ expect(() =>
100
+ validateCategoryMetadataFile(content),
101
+ ).toThrowErrorMatchingInlineSnapshot(
102
+ `"\\"link.permalink\\" is not allowed"`,
103
+ );
104
+ });
105
+ });
@@ -0,0 +1,310 @@
1
+ /**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ import type {
9
+ SidebarItem,
10
+ SidebarItemDoc,
11
+ SidebarItemCategory,
12
+ SidebarItemsGenerator,
13
+ SidebarItemsGeneratorDoc,
14
+ SidebarItemCategoryLink,
15
+ SidebarItemCategoryLinkConfig,
16
+ } from './types';
17
+ import {sortBy, last} from 'lodash';
18
+ import {addTrailingSlash, posixPath} from '@docusaurus/utils';
19
+ import chalk from 'chalk';
20
+ import path from 'path';
21
+ import fs from 'fs-extra';
22
+ import Yaml from 'js-yaml';
23
+ import {validateCategoryMetadataFile} from './validation';
24
+ import {createDocsByIdIndex, isConventionalDocIndex} from '../docs';
25
+
26
+ const BreadcrumbSeparator = '/';
27
+ // To avoid possible name clashes with a folder of the same name as the ID
28
+ const docIdPrefix = '$doc$/';
29
+
30
+ // Just an alias to the make code more explicit
31
+ function getLocalDocId(docId: string): string {
32
+ return last(docId.split('/'))!;
33
+ }
34
+
35
+ export const CategoryMetadataFilenameBase = '_category_';
36
+ export const CategoryMetadataFilenamePattern = '_category_.{json,yml,yaml}';
37
+
38
+ export type CategoryMetadataFile = {
39
+ label?: string;
40
+ position?: number;
41
+ collapsed?: boolean;
42
+ collapsible?: boolean;
43
+ className?: string;
44
+ link?: SidebarItemCategoryLinkConfig;
45
+
46
+ // TODO should we allow "items" here? how would this work? would an "autogenerated" type be allowed?
47
+ // This mkdocs plugin do something like that: https://github.com/lukasgeiter/mkdocs-awesome-pages-plugin/
48
+ // cf comment: https://github.com/facebook/docusaurus/issues/3464#issuecomment-784765199
49
+ };
50
+
51
+ type WithPosition<T> = T & {position?: number};
52
+
53
+ /**
54
+ * A representation of the fs structure. For each object entry:
55
+ * If it's a folder, the key is the directory name, and value is the directory content;
56
+ * If it's a doc file, the key is the doc id prefixed with '$doc$/', and value is null
57
+ */
58
+ type Dir = {
59
+ [item: string]: Dir | null;
60
+ };
61
+
62
+ // TODO I now believe we should read all the category metadata files ahead of time: we may need this metadata to customize docs metadata
63
+ // Example use-case being able to disable number prefix parsing at the folder level, or customize the default route path segment for an intermediate directory...
64
+ // TODO later if there is `CategoryFolder/with-category-name-doc.md`, we may want to read the metadata as yaml on it
65
+ // see https://github.com/facebook/docusaurus/issues/3464#issuecomment-818670449
66
+ async function readCategoryMetadataFile(
67
+ categoryDirPath: string,
68
+ ): Promise<CategoryMetadataFile | null> {
69
+ async function tryReadFile(filePath: string): Promise<CategoryMetadataFile> {
70
+ const contentString = await fs.readFile(filePath, {encoding: 'utf8'});
71
+ const unsafeContent = Yaml.load(contentString);
72
+ try {
73
+ return validateCategoryMetadataFile(unsafeContent);
74
+ } catch (e) {
75
+ console.error(
76
+ chalk.red(
77
+ `The docs sidebar category metadata file looks invalid!\nPath: ${filePath}`,
78
+ ),
79
+ );
80
+ throw e;
81
+ }
82
+ }
83
+ // eslint-disable-next-line no-restricted-syntax
84
+ for (const ext of ['.json', '.yml', '.yaml']) {
85
+ // Simpler to use only posix paths for mocking file metadata in tests
86
+ const filePath = posixPath(
87
+ path.join(categoryDirPath, `${CategoryMetadataFilenameBase}${ext}`),
88
+ );
89
+ if (await fs.pathExists(filePath)) {
90
+ return tryReadFile(filePath);
91
+ }
92
+ }
93
+ return null;
94
+ }
95
+
96
+ // Comment for this feature: https://github.com/facebook/docusaurus/issues/3464#issuecomment-818670449
97
+ export const DefaultSidebarItemsGenerator: SidebarItemsGenerator = async ({
98
+ numberPrefixParser,
99
+ docs: allDocs,
100
+ options,
101
+ item: {dirName: autogenDir},
102
+ version,
103
+ }) => {
104
+ const docsById = createDocsByIdIndex(allDocs);
105
+ const findDoc = (docId: string): SidebarItemsGeneratorDoc | undefined =>
106
+ docsById[docId];
107
+ const getDoc = (docId: string): SidebarItemsGeneratorDoc => {
108
+ const doc = findDoc(docId);
109
+ if (!doc) {
110
+ throw new Error(
111
+ `Can't find any doc with id=${docId}.\nAvailable doc ids:\n- ${Object.keys(
112
+ docsById,
113
+ ).join('\n- ')}`,
114
+ );
115
+ }
116
+ return doc;
117
+ };
118
+
119
+ /**
120
+ * Step 1. Extract the docs that are in the autogen dir.
121
+ */
122
+ function getAutogenDocs(): SidebarItemsGeneratorDoc[] {
123
+ function isInAutogeneratedDir(doc: SidebarItemsGeneratorDoc) {
124
+ return (
125
+ // Doc at the root of the autogenerated sidebar dir
126
+ doc.sourceDirName === autogenDir ||
127
+ // autogen dir is . and doc is in subfolder
128
+ autogenDir === '.' ||
129
+ // autogen dir is not . and doc is in subfolder
130
+ // "api/myDoc" startsWith "api/" (note "api2/myDoc" is not included)
131
+ doc.sourceDirName.startsWith(addTrailingSlash(autogenDir))
132
+ );
133
+ }
134
+ const docs = allDocs.filter(isInAutogeneratedDir);
135
+
136
+ if (docs.length === 0) {
137
+ console.warn(
138
+ chalk.yellow(
139
+ `No docs found in dir ${autogenDir}: can't auto-generate a sidebar.`,
140
+ ),
141
+ );
142
+ }
143
+ return docs;
144
+ }
145
+
146
+ /**
147
+ * Step 2. Turn the linear file list into a tree structure.
148
+ */
149
+ function treeify(docs: SidebarItemsGeneratorDoc[]): Dir {
150
+ // Get the category breadcrumb of a doc (relative to the dir of the autogenerated sidebar item)
151
+ // autogenDir=a/b and docDir=a/b/c/d => returns [c, d]
152
+ // autogenDir=a/b and docDir=a/b => returns []
153
+ // TODO: try to use path.relative()
154
+ function getRelativeBreadcrumb(doc: SidebarItemsGeneratorDoc): string[] {
155
+ return autogenDir === doc.sourceDirName
156
+ ? []
157
+ : doc.sourceDirName
158
+ .replace(addTrailingSlash(autogenDir), '')
159
+ .split(BreadcrumbSeparator);
160
+ }
161
+ const treeRoot: Dir = {};
162
+ docs.forEach((doc) => {
163
+ const breadcrumb = getRelativeBreadcrumb(doc);
164
+ let currentDir = treeRoot; // We walk down the file's path to generate the fs structure
165
+ // eslint-disable-next-line no-restricted-syntax
166
+ for (const dir of breadcrumb) {
167
+ if (typeof currentDir[dir] === 'undefined') {
168
+ currentDir[dir] = {}; // Create new folder.
169
+ }
170
+ currentDir = currentDir[dir]!; // Go into the subdirectory.
171
+ }
172
+ currentDir[`${docIdPrefix}${doc.id}`] = null; // We've walked through the file path. Register the file in this directory.
173
+ });
174
+ return treeRoot;
175
+ }
176
+
177
+ /**
178
+ * Step 3. Recursively transform the tree-like file structure to sidebar items.
179
+ * (From a record to an array of items, akin to normalizing shorthand)
180
+ */
181
+ function generateSidebar(fsModel: Dir): Promise<WithPosition<SidebarItem>[]> {
182
+ function createDocItem(id: string): WithPosition<SidebarItemDoc> {
183
+ const {
184
+ sidebarPosition: position,
185
+ frontMatter: {sidebar_label: label, sidebar_class_name: className},
186
+ } = getDoc(id);
187
+ return {
188
+ type: 'doc',
189
+ id,
190
+ position,
191
+ // We don't want these fields to magically appear in the generated sidebar
192
+ ...(label !== undefined && {label}),
193
+ ...(className !== undefined && {className}),
194
+ };
195
+ }
196
+ async function createCategoryItem(
197
+ dir: Dir,
198
+ fullPath: string,
199
+ folderName: string,
200
+ ): Promise<WithPosition<SidebarItemCategory>> {
201
+ const categoryPath = path.join(version.contentPath, autogenDir, fullPath);
202
+ const categoryMetadata = await readCategoryMetadataFile(categoryPath);
203
+ const className = categoryMetadata?.className;
204
+ const {filename, numberPrefix} = numberPrefixParser(folderName);
205
+ const allItems = await Promise.all(
206
+ Object.entries(dir).map(([key, content]) =>
207
+ dirToItem(content, key, `${fullPath}/${key}`),
208
+ ),
209
+ );
210
+
211
+ // Try to match a doc inside the category folder,
212
+ // using the "local id" (myDoc) or "qualified id" (dirName/myDoc)
213
+ function findDocByLocalId(localId: string): SidebarItemDoc | undefined {
214
+ return allItems.find(
215
+ (item) => item.type === 'doc' && getLocalDocId(item.id) === localId,
216
+ ) as SidebarItemDoc | undefined;
217
+ }
218
+
219
+ function findConventionalCategoryDocLink(): SidebarItemDoc | undefined {
220
+ return allItems.find(
221
+ (item) =>
222
+ item.type === 'doc' && isConventionalDocIndex(getDoc(item.id)),
223
+ ) as SidebarItemDoc | undefined;
224
+ }
225
+
226
+ function getCategoryLinkedDocId(): string | undefined {
227
+ const link = categoryMetadata?.link;
228
+ if (link) {
229
+ if (link.type === 'doc') {
230
+ return findDocByLocalId(link.id)?.id || getDoc(link.id).id;
231
+ } else {
232
+ // We don't continue for other link types on purpose!
233
+ // IE if user decide to use type "generated-index", we should not pick a README.md file as the linked doc
234
+ return undefined;
235
+ }
236
+ }
237
+ // Apply default convention to pick index.md, README.md or <categoryName>.md as the category doc
238
+ return findConventionalCategoryDocLink()?.id;
239
+ }
240
+
241
+ const categoryLinkedDocId = getCategoryLinkedDocId();
242
+
243
+ const link: SidebarItemCategoryLink | undefined = categoryLinkedDocId
244
+ ? {
245
+ type: 'doc',
246
+ id: categoryLinkedDocId, // We "remap" a potentially "local id" to a "qualified id"
247
+ }
248
+ : // TODO typing issue
249
+ (categoryMetadata?.link as SidebarItemCategoryLink | undefined);
250
+
251
+ // If a doc is linked, remove it from the category subItems
252
+ const items = allItems.filter(
253
+ (item) => !(item.type === 'doc' && item.id === categoryLinkedDocId),
254
+ );
255
+
256
+ return {
257
+ type: 'category',
258
+ label: categoryMetadata?.label ?? filename,
259
+ collapsible:
260
+ categoryMetadata?.collapsible ?? options.sidebarCollapsible,
261
+ collapsed: categoryMetadata?.collapsed ?? options.sidebarCollapsed,
262
+ position: categoryMetadata?.position ?? numberPrefix,
263
+ ...(className !== undefined && {className}),
264
+ items,
265
+ ...(link && {link}),
266
+ };
267
+ }
268
+ async function dirToItem(
269
+ dir: Dir | null, // The directory item to be transformed.
270
+ itemKey: string, // For docs, it's the doc ID; for categories, it's used to generate the next `relativePath`.
271
+ fullPath: string, // `dir`'s full path relative to the autogen dir.
272
+ ): Promise<WithPosition<SidebarItem>> {
273
+ return dir
274
+ ? createCategoryItem(dir, fullPath, itemKey)
275
+ : createDocItem(itemKey.substring(docIdPrefix.length));
276
+ }
277
+ return Promise.all(
278
+ Object.entries(fsModel).map(([key, content]) =>
279
+ dirToItem(content, key, key),
280
+ ),
281
+ );
282
+ }
283
+
284
+ /**
285
+ * Step 4. Recursively sort the categories/docs + remove the "position" attribute from final output.
286
+ * Note: the "position" is only used to sort "inside" a sidebar slice. It is not
287
+ * used to sort across multiple consecutive sidebar slices (ie a whole Category
288
+ * composed of multiple autogenerated items)
289
+ */
290
+ function sortItems(sidebarItems: WithPosition<SidebarItem>[]): SidebarItem[] {
291
+ const processedSidebarItems = sidebarItems.map((item) => {
292
+ if (item.type === 'category') {
293
+ return {...item, items: sortItems(item.items)};
294
+ }
295
+ return item;
296
+ });
297
+ const sortedSidebarItems = sortBy(
298
+ processedSidebarItems,
299
+ (item) => item.position,
300
+ );
301
+ return sortedSidebarItems.map(({position, ...item}) => item);
302
+ }
303
+ // TODO: the whole code is designed for pipeline operator
304
+ // return getAutogenDocs() |> treeify |> await generateSidebar(^) |> sortItems;
305
+ const docs = getAutogenDocs();
306
+ const fsModel = treeify(docs);
307
+ const sidebarWithPosition = await generateSidebar(fsModel);
308
+ const sortedSidebar = sortItems(sidebarWithPosition);
309
+ return sortedSidebar;
310
+ };
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ import fs from 'fs-extra';
9
+ import importFresh from 'import-fresh';
10
+ import type {SidebarsConfig, Sidebars, NormalizedSidebars} from './types';
11
+ import type {NormalizeSidebarsParams, PluginOptions} from '../types';
12
+ import {validateSidebars} from './validation';
13
+ import {normalizeSidebars} from './normalization';
14
+ import {processSidebars, SidebarProcessorParams} from './processor';
15
+ import path from 'path';
16
+ import {createSlugger} from '@docusaurus/utils';
17
+
18
+ export const DefaultSidebars: SidebarsConfig = {
19
+ defaultSidebar: [
20
+ {
21
+ type: 'autogenerated',
22
+ dirName: '.',
23
+ },
24
+ ],
25
+ };
26
+
27
+ export const DisabledSidebars: SidebarsConfig = {};
28
+
29
+ // If a path is provided, make it absolute
30
+ // use this before loadSidebars()
31
+ export function resolveSidebarPathOption(
32
+ siteDir: string,
33
+ sidebarPathOption: PluginOptions['sidebarPath'],
34
+ ): PluginOptions['sidebarPath'] {
35
+ return sidebarPathOption
36
+ ? path.resolve(siteDir, sidebarPathOption)
37
+ : sidebarPathOption;
38
+ }
39
+
40
+ function loadSidebarsFileUnsafe(
41
+ sidebarFilePath: string | false | undefined,
42
+ ): SidebarsConfig {
43
+ // false => no sidebars
44
+ if (sidebarFilePath === false) {
45
+ return DisabledSidebars;
46
+ }
47
+
48
+ // undefined => defaults to autogenerated sidebars
49
+ if (typeof sidebarFilePath === 'undefined') {
50
+ return DefaultSidebars;
51
+ }
52
+
53
+ // Non-existent sidebars file: no sidebars
54
+ // Note: this edge case can happen on versioned docs, not current version
55
+ // We avoid creating empty versioned sidebars file with the CLI
56
+ if (!fs.existsSync(sidebarFilePath)) {
57
+ return DisabledSidebars;
58
+ }
59
+
60
+ // We don't want sidebars to be cached because of hot reloading.
61
+ return importFresh(sidebarFilePath);
62
+ }
63
+
64
+ export function loadSidebarsFile(
65
+ sidebarFilePath: string | false | undefined,
66
+ ): SidebarsConfig {
67
+ const sidebarsConfig = loadSidebarsFileUnsafe(sidebarFilePath);
68
+ validateSidebars(sidebarsConfig);
69
+ return sidebarsConfig;
70
+ }
71
+
72
+ export function loadNormalizedSidebars(
73
+ sidebarFilePath: string | false | undefined,
74
+ params: NormalizeSidebarsParams,
75
+ ): NormalizedSidebars {
76
+ return normalizeSidebars(loadSidebarsFile(sidebarFilePath), params);
77
+ }
78
+
79
+ // Note: sidebarFilePath must be absolute, use resolveSidebarPathOption
80
+ export async function loadSidebars(
81
+ sidebarFilePath: string | false | undefined,
82
+ options: SidebarProcessorParams,
83
+ ): Promise<Sidebars> {
84
+ const normalizeSidebarsParams: NormalizeSidebarsParams = {
85
+ ...options.sidebarOptions,
86
+ version: options.version,
87
+ categoryLabelSlugger: createSlugger(),
88
+ };
89
+ const normalizedSidebars = loadNormalizedSidebars(
90
+ sidebarFilePath,
91
+ normalizeSidebarsParams,
92
+ );
93
+ return processSidebars(normalizedSidebars, options);
94
+ }
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ import type {NormalizeSidebarsParams, SidebarOptions} from '../types';
9
+ import type {
10
+ NormalizedSidebarItem,
11
+ NormalizedSidebar,
12
+ NormalizedSidebars,
13
+ SidebarCategoriesShorthand,
14
+ SidebarItemCategoryConfig,
15
+ SidebarItemConfig,
16
+ SidebarConfig,
17
+ SidebarsConfig,
18
+ SidebarItemCategoryLink,
19
+ NormalizedSidebarItemCategory,
20
+ } from './types';
21
+ import {isCategoriesShorthand} from './utils';
22
+ import {mapValues} from 'lodash';
23
+ import {normalizeUrl} from '@docusaurus/utils';
24
+
25
+ function normalizeCategoryLink(
26
+ category: SidebarItemCategoryConfig,
27
+ params: NormalizeSidebarsParams,
28
+ ): SidebarItemCategoryLink | undefined {
29
+ if (category.link?.type === 'generated-index') {
30
+ // default slug logic can be improved
31
+ const getDefaultSlug = () =>
32
+ `/category/${params.categoryLabelSlugger.slug(category.label)}`;
33
+ const slug = category.link.slug ?? getDefaultSlug();
34
+ const permalink = normalizeUrl([params.version.versionPath, slug]);
35
+ return {
36
+ ...category.link,
37
+ slug,
38
+ permalink,
39
+ };
40
+ }
41
+ return category.link;
42
+ }
43
+
44
+ function normalizeCategoriesShorthand(
45
+ sidebar: SidebarCategoriesShorthand,
46
+ options: SidebarOptions,
47
+ ): SidebarItemCategoryConfig[] {
48
+ return Object.entries(sidebar).map(([label, items]) => ({
49
+ type: 'category',
50
+ collapsed: options.sidebarCollapsed,
51
+ collapsible: options.sidebarCollapsible,
52
+ label,
53
+ items,
54
+ }));
55
+ }
56
+
57
+ /**
58
+ * Normalizes recursively item and all its children. Ensures that at the end
59
+ * each item will be an object with the corresponding type.
60
+ */
61
+ export function normalizeItem(
62
+ item: SidebarItemConfig,
63
+ options: NormalizeSidebarsParams,
64
+ ): NormalizedSidebarItem[] {
65
+ if (typeof item === 'string') {
66
+ return [
67
+ {
68
+ type: 'doc',
69
+ id: item,
70
+ },
71
+ ];
72
+ }
73
+ if (isCategoriesShorthand(item)) {
74
+ return normalizeCategoriesShorthand(item, options).flatMap((subItem) =>
75
+ normalizeItem(subItem, options),
76
+ );
77
+ }
78
+ if (item.type === 'category') {
79
+ const link = normalizeCategoryLink(item, options);
80
+ const normalizedCategory: NormalizedSidebarItemCategory = {
81
+ ...item,
82
+ link,
83
+ items: (item.items ?? []).flatMap((subItem) =>
84
+ normalizeItem(subItem, options),
85
+ ),
86
+ collapsible: item.collapsible ?? options.sidebarCollapsible,
87
+ collapsed: item.collapsed ?? options.sidebarCollapsed,
88
+ };
89
+ return [normalizedCategory];
90
+ }
91
+ return [item];
92
+ }
93
+
94
+ function normalizeSidebar(
95
+ sidebar: SidebarConfig,
96
+ options: NormalizeSidebarsParams,
97
+ ): NormalizedSidebar {
98
+ const normalizedSidebar = Array.isArray(sidebar)
99
+ ? sidebar
100
+ : normalizeCategoriesShorthand(sidebar, options);
101
+
102
+ return normalizedSidebar.flatMap((subItem) =>
103
+ normalizeItem(subItem, options),
104
+ );
105
+ }
106
+
107
+ export function normalizeSidebars(
108
+ sidebars: SidebarsConfig,
109
+ params: NormalizeSidebarsParams,
110
+ ): NormalizedSidebars {
111
+ return mapValues(sidebars, (items) => normalizeSidebar(items, params));
112
+ }