-
-
Notifications
You must be signed in to change notification settings - Fork 8.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
225 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
151 changes: 151 additions & 0 deletions
151
packages/docusaurus-mdx-loader/src/remark/contentTitle/__tests__/index.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
/** | ||
* 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 plugin from '../index'; | ||
|
||
async function process( | ||
content: string, | ||
options: {removeContentTitle?: boolean} = {}, | ||
) { | ||
const {remark} = await import('remark'); | ||
const processor = await remark().use({plugins: [[plugin, options]]}); | ||
return processor.process(content); | ||
} | ||
|
||
describe('contentTitle remark plugin', () => { | ||
describe('extracts data.contentTitle', () => { | ||
it('extracts h1 heading', async () => { | ||
const result = await process(` | ||
# contentTitle 1 | ||
## Heading Two {#custom-heading-two} | ||
# contentTitle 2 | ||
some **markdown** *content* | ||
`); | ||
|
||
expect(result.data.contentTitle).toBe('contentTitle 1'); | ||
}); | ||
|
||
it('extracts h1 heading alt syntax', async () => { | ||
const result = await process(` | ||
contentTitle alt | ||
=== | ||
# contentTitle 1 | ||
## Heading Two {#custom-heading-two} | ||
# contentTitle 2 | ||
some **markdown** *content* | ||
`); | ||
|
||
expect(result.data.contentTitle).toBe('contentTitle alt'); | ||
}); | ||
|
||
it('works with no contentTitle', async () => { | ||
const result = await process(` | ||
## Heading Two {#custom-heading-two} | ||
some **markdown** *content* | ||
`); | ||
|
||
expect(result.data.contentTitle).toBeUndefined(); | ||
}); | ||
|
||
it('ignore contentTitle if not in first position', async () => { | ||
const result = await process(` | ||
## Heading Two {#custom-heading-two} | ||
# contentTitle 1 | ||
some **markdown** *content* | ||
`); | ||
|
||
expect(result.data.contentTitle).toBeUndefined(); | ||
}); | ||
|
||
it('is able to decently serialize Markdown syntax', async () => { | ||
const result = await process(` | ||
# some **markdown** \`content\` _italic_ | ||
some **markdown** *content* | ||
`); | ||
|
||
expect(result.data.contentTitle).toBe('some markdown content italic'); | ||
}); | ||
}); | ||
|
||
describe('returns appropriate content', () => { | ||
it('returns content unmodified', async () => { | ||
const content = ` | ||
# contentTitle 1 | ||
## Heading Two {#custom-heading-two} | ||
# contentTitle 2 | ||
some **markdown** *content* | ||
`.trim(); | ||
|
||
const result = await process(content); | ||
|
||
expect(result.toString().trim()).toEqual(content); | ||
}); | ||
|
||
it('can strip contentTitle', async () => { | ||
const content = ` | ||
# contentTitle 1 | ||
## Heading Two {#custom-heading-two} | ||
# contentTitle 2 | ||
some **markdown** *content* | ||
`.trim(); | ||
|
||
const result = await process(content, {removeContentTitle: true}); | ||
|
||
expect(result.toString().trim()).toEqual( | ||
` | ||
## Heading Two {#custom-heading-two} | ||
# contentTitle 2 | ||
some **markdown** *content* | ||
`.trim(), | ||
); | ||
}); | ||
|
||
it('can strip contentTitle alt', async () => { | ||
const content = ` | ||
contentTitle alt | ||
=== | ||
## Heading Two {#custom-heading-two} | ||
# contentTitle 2 | ||
some **markdown** *content* | ||
`.trim(); | ||
|
||
const result = await process(content, {removeContentTitle: true}); | ||
|
||
expect(result.toString().trim()).toEqual( | ||
` | ||
## Heading Two {#custom-heading-two} | ||
# contentTitle 2 | ||
some **markdown** *content* | ||
`.trim(), | ||
); | ||
}); | ||
}); | ||
}); |
53 changes: 53 additions & 0 deletions
53
packages/docusaurus-mdx-loader/src/remark/contentTitle/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
/** | ||
* 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 visit, {EXIT} from 'unist-util-visit'; | ||
// @ts-expect-error: TODO see https://github.com/microsoft/TypeScript/issues/49721 | ||
import type {Transformer} from 'unified'; | ||
import type {Heading} from 'mdast'; | ||
|
||
// TODO as of April 2023, no way to import/re-export this ESM type easily :/ | ||
// TODO upgrade to TS 5.3 | ||
// See https://github.com/microsoft/TypeScript/issues/49721#issuecomment-1517839391 | ||
// import type {Plugin} from 'unified'; | ||
type Plugin = any; // TODO fix this asap | ||
|
||
interface PluginOptions { | ||
removeContentTitle?: boolean; | ||
} | ||
|
||
/** | ||
* A remark plugin to extract the h1 heading found in Markdown files | ||
* This is exposed as "data.contentTitle" to the processed vfile | ||
* Also gives the ability to strip that content title (used for the blog plugin) | ||
*/ | ||
const plugin: Plugin = function plugin( | ||
options: PluginOptions = {}, | ||
): Transformer { | ||
// content title is | ||
const removeContentTitle = options.removeContentTitle ?? false; | ||
|
||
return async (root, vfile) => { | ||
const {toString} = await import('mdast-util-to-string'); | ||
visit(root, 'heading', (headingNode: Heading, index, parent) => { | ||
if (headingNode.depth === 1) { | ||
vfile.data.contentTitle = toString(headingNode); | ||
if (removeContentTitle) { | ||
parent!.children.splice(index, 1); | ||
} | ||
return EXIT; // We only handle the very first heading | ||
} | ||
// We only handle contentTitle if it's the very first heading found | ||
if (headingNode.depth >= 1) { | ||
return EXIT; | ||
} | ||
return undefined; | ||
}); | ||
}; | ||
}; | ||
|
||
export default plugin; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters