@docusaurus/plugin-content-docs 2.0.0-beta.15 → 2.0.0-beta.16

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 (69) hide show
  1. package/lib/cli.d.ts +1 -1
  2. package/lib/cli.js +18 -14
  3. package/lib/client/docsClientUtils.js +2 -2
  4. package/lib/client/index.d.ts +13 -1
  5. package/lib/client/index.js +66 -1
  6. package/lib/docFrontMatter.js +1 -0
  7. package/lib/docs.d.ts +2 -1
  8. package/lib/docs.js +23 -22
  9. package/lib/globalData.js +3 -2
  10. package/lib/index.js +11 -6
  11. package/lib/lastUpdate.js +14 -27
  12. package/lib/numberPrefix.js +7 -6
  13. package/lib/options.js +5 -2
  14. package/lib/props.js +15 -10
  15. package/lib/routes.js +6 -4
  16. package/lib/server-export.d.ts +8 -0
  17. package/lib/server-export.js +23 -0
  18. package/lib/sidebars/generator.d.ts +1 -9
  19. package/lib/sidebars/generator.js +26 -50
  20. package/lib/sidebars/index.d.ts +2 -5
  21. package/lib/sidebars/index.js +35 -20
  22. package/lib/sidebars/normalization.d.ts +2 -3
  23. package/lib/sidebars/normalization.js +17 -39
  24. package/lib/sidebars/postProcessor.d.ts +8 -0
  25. package/lib/sidebars/postProcessor.js +72 -0
  26. package/lib/sidebars/processor.d.ts +2 -13
  27. package/lib/sidebars/processor.js +25 -33
  28. package/lib/sidebars/types.d.ts +44 -10
  29. package/lib/sidebars/utils.js +53 -61
  30. package/lib/sidebars/validation.d.ts +2 -3
  31. package/lib/sidebars/validation.js +25 -30
  32. package/lib/slug.js +6 -8
  33. package/lib/tags.js +3 -2
  34. package/lib/translations.js +18 -17
  35. package/lib/types.d.ts +3 -6
  36. package/lib/versions.d.ts +25 -1
  37. package/lib/versions.js +25 -29
  38. package/package.json +19 -18
  39. package/src/cli.ts +25 -16
  40. package/src/client/docsClientUtils.ts +2 -2
  41. package/src/client/index.ts +97 -1
  42. package/src/docFrontMatter.ts +1 -0
  43. package/src/docs.ts +25 -20
  44. package/src/globalData.ts +2 -2
  45. package/src/index.ts +17 -7
  46. package/src/lastUpdate.ts +12 -33
  47. package/src/numberPrefix.ts +7 -6
  48. package/src/options.ts +5 -2
  49. package/src/plugin-content-docs.d.ts +16 -60
  50. package/src/props.ts +17 -12
  51. package/src/routes.ts +6 -4
  52. package/src/server-export.ts +24 -0
  53. package/src/sidebars/README.md +9 -0
  54. package/src/sidebars/generator.ts +50 -94
  55. package/src/sidebars/index.ts +50 -32
  56. package/src/sidebars/normalization.ts +22 -50
  57. package/src/sidebars/postProcessor.ts +94 -0
  58. package/src/sidebars/processor.ts +37 -66
  59. package/src/sidebars/types.ts +68 -10
  60. package/src/sidebars/utils.ts +63 -68
  61. package/src/sidebars/validation.ts +53 -53
  62. package/src/slug.ts +9 -10
  63. package/src/tags.ts +2 -2
  64. package/src/translations.ts +19 -16
  65. package/src/types.ts +3 -10
  66. package/src/versions.ts +30 -34
  67. package/lib/client/globalDataHooks.d.ts +0 -19
  68. package/lib/client/globalDataHooks.js +0 -76
  69. package/src/client/globalDataHooks.ts +0 -107
package/src/lastUpdate.ts CHANGED
@@ -5,13 +5,11 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
 
8
- import shell from 'shelljs';
9
8
  import logger from '@docusaurus/logger';
9
+ import {getFileCommitDate, GitNotFoundError} from '@docusaurus/utils';
10
10
 
11
11
  type FileLastUpdateData = {timestamp?: number; author?: string};
12
12
 
13
- const GIT_COMMIT_TIMESTAMP_AUTHOR_REGEX = /^(\d+),(.+)$/;
14
-
15
13
  let showedGitRequirementError = false;
16
14
 
