mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-10 07:37:19 +02:00
fix(v2): fix docs homepage permalink issues (#2905)
* better fixes for docs homepage * fix tests * create special route for docs homepage + cleanup existing code * no need to create multiple docs parent paths * useful comment * add test for slug + doc home usage at the same time error * remove confusing variable name * fix tests by using same suffix as before for docs base metadata path * metadata: use homePageId correctly for nested docs: the full docId (including /) should be used to compare against homePageId * add folder/testNested test doc * refactor a bit processMetadata, the home should be handled correctly for all versions * Workaround to fix issue when parent layout route (DocPage) has same path as the child route (DocItem): see https://github.com/facebook/docusaurus/issues/2917 * revert homePageId * remove test doc * remove test doc * add useful comment
This commit is contained in:
parent
a3f54d747d
commit
f6b1c85b01
10 changed files with 264 additions and 218 deletions
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
slug: docWithSlug.html
|
||||
---
|
||||
|
||||
Lorem
|
|
@ -29,7 +29,7 @@ Object {
|
|||
"type": "link",
|
||||
},
|
||||
Object {
|
||||
"href": "/docs",
|
||||
"href": "/docs/",
|
||||
"label": "Hello, World !",
|
||||
"type": "link",
|
||||
},
|
||||
|
@ -41,7 +41,7 @@ Object {
|
|||
"collapsed": true,
|
||||
"items": Array [
|
||||
Object {
|
||||
"href": "/docs",
|
||||
"href": "/docs/",
|
||||
"label": "Hello, World !",
|
||||
"type": "link",
|
||||
},
|
||||
|
@ -57,21 +57,21 @@ exports[`simple website content 2`] = `
|
|||
Array [
|
||||
Object {
|
||||
"component": "@theme/DocPage",
|
||||
"exact": true,
|
||||
"modules": Object {
|
||||
"content": "@site/docs/hello.md",
|
||||
"docsMetadata": "~docs/site-docs-hello-md-9df-base.json",
|
||||
},
|
||||
"path": "/docs",
|
||||
},
|
||||
Object {
|
||||
"component": "@theme/DocPage",
|
||||
"exact": false,
|
||||
"modules": Object {
|
||||
"docsMetadata": "~docs/docs-route-ff2.json",
|
||||
},
|
||||
"path": "/docs/:route",
|
||||
"path": "/docs",
|
||||
"priority": undefined,
|
||||
"routes": Array [
|
||||
Object {
|
||||
"component": "@theme/DocItem",
|
||||
"exact": true,
|
||||
"modules": Object {
|
||||
"content": "@site/docs/hello.md",
|
||||
},
|
||||
"path": "/docs/",
|
||||
},
|
||||
Object {
|
||||
"component": "@theme/DocItem",
|
||||
"exact": true,
|
||||
|
@ -123,39 +123,21 @@ exports[`versioned website content 1`] = `
|
|||
Array [
|
||||
Object {
|
||||
"component": "@theme/DocPage",
|
||||
"exact": true,
|
||||
"modules": Object {
|
||||
"content": "@site/versioned_docs/version-1.0.1/hello.md",
|
||||
"docsMetadata": "~docs/site-versioned-docs-version-1-0-1-hello-md-0c7-base.json",
|
||||
},
|
||||
"path": "/docs",
|
||||
},
|
||||
Object {
|
||||
"component": "@theme/DocPage",
|
||||
"exact": true,
|
||||
"modules": Object {
|
||||
"content": "@site/versioned_docs/version-1.0.0/hello.md",
|
||||
"docsMetadata": "~docs/site-versioned-docs-version-1-0-0-hello-md-3ef-base.json",
|
||||
},
|
||||
"path": "/docs/1.0.0",
|
||||
},
|
||||
Object {
|
||||
"component": "@theme/DocPage",
|
||||
"exact": true,
|
||||
"modules": Object {
|
||||
"content": "@site/docs/hello.md",
|
||||
"docsMetadata": "~docs/site-docs-hello-md-9df-base.json",
|
||||
},
|
||||
"path": "/docs/next",
|
||||
},
|
||||
Object {
|
||||
"component": "@theme/DocPage",
|
||||
"exact": false,
|
||||
"modules": Object {
|
||||
"docsMetadata": "~docs/docs-1-0-0-route-660.json",
|
||||
},
|
||||
"path": "/docs/1.0.0/:route",
|
||||
"path": "/docs/1.0.0",
|
||||
"priority": undefined,
|
||||
"routes": Array [
|
||||
Object {
|
||||
"component": "@theme/DocItem",
|
||||
"exact": true,
|
||||
"modules": Object {
|
||||
"content": "@site/versioned_docs/version-1.0.0/hello.md",
|
||||
},
|
||||
"path": "/docs/1.0.0/",
|
||||
},
|
||||
Object {
|
||||
"component": "@theme/DocItem",
|
||||
"exact": true,
|
||||
|
@ -176,12 +158,21 @@ Array [
|
|||
},
|
||||
Object {
|
||||
"component": "@theme/DocPage",
|
||||
"exact": false,
|
||||
"modules": Object {
|
||||
"docsMetadata": "~docs/docs-next-route-1c8.json",
|
||||
},
|
||||
"path": "/docs/next/:route",
|
||||
"path": "/docs/next",
|
||||
"priority": undefined,
|
||||
"routes": Array [
|
||||
Object {
|
||||
"component": "@theme/DocItem",
|
||||
"exact": true,
|
||||
"modules": Object {
|
||||
"content": "@site/docs/hello.md",
|
||||
},
|
||||
"path": "/docs/next/",
|
||||
},
|
||||
Object {
|
||||
"component": "@theme/DocItem",
|
||||
"exact": true,
|
||||
|
@ -194,12 +185,21 @@ Array [
|
|||
},
|
||||
Object {
|
||||
"component": "@theme/DocPage",
|
||||
"exact": false,
|
||||
"modules": Object {
|
||||
"docsMetadata": "~docs/docs-route-ff2.json",
|
||||
},
|
||||
"path": "/docs/:route",
|
||||
"path": "/docs",
|
||||
"priority": -1,
|
||||
"routes": Array [
|
||||
Object {
|
||||
"component": "@theme/DocItem",
|
||||
"exact": true,
|
||||
"modules": Object {
|
||||
"content": "@site/versioned_docs/version-1.0.1/hello.md",
|
||||
},
|
||||
"path": "/docs/",
|
||||
},
|
||||
Object {
|
||||
"component": "@theme/DocItem",
|
||||
"exact": true,
|
||||
|
@ -232,7 +232,7 @@ Object {
|
|||
"collapsed": true,
|
||||
"items": Array [
|
||||
Object {
|
||||
"href": "/docs/next",
|
||||
"href": "/docs/next/",
|
||||
"label": "hello",
|
||||
"type": "link",
|
||||
},
|
||||
|
@ -263,7 +263,7 @@ Object {
|
|||
"collapsed": true,
|
||||
"items": Array [
|
||||
Object {
|
||||
"href": "/docs/1.0.0",
|
||||
"href": "/docs/1.0.0/",
|
||||
"label": "hello",
|
||||
"type": "link",
|
||||
},
|
||||
|
@ -289,7 +289,7 @@ Object {
|
|||
"collapsed": true,
|
||||
"items": Array [
|
||||
Object {
|
||||
"href": "/docs",
|
||||
"href": "/docs/",
|
||||
"label": "hello",
|
||||
"type": "link",
|
||||
},
|
||||
|
@ -326,7 +326,7 @@ Object {
|
|||
"collapsed": true,
|
||||
"items": Array [
|
||||
Object {
|
||||
"href": "/docs/1.0.0",
|
||||
"href": "/docs/1.0.0/",
|
||||
"label": "hello",
|
||||
"type": "link",
|
||||
},
|
||||
|
@ -337,9 +337,9 @@ Object {
|
|||
],
|
||||
},
|
||||
"permalinkToSidebar": Object {
|
||||
"/docs/1.0.0/": "version-1.0.0/docs",
|
||||
"/docs/1.0.0/foo/barSlug": "version-1.0.0/docs",
|
||||
"/docs/1.0.0/foo/baz": "version-1.0.0/docs",
|
||||
"/docs/1.0.0/hello": "version-1.0.0/docs",
|
||||
},
|
||||
"version": "1.0.0",
|
||||
}
|
||||
|
@ -365,7 +365,7 @@ Object {
|
|||
"collapsed": true,
|
||||
"items": Array [
|
||||
Object {
|
||||
"href": "/docs",
|
||||
"href": "/docs/",
|
||||
"label": "hello",
|
||||
"type": "link",
|
||||
},
|
||||
|
@ -376,8 +376,8 @@ Object {
|
|||
],
|
||||
},
|
||||
"permalinkToSidebar": Object {
|
||||
"/docs/": "version-1.0.1/docs",
|
||||
"/docs/foo/bar": "version-1.0.1/docs",
|
||||
"/docs/hello": "version-1.0.1/docs",
|
||||
},
|
||||
"version": "1.0.1",
|
||||
}
|
||||
|
@ -403,7 +403,7 @@ Object {
|
|||
"collapsed": true,
|
||||
"items": Array [
|
||||
Object {
|
||||
"href": "/docs/next",
|
||||
"href": "/docs/next/",
|
||||
"label": "hello",
|
||||
"type": "link",
|
||||
},
|
||||
|
@ -414,8 +414,8 @@ Object {
|
|||
],
|
||||
},
|
||||
"permalinkToSidebar": Object {
|
||||
"/docs/next/": "docs",
|
||||
"/docs/next/foo/barSlug": "docs",
|
||||
"/docs/next/hello": "docs",
|
||||
},
|
||||
"version": "next",
|
||||
}
|
||||
|
|
|
@ -154,7 +154,8 @@ describe('simple website', () => {
|
|||
expect(versionToSidebars).toEqual({});
|
||||
expect(docsMetadata.hello).toEqual({
|
||||
id: 'hello',
|
||||
permalink: '/docs/hello',
|
||||
isDocsHomePage: true,
|
||||
permalink: '/docs/',
|
||||
previous: {
|
||||
title: 'baz',
|
||||
permalink: '/docs/foo/bazSlug.html',
|
||||
|
@ -168,6 +169,7 @@ describe('simple website', () => {
|
|||
|
||||
expect(docsMetadata['foo/bar']).toEqual({
|
||||
id: 'foo/bar',
|
||||
isDocsHomePage: false,
|
||||
next: {
|
||||
title: 'baz',
|
||||
permalink: '/docs/foo/bazSlug.html',
|
||||
|
@ -296,6 +298,7 @@ describe('versioned website', () => {
|
|||
expect(docsMetadata['version-1.0.1/foo/baz']).toBeUndefined();
|
||||
expect(docsMetadata['foo/bar']).toEqual({
|
||||
id: 'foo/bar',
|
||||
isDocsHomePage: false,
|
||||
permalink: '/docs/next/foo/barSlug',
|
||||
source: path.join('@site', routeBasePath, 'foo', 'bar.md'),
|
||||
title: 'bar',
|
||||
|
@ -304,12 +307,13 @@ describe('versioned website', () => {
|
|||
sidebar: 'docs',
|
||||
next: {
|
||||
title: 'hello',
|
||||
permalink: '/docs/next/hello',
|
||||
permalink: '/docs/next/',
|
||||
},
|
||||
});
|
||||
expect(docsMetadata['hello']).toEqual({
|
||||
id: 'hello',
|
||||
permalink: '/docs/next/hello',
|
||||
isDocsHomePage: true,
|
||||
permalink: '/docs/next/',
|
||||
source: path.join('@site', routeBasePath, 'hello.md'),
|
||||
title: 'hello',
|
||||
description: 'Hello next !',
|
||||
|
@ -322,7 +326,8 @@ describe('versioned website', () => {
|
|||
});
|
||||
expect(docsMetadata['version-1.0.1/hello']).toEqual({
|
||||
id: 'version-1.0.1/hello',
|
||||
permalink: '/docs/hello',
|
||||
isDocsHomePage: true,
|
||||
permalink: '/docs/',
|
||||
source: path.join(
|
||||
'@site',
|
||||
path.relative(siteDir, versionedDir),
|
||||
|
@ -341,6 +346,7 @@ describe('versioned website', () => {
|
|||
});
|
||||
expect(docsMetadata['version-1.0.0/foo/baz']).toEqual({
|
||||
id: 'version-1.0.0/foo/baz',
|
||||
isDocsHomePage: false,
|
||||
permalink: '/docs/1.0.0/foo/baz',
|
||||
source: path.join(
|
||||
'@site',
|
||||
|
@ -356,7 +362,7 @@ describe('versioned website', () => {
|
|||
sidebar: 'version-1.0.0/docs',
|
||||
next: {
|
||||
title: 'hello',
|
||||
permalink: '/docs/1.0.0/hello',
|
||||
permalink: '/docs/1.0.0/',
|
||||
},
|
||||
previous: {
|
||||
title: 'bar',
|
||||
|
|
|
@ -46,6 +46,7 @@ describe('simple site', () => {
|
|||
|
||||
expect(dataA).toEqual({
|
||||
id: 'foo/bar',
|
||||
isDocsHomePage: false,
|
||||
permalink: '/docs/foo/bar',
|
||||
source: path.join('@site', routeBasePath, sourceA),
|
||||
title: 'Bar',
|
||||
|
@ -54,6 +55,7 @@ describe('simple site', () => {
|
|||
});
|
||||
expect(dataB).toEqual({
|
||||
id: 'hello',
|
||||
isDocsHomePage: false,
|
||||
permalink: '/docs/hello',
|
||||
source: path.join('@site', routeBasePath, sourceB),
|
||||
title: 'Hello, World !',
|
||||
|
@ -62,6 +64,56 @@ describe('simple site', () => {
|
|||
});
|
||||
});
|
||||
|
||||
test('homePageId doc', async () => {
|
||||
const source = path.join('hello.md');
|
||||
const options = {
|
||||
routeBasePath,
|
||||
homePageId: 'hello',
|
||||
};
|
||||
|
||||
const data = await processMetadata({
|
||||
source,
|
||||
refDir: docsDir,
|
||||
context,
|
||||
options,
|
||||
env,
|
||||
});
|
||||
|
||||
expect(data).toEqual({
|
||||
id: 'hello',
|
||||
isDocsHomePage: true,
|
||||
permalink: '/docs/',
|
||||
source: path.join('@site', routeBasePath, source),
|
||||
title: 'Hello, World !',
|
||||
description: `Hi, Endilie here :)`,
|
||||
});
|
||||
});
|
||||
|
||||
test('homePageId doc nested', async () => {
|
||||
const source = path.join('foo', 'bar.md');
|
||||
const options = {
|
||||
routeBasePath,
|
||||
homePageId: 'foo/bar',
|
||||
};
|
||||
|
||||
const data = await processMetadata({
|
||||
source,
|
||||
refDir: docsDir,
|
||||
context,
|
||||
options,
|
||||
env,
|
||||
});
|
||||
|
||||
expect(data).toEqual({
|
||||
id: 'foo/bar',
|
||||
isDocsHomePage: true,
|
||||
permalink: '/docs/',
|
||||
source: path.join('@site', routeBasePath, source),
|
||||
title: 'Bar',
|
||||
description: 'This is custom description',
|
||||
});
|
||||
});
|
||||
|
||||
test('docs with editUrl', async () => {
|
||||
const editUrl =
|
||||
'https://github.com/facebook/docusaurus/edit/master/website';
|
||||
|
@ -81,6 +133,7 @@ describe('simple site', () => {
|
|||
|
||||
expect(data).toEqual({
|
||||
id: 'foo/baz',
|
||||
isDocsHomePage: false,
|
||||
permalink: '/docs/foo/bazSlug.html',
|
||||
source: path.join('@site', routeBasePath, source),
|
||||
title: 'baz',
|
||||
|
@ -107,6 +160,7 @@ describe('simple site', () => {
|
|||
|
||||
expect(data).toEqual({
|
||||
id: 'lorem',
|
||||
isDocsHomePage: false,
|
||||
permalink: '/docs/lorem',
|
||||
source: path.join('@site', routeBasePath, source),
|
||||
title: 'lorem',
|
||||
|
@ -137,6 +191,7 @@ describe('simple site', () => {
|
|||
|
||||
expect(data).toEqual({
|
||||
id: 'lorem',
|
||||
isDocsHomePage: false,
|
||||
permalink: '/docs/lorem',
|
||||
source: path.join('@site', routeBasePath, source),
|
||||
title: 'lorem',
|
||||
|
@ -166,6 +221,7 @@ describe('simple site', () => {
|
|||
|
||||
expect(data).toEqual({
|
||||
id: 'ipsum',
|
||||
isDocsHomePage: false,
|
||||
permalink: '/docs/ipsum',
|
||||
source: path.join('@site', routeBasePath, source),
|
||||
title: 'ipsum',
|
||||
|
@ -214,6 +270,26 @@ describe('simple site', () => {
|
|||
`"Document slug cannot include \\"/\\"."`,
|
||||
);
|
||||
});
|
||||
|
||||
test('docs with slug on doc home', async () => {
|
||||
const badSiteDir = path.join(fixtureDir, 'bad-slug-on-doc-home-site');
|
||||
const options = {
|
||||
routeBasePath,
|
||||
homePageId: 'docWithSlug',
|
||||
};
|
||||
|
||||
await expect(
|
||||
processMetadata({
|
||||
source: 'docWithSlug.md',
|
||||
refDir: path.join(badSiteDir, 'docs'),
|
||||
context,
|
||||
options,
|
||||
env,
|
||||
}),
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"The docs homepage (homePageId=docWithSlug) is not allowed to have a frontmatter slug=docWithSlug.html => you have to chooser either homePageId or slug, not both"`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('versioned site', () => {
|
||||
|
@ -250,6 +326,7 @@ describe('versioned site', () => {
|
|||
|
||||
expect(dataA).toEqual({
|
||||
id: 'foo/bar',
|
||||
isDocsHomePage: false,
|
||||
permalink: '/docs/next/foo/barSlug',
|
||||
source: path.join('@site', routeBasePath, sourceA),
|
||||
title: 'bar',
|
||||
|
@ -258,6 +335,7 @@ describe('versioned site', () => {
|
|||
});
|
||||
expect(dataB).toEqual({
|
||||
id: 'hello',
|
||||
isDocsHomePage: false,
|
||||
permalink: '/docs/next/hello',
|
||||
source: path.join('@site', routeBasePath, sourceB),
|
||||
title: 'hello',
|
||||
|
@ -308,6 +386,7 @@ describe('versioned site', () => {
|
|||
|
||||
expect(dataA).toEqual({
|
||||
id: 'version-1.0.0/foo/bar',
|
||||
isDocsHomePage: false,
|
||||
permalink: '/docs/1.0.0/foo/barSlug',
|
||||
source: path.join('@site', path.relative(siteDir, versionedDir), sourceA),
|
||||
title: 'bar',
|
||||
|
@ -316,6 +395,7 @@ describe('versioned site', () => {
|
|||
});
|
||||
expect(dataB).toEqual({
|
||||
id: 'version-1.0.0/hello',
|
||||
isDocsHomePage: false,
|
||||
permalink: '/docs/1.0.0/hello',
|
||||
source: path.join('@site', path.relative(siteDir, versionedDir), sourceB),
|
||||
title: 'hello',
|
||||
|
@ -324,6 +404,7 @@ describe('versioned site', () => {
|
|||
});
|
||||
expect(dataC).toEqual({
|
||||
id: 'version-1.0.1/foo/bar',
|
||||
isDocsHomePage: false,
|
||||
permalink: '/docs/foo/bar',
|
||||
source: path.join('@site', path.relative(siteDir, versionedDir), sourceC),
|
||||
title: 'bar',
|
||||
|
@ -332,6 +413,7 @@ describe('versioned site', () => {
|
|||
});
|
||||
expect(dataD).toEqual({
|
||||
id: 'version-1.0.1/hello',
|
||||
isDocsHomePage: false,
|
||||
permalink: '/docs/hello',
|
||||
source: path.join('@site', path.relative(siteDir, versionedDir), sourceD),
|
||||
title: 'hello',
|
||||
|
|
|
@ -85,7 +85,9 @@ export default function pluginContentDocs(
|
|||
context: LoadContext,
|
||||
opts: Partial<PluginOptions>,
|
||||
): Plugin<LoadedContent | null> {
|
||||
const options = {...DEFAULT_OPTIONS, ...opts};
|
||||
const options: PluginOptions = {...DEFAULT_OPTIONS, ...opts};
|
||||
const homePageDocsRoutePath =
|
||||
options.routeBasePath === '' ? '/' : options.routeBasePath;
|
||||
|
||||
if (options.admonitions) {
|
||||
options.remarkPlugins = options.remarkPlugins.concat([
|
||||
|
@ -112,24 +114,6 @@ export default function pluginContentDocs(
|
|||
} = versioning;
|
||||
const versionsNames = versions.map((version) => `version-${version}`);
|
||||
|
||||
// Docs home page.
|
||||
const homePageDocsRoutePath =
|
||||
options.routeBasePath === '' ? '/' : options.routeBasePath;
|
||||
const isDocsHomePagePath = (permalink: string) => {
|
||||
const documentIdMatch = new RegExp(
|
||||
`^\/(?:${homePageDocsRoutePath}\/)?(?:(?:${versions.join(
|
||||
'|',
|
||||
)}|next)\/)?(.*)`,
|
||||
'i',
|
||||
).exec(permalink);
|
||||
|
||||
if (documentIdMatch) {
|
||||
return documentIdMatch[1] === options.homePageId;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
return {
|
||||
name: 'docusaurus-plugin-content-docs',
|
||||
|
||||
|
@ -307,9 +291,7 @@ Available document ids=
|
|||
return {
|
||||
type: 'link',
|
||||
label: sidebar_label || title,
|
||||
href: isDocsHomePagePath(permalink)
|
||||
? permalink.replace(`/${options.homePageId}`, '')
|
||||
: permalink,
|
||||
href: permalink,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -376,50 +358,8 @@ Available document ids=
|
|||
const genRoutes = async (
|
||||
metadataItems: Metadata[],
|
||||
): Promise<RouteConfig[]> => {
|
||||
const versionsRegex = new RegExp(versionsNames.join('|'), 'i');
|
||||
|
||||
const routes = await Promise.all(
|
||||
metadataItems.map(async (metadataItem) => {
|
||||
const isDocsHomePage =
|
||||
metadataItem.id.replace(versionsRegex, '').replace(/^\//, '') ===
|
||||
options.homePageId;
|
||||
if (isDocsHomePage) {
|
||||
const versionDocsPathPrefix =
|
||||
(metadataItem?.version === versioning.latestVersion
|
||||
? ''
|
||||
: metadataItem.version!) ?? '';
|
||||
|
||||
const docsBaseMetadata = createDocsBaseMetadata(
|
||||
metadataItem.version!,
|
||||
);
|
||||
docsBaseMetadata.isHomePage = true;
|
||||
docsBaseMetadata.homePagePath = normalizeUrl([
|
||||
baseUrl,
|
||||
homePageDocsRoutePath,
|
||||
versionDocsPathPrefix,
|
||||
]);
|
||||
|
||||
const docsBaseMetadataPath = await createData(
|
||||
`${docuHash(metadataItem.source)}-base.json`,
|
||||
JSON.stringify(docsBaseMetadata, null, 2),
|
||||
);
|
||||
|
||||
// Add a route for docs home page.
|
||||
addRoute({
|
||||
path: normalizeUrl([
|
||||
baseUrl,
|
||||
homePageDocsRoutePath,
|
||||
versionDocsPathPrefix,
|
||||
]),
|
||||
component: docLayoutComponent,
|
||||
exact: true,
|
||||
modules: {
|
||||
docsMetadata: aliasedSource(docsBaseMetadataPath),
|
||||
content: metadataItem.source,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
await createData(
|
||||
// Note that this created data path must be in sync with
|
||||
// metadataPath provided to mdx-loader.
|
||||
|
@ -438,15 +378,14 @@ Available document ids=
|
|||
}),
|
||||
);
|
||||
|
||||
return (
|
||||
routes
|
||||
// Do not create a route for a document serve as docs home page.
|
||||
// TODO: need way to do this filtering when generating routes for better perf.
|
||||
.filter(({path}) => !isDocsHomePagePath(path))
|
||||
.sort((a, b) => (a.path > b.path ? 1 : b.path > a.path ? -1 : 0))
|
||||
return routes.sort((a, b) =>
|
||||
a.path > b.path ? 1 : b.path > a.path ? -1 : 0,
|
||||
);
|
||||
};
|
||||
|
||||
// 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 (
|
||||
docsBaseRoute: string,
|
||||
docsBaseMetadata: DocsBaseMetadata,
|
||||
|
@ -454,14 +393,20 @@ Available document ids=
|
|||
priority?: number,
|
||||
) => {
|
||||
const docsBaseMetadataPath = await createData(
|
||||
`${docuHash(docsBaseRoute)}.json`,
|
||||
`${docuHash(normalizeUrl([docsBaseRoute, ':route']))}.json`,
|
||||
JSON.stringify(docsBaseMetadata, null, 2),
|
||||
);
|
||||
|
||||
// Important: the layout component should not end with /,
|
||||
// as it conflicts with the home doc
|
||||
// Workaround fix for https://github.com/facebook/docusaurus/issues/2917
|
||||
const path = docsBaseRoute === '/' ? '' : docsBaseRoute;
|
||||
|
||||
addRoute({
|
||||
path: docsBaseRoute,
|
||||
component: docLayoutComponent,
|
||||
routes,
|
||||
path,
|
||||
exact: false, // allow matching /docs/* as well
|
||||
component: docLayoutComponent, // main docs component (DocPage)
|
||||
routes, // subroute for each doc
|
||||
modules: {
|
||||
docsMetadata: aliasedSource(docsBaseMetadataPath),
|
||||
},
|
||||
|
@ -499,21 +444,20 @@ Available document ids=
|
|||
);
|
||||
|
||||
const isLatestVersion = version === versioning.latestVersion;
|
||||
const docsBasePermalink = normalizeUrl([
|
||||
const docsBaseRoute = normalizeUrl([
|
||||
baseUrl,
|
||||
routeBasePath,
|
||||
isLatestVersion ? '' : version,
|
||||
]);
|
||||
const docsBaseRoute = normalizeUrl([docsBasePermalink, ':route']);
|
||||
const docsBaseMetadata = createDocsBaseMetadata(version);
|
||||
|
||||
// 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`.
|
||||
return addBaseRoute(
|
||||
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,
|
||||
);
|
||||
}),
|
||||
|
@ -521,8 +465,7 @@ Available document ids=
|
|||
} else {
|
||||
const routes = await genRoutes(Object.values(content.docsMetadata));
|
||||
const docsBaseMetadata = createDocsBaseMetadata();
|
||||
|
||||
const docsBaseRoute = normalizeUrl([baseUrl, routeBasePath, ':route']);
|
||||
const docsBaseRoute = normalizeUrl([baseUrl, routeBasePath]);
|
||||
return addBaseRoute(docsBaseRoute, docsBaseMetadata, routes);
|
||||
}
|
||||
},
|
||||
|
|
|
@ -15,7 +15,43 @@ import {
|
|||
import {LoadContext} from '@docusaurus/types';
|
||||
|
||||
import lastUpdate from './lastUpdate';
|
||||
import {MetadataRaw, LastUpdateData, MetadataOptions, Env} from './types';
|
||||
import {
|
||||
MetadataRaw,
|
||||
LastUpdateData,
|
||||
MetadataOptions,
|
||||
Env,
|
||||
VersioningEnv,
|
||||
} from './types';
|
||||
|
||||
function removeVersionPrefix(str: string, version: string): string {
|
||||
return str.replace(new RegExp(`^version-${version}/`), '');
|
||||
}
|
||||
|
||||
function inferVersion(
|
||||
dirName: string,
|
||||
versioning: VersioningEnv,
|
||||
): string | undefined {
|
||||
if (!versioning.enabled) {
|
||||
return undefined;
|
||||
}
|
||||
if (/^version-/.test(dirName)) {
|
||||
const inferredVersion = dirName
|
||||
.split('/', 1)
|
||||
.shift()!
|
||||
.replace(/^version-/, '');
|
||||
if (inferredVersion && versioning.versions.includes(inferredVersion)) {
|
||||
return inferredVersion;
|
||||
} else {
|
||||
throw new Error(
|
||||
`Can't infer version from folder=${dirName}
|
||||
Expected versions:
|
||||
- ${versioning.versions.join('- ')}`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return 'next';
|
||||
}
|
||||
}
|
||||
|
||||
type Args = {
|
||||
source: string;
|
||||
|
@ -59,7 +95,7 @@ export default async function processMetadata({
|
|||
options,
|
||||
env,
|
||||
}: Args): Promise<MetadataRaw> {
|
||||
const {routeBasePath, editUrl} = options;
|
||||
const {routeBasePath, editUrl, homePageId} = options;
|
||||
const {siteDir, baseUrl} = context;
|
||||
const {versioning} = env;
|
||||
const filePath = path.join(refDir, source);
|
||||
|
@ -67,21 +103,8 @@ export default async function processMetadata({
|
|||
const fileMarkdownPromise = parseMarkdownFile(filePath);
|
||||
const lastUpdatedPromise = lastUpdated(filePath, options);
|
||||
|
||||
let version;
|
||||
const dirName = path.dirname(source);
|
||||
if (versioning.enabled) {
|
||||
if (/^version-/.test(dirName)) {
|
||||
const inferredVersion = dirName
|
||||
.split('/', 1)
|
||||
.shift()!
|
||||
.replace(/^version-/, '');
|
||||
if (inferredVersion && versioning.versions.includes(inferredVersion)) {
|
||||
version = inferredVersion;
|
||||
}
|
||||
} else {
|
||||
version = 'next';
|
||||
}
|
||||
}
|
||||
const version = inferVersion(dirName, versioning);
|
||||
|
||||
// The version portion of the url path. Eg: 'next', '1.0.0', and ''.
|
||||
const versionPath =
|
||||
|
@ -100,14 +123,20 @@ export default async function processMetadata({
|
|||
if (baseID.includes('/')) {
|
||||
throw new Error('Document id cannot include "/".');
|
||||
}
|
||||
const id = dirName !== '.' ? `${dirName}/${baseID}` : baseID;
|
||||
const idWithoutVersion = version ? removeVersionPrefix(id, version) : id;
|
||||
|
||||
const isDocsHomePage = idWithoutVersion === homePageId;
|
||||
if (frontMatter.slug && isDocsHomePage) {
|
||||
throw new Error(
|
||||
`The docs homepage (homePageId=${homePageId}) is not allowed to have a frontmatter slug=${frontMatter.slug} => you have to chooser either homePageId or slug, not both`,
|
||||
);
|
||||
}
|
||||
|
||||
const baseSlug: string = frontMatter.slug || baseID;
|
||||
if (baseSlug.includes('/')) {
|
||||
throw new Error('Document slug cannot include "/".');
|
||||
}
|
||||
|
||||
// Append subdirectory as part of id/slug.
|
||||
const id = dirName !== '.' ? `${dirName}/${baseID}` : baseID;
|
||||
const slug = dirName !== '.' ? `${dirName}/${baseSlug}` : baseSlug;
|
||||
|
||||
// Default title is the id.
|
||||
|
@ -116,10 +145,16 @@ export default async function processMetadata({
|
|||
const description: string = frontMatter.description || excerpt;
|
||||
|
||||
// The last portion of the url path. Eg: 'foo/bar', 'bar'.
|
||||
const routePath =
|
||||
version && version !== 'next'
|
||||
? slug.replace(new RegExp(`^version-${version}/`), '')
|
||||
: slug;
|
||||
let routePath;
|
||||
if (isDocsHomePage) {
|
||||
// TODO can we remove this trailing / ?
|
||||
// Seems it's not that easy...
|
||||
// Related to https://github.com/facebook/docusaurus/issues/2917
|
||||
routePath = '/';
|
||||
} else {
|
||||
routePath =
|
||||
version && version !== 'next' ? removeVersionPrefix(slug, version) : slug;
|
||||
}
|
||||
|
||||
const permalink = normalizeUrl([
|
||||
baseUrl,
|
||||
|
@ -136,6 +171,7 @@ export default async function processMetadata({
|
|||
// class transitions.
|
||||
const metadata: MetadataRaw = {
|
||||
id,
|
||||
isDocsHomePage,
|
||||
title,
|
||||
description,
|
||||
source: aliasedSitePath(filePath, siteDir),
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
export interface MetadataOptions {
|
||||
routeBasePath: string;
|
||||
homePageId?: string;
|
||||
editUrl?: string;
|
||||
showLastUpdateTime?: boolean;
|
||||
showLastUpdateAuthor?: boolean;
|
||||
|
@ -24,7 +25,6 @@ export interface PluginOptions extends MetadataOptions, PathOptions {
|
|||
remarkPlugins: ([Function, object] | Function)[];
|
||||
rehypePlugins: string[];
|
||||
admonitions: any;
|
||||
homePageId: string;
|
||||
}
|
||||
|
||||
export type SidebarItemDoc = {
|
||||
|
@ -111,6 +111,7 @@ export interface LastUpdateData {
|
|||
|
||||
export interface MetadataRaw extends LastUpdateData {
|
||||
id: string;
|
||||
isDocsHomePage: boolean;
|
||||
title: string;
|
||||
description: string;
|
||||
source: string;
|
||||
|
@ -165,8 +166,6 @@ export type DocsBaseMetadata = Pick<
|
|||
'docsSidebars' | 'permalinkToSidebar'
|
||||
> & {
|
||||
version?: string;
|
||||
isHomePage?: boolean;
|
||||
homePagePath?: string;
|
||||
};
|
||||
|
||||
export type VersioningEnv = {
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
import React from 'react';
|
||||
import renderRoutes from '@docusaurus/renderRoutes';
|
||||
import NotFound from '@theme/NotFound';
|
||||
import DocItem from '@theme/DocItem';
|
||||
import DocSidebar from '@theme/DocSidebar';
|
||||
import MDXComponents from '@theme/MDXComponents';
|
||||
import Layout from '@theme/Layout';
|
||||
|
@ -16,24 +15,16 @@ import {MDXProvider} from '@mdx-js/react';
|
|||
import {matchPath} from '@docusaurus/router';
|
||||
|
||||
function DocPage(props) {
|
||||
const {route: baseRoute, docsMetadata, location, content} = props;
|
||||
const {
|
||||
permalinkToSidebar,
|
||||
docsSidebars,
|
||||
isHomePage,
|
||||
homePagePath,
|
||||
} = docsMetadata;
|
||||
const {route: baseRoute, docsMetadata, location} = props;
|
||||
// case-sensitive route such as it is defined in the sidebar
|
||||
const currentRoute = !isHomePage
|
||||
? baseRoute.routes.find((route) => {
|
||||
const currentRoute =
|
||||
baseRoute.routes.find((route) => {
|
||||
return matchPath(location.pathname, route);
|
||||
}) || {}
|
||||
: {};
|
||||
const sidebar = isHomePage
|
||||
? content.metadata.sidebar
|
||||
: permalinkToSidebar[currentRoute.path];
|
||||
}) || {};
|
||||
const {permalinkToSidebar, docsSidebars} = docsMetadata;
|
||||
const sidebar = permalinkToSidebar[currentRoute.path];
|
||||
|
||||
if (!isHomePage && Object.keys(currentRoute).length === 0) {
|
||||
if (Object.keys(currentRoute).length === 0) {
|
||||
return <NotFound {...props} />;
|
||||
}
|
||||
|
||||
|
@ -41,16 +32,12 @@ function DocPage(props) {
|
|||
<Layout title="Doc page" description="My Doc page">
|
||||
<DocSidebar
|
||||
docsSidebars={docsSidebars}
|
||||
path={isHomePage ? homePagePath : currentRoute.path}
|
||||
path={currentRoute.path}
|
||||
sidebar={sidebar}
|
||||
/>
|
||||
<section className="offset-1 mr-4 mt-4 col-xl-6 offset-xl-4 p-0 justify-content-center align-self-center overflow-hidden">
|
||||
<MDXProvider components={MDXComponents}>
|
||||
{isHomePage ? (
|
||||
<DocItem content={content} />
|
||||
) : (
|
||||
renderRoutes(baseRoute.routes)
|
||||
)}
|
||||
{renderRoutes(baseRoute.routes)}
|
||||
</MDXProvider>
|
||||
</section>
|
||||
</Layout>
|
||||
|
|
|
@ -11,7 +11,6 @@ import {MDXProvider} from '@mdx-js/react';
|
|||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||
import renderRoutes from '@docusaurus/renderRoutes';
|
||||
import Layout from '@theme/Layout';
|
||||
import DocItem from '@theme/DocItem';
|
||||
import DocSidebar from '@theme/DocSidebar';
|
||||
import MDXComponents from '@theme/MDXComponents';
|
||||
import NotFound from '@theme/NotFound';
|
||||
|
@ -20,35 +19,22 @@ import {matchPath} from '@docusaurus/router';
|
|||
import styles from './styles.module.css';
|
||||
|
||||
function DocPage(props) {
|
||||
const {route: baseRoute, docsMetadata, location, content} = props;
|
||||
const {
|
||||
permalinkToSidebar,
|
||||
docsSidebars,
|
||||
version,
|
||||
isHomePage,
|
||||
homePagePath,
|
||||
} = docsMetadata;
|
||||
|
||||
// Get case-sensitive route such as it is defined in the sidebar.
|
||||
const currentRoute = !isHomePage
|
||||
? baseRoute.routes.find((route) => {
|
||||
const {route: baseRoute, docsMetadata, location} = props;
|
||||
// case-sensitive route such as it is defined in the sidebar
|
||||
const currentRoute =
|
||||
baseRoute.routes.find((route) => {
|
||||
return matchPath(location.pathname, route);
|
||||
}) || {}
|
||||
: {};
|
||||
|
||||
const sidebar = isHomePage
|
||||
? content.metadata.sidebar
|
||||
: permalinkToSidebar[currentRoute.path];
|
||||
}) || {};
|
||||
const {permalinkToSidebar, docsSidebars, version} = docsMetadata;
|
||||
const sidebar = permalinkToSidebar[currentRoute.path];
|
||||
const {
|
||||
siteConfig: {themeConfig: {sidebarCollapsible = true} = {}} = {},
|
||||
siteConfig: {themeConfig = {}} = {},
|
||||
isClient,
|
||||
} = useDocusaurusContext();
|
||||
|
||||
if (isHomePage) {
|
||||
content.metadata.permalink = homePagePath;
|
||||
}
|
||||
const {sidebarCollapsible = true} = themeConfig;
|
||||
|
||||
if (!isHomePage && Object.keys(currentRoute).length === 0) {
|
||||
if (Object.keys(currentRoute).length === 0) {
|
||||
return <NotFound {...props} />;
|
||||
}
|
||||
|
||||
|
@ -59,7 +45,7 @@ function DocPage(props) {
|
|||
<div className={styles.docSidebarContainer} role="complementary">
|
||||
<DocSidebar
|
||||
docsSidebars={docsSidebars}
|
||||
path={isHomePage ? homePagePath : currentRoute.path}
|
||||
path={currentRoute.path}
|
||||
sidebar={sidebar}
|
||||
sidebarCollapsible={sidebarCollapsible}
|
||||
/>
|
||||
|
@ -67,11 +53,7 @@ function DocPage(props) {
|
|||
)}
|
||||
<main className={styles.docMainContainer}>
|
||||
<MDXProvider components={MDXComponents}>
|
||||
{isHomePage ? (
|
||||
<DocItem content={content} />
|
||||
) : (
|
||||
renderRoutes(baseRoute.routes)
|
||||
)}
|
||||
{renderRoutes(baseRoute.routes)}
|
||||
</MDXProvider>
|
||||
</main>
|
||||
</div>
|
||||
|
|
|
@ -28,9 +28,15 @@ function usePrevious(value) {
|
|||
return ref.current;
|
||||
}
|
||||
|
||||
// Compare the 2 paths, ignoring trailing /
|
||||
const isSamePath = (path1, path2) => {
|
||||
const normalize = (str) => (str.endsWith('/') ? str : `${str}/`);
|
||||
return normalize(path1) === normalize(path2);
|
||||
};
|
||||
|
||||
const isActiveSidebarItem = (item, activePath) => {
|
||||
if (item.type === 'link') {
|
||||
return item.href === activePath;
|
||||
return isSamePath(item.href, activePath);
|
||||
}
|
||||
if (item.type === 'category') {
|
||||
return item.items.some((subItem) =>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue