@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/routes.ts CHANGED
@@ -5,30 +5,32 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
 
8
+ import _ from 'lodash';
8
9
  import logger from '@docusaurus/logger';
9
- import {docuHash, createSlugger} from '@docusaurus/utils';
10
- import {toVersionMetadataProp} from './props';
10
+ import {docuHash, createSlugger, normalizeUrl} from '@docusaurus/utils';
11
+ import {
12
+ toTagDocListProp,
13
+ toTagsListTagsProp,
14
+ toVersionMetadataProp,
15
+ } from './props';
16
+ import {getVersionTags} from './tags';
11
17
  import type {PluginContentLoadedActions, RouteConfig} from '@docusaurus/types';
12
- import type {FullVersion} from './types';
18
+ import type {FullVersion, VersionTag} from './types';
13
19
  import type {
14
20
  CategoryGeneratedIndexMetadata,
15
- DocMetadata,
21
+ PluginOptions,
22
+ PropTagsListPage,
16
23
  } from '@docusaurus/plugin-content-docs';
17
24
 
18
- export async function createCategoryGeneratedIndexRoutes({
25
+ async function buildVersionCategoryGeneratedIndexRoutes({
19
26
  version,
20
27
  actions,
21
- docCategoryGeneratedIndexComponent,
28
+ options,
22
29
  aliasedSource,
23
- }: {
24
- version: FullVersion;
25
- actions: PluginContentLoadedActions;
26
- docCategoryGeneratedIndexComponent: string;
27
- aliasedSource: (str: string) => string;
28
- }): Promise<RouteConfig[]> {
30
+ }: BuildVersionRoutesParam): Promise<RouteConfig[]> {
29
31
  const slugs = createSlugger();
30
32
 
31
- async function createCategoryGeneratedIndexRoute(
33
+ async function buildCategoryGeneratedIndexRoute(
32
34
  categoryGeneratedIndex: CategoryGeneratedIndexMetadata,
33
35
  ): Promise<RouteConfig> {
34
36
  const {sidebar, ...prop} = categoryGeneratedIndex;
@@ -44,7 +46,7 @@ export async function createCategoryGeneratedIndexRoutes({
44
46
 
45
47
  return {
46
48
  path: categoryGeneratedIndex.permalink,
47
- component: docCategoryGeneratedIndexComponent,
49
+ component: options.docCategoryGeneratedIndexComponent,
48
50
  exact: true,
49
51
  modules: {
50
52
  categoryGeneratedIndex: aliasedSource(propData),
@@ -56,21 +58,17 @@ export async function createCategoryGeneratedIndexRoutes({
56
58
  }
57
59
 
58
60
  return Promise.all(
59
- version.categoryGeneratedIndices.map(createCategoryGeneratedIndexRoute),
61
+ version.categoryGeneratedIndices.map(buildCategoryGeneratedIndexRoute),
60
62
  );
61
63
  }
62
64
 
63
- export async function createDocRoutes({
64
- docs,
65
+ async function buildVersionDocRoutes({
66
+ version,
65
67
  actions,
66
- docItemComponent,
67
- }: {
68
- docs: DocMetadata[];
69
- actions: PluginContentLoadedActions;
70
- docItemComponent: string;
71
- }): Promise<RouteConfig[]> {
68
+ options,
69
+ }: BuildVersionRoutesParam): Promise<RouteConfig[]> {
72
70
  return Promise.all(
73
- docs.map(async (metadataItem) => {
71
+ version.docs.map(async (metadataItem) => {
74
72
  await actions.createData(
75
73
  // Note that this created data path must be in sync with
76
74
  // metadataPath provided to mdx-loader.
@@ -80,12 +78,12 @@ export async function createDocRoutes({
80
78
 
81
79
  const docRoute: RouteConfig = {
82
80
  path: metadataItem.permalink,
83
- component: docItemComponent,
81
+ component: options.docItemComponent,
84
82
  exact: true,
85
83
  modules: {
86
84
  content: metadataItem.source,
87
85
  },
88
- // Because the parent (DocPage) comp need to access it easily
86
+ // Because the parent (DocRoot) comp need to access it easily
89
87
  // This permits to render the sidebar once without unmount/remount when
90
88
  // navigating (and preserve sidebar state)
91
89
  ...(metadataItem.sidebar && {
@@ -98,62 +96,160 @@ export async function createDocRoutes({
98
96
  );
99
97
  }
100
98
 
101
- export async function createVersionRoutes({
102
- version,
103
- actions,
104
- docItemComponent,
105
- docLayoutComponent,
106
- docCategoryGeneratedIndexComponent,
107
- pluginId,
108
- aliasedSource,
109
- }: {
110
- version: FullVersion;
111
- actions: PluginContentLoadedActions;
112
- docLayoutComponent: string;
113
- docItemComponent: string;
114
- docCategoryGeneratedIndexComponent: string;
115
- pluginId: string;
116
- aliasedSource: (str: string) => string;
117
- }): Promise<void> {
118
- async function doCreateVersionRoutes(): Promise<void> {
119
- const versionMetadata = toVersionMetadataProp(pluginId, version);
120
- const versionMetadataPropPath = await actions.createData(
121
- `${docuHash(`version-${version.versionName}-metadata-prop`)}.json`,
122
- JSON.stringify(versionMetadata, null, 2),
123
- );
99
+ async function buildVersionSidebarRoute(param: BuildVersionRoutesParam) {
100
+ const [docRoutes, categoryGeneratedIndexRoutes] = await Promise.all([
101
+ buildVersionDocRoutes(param),
102
+ buildVersionCategoryGeneratedIndexRoutes(param),
103
+ ]);
104
+ const subRoutes = [...docRoutes, ...categoryGeneratedIndexRoutes];
105
+ return {
106
+ path: param.version.path,
107
+ exact: false,
108
+ component: param.options.docRootComponent,
109
+ routes: subRoutes,
110
+ };
111
+ }
124
112
 
125
- async function createVersionSubRoutes() {
126
- const [docRoutes, sidebarsRoutes] = await Promise.all([
127
- createDocRoutes({docs: version.docs, actions, docItemComponent}),
128
- createCategoryGeneratedIndexRoutes({
129
- version,
130
- actions,
131
- docCategoryGeneratedIndexComponent,
132
- aliasedSource,
133
- }),
134
- ]);
113
+ async function buildVersionTagsRoutes(
114
+ param: BuildVersionRoutesParam,
115
+ ): Promise<RouteConfig[]> {
116
+ const {version, options, actions, aliasedSource} = param;
117
+ const versionTags = getVersionTags(version.docs);
135
118
 
136
- const routes = [...docRoutes, ...sidebarsRoutes];
137
- return routes.sort((a, b) => a.path.localeCompare(b.path));
119
+ async function buildTagsListRoute(): Promise<RouteConfig | null> {
120
+ // Don't create a tags list page if there's no tag
121
+ if (Object.keys(versionTags).length === 0) {
122
+ return null;
138
123
  }
124
+ const tagsProp: PropTagsListPage['tags'] = toTagsListTagsProp(versionTags);
125
+ const tagsPropPath = await actions.createData(
126
+ `${docuHash(`tags-list-${version.versionName}-prop`)}.json`,
127
+ JSON.stringify(tagsProp, null, 2),
128
+ );
129
+ return {
130
+ path: version.tagsPath,
131
+ exact: true,
132
+ component: options.docTagsListComponent,
133
+ modules: {
134
+ tags: aliasedSource(tagsPropPath),
135
+ },
136
+ };
137
+ }
138
+
139
+ async function buildTagDocListRoute(tag: VersionTag): Promise<RouteConfig> {
140
+ const tagProps = toTagDocListProp({
141
+ allTagsPath: version.tagsPath,
142
+ tag,
143
+ docs: version.docs,
144
+ });
145
+ const tagPropPath = await actions.createData(
146
+ `${docuHash(`tag-${tag.permalink}`)}.json`,
147
+ JSON.stringify(tagProps, null, 2),
148
+ );
149
+ return {
150
+ path: tag.permalink,
151
+ component: options.docTagDocListComponent,
152
+ exact: true,
153
+ modules: {
154
+ tag: aliasedSource(tagPropPath),
155
+ },
156
+ };
157
+ }
158
+
159
+ const [tagsListRoute, allTagsDocListRoutes] = await Promise.all([
160
+ buildTagsListRoute(),
161
+ Promise.all(Object.values(versionTags).map(buildTagDocListRoute)),
162
+ ]);
163
+
164
+ return _.compact([tagsListRoute, ...allTagsDocListRoutes]);
165
+ }
166
+
167
+ type BuildVersionRoutesParam = Omit<BuildAllRoutesParam, 'versions'> & {
168
+ version: FullVersion;
169
+ };
170
+
171
+ async function buildVersionRoutes(
172
+ param: BuildVersionRoutesParam,
173
+ ): Promise<RouteConfig> {
174
+ const {version, actions, options, aliasedSource} = param;
139
175
 
140
- actions.addRoute({
176
+ async function buildVersionSubRoutes() {
177
+ const [sidebarRoute, tagsRoutes] = await Promise.all([
178
+ buildVersionSidebarRoute(param),
179
+ buildVersionTagsRoutes(param),
180
+ ]);
181
+
182
+ return [sidebarRoute, ...tagsRoutes];
183
+ }
184
+
185
+ async function doBuildVersionRoutes(): Promise<RouteConfig> {
186
+ const versionProp = toVersionMetadataProp(options.id, version);
187
+ const versionPropPath = await actions.createData(
188
+ `${docuHash(`version-${version.versionName}-metadata-prop`)}.json`,
189
+ JSON.stringify(versionProp, null, 2),
190
+ );
191
+ const subRoutes = await buildVersionSubRoutes();
192
+ return {
141
193
  path: version.path,
142
- // Allow matching /docs/* since this is the wrapping route
143
194
  exact: false,
144
- component: docLayoutComponent,
145
- routes: await createVersionSubRoutes(),
195
+ component: options.docVersionRootComponent,
196
+ routes: subRoutes,
146
197
  modules: {
147
- versionMetadata: aliasedSource(versionMetadataPropPath),
198
+ version: aliasedSource(versionPropPath),
148
199
  },
149
200
  priority: version.routePriority,
150
- });
201
+ };
151
202
  }
152
203
 
153
204
  try {
154
- return await doCreateVersionRoutes();
205
+ return await doBuildVersionRoutes();
155
206
  } catch (err) {
156
207
  logger.error`Can't create version routes for version name=${version.versionName}`;
157
208
  throw err;
158
209
  }
159
210
  }
211
+
212
+ type BuildAllRoutesParam = Omit<CreateAllRoutesParam, 'actions'> & {
213
+ actions: Omit<PluginContentLoadedActions, 'addRoute' | 'setGlobalData'>;
214
+ };
215
+
216
+ // TODO we want this buildAllRoutes function to be easily testable
217
+ // Ideally, we should avoid side effects here (ie not injecting actions)
218
+ export async function buildAllRoutes(
219
+ param: BuildAllRoutesParam,
220
+ ): Promise<RouteConfig[]> {
221
+ const subRoutes = await Promise.all(
222
+ param.versions.map((version) =>
223
+ buildVersionRoutes({
224
+ ...param,
225
+ version,
226
+ }),
227
+ ),
228
+ );
229
+
230
+ // all docs routes are wrapped under a single parent route, this ensures
231
+ // the theme layout never unmounts/remounts when navigating between versions
232
+ return [
233
+ {
234
+ path: normalizeUrl([param.baseUrl, param.options.routeBasePath]),
235
+ exact: false,
236
+ component: param.options.docsRootComponent,
237
+ routes: subRoutes,
238
+ },
239
+ ];
240
+ }
241
+
242
+ type CreateAllRoutesParam = {
243
+ baseUrl: string;
244
+ versions: FullVersion[];
245
+ options: PluginOptions;
246
+ actions: PluginContentLoadedActions;
247
+ aliasedSource: (str: string) => string;
248
+ };
249
+
250
+ export async function createAllRoutes(
251
+ param: CreateAllRoutesParam,
252
+ ): Promise<void> {
253
+ const routes = await buildAllRoutes(param);
254
+ routes.forEach(param.actions.addRoute);
255
+ }
@@ -185,11 +185,18 @@ export type PropSidebarItemCategory = Expand<
185
185
  SidebarItemCategoryBase & {
186
186
  items: PropSidebarItem[];
187
187
  href?: string;
188
+
189
+ // Weird name => it would have been more convenient to have link.unlisted
190
+ // Note it is the category link that is unlisted, not the category itself
191
+ // We want to prevent users from clicking on an unlisted category link
192
+ // We can't use "href: undefined" otherwise sidebar item is not highlighted
193
+ linkUnlisted?: boolean;
188
194
  }
189
195
  >;
190
196
 
191
197
  export type PropSidebarItemLink = SidebarItemLink & {
192
198
  docId?: string;
199
+ unlisted?: boolean;
193
200
  };
194
201
 
195
202
  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;
@@ -14,12 +14,16 @@ import {
14
14
  getVersionMetadataPaths,
15
15
  readVersionNames,
16
16
  } from './files';
17
+ import {createSidebarsUtils} from '../sidebars/utils';
18
+ import {getCategoryGeneratedIndexMetadataList} from '../categoryGeneratedIndex';
19
+ import type {FullVersion} from '../types';
20
+ import type {LoadContext} from '@docusaurus/types';
17
21
  import type {
22
+ LoadedVersion,
18
23
  PluginOptions,
19
24
  VersionBanner,
20
25
  VersionMetadata,
21
26
  } from '@docusaurus/plugin-content-docs';
22
- import type {LoadContext} from '@docusaurus/types';
23
27
 
24
28
  export type VersionContext = {
25
29
  /** The version name to get banner of. */
@@ -252,3 +256,15 @@ export async function readVersionsMetadata({
252
256
  );
253
257
  return versionsMetadata;
254
258
  }
259
+
260
+ export function toFullVersion(version: LoadedVersion): FullVersion {
261
+ const sidebarsUtils = createSidebarsUtils(version.sidebars);
262
+ return {
263
+ ...version,
264
+ sidebarsUtils,
265
+ categoryGeneratedIndices: getCategoryGeneratedIndexMetadataList({
266
+ docs: version.docs,
267
+ sidebarsUtils,
268
+ }),
269
+ };
270
+ }