From 157281aec83f9029b7319825a49403ca115729ca Mon Sep 17 00:00:00 2001 From: pengzhanbo Date: Fri, 5 Dec 2025 17:18:13 +0800 Subject: [PATCH] feat(plugin-md-power): add qrcode syntax plugin for markdown (#777) * feat(plugin-md-power): add qrcode syntax plugin for markdown * chore: tweak --- .vscode/settings.json | 1 + docs/.vuepress/collections/en/theme-guide.ts | 1 + docs/.vuepress/collections/zh/theme-guide.ts | 1 + docs/.vuepress/theme.ts | 1 + docs/en/guide/markdown/qrcode.md | 238 ++++++++++++++++++ docs/guide/markdown/qrcode.md | 204 +++++++++++++++ package.json | 1 + .../__snapshots__/qrcodePlugin.spec.ts.snap | 13 + .../__test__/qrcodePlugin.spec.ts | 27 ++ plugins/plugin-md-power/package.json | 1 + .../src/client/components/VPQRCode.vue | 228 +++++++++++++++++ .../plugin-md-power/src/node/embed/index.ts | 6 + .../plugin-md-power/src/node/embed/qrcode.ts | 37 +++ plugins/plugin-md-power/src/node/plugin.ts | 3 + .../src/node/prepareConfigFile.ts | 8 +- plugins/plugin-md-power/src/shared/index.ts | 1 + plugins/plugin-md-power/src/shared/plugin.ts | 7 + plugins/plugin-md-power/src/shared/qrcode.ts | 89 +++++++ pnpm-lock.yaml | 165 +++++++++++- pnpm-workspace.yaml | 2 + theme/src/node/detector/fields.ts | 1 + 21 files changed, 1030 insertions(+), 5 deletions(-) create mode 100644 docs/en/guide/markdown/qrcode.md create mode 100644 docs/guide/markdown/qrcode.md create mode 100644 plugins/plugin-md-power/__test__/__snapshots__/qrcodePlugin.spec.ts.snap create mode 100644 plugins/plugin-md-power/__test__/qrcodePlugin.spec.ts create mode 100644 plugins/plugin-md-power/src/client/components/VPQRCode.vue create mode 100644 plugins/plugin-md-power/src/node/embed/qrcode.ts create mode 100644 plugins/plugin-md-power/src/shared/qrcode.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 9a472acb..08fc76db 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -76,6 +76,7 @@ "nprogress", "pnpm", "portfinder", + "qrcode", "shiki", "shikiji", "shikijs", diff --git a/docs/.vuepress/collections/en/theme-guide.ts b/docs/.vuepress/collections/en/theme-guide.ts index 26e945ac..8f85637d 100644 --- a/docs/.vuepress/collections/en/theme-guide.ts +++ b/docs/.vuepress/collections/en/theme-guide.ts @@ -58,6 +58,7 @@ export const themeGuide: ThemeCollectionItem = defineCollection({ 'code-tree', 'field', 'tabs', + 'qrcode', 'timeline', 'demo-wrapper', 'flex', diff --git a/docs/.vuepress/collections/zh/theme-guide.ts b/docs/.vuepress/collections/zh/theme-guide.ts index bcfadf05..d87c7c98 100644 --- a/docs/.vuepress/collections/zh/theme-guide.ts +++ b/docs/.vuepress/collections/zh/theme-guide.ts @@ -58,6 +58,7 @@ export const themeGuide: ThemeCollectionItem = defineCollection({ 'code-tree', 'field', 'tabs', + 'qrcode', 'timeline', 'demo-wrapper', 'flex', diff --git a/docs/.vuepress/theme.ts b/docs/.vuepress/theme.ts index bab440aa..d026ad8f 100644 --- a/docs/.vuepress/theme.ts +++ b/docs/.vuepress/theme.ts @@ -36,6 +36,7 @@ export const theme: Theme = plumeTheme({ imageSize: 'all', mark: 'lazy', pdf: true, + qrcode: true, caniuse: true, acfun: true, bilibili: true, diff --git a/docs/en/guide/markdown/qrcode.md b/docs/en/guide/markdown/qrcode.md new file mode 100644 index 00000000..43673379 --- /dev/null +++ b/docs/en/guide/markdown/qrcode.md @@ -0,0 +1,238 @@ +--- +title: qrcode +icon: uiw:qrcode +createTime: 2025/12/05 16:16:06 +permalink: /en/guide/markdown/qrcode/ +badge: 新 +--- + +@[qrcode](https://github.com/pengzhanbo/vuepress-theme-plume) + +## Overview + +In Markdown, you can embed QR code images generated from text using simple syntax, allowing them to be scanned when needed. + +The text can be: + +- A remotely accessible link address +- A path to a `.md` file within the VuePress site _(both absolute and relative paths are supported)_ +- Any plain text _(avoid overly long text)_ + +## Configuration + +This feature is disabled by default. You need to enable it in the `theme` configuration. + +```ts title=".vuepress/config.ts" +export default defineUserConfig({ + theme: plumeTheme({ + markdown: { + qrcode: true, // [!code ++] + } + }) +}) +``` + +## Syntax + +### Inline Syntax + +Inline syntax is suitable for shorter `text`, such as links. + +```md + +@[qrcode](text) + +@[qrcode card svg title="xxx" align="center"](text) +``` + +### Container Syntax + +Container syntax is suitable for longer `text`, such as paragraphs or multi-line text. + +```md +::: qrcode card svg title="xxx" align="center" +text +::: +``` + +::: warning QR codes generated from overly long text may be truncated and potentially unscannable. +::: + +## Attributes + +:::: field-group +::: field name="card" type="boolean" optional default="false" +Whether to enable the card style. +::: +::: field name="svg" type="boolean" optional default="false" +Whether to render the QR code in SVG format. The default format is PNG. +::: +::: field name="title" type="string" optional +The title of the QR code. +::: +::: field name="align" type="'left' | 'center' | 'right'" optional default="left" +The alignment of the QR code. +::: +::: field name="width" type="number" optional default="300" +The width of the QR code. +::: +:::: + +The following attribute configurations directly affect the final rendering of the QR code. +Usually, the default values are sufficient and do not require configuration. + +:::: field-group +::: field name="light" type="string" optional default="#ffffffff" +The color for the light parts of the QR code, i.e., the background color. +::: +::: field name="dark" type="string" optional default="#000000ff" +The color for the dark parts of the QR code, i.e., the QR code color. +::: +::: field name="margin" type="number" optional default="2" +The margin of the QR code. +::: +::: field name="level" type="'L' | 'M' | 'Q' | 'H'" optional default="M" +**Error Correction Level** + +Error correction allows the QR code to be successfully scanned even if it is dirty or damaged. +Four levels are available depending on the operating environment. + +Higher levels provide better error resistance but reduce the data capacity of the symbol. + +If the QR code symbol is unlikely to be damaged, lower error correction levels like Low or Medium can be safely used. +::: +::: field name="version" type="number" optional +**QR Code Version** + +If not specified, a more suitable value will be automatically calculated. Valid range: `1-40`. +::: +::: field name="scale" type="number" optional default="4" +Scaling factor. A value of 1 means 1 pixel per module (black dot). +::: +::: field name="mask" type="1 | 2 | 3 | 4 | 5 | 6 | 7" optional +The mask pattern used to mask the symbol. + +If not specified, a more suitable value will be automatically calculated. +:::: + +## Examples + +### Accessible Remote Link + +**Input:** + +```md +@[qrcode](https://github.com/pengzhanbo/vuepress-theme-plume) +``` + +**Output:** + +@[qrcode](https://github.com/pengzhanbo/vuepress-theme-plume) + +### Internal Page Path + +**Input:** + +```md +@[qrcode](.) +@[qrcode](./steps.md) +@[qrcode](/guide/markdown/qrcode.md) +``` + +**Output:** + +::: flex +@[qrcode](.) +@[qrcode](./steps.md) +@[qrcode](/guide/markdown/qrcode.md) +::: + +### Arbitrary Text + +**Input:** + +```md +@[qrcode](vuepress-theme-plume is an open-source VuePress theme) +``` + +**Output:** + +@[qrcode](vuepress-theme-plume is an open-source VuePress theme) + +**Input:** + +```md +::: qrcode title="The Road Not Taken · by Robert Frost" +Two roads diverged in a yellow wood, +And sorry I could not travel both +And be one traveler, long I stood +And looked down one as far as I could +To where it bent in the undergrowth; + +Then took the other, as just as fair, +And having perhaps the better claim, +Because it was grassy and wanted wear; +Though as for that the passing there +Had worn them really about the same, + +And both that morning equally lay +In leaves no step had trodden black. +Oh, I kept the first for another day! +Yet knowing how way leads on to way, +I doubted if I should ever come back. + +I shall be telling this with a sigh +Somewhere ages and ages hence: +Two roads diverged in a wood, and I— +I took the one less traveled by, +And that has made all the difference. +::: +``` + +**Output:** + +::: qrcode title="The Road Not Taken · by Robert Frost" +Two roads diverged in a yellow wood, +And sorry I could not travel both +And be one traveler, long I stood +And looked down one as far as I could +To where it bent in the undergrowth; + +Then took the other, as just as fair, +And having perhaps the better claim, +Because it was grassy and wanted wear; +Though as for that the passing there +Had worn them really about the same, + +And both that morning equally lay +In leaves no step had trodden black. +Oh, I kept the first for another day! +Yet knowing how way leads on to way, +I doubted if I should ever come back. + +I shall be telling this with a sigh +Somewhere ages and ages hence: +Two roads diverged in a wood, and I— +I took the one less traveled by, +And that has made all the difference. +::: + +### QR Code Card with Information + +**Input:** + +```md +@[qrcode card title="vuepress-theme-plume"](https://github.com/pengzhanbo/vuepress-theme-plume) +``` + +Equivalent to: + +```md +::: qrcode card title="vuepress-theme-plume" +https://github.com/pengzhanbo/vuepress-theme-plume +::: +``` + +**Output:** + +@[qrcode card title="vuepress-theme-plume"](https://github.com/pengzhanbo/vuepress-theme-plume) diff --git a/docs/guide/markdown/qrcode.md b/docs/guide/markdown/qrcode.md new file mode 100644 index 00000000..bfdcf3e2 --- /dev/null +++ b/docs/guide/markdown/qrcode.md @@ -0,0 +1,204 @@ +--- +title: 二维码 +icon: uiw:qrcode +createTime: 2025/12/05 10:37:43 +permalink: /guide/markdown/qrcode/ +badge: 新 +--- + +@[qrcode](https://github.com/pengzhanbo/vuepress-theme-plume) + +## 概述 + +在 Markdown 中,通过简单的语法,可以在文档中插入由文本转换成的二维码图片,以便在需要时进行扫描。 + +文本可以是: + +- 远程可访问的链接地址 +- vuepress 站点内的 `.md` 文件路径 _(绝对路径 或相对路径 均支持)_ +- 任意普通文本 (避免过长的文本) + +## 配置 + +该功能默认不启用,你需要在 `theme` 配置中启用。 + +```ts title=".vuepress/config.ts" +export default defineUserConfig({ + theme: plumeTheme({ + markdown: { + qrcode: true, // [!code ++] + } + }) +}) +``` + +## 语法 + +### 单行语法 + +单行语法适用于 `text` 文本较短时,比如 链接 等。 + +```md + +@[qrcode](text) + +@[qrcode card svg title="xxx" align="center"](text) +``` + +### 容器语法 + +容器语法适用于 `text` 文本较长时,比如 段落,多行文本 等。 + +```md +::: qrcode card svg title="xxx" align="center" +text +::: +``` + +::: warning 过长的文本生成的二维码可能会被截断,且可能无法扫描 +::: + +## 属性 + +:::: field-group +::: field name="card" type="boolean" optional default="false" +是否启用卡片样式。 +::: +::: field name="svg" type="boolean" optional default="false" +是否将二维码渲染为 SVG 格式。默认渲染为 PNG 格式。 +::: +::: field name="title" type="string" optional +二维码标题。 +::: +::: field name="align" type="'left' | 'center' | 'right'" optional default="left" +二维码对齐方式。 +::: +::: field name="width" type="number" optional default="300" +二维码宽度。 +::: +:::: + +以下属性配置将直接影响二维码的最终渲染效果,通常使用默认值即可,无需配置。 + +:::: field-group +::: field name="light" type="string" optional default="#ffffffff" +二维码亮色部分颜色,即背景色 +::: +::: field name="dark" type="string" optional default="#000000ff" +二维码暗色部分颜色,即二维码颜色 +::: +::: field name="margin" type="number" optional default="2" +二维码边距 +::: +::: field name="level" type="'L' | 'M' | 'Q' | 'H'" optional default="M" +**纠错等级** + +纠错能力使得即使二维码符号被污染或损坏,也能成功扫描。根据操作环境,有四个级别可供选择。 + +更高级别提供更好的抗错能力,但会降低符号的容量。 + +如果二维码符号可能被损坏的几率较低,则可以安全使用低纠错级别,如低或中。 +::: +::: field name="version" type="number" optional +**二维码版本** + +若未指定,将自动计算更合适的值。取值范围 `1-40`。 +::: +::: field name="scale" type="number" optional default="4" +缩放因子。值为 1 表示每个模块(黑点)对应 1 像素。 +::: +::: field name="mask" type="1 | 2 | 3 | 4 | 5 | 6 | 7" optional +用于遮蔽符号的掩码模式。 + +若未指定,将自动计算更合适的值。 +:::: + +## 示例 + +### 可访问的远程链接 + +**输入:** + +```md +@[qrcode](https://github.com/pengzhanbo/vuepress-theme-plume) +``` + +**输出:** + +@[qrcode](https://github.com/pengzhanbo/vuepress-theme-plume) + +### 站内的页面路径 + +**输入:** + +```md +@[qrcode](.) +@[qrcode](./steps.md) +@[qrcode](/guide/markdown/qrcode.md) +``` + +**输出:** + +::: flex +@[qrcode](.) +@[qrcode](./steps.md) +@[qrcode](/guide/markdown/qrcode.md) +::: + +### 任意文本 + +**输入:** + +```md +@[qrcode](vuepress-theme-plume 是一款开源的 VuePress 主题) +``` + +**输出:** + +@[qrcode](vuepress-theme-plume 是一款开源的 VuePress 主题) + +**输入:** + +```md +::: qrcode title="宣州谢朓楼饯别校书叔云 <唐·李白>" +弃我去者,昨日之日不可留。 +乱我心者,今日之日多烦忧。 +长风万里送秋雁,对此可以酣高楼。 +蓬莱文章建安骨,中间小谢又清发。 +俱怀逸兴壮思飞,欲上青天览明月。 +抽刀断水水更流,举杯消愁愁更愁。 +人生在世不称意,明朝散发弄扁舟。 +::: +``` + +**输出:** + +::: qrcode title="宣州谢朓楼饯别校书叔云 <唐·李白>" +弃我去者,昨日之日不可留。 +乱我心者,今日之日多烦忧。 +长风万里送秋雁,对此可以酣高楼。 +蓬莱文章建安骨,中间小谢又清发。 +俱怀逸兴壮思飞,欲上青天览明月。 +抽刀断水水更流,举杯消愁愁更愁。 +人生在世不称意,明朝散发弄扁舟。 +::: + +### 带信息的二维码卡片 + +**输入:** + +```md +@[qrcode card title="vuepress-theme-plume"](https://github.com/pengzhanbo/vuepress-theme-plume) +``` + +等同于: + +```md +::: qrcode card title="vuepress-theme-plume" +https://github.com/pengzhanbo/vuepress-theme-plume +::: +``` + +**输出:** + +@[qrcode card title="vuepress-theme-plume"](https://github.com/pengzhanbo/vuepress-theme-plume) diff --git a/package.json b/package.json index 9e86ce69..bb8442e5 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "@types/minimist": "catalog:dev", "@types/node": "catalog:dev", "@types/picomatch": "catalog:dev", + "@types/qrcode": "catalog:dev", "@types/stylus": "catalog:dev", "@types/three": "catalog:dev", "@types/webpack-env": "catalog:dev", diff --git a/plugins/plugin-md-power/__test__/__snapshots__/qrcodePlugin.spec.ts.snap b/plugins/plugin-md-power/__test__/__snapshots__/qrcodePlugin.spec.ts.snap new file mode 100644 index 00000000..98835b38 --- /dev/null +++ b/plugins/plugin-md-power/__test__/__snapshots__/qrcodePlugin.spec.ts.snap @@ -0,0 +1,13 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`qrcodePlugin > should not work with container syntax 1`] = `""`; + +exports[`qrcodePlugin > should not work with container syntax 2`] = `""`; + +exports[`qrcodePlugin > should not work with container syntax 3`] = `""`; + +exports[`qrcodePlugin > should work with embed syntax 1`] = `""`; + +exports[`qrcodePlugin > should work with embed syntax 2`] = `""`; + +exports[`qrcodePlugin > should work with embed syntax 3`] = `""`; diff --git a/plugins/plugin-md-power/__test__/qrcodePlugin.spec.ts b/plugins/plugin-md-power/__test__/qrcodePlugin.spec.ts new file mode 100644 index 00000000..dd57781c --- /dev/null +++ b/plugins/plugin-md-power/__test__/qrcodePlugin.spec.ts @@ -0,0 +1,27 @@ +import MarkdownIt from 'markdown-it' +import { describe, expect, it } from 'vitest' +import { qrcodePlugin } from '../src/node/embed/qrcode.js' + +describe('qrcodePlugin', () => { + it('should work with embed syntax', () => { + const md = MarkdownIt().use((md) => { + md.block.ruler.before('code', 'import_code', () => false) + md.renderer.rules.import_code = () => '' + }).use(qrcodePlugin) + + expect(md.render('@[qrcode](text)')).toMatchSnapshot() + expect(md.render('@[qrcode svg card](text)')).toMatchSnapshot() + expect(md.render('@[qrcode title="title"](text)')).toMatchSnapshot() + }) + + it('should not work with container syntax', () => { + const md = MarkdownIt().use((md) => { + md.block.ruler.before('code', 'import_code', () => false) + md.renderer.rules.import_code = () => '' + }).use(qrcodePlugin) + + expect(md.render(':::qrcode\ntext\n:::')).toMatchSnapshot() + expect(md.render(':::qrcode svg card\ntext\n:::')).toMatchSnapshot() + expect(md.render(':::qrcode title="title"\ntext\n:::')).toMatchSnapshot() + }) +}) diff --git a/plugins/plugin-md-power/package.json b/plugins/plugin-md-power/package.json index db35f12d..714d52f2 100644 --- a/plugins/plugin-md-power/package.json +++ b/plugins/plugin-md-power/package.json @@ -103,6 +103,7 @@ "markdown-it-cjk-friendly": "catalog:prod", "markdown-it-container": "catalog:prod", "nanoid": "catalog:prod", + "qrcode": "catalog:prod", "shiki": "catalog:prod", "tm-grammars": "catalog:prod", "tm-themes": "catalog:prod", diff --git a/plugins/plugin-md-power/src/client/components/VPQRCode.vue b/plugins/plugin-md-power/src/client/components/VPQRCode.vue new file mode 100644 index 00000000..e3ddc0b8 --- /dev/null +++ b/plugins/plugin-md-power/src/client/components/VPQRCode.vue @@ -0,0 +1,228 @@ + + + + + diff --git a/plugins/plugin-md-power/src/node/embed/index.ts b/plugins/plugin-md-power/src/node/embed/index.ts index 15751336..f7ab6446 100644 --- a/plugins/plugin-md-power/src/node/embed/index.ts +++ b/plugins/plugin-md-power/src/node/embed/index.ts @@ -7,6 +7,7 @@ import { codeSandboxPlugin } from './code/codeSandbox.js' import { jsfiddlePlugin } from './code/jsfiddle.js' import { replitPlugin } from './code/replit.js' import { pdfPlugin } from './pdf.js' +import { qrcodePlugin } from './qrcode.js' import { acfunPlugin } from './video/acfun.js' import { artPlayerPlugin } from './video/artPlayer.js' import { bilibiliPlugin } from './video/bilibili.js' @@ -70,4 +71,9 @@ export function embedSyntaxPlugin(md: Markdown, options: MarkdownPowerPluginOpti // @[jsfiddle](user/id) md.use(jsfiddlePlugin) } + + if (options.qrcode) { + // @[qrcode svg card title="xxx"](text) + md.use(qrcodePlugin) + } } diff --git a/plugins/plugin-md-power/src/node/embed/qrcode.ts b/plugins/plugin-md-power/src/node/embed/qrcode.ts new file mode 100644 index 00000000..5f628cd8 --- /dev/null +++ b/plugins/plugin-md-power/src/node/embed/qrcode.ts @@ -0,0 +1,37 @@ +/** + * @[qrcode svg card title="xxx"](text) + */ +import type { PluginWithOptions } from 'markdown-it' +import type { QRCodeMeta, QRCodeProps } from '../../shared/index.js' +import { omit } from '@pengzhanbo/utils' +import { createContainerSyntaxPlugin } from '../container/createContainer.js' +import { resolveAttrs } from '../utils/resolveAttrs.js' +import { stringifyAttrs } from '../utils/stringifyAttrs.js' +import { createEmbedRuleBlock } from './createEmbedRuleBlock.js' + +export const qrcodePlugin: PluginWithOptions = (md) => { + createEmbedRuleBlock(md, { + type: 'qrcode', + syntaxPattern: /^@\[qrcode([^\]]*)\]\(([^)]*)\)/, + meta([, info, text]) { + const { attrs } = resolveAttrs(info) + const { card, ...rest } = omit(attrs, ['text']) + + return { + text, + ...rest, + mode: rest.mode || (card ? 'card' : 'img'), + } + }, + content(meta) { + return `` + }, + }) + + createContainerSyntaxPlugin(md, 'qrcode', (tokens, index) => { + const { content, meta } = tokens[index] + const { card, ...rest } = omit(meta, ['text']) + const props: QRCodeProps = { text: content?.trim(), ...rest, mode: rest.mode || (card ? 'card' : 'img') } + return `` + }) +} diff --git a/plugins/plugin-md-power/src/node/plugin.ts b/plugins/plugin-md-power/src/node/plugin.ts index 90ab7d14..ef871cc7 100644 --- a/plugins/plugin-md-power/src/node/plugin.ts +++ b/plugins/plugin-md-power/src/node/plugin.ts @@ -42,6 +42,9 @@ export function markdownPowerPlugin( ['artplayer', 'dashjs', 'hls.js', 'mpegts.js/dist/mpegts.js'], ) } + if (options.qrcode) { + addViteOptimizeDepsInclude(bundlerOptions, app, ['qrcode']) + } }, extendsMarkdown: async (md, app) => { diff --git a/plugins/plugin-md-power/src/node/prepareConfigFile.ts b/plugins/plugin-md-power/src/node/prepareConfigFile.ts index 6de37100..2da8c52a 100644 --- a/plugins/plugin-md-power/src/node/prepareConfigFile.ts +++ b/plugins/plugin-md-power/src/node/prepareConfigFile.ts @@ -131,15 +131,19 @@ export async function prepareConfigFile(app: App, options: MarkdownPowerPluginOp enhances.add(`app.component('VPTable', VPTable)`) } + if (options.qrcode) { + imports.add(`import VPQRCode from '${CLIENT_FOLDER}components/VPQRCode.vue'`) + enhances.add(`app.component('VPQRCode', VPQRCode)`) + } + const setupIcon = prepareIcon(imports, options.icon) const setupStmts: string[] = [] const iconSetup = setupIcon.trim() if (iconSetup) setupStmts.push(iconSetup) - const markMode = options.mark === 'lazy' ? 'lazy' : 'eager' imports.add(`import { setupMarkHighlight } from '${CLIENT_FOLDER}composables/mark.js'`) - setupStmts.push(`setupMarkHighlight(${JSON.stringify(markMode)})`) + setupStmts.push(`setupMarkHighlight(${JSON.stringify(options.mark === 'lazy' ? 'lazy' : 'eager')})`) const setupContent = setupStmts.length ? ` ${setupStmts.join('\n ')}\n` diff --git a/plugins/plugin-md-power/src/shared/index.ts b/plugins/plugin-md-power/src/shared/index.ts index 225c6017..e059c241 100644 --- a/plugins/plugin-md-power/src/shared/index.ts +++ b/plugins/plugin-md-power/src/shared/index.ts @@ -10,6 +10,7 @@ export * from './npmTo.js' export * from './pdf.js' export * from './plot.js' export * from './plugin.js' +export * from './qrcode.js' export * from './repl.js' export * from './replit.js' export * from './size.js' diff --git a/plugins/plugin-md-power/src/shared/plugin.ts b/plugins/plugin-md-power/src/shared/plugin.ts index 84a72a19..62416009 100644 --- a/plugins/plugin-md-power/src/shared/plugin.ts +++ b/plugins/plugin-md-power/src/shared/plugin.ts @@ -256,6 +256,13 @@ export interface MarkdownPowerPluginOptions { */ table?: boolean | TableContainerOptions + /** + * 是否启用 二维码 嵌入语法 + * + * @default false + */ + qrcode?: boolean + // enhance /** * 是否启用 自动填充 图片宽高属性 diff --git a/plugins/plugin-md-power/src/shared/qrcode.ts b/plugins/plugin-md-power/src/shared/qrcode.ts new file mode 100644 index 00000000..5af63ff1 --- /dev/null +++ b/plugins/plugin-md-power/src/shared/qrcode.ts @@ -0,0 +1,89 @@ +export interface QRCodeMeta extends QRCodeProps { + /** + * mode: 'card' 的别名 + */ + card?: boolean +} + +export interface QRCodeProps { + /** + * 二维码标题 + * 作为 HTML 标签的 `title` 属性、`alt` 属性 + */ + title?: string + + /** + * 二维码内容 + */ + text?: string + /** + * 二维码宽度 + */ + width?: number | string + + /** + * 显示模式 + * - img: 以图片的形式显示二维码 + * - card: 以卡片的形式显示,卡片以左右布局,左侧二维码,右侧 标题 + 内容 + * @default 'img' + */ + mode?: 'img' | 'card' + + /** + * 在 card 模式下是否翻转布局 + */ + reverse?: boolean + + /** + * 二维码的对齐方式 + * @default 'left' + */ + align?: 'left' | 'center' | 'right' + + /** + * 是否渲染为 SVG 格式的二维码 + * 默认输出为 PNG 格式的 dataURL + * @default false + */ + svg?: boolean + /** + * 纠错等级。 + * 可能的取值为低、中、四分位、高,分别对应 L、M、Q、H。 + * @default 'M' + */ + level?: 'L' | 'M' | 'Q' | 'H' | 'l' | 'm' | 'q' | 'h' + /** + * 二维码版本。若未指定,将自动计算更合适的值。 + * 取值范围 1-40 + */ + version?: number + /** + * 用于遮蔽符号的掩码模式。 + * 可能的取值为0、1、2、3、4、5、6、7。 + * 若未指定,系统将自动计算更合适的值。 + */ + mask?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 + /** + * 定义静区应有多宽。 + * @default 4 + */ + margin?: number + /** + * 缩放因子。值为1表示每个模块(黑点)对应1像素。 + */ + scale?: number + + /** + * 暗色模块的颜色。值必须为十六进制格式(RGBA)。 + * 注意:暗色应始终比浅色模块的颜色更深。 + * @default '#000000ff' + */ + light?: string + + /** + * 亮色模块的颜色。值必须为十六进制格式(RGBA)。 + * 注意:亮色应始终比暗色模块的颜色更浅。 + * @default '#ffffffff' + */ + dark?: string +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c3f86645..d63c4dd3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -45,6 +45,9 @@ catalogs: '@types/picomatch': specifier: ^4.0.2 version: 4.0.2 + '@types/qrcode': + specifier: ^1.5.6 + version: 1.5.6 '@types/stylus': specifier: ^0.48.43 version: 0.48.43 @@ -293,6 +296,9 @@ catalogs: picomatch: specifier: ^4.0.3 version: 4.0.3 + qrcode: + specifier: ^1.5.4 + version: 1.5.4 shiki: specifier: ^3.17.0 version: 3.17.0 @@ -423,6 +429,9 @@ importers: '@types/picomatch': specifier: catalog:dev version: 4.0.2 + '@types/qrcode': + specifier: catalog:dev + version: 1.5.6 '@types/stylus': specifier: catalog:dev version: 0.48.43 @@ -703,6 +712,9 @@ importers: pyodide: specifier: catalog:peer version: 0.29.0 + qrcode: + specifier: catalog:prod + version: 1.5.4 sass: specifier: catalog:peer version: 1.94.2 @@ -754,7 +766,7 @@ importers: version: 14.1.0(vue@3.5.25(typescript@5.9.3)) '@vueuse/integrations': specifier: catalog:prod - version: 14.1.0(axios@1.13.2)(change-case@5.4.4)(focus-trap@7.6.6)(vue@3.5.25(typescript@5.9.3)) + version: 14.1.0(axios@1.13.2)(change-case@5.4.4)(focus-trap@7.6.6)(qrcode@1.5.4)(vue@3.5.25(typescript@5.9.3)) chokidar: specifier: ^5.0.0 version: 5.0.0 @@ -2630,6 +2642,9 @@ packages: '@types/picomatch@4.0.2': resolution: {integrity: sha512-qHHxQ+P9PysNEGbALT8f8YOSHW0KJu6l2xU8DYY0fu/EmGxXdVnuTLvFUvBgPJMSqXq29SYHveejeAha+4AYgA==} + '@types/qrcode@1.5.6': + resolution: {integrity: sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==} + '@types/qs@6.14.0': resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==} @@ -3394,6 +3409,10 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} + camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + caniuse-lite@1.0.30001733: resolution: {integrity: sha512-e4QKw/O2Kavj2VQTKZWrwzkt3IxOmIlU6ajRb6LP64LHpBo1J67k2Hi4Vu/TgJWsNtynurfS0uK3MaUTCPfu5Q==} @@ -3504,6 +3523,9 @@ packages: resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} engines: {node: '>= 10'} + cliui@6.0.0: + resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} + cliui@8.0.1: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} @@ -3965,6 +3987,10 @@ packages: supports-color: optional: true + decamelize@1.2.0: + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} + engines: {node: '>=0.10.0'} + decode-named-character-reference@1.2.0: resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==} @@ -4034,6 +4060,9 @@ packages: resolution: {integrity: sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==} engines: {node: '>=0.3.1'} + dijkstrajs@1.0.3: + resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==} + dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -4526,6 +4555,10 @@ packages: resolution: {integrity: sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==} engines: {node: '>=18'} + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} @@ -5416,6 +5449,10 @@ packages: localforage@1.10.0: resolution: {integrity: sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==} + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -5982,6 +6019,10 @@ packages: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} engines: {node: '>= 0.4'} + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} @@ -5990,6 +6031,10 @@ packages: resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + p-locate@5.0.0: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} @@ -6006,6 +6051,10 @@ packages: resolution: {integrity: sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==} engines: {node: '>=18'} + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} @@ -6134,6 +6183,10 @@ packages: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} + pngjs@5.0.0: + resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==} + engines: {node: '>=10.13.0'} + pnpm-workspace-yaml@1.3.0: resolution: {integrity: sha512-Krb5q8Totd5mVuLx7we+EFHq/AfxA75nbfTm25Q1pIf606+RlaKUG+PXH8SDihfe5b5k4H09gE+sL47L1t5lbw==} @@ -6251,6 +6304,11 @@ packages: resolution: {integrity: sha512-7gJ6mxcQb9vUBOtbKm5mDevbe2uRcOEVp1g4gb/Q+oLntB3HY8eBhOYRxFI2mlDFlY1e4DOSCptzxarXRvzxCA==} engines: {node: '>=20'} + qrcode@1.5.4: + resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==} + engines: {node: '>=10.13.0'} + hasBin: true + qs@6.14.0: resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} engines: {node: '>=0.6'} @@ -6346,6 +6404,9 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} + require-main-filename@2.0.0: + resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} + requires-port@1.0.0: resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} @@ -6619,6 +6680,9 @@ packages: engines: {node: '>=10'} hasBin: true + set-blocking@2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -7469,6 +7533,9 @@ packages: resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} engines: {node: '>= 0.4'} + which-module@2.0.1: + resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} + which-typed-array@1.1.19: resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} engines: {node: '>= 0.4'} @@ -7497,6 +7564,10 @@ packages: wordwrap@1.0.0: resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -7532,6 +7603,9 @@ packages: resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} engines: {node: '>=12'} + y18n@4.0.3: + resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} + y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} @@ -7548,10 +7622,18 @@ packages: engines: {node: '>= 14.6'} hasBin: true + yargs-parser@18.1.3: + resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} + engines: {node: '>=6'} + yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} + yargs@15.4.1: + resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} + engines: {node: '>=8'} + yargs@17.7.2: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} @@ -9233,7 +9315,7 @@ snapshots: '@types/jsonfile@6.1.4': dependencies: - '@types/node': 24.10.0 + '@types/node': 24.10.1 '@types/less@3.0.8': {} @@ -9272,6 +9354,10 @@ snapshots: '@types/picomatch@4.0.2': {} + '@types/qrcode@1.5.6': + dependencies: + '@types/node': 24.10.1 + '@types/qs@6.14.0': {} '@types/range-parser@1.2.7': {} @@ -9976,7 +10062,7 @@ snapshots: '@vueuse/shared': 14.1.0(vue@3.5.25(typescript@5.9.3)) vue: 3.5.25(typescript@5.9.3) - '@vueuse/integrations@14.1.0(axios@1.13.2)(change-case@5.4.4)(focus-trap@7.6.6)(vue@3.5.25(typescript@5.9.3))': + '@vueuse/integrations@14.1.0(axios@1.13.2)(change-case@5.4.4)(focus-trap@7.6.6)(qrcode@1.5.4)(vue@3.5.25(typescript@5.9.3))': dependencies: '@vueuse/core': 14.1.0(vue@3.5.25(typescript@5.9.3)) '@vueuse/shared': 14.1.0(vue@3.5.25(typescript@5.9.3)) @@ -9985,6 +10071,7 @@ snapshots: axios: 1.13.2 change-case: 5.4.4 focus-trap: 7.6.6 + qrcode: 1.5.4 '@vueuse/metadata@14.1.0': {} @@ -10323,6 +10410,8 @@ snapshots: callsites@3.1.0: {} + camelcase@5.3.1: {} + caniuse-lite@1.0.30001733: {} caniuse-lite@1.0.30001752: {} @@ -10447,6 +10536,12 @@ snapshots: cli-width@3.0.0: {} + cliui@6.0.0: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + cliui@8.0.1: dependencies: string-width: 4.2.3 @@ -10962,6 +11057,8 @@ snapshots: dependencies: ms: 2.1.3 + decamelize@1.2.0: {} + decode-named-character-reference@1.2.0: dependencies: character-entities: 2.0.2 @@ -11017,6 +11114,8 @@ snapshots: diff@8.0.2: {} + dijkstrajs@1.0.3: {} + dir-glob@3.0.1: dependencies: path-type: 4.0.0 @@ -11649,6 +11748,11 @@ snapshots: find-up-simple@1.0.1: {} + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + find-up@5.0.0: dependencies: locate-path: 6.0.0 @@ -12621,6 +12725,10 @@ snapshots: dependencies: lie: 3.1.1 + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + locate-path@6.0.0: dependencies: p-locate: 5.0.0 @@ -13411,6 +13519,10 @@ snapshots: object-keys: 1.1.1 safe-push-apply: 1.0.0 + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 @@ -13419,6 +13531,10 @@ snapshots: dependencies: yocto-queue: 1.2.1 + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + p-locate@5.0.0: dependencies: p-limit: 3.1.0 @@ -13431,6 +13547,8 @@ snapshots: p-map@7.0.4: {} + p-try@2.2.0: {} + package-json-from-dist@1.0.1: {} package-manager-detector@1.5.0: {} @@ -13538,6 +13656,8 @@ snapshots: pluralize@8.0.0: {} + pngjs@5.0.0: {} + pnpm-workspace-yaml@1.3.0: dependencies: yaml: 2.8.1 @@ -13637,6 +13757,12 @@ snapshots: dependencies: hookified: 1.13.0 + qrcode@1.5.4: + dependencies: + dijkstrajs: 1.0.3 + pngjs: 5.0.0 + yargs: 15.4.1 + qs@6.14.0: dependencies: side-channel: 1.1.0 @@ -13768,6 +13894,8 @@ snapshots: require-from-string@2.0.2: {} + require-main-filename@2.0.0: {} + requires-port@1.0.0: {} reserved-identifiers@1.2.0: {} @@ -14052,6 +14180,8 @@ snapshots: semver@7.7.3: {} + set-blocking@2.0.0: {} + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -14982,6 +15112,8 @@ snapshots: is-weakmap: 2.0.2 is-weakset: 2.0.4 + which-module@2.0.1: {} + which-typed-array@1.1.19: dependencies: available-typed-arrays: 1.0.7 @@ -15011,6 +15143,12 @@ snapshots: wordwrap@1.0.0: {} + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 @@ -15040,6 +15178,8 @@ snapshots: xml-name-validator@4.0.0: {} + y18n@4.0.3: {} + y18n@5.0.8: {} yallist@3.1.1: {} @@ -15051,8 +15191,27 @@ snapshots: yaml@2.8.1: {} + yargs-parser@18.1.3: + dependencies: + camelcase: 5.3.1 + decamelize: 1.2.0 + yargs-parser@21.1.1: {} + yargs@15.4.1: + dependencies: + cliui: 6.0.0 + decamelize: 1.2.0 + find-up: 4.1.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + require-main-filename: 2.0.0 + set-blocking: 2.0.0 + string-width: 4.2.3 + which-module: 2.0.1 + y18n: 4.0.3 + yargs-parser: 18.1.3 + yargs@17.7.2: dependencies: cliui: 8.0.1 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 983437c0..bdece684 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -30,6 +30,7 @@ catalogs: '@types/minimist': ^1.2.5 '@types/node': ^24.10.1 '@types/picomatch': ^4.0.2 + '@types/qrcode': ^1.5.6 '@types/stylus': ^0.48.43 '@types/three': ^0.181.0 '@types/webpack-env': ^1.18.8 @@ -118,6 +119,7 @@ catalogs: package-manager-detector: ^1.6.0 picocolors: ^1.1.1 picomatch: ^4.0.3 + qrcode: ^1.5.4 shiki: ^3.17.0 sort-package-json: ^3.5.0 tm-grammars: ^1.26.0 diff --git a/theme/src/node/detector/fields.ts b/theme/src/node/detector/fields.ts index 799cb351..feb94b2e 100644 --- a/theme/src/node/detector/fields.ts +++ b/theme/src/node/detector/fields.ts @@ -64,6 +64,7 @@ export const MARKDOWN_POWER_FIELDS: (keyof MarkdownPowerPluginOptions)[] = [ 'collapse', 'chat', 'youtube', + 'qrcode', ] export const MARKDOWN_SUPPORT_FIELDS: (keyof MarkdownOptions)[] = [