mirror of
https://github.com/facebook/docusaurus.git
synced 2025-06-09 14:22:27 +02:00
Co-authored-by: Sébastien Lorber <slorber@users.noreply.github.com> Co-authored-by: OzakIOne <OzakIOne@users.noreply.github.com> Co-authored-by: sebastien <lorber.sebastien@gmail.com> Co-authored-by: slorber <slorber@users.noreply.github.com>
263 lines
7.1 KiB
TypeScript
263 lines
7.1 KiB
TypeScript
/**
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*/
|
|
|
|
import _ from 'lodash';
|
|
import logger from '@docusaurus/logger';
|
|
import {
|
|
docuHash,
|
|
normalizeUrl,
|
|
aliasedSitePathToRelativePath,
|
|
groupTaggedItems,
|
|
getTagVisibility,
|
|
} from '@docusaurus/utils';
|
|
import {
|
|
toTagDocListProp,
|
|
toTagsListTagsProp,
|
|
toVersionMetadataProp,
|
|
} from './props';
|
|
import type {
|
|
PluginContentLoadedActions,
|
|
RouteConfig,
|
|
RouteMetadata,
|
|
} from '@docusaurus/types';
|
|
import type {FullVersion, VersionTag, VersionTags} from './types';
|
|
import type {
|
|
CategoryGeneratedIndexMetadata,
|
|
DocMetadata,
|
|
PluginOptions,
|
|
} from '@docusaurus/plugin-content-docs';
|
|
|
|
function createDocRouteMetadata(docMeta: DocMetadata): RouteMetadata {
|
|
return {
|
|
sourceFilePath: aliasedSitePathToRelativePath(docMeta.source),
|
|
lastUpdatedAt: docMeta.lastUpdatedAt,
|
|
};
|
|
}
|
|
|
|
async function buildVersionCategoryGeneratedIndexRoutes({
|
|
version,
|
|
options,
|
|
}: BuildVersionRoutesParam): Promise<RouteConfig[]> {
|
|
async function buildCategoryGeneratedIndexRoute(
|
|
categoryGeneratedIndex: CategoryGeneratedIndexMetadata,
|
|
): Promise<RouteConfig> {
|
|
return {
|
|
path: categoryGeneratedIndex.permalink,
|
|
component: options.docCategoryGeneratedIndexComponent,
|
|
exact: true,
|
|
props: {
|
|
categoryGeneratedIndex,
|
|
},
|
|
// Same as doc, this sidebar route attribute permits to associate this
|
|
// subpage to the given sidebar
|
|
...(categoryGeneratedIndex.sidebar && {
|
|
sidebar: categoryGeneratedIndex.sidebar,
|
|
}),
|
|
};
|
|
}
|
|
|
|
return Promise.all(
|
|
version.categoryGeneratedIndices.map(buildCategoryGeneratedIndexRoute),
|
|
);
|
|
}
|
|
|
|
async function buildVersionDocRoutes({
|
|
version,
|
|
actions,
|
|
options,
|
|
}: BuildVersionRoutesParam): Promise<RouteConfig[]> {
|
|
return Promise.all(
|
|
version.docs.map(async (doc) => {
|
|
await actions.createData(
|
|
// Note that this created data path must be in sync with
|
|
// metadataPath provided to mdx-loader.
|
|
`${docuHash(doc.source)}.json`,
|
|
doc,
|
|
);
|
|
|
|
const docRoute: RouteConfig = {
|
|
path: doc.permalink,
|
|
component: options.docItemComponent,
|
|
exact: true,
|
|
modules: {
|
|
content: doc.source,
|
|
},
|
|
metadata: createDocRouteMetadata(doc),
|
|
// Because the parent (DocRoot) comp need to access it easily
|
|
// This permits to render the sidebar once without unmount/remount when
|
|
// navigating (and preserve sidebar state)
|
|
...(doc.sidebar && {
|
|
sidebar: doc.sidebar,
|
|
}),
|
|
};
|
|
|
|
return docRoute;
|
|
}),
|
|
);
|
|
}
|
|
|
|
async function buildVersionSidebarRoute(param: BuildVersionRoutesParam) {
|
|
const [docRoutes, categoryGeneratedIndexRoutes] = await Promise.all([
|
|
buildVersionDocRoutes(param),
|
|
buildVersionCategoryGeneratedIndexRoutes(param),
|
|
]);
|
|
const subRoutes = [...docRoutes, ...categoryGeneratedIndexRoutes];
|
|
return {
|
|
path: param.version.path,
|
|
exact: false,
|
|
component: param.options.docRootComponent,
|
|
routes: subRoutes,
|
|
};
|
|
}
|
|
function getVersionTags(docs: DocMetadata[]): VersionTags {
|
|
const groups = groupTaggedItems(docs, (doc) => doc.tags);
|
|
return _.mapValues(groups, ({tag, items: tagDocs}) => {
|
|
const tagVisibility = getTagVisibility({
|
|
items: tagDocs,
|
|
isUnlisted: (item) => item.unlisted,
|
|
});
|
|
return {
|
|
inline: tag.inline,
|
|
label: tag.label,
|
|
permalink: tag.permalink,
|
|
description: tag.description,
|
|
docIds: tagVisibility.listedItems.map((item) => item.id),
|
|
unlisted: tagVisibility.unlisted,
|
|
};
|
|
});
|
|
}
|
|
|
|
async function buildVersionTagsRoutes(
|
|
param: BuildVersionRoutesParam,
|
|
): Promise<RouteConfig[]> {
|
|
const {version, options} = param;
|
|
const versionTags = getVersionTags(version.docs);
|
|
|
|
async function buildTagsListRoute(): Promise<RouteConfig | null> {
|
|
const tags = toTagsListTagsProp(versionTags);
|
|
// Don't create a tags list page if there's no tag
|
|
if (tags.length === 0) {
|
|
return null;
|
|
}
|
|
return {
|
|
path: version.tagsPath,
|
|
exact: true,
|
|
component: options.docTagsListComponent,
|
|
props: {
|
|
tags,
|
|
},
|
|
};
|
|
}
|
|
|
|
async function buildTagDocListRoute(tag: VersionTag): Promise<RouteConfig> {
|
|
return {
|
|
path: tag.permalink,
|
|
component: options.docTagDocListComponent,
|
|
exact: true,
|
|
props: {
|
|
tag: toTagDocListProp({
|
|
allTagsPath: version.tagsPath,
|
|
tag,
|
|
docs: version.docs,
|
|
}),
|
|
},
|
|
};
|
|
}
|
|
|
|
const [tagsListRoute, allTagsDocListRoutes] = await Promise.all([
|
|
buildTagsListRoute(),
|
|
Promise.all(Object.values(versionTags).map(buildTagDocListRoute)),
|
|
]);
|
|
|
|
return _.compact([tagsListRoute, ...allTagsDocListRoutes]);
|
|
}
|
|
|
|
type BuildVersionRoutesParam = Omit<BuildAllRoutesParam, 'versions'> & {
|
|
version: FullVersion;
|
|
};
|
|
|
|
async function buildVersionRoutes(
|
|
param: BuildVersionRoutesParam,
|
|
): Promise<RouteConfig> {
|
|
const {version, options} = param;
|
|
|
|
async function buildVersionSubRoutes() {
|
|
const [sidebarRoute, tagsRoutes] = await Promise.all([
|
|
buildVersionSidebarRoute(param),
|
|
buildVersionTagsRoutes(param),
|
|
]);
|
|
|
|
return [sidebarRoute, ...tagsRoutes];
|
|
}
|
|
|
|
async function doBuildVersionRoutes(): Promise<RouteConfig> {
|
|
return {
|
|
path: version.path,
|
|
exact: false,
|
|
component: options.docVersionRootComponent,
|
|
routes: await buildVersionSubRoutes(),
|
|
props: {
|
|
// TODO Docusaurus v4 breaking change?
|
|
// expose version metadata as route context instead of props
|
|
version: toVersionMetadataProp(options.id, version),
|
|
},
|
|
priority: version.routePriority,
|
|
};
|
|
}
|
|
|
|
try {
|
|
return await doBuildVersionRoutes();
|
|
} catch (err) {
|
|
logger.error`Can't create version routes for version name=${version.versionName}`;
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
type BuildAllRoutesParam = Omit<CreateAllRoutesParam, 'actions'> & {
|
|
actions: Omit<PluginContentLoadedActions, 'addRoute' | 'setGlobalData'>;
|
|
};
|
|
|
|
// TODO we want this buildAllRoutes function to be easily testable
|
|
// Ideally, we should avoid side effects here (ie not injecting actions)
|
|
export async function buildAllRoutes(
|
|
param: BuildAllRoutesParam,
|
|
): Promise<RouteConfig[]> {
|
|
const subRoutes = await Promise.all(
|
|
param.versions.map((version) =>
|
|
buildVersionRoutes({
|
|
...param,
|
|
version,
|
|
}),
|
|
),
|
|
);
|
|
|
|
// all docs routes are wrapped under a single parent route, this ensures
|
|
// the theme layout never unmounts/remounts when navigating between versions
|
|
return [
|
|
{
|
|
path: normalizeUrl([param.baseUrl, param.options.routeBasePath]),
|
|
exact: false,
|
|
component: param.options.docsRootComponent,
|
|
routes: subRoutes,
|
|
},
|
|
];
|
|
}
|
|
|
|
type CreateAllRoutesParam = {
|
|
baseUrl: string;
|
|
versions: FullVersion[];
|
|
options: PluginOptions;
|
|
actions: PluginContentLoadedActions;
|
|
aliasedSource: (str: string) => string;
|
|
};
|
|
|
|
export async function createAllRoutes(
|
|
param: CreateAllRoutesParam,
|
|
): Promise<void> {
|
|
const routes = await buildAllRoutes(param);
|
|
routes.forEach(param.actions.addRoute);
|
|
}
|