feat(mdx-loader): wrap mdx content title (# Title) in <header> for concistency (#10335)

Co-authored-by: sebastien <lorber.sebastien@gmail.com>
This commit is contained in:
ozaki 2024-07-25 15:14:31 +02:00 committed by GitHub
parent fb4e32fb81
commit 23dbf9c0ae
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 59 additions and 11 deletions

View file

@ -41,6 +41,8 @@ function isBlacklisted(pathname: string) {
'/feature-requests', '/feature-requests',
// Flaky because of dynamic canary version fetched from npm // Flaky because of dynamic canary version fetched from npm
'/community/canary', '/community/canary',
// Flaky because of screenshots being taken dynamically
'/showcase',
// Long blog post with many image carousels, often timeouts // Long blog post with many image carousels, often timeouts
'/blog/2022/08/01/announcing-docusaurus-2.0', '/blog/2022/08/01/announcing-docusaurus-2.0',
]; ];

View file

@ -5,6 +5,7 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import {escapeMarkdownHeadingIds} from '@docusaurus/utils';
import plugin from '../index'; import plugin from '../index';
async function process( async function process(
@ -12,8 +13,14 @@ async function process(
options: {removeContentTitle?: boolean} = {}, options: {removeContentTitle?: boolean} = {},
) { ) {
const {remark} = await import('remark'); const {remark} = await import('remark');
const processor = await remark().use({plugins: [[plugin, options]]}); const {default: mdx} = await import('remark-mdx');
return processor.process(content);
const result = await remark()
.use(mdx)
.use(plugin, options)
.process(escapeMarkdownHeadingIds(content));
return result;
} }
describe('contentTitle remark plugin', () => { describe('contentTitle remark plugin', () => {
@ -33,7 +40,8 @@ some **markdown** *content*
}); });
it('extracts h1 heading alt syntax', async () => { it('extracts h1 heading alt syntax', async () => {
const result = await process(` const result = await process(
`
contentTitle alt contentTitle alt
=== ===
@ -44,7 +52,8 @@ contentTitle alt
# contentTitle 2 # contentTitle 2
some **markdown** *content* some **markdown** *content*
`); `,
);
expect(result.data.contentTitle).toBe('contentTitle alt'); expect(result.data.contentTitle).toBe('contentTitle alt');
}); });
@ -98,7 +107,9 @@ some **markdown** *content*
}); });
describe('returns appropriate content', () => { describe('returns appropriate content', () => {
it('returns content unmodified', async () => { it('returns heading wrapped in <header>', async () => {
// Test case for https://github.com/facebook/docusaurus/issues/8476
const content = ` const content = `
# contentTitle 1 # contentTitle 1
@ -111,7 +122,19 @@ some **markdown** *content*
const result = await process(content); const result = await process(content);
expect(result.toString().trim()).toEqual(content); expect(result.toString().trim()).toEqual(
`
<header>
# contentTitle 1
</header>
## Heading Two \\{#custom-heading-two}
# contentTitle 2
some **markdown** *content*
`.trim(),
);
}); });
it('can strip contentTitle', async () => { it('can strip contentTitle', async () => {
@ -129,7 +152,7 @@ some **markdown** *content*
expect(result.toString().trim()).toEqual( expect(result.toString().trim()).toEqual(
` `
## Heading Two {#custom-heading-two} ## Heading Two \\{#custom-heading-two}
# contentTitle 2 # contentTitle 2
@ -154,7 +177,7 @@ some **markdown** *content*
expect(result.toString().trim()).toEqual( expect(result.toString().trim()).toEqual(
` `
## Heading Two {#custom-heading-two} ## Heading Two \\{#custom-heading-two}
# contentTitle 2 # contentTitle 2

View file

@ -7,7 +7,10 @@
// @ts-expect-error: TODO see https://github.com/microsoft/TypeScript/issues/49721 // @ts-expect-error: TODO see https://github.com/microsoft/TypeScript/issues/49721
import type {Transformer} from 'unified'; import type {Transformer} from 'unified';
import type {Heading} from 'mdast'; import type {Heading, Parent} from 'mdast';
// @ts-expect-error: ES support...
import type {MdxJsxFlowElement} from 'mdast-util-mdx';
// TODO as of April 2023, no way to import/re-export this ESM type easily :/ // TODO as of April 2023, no way to import/re-export this ESM type easily :/
// TODO upgrade to TS 5.3 // TODO upgrade to TS 5.3
@ -19,6 +22,20 @@ interface PluginOptions {
removeContentTitle?: boolean; removeContentTitle?: boolean;
} }
function wrapHeadingInJsxHeader(
headingNode: Heading,
parent: Parent,
index: number,
) {
const header: MdxJsxFlowElement = {
type: 'mdxJsxFlowElement',
name: 'header',
attributes: [],
children: [headingNode],
};
parent.children[index] = header;
}
/** /**
* A remark plugin to extract the h1 heading found in Markdown files * A remark plugin to extract the h1 heading found in Markdown files
* This is exposed as "data.contentTitle" to the processed vfile * This is exposed as "data.contentTitle" to the processed vfile
@ -33,15 +50,21 @@ const plugin: Plugin = function plugin(
return async (root, vfile) => { return async (root, vfile) => {
const {toString} = await import('mdast-util-to-string'); const {toString} = await import('mdast-util-to-string');
const {visit, EXIT} = await import('unist-util-visit'); const {visit, EXIT} = await import('unist-util-visit');
visit(root, ['heading', 'thematicBreak'], (node, index, parent) => { visit(root, ['heading', 'thematicBreak'], (node, index, parent) => {
if (node.type === 'heading') { if (node.type === 'heading') {
const headingNode = node as Heading; const headingNode = node as Heading;
// console.log('headingNode:', headingNode);
if (headingNode.depth === 1) { if (headingNode.depth === 1) {
vfile.data.contentTitle = toString(headingNode); vfile.data.contentTitle = toString(headingNode);
if (removeContentTitle) { if (removeContentTitle) {
// @ts-expect-error: TODO how to fix? // @ts-expect-error: TODO how to fix?
parent!.children.splice(index, 1); parent!.children.splice(index, 1);
} else {
// TODO in the future it might be better to export contentTitle as
// as JSX node to keep this logic a theme concern?
// See https://github.com/facebook/docusaurus/pull/10335#issuecomment-2250187371
wrapHeadingInJsxHeader(headingNode, parent, index!);
} }
return EXIT; // We only handle the very first heading return EXIT; // We only handle the very first heading
} }

View file

@ -8,7 +8,7 @@
// @ts-expect-error: TODO see https://github.com/microsoft/TypeScript/issues/49721 // @ts-expect-error: TODO see https://github.com/microsoft/TypeScript/issues/49721
import type {Transformer} from 'unified'; import type {Transformer} from 'unified';
// @ts-expect-error: ES support... // @ts-expect-error: TODO see https://github.com/microsoft/TypeScript/issues/49721
import type {MdxJsxFlowElement} from 'mdast-util-mdx'; import type {MdxJsxFlowElement} from 'mdast-util-mdx';
// Transform <head> to <Head> // Transform <head> to <Head>