feat(content-docs): new front matter options to customize pagination (#5705)

This commit is contained in:
Joshua Chen 2021-10-21 18:27:57 +08:00 committed by GitHub
parent 29d13351a4
commit 3127f12654
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 489 additions and 95 deletions

View file

@ -1,5 +1,7 @@
--- ---
slug: /rootAbsoluteSlug slug: /rootAbsoluteSlug
pagination_next: headingAsTitle
pagination_prev: foo/baz
--- ---
Lorem Lorem

View file

@ -1,5 +1,7 @@
--- ---
slug: rootRelativeSlug slug: rootRelativeSlug
pagination_next: headingAsTitle
pagination_prev: foo/baz
--- ---
Lorem Lorem

View file

@ -1,5 +1,7 @@
--- ---
slug: ./hey/ho/../rootResolvedSlug slug: ./hey/ho/../rootResolvedSlug
pagination_next: headingAsTitle
pagination_prev: foo/baz
--- ---
Lorem Lorem

View file

@ -1,5 +1,7 @@
--- ---
slug: ../../../../../../../../rootTryToEscapeSlug slug: ../../../../../../../../rootTryToEscapeSlug
pagination_next: headingAsTitle
pagination_prev: foo/baz
--- ---
Lorem Lorem

View file

@ -6,6 +6,20 @@
"label": "foo", "label": "foo",
"items": ["foo/bar", "foo/baz"] "items": ["foo/bar", "foo/baz"]
}, },
{
"type": "category",
"label": "Slugs",
"items": [
"rootAbsoluteSlug",
"rootRelativeSlug",
"rootResolvedSlug",
"rootTryToEscapeSlug"
]
},
{
"type": "doc",
"id": "headingAsTitle"
},
{ {
"type": "link", "type": "link",
"label": "Github", "label": "Github",

View file

@ -23,6 +23,34 @@ Object {
"label": "foo", "label": "foo",
"type": "category", "type": "category",
}, },
Object {
"collapsed": true,
"collapsible": true,
"items": Array [
Object {
"id": "version-1.0.0/rootAbsoluteSlug",
"type": "doc",
},
Object {
"id": "version-1.0.0/rootRelativeSlug",
"type": "doc",
},
Object {
"id": "version-1.0.0/rootResolvedSlug",
"type": "doc",
},
Object {
"id": "version-1.0.0/rootTryToEscapeSlug",
"type": "doc",
},
],
"label": "Slugs",
"type": "category",
},
Object {
"id": "version-1.0.0/headingAsTitle",
"type": "doc",
},
Object { Object {
"href": "https://github.com", "href": "https://github.com",
"label": "Github", "label": "Github",

View file

@ -0,0 +1,140 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`simple site custom pagination 1`] = `
Array [
Array [
Object {
"permalink": "/docs/rootTryToEscapeSlug",
"title": "rootTryToEscapeSlug",
},
Object {
"permalink": "/docs/foo/bazSlug.html",
"title": "baz pagination_label",
},
],
Array [
Object {
"permalink": "/docs/foo/bar",
"title": "Bar",
},
Object {
"permalink": "/docs/absoluteSlug",
"title": "absoluteSlug",
},
],
Array [
undefined,
Object {
"permalink": "/docs/hello",
"title": "Hello sidebar_label",
},
],
Array [
Object {
"permalink": "/docs/headingAsTitle",
"title": "My heading as title",
},
Object {
"permalink": "/docs/ipsum",
"title": "ipsum",
},
],
Array [
Object {
"permalink": "/docs/hello",
"title": "Hello sidebar_label",
},
Object {
"permalink": "/docs/lorem",
"title": "lorem",
},
],
Array [
Object {
"permalink": "/docs/ipsum",
"title": "ipsum",
},
Object {
"permalink": "/docs/rootAbsoluteSlug",
"title": "rootAbsoluteSlug",
},
],
Array [
Object {
"permalink": "/docs/foo/bazSlug.html",
"title": "baz pagination_label",
},
Object {
"permalink": "/docs/headingAsTitle",
"title": "My heading as title",
},
],
Array [
Object {
"permalink": "/docs/foo/bazSlug.html",
"title": "baz pagination_label",
},
Object {
"permalink": "/docs/headingAsTitle",
"title": "My heading as title",
},
],
Array [
Object {
"permalink": "/docs/foo/bazSlug.html",
"title": "baz pagination_label",
},
Object {
"permalink": "/docs/headingAsTitle",
"title": "My heading as title",
},
],
Array [
Object {
"permalink": "/docs/foo/bazSlug.html",
"title": "baz pagination_label",
},
Object {
"permalink": "/docs/headingAsTitle",
"title": "My heading as title",
},
],
Array [
Object {
"permalink": "/docs/foo/bazSlug.html",
"title": "baz pagination_label",
},
Object {
"permalink": "/docs/slugs/relativeSlug",
"title": "relativeSlug",
},
],
Array [
Object {
"permalink": "/docs/absoluteSlug",
"title": "absoluteSlug",
},
Object {
"permalink": "/docs/slugs/hey/resolvedSlug",
"title": "resolvedSlug",
},
],
Array [
Object {
"permalink": "/docs/slugs/relativeSlug",
"title": "relativeSlug",
},
Object {
"permalink": "/docs/tryToEscapeSlug",
"title": "tryToEscapeSlug",
},
],
Array [
Object {
"permalink": "/docs/slugs/hey/resolvedSlug",
"title": "resolvedSlug",
},
undefined,
],
]
`;

View file

@ -45,6 +45,34 @@ Object {
"label": "foo", "label": "foo",
"type": "category", "type": "category",
}, },
Object {
"collapsed": true,
"collapsible": true,
"items": Array [
Object {
"id": "rootAbsoluteSlug",
"type": "doc",
},
Object {
"id": "rootRelativeSlug",
"type": "doc",
},
Object {
"id": "rootResolvedSlug",
"type": "doc",
},
Object {
"id": "rootTryToEscapeSlug",
"type": "doc",
},
],
"label": "Slugs",
"type": "category",
},
Object {
"id": "headingAsTitle",
"type": "doc",
},
Object { Object {
"href": "https://github.com", "href": "https://github.com",
"label": "Github", "label": "Github",
@ -95,7 +123,7 @@ Object {
Object { Object {
"id": "headingAsTitle", "id": "headingAsTitle",
"path": "/docs/headingAsTitle", "path": "/docs/headingAsTitle",
"sidebar": undefined, "sidebar": "docs",
}, },
Object { Object {
"id": "hello", "id": "hello",
@ -115,22 +143,22 @@ Object {
Object { Object {
"id": "rootAbsoluteSlug", "id": "rootAbsoluteSlug",
"path": "/docs/rootAbsoluteSlug", "path": "/docs/rootAbsoluteSlug",
"sidebar": undefined, "sidebar": "docs",
}, },
Object { Object {
"id": "rootRelativeSlug", "id": "rootRelativeSlug",
"path": "/docs/rootRelativeSlug", "path": "/docs/rootRelativeSlug",
"sidebar": undefined, "sidebar": "docs",
}, },
Object { Object {
"id": "rootResolvedSlug", "id": "rootResolvedSlug",
"path": "/docs/hey/rootResolvedSlug", "path": "/docs/hey/rootResolvedSlug",
"sidebar": undefined, "sidebar": "docs",
}, },
Object { Object {
"id": "rootTryToEscapeSlug", "id": "rootTryToEscapeSlug",
"path": "/docs/rootTryToEscapeSlug", "path": "/docs/rootTryToEscapeSlug",
"sidebar": undefined, "sidebar": "docs",
}, },
Object { Object {
"id": "slugs/absoluteSlug", "id": "slugs/absoluteSlug",
@ -231,8 +259,8 @@ Object {
\\"permalink\\": \\"/docs/foo/bar\\" \\"permalink\\": \\"/docs/foo/bar\\"
}, },
\\"next\\": { \\"next\\": {
\\"title\\": \\"Hello sidebar_label\\", \\"title\\": \\"rootAbsoluteSlug\\",
\\"permalink\\": \\"/docs/\\" \\"permalink\\": \\"/docs/rootAbsoluteSlug\\"
} }
}", }",
"site-docs-heading-as-title-md-c6d.json": "{ "site-docs-heading-as-title-md-c6d.json": "{
@ -247,7 +275,16 @@ Object {
\\"permalink\\": \\"/docs/headingAsTitle\\", \\"permalink\\": \\"/docs/headingAsTitle\\",
\\"tags\\": [], \\"tags\\": [],
\\"version\\": \\"current\\", \\"version\\": \\"current\\",
\\"frontMatter\\": {} \\"frontMatter\\": {},
\\"sidebar\\": \\"docs\\",
\\"previous\\": {
\\"title\\": \\"rootTryToEscapeSlug\\",
\\"permalink\\": \\"/docs/rootTryToEscapeSlug\\"
},
\\"next\\": {
\\"title\\": \\"Hello sidebar_label\\",
\\"permalink\\": \\"/docs/\\"
}
}", }",
"site-docs-hello-md-9df.json": "{ "site-docs-hello-md-9df.json": "{
\\"unversionedId\\": \\"hello\\", \\"unversionedId\\": \\"hello\\",
@ -281,8 +318,8 @@ Object {
}, },
\\"sidebar\\": \\"docs\\", \\"sidebar\\": \\"docs\\",
\\"previous\\": { \\"previous\\": {
\\"title\\": \\"baz pagination_label\\", \\"title\\": \\"My heading as title\\",
\\"permalink\\": \\"/docs/foo/bazSlug.html\\" \\"permalink\\": \\"/docs/headingAsTitle\\"
} }
}", }",
"site-docs-ipsum-md-c61.json": "{ "site-docs-ipsum-md-c61.json": "{
@ -333,7 +370,18 @@ Object {
\\"tags\\": [], \\"tags\\": [],
\\"version\\": \\"current\\", \\"version\\": \\"current\\",
\\"frontMatter\\": { \\"frontMatter\\": {
\\"slug\\": \\"/rootAbsoluteSlug\\" \\"slug\\": \\"/rootAbsoluteSlug\\",
\\"pagination_next\\": \\"headingAsTitle\\",
\\"pagination_prev\\": \\"foo/baz\\"
},
\\"sidebar\\": \\"docs\\",
\\"previous\\": {
\\"title\\": \\"baz pagination_label\\",
\\"permalink\\": \\"/docs/foo/bazSlug.html\\"
},
\\"next\\": {
\\"title\\": \\"My heading as title\\",
\\"permalink\\": \\"/docs/headingAsTitle\\"
} }
}", }",
"site-docs-root-relative-slug-md-3dd.json": "{ "site-docs-root-relative-slug-md-3dd.json": "{
@ -349,7 +397,18 @@ Object {
\\"tags\\": [], \\"tags\\": [],
\\"version\\": \\"current\\", \\"version\\": \\"current\\",
\\"frontMatter\\": { \\"frontMatter\\": {
\\"slug\\": \\"rootRelativeSlug\\" \\"slug\\": \\"rootRelativeSlug\\",
\\"pagination_next\\": \\"headingAsTitle\\",
\\"pagination_prev\\": \\"foo/baz\\"
},
\\"sidebar\\": \\"docs\\",
\\"previous\\": {
\\"title\\": \\"baz pagination_label\\",
\\"permalink\\": \\"/docs/foo/bazSlug.html\\"
},
\\"next\\": {
\\"title\\": \\"My heading as title\\",
\\"permalink\\": \\"/docs/headingAsTitle\\"
} }
}", }",
"site-docs-root-resolved-slug-md-4d1.json": "{ "site-docs-root-resolved-slug-md-4d1.json": "{
@ -365,7 +424,18 @@ Object {
\\"tags\\": [], \\"tags\\": [],
\\"version\\": \\"current\\", \\"version\\": \\"current\\",
\\"frontMatter\\": { \\"frontMatter\\": {
\\"slug\\": \\"./hey/ho/../rootResolvedSlug\\" \\"slug\\": \\"./hey/ho/../rootResolvedSlug\\",
\\"pagination_next\\": \\"headingAsTitle\\",
\\"pagination_prev\\": \\"foo/baz\\"
},
\\"sidebar\\": \\"docs\\",
\\"previous\\": {
\\"title\\": \\"baz pagination_label\\",
\\"permalink\\": \\"/docs/foo/bazSlug.html\\"
},
\\"next\\": {
\\"title\\": \\"My heading as title\\",
\\"permalink\\": \\"/docs/headingAsTitle\\"
} }
}", }",
"site-docs-root-try-to-escape-slug-md-9ee.json": "{ "site-docs-root-try-to-escape-slug-md-9ee.json": "{
@ -381,7 +451,18 @@ Object {
\\"tags\\": [], \\"tags\\": [],
\\"version\\": \\"current\\", \\"version\\": \\"current\\",
\\"frontMatter\\": { \\"frontMatter\\": {
\\"slug\\": \\"../../../../../../../../rootTryToEscapeSlug\\" \\"slug\\": \\"../../../../../../../../rootTryToEscapeSlug\\",
\\"pagination_next\\": \\"headingAsTitle\\",
\\"pagination_prev\\": \\"foo/baz\\"
},
\\"sidebar\\": \\"docs\\",
\\"previous\\": {
\\"title\\": \\"baz pagination_label\\",
\\"permalink\\": \\"/docs/foo/bazSlug.html\\"
},
\\"next\\": {
\\"title\\": \\"My heading as title\\",
\\"permalink\\": \\"/docs/headingAsTitle\\"
} }
}", }",
"site-docs-slugs-absolute-slug-md-4e8.json": "{ "site-docs-slugs-absolute-slug-md-4e8.json": "{
@ -544,6 +625,39 @@ Object {
\\"collapsible\\": true, \\"collapsible\\": true,
\\"collapsed\\": true \\"collapsed\\": true
}, },
{
\\"type\\": \\"category\\",
\\"label\\": \\"Slugs\\",
\\"items\\": [
{
\\"type\\": \\"link\\",
\\"label\\": \\"rootAbsoluteSlug\\",
\\"href\\": \\"/docs/rootAbsoluteSlug\\"
},
{
\\"type\\": \\"link\\",
\\"label\\": \\"rootRelativeSlug\\",
\\"href\\": \\"/docs/rootRelativeSlug\\"
},
{
\\"type\\": \\"link\\",
\\"label\\": \\"rootResolvedSlug\\",
\\"href\\": \\"/docs/hey/rootResolvedSlug\\"
},
{
\\"type\\": \\"link\\",
\\"label\\": \\"rootTryToEscapeSlug\\",
\\"href\\": \\"/docs/rootTryToEscapeSlug\\"
}
],
\\"collapsible\\": true,
\\"collapsed\\": true
},
{
\\"type\\": \\"link\\",
\\"label\\": \\"My heading as title\\",
\\"href\\": \\"/docs/headingAsTitle\\"
},
{ {
\\"type\\": \\"link\\", \\"type\\": \\"link\\",
\\"label\\": \\"Github\\", \\"label\\": \\"Github\\",
@ -596,7 +710,7 @@ Object {
Object { Object {
"id": "headingAsTitle", "id": "headingAsTitle",
"path": "/docs/headingAsTitle", "path": "/docs/headingAsTitle",
"sidebar": undefined, "sidebar": "docs",
}, },
Object { Object {
"id": "hello", "id": "hello",
@ -616,22 +730,22 @@ Object {
Object { Object {
"id": "rootAbsoluteSlug", "id": "rootAbsoluteSlug",
"path": "/docs/rootAbsoluteSlug", "path": "/docs/rootAbsoluteSlug",
"sidebar": undefined, "sidebar": "docs",
}, },
Object { Object {
"id": "rootRelativeSlug", "id": "rootRelativeSlug",
"path": "/docs/rootRelativeSlug", "path": "/docs/rootRelativeSlug",
"sidebar": undefined, "sidebar": "docs",
}, },
Object { Object {
"id": "rootResolvedSlug", "id": "rootResolvedSlug",
"path": "/docs/hey/rootResolvedSlug", "path": "/docs/hey/rootResolvedSlug",
"sidebar": undefined, "sidebar": "docs",
}, },
Object { Object {
"id": "rootTryToEscapeSlug", "id": "rootTryToEscapeSlug",
"path": "/docs/rootTryToEscapeSlug", "path": "/docs/rootTryToEscapeSlug",
"sidebar": undefined, "sidebar": "docs",
}, },
Object { Object {
"id": "slugs/absoluteSlug", "id": "slugs/absoluteSlug",
@ -751,6 +865,7 @@ Array [
"content": "@site/docs/headingAsTitle.md", "content": "@site/docs/headingAsTitle.md",
}, },
"path": "/docs/headingAsTitle", "path": "/docs/headingAsTitle",
"sidebar": "docs",
}, },
Object { Object {
"component": "@theme/DocItem", "component": "@theme/DocItem",
@ -759,6 +874,7 @@ Array [
"content": "@site/docs/rootResolvedSlug.md", "content": "@site/docs/rootResolvedSlug.md",
}, },
"path": "/docs/hey/rootResolvedSlug", "path": "/docs/hey/rootResolvedSlug",
"sidebar": "docs",
}, },
Object { Object {
"component": "@theme/DocItem", "component": "@theme/DocItem",
@ -783,6 +899,7 @@ Array [
"content": "@site/docs/rootAbsoluteSlug.md", "content": "@site/docs/rootAbsoluteSlug.md",
}, },
"path": "/docs/rootAbsoluteSlug", "path": "/docs/rootAbsoluteSlug",
"sidebar": "docs",
}, },
Object { Object {
"component": "@theme/DocItem", "component": "@theme/DocItem",
@ -791,6 +908,7 @@ Array [
"content": "@site/docs/rootRelativeSlug.md", "content": "@site/docs/rootRelativeSlug.md",
}, },
"path": "/docs/rootRelativeSlug", "path": "/docs/rootRelativeSlug",
"sidebar": "docs",
}, },
Object { Object {
"component": "@theme/DocItem", "component": "@theme/DocItem",
@ -799,6 +917,7 @@ Array [
"content": "@site/docs/rootTryToEscapeSlug.md", "content": "@site/docs/rootTryToEscapeSlug.md",
}, },
"path": "/docs/rootTryToEscapeSlug", "path": "/docs/rootTryToEscapeSlug",
"sidebar": "docs",
}, },
Object { Object {
"component": "@theme/DocItem", "component": "@theme/DocItem",

View file

@ -7,7 +7,13 @@
import path from 'path'; import path from 'path';
import {loadContext} from '@docusaurus/core/src/server/index'; import {loadContext} from '@docusaurus/core/src/server/index';
import {processDocMetadata, readVersionDocs, readDocFile} from '../docs'; import {
processDocMetadata,
readVersionDocs,
readDocFile,
handleNavigation,
} from '../docs';
import {loadSidebars} from '../sidebars';
import {readVersionsMetadata} from '../versions'; import {readVersionsMetadata} from '../versions';
import { import {
DocFile, DocFile,
@ -16,6 +22,7 @@ import {
VersionMetadata, VersionMetadata,
PluginOptions, PluginOptions,
EditUrlFunction, EditUrlFunction,
DocNavLink,
} from '../types'; } from '../types';
import {LoadContext} from '@docusaurus/types'; import {LoadContext} from '@docusaurus/types';
import {DEFAULT_PLUGIN_ID} from '@docusaurus/core/lib/constants'; import {DEFAULT_PLUGIN_ID} from '@docusaurus/core/lib/constants';
@ -110,7 +117,38 @@ function createTestUtils({
expect(metadata.permalink).toEqual(expectedPermalink); expect(metadata.permalink).toEqual(expectedPermalink);
} }
return {processDocFile, testMeta, testSlug}; async function generateNavigation(
docFiles: DocFile[],
): Promise<[DocNavLink, DocNavLink][]> {
const rawDocs = await Promise.all(
docFiles.map((docFile) =>
processDocMetadata({
docFile,
versionMetadata,
context,
options,
}),
),
);
const sidebars = await loadSidebars(versionMetadata.sidebarFilePath, {
sidebarItemsGenerator: ({defaultSidebarItemsGenerator, ...args}) =>
defaultSidebarItemsGenerator({...args}),
numberPrefixParser: options.numberPrefixParser,
docs: rawDocs,
version: versionMetadata,
options: {
sidebarCollapsed: false,
sidebarCollapsible: true,
},
});
return handleNavigation(
rawDocs,
sidebars,
versionMetadata.sidebarFilePath as string,
).docs.map((doc) => [doc.previous, doc.next]);
}
return {processDocFile, testMeta, testSlug, generateNavigation};
} }
describe('simple site', () => { describe('simple site', () => {
@ -541,6 +579,28 @@ describe('simple site', () => {
`"The docs homepage (homePageId=homePageId) is not allowed to have a frontmatter slug=/x/y => you have to choose either homePageId or slug, not both"`, `"The docs homepage (homePageId=homePageId) is not allowed to have a frontmatter slug=/x/y => you have to choose either homePageId or slug, not both"`,
); );
}); });
test('custom pagination', async () => {
const {defaultTestUtils, options, versionsMetadata} = await loadSite();
const docs = await readVersionDocs(versionsMetadata[0], options);
expect(await defaultTestUtils.generateNavigation(docs)).toMatchSnapshot();
});
test('bad pagination', async () => {
const {defaultTestUtils, options, versionsMetadata} = await loadSite();
const docs = await readVersionDocs(versionsMetadata[0], options);
docs.push(
createFakeDocFile({
source: 'hehe',
frontmatter: {pagination_prev: 'nonexistent'},
}),
);
await expect(async () => {
await defaultTestUtils.generateNavigation(docs);
}).rejects.toThrowErrorMatchingInlineSnapshot(
`"Error when loading hehe in .: the pagination_prev front matter points to a non-existent ID nonexistent."`,
);
});
}); });
describe('versioned site', () => { describe('versioned site', () => {

View file

@ -345,10 +345,11 @@ describe('simple website', () => {
permalink: '/docs/foo/bar', permalink: '/docs/foo/bar',
}, },
next: { next: {
title: 'Hello sidebar_label', title: 'rootAbsoluteSlug',
permalink: '/docs/', permalink: '/docs/rootAbsoluteSlug',
}, },
sidebar: 'docs', sidebar: 'docs',
sidebarPosition: undefined,
source: path.posix.join( source: path.posix.join(
'@site', '@site',
posixPath(path.relative(siteDir, currentVersion.contentPath)), posixPath(path.relative(siteDir, currentVersion.contentPath)),
@ -391,8 +392,8 @@ describe('simple website', () => {
permalink: '/docs/', permalink: '/docs/',
slug: '/', slug: '/',
previous: { previous: {
title: 'baz pagination_label', title: 'My heading as title',
permalink: '/docs/foo/bazSlug.html', permalink: '/docs/headingAsTitle',
}, },
sidebar: 'docs', sidebar: 'docs',
source: path.posix.join( source: path.posix.join(

View file

@ -34,6 +34,8 @@ const DocFrontMatterSchema = Joi.object<DocFrontMatter>({
pagination_label: Joi.string(), pagination_label: Joi.string(),
custom_edit_url: URISchema.allow('', null), custom_edit_url: URISchema.allow('', null),
parse_number_prefixes: Joi.boolean(), parse_number_prefixes: Joi.boolean(),
pagination_next: Joi.string().allow(null),
pagination_prev: Joi.string().allow(null),
...FrontMatterTOCHeadingLevels, ...FrontMatterTOCHeadingLevels,
}).unknown(); }).unknown();

View file

@ -7,6 +7,8 @@
import path from 'path'; import path from 'path';
import fs from 'fs-extra'; import fs from 'fs-extra';
import chalk from 'chalk';
import {keyBy} from 'lodash';
import { import {
aliasedSitePath, aliasedSitePath,
getEditUrl, getEditUrl,
@ -23,17 +25,21 @@ import {getFileLastUpdate} from './lastUpdate';
import { import {
DocFile, DocFile,
DocMetadataBase, DocMetadataBase,
DocMetadata,
DocNavLink,
LastUpdateData, LastUpdateData,
MetadataOptions, MetadataOptions,
PluginOptions, PluginOptions,
VersionMetadata, VersionMetadata,
LoadedVersion,
} from './types'; } from './types';
import getSlug from './slug'; import getSlug from './slug';
import {CURRENT_VERSION_NAME} from './constants'; import {CURRENT_VERSION_NAME} from './constants';
import {getDocsDirPaths} from './versions'; import {getDocsDirPaths} from './versions';
import {stripPathNumberPrefixes} from './numberPrefix'; import {stripPathNumberPrefixes} from './numberPrefix';
import {validateDocFrontMatter} from './docFrontMatter'; import {validateDocFrontMatter} from './docFrontMatter';
import chalk from 'chalk'; import type {Sidebars} from './sidebars/types';
import {createSidebarsUtils} from './sidebars/utils';
type LastUpdateOptions = Pick< type LastUpdateOptions = Pick<
PluginOptions, PluginOptions,
@ -284,3 +290,77 @@ export function processDocMetadata(args: {
throw e; throw e;
} }
} }
export function handleNavigation(
docsBase: DocMetadataBase[],
sidebars: Sidebars,
sidebarFilePath: string,
): Pick<LoadedVersion, 'mainDocId' | 'docs'> {
const docsBaseById = keyBy(docsBase, (doc) => doc.id);
const {checkSidebarsDocIds, getDocNavigation, getFirstDocIdOfFirstSidebar} =
createSidebarsUtils(sidebars);
const validDocIds = Object.keys(docsBaseById);
checkSidebarsDocIds(validDocIds, sidebarFilePath);
// Add sidebar/next/previous to the docs
function addNavData(doc: DocMetadataBase): DocMetadata {
const {sidebarName, previousId, nextId} = getDocNavigation(doc.id);
const toDocNavLink = (
docId: string | null | undefined,
type: 'prev' | 'next',
): DocNavLink | undefined => {
if (!docId) {
return undefined;
}
if (!docsBaseById[docId]) {
// This could only happen if user provided the ID through front matter
throw new Error(
`Error when loading ${doc.id} in ${doc.sourceDirName}: the pagination_${type} front matter points to a non-existent ID ${docId}.`,
);
}
const {
title,
permalink,
frontMatter: {
pagination_label: paginationLabel,
sidebar_label: sidebarLabel,
},
} = docsBaseById[docId];
return {title: paginationLabel ?? sidebarLabel ?? title, permalink};
};
const {
frontMatter: {
pagination_next: paginationNext = nextId,
pagination_prev: paginationPrev = previousId,
},
} = doc;
const previous = toDocNavLink(paginationPrev, 'prev');
const next = toDocNavLink(paginationNext, 'next');
return {...doc, sidebar: sidebarName, previous, next};
}
const docs = docsBase.map(addNavData);
// sort to ensure consistent output for tests
docs.sort((a, b) => a.id.localeCompare(b.id));
/**
* The "main doc" is the "version entry point"
* We browse this doc by clicking on a version:
* - the "home" doc (at '/docs/')
* - the first doc of the first sidebar
* - a random doc (if no docs are in any sidebar... edge case)
*/
function getMainDoc(): DocMetadata {
const versionHomeDoc = docs.find((doc) => doc.slug === '/');
const firstDocIdOfFirstSidebar = getFirstDocIdOfFirstSidebar();
if (versionHomeDoc) {
return versionHomeDoc;
} else if (firstDocIdOfFirstSidebar) {
return docs.find((doc) => doc.id === firstDocIdOfFirstSidebar)!;
} else {
return docs[0];
}
}
return {mainDocId: getMainDoc().unversionedId, docs};
}

View file

@ -22,9 +22,8 @@ import {
} from '@docusaurus/utils'; } from '@docusaurus/utils';
import {LoadContext, Plugin, RouteConfig} from '@docusaurus/types'; import {LoadContext, Plugin, RouteConfig} from '@docusaurus/types';
import {loadSidebars} from './sidebars'; import {loadSidebars} from './sidebars';
import {createSidebarsUtils} from './sidebars/utils';
import {CategoryMetadataFilenamePattern} from './sidebars/generator'; import {CategoryMetadataFilenamePattern} from './sidebars/generator';
import {readVersionDocs, processDocMetadata} from './docs'; import {readVersionDocs, processDocMetadata, handleNavigation} from './docs';
import {getDocsDirPaths, readVersionsMetadata} from './versions'; import {getDocsDirPaths, readVersionsMetadata} from './versions';
import { import {
@ -35,7 +34,6 @@ import {
DocMetadata, DocMetadata,
GlobalPluginData, GlobalPluginData,
VersionMetadata, VersionMetadata,
DocNavLink,
LoadedVersion, LoadedVersion,
DocFile, DocFile,
DocsMarkdownOption, DocsMarkdownOption,
@ -165,10 +163,6 @@ export default function pluginContentDocs(
const docsBase: DocMetadataBase[] = await loadVersionDocsBase( const docsBase: DocMetadataBase[] = await loadVersionDocsBase(
versionMetadata, versionMetadata,
); );
const docsBaseById: Record<string, DocMetadataBase> = keyBy(
docsBase,
(doc) => doc.id,
);
const sidebars = await loadSidebars(versionMetadata.sidebarFilePath, { const sidebars = await loadSidebars(versionMetadata.sidebarFilePath, {
sidebarItemsGenerator: options.sidebarItemsGenerator, sidebarItemsGenerator: options.sidebarItemsGenerator,
@ -180,70 +174,14 @@ export default function pluginContentDocs(
sidebarCollapsible: options.sidebarCollapsible, sidebarCollapsible: options.sidebarCollapsible,
}, },
}); });
const {
checkSidebarsDocIds,
getDocNavigation,
getFirstDocIdOfFirstSidebar,
} = createSidebarsUtils(sidebars);
const validDocIds = Object.keys(docsBaseById);
checkSidebarsDocIds(
validDocIds,
versionMetadata.sidebarFilePath as string,
);
// Add sidebar/next/previous to the docs
function addNavData(doc: DocMetadataBase): DocMetadata {
const {sidebarName, previousId, nextId} = getDocNavigation(doc.id);
const toDocNavLink = (navDocId: string): DocNavLink => {
const {title, permalink, frontMatter} = docsBaseById[navDocId];
return {
title:
frontMatter.pagination_label ??
frontMatter.sidebar_label ??
title,
permalink,
};
};
return {
...doc,
sidebar: sidebarName,
previous: previousId ? toDocNavLink(previousId) : undefined,
next: nextId ? toDocNavLink(nextId) : undefined,
};
}
const docs = docsBase.map(addNavData);
// sort to ensure consistent output for tests
docs.sort((a, b) => a.id.localeCompare(b.id));
// The "main doc" is the "version entry point"
// We browse this doc by clicking on a version:
// - the "home" doc (at '/docs/')
// - the first doc of the first sidebar
// - a random doc (if no docs are in any sidebar... edge case)
function getMainDoc(): DocMetadata {
const versionHomeDoc = docs.find(
(doc) =>
doc.unversionedId === options.homePageId || doc.slug === '/',
);
const firstDocIdOfFirstSidebar = getFirstDocIdOfFirstSidebar();
if (versionHomeDoc) {
return versionHomeDoc;
} else if (firstDocIdOfFirstSidebar) {
return docs.find((doc) => doc.id === firstDocIdOfFirstSidebar)!;
} else {
return docs[0];
}
}
return { return {
...versionMetadata, ...versionMetadata,
mainDocId: getMainDoc().unversionedId, ...handleNavigation(
docsBase,
sidebars,
versionMetadata.sidebarFilePath as string,
),
sidebars, sidebars,
docs: docs.map(addNavData),
}; };
} }

View file

@ -131,6 +131,8 @@ export type DocFrontMatter = {
parse_number_prefixes?: boolean; parse_number_prefixes?: boolean;
toc_min_heading_level?: number; toc_min_heading_level?: number;
toc_max_heading_level?: number; toc_max_heading_level?: number;
pagination_next?: string | null;
pagination_prev?: string | null;
/* eslint-enable camelcase */ /* eslint-enable camelcase */
}; };

View file

@ -252,6 +252,8 @@ Accepted fields:
| `hide_table_of_contents` | `boolean` | `false` | Whether to hide the table of contents to the right. | | `hide_table_of_contents` | `boolean` | `false` | Whether to hide the table of contents to the right. |
| `toc_min_heading_level` | `number` | `2` | The minimum heading level shown in the table of contents. Must be between 2 and 6 and lower or equal to the max value. | | `toc_min_heading_level` | `number` | `2` | The minimum heading level shown in the table of contents. Must be between 2 and 6 and lower or equal to the max value. |
| `toc_max_heading_level` | `number` | `3` | The max heading level shown in the table of contents. Must be between 2 and 6. | | `toc_max_heading_level` | `number` | `3` | The max heading level shown in the table of contents. Must be between 2 and 6. |
| `pagination_next` | <code>string \| null</code> | Next doc in the sidebar | The ID of the documentation you want the "Next" pagination to link to. Use `null` to disable showing "Next" for this page. |
| `pagination_prev` | <code>string \| null</code> | Previous doc in the sidebar | The ID of the documentation you want the "Previous" pagination to link to. Use `null` to disable showing "Previous" for this page. |
| `parse_number_prefixes` | `boolean` | `numberPrefixParser` plugin option | Whether number prefix parsing is disabled on this doc. See also [Using number prefixes](/docs/sidebar#using-number-prefixes). | | `parse_number_prefixes` | `boolean` | `numberPrefixParser` plugin option | Whether number prefix parsing is disabled on this doc. See also [Using number prefixes](/docs/sidebar#using-number-prefixes). |
| `custom_edit_url` | `string` | Computed using the `editUrl` plugin option | The URL for editing this document. | | `custom_edit_url` | `string` | Computed using the `editUrl` plugin option | The URL for editing this document. |
| `keywords` | `string[]` | `undefined` | Keywords meta tag for the document page, for search engines. | | `keywords` | `string[]` | `undefined` | Keywords meta tag for the document page, for search engines. |