diff --git a/packages/docusaurus-mdx-loader/src/index.js b/packages/docusaurus-mdx-loader/src/index.js
index a7f509a79c1c..ea9efc1240af 100644
--- a/packages/docusaurus-mdx-loader/src/index.js
+++ b/packages/docusaurus-mdx-loader/src/index.js
@@ -31,9 +31,8 @@ module.exports = async function docusaurusMdxLoader(fileString) {
const {frontMatter, content: contentWithTitle} = parseFrontMatter(fileString);
- // By default, will remove the markdown title from the content
- const {content} = parseMarkdownContentTitle(contentWithTitle, {
- keepContentTitle: reqOptions.keepContentTitle,
+ const {content, contentTitle} = parseMarkdownContentTitle(contentWithTitle, {
+ removeContentTitle: reqOptions.removeContentTitle,
});
const hasFrontMatter = Object.keys(frontMatter).length > 0;
@@ -69,7 +68,11 @@ module.exports = async function docusaurusMdxLoader(fileString) {
return callback(err);
}
- let exportStr = `export const frontMatter = ${stringifyObject(frontMatter)};`;
+ let exportStr = ``;
+ exportStr += `\nexport const frontMatter = ${stringifyObject(frontMatter)};`;
+ exportStr += `\nexport const contentTitle = ${stringifyObject(
+ contentTitle,
+ )};`;
// Read metadata for this MDX and export it.
if (options.metadataPath && typeof options.metadataPath === 'function') {
diff --git a/packages/docusaurus-plugin-content-blog/src/blogUtils.ts b/packages/docusaurus-plugin-content-blog/src/blogUtils.ts
index dbcb29eefd5d..84e173caaeb7 100644
--- a/packages/docusaurus-plugin-content-blog/src/blogUtils.ts
+++ b/packages/docusaurus-plugin-content-blog/src/blogUtils.ts
@@ -146,7 +146,7 @@ export async function generateBlogPosts(
content,
contentTitle,
excerpt,
- } = await parseMarkdownFile(source);
+ } = await parseMarkdownFile(source, {removeContentTitle: true});
const frontMatter = validateBlogPostFrontMatter(unsafeFrontMatter);
const aliasedSource = aliasedSitePath(source, siteDir);
diff --git a/packages/docusaurus-plugin-content-blog/src/index.ts b/packages/docusaurus-plugin-content-blog/src/index.ts
index d0228b6720f5..1d47eb628cb3 100644
--- a/packages/docusaurus-plugin-content-blog/src/index.ts
+++ b/packages/docusaurus-plugin-content-blog/src/index.ts
@@ -459,6 +459,9 @@ export default function pluginContentBlog(
`${docuHash(aliasedPath)}.json`,
);
},
+ // For blog posts a title in markdown is always removed
+ // Blog posts title are rendered separately
+ removeContentTitle: true,
},
},
{
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 9572c7a04d3f..722dd3df6475 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
@@ -168,7 +168,7 @@ Object {
\\"unversionedId\\": \\"foo/bar\\",
\\"id\\": \\"foo/bar\\",
\\"isDocsHomePage\\": false,
- \\"title\\": \\"Remarkable\\",
+ \\"title\\": \\"Bar\\",
\\"description\\": \\"This is custom description\\",
\\"source\\": \\"@site/docs/foo/bar.md\\",
\\"sourceDirName\\": \\"foo\\",
@@ -190,7 +190,7 @@ Object {
\\"unversionedId\\": \\"foo/baz\\",
\\"id\\": \\"foo/baz\\",
\\"isDocsHomePage\\": false,
- \\"title\\": \\"Baz markdown title\\",
+ \\"title\\": \\"baz\\",
\\"description\\": \\"Images\\",
\\"source\\": \\"@site/docs/foo/baz.md\\",
\\"sourceDirName\\": \\"foo\\",
@@ -418,12 +418,12 @@ Object {
\\"items\\": [
{
\\"type\\": \\"link\\",
- \\"label\\": \\"Remarkable\\",
+ \\"label\\": \\"Bar\\",
\\"href\\": \\"/docs/foo/bar\\"
},
{
\\"type\\": \\"link\\",
- \\"label\\": \\"Baz markdown title\\",
+ \\"label\\": \\"baz\\",
\\"href\\": \\"/docs/foo/bazSlug.html\\"
}
]
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 9d123cc6cc99..d23cc7385269 100644
--- a/packages/docusaurus-plugin-content-docs/src/__tests__/docs.test.ts
+++ b/packages/docusaurus-plugin-content-docs/src/__tests__/docs.test.ts
@@ -180,7 +180,7 @@ describe('simple site', () => {
isDocsHomePage: false,
permalink: '/docs/foo/bar',
slug: '/foo/bar',
- title: 'Remarkable',
+ title: 'Bar',
description: 'This is custom description',
frontMatter: {
description: 'This is custom description',
@@ -254,7 +254,7 @@ describe('simple site', () => {
isDocsHomePage: true,
permalink: '/docs/',
slug: '/',
- title: 'Remarkable',
+ title: 'Bar',
description: 'This is custom description',
frontMatter: {
description: 'This is custom description',
@@ -286,7 +286,7 @@ describe('simple site', () => {
isDocsHomePage: false,
permalink: '/docs/foo/bazSlug.html',
slug: '/foo/bazSlug.html',
- title: 'Baz markdown title',
+ title: 'baz',
editUrl:
'https://github.com/facebook/docusaurus/edit/master/website/docs/foo/baz.md',
description: 'Images',
@@ -345,7 +345,7 @@ describe('simple site', () => {
isDocsHomePage: false,
permalink: '/docs/foo/bazSlug.html',
slug: '/foo/bazSlug.html',
- title: 'Baz markdown title',
+ title: 'baz',
editUrl: hardcodedEditUrl,
description: 'Images',
frontMatter: {
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 3bd8944ed6c7..77c8d3db4d33 100644
--- a/packages/docusaurus-plugin-content-docs/src/__tests__/index.test.ts
+++ b/packages/docusaurus-plugin-content-docs/src/__tests__/index.test.ts
@@ -375,7 +375,7 @@ describe('simple website', () => {
'foo',
'bar.md',
),
- title: 'Remarkable',
+ title: 'Bar',
description: 'This is custom description',
frontMatter: {
description: 'This is custom description',
diff --git a/packages/docusaurus-plugin-content-docs/src/docs.ts b/packages/docusaurus-plugin-content-docs/src/docs.ts
index 12809041a8cd..b12e2df7c821 100644
--- a/packages/docusaurus-plugin-content-docs/src/docs.ts
+++ b/packages/docusaurus-plugin-content-docs/src/docs.ts
@@ -202,11 +202,9 @@ export function processDocMetadata({
numberPrefixParser: options.numberPrefixParser,
});
- // TODO expose both headingTitle+metaTitle to theme?
- // Different fallbacks order on purpose!
- // See https://github.com/facebook/docusaurus/issues/4665#issuecomment-825831367
- const headingTitle: string = contentTitle ?? frontMatter.title ?? baseID;
- // const metaTitle: string = frontMatter.title ?? contentTitle ?? baseID;
+ // Note: the title is used by default for page title, sidebar label, pagination buttons...
+ // frontMatter.title should be used in priority over contentTitle (because it can contain markdown/JSX syntax)
+ const title: string = frontMatter.title ?? contentTitle ?? baseID;
const description: string = frontMatter.description ?? excerpt ?? '';
@@ -245,7 +243,7 @@ export function processDocMetadata({
unversionedId,
id,
isDocsHomePage,
- title: headingTitle,
+ title,
description,
source: aliasedSitePath(filePath, siteDir),
sourceDirName,
diff --git a/packages/docusaurus-plugin-content-docs/src/index.ts b/packages/docusaurus-plugin-content-docs/src/index.ts
index 319549a36ace..76eb8af6d843 100644
--- a/packages/docusaurus-plugin-content-docs/src/index.ts
+++ b/packages/docusaurus-plugin-content-docs/src/index.ts
@@ -199,11 +199,7 @@ export default function pluginContentDocs(
nextId,
} = sidebarsUtils.getDocNavigation(doc.id);
const toDocNavLink = (navDocId: string): DocNavLink => ({
- // Use frontMatter.title in priority over a potential # title found in markdown
- // See https://github.com/facebook/docusaurus/issues/4665#issuecomment-825831367
- title:
- docsBaseById[navDocId].frontMatter.title ||
- docsBaseById[navDocId].title,
+ title: docsBaseById[navDocId].title,
permalink: docsBaseById[navDocId].permalink,
});
return {
diff --git a/packages/docusaurus-plugin-content-docs/src/plugin-content-docs.d.ts b/packages/docusaurus-plugin-content-docs/src/plugin-content-docs.d.ts
index 87b6c07d07e5..abef806debfb 100644
--- a/packages/docusaurus-plugin-content-docs/src/plugin-content-docs.d.ts
+++ b/packages/docusaurus-plugin-content-docs/src/plugin-content-docs.d.ts
@@ -82,6 +82,7 @@ declare module '@theme/DocItem' {
readonly frontMatter: FrontMatter;
readonly metadata: Metadata;
readonly toc: readonly TOCItem[];
+ readonly contentTitle: string | undefined;
(): JSX.Element;
};
};
diff --git a/packages/docusaurus-plugin-content-pages/src/index.ts b/packages/docusaurus-plugin-content-pages/src/index.ts
index d94075e9f745..9e28197a934a 100644
--- a/packages/docusaurus-plugin-content-pages/src/index.ts
+++ b/packages/docusaurus-plugin-content-pages/src/index.ts
@@ -222,7 +222,6 @@ export default function pluginContentPages(
rehypePlugins,
beforeDefaultRehypePlugins,
beforeDefaultRemarkPlugins,
- keepContentTitle: true,
staticDir: path.join(siteDir, STATIC_DIR_NAME),
// Note that metadataPath must be the same/in-sync as
// the path from createData for each MDX.
diff --git a/packages/docusaurus-theme-classic/src/theme/BlogPostItem/index.tsx b/packages/docusaurus-theme-classic/src/theme/BlogPostItem/index.tsx
index 2307b57da905..f4b76b68d659 100644
--- a/packages/docusaurus-theme-classic/src/theme/BlogPostItem/index.tsx
+++ b/packages/docusaurus-theme-classic/src/theme/BlogPostItem/index.tsx
@@ -47,8 +47,8 @@ function BlogPostItem(props: Props): JSX.Element {
truncated,
isBlogPostPage = false,
} = props;
- const {date, formattedDate, permalink, tags, readingTime} = metadata;
- const {author, title, image, keywords} = frontMatter;
+ const {date, formattedDate, permalink, tags, readingTime, title} = metadata;
+ const {author, image, keywords} = frontMatter;
const authorURL = frontMatter.author_url || frontMatter.authorURL;
const authorTitle = frontMatter.author_title || frontMatter.authorTitle;
diff --git a/packages/docusaurus-theme-classic/src/theme/DocItem/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocItem/index.tsx
index 40d1b9b6a300..9146400f7966 100644
--- a/packages/docusaurus-theme-classic/src/theme/DocItem/index.tsx
+++ b/packages/docusaurus-theme-classic/src/theme/DocItem/index.tsx
@@ -13,6 +13,7 @@ import LastUpdated from '@theme/LastUpdated';
import type {Props} from '@theme/DocItem';
import TOC from '@theme/TOC';
import EditThisPage from '@theme/EditThisPage';
+import {MainHeading} from '@theme/Heading';
import clsx from 'clsx';
import styles from './styles.module.css';
@@ -49,13 +50,15 @@ function DocItem(props: Props): JSX.Element {
// See https://github.com/facebook/docusaurus/issues/3362
const showVersionBadge = versions.length > 1;
- // For meta title, using frontMatter.title in priority over a potential # title found in markdown
- // See https://github.com/facebook/docusaurus/issues/4665#issuecomment-825831367
- const metaTitle = frontMatter.title || title;
+ // We only add a title if:
+ // - user asks to hide it with frontmatter
+ // - the markdown content does not already contain a top-level h1 heading
+ const shouldAddTitle =
+ !hideTitle && typeof DocContent.contentTitle === 'undefined';
return (
<>
-
+
)}
- {!hideTitle && (
-
- )}
+ {/*
+ Title can be declared inside md content or declared through frontmatter and added manually
+ To make both cases consistent, the added title is added under the same div.markdown block
+ See https://github.com/facebook/docusaurus/pull/4882#issuecomment-853021120
+ */}
+ {shouldAddTitle && {title}}
diff --git a/packages/docusaurus-theme-classic/src/theme/DocItem/styles.module.css b/packages/docusaurus-theme-classic/src/theme/DocItem/styles.module.css
index 283d21b71b62..7d428e957a56 100644
--- a/packages/docusaurus-theme-classic/src/theme/DocItem/styles.module.css
+++ b/packages/docusaurus-theme-classic/src/theme/DocItem/styles.module.css
@@ -5,11 +5,6 @@
* LICENSE file in the root directory of this source tree.
*/
-.docTitle {
- font-size: 3rem;
- margin-bottom: calc(var(--ifm-leading-desktop) * var(--ifm-leading));
-}
-
.docItemContainer {
margin: 0 auto;
padding: 0 0.5rem;
diff --git a/packages/docusaurus-theme-classic/src/theme/Heading/index.tsx b/packages/docusaurus-theme-classic/src/theme/Heading/index.tsx
index 6c0cc35b2747..4daaebf30c62 100644
--- a/packages/docusaurus-theme-classic/src/theme/Heading/index.tsx
+++ b/packages/docusaurus-theme-classic/src/theme/Heading/index.tsx
@@ -16,7 +16,24 @@ import {useThemeConfig} from '@docusaurus/theme-common';
import './styles.css';
import styles from './styles.module.css';
-const Heading = (Tag: HeadingType): ((props: Props) => JSX.Element) =>
+type HeadingComponent = (props: Props) => JSX.Element;
+
+export const MainHeading: HeadingComponent = function MainHeading({...props}) {
+ return (
+
+ );
+};
+
+const createAnchorHeading = (
+ Tag: HeadingType,
+): ((props: Props) => JSX.Element) =>
function TargetComponent({id, ...props}) {
const {
navbar: {hideOnScroll},
@@ -51,4 +68,8 @@ const Heading = (Tag: HeadingType): ((props: Props) => JSX.Element) =>
);
};
+const Heading = (headingType: HeadingType): ((props: Props) => JSX.Element) => {
+ return headingType === 'h1' ? MainHeading : createAnchorHeading(headingType);
+};
+
export default Heading;
diff --git a/packages/docusaurus-theme-classic/src/theme/Heading/styles.module.css b/packages/docusaurus-theme-classic/src/theme/Heading/styles.module.css
index 4e24477449a5..3f94fe995476 100644
--- a/packages/docusaurus-theme-classic/src/theme/Heading/styles.module.css
+++ b/packages/docusaurus-theme-classic/src/theme/Heading/styles.module.css
@@ -8,3 +8,8 @@
.enhancedAnchor {
top: calc(var(--ifm-navbar-height) * -1 - 0.5rem);
}
+
+.h1Heading {
+ font-size: 3rem;
+ margin-bottom: calc(var(--ifm-leading-desktop) * var(--ifm-leading));
+}
diff --git a/packages/docusaurus-theme-classic/src/types.d.ts b/packages/docusaurus-theme-classic/src/types.d.ts
index 114e977f7705..3174b1f6b0d4 100644
--- a/packages/docusaurus-theme-classic/src/types.d.ts
+++ b/packages/docusaurus-theme-classic/src/types.d.ts
@@ -114,6 +114,7 @@ declare module '@theme/Heading' {
const Heading: (Tag: HeadingType) => (props: Props) => JSX.Element;
export default Heading;
+ export const MainHeading: (props: Props) => JSX.Element;
}
declare module '@theme/hooks/useAnnouncementBar' {
diff --git a/packages/docusaurus-utils/src/__tests__/markdownParser.test.ts b/packages/docusaurus-utils/src/__tests__/markdownParser.test.ts
index 0edb4c0cb09b..2f36481e4b41 100644
--- a/packages/docusaurus-utils/src/__tests__/markdownParser.test.ts
+++ b/packages/docusaurus-utils/src/__tests__/markdownParser.test.ts
@@ -141,11 +141,71 @@ describe('parseMarkdownContentTitle', () => {
`;
expect(parseMarkdownContentTitle(markdown)).toEqual({
+ content: markdown,
+ contentTitle: 'Markdown Title',
+ });
+ });
+
+ test('Should parse markdown h1 title at the top and remove it', () => {
+ const markdown = dedent`
+
+ # Markdown Title
+
+ Lorem Ipsum
+
+ `;
+ expect(
+ parseMarkdownContentTitle(markdown, {removeContentTitle: true}),
+ ).toEqual({
content: 'Lorem Ipsum',
contentTitle: 'Markdown Title',
});
});
+ test('Should parse markdown h1 title at the top and unwrap inline code block', () => {
+ const markdown = dedent`
+
+ # \`Markdown Title\`
+
+ Lorem Ipsum
+
+ `;
+ expect(parseMarkdownContentTitle(markdown)).toEqual({
+ content: markdown,
+ contentTitle: 'Markdown Title',
+ });
+ });
+
+ test('Should parse markdown h1 title and trim content', () => {
+ const markdown = `
+
+# Markdown Title
+
+Lorem Ipsum
+
+
+
+`;
+
+ expect(parseMarkdownContentTitle(markdown)).toEqual({
+ content: markdown.trim(),
+ contentTitle: 'Markdown Title',
+ });
+ });
+
+ test('Should parse not parse markdown h1 title and trim content', () => {
+ const markdown = `
+
+Lorem Ipsum
+
+`;
+
+ expect(parseMarkdownContentTitle(markdown)).toEqual({
+ content: markdown.trim(),
+ contentTitle: undefined,
+ });
+ });
+
test('Should parse markdown h1 title with fixed anchor-id syntax', () => {
const markdown = dedent`
@@ -155,7 +215,7 @@ describe('parseMarkdownContentTitle', () => {
`;
expect(parseMarkdownContentTitle(markdown)).toEqual({
- content: 'Lorem Ipsum',
+ content: markdown,
contentTitle: 'Markdown Title',
});
});
@@ -169,7 +229,7 @@ describe('parseMarkdownContentTitle', () => {
`;
expect(parseMarkdownContentTitle(markdown)).toEqual({
- content: 'Lorem Ipsum',
+ content: markdown,
contentTitle: 'Markdown Title',
});
});
@@ -185,12 +245,7 @@ describe('parseMarkdownContentTitle', () => {
`;
expect(parseMarkdownContentTitle(markdown)).toEqual({
- content: dedent`
- ## Heading 2
-
- Lorem Ipsum
-
- `,
+ content: markdown,
contentTitle: 'Markdown Title',
});
});
@@ -206,12 +261,7 @@ describe('parseMarkdownContentTitle', () => {
`;
expect(parseMarkdownContentTitle(markdown)).toEqual({
- content: dedent`
- # Markdown Title 2
-
- Lorem Ipsum
-
- `,
+ content: markdown,
contentTitle: 'Markdown Title',
});
});
@@ -227,20 +277,27 @@ describe('parseMarkdownContentTitle', () => {
`;
expect(parseMarkdownContentTitle(markdown)).toEqual({
- content: dedent`
+ content: markdown,
+ contentTitle: undefined,
+ });
+ });
- Lorem Ipsum
+ test('Should parse markdown h1 alternate title', () => {
+ const markdown = dedent`
- # Markdown Title 2
+ Markdown Title
+ ================
Lorem Ipsum
- `,
- contentTitle: undefined,
+ `;
+ expect(parseMarkdownContentTitle(markdown)).toEqual({
+ content: markdown,
+ contentTitle: 'Markdown Title',
});
});
- test('Should parse markdown h1 alternate title', () => {
+ test('Should parse markdown h1 alternate title and remove it', () => {
const markdown = dedent`
Markdown Title
@@ -249,7 +306,9 @@ describe('parseMarkdownContentTitle', () => {
Lorem Ipsum
`;
- expect(parseMarkdownContentTitle(markdown)).toEqual({
+ expect(
+ parseMarkdownContentTitle(markdown, {removeContentTitle: true}),
+ ).toEqual({
content: 'Lorem Ipsum',
contentTitle: 'Markdown Title',
});
@@ -271,17 +330,7 @@ describe('parseMarkdownContentTitle', () => {
// remove the useless line breaks? Does not matter too much
expect(parseMarkdownContentTitle(markdown)).toEqual({
- content: dedent`
- import Component1 from '@site/src/components/Component1';
-
- import Component2 from '@site/src/components/Component2'
- import Component3 from '@site/src/components/Component3'
- import './styles.css';
-
-
-
- Lorem Ipsum
- `,
+ content: markdown,
contentTitle: 'Markdown Title',
});
});
@@ -306,10 +355,16 @@ import "module-name"
# Markdown Title
Lorem Ipsum
- `;
+`;
expect(parseMarkdownContentTitle(markdown)).toEqual({
- content: `
+ content: markdown.trim(),
+ contentTitle: 'Markdown Title',
+ });
+ });
+
+ test('Should parse markdown h1 title placed after various import declarations and remove it', () => {
+ const markdown = `
import DefaultComponent from '@site/src/components/Component1';
import DefaultComponent2 from '../relative/path/Component2';
import * as EntireComponent from './relative/path/Component3';
@@ -325,16 +380,22 @@ import './styles.css';
import _ from 'underscore';
import "module-name"
-
+# Markdown Title
Lorem Ipsum
- `.trim(),
+`;
+
+ expect(
+ parseMarkdownContentTitle(markdown, {removeContentTitle: true}),
+ ).toEqual({
+ content: markdown.trim().replace('# Markdown Title', ''),
contentTitle: 'Markdown Title',
});
});
test('Should parse markdown h1 alternate title placed after import declarations', () => {
const markdown = dedent`
+
import Component from '@site/src/components/Component';
import Component from '@site/src/components/Component'
import './styles.css';
@@ -346,41 +407,40 @@ Lorem Ipsum
`;
expect(parseMarkdownContentTitle(markdown)).toEqual({
- content: dedent`
- import Component from '@site/src/components/Component';
- import Component from '@site/src/components/Component'
- import './styles.css';
-
- Lorem Ipsum
- `,
+ content: markdown,
contentTitle: 'Markdown Title',
});
});
- test('Should parse title-only', () => {
- const markdown = '# Document With Only A Title ';
- expect(parseMarkdownContentTitle(markdown)).toEqual({
- content: '',
- contentTitle: 'Document With Only A Title',
- });
- });
-
- test('Should parse markdown h1 title at the top but keep it in content', () => {
+ test('Should parse markdown h1 alternate title placed after import declarations and remove it', () => {
const markdown = dedent`
- # Markdown Title
+ import Component from '@site/src/components/Component';
+ import Component from '@site/src/components/Component'
+ import './styles.css';
+
+ Markdown Title
+ ==============
Lorem Ipsum
`;
expect(
- parseMarkdownContentTitle(markdown, {keepContentTitle: true}),
+ parseMarkdownContentTitle(markdown, {removeContentTitle: true}),
).toEqual({
- content: markdown.trim(),
+ content: markdown.replace('Markdown Title\n==============\n\n', ''),
contentTitle: 'Markdown Title',
});
});
+ test('Should parse title-only', () => {
+ const markdown = '# Document With Only A Title';
+ expect(parseMarkdownContentTitle(markdown)).toEqual({
+ content: markdown,
+ contentTitle: 'Document With Only A Title',
+ });
+ });
+
test('Should not parse markdown h1 title in the middle of a doc', () => {
const markdown = dedent`
@@ -439,7 +499,13 @@ Lorem Ipsum
`;
expect(parseMarkdownContentTitle(markdown)).toEqual({
- content: dedent`
+ content: markdown,
+ contentTitle: 'Markdown Title',
+ });
+ });
+
+ test('Should parse markdown h1 title placed after multiple import declarations and remove it', () => {
+ const markdown = dedent`
import Component1 from '@site/src/components/Component1';
import Component2 from '@site/src/components/Component2';
import Component3 from '@site/src/components/Component3';
@@ -456,11 +522,16 @@ Lorem Ipsum
import Component14 from '@site/src/components/Component14';
import Component15 from '@site/src/components/Component15';
-
+ # Markdown Title
Lorem Ipsum
- `,
+ `;
+
+ expect(
+ parseMarkdownContentTitle(markdown, {removeContentTitle: true}),
+ ).toEqual({
+ content: markdown.replace('# Markdown Title', ''),
contentTitle: 'Markdown Title',
});
});
@@ -497,7 +568,9 @@ describe('parseMarkdownString', () => {
`),
).toMatchInlineSnapshot(`
Object {
- "content": "Some text",
+ "content": "# Markdown Title
+
+ Some text",
"contentTitle": "Markdown Title",
"excerpt": "Some text",
"frontMatter": Object {},
@@ -518,7 +591,9 @@ describe('parseMarkdownString', () => {
`),
).toMatchInlineSnapshot(`
Object {
- "content": "Some text",
+ "content": "# Markdown Title
+
+ Some text",
"contentTitle": "Markdown Title",
"excerpt": "Some text",
"frontMatter": Object {
@@ -542,36 +617,11 @@ describe('parseMarkdownString', () => {
`),
).toMatchInlineSnapshot(`
Object {
- "content": "Some text",
- "contentTitle": "Markdown Title alternate",
- "excerpt": "Some text",
- "frontMatter": Object {
- "title": "Frontmatter title",
- },
- }
- `);
- });
-
- test('should not warn for duplicate title if keepContentTitle=true', () => {
- expect(
- parseMarkdownString(
- dedent`
- ---
- title: Frontmatter title
- ---
-
- # Markdown Title
-
- Some text
- `,
- {keepContentTitle: true},
- ),
- ).toMatchInlineSnapshot(`
- Object {
- "content": "# Markdown Title
+ "content": "Markdown Title alternate
+ ================
Some text",
- "contentTitle": "Markdown Title",
+ "contentTitle": "Markdown Title alternate",
"excerpt": "Some text",
"frontMatter": Object {
"title": "Frontmatter title",
@@ -605,24 +655,6 @@ describe('parseMarkdownString', () => {
`);
});
- test('should parse markdown title and keep it in content', () => {
- expect(
- parseMarkdownString(
- dedent`
- # Markdown Title
- `,
- {keepContentTitle: true},
- ),
- ).toMatchInlineSnapshot(`
- Object {
- "content": "# Markdown Title",
- "contentTitle": "Markdown Title",
- "excerpt": undefined,
- "frontMatter": Object {},
- }
- `);
- });
-
test('should delete only first heading', () => {
expect(
parseMarkdownString(dedent`
@@ -636,7 +668,9 @@ describe('parseMarkdownString', () => {
`),
).toMatchInlineSnapshot(`
Object {
- "content": "test test test # test bar
+ "content": "# Markdown Title
+
+ test test test # test bar
# Markdown Title 2
@@ -692,7 +726,7 @@ describe('parseMarkdownString', () => {
test('should parse title only', () => {
expect(parseMarkdownString('# test')).toMatchInlineSnapshot(`
Object {
- "content": "",
+ "content": "# test",
"contentTitle": "test",
"excerpt": undefined,
"frontMatter": Object {},
@@ -708,7 +742,8 @@ describe('parseMarkdownString', () => {
`),
).toMatchInlineSnapshot(`
Object {
- "content": "",
+ "content": "test
+ ===",
"contentTitle": "test",
"excerpt": undefined,
"frontMatter": Object {},
@@ -726,7 +761,7 @@ describe('parseMarkdownString', () => {
`),
).toMatchInlineSnapshot(`
Object {
- "content": "",
+ "content": "# test",
"contentTitle": "test",
"excerpt": undefined,
"frontMatter": Object {
@@ -766,7 +801,9 @@ describe('parseMarkdownString', () => {
`),
).toMatchInlineSnapshot(`
Object {
- "content": "test test test test test test
+ "content": "# test
+
+ test test test test test test
test test test # test bar
# test2
### test
diff --git a/packages/docusaurus-utils/src/markdownParser.ts b/packages/docusaurus-utils/src/markdownParser.ts
index 9bdfc02772ca..6270a791c133 100644
--- a/packages/docusaurus-utils/src/markdownParser.ts
+++ b/packages/docusaurus-utils/src/markdownParser.ts
@@ -80,11 +80,21 @@ export function parseFrontMatter(
};
}
+// Try to convert markdown heading as text
+// Does not need to be perfect, it is only used as a fallback when frontMatter.title is not provided
+// For now, we just unwrap possible inline code blocks (# `config.js`)
+function toTextContentTitle(contentTitle: string): string {
+ if (contentTitle.startsWith('`') && contentTitle.endsWith('`')) {
+ return contentTitle.substring(1, contentTitle.length - 1);
+ }
+ return contentTitle;
+}
+
export function parseMarkdownContentTitle(
contentUntrimmed: string,
- options?: {keepContentTitle?: boolean},
+ options?: {removeContentTitle?: boolean},
): {content: string; contentTitle: string | undefined} {
- const keepContentTitleOption = options?.keepContentTitle ?? false;
+ const removeContentTitleOption = options?.removeContentTitle ?? false;
const content = contentUntrimmed.trim();
@@ -108,16 +118,15 @@ export function parseMarkdownContentTitle(
if (!pattern || !title) {
return {content, contentTitle: undefined};
+ } else {
+ const newContent = removeContentTitleOption
+ ? content.replace(pattern, '')
+ : content;
+ return {
+ content: newContent.trim(),
+ contentTitle: toTextContentTitle(title.trim()).trim(),
+ };
}
-
- const newContent = keepContentTitleOption
- ? content
- : content.replace(pattern, '');
-
- return {
- content: newContent.trim(),
- contentTitle: title.trim(),
- };
}
type ParsedMarkdown = {
@@ -129,22 +138,16 @@ type ParsedMarkdown = {
export function parseMarkdownString(
markdownFileContent: string,
- options?: {
- keepContentTitle?: boolean;
- },
+ options?: {removeContentTitle?: boolean},
): ParsedMarkdown {
try {
- const keepContentTitle = options?.keepContentTitle ?? false;
-
const {frontMatter, content: contentWithoutFrontMatter} = parseFrontMatter(
markdownFileContent,
);
const {content, contentTitle} = parseMarkdownContentTitle(
contentWithoutFrontMatter,
- {
- keepContentTitle,
- },
+ options,
);
const excerpt = createExcerpt(content);
@@ -166,10 +169,11 @@ This can happen if you use special characters like : in frontmatter values (try
export async function parseMarkdownFile(
source: string,
+ options?: {removeContentTitle?: boolean},
): Promise
{
const markdownString = await fs.readFile(source, 'utf-8');
try {
- return parseMarkdownString(markdownString);
+ return parseMarkdownString(markdownString, options);
} catch (e) {
throw new Error(
`Error while parsing markdown file ${source}
diff --git a/website/docs/api/docusaurus.config.js.md b/website/docs/api/docusaurus.config.js.md
index 5af7569d277c..b44fb6a32c22 100644
--- a/website/docs/api/docusaurus.config.js.md
+++ b/website/docs/api/docusaurus.config.js.md
@@ -1,10 +1,11 @@
---
id: docusaurus.config.js
-title: docusaurus.config.js
description: API reference for Docusaurus configuration file.
slug: /docusaurus.config.js
---
+# `docusaurus.config.js`
+
## Overview {#overview}
`docusaurus.config.js` contains configurations for your site and is placed in the root directory of your site.
diff --git a/website/docs/api/plugins/plugin-content-docs.md b/website/docs/api/plugins/plugin-content-docs.md
index 2861ce4f72c9..a51eaf5f7447 100644
--- a/website/docs/api/plugins/plugin-content-docs.md
+++ b/website/docs/api/plugins/plugin-content-docs.md
@@ -197,7 +197,7 @@ Markdown documents can use the following markdown frontmatter metadata fields, e
- `id`: A unique document id. If this field is not present, the document's `id` will default to its file name (without the extension)
- `title`: The title of your document. If this field is not present, the document's `title` will default to its `id`
-- `hide_title`: Whether to hide the title at the top of the doc. By default, it is `false`
+- `hide_title`: Whether to hide the title at the top of the doc. It only hides a title declared through the frontmatter, and have no effect on a title at the top of your Markdown document. By default, it is `false`
- `hide_table_of_contents`: Whether to hide the table of contents to the right. By default it is `false`
- `sidebar_label`: The text shown in the document sidebar and in the next/previous button for this document. If this field is not present, the document's `sidebar_label` will default to its `title`
- `sidebar_position`: Permits to control the position of a doc inside the generated sidebar slice, when using `autogenerated` sidebar items. Can be Int or Float.