@docusaurus/plugin-content-docs 0.0.0-5362 → 0.0.0-5367

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.
@@ -24,7 +24,8 @@ export declare type GlobalDoc = {
24
24
  */
25
25
  id: string;
26
26
  path: string;
27
- sidebar: string | undefined;
27
+ sidebar?: string;
28
+ unlisted?: boolean;
28
29
  };
29
30
  export declare type GlobalVersion = {
30
31
  name: string;
package/lib/docs.d.ts CHANGED
@@ -18,7 +18,11 @@ export declare function processDocMetadata(args: {
18
18
  options: MetadataOptions;
19
19
  env: DocEnv;
20
20
  }): Promise<DocMetadataBase>;
21
- export declare function addDocNavigation(docsBase: DocMetadataBase[], sidebarsUtils: SidebarsUtils, sidebarFilePath: string): LoadedVersion['docs'];
21
+ export declare function addDocNavigation({ docs, sidebarsUtils, sidebarFilePath, }: {
22
+ docs: DocMetadataBase[];
23
+ sidebarsUtils: SidebarsUtils;
24
+ sidebarFilePath: string;
25
+ }): LoadedVersion['docs'];
22
26
  /**
23
27
  * The "main doc" is the "version entry point"
24
28
  * We browse this doc by clicking on a version:
package/lib/docs.js CHANGED
@@ -64,10 +64,6 @@ async function readVersionDocs(versionMetadata, options) {
64
64
  return Promise.all(sources.map((source) => readDocFile(versionMetadata, source)));
65
65
  }
66
66
  exports.readVersionDocs = readVersionDocs;
67
- /** Docs with draft front matter are only considered draft in production. */
68
- function isDraftForEnvironment({ env, frontMatter, }) {
69
- return (env === 'production' && frontMatter.draft) ?? false;
70
- }
71
67
  async function doProcessDocMetadata({ docFile, versionMetadata, context, options, env, }) {
72
68
  const { source, content, contentPath, filePath } = docFile;
73
69
  const { siteDir, i18n } = context;
@@ -150,7 +146,8 @@ async function doProcessDocMetadata({ docFile, versionMetadata, context, options
150
146
  }
151
147
  return undefined;
152
148
  }
153
- const draft = isDraftForEnvironment({ env, frontMatter });
149
+ const draft = (0, utils_1.isDraft)({ env, frontMatter });
150
+ const unlisted = (0, utils_1.isUnlisted)({ env, frontMatter });
154
151
  const formatDate = (locale, date, calendar) => {
155
152
  try {
156
153
  return new Intl.DateTimeFormat(locale, {
@@ -180,6 +177,7 @@ async function doProcessDocMetadata({ docFile, versionMetadata, context, options
180
177
  slug: docSlug,
181
178
  permalink,
182
179
  draft,
180
+ unlisted,
183
181
  editUrl: customEditURL !== undefined ? customEditURL : getDocEditUrl(),
184
182
  tags: (0, utils_1.normalizeFrontMatterTags)(versionMetadata.tagsPath, frontMatter.tags),
185
183
  version: versionMetadata.versionName,
@@ -201,12 +199,21 @@ async function processDocMetadata(args) {
201
199
  }
202
200
  }
203
201
  exports.processDocMetadata = processDocMetadata;
204
- function addDocNavigation(docsBase, sidebarsUtils, sidebarFilePath) {
205
- const docsById = createDocsByIdIndex(docsBase);
206
- sidebarsUtils.checkSidebarsDocIds(docsBase.flatMap(getDocIds), sidebarFilePath);
202
+ function getUnlistedIds(docs) {
203
+ return new Set(docs.filter((doc) => doc.unlisted).map((doc) => doc.id));
204
+ }
205
+ function addDocNavigation({ docs, sidebarsUtils, sidebarFilePath, }) {
206
+ const docsById = createDocsByIdIndex(docs);
207
+ const unlistedIds = getUnlistedIds(docs);
208
+ sidebarsUtils.checkSidebarsDocIds(docs.flatMap(getDocIds), sidebarFilePath);
207
209
  // Add sidebar/next/previous to the docs
208
210
  function addNavData(doc) {
209
- const navigation = sidebarsUtils.getDocNavigation(doc.unversionedId, doc.id, doc.frontMatter.displayed_sidebar);
211
+ const navigation = sidebarsUtils.getDocNavigation({
212
+ unversionedId: doc.unversionedId,
213
+ versionedId: doc.id,
214
+ displayedSidebar: doc.frontMatter.displayed_sidebar,
215
+ unlistedIds,
216
+ });
210
217
  const toNavigationLinkByDocId = (docId, type) => {
211
218
  if (!docId) {
212
219
  return undefined;
@@ -216,6 +223,10 @@ function addDocNavigation(docsBase, sidebarsUtils, sidebarFilePath) {
216
223
  // This could only happen if user provided the ID through front matter
217
224
  throw new Error(`Error when loading ${doc.id} in ${doc.sourceDirName}: the pagination_${type} front matter points to a non-existent ID ${docId}.`);
218
225
  }
226
+ // Gracefully handle explicitly providing an unlisted doc ID in production
227
+ if (navDoc.unlisted) {
228
+ return undefined;
229
+ }
219
230
  return (0, utils_2.toDocNavigationLink)(navDoc);
220
231
  };
221
232
  const previous = doc.frontMatter.pagination_prev !== undefined
@@ -226,7 +237,7 @@ function addDocNavigation(docsBase, sidebarsUtils, sidebarFilePath) {
226
237
  : (0, utils_2.toNavigationLink)(navigation.next, docsById);
227
238
  return { ...doc, sidebar: navigation.sidebarName, previous, next };
228
239
  }
229
- const docsWithNavigation = docsBase.map(addNavData);
240
+ const docsWithNavigation = docs.map(addNavData);
230
241
  // Sort to ensure consistent output for tests
231
242
  docsWithNavigation.sort((a, b) => a.id.localeCompare(b.id));
232
243
  return docsWithNavigation;
@@ -35,7 +35,6 @@ const DocFrontMatterSchema = utils_validation_1.JoiFrontMatter.object({
35
35
  parse_number_prefixes: utils_validation_1.JoiFrontMatter.boolean(),
36
36
  pagination_next: utils_validation_1.JoiFrontMatter.string().allow(null),
37
37
  pagination_prev: utils_validation_1.JoiFrontMatter.string().allow(null),
38
- draft: utils_validation_1.JoiFrontMatter.boolean(),
39
38
  ...utils_validation_1.FrontMatterTOCHeadingLevels,
40
39
  last_update: utils_validation_1.JoiFrontMatter.object({
41
40
  author: utils_validation_1.JoiFrontMatter.string(),
@@ -46,7 +45,9 @@ const DocFrontMatterSchema = utils_validation_1.JoiFrontMatter.object({
46
45
  'object.missing': FrontMatterLastUpdateErrorMessage,
47
46
  'object.base': FrontMatterLastUpdateErrorMessage,
48
47
  }),
49
- }).unknown();
48
+ })
49
+ .unknown()
50
+ .concat(utils_validation_1.ContentVisibilitySchema);
50
51
  function validateDocFrontMatter(frontMatter) {
51
52
  return (0, utils_validation_1.validateFrontMatter)(frontMatter, DocFrontMatterSchema);
52
53
  }
package/lib/globalData.js CHANGED
@@ -14,6 +14,9 @@ function toGlobalDataDoc(doc) {
14
14
  return {
15
15
  id: doc.unversionedId,
16
16
  path: doc.permalink,
17
+ // optimize global data size: do not add unlisted: false/undefined
18
+ ...(doc.unlisted && { unlisted: doc.unlisted }),
19
+ // TODO optimize size? remove attribute when no sidebar (breaking change?)
17
20
  sidebar: doc.sidebar,
18
21
  };
19
22
  }
package/lib/index.js CHANGED
@@ -31,6 +31,8 @@ async function pluginContentDocs(context, options) {
31
31
  const pluginDataDirRoot = path_1.default.join(generatedFilesDir, 'docusaurus-plugin-content-docs');
32
32
  const dataDir = path_1.default.join(pluginDataDirRoot, pluginId);
33
33
  const aliasedSource = (source) => `~docs/${(0, utils_1.posixPath)(path_1.default.relative(pluginDataDirRoot, source))}`;
34
+ // TODO env should be injected into all plugins
35
+ const env = process.env.NODE_ENV;
34
36
  return {
35
37
  name: 'docusaurus-plugin-content-docs',
36
38
  extendCli(cli) {
@@ -77,13 +79,16 @@ async function pluginContentDocs(context, options) {
77
79
  versionMetadata,
78
80
  context,
79
81
  options,
80
- env: process.env.NODE_ENV,
82
+ env,
81
83
  });
82
84
  }
83
85
  return Promise.all(docFiles.map(processVersionDoc));
84
86
  }
85
87
  async function doLoadVersion(versionMetadata) {
86
88
  const docsBase = await loadVersionDocsBase(versionMetadata);
89
+ // TODO we only ever need draftIds in further code, not full draft items
90
+ // To simplify and prevent mistakes, avoid exposing draft
91
+ // replace draft=>draftIds in content loaded
87
92
  const [drafts, docs] = lodash_1.default.partition(docsBase, (doc) => doc.draft);
88
93
  const sidebars = await (0, sidebars_1.loadSidebars)(versionMetadata.sidebarFilePath, {
89
94
  sidebarItemsGenerator: options.sidebarItemsGenerator,
@@ -100,7 +105,11 @@ async function pluginContentDocs(context, options) {
100
105
  const sidebarsUtils = (0, utils_2.createSidebarsUtils)(sidebars);
101
106
  return {
102
107
  ...versionMetadata,
103
- docs: (0, docs_1.addDocNavigation)(docs, sidebarsUtils, versionMetadata.sidebarFilePath),
108
+ docs: (0, docs_1.addDocNavigation)({
109
+ docs,
110
+ sidebarsUtils,
111
+ sidebarFilePath: versionMetadata.sidebarFilePath,
112
+ }),
104
113
  drafts,
105
114
  sidebars,
106
115
  };
package/lib/props.d.ts CHANGED
@@ -5,7 +5,12 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
  import type { VersionTag, VersionTags } from './types';
8
- import type { PropSidebars, PropVersionMetadata, PropTagDocList, PropTagsListPage, DocMetadata, LoadedVersion } from '@docusaurus/plugin-content-docs';
8
+ import type { SidebarItemDoc } from './sidebars/types';
9
+ import type { PropSidebars, PropVersionMetadata, PropTagDocList, PropTagsListPage, PropSidebarItemLink, DocMetadata, LoadedVersion } from '@docusaurus/plugin-content-docs';
10
+ export declare function toSidebarDocItemLinkProp({ item, doc, }: {
11
+ item: SidebarItemDoc;
12
+ doc: Pick<DocMetadata, 'id' | 'title' | 'permalink' | 'unlisted' | 'frontMatter' | 'unversionedId'>;
13
+ }): PropSidebarItemLink;
9
14
  export declare function toSidebarsProp(loadedVersion: LoadedVersion): PropSidebars;
10
15
  export declare function toVersionMetadataProp(pluginId: string, loadedVersion: LoadedVersion): PropVersionMetadata;
11
16
  export declare function toTagDocListProp({ allTagsPath, tag, docs, }: {
package/lib/props.js CHANGED
@@ -6,10 +6,23 @@
6
6
  * LICENSE file in the root directory of this source tree.
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.toTagsListTagsProp = exports.toTagDocListProp = exports.toVersionMetadataProp = exports.toSidebarsProp = void 0;
9
+ exports.toTagsListTagsProp = exports.toTagDocListProp = exports.toVersionMetadataProp = exports.toSidebarsProp = exports.toSidebarDocItemLinkProp = void 0;
10
10
  const tslib_1 = require("tslib");
11
11
  const lodash_1 = tslib_1.__importDefault(require("lodash"));
12
12
  const docs_1 = require("./docs");
13
+ function toSidebarDocItemLinkProp({ item, doc, }) {
14
+ const { title, permalink, frontMatter: { sidebar_label: sidebarLabel, sidebar_custom_props: customProps, }, unlisted, unversionedId, } = doc;
15
+ return {
16
+ type: 'link',
17
+ label: sidebarLabel ?? item.label ?? title,
18
+ href: permalink,
19
+ className: item.className,
20
+ customProps: item.customProps ?? customProps,
21
+ docId: unversionedId,
22
+ unlisted,
23
+ };
24
+ }
25
+ exports.toSidebarDocItemLinkProp = toSidebarDocItemLinkProp;
13
26
  function toSidebarsProp(loadedVersion) {
14
27
  const docsById = (0, docs_1.createDocsByIdIndex)(loadedVersion.docs);
15
28
  function getDocById(docId) {
@@ -22,16 +35,8 @@ Available document ids are:
22
35
  return docMetadata;
23
36
  }
24
37
  const convertDocLink = (item) => {
25
- const docMetadata = getDocById(item.id);
26
- const { title, permalink, frontMatter: { sidebar_label: sidebarLabel }, } = docMetadata;
27
- return {
28
- type: 'link',
29
- label: sidebarLabel ?? item.label ?? title,
30
- href: permalink,
31
- className: item.className,
32
- customProps: item.customProps ?? docMetadata.frontMatter.sidebar_custom_props,
33
- docId: docMetadata.unversionedId,
34
- };
38
+ const doc = getDocById(item.id);
39
+ return toSidebarDocItemLinkProp({ item, doc });
35
40
  };
36
41
  function getCategoryLinkHref(link) {
37
42
  switch (link?.type) {
@@ -43,6 +48,12 @@ Available document ids are:
43
48
  return undefined;
44
49
  }
45
50
  }
51
+ function getCategoryLinkUnlisted(link) {
52
+ if (link?.type === 'doc') {
53
+ return getDocById(link.id).unlisted;
54
+ }
55
+ return false;
56
+ }
46
57
  function getCategoryLinkCustomProps(link) {
47
58
  switch (link?.type) {
48
59
  case 'doc':
@@ -54,11 +65,13 @@ Available document ids are:
54
65
  function convertCategory(item) {
55
66
  const { link, ...rest } = item;
56
67
  const href = getCategoryLinkHref(link);
68
+ const linkUnlisted = getCategoryLinkUnlisted(link);
57
69
  const customProps = item.customProps ?? getCategoryLinkCustomProps(link);
58
70
  return {
59
71
  ...rest,
60
72
  items: item.items.map(normalizeItem),
61
73
  ...(href && { href }),
74
+ ...(linkUnlisted && { linkUnlisted }),
62
75
  ...(customProps && { customProps }),
63
76
  };
64
77
  }
@@ -124,11 +137,14 @@ function toTagDocListProp({ allTagsPath, tag, docs, }) {
124
137
  allTagsPath,
125
138
  count: tag.docIds.length,
126
139
  items: toDocListProp(),
140
+ unlisted: tag.unlisted,
127
141
  };
128
142
  }
129
143
  exports.toTagDocListProp = toTagDocListProp;
130
144
  function toTagsListTagsProp(versionTags) {
131
- return Object.values(versionTags).map((tagValue) => ({
145
+ return Object.values(versionTags)
146
+ .filter((tagValue) => !tagValue.unlisted)
147
+ .map((tagValue) => ({
132
148
  label: tagValue.label,
133
149
  permalink: tagValue.permalink,
134
150
  count: tagValue.docIds.length,
@@ -120,9 +120,11 @@ export declare type Sidebars = {
120
120
  export declare type PropSidebarItemCategory = Expand<SidebarItemCategoryBase & {
121
121
  items: PropSidebarItem[];
122
122
  href?: string;
123
+ linkUnlisted?: boolean;
123
124
  }>;
124
125
  export declare type PropSidebarItemLink = SidebarItemLink & {
125
126
  docId?: string;
127
+ unlisted?: boolean;
126
128
  };
127
129
  export declare type PropSidebarItemHtml = SidebarItemHtml;
128
130
  export declare type PropSidebarItem = PropSidebarItemLink | PropSidebarItemCategory | PropSidebarItemHtml;
@@ -29,7 +29,12 @@ export declare type SidebarsUtils = {
29
29
  sidebars: Sidebars;
30
30
  getFirstDocIdOfFirstSidebar: () => string | undefined;
31
31
  getSidebarNameByDocId: (docId: string) => string | undefined;
32
- getDocNavigation: (unversionedId: string, versionedId: string, displayedSidebar: string | null | undefined) => SidebarNavigation;
32
+ getDocNavigation: (params: {
33
+ unversionedId: string;
34
+ versionedId: string;
35
+ displayedSidebar: string | null | undefined;
36
+ unlistedIds: Set<string>;
37
+ }) => SidebarNavigation;
33
38
  getCategoryGeneratedIndexList: () => SidebarItemCategoryWithGeneratedIndex[];
34
39
  getCategoryGeneratedIndexNavigation: (categoryGeneratedIndexPermalink: string) => SidebarNavigation;
35
40
  /**
@@ -110,7 +110,7 @@ function createSidebarsUtils(sidebars) {
110
110
  next: undefined,
111
111
  };
112
112
  }
113
- function getDocNavigation(unversionedId, versionedId, displayedSidebar) {
113
+ function getDocNavigation({ unversionedId, versionedId, displayedSidebar, unlistedIds, }) {
114
114
  // TODO legacy id retro-compatibility!
115
115
  let docId = unversionedId;
116
116
  let sidebarName = displayedSidebar === undefined
@@ -123,10 +123,22 @@ function createSidebarsUtils(sidebars) {
123
123
  if (!sidebarName) {
124
124
  return emptySidebarNavigation();
125
125
  }
126
- const navigationItems = sidebarNameToNavigationItems[sidebarName];
126
+ let navigationItems = sidebarNameToNavigationItems[sidebarName];
127
127
  if (!navigationItems) {
128
128
  throw new Error(`Doc with ID ${docId} wants to display sidebar ${sidebarName} but a sidebar with this name doesn't exist`);
129
129
  }
130
+ // Filter unlisted items from navigation
131
+ navigationItems = navigationItems.filter((item) => {
132
+ if (item.type === 'doc' && unlistedIds.has(item.id)) {
133
+ return false;
134
+ }
135
+ if (item.type === 'category' &&
136
+ item.link.type === 'doc' &&
137
+ unlistedIds.has(item.link.id)) {
138
+ return false;
139
+ }
140
+ return true;
141
+ });
130
142
  const currentItemIndex = navigationItems.findIndex((item) => {
131
143
  if (item.type === 'doc') {
132
144
  return item.id === docId;
package/lib/tags.js CHANGED
@@ -12,10 +12,17 @@ const lodash_1 = tslib_1.__importDefault(require("lodash"));
12
12
  const utils_1 = require("@docusaurus/utils");
13
13
  function getVersionTags(docs) {
14
14
  const groups = (0, utils_1.groupTaggedItems)(docs, (doc) => doc.tags);
15
- return lodash_1.default.mapValues(groups, (group) => ({
16
- label: group.tag.label,
17
- docIds: group.items.map((item) => item.id),
18
- permalink: group.tag.permalink,
19
- }));
15
+ return lodash_1.default.mapValues(groups, ({ tag, items: tagDocs }) => {
16
+ const tagVisibility = (0, utils_1.getTagVisibility)({
17
+ items: tagDocs,
18
+ isUnlisted: (item) => item.unlisted,
19
+ });
20
+ return {
21
+ label: tag.label,
22
+ docIds: tagVisibility.listedItems.map((item) => item.id),
23
+ permalink: tag.permalink,
24
+ unlisted: tagVisibility.unlisted,
25
+ };
26
+ });
20
27
  }
21
28
  exports.getVersionTags = getVersionTags;
package/lib/types.d.ts CHANGED
@@ -19,6 +19,7 @@ export declare type SourceToPermalink = {
19
19
  export declare type VersionTag = Tag & {
20
20
  /** All doc ids having this tag. */
21
21
  docIds: string[];
22
+ unlisted: boolean;
22
23
  };
23
24
  export declare type VersionTags = {
24
25
  [permalink: string]: VersionTag;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@docusaurus/plugin-content-docs",
3
- "version": "0.0.0-5362",
3
+ "version": "0.0.0-5367",
4
4
  "description": "Docs plugin for Docusaurus.",
5
5
  "main": "lib/index.js",
6
6
  "sideEffects": false,
@@ -35,13 +35,13 @@
35
35
  },
36
36
  "license": "MIT",
37
37
  "dependencies": {
38
- "@docusaurus/core": "0.0.0-5362",
39
- "@docusaurus/logger": "0.0.0-5362",
40
- "@docusaurus/mdx-loader": "0.0.0-5362",
41
- "@docusaurus/module-type-aliases": "0.0.0-5362",
42
- "@docusaurus/types": "0.0.0-5362",
43
- "@docusaurus/utils": "0.0.0-5362",
44
- "@docusaurus/utils-validation": "0.0.0-5362",
38
+ "@docusaurus/core": "0.0.0-5367",
39
+ "@docusaurus/logger": "0.0.0-5367",
40
+ "@docusaurus/mdx-loader": "0.0.0-5367",
41
+ "@docusaurus/module-type-aliases": "0.0.0-5367",
42
+ "@docusaurus/types": "0.0.0-5367",
43
+ "@docusaurus/utils": "0.0.0-5367",
44
+ "@docusaurus/utils-validation": "0.0.0-5367",
45
45
  "@types/react-router-config": "^5.0.6",
46
46
  "combine-promises": "^1.1.0",
47
47
  "fs-extra": "^10.1.0",
@@ -67,5 +67,5 @@
67
67
  "engines": {
68
68
  "node": ">=16.14"
69
69
  },
70
- "gitHead": "c3bef651473ab5aabf0b28f56c2e3d680a3d5615"
70
+ "gitHead": "4cb5f25e6e83097230018df14308090a9dd8df20"
71
71
  }
@@ -37,7 +37,8 @@ export type GlobalDoc = {
37
37
  */
38
38
  id: string;
39
39
  path: string;
40
- sidebar: string | undefined;
40
+ sidebar?: string;
41
+ unlisted?: boolean;
41
42
  };
42
43
 
43
44
  export type GlobalVersion = {
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
@@ -75,6 +75,9 @@ export default async function pluginContentDocs(
75
75
  const aliasedSource = (source: string) =>
76
76
  `~docs/${posixPath(path.relative(pluginDataDirRoot, source))}`;
77
77
 
78
+ // TODO env should be injected into all plugins
79
+ const env = process.env.NODE_ENV as DocEnv;
80
+
78
81
  return {
79
82
  name: 'docusaurus-plugin-content-docs',
80
83
 
@@ -143,7 +146,7 @@ export default async function pluginContentDocs(
143
146
  versionMetadata,
144
147
  context,
145
148
  options,
146
- env: process.env.NODE_ENV as DocEnv,
149
+ env,
147
150
  });
148
151
  }
149
152
  return Promise.all(docFiles.map(processVersionDoc));
@@ -156,6 +159,9 @@ export default async function pluginContentDocs(
156
159
  versionMetadata,
157
160
  );
158
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
159
165
  const [drafts, docs] = _.partition(docsBase, (doc) => doc.draft);
160
166
 
161
167
  const sidebars = await loadSidebars(versionMetadata.sidebarFilePath, {
@@ -175,11 +181,11 @@ export default async function pluginContentDocs(
175
181
 
176
182
  return {
177
183
  ...versionMetadata,
178
- docs: addDocNavigation(
184
+ docs: addDocNavigation({
179
185
  docs,
180
186
  sidebarsUtils,
181
- versionMetadata.sidebarFilePath as string,
182
- ),
187
+ sidebarFilePath: versionMetadata.sidebarFilePath as string,
188
+ }),
183
189
  drafts,
184
190
  sidebars,
185
191
  };
@@ -398,6 +398,8 @@ declare module '@docusaurus/plugin-content-docs' {
398
398
  pagination_prev?: string | null;
399
399
  /** Should this doc be excluded from production builds? */
400
400
  draft?: boolean;
401
+ /** Should this doc be accessible but hidden in production builds? */
402
+ unlisted?: boolean;
401
403
  /** Allows overriding the last updated author and/or date. */
402
404
  last_update?: FileChange;
403
405
  };
@@ -448,6 +450,11 @@ declare module '@docusaurus/plugin-content-docs' {
448
450
  * Draft docs will be excluded for production environment.
449
451
  */
450
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;
451
458
  /**
452
459
  * Position in an autogenerated sidebar slice, acquired through front matter
453
460
  * or number prefix.
package/src/props.ts CHANGED
@@ -28,6 +28,37 @@ import type {
28
28
  LoadedVersion,
29
29
  } from '@docusaurus/plugin-content-docs';
30
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
+
31
62
  export function toSidebarsProp(loadedVersion: LoadedVersion): PropSidebars {
32
63
  const docsById = createDocsByIdIndex(loadedVersion.docs);
33
64
 
@@ -44,21 +75,8 @@ Available document ids are:
44
75
  }
45
76
 
46
77
  const convertDocLink = (item: SidebarItemDoc): PropSidebarItemLink => {
47
- const docMetadata = getDocById(item.id);
48
- const {
49
- title,
50
- permalink,
51
- frontMatter: {sidebar_label: sidebarLabel},
52
- } = docMetadata;
53
- return {
54
- type: 'link',
55
- label: sidebarLabel ?? item.label ?? title,
56
- href: permalink,
57
- className: item.className,
58
- customProps:
59
- item.customProps ?? docMetadata.frontMatter.sidebar_custom_props,
60
- docId: docMetadata.unversionedId,
61
- };
78
+ const doc = getDocById(item.id);
79
+ return toSidebarDocItemLinkProp({item, doc});
62
80
  };
63
81
 
64
82
  function getCategoryLinkHref(
@@ -74,6 +92,15 @@ Available document ids are:
74
92
  }
75
93
  }
76
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
+
77
104
  function getCategoryLinkCustomProps(
78
105
  link: SidebarItemCategoryLink | undefined,
79
106
  ) {
@@ -88,12 +115,14 @@ Available document ids are:
88
115
  function convertCategory(item: SidebarItemCategory): PropSidebarItemCategory {
89
116
  const {link, ...rest} = item;
90
117
  const href = getCategoryLinkHref(link);
118
+ const linkUnlisted = getCategoryLinkUnlisted(link);
91
119
  const customProps = item.customProps ?? getCategoryLinkCustomProps(link);
92
120
 
93
121
  return {
94
122
  ...rest,
95
123
  items: item.items.map(normalizeItem),
96
124
  ...(href && {href}),
125
+ ...(linkUnlisted && {linkUnlisted}),
97
126
  ...(customProps && {customProps}),
98
127
  };
99
128
  }
@@ -180,15 +209,18 @@ export function toTagDocListProp({
180
209
  allTagsPath,
181
210
  count: tag.docIds.length,
182
211
  items: toDocListProp(),
212
+ unlisted: tag.unlisted,
183
213
  };
184
214
  }
185
215
 
186
216
  export function toTagsListTagsProp(
187
217
  versionTags: VersionTags,
188
218
  ): PropTagsListPage['tags'] {
189
- return Object.values(versionTags).map((tagValue) => ({
190
- label: tagValue.label,
191
- permalink: tagValue.permalink,
192
- count: tagValue.docIds.length,
193
- }));
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
+ }));
194
226
  }
@@ -183,11 +183,18 @@ export type PropSidebarItemCategory = Expand<
183
183
  SidebarItemCategoryBase & {
184
184
  items: PropSidebarItem[];
185
185
  href?: string;
186
+
187
+ // Weird name => it would have been more convenient to have link.unlisted
188
+ // Note it is the category link that is unlisted, not the category itself
189
+ // We want to prevent users from clicking on an unlisted category link
190
+ // We can't use "href: undefined" otherwise sidebar item is not highlighted
191
+ linkUnlisted?: boolean;
186
192
  }
187
193
  >;
188
194
 
189
195
  export type PropSidebarItemLink = SidebarItemLink & {
190
196
  docId?: string;
197
+ unlisted?: boolean;
191
198
  };
192
199
 
193
200
  export type PropSidebarItemHtml = SidebarItemHtml;
@@ -135,11 +135,12 @@ export type SidebarsUtils = {
135
135
  sidebars: Sidebars;
136
136
  getFirstDocIdOfFirstSidebar: () => string | undefined;
137
137
  getSidebarNameByDocId: (docId: string) => string | undefined;
138
- getDocNavigation: (
139
- unversionedId: string,
140
- versionedId: string,
141
- displayedSidebar: string | null | undefined,
142
- ) => SidebarNavigation;
138
+ getDocNavigation: (params: {
139
+ unversionedId: string;
140
+ versionedId: string;
141
+ displayedSidebar: string | null | undefined;
142
+ unlistedIds: Set<string>;
143
+ }) => SidebarNavigation;
143
144
  getCategoryGeneratedIndexList: () => SidebarItemCategoryWithGeneratedIndex[];
144
145
  getCategoryGeneratedIndexNavigation: (
145
146
  categoryGeneratedIndexPermalink: string,
@@ -192,11 +193,17 @@ export function createSidebarsUtils(sidebars: Sidebars): SidebarsUtils {
192
193
  };
193
194
  }
194
195
 
195
- function getDocNavigation(
196
- unversionedId: string,
197
- versionedId: string,
198
- displayedSidebar: string | null | undefined,
199
- ): SidebarNavigation {
196
+ function getDocNavigation({
197
+ unversionedId,
198
+ versionedId,
199
+ displayedSidebar,
200
+ unlistedIds,
201
+ }: {
202
+ unversionedId: string;
203
+ versionedId: string;
204
+ displayedSidebar: string | null | undefined;
205
+ unlistedIds: Set<string>;
206
+ }): SidebarNavigation {
200
207
  // TODO legacy id retro-compatibility!
201
208
  let docId = unversionedId;
202
209
  let sidebarName =
@@ -211,12 +218,28 @@ export function createSidebarsUtils(sidebars: Sidebars): SidebarsUtils {
211
218
  if (!sidebarName) {
212
219
  return emptySidebarNavigation();
213
220
  }
214
- const navigationItems = sidebarNameToNavigationItems[sidebarName];
221
+ let navigationItems = sidebarNameToNavigationItems[sidebarName];
215
222
  if (!navigationItems) {
216
223
  throw new Error(
217
224
  `Doc with ID ${docId} wants to display sidebar ${sidebarName} but a sidebar with this name doesn't exist`,
218
225
  );
219
226
  }
227
+
228
+ // Filter unlisted items from navigation
229
+ navigationItems = navigationItems.filter((item) => {
230
+ if (item.type === 'doc' && unlistedIds.has(item.id)) {
231
+ return false;
232
+ }
233
+ if (
234
+ item.type === 'category' &&
235
+ item.link.type === 'doc' &&
236
+ unlistedIds.has(item.link.id)
237
+ ) {
238
+ return false;
239
+ }
240
+ return true;
241
+ });
242
+
220
243
  const currentItemIndex = navigationItems.findIndex((item) => {
221
244
  if (item.type === 'doc') {
222
245
  return item.id === docId;
package/src/tags.ts CHANGED
@@ -6,15 +6,22 @@
6
6
  */
7
7
 
8
8
  import _ from 'lodash';
9
- import {groupTaggedItems} from '@docusaurus/utils';
9
+ import {getTagVisibility, groupTaggedItems} from '@docusaurus/utils';
10
10
  import type {VersionTags} from './types';
11
11
  import type {DocMetadata} from '@docusaurus/plugin-content-docs';
12
12
 
13
13
  export function getVersionTags(docs: DocMetadata[]): VersionTags {
14
14
  const groups = groupTaggedItems(docs, (doc) => doc.tags);
15
- return _.mapValues(groups, (group) => ({
16
- label: group.tag.label,
17
- docIds: group.items.map((item) => item.id),
18
- permalink: group.tag.permalink,
19
- }));
15
+ return _.mapValues(groups, ({tag, items: tagDocs}) => {
16
+ const tagVisibility = getTagVisibility({
17
+ items: tagDocs,
18
+ isUnlisted: (item) => item.unlisted,
19
+ });
20
+ return {
21
+ label: tag.label,
22
+ docIds: tagVisibility.listedItems.map((item) => item.id),
23
+ permalink: tag.permalink,
24
+ unlisted: tagVisibility.unlisted,
25
+ };
26
+ });
20
27
  }
package/src/types.ts CHANGED
@@ -27,6 +27,7 @@ export type SourceToPermalink = {
27
27
  export type VersionTag = Tag & {
28
28
  /** All doc ids having this tag. */
29
29
  docIds: string[];
30
+ unlisted: boolean;
30
31
  };
31
32
  export type VersionTags = {
32
33
  [permalink: string]: VersionTag;