17
15
  export async function getFileLastUpdate(
@@ -20,41 +18,22 @@ export async function getFileLastUpdate(
20
18
  if (!filePath) {
21
19
  return null;
22
20
  }
23
- function getTimestampAndAuthor(str: string): FileLastUpdateData | null {
24
- if (!str) {
25
- return null;
26
- }
27
-
28
- const temp = str.match(GIT_COMMIT_TIMESTAMP_AUTHOR_REGEX);
29
- return !temp || temp.length < 3
30
- ? null
31
- : {timestamp: +temp[1], author: temp[2]};
32
- }
33
21
 
34
22
  // Wrap in try/catch in case the shell commands fail
35
23
  // (e.g. project doesn't use Git, etc).
36
24
  try {
37
- if (!shell.which('git')) {
38
- if (!showedGitRequirementError) {
39
- showedGitRequirementError = true;
40
- logger.warn('Sorry, the docs plugin last update options require Git.');
41
- }
42
-
43
- return null;
44
- }
45
-
46
- const result = shell.exec(`git log -1 --format=%ct,%an "${filePath}"`, {
47
- silent: true,
25
+ const result = getFileCommitDate(filePath, {
26
+ age: 'newest',
27
+ includeAuthor: true,
48
28
  });
49
- if (result.code !== 0) {
50
- throw new Error(
51
- `Retrieval of git history failed at "${filePath}" with exit code ${result.code}: ${result.stderr}`,
52
- );
29
+ return {timestamp: result.timestamp, author: result.author};
30
+ } catch (err) {
31
+ if (err instanceof GitNotFoundError && !showedGitRequirementError) {
32
+ logger.warn('Sorry, the docs plugin last update options require Git.');
33
+ showedGitRequirementError = true;
34
+ } else {
35
+ logger.error(err);
53
36
  }
54
- return getTimestampAndAuthor(result.stdout.trim());
55
- } catch (e) {
56
- logger.error(e);
37
+ return null;
57
38
  }
58
-
59
- return null;
60
39
  }
@@ -8,16 +8,17 @@
8
8
  import type {NumberPrefixParser} from '@docusaurus/plugin-content-docs';
9
9
 
10
10
  // Best-effort to avoid parsing some patterns as number prefix
11
- const IgnoredPrefixPatterns = (function () {
11
+ const IgnoredPrefixPatterns = (() => {
12
12
  // ignore common date-like patterns: https://github.com/facebook/docusaurus/issues/4640
13
13
  const DateLikePrefixRegex =
14
- /^((\d{2}|\d{4})[-_.]\d{2}([-_.](\d{2}|\d{4}))?)(.*)$/;
14
+ /^(?:\d{2}|\d{4})[-_.]\d{2}(?:[-_.](?:\d{2}|\d{4}))?.*$/;
15
15
 
16
16
  // ignore common versioning patterns: https://github.com/facebook/docusaurus/issues/4653
17
- // note: we could try to parse float numbers in filenames but that is probably not worth it
18
- // as a version such as "8.0" can be interpreted as both a version and a float
19
- // User can configure his own NumberPrefixParser if he wants 8.0 to be interpreted as a float
20
- const VersionLikePrefixRegex = /^(\d+[-_.]\d+)(.*)$/;
17
+ // note: we could try to parse float numbers in filenames but that is
18
+ // probably not worth it as a version such as "8.0" can be interpreted as both
19
+ // a version and a float. User can configure her own NumberPrefixParser if
20
+ // she wants 8.0 to be interpreted as a float
21
+ const VersionLikePrefixRegex = /^\d+[-_.]\d+.*$/;
21
22
 
22
23
  return new RegExp(
23
24
  `${DateLikePrefixRegex.source}|${VersionLikePrefixRegex.source}`,
package/src/options.ts CHANGED
@@ -55,6 +55,7 @@ export const DEFAULT_OPTIONS: Omit<PluginOptions, 'id' | 'sidebarPath'> = {
55
55
  editLocalizedFiles: false,
56
56
  sidebarCollapsible: true,
57
57
  sidebarCollapsed: true,
58
+ breadcrumbs: true,
58
59
  };
59
60
 
60
61
  const VersionOptionsSchema = Joi.object({
@@ -139,6 +140,7 @@ export const OptionsSchema = Joi.object({
139
140
  disableVersioning: Joi.bool().default(DEFAULT_OPTIONS.disableVersioning),
140
141
  lastVersion: Joi.string().optional(),
141
142
  versions: VersionsOptionsSchema,
143
+ breadcrumbs: Joi.bool().default(DEFAULT_OPTIONS.breadcrumbs),
142
144
  });
143
145
 
144
146
  export function validateOptions({
@@ -148,8 +150,9 @@ export function validateOptions({
148
150
  let options = userOptions;
149
151
 
150
152
  if (options.sidebarCollapsible === false) {
151
- // When sidebarCollapsible=false and sidebarCollapsed=undefined, we don't want to have the inconsistency warning
152
- // We let options.sidebarCollapsible become the default value for options.sidebarCollapsed
153
+ // When sidebarCollapsible=false and sidebarCollapsed=undefined, we don't
154
+ // want to have the inconsistency warning. We let options.sidebarCollapsible
155
+ // become the default value for options.sidebarCollapsed
153
156
  if (typeof options.sidebarCollapsed === 'undefined') {
154
157
  options = {
155
158
  ...options,
@@ -8,6 +8,10 @@
8
8
  declare module '@docusaurus/plugin-content-docs' {
9
9
  import type {RemarkAndRehypePluginOptions} from '@docusaurus/mdx-loader';
10
10
 
11
+ export interface Assets {
12
+ image?: string;
13
+ }
14
+
11
15
  export type NumberPrefixParser = (filename: string) => {
12
16
  filename: string;
13
17
  numberPrefix?: number;
@@ -38,6 +42,7 @@ declare module '@docusaurus/plugin-content-docs' {
38
42
  showLastUpdateTime?: boolean;
39
43
  showLastUpdateAuthor?: boolean;
40
44
  numberPrefixParser: NumberPrefixParser;
45
+ breadcrumbs: boolean;
41
46
  };
42
47
 
43
48
  export type PathOptions = {
@@ -45,7 +50,8 @@ declare module '@docusaurus/plugin-content-docs' {
45
50
  sidebarPath?: string | false | undefined;
46
51
  };
47
52
 
48
- // TODO support custom version banner? {type: "error", content: "html content"}
53
+ // TODO support custom version banner?
54
+ // {type: "error", content: "html content"}
49
55
  export type VersionBanner = 'unreleased' | 'unmaintained';
50
56
  export type VersionOptions = {
51
57
  path?: string;
@@ -125,6 +131,8 @@ declare module '@docusaurus/plugin-content-docs' {
125
131
  export type PropSidebarItemCategory =
126
132
  import('./sidebars/types').PropSidebarItemCategory;
127
133
  export type PropSidebarItem = import('./sidebars/types').PropSidebarItem;
134
+ export type PropSidebarBreadcrumbsItem =
135
+ import('./sidebars/types').PropSidebarBreadcrumbsItem;
128
136
  export type PropSidebar = import('./sidebars/types').PropSidebar;
129
137
  export type PropSidebars = import('./sidebars/types').PropSidebars;
130
138
 
@@ -155,6 +163,7 @@ declare module '@theme/DocItem' {
155
163
  import type {
156
164
  PropNavigationLink,
157
165
  PropVersionMetadata,
166
+ Assets,
158
167
  } from '@docusaurus/plugin-content-docs';
159
168
 
160
169
  export type DocumentRoute = {
@@ -200,32 +209,12 @@ declare module '@theme/DocItem' {
200
209
  readonly metadata: Metadata;
201
210
  readonly toc: readonly TOCItem[];
202
211
  readonly contentTitle: string | undefined;
212
+ readonly assets: Assets;
203
213
  (): JSX.Element;
204
214
  };
205
215
  }
206
216
 
207
- const DocItem: (props: Props) => JSX.Element;
208
- export default DocItem;
209
- }
210
-
211
- declare module '@theme/DocCard' {
212
- import type {PropSidebarItem} from '@docusaurus/plugin-content-docs';
213
-
214
- export interface Props {
215
- readonly item: PropSidebarItem;
216
- }
217
-
218
- export default function DocCard(props: Props): JSX.Element;
219
- }
220
-
221
- declare module '@theme/DocCardList' {
222
- import type {PropSidebarItem} from '@docusaurus/plugin-content-docs';
223
-
224
- export interface Props {
225
- readonly items: PropSidebarItem[];
226
- }
227
-
228
- export default function DocCardList(props: Props): JSX.Element;
217
+ export default function DocItem(props: Props): JSX.Element;
229
218
  }
230
219
 
231
220
  declare module '@theme/DocCategoryGeneratedIndexPage' {
@@ -240,12 +229,6 @@ declare module '@theme/DocCategoryGeneratedIndexPage' {
240
229
  ): JSX.Element;
241
230
  }
242
231
 
243
- declare module '@theme/DocItemFooter' {
244
- import type {Props} from '@theme/DocItem';
245
-
246
- export default function DocItemFooter(props: Props): JSX.Element;
247
- }
248
-
249
232
  declare module '@theme/DocTagsListPage' {
250
233
  import type {PropTagsListPage} from '@docusaurus/plugin-content-docs';
251
234
 
@@ -262,20 +245,8 @@ declare module '@theme/DocTagDocListPage' {
262
245
  export default function DocTagDocListPage(props: Props): JSX.Element;
263
246
  }
264
247
 
265
- declare module '@theme/DocVersionBanner' {
266
- export interface Props {
267
- readonly className?: string;
268
- }
269
-
270
- export default function DocVersionBanner(props: Props): JSX.Element;
271
- }
272
-
273
- declare module '@theme/DocVersionBadge' {
274
- export interface Props {
275
- readonly className?: string;
276
- }
277
-
278
- export default function DocVersionBadge(props: Props): JSX.Element;
248
+ declare module '@theme/DocBreadcrumbs' {
249
+ export default function DocBreadcrumbs(): JSX.Element;
279
250
  }
280
251
 
281
252
  declare module '@theme/DocPage' {
@@ -292,23 +263,7 @@ declare module '@theme/DocPage' {
292
263
  };
293
264
  }
294
265
 
295
- const DocPage: (props: Props) => JSX.Element;
296
- export default DocPage;
297
- }
298
-
299
- declare module '@theme/Seo' {
300
- import type {ReactNode} from 'react';
301
-
302
- export interface Props {
303
- readonly title?: string;
304
- readonly description?: string;
305
- readonly keywords?: readonly string[] | string;
306
- readonly image?: string;
307
- readonly children?: ReactNode;
308
- }
309
-
310
- const Seo: (props: Props) => JSX.Element;
311
- export default Seo;
266
+ export default function DocPage(props: Props): JSX.Element;
312
267
  }
313
268
 
314
269
  // TODO until TS supports exports field... hope it's in 4.6
@@ -350,6 +305,7 @@ declare module '@docusaurus/plugin-content-docs/client' {
350
305
  export type GlobalPluginData = {
351
306
  path: string;
352
307
  versions: GlobalVersion[];
308
+ breadcrumbs: boolean;
353
309
  };
354
310
  export type DocVersionSuggestions = {
355
311
  // suggest the latest version
package/src/props.ts CHANGED
@@ -22,7 +22,7 @@ import type {
22
22
  PropTagDocListDoc,
23
23
  PropSidebarItemLink,
24
24
  } from '@docusaurus/plugin-content-docs';
25
- import {compact, keyBy, mapValues} from 'lodash';
25
+ import _ from 'lodash';
26
26
  import {createDocsByIdIndex} from './docs';
27
27
 
28
28
  export function toSidebarsProp(loadedVersion: LoadedVersion): PropSidebars {
@@ -52,7 +52,8 @@ Available document ids are:
52
52
  label: sidebarLabel || item.label || title,
53
53
  href: permalink,
54
54
  className: item.className,
55
- customProps: item.customProps,
55
+ customProps:
56
+ item.customProps ?? docMetadata.frontMatter.sidebar_custom_props,
56
57
  docId: docMetadata.unversionedId,
57
58
  };
58
59
  };
@@ -92,18 +93,22 @@ Available document ids are:
92
93
  // Transform the sidebar so that all sidebar item will be in the
93
94
  // form of 'link' or 'category' only.
94
95
  // This is what will be passed as props to the UI component.
95
- return mapValues(loadedVersion.sidebars, (items) => items.map(normalizeItem));
96
+ return _.mapValues(loadedVersion.sidebars, (items) =>
97
+ items.map(normalizeItem),
98
+ );
96
99
  }
97
100
 
98
101
  function toVersionDocsProp(loadedVersion: LoadedVersion): PropVersionDocs {
99
- return mapValues(
100
- keyBy(loadedVersion.docs, (doc) => doc.unversionedId),
101
- (doc) => ({
102
- id: doc.unversionedId,
103
- title: doc.title,
104
- description: doc.description,
105
- sidebar: doc.sidebar,
106
- }),
102
+ return Object.fromEntries(
103
+ loadedVersion.docs.map((doc) => [
104
+ doc.unversionedId,
105
+ {
106
+ id: doc.unversionedId,
107
+ title: doc.title,
108
+ description: doc.description,
109
+ sidebar: doc.sidebar,
110
+ },
111
+ ]),
107
112
  );
108
113
  }
109
114
 
@@ -134,7 +139,7 @@ export function toTagDocListProp({
134
139
  docs: Pick<DocMetadata, 'id' | 'title' | 'description' | 'permalink'>[];
135
140
  }): PropTagDocList {
136
141
  function toDocListProp(): PropTagDocListDoc[] {
137
- const list = compact(
142
+ const list = _.compact(
138
143
  tag.docIds.map((id) => docs.find((doc) => doc.id === id)),
139
144
  );
140
145
  // Sort docs by title
package/src/routes.ts CHANGED
@@ -73,7 +73,8 @@ export async function createCategoryGeneratedIndexRoutes({
73
73
  modules: {
74
74
  categoryGeneratedIndex: aliasedSource(propData),
75
75
  },
76
- // Same as doc, this sidebar route attribute permits to associate this subpage to the given sidebar
76
+ // Same as doc, this sidebar route attribute permits to associate this
77
+ // subpage to the given sidebar
77
78
  ...(sidebar && {sidebar}),
78
79
  };
79
80
  }
@@ -109,7 +110,8 @@ export async function createDocRoutes({
109
110
  content: metadataItem.source,
110
111
  },
111
112
  // Because the parent (DocPage) comp need to access it easily
112
- // This permits to render the sidebar once without unmount/remount when navigating (and preserve sidebar state)
113
+ // This permits to render the sidebar once without unmount/remount when
114
+ // navigating (and preserve sidebar state)
113
115
  ...(metadataItem.sidebar && {
114
116
  sidebar: metadataItem.sidebar,
115
117
  }),
@@ -176,8 +178,8 @@ export async function createVersionRoutes({
176
178
 
177
179
  try {
178
180
  return await doCreateVersionRoutes(loadedVersion);
179
- } catch (e) {
181
+ } catch (err) {
180
182
  logger.error`Can't create version routes for version name=${loadedVersion.versionName}`;
181
- throw e;
183
+ throw err;
182
184
  }
183
185
  }
@@ -0,0 +1,24 @@
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
+ // APIs available to Node.js
9
+ export {
10
+ CURRENT_VERSION_NAME,
11
+ VERSIONED_DOCS_DIR,
12
+ VERSIONED_SIDEBARS_DIR,
13
+ VERSIONS_JSON_FILE,
14
+ } from './constants';
15
+
16
+ export {
17
+ filterVersions,
18
+ getDefaultVersionBanner,
19
+ getVersionBadge,
20
+ getVersionBanner,
21
+ getVersionsFilePath,
22
+ readVersionsFile,
23
+ readVersionNames,
24
+ } from './versions';
@@ -0,0 +1,9 @@
1
+ # Sidebars
2
+
3
+ This part is very complicated and hard to navigate. Sidebars are loaded through the following steps:
4
+
5
+ 1. **Loading**. The sidebars file is read. Returns `SidebarsConfig`.
6
+ 2. **Normalization**. The shorthands are expanded. This step is very lenient about the sidebars' shapes. Returns `NormalizedSidebars`.
7
+ 3. **Validation**. The normalized sidebars are validated. This step happens after normalization, because the normalized sidebars are easier to validate, and allows us to repeatedly validate & generate in the future.
8
+ 4. **Generation**. This step is done through the "processor" (naming is hard). The `autogenerated` items are unwrapped. In the future, steps 3 and 4 may be repeatedly done until all autogenerated items are unwrapped. Returns `ProcessedSidebars`.
9
+ 5. **Post-processing**. Defaults are applied (collapsed states), category links are resolved, empty categories are flattened. Returns `Sidebars`.
@@ -6,25 +6,17 @@
6
6
  */
7
7
 
8
8
  import type {
9
- SidebarItem,
10
9
  SidebarItemDoc,
11
- SidebarItemCategory,
12
10
  SidebarItemsGenerator,
13
11
  SidebarItemsGeneratorDoc,
14
- SidebarItemCategoryLink,
12
+ NormalizedSidebarItemCategory,
13
+ NormalizedSidebarItem,
15
14
  SidebarItemCategoryLinkConfig,
16
15
  } from './types';
17
- import {sortBy, last} from 'lodash';
18
- import {
19
- addTrailingSlash,
20
- posixPath,
21
- findAsyncSequential,
22
- } from '@docusaurus/utils';
16
+ import _ from 'lodash';
17
+ import {addTrailingSlash, posixPath} from '@docusaurus/utils';
23
18
  import logger from '@docusaurus/logger';
24
19
  import path from 'path';
25
- import fs from 'fs-extra';
26
- import Yaml from 'js-yaml';
27
- import {validateCategoryMetadataFile} from './validation';
28
20
  import {createDocsByIdIndex, toCategoryIndexMatcherParam} from '../docs';
29
21
 
30
22
  const BreadcrumbSeparator = '/';
@@ -33,72 +25,31 @@ const docIdPrefix = '$doc$/';
33
25
 
34
26
  // Just an alias to the make code more explicit
35
27
  function getLocalDocId(docId: string): string {
36
- return last(docId.split('/'))!;
28
+ return _.last(docId.split('/'))!;
37
29
  }
38
30
 
39
31
  export const CategoryMetadataFilenameBase = '_category_';
40
32
  export const CategoryMetadataFilenamePattern = '_category_.{json,yml,yaml}';
41
33
 
42
- export type CategoryMetadataFile = {
43
- label?: string;
44
- position?: number;
45
- collapsed?: boolean;
46
- collapsible?: boolean;
47
- className?: string;
48
- link?: SidebarItemCategoryLinkConfig;
49
-
50
- // TODO should we allow "items" here? how would this work? would an "autogenerated" type be allowed?
51
- // This mkdocs plugin do something like that: https://github.com/lukasgeiter/mkdocs-awesome-pages-plugin/
52
- // cf comment: https://github.com/facebook/docusaurus/issues/3464#issuecomment-784765199
53
- };
54
-
55
34
  type WithPosition<T> = T & {position?: number};
56
35
 
57
36
  /**
58
37
  * A representation of the fs structure. For each object entry:
59
- * If it's a folder, the key is the directory name, and value is the directory content;
60
- * If it's a doc file, the key is the doc id prefixed with '$doc$/', and value is null
38
+ * If it's a folder, the key is the directory name, and value is the directory
39
+ * content; If it's a doc file, the key is the doc id prefixed with '$doc$/',
40
+ * and value is null
61
41
  */
62
42
  type Dir = {
63
43
  [item: string]: Dir | null;
64
44
  };
65
45
 
66
- // TODO I now believe we should read all the category metadata files ahead of time: we may need this metadata to customize docs metadata
67
- // 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...
68
- // TODO later if there is `CategoryFolder/with-category-name-doc.md`, we may want to read the metadata as yaml on it
69
- // see https://github.com/facebook/docusaurus/issues/3464#issuecomment-818670449
70
- async function readCategoryMetadataFile(
71
- categoryDirPath: string,
72
- ): Promise<CategoryMetadataFile | null> {
73
- async function tryReadFile(filePath: string): Promise<CategoryMetadataFile> {
74
- const contentString = await fs.readFile(filePath, {encoding: 'utf8'});
75
- const unsafeContent = Yaml.load(contentString);
76
- try {
77
- return validateCategoryMetadataFile(unsafeContent);
78
- } catch (e) {
79
- logger.error`The docs sidebar category metadata file path=${filePath} looks invalid!`;
80
- throw e;
81
- }
82
- }
83
- const filePath = await findAsyncSequential(
84
- ['.json', '.yml', '.yaml'].map((ext) =>
85
- posixPath(
86
- path.join(categoryDirPath, `${CategoryMetadataFilenameBase}${ext}`),
87
- ),
88
- ),
89
- fs.pathExists,
90
- );
91
- return filePath ? tryReadFile(filePath) : null;
92
- }
93
-
94
46
  // Comment for this feature: https://github.com/facebook/docusaurus/issues/3464#issuecomment-818670449
95
47
  export const DefaultSidebarItemsGenerator: SidebarItemsGenerator = async ({
96
48
  numberPrefixParser,
97
49
  isCategoryIndex,
98
50
  docs: allDocs,
99
- options,
100
51
  item: {dirName: autogenDir},
101
- version,
52
+ categoriesMetadata,
102
53
  }) => {
103
54
  const docsById = createDocsByIdIndex(allDocs);
104
55
  const findDoc = (docId: string): SidebarItemsGeneratorDoc | undefined =>
@@ -142,7 +93,8 @@ export const DefaultSidebarItemsGenerator: SidebarItemsGenerator = async ({
142
93
  * Step 2. Turn the linear file list into a tree structure.
143
94
  */
144
95
  function treeify(docs: SidebarItemsGeneratorDoc[]): Dir {
145
- // Get the category breadcrumb of a doc (relative to the dir of the autogenerated sidebar item)
96
+ // Get the category breadcrumb of a doc (relative to the dir of the
97
+ // autogenerated sidebar item)
146
98
  // autogenDir=a/b and docDir=a/b/c/d => returns [c, d]
147
99
  // autogenDir=a/b and docDir=a/b => returns []
148
100
  // TODO: try to use path.relative()
@@ -169,10 +121,12 @@ export const DefaultSidebarItemsGenerator: SidebarItemsGenerator = async ({
169
121
  }
170
122
 
171
123
  /**
172
- * Step 3. Recursively transform the tree-like file structure to sidebar items.
124
+ * Step 3. Recursively transform the tree-like structure to sidebar items.
173
125
  * (From a record to an array of items, akin to normalizing shorthand)
174
126
  */
175
- function generateSidebar(fsModel: Dir): Promise<WithPosition<SidebarItem>[]> {
127
+ function generateSidebar(
128
+ fsModel: Dir,
129
+ ): Promise<WithPosition<NormalizedSidebarItem>[]> {
176
130
  function createDocItem(id: string): WithPosition<SidebarItemDoc> {
177
131
  const {
178
132
  sidebarPosition: position,
@@ -182,7 +136,8 @@ export const DefaultSidebarItemsGenerator: SidebarItemsGenerator = async ({
182
136
  type: 'doc',
183
137
  id,
184
138
  position,
185
- // We don't want these fields to magically appear in the generated sidebar
139
+ // We don't want these fields to magically appear in the generated
140
+ // sidebar
186
141
  ...(label !== undefined && {label}),
187
142
  ...(className !== undefined && {className}),
188
143
  };
@@ -191,9 +146,9 @@ export const DefaultSidebarItemsGenerator: SidebarItemsGenerator = async ({
191
146
  dir: Dir,
192
147
  fullPath: string,
193
148
  folderName: string,
194
- ): Promise<WithPosition<SidebarItemCategory>> {
195
- const categoryPath = path.join(version.contentPath, autogenDir, fullPath);
196
- const categoryMetadata = await readCategoryMetadataFile(categoryPath);
149
+ ): Promise<WithPosition<NormalizedSidebarItemCategory>> {
150
+ const categoryMetadata =
151
+ categoriesMetadata[posixPath(path.join(autogenDir, fullPath))];
197
152
  const className = categoryMetadata?.className;
198
153
  const {filename, numberPrefix} = numberPrefixParser(folderName);
199
154
  const allItems = await Promise.all(
@@ -206,44 +161,44 @@ export const DefaultSidebarItemsGenerator: SidebarItemsGenerator = async ({
206
161
  // using the "local id" (myDoc) or "qualified id" (dirName/myDoc)
207
162
  function findDocByLocalId(localId: string): SidebarItemDoc | undefined {
208
163
  return allItems.find(
209
- (item) => item.type === 'doc' && getLocalDocId(item.id) === localId,
210
- ) as SidebarItemDoc | undefined;
164
+ (item): item is SidebarItemDoc =>
165
+ item.type === 'doc' && getLocalDocId(item.id) === localId,
166
+ );
211
167
  }
212
168
 
213
169
  function findConventionalCategoryDocLink(): SidebarItemDoc | undefined {
214
- return allItems.find((item) => {
170
+ return allItems.find((item): item is SidebarItemDoc => {
215
171
  if (item.type !== 'doc') {
216
172
  return false;
217
173
  }
218
174
  const doc = getDoc(item.id);
219
175
  return isCategoryIndex(toCategoryIndexMatcherParam(doc));
220
- }) as SidebarItemDoc | undefined;
176
+ });
221
177
  }
222
178
 
223
179
  function getCategoryLinkedDocId(): string | undefined {
224
180
  const link = categoryMetadata?.link;
225
- if (link) {
226
- if (link.type === 'doc') {
181
+ if (link !== undefined) {
182
+ if (link && link.type === 'doc') {
227
183
  return findDocByLocalId(link.id)?.id || getDoc(link.id).id;
228
- } else {
229
- // We don't continue for other link types on purpose!
230
- // IE if user decide to use type "generated-index", we should not pick a README.md file as the linked doc
231
- return undefined;
232
184
  }
185
+ // If a link is explicitly specified, we won't apply conventions
186
+ return undefined;
233
187
  }
234
- // Apply default convention to pick index.md, README.md or <categoryName>.md as the category doc
188
+ // Apply default convention to pick index.md, README.md or
189
+ // <categoryName>.md as the category doc
235
190
  return findConventionalCategoryDocLink()?.id;
236
191
  }
237
192
 
238
193
  const categoryLinkedDocId = getCategoryLinkedDocId();
239
194
 
240
- const link: SidebarItemCategoryLink | undefined = categoryLinkedDocId
241
- ? {
242
- type: 'doc',
243
- id: categoryLinkedDocId, // We "remap" a potentially "local id" to a "qualified id"
244
- }
245
- : // TODO typing issue
246
- (categoryMetadata?.link as SidebarItemCategoryLink | undefined);
195
+ const link: SidebarItemCategoryLinkConfig | null | undefined =
196
+ categoryLinkedDocId
197
+ ? {
198
+ type: 'doc',
199
+ id: categoryLinkedDocId, // We "remap" a potentially "local id" to a "qualified id"
200
+ }
201
+ : categoryMetadata?.link;
247
202
 
248
203
  // If a doc is linked, remove it from the category subItems
249
204
  const items = allItems.filter(
@@ -253,9 +208,8 @@ export const DefaultSidebarItemsGenerator: SidebarItemsGenerator = async ({
253
208
  return {
254
209
  type: 'category',
255
210
  label: categoryMetadata?.label ?? filename,
256
- collapsible:
257
- categoryMetadata?.collapsible ?? options.sidebarCollapsible,
258
- collapsed: categoryMetadata?.collapsed ?? options.sidebarCollapsed,
211
+ collapsible: categoryMetadata?.collapsible,
212
+ collapsed: categoryMetadata?.collapsed,
259
213
  position: categoryMetadata?.position ?? numberPrefix,
260
214
  ...(className !== undefined && {className}),
261
215
  items,
@@ -266,7 +220,7 @@ export const DefaultSidebarItemsGenerator: SidebarItemsGenerator = async ({
266
220
  dir: Dir | null, // The directory item to be transformed.
267
221
  itemKey: string, // For docs, it's the doc ID; for categories, it's used to generate the next `relativePath`.
268
222
  fullPath: string, // `dir`'s full path relative to the autogen dir.
269
- ): Promise<WithPosition<SidebarItem>> {
223
+ ): Promise<WithPosition<NormalizedSidebarItem>> {
270
224
  return dir
271
225
  ? createCategoryItem(dir, fullPath, itemKey)
272
226
  : createDocItem(itemKey.substring(docIdPrefix.length));
@@ -279,26 +233,28 @@ export const DefaultSidebarItemsGenerator: SidebarItemsGenerator = async ({
279
233
  }
280
234
 
281
235
  /**
282
- * Step 4. Recursively sort the categories/docs + remove the "position" attribute from final output.
283
- * Note: the "position" is only used to sort "inside" a sidebar slice. It is not
284
- * used to sort across multiple consecutive sidebar slices (ie a whole Category
285
- * composed of multiple autogenerated items)
236
+ * Step 4. Recursively sort the categories/docs + remove the "position"
237
+ * attribute from final output. Note: the "position" is only used to sort
238
+ * "inside" a sidebar slice. It is not used to sort across multiple
239
+ * consecutive sidebar slices (i.e. a whole category composed of multiple
240
+ * autogenerated items)
286
241
  */
287
- function sortItems(sidebarItems: WithPosition<SidebarItem>[]): SidebarItem[] {
242
+ function sortItems(
243
+ sidebarItems: WithPosition<NormalizedSidebarItem>[],
244
+ ): NormalizedSidebarItem[] {
288
245
  const processedSidebarItems = sidebarItems.map((item) => {
289
246
  if (item.type === 'category') {
290
247
  return {...item, items: sortItems(item.items)};
291
248
  }
292
249
  return item;
293
250
  });
294
- const sortedSidebarItems = sortBy(
251
+ const sortedSidebarItems = _.sortBy(
295
252
  processedSidebarItems,
296
253
  (item) => item.position,
297
254
  );
298
255
  return sortedSidebarItems.map(({position, ...item}) => item);
299
256
  }
300
257
  // TODO: the whole code is designed for pipeline operator
301
- // return getAutogenDocs() |> treeify |> await generateSidebar(^) |> sortItems;
302
258
  const docs = getAutogenDocs();
303
259
  const fsModel = treeify(docs);
304
260
  const sidebarWithPosition = await generateSidebar(fsModel);