diff --git a/packages/docusaurus-mdx-loader/src/loader.ts b/packages/docusaurus-mdx-loader/src/loader.ts index a475220cd5..bde02542be 100644 --- a/packages/docusaurus-mdx-loader/src/loader.ts +++ b/packages/docusaurus-mdx-loader/src/loader.ts @@ -8,7 +8,7 @@ import fs from 'fs-extra'; import logger from '@docusaurus/logger'; import { - parseFrontMatter, + DEFAULT_PARSE_FRONT_MATTER, escapePath, getFileLoaderUtils, getWebpackLoaderCompilerName, @@ -133,7 +133,7 @@ function extractContentTitleData(data: { export async function mdxLoader( this: LoaderContext, - fileString: string, + fileContent: string, ): Promise { const compilerName = getWebpackLoaderCompilerName(this); const callback = this.async(); @@ -143,11 +143,15 @@ export async function mdxLoader( ensureMarkdownConfig(reqOptions); - const {frontMatter} = parseFrontMatter(fileString); + const {frontMatter} = await reqOptions.markdownConfig.parseFrontMatter({ + filePath, + fileContent, + defaultParseFrontMatter: DEFAULT_PARSE_FRONT_MATTER, + }); const mdxFrontMatter = validateMDXFrontMatter(frontMatter.mdx); const preprocessedContent = preprocessor({ - fileContent: fileString, + fileContent, filePath, admonitions: reqOptions.admonitions, markdownConfig: reqOptions.markdownConfig, diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts index f928296bf9..022d4ce749 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts @@ -8,6 +8,7 @@ import {jest} from '@jest/globals'; import path from 'path'; import fs from 'fs-extra'; +import {DEFAULT_PARSE_FRONT_MATTER} from '@docusaurus/utils'; import {DEFAULT_OPTIONS} from '../options'; import {generateBlogPosts} from '../blogUtils'; import {createBlogFeedFiles} from '../feed'; @@ -31,6 +32,8 @@ const DefaultI18N: I18n = { }, }; +const markdown = {parseFrontMatter: DEFAULT_PARSE_FRONT_MATTER}; + function getBlogContentPaths(siteDir: string): BlogContentPaths { return { contentPath: path.resolve(siteDir, 'blog'), @@ -72,6 +75,7 @@ describe.each(['atom', 'rss', 'json'])('%s', (feedType) => { baseUrl: '/', url: 'https://docusaurus.io', favicon: 'image/favicon.ico', + markdown, }; const outDir = path.join(siteDir, 'build-snap'); @@ -110,6 +114,7 @@ describe.each(['atom', 'rss', 'json'])('%s', (feedType) => { baseUrl: '/myBaseUrl/', url: 'https://docusaurus.io', favicon: 'image/favicon.ico', + markdown, }; // Build is quite difficult to mock, so we built the blog beforehand and @@ -152,6 +157,7 @@ describe.each(['atom', 'rss', 'json'])('%s', (feedType) => { baseUrl: '/myBaseUrl/', url: 'https://docusaurus.io', favicon: 'image/favicon.ico', + markdown, }; // Build is quite difficult to mock, so we built the blog beforehand and @@ -204,6 +210,7 @@ describe.each(['atom', 'rss', 'json'])('%s', (feedType) => { baseUrl: '/myBaseUrl/', url: 'https://docusaurus.io', favicon: 'image/favicon.ico', + markdown, }; // Build is quite difficult to mock, so we built the blog beforehand and diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts index 8b392611e7..51b2f63f2f 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts @@ -16,6 +16,7 @@ import type { LoadContext, I18n, Validate, + MarkdownConfig, } from '@docusaurus/types'; import type { BlogPost, @@ -24,6 +25,24 @@ import type { EditUrlFunction, } from '@docusaurus/plugin-content-blog'; +const markdown: MarkdownConfig = { + format: 'mdx', + mermaid: true, + mdx1Compat: { + comments: true, + headingIds: true, + admonitions: true, + }, + parseFrontMatter: async (params) => { + // Reuse the default parser + const result = await params.defaultParseFrontMatter(params); + if (result.frontMatter.title === 'Complex Slug') { + result.frontMatter.custom_frontMatter = 'added by parseFrontMatter'; + } + return result; + }, +}; + function findByTitle( blogPosts: BlogPost[], title: string, @@ -81,6 +100,7 @@ const getPlugin = async ( title: 'Hello', baseUrl: '/', url: 'https://docusaurus.io', + markdown, } as DocusaurusConfig; return pluginContentBlog( { @@ -242,6 +262,7 @@ describe('blog plugin', () => { slug: '/hey/my super path/héllô', title: 'Complex Slug', tags: ['date', 'complex'], + custom_frontMatter: 'added by parseFrontMatter', }, tags: [ { diff --git a/packages/docusaurus-plugin-content-blog/src/blogUtils.ts b/packages/docusaurus-plugin-content-blog/src/blogUtils.ts index 0a8d2a0e0b..3bbb5301bf 100644 --- a/packages/docusaurus-plugin-content-blog/src/blogUtils.ts +++ b/packages/docusaurus-plugin-content-blog/src/blogUtils.ts @@ -11,7 +11,7 @@ import _ from 'lodash'; import logger from '@docusaurus/logger'; import readingTime from 'reading-time'; import { - parseMarkdownString, + parseMarkdownFile, normalizeUrl, aliasedSitePath, getEditUrl, @@ -29,7 +29,7 @@ import { } from '@docusaurus/utils'; import {validateBlogPostFrontMatter} from './frontMatter'; import {type AuthorsMap, getAuthorsMap, getBlogPostAuthors} from './authors'; -import type {LoadContext} from '@docusaurus/types'; +import type {LoadContext, ParseFrontMatter} from '@docusaurus/types'; import type { PluginOptions, ReadingTimeFunction, @@ -180,10 +180,19 @@ function formatBlogPostDate( } } -async function parseBlogPostMarkdownFile(blogSourceAbsolute: string) { - const markdownString = await fs.readFile(blogSourceAbsolute, 'utf-8'); +async function parseBlogPostMarkdownFile({ + filePath, + parseFrontMatter, +}: { + filePath: string; + parseFrontMatter: ParseFrontMatter; +}) { + const fileContent = await fs.readFile(filePath, 'utf-8'); try { - const result = parseMarkdownString(markdownString, { + const result = await parseMarkdownFile({ + filePath, + fileContent, + parseFrontMatter, removeContentTitle: true, }); return { @@ -191,7 +200,7 @@ async function parseBlogPostMarkdownFile(blogSourceAbsolute: string) { frontMatter: validateBlogPostFrontMatter(result.frontMatter), }; } catch (err) { - logger.error`Error while parsing blog post file path=${blogSourceAbsolute}.`; + logger.error`Error while parsing blog post file path=${filePath}.`; throw err; } } @@ -207,7 +216,10 @@ async function processBlogSourceFile( authorsMap?: AuthorsMap, ): Promise { const { - siteConfig: {baseUrl}, + siteConfig: { + baseUrl, + markdown: {parseFrontMatter}, + }, siteDir, i18n, } = context; @@ -228,7 +240,10 @@ async function processBlogSourceFile( const blogSourceAbsolute = path.join(blogDirPath, blogSourceRelative); const {frontMatter, content, contentTitle, excerpt} = - await parseBlogPostMarkdownFile(blogSourceAbsolute); + await parseBlogPostMarkdownFile({ + filePath: blogSourceAbsolute, + parseFrontMatter, + }); const aliasedSource = aliasedSitePath(blogSourceAbsolute, siteDir); diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/docusaurus.config.js b/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/docusaurus.config.js index ae48be19a4..bd7de0da9f 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/docusaurus.config.js +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/docusaurus.config.js @@ -11,4 +11,16 @@ module.exports = { url: 'https://your-docusaurus-site.example.com', baseUrl: '/', favicon: 'img/favicon.ico', + markdown: { + parseFrontMatter: async (params) => { + // Reuse the default parser + const result = await params.defaultParseFrontMatter(params); + if (result.frontMatter.last_update?.author) { + result.frontMatter.last_update.author = + result.frontMatter.last_update.author + + ' (processed by parseFrontMatter)'; + } + return result; + }, + }, }; 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 bc57e764ce..2a8b72873b 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 @@ -463,7 +463,7 @@ exports[`simple website content: data 1`] = ` "frontMatter": { "title": "Custom Last Update", "last_update": { - "author": "Custom Author", + "author": "Custom Author (processed by parseFrontMatter)", "date": "1/1/2000" } } @@ -686,7 +686,7 @@ exports[`simple website content: data 1`] = ` "frontMatter": { "title": "Last Update Author Only", "last_update": { - "author": "Custom Author" + "author": "Custom Author (processed by parseFrontMatter)" } } }", 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 026ac0da65..7309620d73 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/docs.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/docs.test.ts @@ -567,14 +567,14 @@ describe('simple site', () => { description: 'Custom last update', frontMatter: { last_update: { - author: 'Custom Author', + author: 'Custom Author (processed by parseFrontMatter)', date: '1/1/2000', }, title: 'Custom Last Update', }, lastUpdatedAt: new Date('1/1/2000').getTime() / 1000, formattedLastUpdatedAt: 'Jan 1, 2000', - lastUpdatedBy: 'Custom Author', + lastUpdatedBy: 'Custom Author (processed by parseFrontMatter)', sidebarPosition: undefined, tags: [], unlisted: false, @@ -607,13 +607,13 @@ describe('simple site', () => { description: 'Only custom author, so it will still use the date from Git', frontMatter: { last_update: { - author: 'Custom Author', + author: 'Custom Author (processed by parseFrontMatter)', }, title: 'Last Update Author Only', }, lastUpdatedAt: 1539502055, formattedLastUpdatedAt: 'Oct 14, 2018', - lastUpdatedBy: 'Custom Author', + lastUpdatedBy: 'Custom Author (processed by parseFrontMatter)', sidebarPosition: undefined, tags: [], unlisted: false, @@ -685,7 +685,7 @@ describe('simple site', () => { description: 'Custom last update', frontMatter: { last_update: { - author: 'Custom Author', + author: 'Custom Author (processed by parseFrontMatter)', date: '1/1/2000', }, title: 'Custom Last Update', diff --git a/packages/docusaurus-plugin-content-docs/src/docs.ts b/packages/docusaurus-plugin-content-docs/src/docs.ts index 8ee73cf406..2907ac7211 100644 --- a/packages/docusaurus-plugin-content-docs/src/docs.ts +++ b/packages/docusaurus-plugin-content-docs/src/docs.ts @@ -15,7 +15,7 @@ import { getFolderContainingFile, getContentPathList, normalizeUrl, - parseMarkdownString, + parseMarkdownFile, posixPath, Globby, normalizeFrontMatterTags, @@ -140,13 +140,23 @@ async function doProcessDocMetadata({ env: DocEnv; }): Promise { const {source, content, contentPath, filePath} = docFile; - const {siteDir, i18n} = context; + const { + siteDir, + i18n, + siteConfig: { + markdown: {parseFrontMatter}, + }, + } = context; const { frontMatter: unsafeFrontMatter, contentTitle, excerpt, - } = parseMarkdownString(content); + } = await parseMarkdownFile({ + filePath, + fileContent: content, + parseFrontMatter, + }); const frontMatter = validateDocFrontMatter(unsafeFrontMatter); const { diff --git a/packages/docusaurus-plugin-content-pages/src/__tests__/__fixtures__/website/docusaurus.config.js b/packages/docusaurus-plugin-content-pages/src/__tests__/__fixtures__/website/docusaurus.config.js index ae48be19a4..d048d2caf5 100644 --- a/packages/docusaurus-plugin-content-pages/src/__tests__/__fixtures__/website/docusaurus.config.js +++ b/packages/docusaurus-plugin-content-pages/src/__tests__/__fixtures__/website/docusaurus.config.js @@ -11,4 +11,11 @@ module.exports = { url: 'https://your-docusaurus-site.example.com', baseUrl: '/', favicon: 'img/favicon.ico', + markdown: { + parseFrontMatter: async (params) => { + const result = await params.defaultParseFrontMatter(params); + result.frontMatter.custom_frontMatter = 'added by parseFrontMatter'; + return result; + }, + }, }; diff --git a/packages/docusaurus-plugin-content-pages/src/__tests__/__snapshots__/index.test.ts.snap b/packages/docusaurus-plugin-content-pages/src/__tests__/__snapshots__/index.test.ts.snap index 5602849877..fc5fa21967 100644 --- a/packages/docusaurus-plugin-content-pages/src/__tests__/__snapshots__/index.test.ts.snap +++ b/packages/docusaurus-plugin-content-pages/src/__tests__/__snapshots__/index.test.ts.snap @@ -14,7 +14,9 @@ exports[`docusaurus-plugin-content-pages loads simple pages 1`] = ` }, { "description": "Markdown index page", - "frontMatter": {}, + "frontMatter": { + "custom_frontMatter": "added by parseFrontMatter", + }, "permalink": "/hello/", "source": "@site/src/pages/hello/index.md", "title": "Index", @@ -24,6 +26,7 @@ exports[`docusaurus-plugin-content-pages loads simple pages 1`] = ` { "description": "my MDX page", "frontMatter": { + "custom_frontMatter": "added by parseFrontMatter", "description": "my MDX page", "title": "MDX page", }, @@ -40,7 +43,9 @@ exports[`docusaurus-plugin-content-pages loads simple pages 1`] = ` }, { "description": "translated Markdown page", - "frontMatter": {}, + "frontMatter": { + "custom_frontMatter": "added by parseFrontMatter", + }, "permalink": "/hello/translatedMd", "source": "@site/src/pages/hello/translatedMd.md", "title": undefined, @@ -69,7 +74,9 @@ exports[`docusaurus-plugin-content-pages loads simple pages with french translat }, { "description": "Markdown index page", - "frontMatter": {}, + "frontMatter": { + "custom_frontMatter": "added by parseFrontMatter", + }, "permalink": "/fr/hello/", "source": "@site/src/pages/hello/index.md", "title": "Index", @@ -79,6 +86,7 @@ exports[`docusaurus-plugin-content-pages loads simple pages with french translat { "description": "my MDX page", "frontMatter": { + "custom_frontMatter": "added by parseFrontMatter", "description": "my MDX page", "title": "MDX page", }, @@ -95,7 +103,9 @@ exports[`docusaurus-plugin-content-pages loads simple pages with french translat }, { "description": "translated Markdown page (fr)", - "frontMatter": {}, + "frontMatter": { + "custom_frontMatter": "added by parseFrontMatter", + }, "permalink": "/fr/hello/translatedMd", "source": "@site/i18n/fr/docusaurus-plugin-content-pages/hello/translatedMd.md", "title": undefined, diff --git a/packages/docusaurus-plugin-content-pages/src/index.ts b/packages/docusaurus-plugin-content-pages/src/index.ts index e62d07c6cb..a4707110f2 100644 --- a/packages/docusaurus-plugin-content-pages/src/index.ts +++ b/packages/docusaurus-plugin-content-pages/src/index.ts @@ -19,7 +19,7 @@ import { createAbsoluteFilePathMatcher, normalizeUrl, DEFAULT_PLUGIN_ID, - parseMarkdownString, + parseMarkdownFile, isUnlisted, isDraft, } from '@docusaurus/utils'; @@ -113,7 +113,11 @@ export default function pluginContentPages( frontMatter: unsafeFrontMatter, contentTitle, excerpt, - } = parseMarkdownString(content); + } = await parseMarkdownFile({ + filePath: source, + fileContent: content, + parseFrontMatter: siteConfig.markdown.parseFrontMatter, + }); const frontMatter = validatePageFrontMatter(unsafeFrontMatter); if (isDraft({frontMatter})) { diff --git a/packages/docusaurus-types/src/config.d.ts b/packages/docusaurus-types/src/config.d.ts index 3a7bb99ae7..0d872e4001 100644 --- a/packages/docusaurus-types/src/config.d.ts +++ b/packages/docusaurus-types/src/config.d.ts @@ -27,6 +27,20 @@ export type MDX1CompatOptions = { headingIds: boolean; }; +export type ParseFrontMatterParams = {filePath: string; fileContent: string}; +export type ParseFrontMatterResult = { + frontMatter: {[key: string]: unknown}; + content: string; +}; +export type DefaultParseFrontMatter = ( + params: ParseFrontMatterParams, +) => Promise; +export type ParseFrontMatter = ( + params: ParseFrontMatterParams & { + defaultParseFrontMatter: DefaultParseFrontMatter; + }, +) => Promise; + export type MarkdownConfig = { /** * The Markdown format to use by default. @@ -44,6 +58,14 @@ export type MarkdownConfig = { */ format: 'mdx' | 'md' | 'detect'; + /** + * A function callback that lets users parse the front matter themselves. + * Gives the opportunity to read it from a different source, or process it. + * + * @see https://github.com/facebook/docusaurus/issues/5568 + */ + parseFrontMatter: ParseFrontMatter; + /** * Allow mermaid language code blocks to be rendered into Mermaid diagrams: * diff --git a/packages/docusaurus-types/src/index.d.ts b/packages/docusaurus-types/src/index.d.ts index 53e83ce963..257ec57811 100644 --- a/packages/docusaurus-types/src/index.d.ts +++ b/packages/docusaurus-types/src/index.d.ts @@ -9,6 +9,8 @@ export { ReportingSeverity, ThemeConfig, MarkdownConfig, + DefaultParseFrontMatter, + ParseFrontMatter, DocusaurusConfig, Config, } from './config'; diff --git a/packages/docusaurus-utils/src/__tests__/__snapshots__/markdownUtils.test.ts.snap b/packages/docusaurus-utils/src/__tests__/__snapshots__/markdownUtils.test.ts.snap index 5a65f0bb82..8fb7a03dfa 100644 --- a/packages/docusaurus-utils/src/__tests__/__snapshots__/markdownUtils.test.ts.snap +++ b/packages/docusaurus-utils/src/__tests__/__snapshots__/markdownUtils.test.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`parseMarkdownString deletes only first heading 1`] = ` +exports[`parseMarkdownFile deletes only first heading 1`] = ` { "content": "# Markdown Title @@ -15,7 +15,7 @@ test test test # test bar } `; -exports[`parseMarkdownString deletes only first heading 2 1`] = ` +exports[`parseMarkdownFile deletes only first heading 2 1`] = ` { "content": "# test @@ -30,7 +30,7 @@ test3", } `; -exports[`parseMarkdownString does not warn for duplicate title if markdown title is not at the top 1`] = ` +exports[`parseMarkdownFile does not warn for duplicate title if markdown title is not at the top 1`] = ` { "content": "foo @@ -43,7 +43,7 @@ exports[`parseMarkdownString does not warn for duplicate title if markdown title } `; -exports[`parseMarkdownString handles code blocks 1`] = ` +exports[`parseMarkdownFile handles code blocks 1`] = ` { "content": "\`\`\`js code @@ -56,7 +56,7 @@ Content", } `; -exports[`parseMarkdownString handles code blocks 2`] = ` +exports[`parseMarkdownFile handles code blocks 2`] = ` { "content": "\`\`\`\`js Foo @@ -73,7 +73,7 @@ Content", } `; -exports[`parseMarkdownString handles code blocks 3`] = ` +exports[`parseMarkdownFile handles code blocks 3`] = ` { "content": "\`\`\`\`js Foo @@ -88,7 +88,7 @@ Content", } `; -exports[`parseMarkdownString ignores markdown title if its not a first text 1`] = ` +exports[`parseMarkdownFile ignores markdown title if its not a first text 1`] = ` { "content": "foo # test", @@ -98,7 +98,21 @@ exports[`parseMarkdownString ignores markdown title if its not a first text 1`] } `; -exports[`parseMarkdownString parse markdown with front matter 1`] = ` +exports[`parseMarkdownFile parse markdown with custom front matter parser 1`] = ` +{ + "content": "Some text", + "contentTitle": undefined, + "excerpt": "Some text", + "frontMatter": { + "age": 84, + "extra": "value", + "great": true, + "title": "Frontmatter title", + }, +} +`; + +exports[`parseMarkdownFile parse markdown with front matter 1`] = ` { "content": "Some text", "contentTitle": undefined, @@ -109,7 +123,7 @@ exports[`parseMarkdownString parse markdown with front matter 1`] = ` } `; -exports[`parseMarkdownString parses first heading as contentTitle 1`] = ` +exports[`parseMarkdownFile parses first heading as contentTitle 1`] = ` { "content": "# Markdown Title @@ -120,7 +134,7 @@ Some text", } `; -exports[`parseMarkdownString parses front-matter and ignore h2 1`] = ` +exports[`parseMarkdownFile parses front-matter and ignore h2 1`] = ` { "content": "## test", "contentTitle": undefined, @@ -131,7 +145,7 @@ exports[`parseMarkdownString parses front-matter and ignore h2 1`] = ` } `; -exports[`parseMarkdownString parses title only 1`] = ` +exports[`parseMarkdownFile parses title only 1`] = ` { "content": "# test", "contentTitle": "test", @@ -140,7 +154,7 @@ exports[`parseMarkdownString parses title only 1`] = ` } `; -exports[`parseMarkdownString parses title only alternate 1`] = ` +exports[`parseMarkdownFile parses title only alternate 1`] = ` { "content": "test ===", @@ -150,7 +164,7 @@ exports[`parseMarkdownString parses title only alternate 1`] = ` } `; -exports[`parseMarkdownString reads front matter only 1`] = ` +exports[`parseMarkdownFile reads front matter only 1`] = ` { "content": "", "contentTitle": undefined, @@ -161,7 +175,7 @@ exports[`parseMarkdownString reads front matter only 1`] = ` } `; -exports[`parseMarkdownString warns about duplicate titles (front matter + markdown alternate) 1`] = ` +exports[`parseMarkdownFile warns about duplicate titles (front matter + markdown alternate) 1`] = ` { "content": "Markdown Title alternate ================ @@ -175,7 +189,7 @@ Some text", } `; -exports[`parseMarkdownString warns about duplicate titles (front matter + markdown) 1`] = ` +exports[`parseMarkdownFile warns about duplicate titles (front matter + markdown) 1`] = ` { "content": "# Markdown Title @@ -188,7 +202,7 @@ Some text", } `; -exports[`parseMarkdownString warns about duplicate titles 1`] = ` +exports[`parseMarkdownFile warns about duplicate titles 1`] = ` { "content": "# test", "contentTitle": "test", diff --git a/packages/docusaurus-utils/src/__tests__/markdownUtils.test.ts b/packages/docusaurus-utils/src/__tests__/markdownUtils.test.ts index 182c95b05f..0e04dbf5c2 100644 --- a/packages/docusaurus-utils/src/__tests__/markdownUtils.test.ts +++ b/packages/docusaurus-utils/src/__tests__/markdownUtils.test.ts @@ -9,12 +9,14 @@ import dedent from 'dedent'; import { createExcerpt, parseMarkdownContentTitle, - parseMarkdownString, parseMarkdownHeadingId, writeMarkdownHeadingId, escapeMarkdownHeadingIds, unwrapMdxCodeBlocks, admonitionTitleToDirectiveLabel, + parseMarkdownFile, + DEFAULT_PARSE_FRONT_MATTER, + parseFileContentFrontMatter, } from '../markdownUtils'; describe('createExcerpt', () => { @@ -623,32 +625,110 @@ Lorem Ipsum }); }); -describe('parseMarkdownString', () => { - it('parse markdown with front matter', () => { - expect( - parseMarkdownString(dedent` +describe('parseFileContentFrontMatter', () => { + function test(fileContent: string) { + return parseFileContentFrontMatter(fileContent); + } + + it('can parse front matter', () => { + const input = dedent` + --- + title: Frontmatter title + author: + age: 42 + birth: 2000-07-23 + --- + + Some text + `; + + const expectedResult = { + content: 'Some text', + frontMatter: { + title: 'Frontmatter title', + author: {age: 42, birth: new Date('2000-07-23')}, + }, + }; + + const result = test(input) as typeof expectedResult; + expect(result).toEqual(expectedResult); + expect(result.frontMatter.author.birth).toBeInstanceOf(Date); + + // A regression test, ensure we don't return gray-matter cached objects + result.frontMatter.title = 'modified'; + // @ts-expect-error: ok + result.frontMatter.author.age = 53; + expect(test(input)).toEqual(expectedResult); + }); +}); + +describe('parseMarkdownFile', () => { + async function test( + fileContent: string, + options?: Partial>[0], + ) { + return parseMarkdownFile({ + fileContent, + filePath: 'some-file-path.mdx', + parseFrontMatter: DEFAULT_PARSE_FRONT_MATTER, + ...options, + }); + } + + it('parse markdown with front matter', async () => { + await expect( + test(dedent` --- title: Frontmatter title --- Some text `), - ).toMatchSnapshot(); + ).resolves.toMatchSnapshot(); }); - it('parses first heading as contentTitle', () => { - expect( - parseMarkdownString(dedent` + it('parse markdown with custom front matter parser', async () => { + await expect( + test( + dedent` + --- + title: Frontmatter title + age: 42 + --- + + Some text + `, + { + parseFrontMatter: async (params) => { + const result = await params.defaultParseFrontMatter(params); + return { + ...result, + frontMatter: { + ...result.frontMatter, + age: result.frontMatter.age * 2, + extra: 'value', + great: true, + }, + }; + }, + }, + ), + ).resolves.toMatchSnapshot(); + }); + + it('parses first heading as contentTitle', async () => { + await expect( + test(dedent` # Markdown Title Some text `), - ).toMatchSnapshot(); + ).resolves.toMatchSnapshot(); }); - it('warns about duplicate titles (front matter + markdown)', () => { - expect( - parseMarkdownString(dedent` + it('warns about duplicate titles (front matter + markdown)', async () => { + await expect( + test(dedent` --- title: Frontmatter title --- @@ -657,12 +737,12 @@ describe('parseMarkdownString', () => { Some text `), - ).toMatchSnapshot(); + ).resolves.toMatchSnapshot(); }); - it('warns about duplicate titles (front matter + markdown alternate)', () => { - expect( - parseMarkdownString(dedent` + it('warns about duplicate titles (front matter + markdown alternate)', async () => { + await expect( + test(dedent` --- title: Frontmatter title --- @@ -672,12 +752,12 @@ describe('parseMarkdownString', () => { Some text `), - ).toMatchSnapshot(); + ).resolves.toMatchSnapshot(); }); - it('does not warn for duplicate title if markdown title is not at the top', () => { - expect( - parseMarkdownString(dedent` + it('does not warn for duplicate title if markdown title is not at the top', async () => { + await expect( + test(dedent` --- title: Frontmatter title --- @@ -686,12 +766,12 @@ describe('parseMarkdownString', () => { # Markdown Title `), - ).toMatchSnapshot(); + ).resolves.toMatchSnapshot(); }); - it('deletes only first heading', () => { - expect( - parseMarkdownString(dedent` + it('deletes only first heading', async () => { + await expect( + test(dedent` # Markdown Title test test test # test bar @@ -700,12 +780,12 @@ describe('parseMarkdownString', () => { ### Markdown Title h3 `), - ).toMatchSnapshot(); + ).resolves.toMatchSnapshot(); }); - it('parses front-matter and ignore h2', () => { - expect( - parseMarkdownString( + it('parses front-matter and ignore h2', async () => { + await expect( + test( dedent` --- title: Frontmatter title @@ -713,55 +793,55 @@ describe('parseMarkdownString', () => { ## test `, ), - ).toMatchSnapshot(); + ).resolves.toMatchSnapshot(); }); - it('reads front matter only', () => { - expect( - parseMarkdownString(dedent` + it('reads front matter only', async () => { + await expect( + test(dedent` --- title: test --- `), - ).toMatchSnapshot(); + ).resolves.toMatchSnapshot(); }); - it('parses title only', () => { - expect(parseMarkdownString('# test')).toMatchSnapshot(); + it('parses title only', async () => { + await expect(test('# test')).resolves.toMatchSnapshot(); }); - it('parses title only alternate', () => { - expect( - parseMarkdownString(dedent` + it('parses title only alternate', async () => { + await expect( + test(dedent` test === `), - ).toMatchSnapshot(); + ).resolves.toMatchSnapshot(); }); - it('warns about duplicate titles', () => { - expect( - parseMarkdownString(dedent` + it('warns about duplicate titles', async () => { + await expect( + test(dedent` --- title: Frontmatter title --- # test `), - ).toMatchSnapshot(); + ).resolves.toMatchSnapshot(); }); - it('ignores markdown title if its not a first text', () => { - expect( - parseMarkdownString(dedent` + it('ignores markdown title if its not a first text', async () => { + await expect( + test(dedent` foo # test `), - ).toMatchSnapshot(); + ).resolves.toMatchSnapshot(); }); - it('deletes only first heading 2', () => { - expect( - parseMarkdownString(dedent` + it('deletes only first heading 2', async () => { + await expect( + test(dedent` # test test test test test test test @@ -770,21 +850,21 @@ describe('parseMarkdownString', () => { ### test test3 `), - ).toMatchSnapshot(); + ).resolves.toMatchSnapshot(); }); - it('handles code blocks', () => { - expect( - parseMarkdownString(dedent` + it('handles code blocks', async () => { + await expect( + test(dedent` \`\`\`js code \`\`\` Content `), - ).toMatchSnapshot(); - expect( - parseMarkdownString(dedent` + ).resolves.toMatchSnapshot(); + await expect( + test(dedent` \`\`\`\`js Foo \`\`\`diff @@ -795,9 +875,9 @@ describe('parseMarkdownString', () => { Content `), - ).toMatchSnapshot(); - expect( - parseMarkdownString(dedent` + ).resolves.toMatchSnapshot(); + await expect( + test(dedent` \`\`\`\`js Foo \`\`\`diff @@ -806,17 +886,17 @@ describe('parseMarkdownString', () => { Content `), - ).toMatchSnapshot(); + ).resolves.toMatchSnapshot(); }); - it('throws for invalid front matter', () => { - expect(() => - parseMarkdownString(dedent` + it('throws for invalid front matter', async () => { + await expect( + test(dedent` --- foo: f: a --- `), - ).toThrowErrorMatchingInlineSnapshot(` + ).rejects.toThrowErrorMatchingInlineSnapshot(` "incomplete explicit mapping pair; a key node is missed; or followed by a non-tabulated empty line at line 2, column 7: foo: f: a ^" diff --git a/packages/docusaurus-utils/src/index.ts b/packages/docusaurus-utils/src/index.ts index 5bb77a0c05..5b374898bf 100644 --- a/packages/docusaurus-utils/src/index.ts +++ b/packages/docusaurus-utils/src/index.ts @@ -70,9 +70,9 @@ export { unwrapMdxCodeBlocks, admonitionTitleToDirectiveLabel, createExcerpt, - parseFrontMatter, + DEFAULT_PARSE_FRONT_MATTER, parseMarkdownContentTitle, - parseMarkdownString, + parseMarkdownFile, writeMarkdownHeadingId, type WriteHeadingIDOptions, } from './markdownUtils'; diff --git a/packages/docusaurus-utils/src/markdownUtils.ts b/packages/docusaurus-utils/src/markdownUtils.ts index a2ca3db101..87aac88f09 100644 --- a/packages/docusaurus-utils/src/markdownUtils.ts +++ b/packages/docusaurus-utils/src/markdownUtils.ts @@ -8,6 +8,10 @@ import logger from '@docusaurus/logger'; import matter from 'gray-matter'; import {createSlugger, type Slugger, type SluggerOptions} from './slugger'; +import type { + ParseFrontMatter, + DefaultParseFrontMatter, +} from '@docusaurus/types'; // Some utilities for parsing Markdown content. These things are only used on // server-side when we infer metadata like `title` and `description` from the @@ -214,19 +218,40 @@ export function createExcerpt(fileString: string): string | undefined { * --- * ``` */ -export function parseFrontMatter(markdownFileContent: string): { +export function parseFileContentFrontMatter(fileContent: string): { /** Front matter as parsed by gray-matter. */ frontMatter: {[key: string]: unknown}; /** The remaining content, trimmed. */ content: string; } { - const {data, content} = matter(markdownFileContent); + // TODO Docusaurus v4: replace gray-matter by a better lib + // gray-matter is unmaintained, not flexible, and the code doesn't look good + const {data, content} = matter(fileContent); + + // gray-matter has an undocumented front matter caching behavior + // https://github.com/jonschlinkert/gray-matter/blob/ce67a86dba419381db0dd01cc84e2d30a1d1e6a5/index.js#L39 + // Unfortunately, this becomes a problem when we mutate returned front matter + // We want to make it possible as part of the parseFrontMatter API + // So we make it safe to mutate by always providing a deep copy + const frontMatter = + // And of course structuredClone() doesn't work well with Date in Jest... + // See https://github.com/jestjs/jest/issues/2549 + // So we parse again for tests with a {} option object + // This undocumented empty option object disables gray-matter caching.. + process.env.JEST_WORKER_ID + ? matter(fileContent, {}).data + : structuredClone(data); + return { - frontMatter: data, + frontMatter, content: content.trim(), }; } +export const DEFAULT_PARSE_FRONT_MATTER: DefaultParseFrontMatter = async ( + params, +) => parseFileContentFrontMatter(params.fileContent); + function toTextContentTitle(contentTitle: string): string { return contentTitle.replace(/`(?[^`]*)`/g, '$'); } @@ -309,10 +334,16 @@ export function parseMarkdownContentTitle( * @throws Throws when `parseFrontMatter` throws, usually because of invalid * syntax. */ -export function parseMarkdownString( - markdownFileContent: string, - options?: ParseMarkdownContentTitleOptions, -): { +export async function parseMarkdownFile({ + filePath, + fileContent, + parseFrontMatter, + removeContentTitle, +}: { + filePath: string; + fileContent: string; + parseFrontMatter: ParseFrontMatter; +} & ParseMarkdownContentTitleOptions): Promise<{ /** @see {@link parseFrontMatter} */ frontMatter: {[key: string]: unknown}; /** @see {@link parseMarkdownContentTitle} */ @@ -324,14 +355,18 @@ export function parseMarkdownString( * the `removeContentTitle` option. */ content: string; -} { +}> { try { const {frontMatter, content: contentWithoutFrontMatter} = - parseFrontMatter(markdownFileContent); + await parseFrontMatter({ + filePath, + fileContent, + defaultParseFrontMatter: DEFAULT_PARSE_FRONT_MATTER, + }); const {content, contentTitle} = parseMarkdownContentTitle( contentWithoutFrontMatter, - options, + {removeContentTitle}, ); const excerpt = createExcerpt(content); diff --git a/packages/docusaurus/src/server/__tests__/__snapshots__/config.test.ts.snap b/packages/docusaurus/src/server/__tests__/__snapshots__/config.test.ts.snap index 2ed2b796fd..c5d21f81ae 100644 --- a/packages/docusaurus/src/server/__tests__/__snapshots__/config.test.ts.snap +++ b/packages/docusaurus/src/server/__tests__/__snapshots__/config.test.ts.snap @@ -24,6 +24,7 @@ exports[`loadSiteConfig website with .cjs siteConfig 1`] = ` "headingIds": true, }, "mermaid": false, + "parseFrontMatter": [Function], "preprocessor": undefined, }, "noIndex": false, @@ -72,6 +73,7 @@ exports[`loadSiteConfig website with ts + js config 1`] = ` "headingIds": true, }, "mermaid": false, + "parseFrontMatter": [Function], "preprocessor": undefined, }, "noIndex": false, @@ -120,6 +122,7 @@ exports[`loadSiteConfig website with valid JS CJS config 1`] = ` "headingIds": true, }, "mermaid": false, + "parseFrontMatter": [Function], "preprocessor": undefined, }, "noIndex": false, @@ -168,6 +171,7 @@ exports[`loadSiteConfig website with valid JS ESM config 1`] = ` "headingIds": true, }, "mermaid": false, + "parseFrontMatter": [Function], "preprocessor": undefined, }, "noIndex": false, @@ -216,6 +220,7 @@ exports[`loadSiteConfig website with valid TypeScript CJS config 1`] = ` "headingIds": true, }, "mermaid": false, + "parseFrontMatter": [Function], "preprocessor": undefined, }, "noIndex": false, @@ -264,6 +269,7 @@ exports[`loadSiteConfig website with valid TypeScript ESM config 1`] = ` "headingIds": true, }, "mermaid": false, + "parseFrontMatter": [Function], "preprocessor": undefined, }, "noIndex": false, @@ -312,6 +318,7 @@ exports[`loadSiteConfig website with valid async config 1`] = ` "headingIds": true, }, "mermaid": false, + "parseFrontMatter": [Function], "preprocessor": undefined, }, "noIndex": false, @@ -362,6 +369,7 @@ exports[`loadSiteConfig website with valid async config creator function 1`] = ` "headingIds": true, }, "mermaid": false, + "parseFrontMatter": [Function], "preprocessor": undefined, }, "noIndex": false, @@ -412,6 +420,7 @@ exports[`loadSiteConfig website with valid config creator function 1`] = ` "headingIds": true, }, "mermaid": false, + "parseFrontMatter": [Function], "preprocessor": undefined, }, "noIndex": false, @@ -465,6 +474,7 @@ exports[`loadSiteConfig website with valid siteConfig 1`] = ` "headingIds": true, }, "mermaid": false, + "parseFrontMatter": [Function], "preprocessor": undefined, }, "noIndex": false, diff --git a/packages/docusaurus/src/server/__tests__/__snapshots__/index.test.ts.snap b/packages/docusaurus/src/server/__tests__/__snapshots__/index.test.ts.snap index 45b94b8694..06caa4c997 100644 --- a/packages/docusaurus/src/server/__tests__/__snapshots__/index.test.ts.snap +++ b/packages/docusaurus/src/server/__tests__/__snapshots__/index.test.ts.snap @@ -98,6 +98,7 @@ exports[`load loads props for site with custom i18n path 1`] = ` "headingIds": true, }, "mermaid": false, + "parseFrontMatter": [Function], "preprocessor": undefined, }, "noIndex": false, diff --git a/packages/docusaurus/src/server/__tests__/configValidation.test.ts b/packages/docusaurus/src/server/__tests__/configValidation.test.ts index b3bc7b2611..925207b636 100644 --- a/packages/docusaurus/src/server/__tests__/configValidation.test.ts +++ b/packages/docusaurus/src/server/__tests__/configValidation.test.ts @@ -61,6 +61,8 @@ describe('normalizeConfig', () => { markdown: { format: 'md', mermaid: true, + parseFrontMatter: async (params) => + params.defaultParseFrontMatter(params), preprocessor: ({fileContent}) => fileContent, mdx1Compat: { comments: true, @@ -504,6 +506,8 @@ describe('markdown', () => { const markdown: DocusaurusConfig['markdown'] = { format: 'md', mermaid: true, + parseFrontMatter: async (params) => + params.defaultParseFrontMatter(params), preprocessor: ({fileContent}) => fileContent, mdx1Compat: { comments: false, diff --git a/packages/docusaurus/src/server/configValidation.ts b/packages/docusaurus/src/server/configValidation.ts index 3f9de2ce68..193b8eb6a7 100644 --- a/packages/docusaurus/src/server/configValidation.ts +++ b/packages/docusaurus/src/server/configValidation.ts @@ -6,6 +6,7 @@ */ import { + DEFAULT_PARSE_FRONT_MATTER, DEFAULT_STATIC_DIR_NAME, DEFAULT_I18N_DIR_NAME, addLeadingSlash, @@ -13,7 +14,11 @@ import { removeTrailingSlash, } from '@docusaurus/utils'; import {Joi, printWarning} from '@docusaurus/utils-validation'; -import type {DocusaurusConfig, I18nConfig} from '@docusaurus/types'; +import type { + DocusaurusConfig, + I18nConfig, + MarkdownConfig, +} from '@docusaurus/types'; const DEFAULT_I18N_LOCALE = 'en'; @@ -24,6 +29,18 @@ export const DEFAULT_I18N_CONFIG: I18nConfig = { localeConfigs: {}, }; +export const DEFAULT_MARKDOWN_CONFIG: MarkdownConfig = { + format: 'mdx', // TODO change this to "detect" in Docusaurus v4? + mermaid: false, + preprocessor: undefined, + parseFrontMatter: DEFAULT_PARSE_FRONT_MATTER, + mdx1Compat: { + comments: true, + admonitions: true, + headingIds: true, + }, +}; + export const DEFAULT_CONFIG: Pick< DocusaurusConfig, | 'i18n' @@ -64,16 +81,7 @@ export const DEFAULT_CONFIG: Pick< tagline: '', baseUrlIssueBanner: true, staticDirectories: [DEFAULT_STATIC_DIR_NAME], - markdown: { - format: 'mdx', // TODO change this to "detect" in Docusaurus v4? - mermaid: false, - preprocessor: undefined, - mdx1Compat: { - comments: true, - admonitions: true, - headingIds: true, - }, - }, + markdown: DEFAULT_MARKDOWN_CONFIG, }; function createPluginSchema(theme: boolean) { @@ -280,6 +288,9 @@ export const ConfigSchema = Joi.object({ format: Joi.string() .equal('mdx', 'md', 'detect') .default(DEFAULT_CONFIG.markdown.format), + parseFrontMatter: Joi.function().default( + () => DEFAULT_CONFIG.markdown.parseFrontMatter, + ), mermaid: Joi.boolean().default(DEFAULT_CONFIG.markdown.mermaid), preprocessor: Joi.function() .arity(1) diff --git a/website/_dogfooding/_docs tests/tests/visibility/force-unlisted.mdx b/website/_dogfooding/_docs tests/tests/visibility/force-unlisted.mdx new file mode 100644 index 0000000000..0801898442 --- /dev/null +++ b/website/_dogfooding/_docs tests/tests/visibility/force-unlisted.mdx @@ -0,0 +1,10 @@ +--- +unlisted: false +force_unlisted_parseFrontMatter_test: true +--- + +# force_unlisted_parseFrontMatter_test + +This doc is hidden despite `unlisted: false` + +We use `parseFrontMatter` to force it to true thanks to `force_unlisted_parseFrontMatter_test: true` diff --git a/website/_dogfooding/_docs tests/tests/visibility/index.mdx b/website/_dogfooding/_docs tests/tests/visibility/index.mdx index 88a78b5d6b..71c3712f2d 100644 --- a/website/_dogfooding/_docs tests/tests/visibility/index.mdx +++ b/website/_dogfooding/_docs tests/tests/visibility/index.mdx @@ -24,6 +24,7 @@ In production, unlisted items should remain accessible, but be hidden in the sid - [./some-unlisteds/unlisted1.md](./some-unlisteds/unlisted1.mdx) - [./some-unlisteds/unlisted2.md](./some-unlisteds/unlisted2.mdx) - [./some-unlisteds/unlisted-subcategory/unlisted3.md](./some-unlisteds/unlisted-subcategory/unlisted3.mdx) +- [./force-unlisted.mdx](./force-unlisted.mdx) --- diff --git a/website/_dogfooding/dogfooding.config.ts b/website/_dogfooding/dogfooding.config.ts index 10f70d71fb..3e57c13c36 100644 --- a/website/_dogfooding/dogfooding.config.ts +++ b/website/_dogfooding/dogfooding.config.ts @@ -10,6 +10,15 @@ import type {Options as DocsOptions} from '@docusaurus/plugin-content-docs'; import type {Options as BlogOptions} from '@docusaurus/plugin-content-blog'; import type {Options as PageOptions} from '@docusaurus/plugin-content-pages'; +export function dogfoodingTransformFrontMatter(frontMatter: { + [key: string]: unknown; +}): {[key: string]: unknown} { + if (frontMatter.force_unlisted_parseFrontMatter_test === true) { + return {...frontMatter, unlisted: true}; + } + return frontMatter; +} + export const dogfoodingThemeInstances: PluginConfig[] = [ function swizzleThemeTests(): Plugin { return { diff --git a/website/docs/api/docusaurus.config.js.mdx b/website/docs/api/docusaurus.config.js.mdx index e7357f4bf5..48cd331e40 100644 --- a/website/docs/api/docusaurus.config.js.mdx +++ b/website/docs/api/docusaurus.config.js.mdx @@ -421,10 +421,20 @@ type MDX1CompatOptions = headingIds: boolean; }; +export type ParseFrontMatter = (params: { + filePath: string; + fileContent: string; + defaultParseFrontMatter: ParseFrontMatter; +}) => Promise<{ + frontMatter: {[key: string]: unknown}; + content: string; +}>; + type MarkdownConfig = { format: 'mdx' | 'md' | 'detect'; mermaid: boolean; preprocessor?: MarkdownPreprocessor; + parseFrontMatter?: ParseFrontMatter; mdx1Compat: MDX1CompatOptions; }; ``` @@ -439,6 +449,12 @@ export default { preprocessor: ({filePath, fileContent}) => { return fileContent.replaceAll('{{MY_VAR}}', 'MY_VALUE'); }, + parseFrontMatter: async (params) => { + const result = await params.defaultParseFrontMatter(params); + result.frontMatter.description = + result.frontMatter.description?.replaceAll('{{MY_VAR}}', 'MY_VALUE'); + return result; + }, mdx1Compat: { comments: true, admonitions: true, @@ -457,6 +473,7 @@ export default { | `format` | `'mdx' \| 'md' \| 'detect'` | `'mdx'` | The default parser format to use for Markdown content. Using 'detect' will select the appropriate format automatically based on file extensions: `.md` vs `.mdx`. | | `mermaid` | `boolean` | `false` | When `true`, allows Docusaurus to render Markdown code blocks with `mermaid` language as Mermaid diagrams. | | `preprocessor` | `MarkdownPreprocessor` | `undefined` | Gives you the ability to alter the Markdown content string before parsing. Use it as a last-resort escape hatch or workaround: it is almost always better to implement a Remark/Rehype plugin. | +| `parseFrontMatter` | `ParseFrontMatter` | `undefined` | Gives you the ability to provide your own front matter parser, or to enhance the default parser. Read our [front matter guide](../guides/markdown-features/markdown-features-intro.mdx#front-matter) for details. | | `mdx1Compat` | `MDX1CompatOptions` | `{comments: true, admonitions: true, headingIds: true}` | Compatibility options to make it easier to upgrade to Docusaurus v3+. | ```mdx-code-block diff --git a/website/docs/guides/markdown-features/markdown-features-intro.mdx b/website/docs/guides/markdown-features/markdown-features-intro.mdx index b3116e2998..b6bcd756be 100644 --- a/website/docs/guides/markdown-features/markdown-features-intro.mdx +++ b/website/docs/guides/markdown-features/markdown-features-intro.mdx @@ -120,6 +120,45 @@ The API documentation of each official plugin lists the supported attributes: ::: +:::tip enhance your front matter + +Use the [Markdown config `parseFrontMatter` function](../../api/docusaurus.config.js.mdx#markdown) to provide your own front matter parser, or to enhance the default parser. + +It is possible to reuse the default parser to wrap it with your own custom proprietary logic. This makes it possible to implement convenient front matter transformations, shortcuts, or to integrate with external systems using front matter that Docusaurus plugins do not support. + +```js title="docusaurus.config.js" +export default { + markdown: { + // highlight-start + parseFrontMatter: async (params) => { + // Reuse the default parser + const result = await params.defaultParseFrontMatter(params); + + // Process front matter description placeholders + result.frontMatter.description = + result.frontMatter.description?.replaceAll('{{MY_VAR}}', 'MY_VALUE'); + + // Create your own front matter shortcut + if (result.frontMatter.i_do_not_want_docs_pagination) { + result.frontMatter.pagination_prev = null; + result.frontMatter.pagination_next = null; + } + + // Rename an unsupported front matter coming from another system + if (result.frontMatter.cms_seo_summary) { + result.frontMatter.description = result.frontMatter.cms_seo_summary; + delete result.frontMatter.cms_seo_summary; + } + + return result; + }, + // highlight-end + }, +}; +``` + +::: + ## Quotes {#quotes} Markdown quotes are beautifully styled: diff --git a/website/docusaurus.config.ts b/website/docusaurus.config.ts index b3dca14db3..3f88352dd6 100644 --- a/website/docusaurus.config.ts +++ b/website/docusaurus.config.ts @@ -17,6 +17,7 @@ import { dogfoodingPluginInstances, dogfoodingThemeInstances, dogfoodingRedirects, + dogfoodingTransformFrontMatter, } from './_dogfooding/dogfooding.config'; import ConfigLocalized from './docusaurus.config.localized.json'; @@ -176,6 +177,13 @@ export default async function createConfigAsync() { mdx1Compat: { // comments: false, }, + parseFrontMatter: async (params) => { + const result = await params.defaultParseFrontMatter(params); + return { + ...result, + frontMatter: dogfoodingTransformFrontMatter(result.frontMatter), + }; + }, preprocessor: ({filePath, fileContent}) => { let result = fileContent;