@docusaurus/plugin-content-docs 2.4.0 → 3.0.0-alpha.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/docs.ts CHANGED
@@ -18,6 +18,8 @@ import {
18
18
  posixPath,
19
19
  Globby,
20
20
  normalizeFrontMatterTags,
21
+ isUnlisted,
22
+ isDraft,
21
23
  } from '@docusaurus/utils';
22
24
 
23
25
  import {getFileLastUpdate} from './lastUpdate';
@@ -35,7 +37,6 @@ import type {
35
37
  PropNavigationLink,
36
38
  LastUpdateData,
37
39
  VersionMetadata,
38
- DocFrontMatter,
39
40
  LoadedVersion,
40
41
  FileChange,
41
42
  } from '@docusaurus/plugin-content-docs';
@@ -125,17 +126,6 @@ export async function readVersionDocs(
125
126
 
126
127
  export type DocEnv = 'production' | 'development';
127
128
 
128
- /** Docs with draft front matter are only considered draft in production. */
129
- function isDraftForEnvironment({
130
- env,
131
- frontMatter,
132
- }: {
133
- frontMatter: DocFrontMatter;
134
- env: DocEnv;
135
- }): boolean {
136
- return (env === 'production' && frontMatter.draft) ?? false;
137
- }
138
-
139
129
  async function doProcessDocMetadata({
140
130
  docFile,
141
131
  versionMetadata,
@@ -268,7 +258,8 @@ async function doProcessDocMetadata({
268
258
  return undefined;
269
259
  }
270
260
 
271
- const draft = isDraftForEnvironment({env, frontMatter});
261
+ const draft = isDraft({env, frontMatter});
262
+ const unlisted = isUnlisted({env, frontMatter});
272
263
 
273
264
  const formatDate = (locale: string, date: Date, calendar: string): string => {
274
265
  try {
@@ -299,6 +290,7 @@ async function doProcessDocMetadata({
299
290
  slug: docSlug,
300
291
  permalink,
301
292
  draft,
293
+ unlisted,
302
294
  editUrl: customEditURL !== undefined ? customEditURL : getDocEditUrl(),
303
295
  tags: normalizeFrontMatterTags(versionMetadata.tagsPath, frontMatter.tags),
304
296
  version: versionMetadata.versionName,
@@ -333,25 +325,32 @@ export async function processDocMetadata(args: {
333
325
  }
334
326
  }
335
327
 
336
- export function addDocNavigation(
337
- docsBase: DocMetadataBase[],
338
- sidebarsUtils: SidebarsUtils,
339
- sidebarFilePath: string,
340
- ): LoadedVersion['docs'] {
341
- const docsById = createDocsByIdIndex(docsBase);
328
+ function getUnlistedIds(docs: DocMetadataBase[]): Set<string> {
329
+ return new Set(docs.filter((doc) => doc.unlisted).map((doc) => doc.id));
330
+ }
342
331
 
343
- sidebarsUtils.checkSidebarsDocIds(
344
- docsBase.flatMap(getDocIds),
345
- sidebarFilePath,
346
- );
332
+ export function addDocNavigation({
333
+ docs,
334
+ sidebarsUtils,
335
+ sidebarFilePath,
336
+ }: {
337
+ docs: DocMetadataBase[];
338
+ sidebarsUtils: SidebarsUtils;
339
+ sidebarFilePath: string;
340
+ }): LoadedVersion['docs'] {
341
+ const docsById = createDocsByIdIndex(docs);
342
+ const unlistedIds = getUnlistedIds(docs);
343
+
344
+ sidebarsUtils.checkSidebarsDocIds(docs.flatMap(getDocIds), sidebarFilePath);
347
345
 
348
346
  // Add sidebar/next/previous to the docs
349
347
  function addNavData(doc: DocMetadataBase): DocMetadata {
350
- const navigation = sidebarsUtils.getDocNavigation(
351
- doc.unversionedId,
352
- doc.id,
353
- doc.frontMatter.displayed_sidebar,
354
- );
348
+ const navigation = sidebarsUtils.getDocNavigation({
349
+ unversionedId: doc.unversionedId,
350
+ versionedId: doc.id,
351
+ displayedSidebar: doc.frontMatter.displayed_sidebar,
352
+ unlistedIds,
353
+ });
355
354
 
356
355
  const toNavigationLinkByDocId = (
357
356
  docId: string | null | undefined,
@@ -367,6 +366,10 @@ export function addDocNavigation(
367
366
  `Error when loading ${doc.id} in ${doc.sourceDirName}: the pagination_${type} front matter points to a non-existent ID ${docId}.`,
368
367
  );
369
368
  }
369
+ // Gracefully handle explicitly providing an unlisted doc ID in production
370
+ if (navDoc.unlisted) {
371
+ return undefined;
372
+ }
370
373
  return toDocNavigationLink(navDoc);
371
374
  };
372
375
 
@@ -382,7 +385,7 @@ export function addDocNavigation(
382
385
  return {...doc, sidebar: navigation.sidebarName, previous, next};
383
386
  }
384
387
 
385
- const docsWithNavigation = docsBase.map(addNavData);
388
+ const docsWithNavigation = docs.map(addNavData);
386
389
  // Sort to ensure consistent output for tests
387
390
  docsWithNavigation.sort((a, b) => a.id.localeCompare(b.id));
388
391
  return docsWithNavigation;
@@ -11,6 +11,7 @@ import {
11
11
  FrontMatterTagsSchema,
12
12
  FrontMatterTOCHeadingLevels,
13
13
  validateFrontMatter,
14
+ ContentVisibilitySchema,
14
15
  } from '@docusaurus/utils-validation';
15
16
  import type {DocFrontMatter} from '@docusaurus/plugin-content-docs';
16
17
 
@@ -43,7 +44,6 @@ const DocFrontMatterSchema = Joi.object<DocFrontMatter>({
43
44
  parse_number_prefixes: Joi.boolean(),
44
45
  pagination_next: Joi.string().allow(null),
45
46
  pagination_prev: Joi.string().allow(null),
46
- draft: Joi.boolean(),
47
47
  ...FrontMatterTOCHeadingLevels,
48
48
  last_update: Joi.object({
49
49
  author: Joi.string(),
@@ -54,7 +54,9 @@ const DocFrontMatterSchema = Joi.object<DocFrontMatter>({
54
54
  'object.missing': FrontMatterLastUpdateErrorMessage,
55
55
  'object.base': FrontMatterLastUpdateErrorMessage,
56
56
  }),
57
- }).unknown();
57
+ })
58
+ .unknown()
59
+ .concat(ContentVisibilitySchema);
58
60
 
59
61
  export function validateDocFrontMatter(frontMatter: {
60
62
  [key: string]: unknown;
package/src/globalData.ts CHANGED
@@ -23,6 +23,11 @@ function toGlobalDataDoc(doc: DocMetadata): GlobalDoc {
23
23
  return {
24
24
  id: doc.unversionedId,
25
25
  path: doc.permalink,
26
+
27
+ // optimize global data size: do not add unlisted: false/undefined
28
+ ...(doc.unlisted && {unlisted: doc.unlisted}),
29
+
30
+ // TODO optimize size? remove attribute when no sidebar (breaking change?)
26
31
  sidebar: doc.sidebar,
27
32
  };
28
33
  }
package/src/index.ts CHANGED
@@ -27,22 +27,18 @@ import {
27
27
  addDocNavigation,
28
28
  type DocEnv,
29
29
  } from './docs';
30
- import {readVersionsMetadata} from './versions';
30
+ import {readVersionsMetadata, toFullVersion} from './versions';
31
31
  import {cliDocsVersionCommand} from './cli';
32
32
  import {VERSIONS_JSON_FILE} from './constants';
33
33
  import {toGlobalDataVersion} from './globalData';
34
- import {toTagDocListProp} from './props';
35
- import {getCategoryGeneratedIndexMetadataList} from './categoryGeneratedIndex';
36
34
  import {
37
35
  translateLoadedContent,
38
36
  getLoadedContentTranslationFiles,
39
37
  } from './translations';
40
- import {getVersionTags} from './tags';
41
- import {createVersionRoutes} from './routes';
38
+ import {createAllRoutes} from './routes';
42
39
  import {createSidebarsUtils} from './sidebars/utils';
43
40
 
44
41
  import type {
45
- PropTagsListPage,
46
42
  PluginOptions,
47
43
  DocMetadataBase,
48
44
  VersionMetadata,
@@ -55,7 +51,6 @@ import type {
55
51
  SourceToPermalink,
56
52
  DocFile,
57
53
  DocsMarkdownOption,
58
- VersionTag,
59
54
  FullVersion,
60
55
  } from './types';
61
56
  import type {RuleSetRule} from 'webpack';
@@ -80,6 +75,9 @@ export default async function pluginContentDocs(
80
75
  const aliasedSource = (source: string) =>
81
76
  `~docs/${posixPath(path.relative(pluginDataDirRoot, source))}`;
82
77
 
78
+ // TODO env should be injected into all plugins
79
+ const env = process.env.NODE_ENV as DocEnv;
80
+
83
81
  return {
84
82
  name: 'docusaurus-plugin-content-docs',
85
83
 
@@ -148,7 +146,7 @@ export default async function pluginContentDocs(
148
146
  versionMetadata,
149
147
  context,
150
148
  options,
151
- env: process.env.NODE_ENV as DocEnv,
149
+ env,
152
150
  });
153
151
  }
154
152
  return Promise.all(docFiles.map(processVersionDoc));
@@ -161,6 +159,9 @@ export default async function pluginContentDocs(
161
159
  versionMetadata,
162
160
  );
163
161
 
162
+ // TODO we only ever need draftIds in further code, not full draft items
163
+ // To simplify and prevent mistakes, avoid exposing draft
164
+ // replace draft=>draftIds in content loaded
164
165
  const [drafts, docs] = _.partition(docsBase, (doc) => doc.draft);
165
166
 
166
167
  const sidebars = await loadSidebars(versionMetadata.sidebarFilePath, {
@@ -180,11 +181,11 @@ export default async function pluginContentDocs(
180
181
 
181
182
  return {
182
183
  ...versionMetadata,
183
- docs: addDocNavigation(
184
+ docs: addDocNavigation({
184
185
  docs,
185
186
  sidebarsUtils,
186
- versionMetadata.sidebarFilePath as string,
187
- ),
187
+ sidebarFilePath: versionMetadata.sidebarFilePath as string,
188
+ }),
188
189
  drafts,
189
190
  sidebars,
190
191
  };
@@ -209,107 +210,24 @@ export default async function pluginContentDocs(
209
210
  },
210
211
 
211
212
  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
- };
213
+ const versions: FullVersion[] = content.loadedVersions.map(toFullVersion);
214
+
215
+ await createAllRoutes({
216
+ baseUrl,
217
+ versions,
218
+ options,
219
+ actions,
220
+ aliasedSource,
230
221
  });
231
222
 
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({
223
+ actions.setGlobalData({
305
224
  path: normalizeUrl([baseUrl, options.routeBasePath]),
306
225
  versions: versions.map(toGlobalDataVersion),
307
- breadcrumbs,
226
+ breadcrumbs: options.breadcrumbs,
308
227
  });
309
228
  },
310
229
 
311
230
  configureWebpack(_config, isServer, utils, content) {
312
- const {getJSLoader} = utils;
313
231
  const {
314
232
  rehypePlugins,
315
233
  remarkPlugins,
@@ -344,7 +262,6 @@ export default async function pluginContentDocs(
344
262
  test: /\.mdx?$/i,
345
263
  include: contentDirs,
346
264
  use: [
347
- getJSLoader({isServer}),
348
265
  {
349
266
  loader: require.resolve('@docusaurus/mdx-loader'),
350
267
  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
  };
@@ -434,6 +450,11 @@ declare module '@docusaurus/plugin-content-docs' {
434
450
  * Draft docs will be excluded for production environment.
435
451
  */
436
452
  draft: boolean;
453
+ /**
454
+ * Unlisted docs are accessible when directly visible, but will be hidden
455
+ * from the sidebar and pagination in production.
456
+ */
457
+ unlisted: boolean;
437
458
  /**
438
459
  * Position in an autogenerated sidebar slice, acquired through front matter
439
460
  * or number prefix.
@@ -610,14 +631,32 @@ declare module '@theme/DocBreadcrumbs' {
610
631
  export default function DocBreadcrumbs(): JSX.Element;
611
632
  }
612
633
 
613
- declare module '@theme/DocPage' {
634
+ declare module '@theme/DocsRoot' {
635
+ import type {RouteConfigComponentProps} from 'react-router-config';
636
+ import type {Required} from 'utility-types';
637
+
638
+ export interface Props extends Required<RouteConfigComponentProps, 'route'> {}
639
+
640
+ export default function DocsRoot(props: Props): JSX.Element;
641
+ }
642
+
643
+ declare module '@theme/DocVersionRoot' {
614
644
  import type {PropVersionMetadata} from '@docusaurus/plugin-content-docs';
615
645
  import type {RouteConfigComponentProps} from 'react-router-config';
616
646
  import type {Required} from 'utility-types';
617
647
 
618
648
  export interface Props extends Required<RouteConfigComponentProps, 'route'> {
619
- readonly versionMetadata: PropVersionMetadata;
649
+ readonly version: PropVersionMetadata;
620
650
  }
621
651
 
622
- export default function DocPage(props: Props): JSX.Element;
652
+ export default function DocVersionRoot(props: Props): JSX.Element;
653
+ }
654
+
655
+ declare module '@theme/DocRoot' {
656
+ import type {RouteConfigComponentProps} from 'react-router-config';
657
+ import type {Required} from 'utility-types';
658
+
659
+ export interface Props extends Required<RouteConfigComponentProps, 'route'> {}
660
+
661
+ export default function DocRoot(props: Props): JSX.Element;
623
662
  }
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' | 'unversionedId'
39
+ >;
40
+ }): PropSidebarItemLink {
41
+ const {
42
+ title,
43
+ permalink,
44
+ frontMatter: {
45
+ sidebar_label: sidebarLabel,
46
+ sidebar_custom_props: customProps,
47
+ },
48
+ unlisted,
49
+ unversionedId,
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: unversionedId,
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
  }
@@ -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
+ }