diff --git a/__tests__/unit/node/markdown/markdown.test.ts b/__tests__/unit/node/markdown/markdown.test.ts
index e8219a54..c042d6ba 100644
--- a/__tests__/unit/node/markdown/markdown.test.ts
+++ b/__tests__/unit/node/markdown/markdown.test.ts
@@ -101,5 +101,13 @@ describe('node/markdown/markdown', () => {
'tabindex'
)
})
+
+ test('cjkFriendlyEmphasis', async () => {
+ const src = 'これは**「テスト」**です'
+ expect(await render(src)).toContain('「テスト」')
+ expect(await render(src, { cjkFriendlyEmphasis: false })).not.toContain(
+ ''
+ )
+ })
})
})
diff --git a/src/node/markdown/markdown.ts b/src/node/markdown/markdown.ts
index 6293b7bc..ee5c3954 100644
--- a/src/node/markdown/markdown.ts
+++ b/src/node/markdown/markdown.ts
@@ -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
/**
- * Setup markdown-it instance
+ * Configure the markdown-it instance after all built-in plugins are applied.
*/
config?: (md: MarkdownItAsync) => Awaitable
/**
* Disable cache (experimental)
*/
cache?: boolean
+ /**
+ * HTML attributes applied to external links.
+ * @default { target: '_blank', rel: 'noreferrer' }
+ */
externalLinks?: Record
/* ==================== 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
/**
- * Custom language labels for display.
- * Overrides the default language label shown in code blocks.
- * Keys are case-insensitive.
- *
- * @example { 'vue': 'Vue SFC' }
- */
- languageLabel?: Record
- /**
- * 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
+
+ /* ==================== 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
+ /**
+ * 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('&', '&') // 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('&', '&') // encoded twice because of restoreEntities
+ return tocOptions?.format?.(title) ?? title
+ }
+ })
}
// apply user config