diff --git a/packages/docusaurus-plugin-content-pages/src/__tests__/__fixtures__/website/src/pages/hello/index.md b/packages/docusaurus-plugin-content-pages/src/__tests__/__fixtures__/website/src/pages/hello/index.md index 3d83ddb74e..eb1ef1c7cb 100644 --- a/packages/docusaurus-plugin-content-pages/src/__tests__/__fixtures__/website/src/pages/hello/index.md +++ b/packages/docusaurus-plugin-content-pages/src/__tests__/__fixtures__/website/src/pages/hello/index.md @@ -1,2 +1,3 @@ +# Index -Markdown index page \ No newline at end of file +Markdown index page diff --git a/packages/docusaurus-plugin-content-pages/src/__tests__/index.test.ts b/packages/docusaurus-plugin-content-pages/src/__tests__/index.test.ts index 6b2dd03b0f..db1979436e 100644 --- a/packages/docusaurus-plugin-content-pages/src/__tests__/index.test.ts +++ b/packages/docusaurus-plugin-content-pages/src/__tests__/index.test.ts @@ -39,11 +39,20 @@ describe('docusaurus-plugin-content-pages', () => { type: 'mdx', permalink: '/hello/', source: path.posix.join('@site', pluginPath, 'hello', 'index.md'), + description: 'Markdown index page', + frontMatter: {}, + title: 'Index', }, { type: 'mdx', permalink: '/hello/mdxPage', source: path.posix.join('@site', pluginPath, 'hello', 'mdxPage.mdx'), + description: 'my mdx page', + title: 'mdx page', + frontMatter: { + description: 'my mdx page', + title: 'mdx page', + }, }, { type: 'jsx', @@ -64,6 +73,9 @@ describe('docusaurus-plugin-content-pages', () => { 'hello', 'translatedMd.md', ), + description: 'translated markdown page', + frontMatter: {}, + title: undefined, }, { type: 'jsx', @@ -113,11 +125,20 @@ describe('docusaurus-plugin-content-pages', () => { type: 'mdx', permalink: '/hello/', source: path.posix.join('@site', pluginPath, 'hello', 'index.md'), + description: 'Markdown index page', + frontMatter: {}, + title: 'Index', }, { type: 'mdx', permalink: '/hello/mdxPage', source: path.posix.join('@site', pluginPath, 'hello', 'mdxPage.mdx'), + description: 'my mdx page', + title: 'mdx page', + frontMatter: { + description: 'my mdx page', + title: 'mdx page', + }, }, { type: 'jsx', @@ -128,6 +149,9 @@ describe('docusaurus-plugin-content-pages', () => { type: 'mdx', permalink: '/hello/translatedMd', source: path.posix.join(frTranslationsPath, 'hello', 'translatedMd.md'), + description: 'translated markdown page (fr)', + frontMatter: {}, + title: undefined, }, { type: 'jsx', diff --git a/packages/docusaurus-plugin-content-pages/src/index.ts b/packages/docusaurus-plugin-content-pages/src/index.ts index caa99d00ff..c553d08444 100644 --- a/packages/docusaurus-plugin-content-pages/src/index.ts +++ b/packages/docusaurus-plugin-content-pages/src/index.ts @@ -19,6 +19,7 @@ import { createAbsoluteFilePathMatcher, normalizeUrl, DEFAULT_PLUGIN_ID, + parseMarkdownString, } from '@docusaurus/utils'; import type { LoadContext, @@ -30,9 +31,10 @@ import type { import type {Configuration} from 'webpack'; import admonitions from 'remark-admonitions'; import {PluginOptionSchema} from './pluginOptionSchema'; +import {validatePageFrontMatter} from './pageFrontMatter'; -import type {LoadedContent, Metadata, PagesContentPaths} from './types'; -import type {PluginOptions} from '@docusaurus/plugin-content-pages'; +import type {LoadedContent, PagesContentPaths} from './types'; +import type {PluginOptions, Metadata} from '@docusaurus/plugin-content-pages'; export function getContentPathList(contentPaths: PagesContentPaths): string[] { return [contentPaths.contentPathLocalized, contentPaths.contentPath]; @@ -111,11 +113,20 @@ export default async function pluginContentPages( encodePath(fileToPath(relativeSource)), ]); if (isMarkdownSource(relativeSource)) { - // TODO: missing frontmatter validation/normalization here + const content = await fs.readFile(source, 'utf-8'); + const { + frontMatter: unsafeFrontMatter, + contentTitle, + excerpt, + } = parseMarkdownString(content); + const frontMatter = validatePageFrontMatter(unsafeFrontMatter); return { type: 'mdx', permalink, source: aliasedSourcePath, + title: frontMatter.title ?? contentTitle, + description: frontMatter.description ?? excerpt, + frontMatter, }; } else { return { diff --git a/packages/docusaurus-plugin-content-pages/src/pageFrontMatter.ts b/packages/docusaurus-plugin-content-pages/src/pageFrontMatter.ts new file mode 100644 index 0000000000..ef03c3d00d --- /dev/null +++ b/packages/docusaurus-plugin-content-pages/src/pageFrontMatter.ts @@ -0,0 +1,27 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { + Joi, + validateFrontMatter, + FrontMatterTOCHeadingLevels, +} from '@docusaurus/utils-validation'; +import type {FrontMatter} from '@docusaurus/plugin-content-pages'; + +const PageFrontMatterSchema = Joi.object({ + title: Joi.string(), + description: Joi.string(), + wrapperClassName: Joi.string(), + hide_table_of_contents: Joi.string(), + ...FrontMatterTOCHeadingLevels, +}); + +export function validatePageFrontMatter( + frontMatter: Record, +): FrontMatter { + return validateFrontMatter(frontMatter, PageFrontMatterSchema); +} diff --git a/packages/docusaurus-plugin-content-pages/src/plugin-content-pages.d.ts b/packages/docusaurus-plugin-content-pages/src/plugin-content-pages.d.ts index 47caf52b63..e4991dea76 100644 --- a/packages/docusaurus-plugin-content-pages/src/plugin-content-pages.d.ts +++ b/packages/docusaurus-plugin-content-pages/src/plugin-content-pages.d.ts @@ -19,22 +19,45 @@ declare module '@docusaurus/plugin-content-pages' { }; export type Options = Partial; + + export type FrontMatter = { + readonly title?: string; + readonly description?: string; + readonly wrapperClassName?: string; + readonly hide_table_of_contents?: string; + readonly toc_min_heading_level?: number; + readonly toc_max_heading_level?: number; + }; + + export type JSXPageMetadata = { + type: 'jsx'; + permalink: string; + source: string; + }; + + export type MDXPageMetadata = { + type: 'mdx'; + permalink: string; + source: string; + frontMatter: FrontMatter & Record; + title?: string; + description?: string; + }; + + export type Metadata = JSXPageMetadata | MDXPageMetadata; } declare module '@theme/MDXPage' { import type {TOCItem} from '@docusaurus/types'; + import type { + MDXPageMetadata, + FrontMatter, + } from '@docusaurus/plugin-content-pages'; export interface Props { readonly content: { - readonly frontMatter: { - readonly title: string; - readonly description: string; - readonly wrapperClassName?: string; - readonly hide_table_of_contents?: string; - readonly toc_min_heading_level?: number; - readonly toc_max_heading_level?: number; - }; - readonly metadata: {readonly permalink: string}; + readonly frontMatter: FrontMatter; + readonly metadata: MDXPageMetadata; readonly toc: readonly TOCItem[]; (): JSX.Element; }; diff --git a/packages/docusaurus-plugin-content-pages/src/types.ts b/packages/docusaurus-plugin-content-pages/src/types.ts index 70873ac56b..e2833c5692 100644 --- a/packages/docusaurus-plugin-content-pages/src/types.ts +++ b/packages/docusaurus-plugin-content-pages/src/types.ts @@ -5,19 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -export type JSXPageMetadata = { - type: 'jsx'; - permalink: string; - source: string; -}; - -export type MDXPageMetadata = { - type: 'mdx'; - permalink: string; - source: string; -}; - -export type Metadata = JSXPageMetadata | MDXPageMetadata; +import type {Metadata} from '@docusaurus/plugin-content-pages'; export type LoadedContent = Metadata[]; diff --git a/packages/docusaurus-theme-classic/src/theme/MDXPage/index.tsx b/packages/docusaurus-theme-classic/src/theme/MDXPage/index.tsx index 5cb38278fb..ccd233a092 100644 --- a/packages/docusaurus-theme-classic/src/theme/MDXPage/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/MDXPage/index.tsx @@ -19,18 +19,10 @@ import styles from './styles.module.css'; function MDXPage(props: Props): JSX.Element { const {content: MDXPageContent} = props; const { - // TODO this frontmatter is not validated/normalized, it's the raw user-provided one. We should expose normalized one too! - frontMatter, - metadata, + metadata: {title, description, permalink, frontMatter}, } = MDXPageContent; - - const { - title, - description, - wrapperClassName, - hide_table_of_contents: hideTableOfContents, - } = frontMatter; - const {permalink} = metadata; + const {wrapperClassName, hide_table_of_contents: hideTableOfContents} = + frontMatter; return (