@docusaurus/plugin-content-docs 2.4.1 → 3.0.0-beta.0

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/src/index.ts CHANGED
@@ -26,23 +26,20 @@ import {
26
26
  processDocMetadata,
27
27
  addDocNavigation,
28
28
  type DocEnv,
29
+ createDocsByIdIndex,
29
30
  } from './docs';
30
- import {readVersionsMetadata} from './versions';
31
+ import {readVersionsMetadata, toFullVersion} from './versions';
31
32
  import {cliDocsVersionCommand} from './cli';
32
33
  import {VERSIONS_JSON_FILE} from './constants';
33
34
  import {toGlobalDataVersion} from './globalData';
34
- import {toTagDocListProp} from './props';
35
- import {getCategoryGeneratedIndexMetadataList} from './categoryGeneratedIndex';
36
35
  import {
37
36
  translateLoadedContent,
38
37
  getLoadedContentTranslationFiles,
39
38
  } from './translations';
40
- import {getVersionTags} from './tags';
41
- import {createVersionRoutes} from './routes';
39
+ import {createAllRoutes} from './routes';
42
40
  import {createSidebarsUtils} from './sidebars/utils';
43
41
 
44
42
  import type {
45
- PropTagsListPage,
46
43
  PluginOptions,
47
44
  DocMetadataBase,
48
45
  VersionMetadata,
@@ -55,7 +52,6 @@ import type {
55
52
  SourceToPermalink,
56
53
  DocFile,
57
54
  DocsMarkdownOption,
58
- VersionTag,
59
55
  FullVersion,
60
56
  } from './types';
61
57
  import type {RuleSetRule} from 'webpack';
@@ -80,6 +76,9 @@ export default async function pluginContentDocs(
80
76
  const aliasedSource = (source: string) =>
81
77
  `~docs/${posixPath(path.relative(pluginDataDirRoot, source))}`;
82
78
 
79
+ // TODO env should be injected into all plugins
80
+ const env = process.env.NODE_ENV as DocEnv;
81
+
83
82
  return {
84
83
  name: 'docusaurus-plugin-content-docs',
85
84
 
@@ -148,7 +147,7 @@ export default async function pluginContentDocs(
148
147
  versionMetadata,
149
148
  context,
150
149
  options,
151
- env: process.env.NODE_ENV as DocEnv,
150
+ env,
152
151
  });
153
152
  }
154
153
  return Promise.all(docFiles.map(processVersionDoc));
@@ -161,6 +160,9 @@ export default async function pluginContentDocs(
161
160
  versionMetadata,
162
161
  );
163
162
 
163
+ // TODO we only ever need draftIds in further code, not full draft items
164
+ // To simplify and prevent mistakes, avoid exposing draft
165
+ // replace draft=>draftIds in content loaded
164
166
  const [drafts, docs] = _.partition(docsBase, (doc) => doc.draft);
165
167
 
166
168
  const sidebars = await loadSidebars(versionMetadata.sidebarFilePath, {
@@ -178,13 +180,25 @@ export default async function pluginContentDocs(
178
180
 
179
181
  const sidebarsUtils = createSidebarsUtils(sidebars);
180
182
 
183
+ const docsById = createDocsByIdIndex(docs);
184
+ const allDocIds = Object.keys(docsById);
185
+
186
+ sidebarsUtils.checkLegacyVersionedSidebarNames({
187
+ sidebarFilePath: versionMetadata.sidebarFilePath as string,
188
+ versionMetadata,
189
+ });
190
+ sidebarsUtils.checkSidebarsDocIds({
191
+ allDocIds,
192
+ sidebarFilePath: versionMetadata.sidebarFilePath as string,
193
+ versionMetadata,
194
+ });
195
+
181
196
  return {
182
197
  ...versionMetadata,
183
- docs: addDocNavigation(
198
+ docs: addDocNavigation({
184
199
  docs,
185
200
  sidebarsUtils,
186
- versionMetadata.sidebarFilePath as string,
187
- ),
201
+ }),
188
202
  drafts,
189
203
  sidebars,
190
204
  };
@@ -209,107 +223,24 @@ export default async function pluginContentDocs(
209
223
  },
210
224
 
211
225
  async contentLoaded({content, actions}) {
212
- const {loadedVersions} = content;
213
- const {
214
- docLayoutComponent,
215
- docItemComponent,
216
- docCategoryGeneratedIndexComponent,
217
- breadcrumbs,
218
- } = options;
219
- const {addRoute, createData, setGlobalData} = actions;
220
- const versions: FullVersion[] = loadedVersions.map((version) => {
221
- const sidebarsUtils = createSidebarsUtils(version.sidebars);
222
- return {
223
- ...version,
224
- sidebarsUtils,
225
- categoryGeneratedIndices: getCategoryGeneratedIndexMetadataList({
226
- docs: version.docs,
227
- sidebarsUtils,
228
- }),
229
- };
226
+ const versions: FullVersion[] = content.loadedVersions.map(toFullVersion);
227
+
228
+ await createAllRoutes({
229
+ baseUrl,
230
+ versions,
231
+ options,
232
+ actions,
233
+ aliasedSource,
230
234
  });
231
235
 
232
- async function createVersionTagsRoutes(version: FullVersion) {
233
- const versionTags = getVersionTags(version.docs);
234
-
235
- // TODO tags should be a sub route of the version route
236
- async function createTagsListPage() {
237
- const tagsProp: PropTagsListPage['tags'] = Object.values(
238
- versionTags,
239
- ).map((tagValue) => ({
240
- label: tagValue.label,
241
- permalink: tagValue.permalink,
242
- count: tagValue.docIds.length,
243
- }));
244
-
245
- // Only create /tags page if there are tags.
246
- if (tagsProp.length > 0) {
247
- const tagsPropPath = await createData(
248
- `${docuHash(`tags-list-${version.versionName}-prop`)}.json`,
249
- JSON.stringify(tagsProp, null, 2),
250
- );
251
- addRoute({
252
- path: version.tagsPath,
253
- exact: true,
254
- component: options.docTagsListComponent,
255
- modules: {
256
- tags: aliasedSource(tagsPropPath),
257
- },
258
- });
259
- }
260
- }
261
-
262
- // TODO tags should be a sub route of the version route
263
- async function createTagDocListPage(tag: VersionTag) {
264
- const tagProps = toTagDocListProp({
265
- allTagsPath: version.tagsPath,
266
- tag,
267
- docs: version.docs,
268
- });
269
- const tagPropPath = await createData(
270
- `${docuHash(`tag-${tag.permalink}`)}.json`,
271
- JSON.stringify(tagProps, null, 2),
272
- );
273
- addRoute({
274
- path: tag.permalink,
275
- component: options.docTagDocListComponent,
276
- exact: true,
277
- modules: {
278
- tag: aliasedSource(tagPropPath),
279
- },
280
- });
281
- }
282
-
283
- await createTagsListPage();
284
- await Promise.all(Object.values(versionTags).map(createTagDocListPage));
285
- }
286
-
287
- await Promise.all(
288
- versions.map((version) =>
289
- createVersionRoutes({
290
- version,
291
- docItemComponent,
292
- docLayoutComponent,
293
- docCategoryGeneratedIndexComponent,
294
- pluginId,
295
- aliasedSource,
296
- actions,
297
- }),
298
- ),
299
- );
300
-
301
- // TODO tags should be a sub route of the version route
302
- await Promise.all(versions.map(createVersionTagsRoutes));
303
-
304
- setGlobalData({
236
+ actions.setGlobalData({
305
237
  path: normalizeUrl([baseUrl, options.routeBasePath]),
306
238
  versions: versions.map(toGlobalDataVersion),
307
- breadcrumbs,
239
+ breadcrumbs: options.breadcrumbs,
308
240
  });
309
241
  },
310
242
 
311
243
  configureWebpack(_config, isServer, utils, content) {
312
- const {getJSLoader} = utils;
313
244
  const {
314
245
  rehypePlugins,
315
246
  remarkPlugins,
@@ -344,7 +275,6 @@ export default async function pluginContentDocs(
344
275
  test: /\.mdx?$/i,
345
276
  include: contentDirs,
346
277
  use: [
347
- getJSLoader({isServer}),
348
278
  {
349
279
  loader: require.resolve('@docusaurus/mdx-loader'),
350
280
  options: {
package/src/options.ts CHANGED
@@ -11,6 +11,7 @@ import {
11
11
  RemarkPluginsSchema,
12
12
  RehypePluginsSchema,
13
13
  AdmonitionsSchema,
14
+ RouteBasePathSchema,
14
15
  URISchema,
15
16
  } from '@docusaurus/utils-validation';
16
17
  import {GlobExcludeDefault} from '@docusaurus/utils';
@@ -30,7 +31,9 @@ export const DEFAULT_OPTIONS: Omit<PluginOptions, 'id' | 'sidebarPath'> = {
30
31
  exclude: GlobExcludeDefault,
31
32
  sidebarItemsGenerator: DefaultSidebarItemsGenerator,
32
33
  numberPrefixParser: DefaultNumberPrefixParser,
33
- docLayoutComponent: '@theme/DocPage',
34
+ docsRootComponent: '@theme/DocsRoot',
35
+ docVersionRootComponent: '@theme/DocVersionRoot',
36
+ docRootComponent: '@theme/DocRoot',
34
37
  docItemComponent: '@theme/DocItem',
35
38
  docTagDocListComponent: '@theme/DocTagDocListPage',
36
39
  docTagsListComponent: '@theme/DocTagsListPage',
@@ -71,10 +74,7 @@ const OptionsSchema = Joi.object<PluginOptions>({
71
74
  editUrl: Joi.alternatives().try(URISchema, Joi.function()),
72
75
  editCurrentVersion: Joi.boolean().default(DEFAULT_OPTIONS.editCurrentVersion),
73
76
  editLocalizedFiles: Joi.boolean().default(DEFAULT_OPTIONS.editLocalizedFiles),
74
- routeBasePath: Joi.string()
75
- // '' not allowed, see https://github.com/facebook/docusaurus/issues/3374
76
- // .allow('') ""
77
- .default(DEFAULT_OPTIONS.routeBasePath),
77
+ routeBasePath: RouteBasePathSchema.default(DEFAULT_OPTIONS.routeBasePath),
78
78
  tagsBasePath: Joi.string().default(DEFAULT_OPTIONS.tagsBasePath),
79
79
  // @ts-expect-error: deprecated
80
80
  homePageId: Joi.any().forbidden().messages({
@@ -104,7 +104,11 @@ const OptionsSchema = Joi.object<PluginOptions>({
104
104
  }),
105
105
  )
106
106
  .default(() => DEFAULT_OPTIONS.numberPrefixParser),
107
- docLayoutComponent: Joi.string().default(DEFAULT_OPTIONS.docLayoutComponent),
107
+ docsRootComponent: Joi.string().default(DEFAULT_OPTIONS.docsRootComponent),
108
+ docVersionRootComponent: Joi.string().default(
109
+ DEFAULT_OPTIONS.docVersionRootComponent,
110
+ ),
111
+ docRootComponent: Joi.string().default(DEFAULT_OPTIONS.docRootComponent),
108
112
  docItemComponent: Joi.string().default(DEFAULT_OPTIONS.docItemComponent),
109
113
  docTagsListComponent: Joi.string().default(
110
114
  DEFAULT_OPTIONS.docTagsListComponent,
@@ -198,10 +198,24 @@ declare module '@docusaurus/plugin-content-docs' {
198
198
  */
199
199
  exclude: string[];
200
200
  /**
201
- * Root layout component of each doc page. Provides the version data
202
- * context, and is not unmounted when switching docs.
201
+ * Parent component of all the docs plugin pages (including all versions).
202
+ * Stays mounted when navigation between docs pages and versions.
203
203
  */
204
- docLayoutComponent: string;
204
+ docsRootComponent: string;
205
+ /**
206
+ * Parent component of all docs pages of an individual version:
207
+ * - docs pages with sidebars
208
+ * - tags pages
209
+ * Stays mounted when navigation between pages of that specific version.
210
+ */
211
+ docVersionRootComponent: string;
212
+ /**
213
+ * Parent component of all docs pages with sidebars:
214
+ * - regular docs pages
215
+ * - category generated index pages
216
+ * Stays mounted when navigation between such pages.
217
+ */
218
+ docRootComponent: string;
205
219
  /** Main doc container, with TOC, pagination, etc. */
206
220
  docItemComponent: string;
207
221
  /** Root component of the "docs containing tag X" page. */
@@ -384,6 +398,8 @@ declare module '@docusaurus/plugin-content-docs' {
384
398
  pagination_prev?: string | null;
385
399
  /** Should this doc be excluded from production builds? */
386
400
  draft?: boolean;
401
+ /** Should this doc be accessible but hidden in production builds? */
402
+ unlisted?: boolean;
387
403
  /** Allows overriding the last updated author and/or date. */
388
404
  last_update?: FileChange;
389
405
  };
@@ -398,17 +414,11 @@ declare module '@docusaurus/plugin-content-docs' {
398
414
  };
399
415
 
400
416
  export type DocMetadataBase = LastUpdateData & {
401
- // TODO
402
417
  /**
403
- * Legacy versioned ID. Will be refactored in the future to be unversioned.
418
+ * The document id.
419
+ * Multiple documents can have the same id, when in different versions.
404
420
  */
405
421
  id: string;
406
- // TODO
407
- /**
408
- * Unversioned ID. Should be preferred everywhere over `id` until the latter
409
- * is refactored.
410
- */
411
- unversionedId: string;
412
422
  /** The name of the version this doc belongs to. */
413
423
  version: string;
414
424
  /**
@@ -434,6 +444,11 @@ declare module '@docusaurus/plugin-content-docs' {
434
444
  * Draft docs will be excluded for production environment.
435
445
  */
436
446
  draft: boolean;
447
+ /**
448
+ * Unlisted docs are accessible when directly visible, but will be hidden
449
+ * from the sidebar and pagination in production.
450
+ */
451
+ unlisted: boolean;
437
452
  /**
438
453
  * Position in an autogenerated sidebar slice, acquired through front matter
439
454
  * or number prefix.
@@ -610,14 +625,32 @@ declare module '@theme/DocBreadcrumbs' {
610
625
  export default function DocBreadcrumbs(): JSX.Element;
611
626
  }
612
627
 
613
- declare module '@theme/DocPage' {
628
+ declare module '@theme/DocsRoot' {
629
+ import type {RouteConfigComponentProps} from 'react-router-config';
630
+ import type {Required} from 'utility-types';
631
+
632
+ export interface Props extends Required<RouteConfigComponentProps, 'route'> {}
633
+
634
+ export default function DocsRoot(props: Props): JSX.Element;
635
+ }
636
+
637
+ declare module '@theme/DocVersionRoot' {
614
638
  import type {PropVersionMetadata} from '@docusaurus/plugin-content-docs';
615
639
  import type {RouteConfigComponentProps} from 'react-router-config';
616
640
  import type {Required} from 'utility-types';
617
641
 
618
642
  export interface Props extends Required<RouteConfigComponentProps, 'route'> {
619
- readonly versionMetadata: PropVersionMetadata;
643
+ readonly version: PropVersionMetadata;
620
644
  }
621
645
 
622
- export default function DocPage(props: Props): JSX.Element;
646
+ export default function DocVersionRoot(props: Props): JSX.Element;
647
+ }
648
+
649
+ declare module '@theme/DocRoot' {
650
+ import type {RouteConfigComponentProps} from 'react-router-config';
651
+ import type {Required} from 'utility-types';
652
+
653
+ export interface Props extends Required<RouteConfigComponentProps, 'route'> {}
654
+
655
+ export default function DocRoot(props: Props): JSX.Element;
623
656
  }
package/src/props.ts CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  import _ from 'lodash';
9
9
  import {createDocsByIdIndex} from './docs';
10
- import type {VersionTag} from './types';
10
+ import type {VersionTag, VersionTags} from './types';
11
11
  import type {
12
12
  SidebarItemDoc,
13
13
  SidebarItem,
@@ -21,12 +21,44 @@ import type {
21
21
  PropSidebarItemCategory,
22
22
  PropTagDocList,
23
23
  PropTagDocListDoc,
24
+ PropTagsListPage,
24
25
  PropSidebarItemLink,
25
26
  PropVersionDocs,
26
27
  DocMetadata,
27
28
  LoadedVersion,
28
29
  } from '@docusaurus/plugin-content-docs';
29
30
 
31
+ export function toSidebarDocItemLinkProp({
32
+ item,
33
+ doc,
34
+ }: {
35
+ item: SidebarItemDoc;
36
+ doc: Pick<
37
+ DocMetadata,
38
+ 'id' | 'title' | 'permalink' | 'unlisted' | 'frontMatter'
39
+ >;
40
+ }): PropSidebarItemLink {
41
+ const {
42
+ id,
43
+ title,
44
+ permalink,
45
+ frontMatter: {
46
+ sidebar_label: sidebarLabel,
47
+ sidebar_custom_props: customProps,
48
+ },
49
+ unlisted,
50
+ } = doc;
51
+ return {
52
+ type: 'link',
53
+ label: sidebarLabel ?? item.label ?? title,
54
+ href: permalink,
55
+ className: item.className,
56
+ customProps: item.customProps ?? customProps,
57
+ docId: id,
58
+ unlisted,
59
+ };
60
+ }
61
+
30
62
  export function toSidebarsProp(loadedVersion: LoadedVersion): PropSidebars {
31
63
  const docsById = createDocsByIdIndex(loadedVersion.docs);
32
64
 
@@ -43,21 +75,8 @@ Available document ids are:
43
75
  }
44
76
 
45
77
  const convertDocLink = (item: SidebarItemDoc): PropSidebarItemLink => {
46
- const docMetadata = getDocById(item.id);
47
- const {
48
- title,
49
- permalink,
50
- frontMatter: {sidebar_label: sidebarLabel},
51
- } = docMetadata;
52
- return {
53
- type: 'link',
54
- label: sidebarLabel ?? item.label ?? title,
55
- href: permalink,
56
- className: item.className,
57
- customProps:
58
- item.customProps ?? docMetadata.frontMatter.sidebar_custom_props,
59
- docId: docMetadata.unversionedId,
60
- };
78
+ const doc = getDocById(item.id);
79
+ return toSidebarDocItemLinkProp({item, doc});
61
80
  };
62
81
 
63
82
  function getCategoryLinkHref(
@@ -73,6 +92,15 @@ Available document ids are:
73
92
  }
74
93
  }
75
94
 
95
+ function getCategoryLinkUnlisted(
96
+ link: SidebarItemCategoryLink | undefined,
97
+ ): boolean {
98
+ if (link?.type === 'doc') {
99
+ return getDocById(link.id).unlisted;
100
+ }
101
+ return false;
102
+ }
103
+
76
104
  function getCategoryLinkCustomProps(
77
105
  link: SidebarItemCategoryLink | undefined,
78
106
  ) {
@@ -87,12 +115,14 @@ Available document ids are:
87
115
  function convertCategory(item: SidebarItemCategory): PropSidebarItemCategory {
88
116
  const {link, ...rest} = item;
89
117
  const href = getCategoryLinkHref(link);
118
+ const linkUnlisted = getCategoryLinkUnlisted(link);
90
119
  const customProps = item.customProps ?? getCategoryLinkCustomProps(link);
91
120
 
92
121
  return {
93
122
  ...rest,
94
123
  items: item.items.map(normalizeItem),
95
124
  ...(href && {href}),
125
+ ...(linkUnlisted && {linkUnlisted}),
96
126
  ...(customProps && {customProps}),
97
127
  };
98
128
  }
@@ -121,9 +151,9 @@ Available document ids are:
121
151
  function toVersionDocsProp(loadedVersion: LoadedVersion): PropVersionDocs {
122
152
  return Object.fromEntries(
123
153
  loadedVersion.docs.map((doc) => [
124
- doc.unversionedId,
154
+ doc.id,
125
155
  {
126
- id: doc.unversionedId,
156
+ id: doc.id,
127
157
  title: doc.title,
128
158
  description: doc.description,
129
159
  sidebar: doc.sidebar,
@@ -179,5 +209,18 @@ export function toTagDocListProp({
179
209
  allTagsPath,
180
210
  count: tag.docIds.length,
181
211
  items: toDocListProp(),
212
+ unlisted: tag.unlisted,
182
213
  };
183
214
  }
215
+
216
+ export function toTagsListTagsProp(
217
+ versionTags: VersionTags,
218
+ ): PropTagsListPage['tags'] {
219
+ return Object.values(versionTags)
220
+ .filter((tagValue) => !tagValue.unlisted)
221
+ .map((tagValue) => ({
222
+ label: tagValue.label,
223
+ permalink: tagValue.permalink,
224
+ count: tagValue.docIds.length,
225
+ }));
226
+ }