@docusaurus/plugin-content-docs 2.0.0-beta.138b4c997 → 2.0.0-beta.14

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 (201) hide show
  1. package/lib/categoryGeneratedIndex.d.ts +12 -0
  2. package/lib/categoryGeneratedIndex.js +37 -0
  3. package/lib/cli.d.ts +2 -2
  4. package/lib/cli.js +14 -35
  5. package/lib/client/docsClientUtils.d.ts +0 -3
  6. package/lib/client/docsClientUtils.js +19 -22
  7. package/lib/docFrontMatter.d.ts +1 -1
  8. package/lib/docFrontMatter.js +8 -4
  9. package/lib/docs.d.ts +25 -3
  10. package/lib/docs.js +126 -41
  11. package/lib/globalData.d.ts +1 -1
  12. package/lib/index.d.ts +1 -1
  13. package/lib/index.js +101 -133
  14. package/lib/lastUpdate.js +12 -12
  15. package/lib/markdown/index.d.ts +3 -6
  16. package/lib/markdown/index.js +3 -3
  17. package/lib/markdown/linkify.js +2 -2
  18. package/lib/numberPrefix.d.ts +1 -1
  19. package/lib/options.d.ts +3 -3
  20. package/lib/options.js +48 -11
  21. package/lib/props.d.ts +7 -2
  22. package/lib/props.js +60 -9
  23. package/lib/routes.d.ts +27 -0
  24. package/lib/routes.js +105 -0
  25. package/lib/{sidebarItemsGenerator.d.ts → sidebars/generator.d.ts} +5 -2
  26. package/lib/sidebars/generator.js +216 -0
  27. package/lib/sidebars/index.d.ts +15 -0
  28. package/lib/sidebars/index.js +73 -0
  29. package/lib/sidebars/normalization.d.ts +14 -0
  30. package/lib/sidebars/normalization.js +77 -0
  31. package/lib/sidebars/processor.d.ts +18 -0
  32. package/lib/sidebars/processor.js +85 -0
  33. package/lib/sidebars/types.d.ts +127 -0
  34. package/{src/__tests__/__fixtures__/site-with-autogenerated-sidebar/partialAutogeneratedSidebars2.js → lib/sidebars/types.js} +2 -10
  35. package/lib/sidebars/utils.d.ts +35 -0
  36. package/lib/sidebars/utils.js +228 -0
  37. package/lib/sidebars/validation.d.ts +10 -0
  38. package/lib/sidebars/validation.js +138 -0
  39. package/lib/slug.d.ts +4 -3
  40. package/lib/slug.js +27 -15
  41. package/{src/__tests__/__fixtures__/site-with-doc-label/docusaurus.config.js → lib/tags.d.ts} +2 -8
  42. package/lib/tags.js +20 -0
  43. package/lib/theme/hooks/useDocs.js +24 -21
  44. package/lib/translations.d.ts +2 -2
  45. package/lib/translations.js +71 -29
  46. package/lib/types.d.ts +52 -64
  47. package/lib/versions.d.ts +3 -3
  48. package/lib/versions.js +41 -22
  49. package/package.json +20 -20
  50. package/src/categoryGeneratedIndex.ts +57 -0
  51. package/src/cli.ts +10 -42
  52. package/src/client/docsClientUtils.ts +14 -26
  53. package/{types.d.ts → src/deps.d.ts} +0 -0
  54. package/src/docFrontMatter.ts +10 -5
  55. package/src/docs.ts +164 -36
  56. package/src/globalData.ts +6 -1
  57. package/src/index.ts +127 -177
  58. package/src/lastUpdate.ts +14 -16
  59. package/src/markdown/index.ts +8 -12
  60. package/src/numberPrefix.ts +5 -3
  61. package/src/options.ts +56 -15
  62. package/src/plugin-content-docs.d.ts +172 -43
  63. package/src/props.ts +90 -16
  64. package/src/routes.ts +169 -0
  65. package/src/sidebars/generator.ts +302 -0
  66. package/src/sidebars/index.ts +94 -0
  67. package/src/sidebars/normalization.ts +112 -0
  68. package/src/sidebars/processor.ts +154 -0
  69. package/src/sidebars/types.ts +211 -0
  70. package/src/sidebars/utils.ts +329 -0
  71. package/src/sidebars/validation.ts +168 -0
  72. package/src/slug.ts +32 -17
  73. package/src/tags.ts +19 -0
  74. package/src/theme/hooks/useDocs.ts +5 -1
  75. package/src/translations.ts +103 -47
  76. package/src/types.ts +64 -108
  77. package/src/versions.ts +59 -25
  78. package/lib/.tsbuildinfo +0 -1
  79. package/lib/sidebarItemsGenerator.js +0 -211
  80. package/lib/sidebars.d.ts +0 -43
  81. package/lib/sidebars.js +0 -320
  82. package/src/__tests__/__fixtures__/bad-id-site/docs/invalid-id.md +0 -5
  83. package/src/__tests__/__fixtures__/bad-slug-on-doc-home-site/docs/docWithSlug.md +0 -5
  84. package/src/__tests__/__fixtures__/empty-site/docusaurus.config.js +0 -16
  85. package/src/__tests__/__fixtures__/empty-site/sidebars.json +0 -1
  86. package/src/__tests__/__fixtures__/sidebars/sidebars-category-shorthand.js +0 -34
  87. package/src/__tests__/__fixtures__/sidebars/sidebars-category-wrong-items.json +0 -11
  88. package/src/__tests__/__fixtures__/sidebars/sidebars-category-wrong-label.json +0 -11
  89. package/src/__tests__/__fixtures__/sidebars/sidebars-category.js +0 -44
  90. package/src/__tests__/__fixtures__/sidebars/sidebars-collapsed-first-level.json +0 -20
  91. package/src/__tests__/__fixtures__/sidebars/sidebars-collapsed.json +0 -21
  92. package/src/__tests__/__fixtures__/sidebars/sidebars-doc-id-not-string.json +0 -10
  93. package/src/__tests__/__fixtures__/sidebars/sidebars-first-level-not-category.js +0 -20
  94. package/src/__tests__/__fixtures__/sidebars/sidebars-link-wrong-href.json +0 -11
  95. package/src/__tests__/__fixtures__/sidebars/sidebars-link-wrong-label.json +0 -11
  96. package/src/__tests__/__fixtures__/sidebars/sidebars-link.json +0 -11
  97. package/src/__tests__/__fixtures__/sidebars/sidebars-unknown-type.json +0 -14
  98. package/src/__tests__/__fixtures__/sidebars/sidebars-wrong-field.json +0 -20
  99. package/src/__tests__/__fixtures__/sidebars/sidebars.json +0 -20
  100. package/src/__tests__/__fixtures__/simple-site/docs/foo/bar.md +0 -69
  101. package/src/__tests__/__fixtures__/simple-site/docs/foo/baz.md +0 -70
  102. package/src/__tests__/__fixtures__/simple-site/docs/headingAsTitle.md +0 -1
  103. package/src/__tests__/__fixtures__/simple-site/docs/hello.md +0 -53
  104. package/src/__tests__/__fixtures__/simple-site/docs/ipsum.md +0 -5
  105. package/src/__tests__/__fixtures__/simple-site/docs/lorem.md +0 -6
  106. package/src/__tests__/__fixtures__/simple-site/docs/rootAbsoluteSlug.md +0 -5
  107. package/src/__tests__/__fixtures__/simple-site/docs/rootRelativeSlug.md +0 -5
  108. package/src/__tests__/__fixtures__/simple-site/docs/rootResolvedSlug.md +0 -5
  109. package/src/__tests__/__fixtures__/simple-site/docs/rootTryToEscapeSlug.md +0 -5
  110. package/src/__tests__/__fixtures__/simple-site/docs/slugs/absoluteSlug.md +0 -5
  111. package/src/__tests__/__fixtures__/simple-site/docs/slugs/relativeSlug.md +0 -5
  112. package/src/__tests__/__fixtures__/simple-site/docs/slugs/resolvedSlug.md +0 -5
  113. package/src/__tests__/__fixtures__/simple-site/docs/slugs/tryToEscapeSlug.md +0 -5
  114. package/src/__tests__/__fixtures__/simple-site/docusaurus.config.js +0 -14
  115. package/src/__tests__/__fixtures__/simple-site/sidebars.json +0 -23
  116. package/src/__tests__/__fixtures__/simple-site/wrong-sidebars.json +0 -7
  117. package/src/__tests__/__fixtures__/site-with-autogenerated-sidebar/docs/0-getting-started.md +0 -3
  118. package/src/__tests__/__fixtures__/site-with-autogenerated-sidebar/docs/1-installation.md +0 -3
  119. package/src/__tests__/__fixtures__/site-with-autogenerated-sidebar/docs/3-API/00_api-overview.md +0 -3
  120. package/src/__tests__/__fixtures__/site-with-autogenerated-sidebar/docs/3-API/01_Core APIs/0 --- Client API.md +0 -1
  121. package/src/__tests__/__fixtures__/site-with-autogenerated-sidebar/docs/3-API/01_Core APIs/1 --- Server API.md +0 -1
  122. package/src/__tests__/__fixtures__/site-with-autogenerated-sidebar/docs/3-API/02_Extension APIs/0. Plugin API.md +0 -1
  123. package/src/__tests__/__fixtures__/site-with-autogenerated-sidebar/docs/3-API/02_Extension APIs/1. Theme API.md +0 -1
  124. package/src/__tests__/__fixtures__/site-with-autogenerated-sidebar/docs/3-API/02_Extension APIs/_category_.yml +0 -1
  125. package/src/__tests__/__fixtures__/site-with-autogenerated-sidebar/docs/3-API/03_api-end.md +0 -3
  126. package/src/__tests__/__fixtures__/site-with-autogenerated-sidebar/docs/3-API/_category_.json +0 -3
  127. package/src/__tests__/__fixtures__/site-with-autogenerated-sidebar/docs/Guides/0-guide2.5.md +0 -8
  128. package/src/__tests__/__fixtures__/site-with-autogenerated-sidebar/docs/Guides/02-guide2.md +0 -7
  129. package/src/__tests__/__fixtures__/site-with-autogenerated-sidebar/docs/Guides/_category_.json +0 -3
  130. package/src/__tests__/__fixtures__/site-with-autogenerated-sidebar/docs/Guides/a-guide4.md +0 -7
  131. package/src/__tests__/__fixtures__/site-with-autogenerated-sidebar/docs/Guides/b-guide5.md +0 -7
  132. package/src/__tests__/__fixtures__/site-with-autogenerated-sidebar/docs/Guides/guide3.md +0 -8
  133. package/src/__tests__/__fixtures__/site-with-autogenerated-sidebar/docs/Guides/z-guide1.md +0 -8
  134. package/src/__tests__/__fixtures__/site-with-autogenerated-sidebar/docusaurus.config.js +0 -14
  135. package/src/__tests__/__fixtures__/site-with-autogenerated-sidebar/partialAutogeneratedSidebars.js +0 -23
  136. package/src/__tests__/__fixtures__/site-with-doc-label/docs/hello-1.md +0 -7
  137. package/src/__tests__/__fixtures__/site-with-doc-label/docs/hello-2.md +0 -8
  138. package/src/__tests__/__fixtures__/site-with-doc-label/sidebars.json +0 -14
  139. package/src/__tests__/__fixtures__/versioned-site/community/team.md +0 -1
  140. package/src/__tests__/__fixtures__/versioned-site/community_sidebars.json +0 -3
  141. package/src/__tests__/__fixtures__/versioned-site/community_versioned_docs/version-1.0.0/team.md +0 -1
  142. package/src/__tests__/__fixtures__/versioned-site/community_versioned_sidebars/version-1.0.0-sidebars.json +0 -3
  143. package/src/__tests__/__fixtures__/versioned-site/community_versions.json +0 -1
  144. package/src/__tests__/__fixtures__/versioned-site/docs/foo/bar.md +0 -4
  145. package/src/__tests__/__fixtures__/versioned-site/docs/hello.md +0 -1
  146. package/src/__tests__/__fixtures__/versioned-site/docs/slugs/absoluteSlug.md +0 -5
  147. package/src/__tests__/__fixtures__/versioned-site/docs/slugs/relativeSlug.md +0 -5
  148. package/src/__tests__/__fixtures__/versioned-site/docs/slugs/resolvedSlug.md +0 -5
  149. package/src/__tests__/__fixtures__/versioned-site/docs/slugs/tryToEscapeSlug.md +0 -5
  150. package/src/__tests__/__fixtures__/versioned-site/docusaurus.config.js +0 -18
  151. package/src/__tests__/__fixtures__/versioned-site/i18n/en/docusaurus-plugin-content-docs/version-1.0.0/hello.md +0 -1
  152. package/src/__tests__/__fixtures__/versioned-site/i18n/en/docusaurus-plugin-content-docs-community/current/team.md +0 -5
  153. package/src/__tests__/__fixtures__/versioned-site/i18n/fr/docusaurus-plugin-content-docs/version-1.0.0/hello.md +0 -1
  154. package/src/__tests__/__fixtures__/versioned-site/sidebars.json +0 -10
  155. package/src/__tests__/__fixtures__/versioned-site/versioned_docs/version-1.0.0/foo/bar.md +0 -4
  156. package/src/__tests__/__fixtures__/versioned-site/versioned_docs/version-1.0.0/foo/baz.md +0 -1
  157. package/src/__tests__/__fixtures__/versioned-site/versioned_docs/version-1.0.0/hello.md +0 -1
  158. package/src/__tests__/__fixtures__/versioned-site/versioned_docs/version-1.0.1/foo/bar.md +0 -1
  159. package/src/__tests__/__fixtures__/versioned-site/versioned_docs/version-1.0.1/hello.md +0 -1
  160. package/src/__tests__/__fixtures__/versioned-site/versioned_docs/version-withSlugs/rootAbsoluteSlug.md +0 -5
  161. package/src/__tests__/__fixtures__/versioned-site/versioned_docs/version-withSlugs/rootRelativeSlug.md +0 -5
  162. package/src/__tests__/__fixtures__/versioned-site/versioned_docs/version-withSlugs/rootResolvedSlug.md +0 -5
  163. package/src/__tests__/__fixtures__/versioned-site/versioned_docs/version-withSlugs/rootTryToEscapeSlug.md +0 -5
  164. package/src/__tests__/__fixtures__/versioned-site/versioned_docs/version-withSlugs/slugs/absoluteSlug.md +0 -5
  165. package/src/__tests__/__fixtures__/versioned-site/versioned_docs/version-withSlugs/slugs/relativeSlug.md +0 -5
  166. package/src/__tests__/__fixtures__/versioned-site/versioned_docs/version-withSlugs/slugs/resolvedSlug.md +0 -5
  167. package/src/__tests__/__fixtures__/versioned-site/versioned_docs/version-withSlugs/slugs/tryToEscapeSlug.md +0 -5
  168. package/src/__tests__/__fixtures__/versioned-site/versioned_sidebars/version-1.0.0-sidebars.json +0 -11
  169. package/src/__tests__/__fixtures__/versioned-site/versioned_sidebars/version-1.0.1-sidebars.json +0 -10
  170. package/src/__tests__/__fixtures__/versioned-site/versioned_sidebars/version-withSlugs-sidebars.json +0 -5
  171. package/src/__tests__/__fixtures__/versioned-site/versions.json +0 -5
  172. package/src/__tests__/__snapshots__/cli.test.ts.snap +0 -90
  173. package/src/__tests__/__snapshots__/index.test.ts.snap +0 -1916
  174. package/src/__tests__/__snapshots__/sidebars.test.ts.snap +0 -218
  175. package/src/__tests__/__snapshots__/translations.test.ts.snap +0 -487
  176. package/src/__tests__/cli.test.ts +0 -333
  177. package/src/__tests__/docFrontMatter.test.ts +0 -244
  178. package/src/__tests__/docs.test.ts +0 -878
  179. package/src/__tests__/index.test.ts +0 -1871
  180. package/src/__tests__/lastUpdate.test.ts +0 -69
  181. package/src/__tests__/numberPrefix.test.ts +0 -199
  182. package/src/__tests__/options.test.ts +0 -231
  183. package/src/__tests__/sidebarItemsGenerator.test.ts +0 -336
  184. package/src/__tests__/sidebars.test.ts +0 -639
  185. package/src/__tests__/slug.test.ts +0 -109
  186. package/src/__tests__/translations.test.ts +0 -159
  187. package/src/__tests__/versions.test.ts +0 -741
  188. package/src/client/__tests__/docsClientUtils.test.ts +0 -371
  189. package/src/markdown/__tests__/__fixtures__/docs/doc-localized.md +0 -1
  190. package/src/markdown/__tests__/__fixtures__/docs/doc1.md +0 -13
  191. package/src/markdown/__tests__/__fixtures__/docs/doc2.md +0 -12
  192. package/src/markdown/__tests__/__fixtures__/docs/doc4.md +0 -19
  193. package/src/markdown/__tests__/__fixtures__/docs/doc5.md +0 -6
  194. package/src/markdown/__tests__/__fixtures__/docs/subdir/doc3.md +0 -3
  195. package/src/markdown/__tests__/__fixtures__/versioned_docs/version-1.0.0/doc2.md +0 -7
  196. package/src/markdown/__tests__/__fixtures__/versioned_docs/version-1.0.0/subdir/doc1.md +0 -3
  197. package/src/markdown/__tests__/__snapshots__/linkify.test.ts.snap +0 -82
  198. package/src/markdown/__tests__/linkify.test.ts +0 -190
  199. package/src/sidebarItemsGenerator.ts +0 -307
  200. package/src/sidebars.ts +0 -522
  201. package/tsconfig.json +0 -9
