From 3127f1265493cc0976d7be76815c3914bfb1a033 Mon Sep 17 00:00:00 2001 From: Joshua Chen Date: Thu, 21 Oct 2021 18:27:57 +0800 Subject: [PATCH] feat(content-docs): new front matter options to customize pagination (#5705) --- .../simple-site/docs/rootAbsoluteSlug.md | 2 + .../simple-site/docs/rootRelativeSlug.md | 2 + .../simple-site/docs/rootResolvedSlug.md | 2 + .../simple-site/docs/rootTryToEscapeSlug.md | 2 + .../__fixtures__/simple-site/sidebars.json | 16 +- .../__tests__/__snapshots__/cli.test.ts.snap | 28 ++++ .../__tests__/__snapshots__/docs.test.ts.snap | 140 ++++++++++++++++ .../__snapshots__/index.test.ts.snap | 157 +++++++++++++++--- .../src/__tests__/docs.test.ts | 64 ++++++- .../src/__tests__/index.test.ts | 9 +- .../src/docFrontMatter.ts | 2 + .../src/docs.ts | 82 ++++++++- .../src/index.ts | 74 +-------- .../src/types.ts | 2 + .../docs/api/plugins/plugin-content-docs.md | 2 + 15 files changed, 489 insertions(+), 95 deletions(-) create mode 100644 packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/docs.test.ts.snap diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/docs/rootAbsoluteSlug.md b/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/docs/rootAbsoluteSlug.md index a9f7e1d353..3aa6ccb0ff 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/docs/rootAbsoluteSlug.md +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/docs/rootAbsoluteSlug.md @@ -1,5 +1,7 @@ --- slug: /rootAbsoluteSlug +pagination_next: headingAsTitle +pagination_prev: foo/baz --- Lorem \ No newline at end of file diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/docs/rootRelativeSlug.md b/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/docs/rootRelativeSlug.md index 660f01f0df..4b795e81f3 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/docs/rootRelativeSlug.md +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/docs/rootRelativeSlug.md @@ -1,5 +1,7 @@ --- slug: rootRelativeSlug +pagination_next: headingAsTitle +pagination_prev: foo/baz --- Lorem \ No newline at end of file diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/docs/rootResolvedSlug.md b/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/docs/rootResolvedSlug.md index e011937562..0581995f86 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/docs/rootResolvedSlug.md +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/docs/rootResolvedSlug.md @@ -1,5 +1,7 @@ --- slug: ./hey/ho/../rootResolvedSlug +pagination_next: headingAsTitle +pagination_prev: foo/baz --- Lorem \ No newline at end of file diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/docs/rootTryToEscapeSlug.md b/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/docs/rootTryToEscapeSlug.md index d736a1881c..b6f0a49aab 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/docs/rootTryToEscapeSlug.md +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/docs/rootTryToEscapeSlug.md @@ -1,5 +1,7 @@ --- slug: ../../../../../../../../rootTryToEscapeSlug +pagination_next: headingAsTitle +pagination_prev: foo/baz --- Lorem \ No newline at end of file diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/sidebars.json b/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/sidebars.json index 7892de099a..65c87bc859 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/sidebars.json +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/sidebars.json @@ -4,7 +4,21 @@ { "type": "category", "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", diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/cli.test.ts.snap b/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/cli.test.ts.snap index 9ed12034a2..2d4f355595 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/cli.test.ts.snap +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/cli.test.ts.snap @@ -23,6 +23,34 @@ Object { "label": "foo", "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 { "href": "https://github.com", "label": "Github", diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/docs.test.ts.snap b/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/docs.test.ts.snap new file mode 100644 index 0000000000..97a9e725b2 --- /dev/null +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/docs.test.ts.snap @@ -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, + ], +] +`; diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/index.test.ts.snap b/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/index.test.ts.snap index 8cd395b58b..368c1c1468 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/index.test.ts.snap +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/index.test.ts.snap @@ -45,6 +45,34 @@ Object { "label": "foo", "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 { "href": "https://github.com", "label": "Github", @@ -95,7 +123,7 @@ Object { Object { "id": "headingAsTitle", "path": "/docs/headingAsTitle", - "sidebar": undefined, + "sidebar": "docs", }, Object { "id": "hello", @@ -115,22 +143,22 @@ Object { Object { "id": "rootAbsoluteSlug", "path": "/docs/rootAbsoluteSlug", - "sidebar": undefined, + "sidebar": "docs", }, Object { "id": "rootRelativeSlug", "path": "/docs/rootRelativeSlug", - "sidebar": undefined, + "sidebar": "docs", }, Object { "id": "rootResolvedSlug", "path": "/docs/hey/rootResolvedSlug", - "sidebar": undefined, + "sidebar": "docs", }, Object { "id": "rootTryToEscapeSlug", "path": "/docs/rootTryToEscapeSlug", - "sidebar": undefined, + "sidebar": "docs", }, Object { "id": "slugs/absoluteSlug", @@ -231,8 +259,8 @@ Object { \\"permalink\\": \\"/docs/foo/bar\\" }, \\"next\\": { - \\"title\\": \\"Hello sidebar_label\\", - \\"permalink\\": \\"/docs/\\" + \\"title\\": \\"rootAbsoluteSlug\\", + \\"permalink\\": \\"/docs/rootAbsoluteSlug\\" } }", "site-docs-heading-as-title-md-c6d.json": "{ @@ -247,7 +275,16 @@ Object { \\"permalink\\": \\"/docs/headingAsTitle\\", \\"tags\\": [], \\"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": "{ \\"unversionedId\\": \\"hello\\", @@ -281,8 +318,8 @@ Object { }, \\"sidebar\\": \\"docs\\", \\"previous\\": { - \\"title\\": \\"baz pagination_label\\", - \\"permalink\\": \\"/docs/foo/bazSlug.html\\" + \\"title\\": \\"My heading as title\\", + \\"permalink\\": \\"/docs/headingAsTitle\\" } }", "site-docs-ipsum-md-c61.json": "{ @@ -333,7 +370,18 @@ Object { \\"tags\\": [], \\"version\\": \\"current\\", \\"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": "{ @@ -349,7 +397,18 @@ Object { \\"tags\\": [], \\"version\\": \\"current\\", \\"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": "{ @@ -365,7 +424,18 @@ Object { \\"tags\\": [], \\"version\\": \\"current\\", \\"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": "{ @@ -381,7 +451,18 @@ Object { \\"tags\\": [], \\"version\\": \\"current\\", \\"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": "{ @@ -544,6 +625,39 @@ Object { \\"collapsible\\": 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\\", \\"label\\": \\"Github\\", @@ -596,7 +710,7 @@ Object { Object { "id": "headingAsTitle", "path": "/docs/headingAsTitle", - "sidebar": undefined, + "sidebar": "docs", }, Object { "id": "hello", @@ -616,22 +730,22 @@ Object { Object { "id": "rootAbsoluteSlug", "path": "/docs/rootAbsoluteSlug", - "sidebar": undefined, + "sidebar": "docs", }, Object { "id": "rootRelativeSlug", "path": "/docs/rootRelativeSlug", - "sidebar": undefined, + "sidebar": "docs", }, Object { "id": "rootResolvedSlug", "path": "/docs/hey/rootResolvedSlug", - "sidebar": undefined, + "sidebar": "docs", }, Object { "id": "rootTryToEscapeSlug", "path": "/docs/rootTryToEscapeSlug", - "sidebar": undefined, + "sidebar": "docs", }, Object { "id": "slugs/absoluteSlug", @@ -751,6 +865,7 @@ Array [ "content": "@site/docs/headingAsTitle.md", }, "path": "/docs/headingAsTitle", + "sidebar": "docs", }, Object { "component": "@theme/DocItem", @@ -759,6 +874,7 @@ Array [ "content": "@site/docs/rootResolvedSlug.md", }, "path": "/docs/hey/rootResolvedSlug", + "sidebar": "docs", }, Object { "component": "@theme/DocItem", @@ -783,6 +899,7 @@ Array [ "content": "@site/docs/rootAbsoluteSlug.md", }, "path": "/docs/rootAbsoluteSlug", + "sidebar": "docs", }, Object { "component": "@theme/DocItem", @@ -791,6 +908,7 @@ Array [ "content": "@site/docs/rootRelativeSlug.md", }, "path": "/docs/rootRelativeSlug", + "sidebar": "docs", }, Object { "component": "@theme/DocItem", @@ -799,6 +917,7 @@ Array [ "content": "@site/docs/rootTryToEscapeSlug.md", }, "path": "/docs/rootTryToEscapeSlug", + "sidebar": "docs", }, Object { "component": "@theme/DocItem", diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/docs.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/docs.test.ts index 0324112b3b..7f2574f101 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/docs.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/docs.test.ts @@ -7,7 +7,13 @@ import path from 'path'; 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 { DocFile, @@ -16,6 +22,7 @@ import { VersionMetadata, PluginOptions, EditUrlFunction, + DocNavLink, } from '../types'; import {LoadContext} from '@docusaurus/types'; import {DEFAULT_PLUGIN_ID} from '@docusaurus/core/lib/constants'; @@ -110,7 +117,38 @@ function createTestUtils({ 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', () => { @@ -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"`, ); }); + + 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', () => { diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/index.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/index.test.ts index 98016bd9ae..a17d351bcd 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/index.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/index.test.ts @@ -345,10 +345,11 @@ describe('simple website', () => { permalink: '/docs/foo/bar', }, next: { - title: 'Hello sidebar_label', - permalink: '/docs/', + title: 'rootAbsoluteSlug', + permalink: '/docs/rootAbsoluteSlug', }, sidebar: 'docs', + sidebarPosition: undefined, source: path.posix.join( '@site', posixPath(path.relative(siteDir, currentVersion.contentPath)), @@ -391,8 +392,8 @@ describe('simple website', () => { permalink: '/docs/', slug: '/', previous: { - title: 'baz pagination_label', - permalink: '/docs/foo/bazSlug.html', + title: 'My heading as title', + permalink: '/docs/headingAsTitle', }, sidebar: 'docs', source: path.posix.join( diff --git a/packages/docusaurus-plugin-content-docs/src/docFrontMatter.ts b/packages/docusaurus-plugin-content-docs/src/docFrontMatter.ts index cfb550ab9d..1a20d1485a 100644 --- a/packages/docusaurus-plugin-content-docs/src/docFrontMatter.ts +++ b/packages/docusaurus-plugin-content-docs/src/docFrontMatter.ts @@ -34,6 +34,8 @@ const DocFrontMatterSchema = Joi.object({ pagination_label: Joi.string(), custom_edit_url: URISchema.allow('', null), parse_number_prefixes: Joi.boolean(), + pagination_next: Joi.string().allow(null), + pagination_prev: Joi.string().allow(null), ...FrontMatterTOCHeadingLevels, }).unknown(); diff --git a/packages/docusaurus-plugin-content-docs/src/docs.ts b/packages/docusaurus-plugin-content-docs/src/docs.ts index 8f92614e57..c2532d12cc 100644 --- a/packages/docusaurus-plugin-content-docs/src/docs.ts +++ b/packages/docusaurus-plugin-content-docs/src/docs.ts @@ -7,6 +7,8 @@ import path from 'path'; import fs from 'fs-extra'; +import chalk from 'chalk'; +import {keyBy} from 'lodash'; import { aliasedSitePath, getEditUrl, @@ -23,17 +25,21 @@ import {getFileLastUpdate} from './lastUpdate'; import { DocFile, DocMetadataBase, + DocMetadata, + DocNavLink, LastUpdateData, MetadataOptions, PluginOptions, VersionMetadata, + LoadedVersion, } from './types'; import getSlug from './slug'; import {CURRENT_VERSION_NAME} from './constants'; import {getDocsDirPaths} from './versions'; import {stripPathNumberPrefixes} from './numberPrefix'; import {validateDocFrontMatter} from './docFrontMatter'; -import chalk from 'chalk'; +import type {Sidebars} from './sidebars/types'; +import {createSidebarsUtils} from './sidebars/utils'; type LastUpdateOptions = Pick< PluginOptions, @@ -284,3 +290,77 @@ export function processDocMetadata(args: { throw e; } } + +export function handleNavigation( + docsBase: DocMetadataBase[], + sidebars: Sidebars, + sidebarFilePath: string, +): Pick { + 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}; +} diff --git a/packages/docusaurus-plugin-content-docs/src/index.ts b/packages/docusaurus-plugin-content-docs/src/index.ts index 340ce40018..4c2f522046 100644 --- a/packages/docusaurus-plugin-content-docs/src/index.ts +++ b/packages/docusaurus-plugin-content-docs/src/index.ts @@ -22,9 +22,8 @@ import { } from '@docusaurus/utils'; import {LoadContext, Plugin, RouteConfig} from '@docusaurus/types'; import {loadSidebars} from './sidebars'; -import {createSidebarsUtils} from './sidebars/utils'; import {CategoryMetadataFilenamePattern} from './sidebars/generator'; -import {readVersionDocs, processDocMetadata} from './docs'; +import {readVersionDocs, processDocMetadata, handleNavigation} from './docs'; import {getDocsDirPaths, readVersionsMetadata} from './versions'; import { @@ -35,7 +34,6 @@ import { DocMetadata, GlobalPluginData, VersionMetadata, - DocNavLink, LoadedVersion, DocFile, DocsMarkdownOption, @@ -165,10 +163,6 @@ export default function pluginContentDocs( const docsBase: DocMetadataBase[] = await loadVersionDocsBase( versionMetadata, ); - const docsBaseById: Record = keyBy( - docsBase, - (doc) => doc.id, - ); const sidebars = await loadSidebars(versionMetadata.sidebarFilePath, { sidebarItemsGenerator: options.sidebarItemsGenerator, @@ -180,70 +174,14 @@ export default function pluginContentDocs( 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 { ...versionMetadata, - mainDocId: getMainDoc().unversionedId, + ...handleNavigation( + docsBase, + sidebars, + versionMetadata.sidebarFilePath as string, + ), sidebars, - docs: docs.map(addNavData), }; } diff --git a/packages/docusaurus-plugin-content-docs/src/types.ts b/packages/docusaurus-plugin-content-docs/src/types.ts index 0b75fe4700..eebe226722 100644 --- a/packages/docusaurus-plugin-content-docs/src/types.ts +++ b/packages/docusaurus-plugin-content-docs/src/types.ts @@ -131,6 +131,8 @@ export type DocFrontMatter = { parse_number_prefixes?: boolean; toc_min_heading_level?: number; toc_max_heading_level?: number; + pagination_next?: string | null; + pagination_prev?: string | null; /* eslint-enable camelcase */ }; diff --git a/website/docs/api/plugins/plugin-content-docs.md b/website/docs/api/plugins/plugin-content-docs.md index 942cd7ef2e..ab324d4a09 100644 --- a/website/docs/api/plugins/plugin-content-docs.md +++ b/website/docs/api/plugins/plugin-content-docs.md @@ -252,6 +252,8 @@ Accepted fields: | `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_max_heading_level` | `number` | `3` | The max heading level shown in the table of contents. Must be between 2 and 6. | +| `pagination_next` | string \| null | 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` | string \| null | 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). | | `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. |