feat(plugin-md-power): add qrcode syntax plugin for markdown (#777)

* feat(plugin-md-power): add  qrcode syntax plugin for markdown

* chore: tweak
This commit is contained in:
pengzhanbo 2025-12-05 17:18:13 +08:00 committed by GitHub
parent 07710247bb
commit 157281aec8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 1030 additions and 5 deletions

View File

@ -76,6 +76,7 @@
"nprogress", "nprogress",
"pnpm", "pnpm",
"portfinder", "portfinder",
"qrcode",
"shiki", "shiki",
"shikiji", "shikiji",
"shikijs", "shikijs",

View File

@ -58,6 +58,7 @@ export const themeGuide: ThemeCollectionItem = defineCollection({
'code-tree', 'code-tree',
'field', 'field',
'tabs', 'tabs',
'qrcode',
'timeline', 'timeline',
'demo-wrapper', 'demo-wrapper',
'flex', 'flex',

View File

@ -58,6 +58,7 @@ export const themeGuide: ThemeCollectionItem = defineCollection({
'code-tree', 'code-tree',
'field', 'field',
'tabs', 'tabs',
'qrcode',
'timeline', 'timeline',
'demo-wrapper', 'demo-wrapper',
'flex', 'flex',

View File

@ -36,6 +36,7 @@ export const theme: Theme = plumeTheme({
imageSize: 'all', imageSize: 'all',
mark: 'lazy', mark: 'lazy',
pdf: true, pdf: true,
qrcode: true,
caniuse: true, caniuse: true,
acfun: true, acfun: true,
bilibili: true, bilibili: true,

View File

@ -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
<!-- Basic Syntax -->
@[qrcode](text)
<!-- With Attributes -->
@[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](.) <!-- `.` represents the current page address -->
@[qrcode](./steps.md) <!-- Relative path -->
@[qrcode](/guide/markdown/qrcode.md) <!-- Absolute path -->
```
**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)

View File

@ -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)

View File

@ -50,6 +50,7 @@
"@types/minimist": "catalog:dev", "@types/minimist": "catalog:dev",
"@types/node": "catalog:dev", "@types/node": "catalog:dev",
"@types/picomatch": "catalog:dev", "@types/picomatch": "catalog:dev",
"@types/qrcode": "catalog:dev",
"@types/stylus": "catalog:dev", "@types/stylus": "catalog:dev",
"@types/three": "catalog:dev", "@types/three": "catalog:dev",
"@types/webpack-env": "catalog:dev", "@types/webpack-env": "catalog:dev",

View File

@ -0,0 +1,13 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`qrcodePlugin > should not work with container syntax 1`] = `"<VPQRCode text="text" mode="img" />"`;
exports[`qrcodePlugin > should not work with container syntax 2`] = `"<VPQRCode text="text" svg mode="card" />"`;
exports[`qrcodePlugin > should not work with container syntax 3`] = `"<VPQRCode text="text" title="title" mode="img" />"`;
exports[`qrcodePlugin > should work with embed syntax 1`] = `"<VPQRCode text="text" mode="img" />"`;
exports[`qrcodePlugin > should work with embed syntax 2`] = `"<VPQRCode text="text" svg mode="card" />"`;
exports[`qrcodePlugin > should work with embed syntax 3`] = `"<VPQRCode text="text" title="title" mode="img" />"`;

View File

@ -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()
})
})

View File

@ -103,6 +103,7 @@
"markdown-it-cjk-friendly": "catalog:prod", "markdown-it-cjk-friendly": "catalog:prod",
"markdown-it-container": "catalog:prod", "markdown-it-container": "catalog:prod",
"nanoid": "catalog:prod", "nanoid": "catalog:prod",
"qrcode": "catalog:prod",
"shiki": "catalog:prod", "shiki": "catalog:prod",
"tm-grammars": "catalog:prod", "tm-grammars": "catalog:prod",
"tm-themes": "catalog:prod", "tm-themes": "catalog:prod",

View File

@ -0,0 +1,228 @@
<script setup lang="ts">
import type { QRCodeToDataURLOptions, QRCodeToStringOptions } from 'qrcode'
import type { QRCodeProps } from '../../shared/index.js'
import { isLinkWithProtocol } from '@vuepress/helper/client'
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import { resolveRoute, usePage, withBase } from 'vuepress/client'
const { title, text, mode, align = 'left', reverse = false, svg = false, width, level, version, mask, margin = 2, scale = 4, light, dark } = defineProps<QRCodeProps>()
const page = usePage()
let qr: typeof import('qrcode') | null = null
const qrcode = ref('')
const parsedText = ref('')
const isLink = ref(false)
const styles = computed(() => {
const size = typeof width === 'number' ? width : width ? Number.parseInt(width) : undefined
return size ? { '--vp-qrcode-size': `${size}px` } : undefined
})
function parseText(): string | void {
isLink.value = false
if (!text || __VUEPRESS_SSR__)
return ''
if (text === '.') {
isLink.value = true
return location.href.split(/[?#]/)[0]
}
if (isLinkWithProtocol(text)) {
isLink.value = true
return text.startsWith('//') ? `${location.protocol}${text}` : text
}
if (text.startsWith('/') || text.startsWith('./')) {
const [routePath, ...rest] = text.split(/([?#])/)
const currentPath = page.value.filePathRelative ? `/${page.value.filePathRelative}` : undefined
const { notFound, path } = resolveRoute(routePath, currentPath)
if (notFound) {
return text
}
isLink.value = true
return new URL(`${withBase(path)}${rest.join('')}`, location.href).toString()
}
return text
}
onMounted(async () => {
const callback = (_: any, url: string) => qrcode.value = url
watch(
() => [text, svg, level, version, mask, margin, scale, light, dark],
async () => {
const text = parseText()
parsedText.value = text || ''
if (!text) {
qrcode.value = ''
return
}
qr ??= (await import('qrcode' /* webpackChunkName: "qrcode" */)).default
const opts: QRCodeToDataURLOptions & QRCodeToStringOptions = {
version,
maskPattern: mask,
errorCorrectionLevel: (level ? level.toUpperCase() : 'M') as any,
width: 300 * Math.round(window.devicePixelRatio || 1),
margin,
scale,
color: { dark, light },
}
if (svg)
qr.toString(text, { type: 'svg', ...opts }, callback)
else
qr.toDataURL(text, { type: 'image/png', ...opts }, callback)
},
{ immediate: true },
)
})
onUnmounted(() => {
qr = null
})
</script>
<template>
<div v-if="qrcode" class="vp-qrcode" :class="{ card: mode === 'card', reverse, [align]: true }">
<div class="qrcode-content">
<div v-if="svg" class="qrcode-svg" :style="styles" :title="parsedText" v-html="qrcode" />
<img v-else class="qrcode-img" :src="qrcode" :alt="parsedText" :title="parsedText" :style="styles">
<div v-if="title && mode !== 'card'" class="qrcode-label">
{{ title }}
</div>
</div>
<div v-if="mode === 'card'" class="qrcode-info">
<p v-if="title" class="qrcode-title">
{{ title }}
</p>
<p v-if="parsedText">
<a v-if="isLink" :href="parsedText" rel="noopener noreferrer" target="_blank">
{{ parsedText }}
</a>
<span v-else v-html="parsedText.replaceAll('\n', '<br>')" />
</p>
</div>
</div>
</template>
<style scoped>
.vp-qrcode {
--vp-qrcode-size: 128px;
margin: 16px 0;
overflow: hidden;
}
@media (min-width: 768px) {
.vp-qrcode {
--vp-qrcode-size: 150px;
}
}
.vp-qrcode:not(.card).center {
display: flex;
align-items: center;
justify-content: center;
}
.vp-qrcode:not(.card).right {
display: flex;
align-items: center;
justify-content: flex-end;
}
.vp-qrcode .qrcode-content {
display: flex;
flex-direction: column;
gap: 8px;
align-items: center;
justify-content: center;
width: max-content;
max-width: 100%;
overflow: hidden;
}
.vp-qrcode .qrcode-label {
font-size: 14px;
color: var(--vp-c-text-2);
text-align: center;
word-break: break-all;
}
.vp-qrcode.card {
display: flex;
flex-direction: column;
gap: 8px;
align-items: center;
justify-content: center;
padding: 16px 20px;
background-color: var(--vp-c-bg-soft);
border-radius: 8px;
}
.vp-qrcode .qrcode-svg,
.vp-qrcode .qrcode-img {
width: var(--vp-qrcode-size);
max-width: 100%;
height: var(--vp-qrcode-size);
aspect-ratio: 1/1;
}
.vp-qrcode .qrcode-svg :deep(svg) {
max-width: 100%;
max-height: 100%;
}
.vp-qrcode .qrcode-info {
display: flex;
flex: 1;
flex-direction: column;
min-width: 0;
text-align: center;
}
.vp-qrcode .qrcode-info > p {
margin: 0;
word-break: break-all;
}
.vp-qrcode .qrcode-info .qrcode-title {
font-weight: 600;
}
@media (min-width: 960px) {
.vp-qrcode.card {
flex-direction: row;
gap: 16px;
align-items: flex-start;
justify-content: flex-start;
}
.vp-qrcode.card .qrcode-info {
align-self: center;
text-align: left;
}
.vp-qrcode.card:where(.reverse, .right) {
flex-direction: row-reverse;
}
.vp-qrcode.card:where(.reverse, .right) .qrcode-info {
text-align: right;
}
.vp-qrcode.card.center {
align-items: center;
}
.vp-qrcode.card.center .qrcode-info {
flex: initial;
}
}
</style>

View File

@ -7,6 +7,7 @@ import { codeSandboxPlugin } from './code/codeSandbox.js'
import { jsfiddlePlugin } from './code/jsfiddle.js' import { jsfiddlePlugin } from './code/jsfiddle.js'
import { replitPlugin } from './code/replit.js' import { replitPlugin } from './code/replit.js'
import { pdfPlugin } from './pdf.js' import { pdfPlugin } from './pdf.js'
import { qrcodePlugin } from './qrcode.js'
import { acfunPlugin } from './video/acfun.js' import { acfunPlugin } from './video/acfun.js'
import { artPlayerPlugin } from './video/artPlayer.js' import { artPlayerPlugin } from './video/artPlayer.js'
import { bilibiliPlugin } from './video/bilibili.js' import { bilibiliPlugin } from './video/bilibili.js'
@ -70,4 +71,9 @@ export function embedSyntaxPlugin(md: Markdown, options: MarkdownPowerPluginOpti
// @[jsfiddle](user/id) // @[jsfiddle](user/id)
md.use(jsfiddlePlugin) md.use(jsfiddlePlugin)
} }
if (options.qrcode) {
// @[qrcode svg card title="xxx"](text)
md.use(qrcodePlugin)
}
} }

View File

@ -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<never> = (md) => {
createEmbedRuleBlock<QRCodeMeta>(md, {
type: 'qrcode',
syntaxPattern: /^@\[qrcode([^\]]*)\]\(([^)]*)\)/,
meta([, info, text]) {
const { attrs } = resolveAttrs<QRCodeMeta>(info)
const { card, ...rest } = omit(attrs, ['text'])
return {
text,
...rest,
mode: rest.mode || (card ? 'card' : 'img'),
}
},
content(meta) {
return `<VPQRCode${stringifyAttrs(meta)} />`
},
})
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 `<VPQRCode ${stringifyAttrs(props)} />`
})
}

View File

@ -42,6 +42,9 @@ export function markdownPowerPlugin(
['artplayer', 'dashjs', 'hls.js', 'mpegts.js/dist/mpegts.js'], ['artplayer', 'dashjs', 'hls.js', 'mpegts.js/dist/mpegts.js'],
) )
} }
if (options.qrcode) {
addViteOptimizeDepsInclude(bundlerOptions, app, ['qrcode'])
}
}, },
extendsMarkdown: async (md, app) => { extendsMarkdown: async (md, app) => {

View File

@ -131,15 +131,19 @@ export async function prepareConfigFile(app: App, options: MarkdownPowerPluginOp
enhances.add(`app.component('VPTable', VPTable)`) 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 setupIcon = prepareIcon(imports, options.icon)
const setupStmts: string[] = [] const setupStmts: string[] = []
const iconSetup = setupIcon.trim() const iconSetup = setupIcon.trim()
if (iconSetup) if (iconSetup)
setupStmts.push(iconSetup) setupStmts.push(iconSetup)
const markMode = options.mark === 'lazy' ? 'lazy' : 'eager'
imports.add(`import { setupMarkHighlight } from '${CLIENT_FOLDER}composables/mark.js'`) 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 const setupContent = setupStmts.length
? ` ${setupStmts.join('\n ')}\n` ? ` ${setupStmts.join('\n ')}\n`

View File

@ -10,6 +10,7 @@ export * from './npmTo.js'
export * from './pdf.js' export * from './pdf.js'
export * from './plot.js' export * from './plot.js'
export * from './plugin.js' export * from './plugin.js'
export * from './qrcode.js'
export * from './repl.js' export * from './repl.js'
export * from './replit.js' export * from './replit.js'
export * from './size.js' export * from './size.js'

View File

@ -256,6 +256,13 @@ export interface MarkdownPowerPluginOptions {
*/ */
table?: boolean | TableContainerOptions table?: boolean | TableContainerOptions
/**
*
*
* @default false
*/
qrcode?: boolean
// enhance // enhance
/** /**
* *

View File

@ -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
/**
*
* LMQH
* @default 'M'
*/
level?: 'L' | 'M' | 'Q' | 'H' | 'l' | 'm' | 'q' | 'h'
/**
*
* 1-40
*/
version?: number
/**
*
* 01234567
*
*/
mask?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7
/**
*
* @default 4
*/
margin?: number
/**
* 11
*/
scale?: number
/**
* RGBA
*
* @default '#000000ff'
*/
light?: string
/**
* RGBA
*
* @default '#ffffffff'
*/
dark?: string
}

165
pnpm-lock.yaml generated
View File

@ -45,6 +45,9 @@ catalogs:
'@types/picomatch': '@types/picomatch':
specifier: ^4.0.2 specifier: ^4.0.2
version: 4.0.2 version: 4.0.2
'@types/qrcode':
specifier: ^1.5.6
version: 1.5.6
'@types/stylus': '@types/stylus':
specifier: ^0.48.43 specifier: ^0.48.43
version: 0.48.43 version: 0.48.43
@ -293,6 +296,9 @@ catalogs:
picomatch: picomatch:
specifier: ^4.0.3 specifier: ^4.0.3
version: 4.0.3 version: 4.0.3
qrcode:
specifier: ^1.5.4
version: 1.5.4
shiki: shiki:
specifier: ^3.17.0 specifier: ^3.17.0
version: 3.17.0 version: 3.17.0
@ -423,6 +429,9 @@ importers:
'@types/picomatch': '@types/picomatch':
specifier: catalog:dev specifier: catalog:dev
version: 4.0.2 version: 4.0.2
'@types/qrcode':
specifier: catalog:dev
version: 1.5.6
'@types/stylus': '@types/stylus':
specifier: catalog:dev specifier: catalog:dev
version: 0.48.43 version: 0.48.43
@ -703,6 +712,9 @@ importers:
pyodide: pyodide:
specifier: catalog:peer specifier: catalog:peer
version: 0.29.0 version: 0.29.0
qrcode:
specifier: catalog:prod
version: 1.5.4
sass: sass:
specifier: catalog:peer specifier: catalog:peer
version: 1.94.2 version: 1.94.2
@ -754,7 +766,7 @@ importers:
version: 14.1.0(vue@3.5.25(typescript@5.9.3)) version: 14.1.0(vue@3.5.25(typescript@5.9.3))
'@vueuse/integrations': '@vueuse/integrations':
specifier: catalog:prod 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: chokidar:
specifier: ^5.0.0 specifier: ^5.0.0
version: 5.0.0 version: 5.0.0
@ -2630,6 +2642,9 @@ packages:
'@types/picomatch@4.0.2': '@types/picomatch@4.0.2':
resolution: {integrity: sha512-qHHxQ+P9PysNEGbALT8f8YOSHW0KJu6l2xU8DYY0fu/EmGxXdVnuTLvFUvBgPJMSqXq29SYHveejeAha+4AYgA==} resolution: {integrity: sha512-qHHxQ+P9PysNEGbALT8f8YOSHW0KJu6l2xU8DYY0fu/EmGxXdVnuTLvFUvBgPJMSqXq29SYHveejeAha+4AYgA==}
'@types/qrcode@1.5.6':
resolution: {integrity: sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==}
'@types/qs@6.14.0': '@types/qs@6.14.0':
resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==} resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==}
@ -3394,6 +3409,10 @@ packages:
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
engines: {node: '>=6'} engines: {node: '>=6'}
camelcase@5.3.1:
resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==}
engines: {node: '>=6'}
caniuse-lite@1.0.30001733: caniuse-lite@1.0.30001733:
resolution: {integrity: sha512-e4QKw/O2Kavj2VQTKZWrwzkt3IxOmIlU6ajRb6LP64LHpBo1J67k2Hi4Vu/TgJWsNtynurfS0uK3MaUTCPfu5Q==} resolution: {integrity: sha512-e4QKw/O2Kavj2VQTKZWrwzkt3IxOmIlU6ajRb6LP64LHpBo1J67k2Hi4Vu/TgJWsNtynurfS0uK3MaUTCPfu5Q==}
@ -3504,6 +3523,9 @@ packages:
resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cliui@6.0.0:
resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==}
cliui@8.0.1: cliui@8.0.1:
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -3965,6 +3987,10 @@ packages:
supports-color: supports-color:
optional: true 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: decode-named-character-reference@1.2.0:
resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==} resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==}
@ -4034,6 +4060,9 @@ packages:
resolution: {integrity: sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==} resolution: {integrity: sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==}
engines: {node: '>=0.3.1'} engines: {node: '>=0.3.1'}
dijkstrajs@1.0.3:
resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==}
dir-glob@3.0.1: dir-glob@3.0.1:
resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
engines: {node: '>=8'} engines: {node: '>=8'}
@ -4526,6 +4555,10 @@ packages:
resolution: {integrity: sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==} resolution: {integrity: sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==}
engines: {node: '>=18'} engines: {node: '>=18'}
find-up@4.1.0:
resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
engines: {node: '>=8'}
find-up@5.0.0: find-up@5.0.0:
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -5416,6 +5449,10 @@ packages:
localforage@1.10.0: localforage@1.10.0:
resolution: {integrity: sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==} 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: locate-path@6.0.0:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -5982,6 +6019,10 @@ packages:
resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
p-limit@2.3.0:
resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}
engines: {node: '>=6'}
p-limit@3.1.0: p-limit@3.1.0:
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -5990,6 +6031,10 @@ packages:
resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 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: p-locate@5.0.0:
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -6006,6 +6051,10 @@ packages:
resolution: {integrity: sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==} resolution: {integrity: sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==}
engines: {node: '>=18'} 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: package-json-from-dist@1.0.1:
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
@ -6134,6 +6183,10 @@ packages:
resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==}
engines: {node: '>=4'} engines: {node: '>=4'}
pngjs@5.0.0:
resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==}
engines: {node: '>=10.13.0'}
pnpm-workspace-yaml@1.3.0: pnpm-workspace-yaml@1.3.0:
resolution: {integrity: sha512-Krb5q8Totd5mVuLx7we+EFHq/AfxA75nbfTm25Q1pIf606+RlaKUG+PXH8SDihfe5b5k4H09gE+sL47L1t5lbw==} resolution: {integrity: sha512-Krb5q8Totd5mVuLx7we+EFHq/AfxA75nbfTm25Q1pIf606+RlaKUG+PXH8SDihfe5b5k4H09gE+sL47L1t5lbw==}
@ -6251,6 +6304,11 @@ packages:
resolution: {integrity: sha512-7gJ6mxcQb9vUBOtbKm5mDevbe2uRcOEVp1g4gb/Q+oLntB3HY8eBhOYRxFI2mlDFlY1e4DOSCptzxarXRvzxCA==} resolution: {integrity: sha512-7gJ6mxcQb9vUBOtbKm5mDevbe2uRcOEVp1g4gb/Q+oLntB3HY8eBhOYRxFI2mlDFlY1e4DOSCptzxarXRvzxCA==}
engines: {node: '>=20'} engines: {node: '>=20'}
qrcode@1.5.4:
resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==}
engines: {node: '>=10.13.0'}
hasBin: true
qs@6.14.0: qs@6.14.0:
resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==}
engines: {node: '>=0.6'} engines: {node: '>=0.6'}
@ -6346,6 +6404,9 @@ packages:
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
require-main-filename@2.0.0:
resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==}
requires-port@1.0.0: requires-port@1.0.0:
resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
@ -6619,6 +6680,9 @@ packages:
engines: {node: '>=10'} engines: {node: '>=10'}
hasBin: true hasBin: true
set-blocking@2.0.0:
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
set-function-length@1.2.2: set-function-length@1.2.2:
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@ -7469,6 +7533,9 @@ packages:
resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
which-module@2.0.1:
resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==}
which-typed-array@1.1.19: which-typed-array@1.1.19:
resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@ -7497,6 +7564,10 @@ packages:
wordwrap@1.0.0: wordwrap@1.0.0:
resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} 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: wrap-ansi@7.0.0:
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -7532,6 +7603,9 @@ packages:
resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==}
engines: {node: '>=12'} engines: {node: '>=12'}
y18n@4.0.3:
resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==}
y18n@5.0.8: y18n@5.0.8:
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -7548,10 +7622,18 @@ packages:
engines: {node: '>= 14.6'} engines: {node: '>= 14.6'}
hasBin: true hasBin: true
yargs-parser@18.1.3:
resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==}
engines: {node: '>=6'}
yargs-parser@21.1.1: yargs-parser@21.1.1:
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
engines: {node: '>=12'} engines: {node: '>=12'}
yargs@15.4.1:
resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==}
engines: {node: '>=8'}
yargs@17.7.2: yargs@17.7.2:
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -9233,7 +9315,7 @@ snapshots:
'@types/jsonfile@6.1.4': '@types/jsonfile@6.1.4':
dependencies: dependencies:
'@types/node': 24.10.0 '@types/node': 24.10.1
'@types/less@3.0.8': {} '@types/less@3.0.8': {}
@ -9272,6 +9354,10 @@ snapshots:
'@types/picomatch@4.0.2': {} '@types/picomatch@4.0.2': {}
'@types/qrcode@1.5.6':
dependencies:
'@types/node': 24.10.1
'@types/qs@6.14.0': {} '@types/qs@6.14.0': {}
'@types/range-parser@1.2.7': {} '@types/range-parser@1.2.7': {}
@ -9976,7 +10062,7 @@ snapshots:
'@vueuse/shared': 14.1.0(vue@3.5.25(typescript@5.9.3)) '@vueuse/shared': 14.1.0(vue@3.5.25(typescript@5.9.3))
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: dependencies:
'@vueuse/core': 14.1.0(vue@3.5.25(typescript@5.9.3)) '@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)) '@vueuse/shared': 14.1.0(vue@3.5.25(typescript@5.9.3))
@ -9985,6 +10071,7 @@ snapshots:
axios: 1.13.2 axios: 1.13.2
change-case: 5.4.4 change-case: 5.4.4
focus-trap: 7.6.6 focus-trap: 7.6.6
qrcode: 1.5.4
'@vueuse/metadata@14.1.0': {} '@vueuse/metadata@14.1.0': {}
@ -10323,6 +10410,8 @@ snapshots:
callsites@3.1.0: {} callsites@3.1.0: {}
camelcase@5.3.1: {}
caniuse-lite@1.0.30001733: {} caniuse-lite@1.0.30001733: {}
caniuse-lite@1.0.30001752: {} caniuse-lite@1.0.30001752: {}
@ -10447,6 +10536,12 @@ snapshots:
cli-width@3.0.0: {} 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: cliui@8.0.1:
dependencies: dependencies:
string-width: 4.2.3 string-width: 4.2.3
@ -10962,6 +11057,8 @@ snapshots:
dependencies: dependencies:
ms: 2.1.3 ms: 2.1.3
decamelize@1.2.0: {}
decode-named-character-reference@1.2.0: decode-named-character-reference@1.2.0:
dependencies: dependencies:
character-entities: 2.0.2 character-entities: 2.0.2
@ -11017,6 +11114,8 @@ snapshots:
diff@8.0.2: {} diff@8.0.2: {}
dijkstrajs@1.0.3: {}
dir-glob@3.0.1: dir-glob@3.0.1:
dependencies: dependencies:
path-type: 4.0.0 path-type: 4.0.0
@ -11649,6 +11748,11 @@ snapshots:
find-up-simple@1.0.1: {} 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: find-up@5.0.0:
dependencies: dependencies:
locate-path: 6.0.0 locate-path: 6.0.0
@ -12621,6 +12725,10 @@ snapshots:
dependencies: dependencies:
lie: 3.1.1 lie: 3.1.1
locate-path@5.0.0:
dependencies:
p-locate: 4.1.0
locate-path@6.0.0: locate-path@6.0.0:
dependencies: dependencies:
p-locate: 5.0.0 p-locate: 5.0.0
@ -13411,6 +13519,10 @@ snapshots:
object-keys: 1.1.1 object-keys: 1.1.1
safe-push-apply: 1.0.0 safe-push-apply: 1.0.0
p-limit@2.3.0:
dependencies:
p-try: 2.2.0
p-limit@3.1.0: p-limit@3.1.0:
dependencies: dependencies:
yocto-queue: 0.1.0 yocto-queue: 0.1.0
@ -13419,6 +13531,10 @@ snapshots:
dependencies: dependencies:
yocto-queue: 1.2.1 yocto-queue: 1.2.1
p-locate@4.1.0:
dependencies:
p-limit: 2.3.0
p-locate@5.0.0: p-locate@5.0.0:
dependencies: dependencies:
p-limit: 3.1.0 p-limit: 3.1.0
@ -13431,6 +13547,8 @@ snapshots:
p-map@7.0.4: {} p-map@7.0.4: {}
p-try@2.2.0: {}
package-json-from-dist@1.0.1: {} package-json-from-dist@1.0.1: {}
package-manager-detector@1.5.0: {} package-manager-detector@1.5.0: {}
@ -13538,6 +13656,8 @@ snapshots:
pluralize@8.0.0: {} pluralize@8.0.0: {}
pngjs@5.0.0: {}
pnpm-workspace-yaml@1.3.0: pnpm-workspace-yaml@1.3.0:
dependencies: dependencies:
yaml: 2.8.1 yaml: 2.8.1
@ -13637,6 +13757,12 @@ snapshots:
dependencies: dependencies:
hookified: 1.13.0 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: qs@6.14.0:
dependencies: dependencies:
side-channel: 1.1.0 side-channel: 1.1.0
@ -13768,6 +13894,8 @@ snapshots:
require-from-string@2.0.2: {} require-from-string@2.0.2: {}
require-main-filename@2.0.0: {}
requires-port@1.0.0: {} requires-port@1.0.0: {}
reserved-identifiers@1.2.0: {} reserved-identifiers@1.2.0: {}
@ -14052,6 +14180,8 @@ snapshots:
semver@7.7.3: {} semver@7.7.3: {}
set-blocking@2.0.0: {}
set-function-length@1.2.2: set-function-length@1.2.2:
dependencies: dependencies:
define-data-property: 1.1.4 define-data-property: 1.1.4
@ -14982,6 +15112,8 @@ snapshots:
is-weakmap: 2.0.2 is-weakmap: 2.0.2
is-weakset: 2.0.4 is-weakset: 2.0.4
which-module@2.0.1: {}
which-typed-array@1.1.19: which-typed-array@1.1.19:
dependencies: dependencies:
available-typed-arrays: 1.0.7 available-typed-arrays: 1.0.7
@ -15011,6 +15143,12 @@ snapshots:
wordwrap@1.0.0: {} 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: wrap-ansi@7.0.0:
dependencies: dependencies:
ansi-styles: 4.3.0 ansi-styles: 4.3.0
@ -15040,6 +15178,8 @@ snapshots:
xml-name-validator@4.0.0: {} xml-name-validator@4.0.0: {}
y18n@4.0.3: {}
y18n@5.0.8: {} y18n@5.0.8: {}
yallist@3.1.1: {} yallist@3.1.1: {}
@ -15051,8 +15191,27 @@ snapshots:
yaml@2.8.1: {} yaml@2.8.1: {}
yargs-parser@18.1.3:
dependencies:
camelcase: 5.3.1
decamelize: 1.2.0
yargs-parser@21.1.1: {} 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: yargs@17.7.2:
dependencies: dependencies:
cliui: 8.0.1 cliui: 8.0.1

View File

@ -30,6 +30,7 @@ catalogs:
'@types/minimist': ^1.2.5 '@types/minimist': ^1.2.5
'@types/node': ^24.10.1 '@types/node': ^24.10.1
'@types/picomatch': ^4.0.2 '@types/picomatch': ^4.0.2
'@types/qrcode': ^1.5.6
'@types/stylus': ^0.48.43 '@types/stylus': ^0.48.43
'@types/three': ^0.181.0 '@types/three': ^0.181.0
'@types/webpack-env': ^1.18.8 '@types/webpack-env': ^1.18.8
@ -118,6 +119,7 @@ catalogs:
package-manager-detector: ^1.6.0 package-manager-detector: ^1.6.0
picocolors: ^1.1.1 picocolors: ^1.1.1
picomatch: ^4.0.3 picomatch: ^4.0.3
qrcode: ^1.5.4
shiki: ^3.17.0 shiki: ^3.17.0
sort-package-json: ^3.5.0 sort-package-json: ^3.5.0
tm-grammars: ^1.26.0 tm-grammars: ^1.26.0

View File

@ -64,6 +64,7 @@ export const MARKDOWN_POWER_FIELDS: (keyof MarkdownPowerPluginOptions)[] = [
'collapse', 'collapse',
'chat', 'chat',
'youtube', 'youtube',
'qrcode',
] ]
export const MARKDOWN_SUPPORT_FIELDS: (keyof MarkdownOptions)[] = [ export const MARKDOWN_SUPPORT_FIELDS: (keyof MarkdownOptions)[] = [