@@ -0,0 +1,302 @@
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 logger from '@docusaurus/logger';
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
+ logger.error`The docs sidebar category metadata file path=${filePath} looks invalid!`;
76
+ throw e;
77
+ }
78
+ }
79
+ // eslint-disable-next-line no-restricted-syntax
80
+ for (const ext of ['.json', '.yml', '.yaml']) {
81
+ // Simpler to use only posix paths for mocking file metadata in tests
82
+ const filePath = posixPath(
83
+ path.join(categoryDirPath, `${CategoryMetadataFilenameBase}${ext}`),
84
+ );
85
+ if (await fs.pathExists(filePath)) {
86
+ return tryReadFile(filePath);
87
+ }
88
+ }
89
+ return null;
90
+ }
91
+
92
+ // Comment for this feature: https://github.com/facebook/docusaurus/issues/3464#issuecomment-818670449
93
+ export const DefaultSidebarItemsGenerator: SidebarItemsGenerator = async ({
94
+ numberPrefixParser,
95
+ docs: allDocs,
96
+ options,
97
+ item: {dirName: autogenDir},
98
+ version,
99
+ }) => {
100
+ const docsById = createDocsByIdIndex(allDocs);
101
+ const findDoc = (docId: string): SidebarItemsGeneratorDoc | undefined =>
102
+ docsById[docId];
103
+ const getDoc = (docId: string): SidebarItemsGeneratorDoc => {
104
+ const doc = findDoc(docId);
105
+ if (!doc) {
106
+ throw new Error(
107
+ `Can't find any doc with id=${docId}.\nAvailable doc ids:\n- ${Object.keys(
108
+ docsById,
109
+ ).join('\n- ')}`,
110
+ );
111
+ }
112
+ return doc;
113
+ };
114
+
115
+ /**
116
+ * Step 1. Extract the docs that are in the autogen dir.
117
+ */
118
+ function getAutogenDocs(): SidebarItemsGeneratorDoc[] {
119
+ function isInAutogeneratedDir(doc: SidebarItemsGeneratorDoc) {
120
+ return (
121
+ // Doc at the root of the autogenerated sidebar dir
122
+ doc.sourceDirName === autogenDir ||
123
+ // autogen dir is . and doc is in subfolder
124
+ autogenDir === '.' ||
125
+ // autogen dir is not . and doc is in subfolder
126
+ // "api/myDoc" startsWith "api/" (note "api2/myDoc" is not included)
127
+ doc.sourceDirName.startsWith(addTrailingSlash(autogenDir))
128
+ );
129
+ }
130
+ const docs = allDocs.filter(isInAutogeneratedDir);
131
+
132
+ if (docs.length === 0) {
133
+ logger.warn`No docs found in path=${autogenDir}: can't auto-generate a sidebar.`;
134
+ }
135
+ return docs;
136
+ }
137
+
138
+ /**
139
+ * Step 2. Turn the linear file list into a tree structure.
140
+ */
141
+ function treeify(docs: SidebarItemsGeneratorDoc[]): Dir {
142
+ // Get the category breadcrumb of a doc (relative to the dir of the autogenerated sidebar item)
143
+ // autogenDir=a/b and docDir=a/b/c/d => returns [c, d]
144
+ // autogenDir=a/b and docDir=a/b => returns []
145
+ // TODO: try to use path.relative()
146
+ function getRelativeBreadcrumb(doc: SidebarItemsGeneratorDoc): string[] {
147
+ return autogenDir === doc.sourceDirName
148
+ ? []
149
+ : doc.sourceDirName
150
+ .replace(addTrailingSlash(autogenDir), '')
151
+ .split(BreadcrumbSeparator);
152
+ }
153
+ const treeRoot: Dir = {};
154
+ docs.forEach((doc) => {
155
+ const breadcrumb = getRelativeBreadcrumb(doc);
156
+ let currentDir = treeRoot; // We walk down the file's path to generate the fs structure
157
+ // eslint-disable-next-line no-restricted-syntax
158
+ for (const dir of breadcrumb) {
159
+ if (typeof currentDir[dir] === 'undefined') {
160
+ currentDir[dir] = {}; // Create new folder.
161
+ }
162
+ currentDir = currentDir[dir]!; // Go into the subdirectory.
163
+ }
164
+ currentDir[`${docIdPrefix}${doc.id}`] = null; // We've walked through the file path. Register the file in this directory.
165
+ });
166
+ return treeRoot;
167
+ }
168
+
169
+ /**
170
+ * Step 3. Recursively transform the tree-like file structure to sidebar items.
171
+ * (From a record to an array of items, akin to normalizing shorthand)
172
+ */
173
+ function generateSidebar(fsModel: Dir): Promise<WithPosition<SidebarItem>[]> {
174
+ function createDocItem(id: string): WithPosition<SidebarItemDoc> {
175
+ const {
176
+ sidebarPosition: position,
177
+ frontMatter: {sidebar_label: label, sidebar_class_name: className},
178
+ } = getDoc(id);
179
+ return {
180
+ type: 'doc',
181
+ id,
182
+ position,
183
+ // We don't want these fields to magically appear in the generated sidebar
184
+ ...(label !== undefined && {label}),
185
+ ...(className !== undefined && {className}),
186
+ };
187
+ }
188
+ async function createCategoryItem(
189
+ dir: Dir,
190
+ fullPath: string,
191
+ folderName: string,
192
+ ): Promise<WithPosition<SidebarItemCategory>> {
193
+ const categoryPath = path.join(version.contentPath, autogenDir, fullPath);
194
+ const categoryMetadata = await readCategoryMetadataFile(categoryPath);
195
+ const className = categoryMetadata?.className;
196
+ const {filename, numberPrefix} = numberPrefixParser(folderName);
197
+ const allItems = await Promise.all(
198
+ Object.entries(dir).map(([key, content]) =>
199
+ dirToItem(content, key, `${fullPath}/${key}`),
200
+ ),
201
+ );
202
+
203
+ // Try to match a doc inside the category folder,
204
+ // using the "local id" (myDoc) or "qualified id" (dirName/myDoc)
205
+ function findDocByLocalId(localId: string): SidebarItemDoc | undefined {
206
+ return allItems.find(
207
+ (item) => item.type === 'doc' && getLocalDocId(item.id) === localId,
208
+ ) as SidebarItemDoc | undefined;
209
+ }
210
+
211
+ function findConventionalCategoryDocLink(): SidebarItemDoc | undefined {
212
+ return allItems.find(
213
+ (item) =>
214
+ item.type === 'doc' && isConventionalDocIndex(getDoc(item.id)),
215
+ ) as SidebarItemDoc | undefined;
216
+ }
217
+
218
+ function getCategoryLinkedDocId(): string | undefined {
219
+ const link = categoryMetadata?.link;
220
+ if (link) {
221
+ if (link.type === 'doc') {
222
+ return findDocByLocalId(link.id)?.id || getDoc(link.id).id;
223
+ } else {
224
+ // We don't continue for other link types on purpose!
225
+ // IE if user decide to use type "generated-index", we should not pick a README.md file as the linked doc
226
+ return undefined;
227
+ }
228
+ }
229
+ // Apply default convention to pick index.md, README.md or <categoryName>.md as the category doc
230
+ return findConventionalCategoryDocLink()?.id;
231
+ }
232
+
233
+ const categoryLinkedDocId = getCategoryLinkedDocId();
234
+
235
+ const link: SidebarItemCategoryLink | undefined = categoryLinkedDocId
236
+ ? {
237
+ type: 'doc',
238
+ id: categoryLinkedDocId, // We "remap" a potentially "local id" to a "qualified id"
239
+ }
240
+ : // TODO typing issue
241
+ (categoryMetadata?.link as SidebarItemCategoryLink | undefined);
242
+
243
+ // If a doc is linked, remove it from the category subItems
244
+ const items = allItems.filter(
245
+ (item) => !(item.type === 'doc' && item.id === categoryLinkedDocId),
246
+ );
247
+
248
+ return {
249
+ type: 'category',
250
+ label: categoryMetadata?.label ?? filename,
251
+ collapsible:
252
+ categoryMetadata?.collapsible ?? options.sidebarCollapsible,
253
+ collapsed: categoryMetadata?.collapsed ?? options.sidebarCollapsed,
254
+ position: categoryMetadata?.position ?? numberPrefix,
255
+ ...(className !== undefined && {className}),
256
+ items,
257
+ ...(link && {link}),
258
+ };
259
+ }
260
+ async function dirToItem(
261
+ dir: Dir | null, // The directory item to be transformed.
262
+ itemKey: string, // For docs, it's the doc ID; for categories, it's used to generate the next `relativePath`.
263
+ fullPath: string, // `dir`'s full path relative to the autogen dir.
264
+ ): Promise<WithPosition<SidebarItem>> {
265
+ return dir
266
+ ? createCategoryItem(dir, fullPath, itemKey)
267
+ : createDocItem(itemKey.substring(docIdPrefix.length));
268
+ }
269
+ return Promise.all(
270
+ Object.entries(fsModel).map(([key, content]) =>
271
+ dirToItem(content, key, key),
272
+ ),
273
+ );
274
+ }
275
+
276
+ /**
277
+ * Step 4. Recursively sort the categories/docs + remove the "position" attribute from final output.
278
+ * Note: the "position" is only used to sort "inside" a sidebar slice. It is not
279
+ * used to sort across multiple consecutive sidebar slices (ie a whole Category
280
+ * composed of multiple autogenerated items)
281
+ */
282
+ function sortItems(sidebarItems: WithPosition<SidebarItem>[]): SidebarItem[] {
283
+ const processedSidebarItems = sidebarItems.map((item) => {
284
+ if (item.type === 'category') {
285
+ return {...item, items: sortItems(item.items)};
286
+ }
287
+ return item;
288
+ });
289
+ const sortedSidebarItems = sortBy(
290
+ processedSidebarItems,
291
+ (item) => item.position,
292
+ );
293
+ return sortedSidebarItems.map(({position, ...item}) => item);
294
+ }
295
+ // TODO: the whole code is designed for pipeline operator
296
+ // return getAutogenDocs() |> treeify |> await generateSidebar(^) |> sortItems;
297
+ const docs = getAutogenDocs();
298
+ const fsModel = treeify(docs);
299
+ const sidebarWithPosition = await generateSidebar(fsModel);
300
+ const sortedSidebar = sortItems(sidebarWithPosition);
301
+ return sortedSidebar;
302
+ };
@@ -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
+ }
@@ -0,0 +1,154 @@
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
+ NumberPrefixParser,
10
+ DocMetadataBase,
11
+ VersionMetadata,
12
+ SidebarOptions,
13
+ } from '../types';
14
+ import type {
15
+ Sidebars,
16
+ Sidebar,
17
+ SidebarItem,
18
+ NormalizedSidebarItem,
19
+ NormalizedSidebar,
20
+ NormalizedSidebars,
21
+ SidebarItemsGeneratorOption,
22
+ SidebarItemsGeneratorDoc,
23
+ SidebarItemsGeneratorVersion,
24
+ NormalizedSidebarItemCategory,
25
+ SidebarItemCategory,
26
+ SidebarItemAutogenerated,
27
+ } from './types';
28
+ import {transformSidebarItems} from './utils';
29
+ import {DefaultSidebarItemsGenerator} from './generator';
30
+ import {mapValues, memoize, pick} from 'lodash';
31
+ import combinePromises from 'combine-promises';
32
+ import {normalizeItem} from './normalization';
33
+ import {Slugger} from '@docusaurus/utils';
34
+
35
+ export type SidebarProcessorParams = {
36
+ sidebarItemsGenerator: SidebarItemsGeneratorOption;
37
+ numberPrefixParser: NumberPrefixParser;
38
+ docs: DocMetadataBase[];
39
+ version: VersionMetadata;
40
+ categoryLabelSlugger: Slugger;
41
+ sidebarOptions: SidebarOptions;
42
+ };
43
+
44
+ function toSidebarItemsGeneratorDoc(
45
+ doc: DocMetadataBase,
46
+ ): SidebarItemsGeneratorDoc {
47
+ return pick(doc, [
48
+ 'id',
49
+ 'unversionedId',
50
+ 'frontMatter',
51
+ 'source',
52
+ 'sourceDirName',
53
+ 'sidebarPosition',
54
+ ]);
55
+ }
56
+
57
+ function toSidebarItemsGeneratorVersion(
58
+ version: VersionMetadata,
59
+ ): SidebarItemsGeneratorVersion {
60
+ return pick(version, ['versionName', 'contentPath']);
61
+ }
62
+
63
+ // Handle the generation of autogenerated sidebar items and other post-processing checks
64
+ async function processSidebar(
65
+ unprocessedSidebar: NormalizedSidebar,
66
+ params: SidebarProcessorParams,
67
+ ): Promise<Sidebar> {
68
+ const {
69
+ sidebarItemsGenerator,
70
+ numberPrefixParser,
71
+ docs,
72
+ version,
73
+ sidebarOptions,
74
+ } = params;
75
+
76
+ // Just a minor lazy transformation optimization
77
+ const getSidebarItemsGeneratorDocsAndVersion = memoize(() => ({
78
+ docs: docs.map(toSidebarItemsGeneratorDoc),
79
+ version: toSidebarItemsGeneratorVersion(version),
80
+ }));
81
+
82
+ async function processCategoryItem(
83
+ item: NormalizedSidebarItemCategory,
84
+ ): Promise<SidebarItemCategory> {
85
+ return {
86
+ ...item,
87
+ items: (await Promise.all(item.items.map(processItem))).flat(),
88
+ };
89
+ }
90
+
91
+ async function processAutoGeneratedItem(
92
+ item: SidebarItemAutogenerated,
93
+ ): Promise<SidebarItem[]> {
94
+ // TODO the returned type can't be trusted in practice (generator can be user-provided)
95
+ const generatedItems = await sidebarItemsGenerator({
96
+ item,
97
+ numberPrefixParser,
98
+ defaultSidebarItemsGenerator: DefaultSidebarItemsGenerator,
99
+ ...getSidebarItemsGeneratorDocsAndVersion(),
100
+ options: sidebarOptions,
101
+ });
102
+ // TODO validate generated items: user can generate bad items
103
+
104
+ const generatedItemsNormalized = generatedItems.flatMap((generatedItem) =>
105
+ normalizeItem(generatedItem, {...params, ...sidebarOptions}),
106
+ );
107
+
108
+ // Process again... weird but sidebar item generated might generate some auto-generated items?
109
+ return processItems(generatedItemsNormalized);
110
+ }
111
+
112
+ async function processItem(
113
+ item: NormalizedSidebarItem,
114
+ ): Promise<SidebarItem[]> {
115
+ if (item.type === 'category') {
116
+ return [await processCategoryItem(item)];
117
+ }
118
+ if (item.type === 'autogenerated') {
119
+ return processAutoGeneratedItem(item);
120
+ }
121
+ return [item];
122
+ }
123
+
124
+ async function processItems(
125
+ items: NormalizedSidebarItem[],
126
+ ): Promise<SidebarItem[]> {
127
+ return (await Promise.all(items.map(processItem))).flat();
128
+ }
129
+
130
+ const processedSidebar = await processItems(unprocessedSidebar);
131
+
132
+ const fixSidebarItemInconsistencies = (item: SidebarItem): SidebarItem => {
133
+ // A non-collapsible category can't be collapsed!
134
+ if (item.type === 'category' && !item.collapsible && item.collapsed) {
135
+ return {
136
+ ...item,
137
+ collapsed: false,
138
+ };
139
+ }
140
+ return item;
141
+ };
142
+ return transformSidebarItems(processedSidebar, fixSidebarItemInconsistencies);
143
+ }
144
+
145
+ export async function processSidebars(
146
+ unprocessedSidebars: NormalizedSidebars,
147
+ params: SidebarProcessorParams,
148
+ ): Promise<Sidebars> {
149
+ return combinePromises(
150
+ mapValues(unprocessedSidebars, (unprocessedSidebar) =>
151
+ processSidebar(unprocessedSidebar, params),
152
+ ),
153
+ );
154
+ }