refactor(markdown): restructure options and plugin registration

- group `MarkdownOptions` into sections by concern (general, syntax
  highlighting, code blocks, markdown extensions, vue integration) and
  rewrite the jsdocs with a consistent voice, documenting the previously
  undocumented `externalLinks` default and correcting the `container`
  description (label customization, not plugin pass-through)
- register plugins in accurately-labeled groups (vitepress
  customizations, community plugins, mdit-vue plugins) and note the
  order-sensitive couplings inline (lineNumbers after preWrapper,
  anchor after attrs)
- add a test for the `cjkFriendlyEmphasis` toggle

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pull/5335/head
Divyansh Singh 2 months ago
parent b8d9c8f9a9
commit 3514d82617

@ -101,5 +101,13 @@ describe('node/markdown/markdown', () => {
'tabindex'
)
})
test('cjkFriendlyEmphasis', async () => {
const src = 'これは**「テスト」**です'
expect(await render(src)).toContain('<strong>「テスト」</strong>')
expect(await render(src, { cjkFriendlyEmphasis: false })).not.toContain(
'<strong>'
)
})
})
})

@ -54,17 +54,21 @@ export interface MarkdownOptions extends MarkdownItAsyncOptions {
/* ==================== General Options ==================== */
/**
* Setup markdown-it instance before applying plugins
* Configure the markdown-it instance before any plugins are applied.
*/
preConfig?: (md: MarkdownItAsync) => Awaitable<void>
/**
* Setup markdown-it instance
* Configure the markdown-it instance after all built-in plugins are applied.
*/
config?: (md: MarkdownItAsync) => Awaitable<void>
/**
* Disable cache (experimental)
*/
cache?: boolean
/**
* HTML attributes applied to external links.
* @default { target: '_blank', rel: 'noreferrer' }
*/
externalLinks?: Record<string, string>
/* ==================== Syntax Highlighting ==================== */
@ -72,7 +76,8 @@ export interface MarkdownOptions extends MarkdownItAsyncOptions {
/**
* Custom theme for syntax highlighting.
*
* You can also pass an object with `light` and `dark` themes to support dual themes.
* You can also pass an object with `light` and `dark` themes to support
* dual themes.
*
* @example { theme: 'github-dark' }
* @example { theme: { light: 'github-light', dark: 'github-dark' } }
@ -91,7 +96,8 @@ export interface MarkdownOptions extends MarkdownItAsyncOptions {
/**
* Custom language aliases for syntax highlighting.
* Maps custom language names to existing languages.
* Alias lookup is case-insensitive and underscores in language names are displayed as spaces.
* Alias lookup is case-insensitive and underscores in language names are
* displayed as spaces.
*
* @example
*
@ -113,31 +119,11 @@ export interface MarkdownOptions extends MarkdownItAsyncOptions {
*/
languageAlias?: Record<string, string>
/**
* Custom language labels for display.
* Overrides the default language label shown in code blocks.
* Keys are case-insensitive.
*
* @example { 'vue': 'Vue SFC' }
*/
languageLabel?: Record<string, string>
/**
* Show line numbers in code blocks. Requires the `preWrapper` plugin.
* @default false
*/
lineNumbers?: boolean
/**
* Wrap code blocks in a container carrying the language label and the
* copy button. The default theme's code block styling relies on this
* markup. Disabling it also disables `lineNumbers`.
* @default true
*/
preWrapper?: boolean
/**
* Fallback language when the specified language is not available.
* Fallback language used when the specified language is not available.
*/
defaultHighlightLang?: string
/**
* Transformers applied to code blocks
* Transformers applied to code blocks.
* @see https://shiki.style/guide/transformers
*/
codeTransformers?: ShikiTransformer[]
@ -148,24 +134,46 @@ export interface MarkdownOptions extends MarkdownItAsyncOptions {
*/
colorReplacements?: CodeToHastOptions['colorReplacements']
/**
* Setup Shiki instance
* Configure the Shiki instance.
*/
shikiSetup?: (shiki: Highlighter) => void | Promise<void>
/* ==================== Code Blocks ==================== */
/**
* Wrap code blocks in a container carrying the language label and the
* copy button. The default theme's code block styling relies on this
* markup. Disabling it also disables `lineNumbers`.
* @default true
*/
preWrapper?: boolean
/**
* The tooltip text for the copy button in code blocks
* The tooltip text for the copy button in code blocks.
* @default 'Copy Code'
*/
codeCopyButtonTitle?: string
/* ==================== Markdown It Plugins ==================== */
/**
* Options for `markdown-it-anchor`. Set to `false` to disable adding ids
* and anchor links to headings. Note that the default theme's outline and
* heading hash links rely on these ids.
* @see https://github.com/valeriangalliat/markdown-it-anchor
* Custom language labels for display.
* Overrides the default language label shown in code blocks.
* Keys are case-insensitive.
*
* @example { 'vue': 'Vue SFC' }
*/
anchor?: anchorPlugin.AnchorOptions | false
languageLabel?: Record<string, string>
/**
* Show line numbers in code blocks. Requires the `preWrapper` plugin.
* @default false
*/
lineNumbers?: boolean
/**
* Enables importing code snippets from files with `<<<`.
* @default true
* @see https://vitepress.dev/guide/markdown#import-code-snippets
*/
snippet?: boolean
/* ==================== Markdown Extensions ==================== */
/**
* Options for `markdown-it-attrs`. Set to `false` to disable.
* @see https://github.com/arve0/markdown-it-attrs
@ -183,20 +191,26 @@ export interface MarkdownOptions extends MarkdownItAsyncOptions {
}
| false
/**
* Options for `@mdit-vue/plugin-frontmatter`
* @see https://github.com/mdit-vue/mdit-vue/tree/main/packages/plugin-frontmatter
* Improves emphasis (`**bold**`) handling in Japanese, Chinese, and
* Korean text.
* @default true
* @see https://github.com/tats-u/markdown-cjk-friendly
*/
frontmatter?: FrontmatterPluginOptions
cjkFriendlyEmphasis?: boolean
/**
* Options for `@mdit-vue/plugin-headers`
* @see https://github.com/mdit-vue/mdit-vue/tree/main/packages/plugin-headers
* Options for `markdown-it-anchor`. Set to `false` to disable adding ids
* and anchor links to headings. Note that the default theme's outline and
* heading hash links rely on these ids.
* @see https://github.com/valeriangalliat/markdown-it-anchor
*/
headers?: HeadersPluginOptions | boolean
anchor?: anchorPlugin.AnchorOptions | false
/**
* Options for `@mdit-vue/plugin-sfc`
* @see https://github.com/mdit-vue/mdit-vue/tree/main/packages/plugin-sfc
* Options for `@mdit-vue/plugin-headers`. Set to `true` or pass options
* to collect page headers into page data.
* @default false
* @see https://github.com/mdit-vue/mdit-vue/tree/main/packages/plugin-headers
*/
sfc?: SfcPluginOptions
headers?: HeadersPluginOptions | boolean
/**
* Options for `@mdit-vue/plugin-toc`. Set to `false` to disable the
* `[[toc]]` syntax.
@ -204,30 +218,32 @@ export interface MarkdownOptions extends MarkdownItAsyncOptions {
*/
toc?: TocPluginOptions | false
/**
* Options for `@mdit-vue/plugin-component`. Set to `false` to disable.
* @see https://github.com/mdit-vue/mdit-vue/tree/main/packages/plugin-component
* Math support.
*
* You need to install `markdown-it-mathjax3` and set `math` to `true` to
* enable it. You can also pass options to `markdown-it-mathjax3` here.
* @default false
* @see https://vitepress.dev/guide/markdown#math-equations
*/
component?: ComponentPluginOptions | false
math?: boolean | any
/**
* Enables importing code snippets from files with `<<<`.
* @default true
* @see https://vitepress.dev/guide/markdown#import-code-snippets
* Custom labels for the built-in containers (`::: tip` etc.). Also used
* as the default titles of GitHub-flavored alerts.
* @see https://vitepress.dev/guide/markdown#custom-containers
*/
snippet?: boolean
container?: ContainerOptions
/**
* Options for `markdown-it-container`
* @see https://github.com/markdown-it/markdown-it-container
* Whether to enable GitHub-flavored alerts (`> [!NOTE]`).
* @default true
* @see https://vitepress.dev/guide/markdown#github-flavored-alerts
*/
container?: ContainerOptions
gfmAlerts?: boolean
/**
* Math support
*
* You need to install `markdown-it-mathjax3` and set `math` to `true` to enable it.
* You can also pass options to `markdown-it-mathjax3` here.
* @default false
* @see https://vitepress.dev/guide/markdown#math-equations
* Add `tabindex="0"` to tables so keyboard users can focus and scroll
* them.
* @default true
*/
math?: boolean | any
tableTabIndex?: boolean
/**
* Options for the image plugin (resolves image sources against the public
* directory, adds dimensions, and supports lazy loading). Set to `false`
@ -235,24 +251,24 @@ export interface MarkdownOptions extends MarkdownItAsyncOptions {
* @see https://vitepress.dev/guide/markdown#image-lazy-loading
*/
image?: ImageOptions | false
/* ==================== Vue Integration ==================== */
/**
* Allows disabling the github alerts plugin
* @default true
* @see https://vitepress.dev/guide/markdown#github-flavored-alerts
* Options for `@mdit-vue/plugin-component`. Set to `false` to disable.
* @see https://github.com/mdit-vue/mdit-vue/tree/main/packages/plugin-component
*/
gfmAlerts?: boolean
component?: ComponentPluginOptions | false
/**
* Add `tabindex="0"` to tables so keyboard users can focus and scroll them.
* @default true
* Options for `@mdit-vue/plugin-frontmatter`.
* @see https://github.com/mdit-vue/mdit-vue/tree/main/packages/plugin-frontmatter
*/
tableTabIndex?: boolean
frontmatter?: FrontmatterPluginOptions
/**
* Allows disabling the CJK-friendly plugin.
* This plugin adds support for emphasis marks (**bold**) in Japanese, Chinese, and Korean text.
* @default true
* @see https://github.com/tats-u/markdown-cjk-friendly
* Options for `@mdit-vue/plugin-sfc`.
* @see https://github.com/mdit-vue/mdit-vue/tree/main/packages/plugin-sfc
*/
cjkFriendlyEmphasis?: boolean
sfc?: SfcPluginOptions
}
export type MarkdownRenderer = MarkdownItAsync
@ -287,7 +303,7 @@ export async function createMarkdownRenderer(
const theme = options.theme ?? { light: 'github-light', dark: 'github-dark' }
const codeCopyButtonTitle = options.codeCopyButtonTitle || 'Copy Code'
let [highlight, dispose] = options.highlight
const [highlight, dispose] = options.highlight
? [options.highlight, () => {}]
: await createHighlighter(theme, options, logger)
@ -305,21 +321,22 @@ export async function createMarkdownRenderer(
const slugify =
(options.anchor ? options.anchor.slugify : undefined) ?? defaultSlugify
// custom plugins
if (options.component !== false) {
componentPlugin(md, options.component)
}
// VitePress customizations
if (options.preWrapper !== false) {
preWrapperPlugin(md, {
codeCopyButtonTitle,
languageLabel: options.languageLabel
})
// must be applied after preWrapper as it augments its output
lineNumberPlugin(md, options.lineNumbers)
}
if (options.snippet !== false) {
snippetPlugin(md, srcDir)
}
containerPlugin(md, options.container)
if (options.gfmAlerts !== false) {
gitHubAlertsPlugin(md, options.container)
}
if (options.image !== false) {
imagePlugin(md, publicDir, options.image)
}
@ -329,25 +346,23 @@ export async function createMarkdownRenderer(
base,
slugify
)
if (options.tableTabIndex !== false) {
tablePlugin(md)
}
if (options.gfmAlerts !== false) {
gitHubAlertsPlugin(md, options.container)
}
// third party plugins
// community plugins
if (options.attrs !== false) {
attrsPlugin(md, options.attrs)
}
if (options.emoji !== false) {
emojiPlugin(md, options.emoji)
}
// mdit-vue plugins
if (options.cjkFriendlyEmphasis !== false) {
mditCjkFriendly(md)
}
if (options.anchor !== false) {
// must be applied after attrs so that user-defined ids from curly
// attributes take precedence over slugified ones
anchorPlugin(md, {
slugify,
getTokensText: (tokens) => {
@ -384,32 +399,6 @@ export async function createMarkdownRenderer(
...options.anchor
})
}
frontmatterPlugin(md, options.frontmatter)
if (options.headers) {
headersPlugin(md, {
level: [2, 3, 4, 5, 6],
slugify,
...(typeof options.headers === 'boolean' ? undefined : options.headers)
})
}
sfcPlugin(md, options.sfc)
titlePlugin(md)
const tocOptions = options.toc
if (tocOptions !== false) {
tocPlugin(md, {
slugify,
...tocOptions,
format: (s) => {
const title = s.replaceAll('&amp;', '&') // encoded twice because of restoreEntities
return tocOptions?.format?.(title) ?? title
}
})
}
if (options.math) {
try {
const mathPlugin = await import('markdown-it-mathjax3')
@ -435,8 +424,30 @@ export async function createMarkdownRenderer(
}
}
if (options.cjkFriendlyEmphasis !== false) {
mditCjkFriendly(md)
// mdit-vue plugins
if (options.component !== false) {
componentPlugin(md, options.component)
}
frontmatterPlugin(md, options.frontmatter)
if (options.headers) {
headersPlugin(md, {
level: [2, 3, 4, 5, 6],
slugify,
...(typeof options.headers === 'boolean' ? undefined : options.headers)
})
}
sfcPlugin(md, options.sfc)
titlePlugin(md)
const tocOptions = options.toc
if (tocOptions !== false) {
tocPlugin(md, {
slugify,
...tocOptions,
format: (s) => {
const title = s.replaceAll('&amp;', '&') // encoded twice because of restoreEntities
return tocOptions?.format?.(title) ?? title
}
})
}
// apply user config

Loading…
Cancel
Save