diff --git a/.vscode/settings.json b/.vscode/settings.json index b2b10e18e6..0914fe961c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -33,9 +33,10 @@ "globby", "gtag", "jsonld", + "katex", "lazyload", "lightmode", - "katex", + "linkify", "mathjax", "mdit", "nord", @@ -48,6 +49,7 @@ "shiki", "shikijs", "slugify", + "tasklist", "tsbuildinfo", "twikoo", "umami", diff --git a/docs/.vuepress/config.ts b/docs/.vuepress/config.ts index 4c9454a4b1..ec603a0792 100644 --- a/docs/.vuepress/config.ts +++ b/docs/.vuepress/config.ts @@ -1,5 +1,4 @@ import process from 'node:process' -import { footnote } from '@mdit/plugin-footnote' import { viteBundler } from '@vuepress/bundler-vite' import { webpackBundler } from '@vuepress/bundler-webpack' import { getRealPath } from '@vuepress/helper' @@ -8,6 +7,7 @@ import { catalogPlugin } from '@vuepress/plugin-catalog' import { commentPlugin } from '@vuepress/plugin-comment' import { docsearchPlugin } from '@vuepress/plugin-docsearch' import { feedPlugin } from '@vuepress/plugin-feed' +import { markdownExtPlugin } from '@vuepress/plugin-markdown-ext' import { markdownImagePlugin } from '@vuepress/plugin-markdown-image' import { markdownMathPlugin } from '@vuepress/plugin-markdown-math' import { redirectPlugin } from '@vuepress/plugin-redirect' @@ -73,10 +73,6 @@ export default defineUserConfig({ }, }, - extendsMarkdown: (md) => { - md.use(footnote) - }, - // configure default theme theme, @@ -91,6 +87,11 @@ export default defineUserConfig({ json: true, rss: true, }), + markdownExtPlugin({ + gfm: true, + component: true, + vPre: true, + }), markdownImagePlugin({ figure: true, mark: true, diff --git a/docs/.vuepress/configs/sidebar/en.ts b/docs/.vuepress/configs/sidebar/en.ts index f9c25a813d..14cecbd128 100644 --- a/docs/.vuepress/configs/sidebar/en.ts +++ b/docs/.vuepress/configs/sidebar/en.ts @@ -99,6 +99,7 @@ export const sidebarEn: SidebarOptions = { '/plugins/markdown/': [ 'append-date', 'markdown-container', + 'markdown-ext', 'markdown-image', 'markdown-hint', 'markdown-math', diff --git a/docs/.vuepress/configs/sidebar/zh.ts b/docs/.vuepress/configs/sidebar/zh.ts index 88c71831cf..41c807fc1a 100644 --- a/docs/.vuepress/configs/sidebar/zh.ts +++ b/docs/.vuepress/configs/sidebar/zh.ts @@ -99,6 +99,7 @@ export const sidebarZh: SidebarOptions = { '/zh/plugins/markdown/': [ 'append-date', 'markdown-container', + 'markdown-ext', 'markdown-image', 'markdown-hint', 'markdown-math', diff --git a/docs/package.json b/docs/package.json index 3e8ae3002c..5d3e203adf 100644 --- a/docs/package.json +++ b/docs/package.json @@ -9,7 +9,6 @@ "docs:serve": "http-server -a localhost .vuepress/dist" }, "dependencies": { - "@mdit/plugin-footnote": "0.13.1", "@vuepress/bundler-vite": "2.0.0-rc.18", "@vuepress/bundler-webpack": "2.0.0-rc.18", "@vuepress/plugin-back-to-top": "workspace:*", @@ -19,6 +18,7 @@ "@vuepress/plugin-copy-code": "workspace:*", "@vuepress/plugin-docsearch": "workspace:*", "@vuepress/plugin-feed": "workspace:*", + "@vuepress/plugin-markdown-ext": "workspace:*", "@vuepress/plugin-markdown-image": "workspace:*", "@vuepress/plugin-markdown-math": "workspace:*", "@vuepress/plugin-markdown-tab": "workspace:*", diff --git a/docs/plugins/markdown/markdown-ext.md b/docs/plugins/markdown/markdown-ext.md new file mode 100644 index 0000000000..0b1ba788ce --- /dev/null +++ b/docs/plugins/markdown/markdown-ext.md @@ -0,0 +1,238 @@ +# markdown-ext + + + +Add basic GFM support to VuePress with useful features. + +## Usage + +```bash +npm i -D @vuepress/plugin-markdown-ext@next +``` + +```ts +import { markdownExtPlugin } from '@vuepress/plugin-markdown-ext' + +export default { + plugins: [ + markdownExtPlugin({ + // options + }), + ], +} +``` + +## Syntax + +### Footnote + +- Use `[^Anchor text]` in Markdown to define a footnote + +- Use `[^Anchor text]: ...` to describe footnote content + +- If there are multiple paragraphs in footnote, the paragraph show be double indented + +::: details Demo + +Footnote 1 link[^first]. + +Footnote 2 link[^second]. + +Inline footnote^[Text of inline footnote] definition. + +Duplicated footnote reference[^second]. + +[^first]: Footnote **can have markup** + + and multiple paragraphs. + +[^second]: Footnote text. + +```md +Footnote 1 link[^first]. + +Footnote 2 link[^second]. + +Inline footnote^[Text of inline footnote] definition. + +Duplicated footnote reference[^second]. + +[^first]: Footnote **can have markup** + + and multiple paragraphs. + +[^second]: Footnote text. +``` + +::: + +### Task list + +- Use `- [ ] some text` to render an unchecked task item. +- Use `- [x] some text` to render a checked task item. (Capital `X` is also supported) + +::: details Demo + +- [ ] Plan A +- [x] Plan B + +```md +- [ ] Plan A +- [x] Plan B +``` + +::: + +### Component + +You can use component fence block to add a component into your markdown content. Both YAML and JSON format props data are supported: + +- YAML : + + ````md + ```component ComponentName + # component data here + ``` + ```` + +- JSON: + + ````md + ```component ComponentName + { + // component data here + } + ``` + ```` + +::: details Demo + +```component Badge +text: Mr.Hope +type: tip +``` + +```component Badge +{ + "text": "Mr.Hope", + "type": "tip" +} +``` + +````md +```component Badge +text: Mr.Hope +type: tip +``` + +```component Badge +{ + "text": "Mr.Hope", + "type": "tip" +} +``` +```` + +::: + +### v-pre + +You can use any mustache syntax as raw text in `v-pre` container: + +:::: details Demo + +::: v-pre + +{{ abc }} + +::: + +```md +::: v-pre + +{{ abc }} + +::: +``` + +:::: + +## Options + +### gfm + +- Type: `boolean` + +- Details: + + Whether tweaks the behavior and features to be more similar to GitHub Flavored Markdown. + + `markdown-it` already supports tables and strike through by default. If this option is `true`, the following new features will be enabled: + + - Auto link (named `linkify` in `markdown-it`) + - Hard breaks + - Footnote + - Task list + + Note, all behavior is not exactly the same as GitHub Flavored Markdown. + +### footnote + +- Type: `boolean` +- Default: `false` +- Enabled in GFM: Yes +- Details: Whether to enable footnote format support. + +### tasklist + +- Type: `TaskListOptions | boolean` + + ```ts + interface TaskListOptions { + /** + * Whether disable checkbox + * + * @default true + */ + disabled?: boolean + + /** + * Whether use `` to wrap text + * + * @default true + */ + label?: boolean + } + ``` + +- Default: `false` +- Enabled in GFM: Yes +- Details: + + Whether to enable tasklist format support. You can pass an object to config tasklist. + +### breaks + +- Type: `boolean` +- Default: `false` +- Enabled in GFM: Yes +- Details: Whether convert `\n` in paragraphs into ``s + +### linkify + +- Type: `boolean` +- Default: `false` +- Enabled in GFM: Yes +- Details: Whether convert URL-like text into links + +### component + +- Type: `boolean` +- Default: `false` +- Details: Whether to enable component fence support + +### vPre + +- Type: `boolean` +- Default: `false` +- Details: Whether to enable v-pre wrapper. diff --git a/docs/zh/plugins/markdown/markdown-ext.md b/docs/zh/plugins/markdown/markdown-ext.md new file mode 100644 index 0000000000..8f984ad3aa --- /dev/null +++ b/docs/zh/plugins/markdown/markdown-ext.md @@ -0,0 +1,238 @@ +# markdown-ext + + + +为 VuePress 添加基本的 GFM 支持,以及一些有用的功能。 + +## 使用 + +```bash +npm i -D @vuepress/plugin-markdown-ext@next +``` + +```ts +import { markdownExtPlugin } from '@vuepress/plugin-markdown-ext' + +export default { + plugins: [ + markdownExtPlugin({ + // 选项 + }), + ], +} +``` + +## 语法 + +### 脚注 + +- 在 Markdown 中使用 `[^锚点文字]` 来定义脚注。 + +- 在之后的任何位置使用 `[^锚点文字]: ...` 来描述脚注内容。 + +- 如果脚注包含多个段落,其后的段落应当保持双层缩进。 + +::: details 示例 + +脚注 1 链接[^链接1]。 + +脚注 2 链接[^链接2]。 + +行内的脚注^[行内脚注文本] 定义。 + +重复的页脚定义[^second]。 + +[^链接1]: 脚注 **可以包含特殊标记** + + 也可以由多个段落组成 + +[^链接2]: 脚注文字。 + +```md +脚注 1 链接[^链接1]。 + +脚注 2 链接[^链接2]。 + +行内的脚注^[行内脚注文本] 定义。 + +重复的页脚定义[^second]。 + +[^链接1]: 脚注 **可以包含特殊标记** + + 也可以由多个段落组成 + +[^链接2]: 脚注文字。 +``` + +::: + +### 任务列表 + +- 使用 `- [ ] 一些文字` 渲染一个未勾选的任务项 +- 使用 `- [x] 一些文字` 渲染一个勾选了的任务项 (我们也支持大写的 `X`) + +::: details 示例 + +- [ ] 计划 A +- [x] 计划 B + +```md +- [ ] 计划 A +- [x] 计划 B +``` + +::: + +### 组件 + +你可以使用 component 代码块来在 Markdown 中添加组件。YAML 和 JSON 的数据格式均受支持: + +- YAML : + + ````md + ```component 组件名称 + # 组件数据 + ``` + ```` + +- JSON: + + ````md + ```component 组件名称 + { + // 组件数据 + } + ``` + ```` + +::: details 示例 + +```component Badge +text: Mr.Hope +type: tip +``` + +```component Badge +{ + "text": "Mr.Hope", + "type": "tip" +} +``` + +````md +```component Badge +text: Mr.Hope +type: tip +``` + +```component Badge +{ + "text": "Mr.Hope", + "type": "tip" +} +``` +```` + +::: + +### v-pre + +你可以使用 `v-pre` 容器来渲染将任何 mustache 语法作为纯文本渲染。 + +:::: details 示例 + +::: v-pre + +{{ abc }} + +::: + +```md +::: v-pre + +{{ abc }} + +::: +``` + +:::: + +## 选项 + +### gfm + +- 类型:`boolean` + +- 详情: + + 是否调整行为和功能,使其更类似于 GitHub Flavored Markdown。 + + `markdown-it` 已经默认支持表格与删除线。如果此选项为 `true`,则会启用以下新功能: + + - 自动链接(在 `markdown-it` 中命名为 `linkify`) + - 硬换行 + - 脚注 + - 任务列表 + + 请注意,所有行为并不完全与 GitHub Flavored Markdown 相同。 + +### footnote + +- 类型:`boolean` +- 默认值:`false` +- 在 GFM 中启用:是 +- 详情:是否启用页脚格式支持。 + +### tasklist + +- 类型:`TaskListOptions | boolean` + + ```ts + interface TaskListOptions { + /** + * 是否禁用 checkbox + * + * @default true + */ + disabled?: boolean + + /** + * 是否使用 `` 来包裹文字 + * + * @default true + */ + label?: boolean + } + ``` + +- 默认值:`false` +- 在 GFM 中启用:是 +- 详情: + + 是否启用任务列表格式支持。您可以传递一个对象来配置任务列表。 + +### breaks + +- 类型:`boolean` +- 默认值:`false` +- 在 GFM 中启用:是 +- 详情:是否将段落中的 `\n` 转换为 ``。 + +### linkify + +- 类型:`boolean` +- 默认值:`false` +- 在 GFM 中启用:是 +- 详情:是否将类似 URL 的文本转换为链接。 + +### component + +- 类型:`boolean` +- 默认值:`false` +- 详情:是否启用组件代码块支持。 + +### vPre + +- 类型:`boolean` +- 默认值:`false` +- 详情:是否启用 `v-pre` 块支持。 diff --git a/plugins/markdown/plugin-markdown-ext/package.json b/plugins/markdown/plugin-markdown-ext/package.json new file mode 100644 index 0000000000..ec8a6ee6fb --- /dev/null +++ b/plugins/markdown/plugin-markdown-ext/package.json @@ -0,0 +1,60 @@ +{ + "name": "@vuepress/plugin-markdown-ext", + "version": "2.0.0-rc.54", + "description": "VuePress plugin - markdown extension", + "keywords": [ + "vuepress-plugin", + "vuepress", + "plugin", + "markdown", + "extension" + ], + "homepage": "https://ecosystem.vuejs.press/plugins/markdown/markdown-ext.html", + "bugs": { + "url": "https://github.com/vuepress/ecosystem/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/vuepress/ecosystem.git", + "directory": "plugins/markdown/plugin-markdown-ext" + }, + "license": "MIT", + "author": { + "name": "Mr.Hope", + "email": "mister-hope@outlook.com", + "url": "https://mister-hope.com" + }, + "type": "module", + "exports": { + ".": "./lib/node/index.js", + "./package.json": "./package.json" + }, + "main": "./lib/node/index.js", + "types": "./lib/node/index.d.ts", + "files": [ + "lib" + ], + "scripts": { + "build": "tsc -b tsconfig.build.json", + "bundle": "rollup -c rollup.config.ts --configPlugin esbuild", + "clean": "rimraf --glob ./lib ./*.tsbuildinfo" + }, + "dependencies": { + "@mdit/plugin-container": "^0.13.1", + "@mdit/plugin-footnote": "^0.13.1", + "@mdit/plugin-tasklist": "^0.13.1", + "@types/markdown-it": "^14.1.2", + "@vuepress/helper": "workspace:*", + "js-yaml": "^4.1.0" + }, + "peerDependencies": { + "vuepress": "2.0.0-rc.18" + }, + "devDependencies": { + "@types/js-yaml": "4.0.9", + "markdown-it": "^14.1.0" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/plugins/markdown/plugin-markdown-ext/rollup.config.ts b/plugins/markdown/plugin-markdown-ext/rollup.config.ts new file mode 100644 index 0000000000..f0a0f96e21 --- /dev/null +++ b/plugins/markdown/plugin-markdown-ext/rollup.config.ts @@ -0,0 +1,10 @@ +import { rollupBundle } from '../../../scripts/rollup.js' + +export default rollupBundle('node/index', { + external: [ + '@mdit/plugin-container', + '@mdit/plugin-footnote', + '@mdit/plugin-tasklist', + 'js-yaml', + ], +}) diff --git a/plugins/markdown/plugin-markdown-ext/src/node/index.ts b/plugins/markdown/plugin-markdown-ext/src/node/index.ts new file mode 100644 index 0000000000..9284b114af --- /dev/null +++ b/plugins/markdown/plugin-markdown-ext/src/node/index.ts @@ -0,0 +1,3 @@ +export * from './markdownExtPlugin.js' +export * from './markdown-it-plugins/index.js' +export type * from './options.js' diff --git a/plugins/markdown/plugin-markdown-ext/src/node/markdown-it-plugins/component.ts b/plugins/markdown/plugin-markdown-ext/src/node/markdown-it-plugins/component.ts new file mode 100644 index 0000000000..b476a313fd --- /dev/null +++ b/plugins/markdown/plugin-markdown-ext/src/node/markdown-it-plugins/component.ts @@ -0,0 +1,67 @@ +import { load } from 'js-yaml' +import type { Options, PluginSimple } from 'markdown-it' +import type { RenderRule } from 'markdown-it/lib/renderer.mjs' +import type Token from 'markdown-it/lib/token.mjs' +import type { MarkdownEnv } from 'vuepress/markdown' + +import { logger } from '../utils.js' +import { stringifyProp } from './utils.js' + +const getComponentRender = + (name: string): RenderRule => + ( + tokens: Token[], + index: number, + _options: Options, + { filePathRelative }: MarkdownEnv, + ): string => { + const token = tokens[index] + const { content } = token + + let config: unknown = null + + if (content.trim().startsWith('{')) + try { + config = JSON.parse(content) as unknown + } catch { + // Do nothing + } + else + try { + config = load(content) + } catch { + // Do nothing + } + + if (config) return `<${name} v-bind='${stringifyProp(config)}' />` + + logger.error( + `Invalid ${name} config${ + filePathRelative ? ` found in ${filePathRelative}` : '' + }: +${content} +`, + ) + + return '' + } + +export const component: PluginSimple = (md) => { + // Handle ```component blocks + const { fence } = md.renderer.rules + + md.renderer.rules.fence = (...args): string => { + const [tokens, index] = args + const { info } = tokens[index] + + const [fenceName, componentName] = info.split(' ', 2) + + if (fenceName === 'component') { + const render = getComponentRender(componentName) + + return render(...args) + } + + return fence!(...args) + } +} diff --git a/plugins/markdown/plugin-markdown-ext/src/node/markdown-it-plugins/index.ts b/plugins/markdown/plugin-markdown-ext/src/node/markdown-it-plugins/index.ts new file mode 100644 index 0000000000..cf1d7d0c90 --- /dev/null +++ b/plugins/markdown/plugin-markdown-ext/src/node/markdown-it-plugins/index.ts @@ -0,0 +1,2 @@ +export * from './component.js' +export * from './vPre.js' diff --git a/plugins/markdown/plugin-markdown-ext/src/node/markdown-it-plugins/utils.ts b/plugins/markdown/plugin-markdown-ext/src/node/markdown-it-plugins/utils.ts new file mode 100644 index 0000000000..b78742555e --- /dev/null +++ b/plugins/markdown/plugin-markdown-ext/src/node/markdown-it-plugins/utils.ts @@ -0,0 +1,3 @@ +// Single quote will break @vue/compiler-sfc +export const stringifyProp = (data: unknown): string => + JSON.stringify(data).replace(/'/g, ''') diff --git a/plugins/markdown/plugin-markdown-ext/src/node/markdown-it-plugins/vPre.ts b/plugins/markdown/plugin-markdown-ext/src/node/markdown-it-plugins/vPre.ts new file mode 100644 index 0000000000..0b46ac88dc --- /dev/null +++ b/plugins/markdown/plugin-markdown-ext/src/node/markdown-it-plugins/vPre.ts @@ -0,0 +1,10 @@ +import { container } from '@mdit/plugin-container' +import type { PluginSimple } from 'markdown-it' + +export const vPre: PluginSimple = (md) => { + container(md, { + name: 'v-pre', + openRender: () => `\n`, + closeRender: () => '\n', + }) +} diff --git a/plugins/markdown/plugin-markdown-ext/src/node/markdownExtPlugin.ts b/plugins/markdown/plugin-markdown-ext/src/node/markdownExtPlugin.ts new file mode 100644 index 0000000000..1b4ab8be0b --- /dev/null +++ b/plugins/markdown/plugin-markdown-ext/src/node/markdownExtPlugin.ts @@ -0,0 +1,38 @@ +import { footnote as footnotePlugin } from '@mdit/plugin-footnote' +import { tasklist as tasklistPlugin } from '@mdit/plugin-tasklist' +import type { Plugin } from 'vuepress/core' +import { isPlainObject } from 'vuepress/shared' + +import { + component as componentPlugin, + vPre as vPrePlugin, +} from './markdown-it-plugins/index.js' +import type { MarkdownExtPluginOptions } from './options.js' +import { PLUGIN_NAME } from './utils.js' + +export const markdownExtPlugin = ({ + gfm, + breaks, + linkify, + footnote, + tasklist, + + component, + vPre, +}: MarkdownExtPluginOptions): Plugin => { + return { + name: PLUGIN_NAME, + + extendsMarkdown: (md) => { + // Behavior + if (breaks ?? gfm) md.options.breaks = true + if (linkify ?? gfm) md.options.linkify = true + + if (footnote ?? gfm) md.use(footnotePlugin) + if (tasklist ?? gfm) + md.use(tasklistPlugin, [isPlainObject(tasklist) ? tasklist : {}]) + if (component) md.use(componentPlugin) + if (vPre) md.use(vPrePlugin) + }, + } +} diff --git a/plugins/markdown/plugin-markdown-ext/src/node/options.ts b/plugins/markdown/plugin-markdown-ext/src/node/options.ts new file mode 100644 index 0000000000..984369d657 --- /dev/null +++ b/plugins/markdown/plugin-markdown-ext/src/node/options.ts @@ -0,0 +1,77 @@ +import type { MarkdownItTaskListOptions } from '@mdit/plugin-tasklist' + +/** + * Options of markdown-ext plugin + */ +export interface MarkdownExtPluginOptions { + /** + * Whether enable standard GFM support + * + * 是否启用标准的 GitHub Favor Markdown 支持 + * + * @default false + */ + gfm?: boolean + + /** + * Whether convert `\n` in paragraphs into ``s + * + * 是否将段落中的 `\n` 转换为 `` + * + * @description enabled in gfm mode + * + * @default false + */ + breaks?: boolean + + /** + * Whether convert URL-like text into links + * + * 是否将文字中的链接格式文字转换为链接 + * + * @description enabled in gfm mode + * + * @default false + */ + linkify?: boolean + + /** + * Whether to enable footnote format support + * + * 是否启用脚注格式支持。 + * + * @description enabled in gfm mode + * + * @default false + */ + footnote?: boolean + + /** + * Whether to enable tasklist format support + * + * 是否启用任务列表支持 + * + * @description enabled in gfm mode + * + * @default false + */ + tasklist?: MarkdownItTaskListOptions | boolean + + /** + * Whether to enable component support + * + * 是否启用组件支持 + * + * @default false + */ + component?: boolean + + /** + * Whether to enable v-pre wrapper. + * + * 是否启用 v-pre 容器。 + * + * @default false + */ + vPre?: boolean +} diff --git a/plugins/markdown/plugin-markdown-ext/src/node/utils.ts b/plugins/markdown/plugin-markdown-ext/src/node/utils.ts new file mode 100644 index 0000000000..9079776dd9 --- /dev/null +++ b/plugins/markdown/plugin-markdown-ext/src/node/utils.ts @@ -0,0 +1,5 @@ +import { Logger } from '@vuepress/helper' + +export const PLUGIN_NAME = '@vuepress/plugin-markdown-ext' + +export const logger = new Logger(PLUGIN_NAME) diff --git a/plugins/markdown/plugin-markdown-ext/tests/node/__snapshots__/component.spec.ts.snap b/plugins/markdown/plugin-markdown-ext/tests/node/__snapshots__/component.spec.ts.snap new file mode 100644 index 0000000000..1332042f5a --- /dev/null +++ b/plugins/markdown/plugin-markdown-ext/tests/node/__snapshots__/component.spec.ts.snap @@ -0,0 +1,11 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`component > Should not break markdown fence 1`] = ` +"const a = 1; + +" +`; + +exports[`component > Should resolve component fence 1`] = `""`; + +exports[`component > Should resolve component fence 2`] = `""`; diff --git a/plugins/markdown/plugin-markdown-ext/tests/node/component.spec.ts b/plugins/markdown/plugin-markdown-ext/tests/node/component.spec.ts new file mode 100644 index 0000000000..c4916cbbe2 --- /dev/null +++ b/plugins/markdown/plugin-markdown-ext/tests/node/component.spec.ts @@ -0,0 +1,95 @@ +import MarkdownIt from 'markdown-it' +import { describe, expect, it } from 'vitest' + +import { component } from '../../src/node/markdown-it-plugins/component.js' + +describe('component', () => { + const markdownIt = MarkdownIt({ linkify: true }).use(component) + + it('Should resolve component fence', () => { + const result1 = markdownIt.render( + ` +\`\`\`component VPCard +title: A card +desc: A card desc +logo: https://example.com/logo.png +link: https://example.com +color: "#000" +\`\`\` +`, + {}, + ) + + const result2 = markdownIt.render( + ` +\`\`\`component VPCard +{ + "title": "A card", + "desc": "A card desc", + "logo": "https://example.com/logo.png", + "link": "https://example.com", + "color": "#000" +} +\`\`\` +`, + {}, + ) + + expect(result1).toContain('VPCard') + expect(result1).toMatchSnapshot() + expect(result2).toContain('VPCard') + expect(result2).toMatchSnapshot() + }) + + it('Should not throw with invalid syntax', () => { + const result1 = markdownIt.render( + ` +\`\`\`component VPCard +title: a +title: b +\`\`\` +`, + {}, + ) + + const result2 = markdownIt.render( + ` +\`\`\`component VPCard +title: a +title: b +\`\`\` +`, + {}, + ) + + expect(result1).toEqual('') + expect(result2).toEqual('') + }) + + it('Should drop when receiving a invalid syntax', () => { + const result = markdownIt.render( + ` +\`\`\`component VPCard +{a:1} +\`\`\` +`, + {}, + ) + + expect(result).toMatch('') + }) + + it('Should not break markdown fence', () => { + const result = markdownIt.render( + ` +\`\`\`js +const a = 1; +\`\`\` +`, + {}, + ) + + expect(result).toMatch(/[\s\S]*<\/pre>/) + expect(result).toMatchSnapshot() + }) +}) diff --git a/plugins/markdown/plugin-markdown-ext/tests/node/vPre.spec.ts b/plugins/markdown/plugin-markdown-ext/tests/node/vPre.spec.ts new file mode 100644 index 0000000000..0c8c845f20 --- /dev/null +++ b/plugins/markdown/plugin-markdown-ext/tests/node/vPre.spec.ts @@ -0,0 +1,14 @@ +import MarkdownIt from 'markdown-it' +import { describe, expect, it } from 'vitest' + +import { vPre } from '../../src/node/markdown-it-plugins/vPre.js' + +describe('v-pre', () => { + it('should add v-pre wrapper', () => { + const markdownIt = MarkdownIt({ linkify: true }).use(vPre) + + expect(markdownIt.render('::: v-pre\n{{a}}\n:::\n')).toBe( + '\n{{a}}\n\n', + ) + }) +}) diff --git a/plugins/markdown/plugin-markdown-ext/tsconfig.build.json b/plugins/markdown/plugin-markdown-ext/tsconfig.build.json new file mode 100644 index 0000000000..1e7fd0d655 --- /dev/null +++ b/plugins/markdown/plugin-markdown-ext/tsconfig.build.json @@ -0,0 +1,8 @@ +{ + "extends": "../../../tsconfig.build.json", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./lib" + }, + "include": ["./src"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aabb288722..abdf37cea5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -119,9 +119,6 @@ importers: docs: dependencies: - '@mdit/plugin-footnote': - specifier: 0.13.1 - version: 0.13.1(markdown-it@14.1.0) '@vuepress/bundler-vite': specifier: 2.0.0-rc.18 version: 2.0.0-rc.18(@types/node@22.7.7)(jiti@1.21.6)(lightningcss@1.27.0)(sass-embedded@1.80.3)(sass@1.80.3)(terser@5.36.0)(tsx@4.19.1)(typescript@5.6.3)(yaml@2.4.5) @@ -152,6 +149,9 @@ importers: '@vuepress/plugin-feed': specifier: workspace:* version: link:../plugins/blog/plugin-feed + '@vuepress/plugin-markdown-ext': + specifier: workspace:* + version: link:../plugins/markdown/plugin-markdown-ext '@vuepress/plugin-markdown-image': specifier: workspace:* version: link:../plugins/markdown/plugin-markdown-image @@ -634,6 +634,37 @@ importers: specifier: ^14.1.0 version: 14.1.0 + plugins/markdown/plugin-markdown-ext: + dependencies: + '@mdit/plugin-container': + specifier: ^0.13.1 + version: 0.13.1(markdown-it@14.1.0) + '@mdit/plugin-footnote': + specifier: ^0.13.1 + version: 0.13.1(markdown-it@14.1.0) + '@mdit/plugin-tasklist': + specifier: ^0.13.1 + version: 0.13.1(markdown-it@14.1.0) + '@types/markdown-it': + specifier: ^14.1.2 + version: 14.1.2 + '@vuepress/helper': + specifier: workspace:* + version: link:../../../tools/helper + js-yaml: + specifier: ^4.1.0 + version: 4.1.0 + vuepress: + specifier: 2.0.0-rc.18 + version: 2.0.0-rc.18(@vuepress/bundler-vite@2.0.0-rc.18(@types/node@22.7.7)(jiti@1.21.6)(lightningcss@1.27.0)(sass-embedded@1.80.3)(sass@1.80.3)(terser@5.36.0)(tsx@4.19.1)(typescript@5.6.3)(yaml@2.4.5))(@vuepress/bundler-webpack@2.0.0-rc.18(esbuild@0.23.1)(typescript@5.6.3))(typescript@5.6.3)(vue@3.5.12(typescript@5.6.3)) + devDependencies: + '@types/js-yaml': + specifier: 4.0.9 + version: 4.0.9 + markdown-it: + specifier: ^14.1.0 + version: 14.1.0 + plugins/markdown/plugin-markdown-hint: dependencies: '@mdit/plugin-alert': @@ -2442,6 +2473,15 @@ packages: markdown-it: optional: true + '@mdit/plugin-tasklist@0.13.1': + resolution: {integrity: sha512-flEWnDJFEB7QZIHRwtkVjAEZe9ONiRQLRg7oazRDBM/3Z0rf28blxOx7qj2QZ/FVzQnRRZTgjFQkpiz61IckKQ==} + engines: {node: '>= 18'} + peerDependencies: + markdown-it: ^14.1.0 + peerDependenciesMeta: + markdown-it: + optional: true + '@mdit/plugin-tex@0.13.1': resolution: {integrity: sha512-lkRf6XrfVfS11FzT3hiooWdOUPJfAd/cnAv4NN/4WU7qOEz0e0HBVQO8PQb5CPwrE94Ld4+E6rQwJfVH1grkwQ==} engines: {node: '>= 18'} @@ -2634,30 +2674,35 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm64-glibc@2.4.1': resolution: {integrity: sha512-BJ7mH985OADVLpbrzCLgrJ3TOpiZggE9FMblfO65PlOCdG++xJpKUJ0Aol74ZUIYfb8WsRlUdgrZxKkz3zXWYA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm64-musl@2.4.1': resolution: {integrity: sha512-p4Xb7JGq3MLgAfYhslU2SjoV9G0kI0Xry0kuxeG/41UfpjHGOhv7UoUDAz/jb1u2elbhazy4rRBL8PegPJFBhA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [musl] '@parcel/watcher-linux-x64-glibc@2.4.1': resolution: {integrity: sha512-s9O3fByZ/2pyYDPoLM6zt92yu6P4E39a03zvO0qCHOTjxmt3GHRMLuRZEWhWLASTMSrrnVNWdVI/+pUElJBBBg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-x64-musl@2.4.1': resolution: {integrity: sha512-L2nZTYR1myLNST0O632g0Dx9LyMNHrn6TOt76sYxWLdff3cB22/GZX2UPtJnaqQPdCRoszoY5rcOj4oMTtp5fQ==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [musl] '@parcel/watcher-win32-arm64@2.4.1': resolution: {integrity: sha512-Uq2BPp5GWhrq/lcuItCHoqxjULU1QYEcyjSO5jqqOK8RNFDBQnenMMx4gAl3v8GiWa59E9+uDM7yZ6LxwUIfRg==} @@ -2772,46 +2817,55 @@ packages: resolution: {integrity: sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.24.0': resolution: {integrity: sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.24.0': resolution: {integrity: sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.24.0': resolution: {integrity: sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-powerpc64le-gnu@4.24.0': resolution: {integrity: sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.24.0': resolution: {integrity: sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-s390x-gnu@4.24.0': resolution: {integrity: sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.24.0': resolution: {integrity: sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.24.0': resolution: {integrity: sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-win32-arm64-msvc@4.24.0': resolution: {integrity: sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==} @@ -2959,6 +3013,9 @@ packages: '@types/istanbul-reports@3.0.4': resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} + '@types/js-yaml@4.0.9': + resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==} + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} @@ -5503,24 +5560,28 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.27.0: resolution: {integrity: sha512-rCGBm2ax7kQ9pBSeITfCW9XSVF69VX+fm5DIpvDZQl4NnQoMQyRwhZQm9pd59m8leZ1IesRqWk2v/DntMo26lg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.27.0: resolution: {integrity: sha512-Dk/jovSI7qqhJDiUibvaikNKI2x6kWPN79AQiD/E/KeQWMjdGe9kw51RAgoWFDi0coP4jinaH14Nrt/J8z3U4A==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.27.0: resolution: {integrity: sha512-QKjTxXm8A9s6v9Tg3Fk0gscCQA1t/HMoF7Woy1u68wCk5kS4fR+q3vXa1p3++REW784cRAtkYKrPy6JKibrEZA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.27.0: resolution: {integrity: sha512-/wXegPS1hnhkeG4OXQKEMQeJd48RDC3qdh+OA8pCuOPCyvnm/yEayrJdJVqzBsqpy1aJklRCVxscpFur80o6iQ==} @@ -9763,6 +9824,12 @@ snapshots: optionalDependencies: markdown-it: 14.1.0 + '@mdit/plugin-tasklist@0.13.1(markdown-it@14.1.0)': + dependencies: + '@types/markdown-it': 14.1.2 + optionalDependencies: + markdown-it: 14.1.0 + '@mdit/plugin-tex@0.13.1(markdown-it@14.1.0)': dependencies: '@types/markdown-it': 14.1.2 @@ -10355,6 +10422,8 @@ snapshots: dependencies: '@types/istanbul-lib-report': 3.0.3 + '@types/js-yaml@4.0.9': {} + '@types/json-schema@7.0.15': {} '@types/jsonfile@6.1.4': diff --git a/tsconfig.build.json b/tsconfig.build.json index 73b6b1cfb2..1076a9fe3f 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -44,6 +44,9 @@ { "path": "./plugins/markdown/plugin-markdown-container/tsconfig.build.json" }, + { + "path": "./plugins/markdown/plugin-markdown-ext/tsconfig.build.json" + }, { "path": "./plugins/markdown/plugin-markdown-hint/tsconfig.build.json" },
const a = 1; +
{{a}}