mirror of
https://github.com/facebook/docusaurus.git
synced 2025-06-16 01:32:37 +02:00
feat(v2): global data + useGlobalData + docs versions dropdown (#2971)
* doc components initial simplification
* doc components initial simplification
* add docContext test
* Add poc of global data system + use it in the theme
* Revert "doc components initial simplification"
This reverts commit f657b4c4
* revert useless changes
* avoid loosing context on docs switch
* fix docs tests
* fix @generated/globalData ts declaration / es import
* typo
* revert bad commit
* refactor navbar in multiple parts + add navbar item types validation + try to fix remaining merge bugs
* add missing watch mode for plugin debug
* fix docs global data integration, move related hooks to docs plugin + convert to TS
* change versions link label
* fix activeClassName react warning
* improve docs global data system + contextual navbar dropdown
* fix bug preventing the deployment
* refactor the global data system to namespace automatically by plugin name + plugin id
* proper NavbarItem comp
* fix tests
* fix snapshot
* extract theme config schema in separate file + rename navbar links to navbar items
* minor typos
* polish docs components/api
* polish useDocs api surface
* fix the docs version suggestions comp + data
* refactors + add docsClientUtils unit tests
* Add documentation
* typo
* Add check for duplicate plugin ids detection
* multi-instance: createData plugin data should be namespaced by plugin instance id
* remove attempt for multi-instance support
This commit is contained in:
parent
a51a56ec42
commit
15e73daae7
53 changed files with 1954 additions and 531 deletions
|
@ -8,6 +8,7 @@
|
|||
import groupBy from 'lodash.groupby';
|
||||
import pick from 'lodash.pick';
|
||||
import pickBy from 'lodash.pickby';
|
||||
import sortBy from 'lodash.sortby';
|
||||
import globby from 'globby';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
|
@ -49,6 +50,10 @@ import {
|
|||
VersionToSidebars,
|
||||
SidebarItem,
|
||||
DocsSidebarItem,
|
||||
GlobalPluginData,
|
||||
DocsVersion,
|
||||
GlobalVersion,
|
||||
GlobalDoc,
|
||||
} from './types';
|
||||
import {Configuration} from 'webpack';
|
||||
import {docsVersion} from './version';
|
||||
|
@ -56,22 +61,6 @@ import {VERSIONS_JSON_FILE} from './constants';
|
|||
import {PluginOptionSchema} from './pluginOptionSchema';
|
||||
import {ValidationError} from '@hapi/joi';
|
||||
|
||||
function getFirstDocLinkOfSidebar(
|
||||
sidebarItems: DocsSidebarItem[],
|
||||
): string | null {
|
||||
for (const sidebarItem of sidebarItems) {
|
||||
if (sidebarItem.type === 'category') {
|
||||
const url = getFirstDocLinkOfSidebar(sidebarItem.items);
|
||||
if (url) {
|
||||
return url;
|
||||
}
|
||||
} else {
|
||||
return sidebarItem.href;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export default function pluginContentDocs(
|
||||
context: LoadContext,
|
||||
options: PluginOptions,
|
||||
|
@ -92,6 +81,7 @@ export default function pluginContentDocs(
|
|||
const dataDir = path.join(
|
||||
generatedFilesDir,
|
||||
'docusaurus-plugin-content-docs',
|
||||
// options.id ?? 'default', // TODO support multi-instance
|
||||
);
|
||||
|
||||
// Versioning.
|
||||
|
@ -329,11 +319,23 @@ Available document ids=
|
|||
}
|
||||
|
||||
const {docLayoutComponent, docItemComponent, routeBasePath} = options;
|
||||
const {addRoute, createData} = actions;
|
||||
const {addRoute, createData, setGlobalData} = actions;
|
||||
|
||||
const pluginInstanceGlobalData: GlobalPluginData = {
|
||||
path: options.path,
|
||||
latestVersionName: versioning.latestVersion,
|
||||
// Initialized empty, will be mutated
|
||||
versions: [],
|
||||
};
|
||||
|
||||
setGlobalData<GlobalPluginData>(pluginInstanceGlobalData);
|
||||
|
||||
const aliasedSource = (source: string) =>
|
||||
`~docs/${path.relative(dataDir, source)}`;
|
||||
|
||||
const createDocsBaseMetadata = (version?: string): DocsBaseMetadata => {
|
||||
const createDocsBaseMetadata = (
|
||||
version: DocsVersion,
|
||||
): DocsBaseMetadata => {
|
||||
const {docsSidebars, permalinkToSidebar, versionToSidebars} = content;
|
||||
const neededSidebars: Set<string> =
|
||||
versionToSidebars[version!] || new Set();
|
||||
|
@ -377,13 +379,19 @@ Available document ids=
|
|||
return routes.sort((a, b) => a.path.localeCompare(b.path));
|
||||
};
|
||||
|
||||
// We want latest version route to have lower priority
|
||||
// Otherwise `/docs/next/foo` would match
|
||||
// `/docs/:route` instead of `/docs/next/:route`.
|
||||
const getVersionRoutePriority = (version: DocsVersion) =>
|
||||
version === versioning.latestVersion ? -1 : undefined;
|
||||
|
||||
// This is the base route of the document root (for a doc given version)
|
||||
// (/docs, /docs/next, /docs/1.0 etc...)
|
||||
// The component applies the layout and renders the appropriate doc
|
||||
const addBaseRoute = async (
|
||||
const addVersionRoute = async (
|
||||
docsBasePath: string,
|
||||
docsBaseMetadata: DocsBaseMetadata,
|
||||
routes: RouteConfig[],
|
||||
docs: Metadata[],
|
||||
priority?: number,
|
||||
) => {
|
||||
const docsBaseMetadataPath = await createData(
|
||||
|
@ -391,18 +399,38 @@ Available document ids=
|
|||
JSON.stringify(docsBaseMetadata, null, 2),
|
||||
);
|
||||
|
||||
const docsRoutes = await genRoutes(docs);
|
||||
|
||||
const mainDoc: Metadata =
|
||||
docs.find((doc) => doc.unversionedId === options.homePageId) ??
|
||||
docs[0];
|
||||
|
||||
const toGlobalDataDoc = (doc: Metadata): GlobalDoc => ({
|
||||
id: doc.unversionedId,
|
||||
path: doc.permalink,
|
||||
});
|
||||
|
||||
pluginInstanceGlobalData.versions.push({
|
||||
name: docsBaseMetadata.version,
|
||||
path: docsBasePath,
|
||||
mainDocId: mainDoc.unversionedId,
|
||||
docs: docs
|
||||
.map(toGlobalDataDoc)
|
||||
// stable ordering, useful for tests
|
||||
.sort((a, b) => a.id.localeCompare(b.id)),
|
||||
});
|
||||
|
||||
addRoute({
|
||||
path: docsBasePath,
|
||||
exact: false, // allow matching /docs/* as well
|
||||
component: docLayoutComponent, // main docs component (DocPage)
|
||||
routes, // subroute for each doc
|
||||
routes: docsRoutes, // subroute for each doc
|
||||
modules: {
|
||||
docsMetadata: aliasedSource(docsBaseMetadataPath),
|
||||
},
|
||||
priority,
|
||||
});
|
||||
};
|
||||
|
||||
// If versioning is enabled, we cleverly chunk the generated routes
|
||||
// to be by version and pick only needed base metadata.
|
||||
if (versioning.enabled) {
|
||||
|
@ -410,27 +438,10 @@ Available document ids=
|
|||
Object.values(content.docsMetadata),
|
||||
'version',
|
||||
);
|
||||
const rootUrl =
|
||||
options.homePageId && content.docsMetadata[options.homePageId]
|
||||
? normalizeUrl([baseUrl, routeBasePath])
|
||||
: getFirstDocLinkOfSidebar(
|
||||
content.docsSidebars[
|
||||
`version-${versioning.latestVersion}/docs`
|
||||
],
|
||||
);
|
||||
if (!rootUrl) {
|
||||
throw new Error('Bad sidebars file. No document linked');
|
||||
}
|
||||
Object.values(content.docsMetadata).forEach((docMetadata) => {
|
||||
if (docMetadata.version !== versioning.latestVersion) {
|
||||
docMetadata.latestVersionMainDocPermalink = rootUrl;
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(
|
||||
Object.keys(docsMetadataByVersion).map(async (version) => {
|
||||
const routes: RouteConfig[] = await genRoutes(
|
||||
docsMetadataByVersion[version],
|
||||
);
|
||||
const docsMetadata = docsMetadataByVersion[version];
|
||||
|
||||
const isLatestVersion = version === versioning.latestVersion;
|
||||
const docsBaseRoute = normalizeUrl([
|
||||
|
@ -440,23 +451,29 @@ Available document ids=
|
|||
]);
|
||||
const docsBaseMetadata = createDocsBaseMetadata(version);
|
||||
|
||||
return addBaseRoute(
|
||||
await addVersionRoute(
|
||||
docsBaseRoute,
|
||||
docsBaseMetadata,
|
||||
routes,
|
||||
// We want latest version route config to be placed last in the
|
||||
// generated routeconfig. Otherwise, `/docs/next/foo` will match
|
||||
// `/docs/:route` instead of `/docs/next/:route`.
|
||||
isLatestVersion ? -1 : undefined,
|
||||
docsMetadata,
|
||||
getVersionRoutePriority(version),
|
||||
);
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
const routes = await genRoutes(Object.values(content.docsMetadata));
|
||||
const docsBaseMetadata = createDocsBaseMetadata();
|
||||
const docsMetadata = Object.values(content.docsMetadata);
|
||||
const docsBaseMetadata = createDocsBaseMetadata(null);
|
||||
const docsBaseRoute = normalizeUrl([baseUrl, routeBasePath]);
|
||||
await addBaseRoute(docsBaseRoute, docsBaseMetadata, routes);
|
||||
await addVersionRoute(docsBaseRoute, docsBaseMetadata, docsMetadata);
|
||||
}
|
||||
|
||||
// ensure version ordering on the global data (latest first)
|
||||
pluginInstanceGlobalData.versions = sortBy(
|
||||
pluginInstanceGlobalData.versions,
|
||||
(versionMetadata: GlobalVersion) => {
|
||||
const orderedVersionNames = ['next', ...versions];
|
||||
return orderedVersionNames.indexOf(versionMetadata.name!);
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
async routesLoaded(routes) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue