@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.
- package/lib/cli.d.ts +1 -1
- package/lib/cli.js +18 -14
- package/lib/client/docsClientUtils.js +2 -2
- package/lib/client/index.d.ts +13 -1
- package/lib/client/index.js +66 -1
- package/lib/docFrontMatter.js +1 -0
- package/lib/docs.d.ts +2 -1
- package/lib/docs.js +23 -22
- package/lib/globalData.js +3 -2
- package/lib/index.js +11 -6
- package/lib/lastUpdate.js +14 -27
- package/lib/numberPrefix.js +7 -6
- package/lib/options.js +5 -2
- package/lib/props.js +15 -10
- package/lib/routes.js +6 -4
- package/lib/server-export.d.ts +8 -0
- package/lib/server-export.js +23 -0
- package/lib/sidebars/generator.d.ts +1 -9
- package/lib/sidebars/generator.js +26 -50
- package/lib/sidebars/index.d.ts +2 -5
- package/lib/sidebars/index.js +35 -20
- package/lib/sidebars/normalization.d.ts +2 -3
- package/lib/sidebars/normalization.js +17 -39
- package/lib/sidebars/postProcessor.d.ts +8 -0
- package/lib/sidebars/postProcessor.js +72 -0
- package/lib/sidebars/processor.d.ts +2 -13
- package/lib/sidebars/processor.js +25 -33
- package/lib/sidebars/types.d.ts +44 -10
- package/lib/sidebars/utils.js +53 -61
- package/lib/sidebars/validation.d.ts +2 -3
- package/lib/sidebars/validation.js +25 -30
- package/lib/slug.js +6 -8
- package/lib/tags.js +3 -2
- package/lib/translations.js +18 -17
- package/lib/types.d.ts +3 -6
- package/lib/versions.d.ts +25 -1
- package/lib/versions.js +25 -29
- package/package.json +19 -18
- package/src/cli.ts +25 -16
- package/src/client/docsClientUtils.ts +2 -2
- package/src/client/index.ts +97 -1
- package/src/docFrontMatter.ts +1 -0
- package/src/docs.ts +25 -20
- package/src/globalData.ts +2 -2
- package/src/index.ts +17 -7
- package/src/lastUpdate.ts +12 -33
- package/src/numberPrefix.ts +7 -6
- package/src/options.ts +5 -2
- package/src/plugin-content-docs.d.ts +16 -60
- package/src/props.ts +17 -12
- package/src/routes.ts +6 -4
- package/src/server-export.ts +24 -0
- package/src/sidebars/README.md +9 -0
- package/src/sidebars/generator.ts +50 -94
- package/src/sidebars/index.ts +50 -32
- package/src/sidebars/normalization.ts +22 -50
- package/src/sidebars/postProcessor.ts +94 -0
- package/src/sidebars/processor.ts +37 -66
- package/src/sidebars/types.ts +68 -10
- package/src/sidebars/utils.ts +63 -68
- package/src/sidebars/validation.ts +53 -53
- package/src/slug.ts +9 -10
- package/src/tags.ts +2 -2
- package/src/translations.ts +19 -16
- package/src/types.ts +3 -10
- package/src/versions.ts +30 -34
- package/lib/client/globalDataHooks.d.ts +0 -19
- package/lib/client/globalDataHooks.js +0 -76
- 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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
|
55
|
-
} catch (e) {
|
|
56
|
-
logger.error(e);
|
|
37
|
+
return null;
|
|
57
38
|
}
|
|
58
|
-
|
|
59
|
-
return null;
|
|
60
39
|
}
|
package/src/numberPrefix.ts
CHANGED
|
@@ -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 = (
|
|
11
|
+
const IgnoredPrefixPatterns = (() => {
|
|
12
12
|
// ignore common date-like patterns: https://github.com/facebook/docusaurus/issues/4640
|
|
13
13
|
const DateLikePrefixRegex =
|
|
14
|
-
/^(
|
|
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
|
|
18
|
-
// as a version such as "8.0" can be interpreted as both
|
|
19
|
-
// User can configure
|
|
20
|
-
|
|
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
|
|
152
|
-
//
|
|
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?
|
|
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
|
-
|
|
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/
|
|
266
|
-
export
|
|
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
|
-
|
|
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
|
|
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:
|
|
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) =>
|
|
96
|
+
return _.mapValues(loadedVersion.sidebars, (items) =>
|
|
97
|
+
items.map(normalizeItem),
|
|
98
|
+
);
|
|
96
99
|
}
|
|
97
100
|
|
|
98
101
|
function toVersionDocsProp(loadedVersion: LoadedVersion): PropVersionDocs {
|
|
99
|
-
return
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
|
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
|
|
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 (
|
|
181
|
+
} catch (err) {
|
|
180
182
|
logger.error`Can't create version routes for version name=${loadedVersion.versionName}`;
|
|
181
|
-
throw
|
|
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
|
-
|
|
12
|
+
NormalizedSidebarItemCategory,
|
|
13
|
+
NormalizedSidebarItem,
|
|
15
14
|
SidebarItemCategoryLinkConfig,
|
|
16
15
|
} from './types';
|
|
17
|
-
import
|
|
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
|
|
60
|
-
* If it's a doc file, the key is the doc id prefixed with '$doc$/',
|
|
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
|
-
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
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<
|
|
195
|
-
const
|
|
196
|
-
|
|
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)
|
|
210
|
-
|
|
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
|
-
})
|
|
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
|
|
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:
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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
|
-
|
|
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<
|
|
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"
|
|
283
|
-
* Note: the "position" is only used to sort
|
|
284
|
-
*
|
|
285
|
-
* composed of multiple
|
|
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(
|
|
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);
|