feat!: remove plugin-shikiji, migrate to @vuepress/plugin-shiki, close #489 (#508)

* feat: remove shiki plugin

* chore: tweak

* chore: tweak

---------

Co-authored-by: pengzhanbo <volodymyr@foxmail.com>
This commit is contained in:
Mister-Hope 2025-03-05 12:36:53 +08:00 committed by GitHub
parent de5469201a
commit b8b32201ef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
51 changed files with 238 additions and 2977 deletions

View File

@ -14,7 +14,6 @@ export const theme: Theme = plumeTheme({
shiki: {
twoslash: true,
lineNumbers: 10,
languages: ['sh', 'ts', 'md', 'html', 'js', 'go', 'kotlin', 'rust', 'vue', 'css', 'json', 'scss', 'yaml', 'bash', 'c++', 'java', 'py', 'ruby', 'make', 'objc', 'swift', 'php', 'rs', 'sql', 'xml', 'zig', 'pug', 'http', 'less', 'styl', 'jsx', 'tsx', 'astro', 'svelte', 'wasm', 'vb', 'bat', 'cs', 'cpp'],
},
markdownEnhance: {

View File

@ -9,22 +9,13 @@ permalink: /config/plugins/code-highlight/
主题内置的代码高亮插件, 对代码块进行代码高亮。
关联插件:[@vuepress/plugin-shiki](https://ecosystem.vuejs.press/zh/plugins/markdown/shiki.html)
主题 使用 [Shiki](https://github.com/shikijs/shiki) 在 Markdown 代码块中使用彩色文本实现语法高亮。
Shiki 支持多种编程语言。
在 Shiki 的代码仓库中,可以找到 [合法的编程语言列表](https://shiki.style/languages) 。
关联插件: [@vuepress-plume/plugin-shikiji](https://github.com/pengzhanbo/vuepress-theme-plume/tree/main/plugins/plugin-shikiji)
::: details 为什么不用 官方的 @vuepress/plugin-shiki
你可以认为本插件是 官方 [@vuepress/plugin-shiki](https://ecosystem.vuejs.press/zh/plugins/shiki.html) 的
一个分支版本,但本插件更为激进,支持更多新的特性。
同时,我也是 [@vuepress/plugin-shiki](https://ecosystem.vuejs.press/zh/plugins/shiki.html) 的主要维护者之一
,在 `@vuepress-plume/plugin-shikiji` 插件中新增的试验性的新特性,会在未来合适的时候合并到 官方插件中。
:::
## 特性
- [代码行高亮](../../guide/代码/特性支持.md#在代码块中实现行高亮)
@ -50,7 +41,7 @@ export default defineUserConfig({
theme: plumeTheme({
plugins: {
shiki: {
theme: { light: 'vitesse-light', dark: 'vitesse-dark' },
themes: { light: 'vitesse-light', dark: 'vitesse-dark' },
},
}
}),
@ -59,16 +50,16 @@ export default defineUserConfig({
:::
### theme
### themes
- 类型: `string | { light: string, dark: string }`
- 类型: `{ light: string, dark: string }`
- 默认值: `{ light: 'vitesse-light', dark: 'vitesse-dark' }`
代码高亮主题,支持 浅色/暗色 双主题。
可在 支持的 [主题列表](https://shiki.style/themes) 中选择你喜欢的主题。
### languages
### langs
- 类型: `string[]`
- 默认值: `[]`
@ -78,12 +69,12 @@ export default defineUserConfig({
在 Shiki 的代码仓库中,可以找到 [合法的编程语言列表](https://shiki.style/languages) 。
### defaultHighlightLang
### defaultLang
- 类型: `string`
- 默认值: `text`
默认高亮的编程语言。当代码块未指定语言时使用。
指定语言不可用使用的备选语言
### lineNumbers
@ -96,51 +87,12 @@ export default defineUserConfig({
`false` 不显示行号\
`number` 指定需要显式代码行号的最小行数。
### copyCode
- 类型: `boolean | CopyCodeOptions`
- 默认值: `true`
是否允许复制代码。启用时,会在代码块右侧显示复制按钮。
```ts
interface CopyCodeOptions {
/**
* 复制成功后提示文本持续时间
*
* @default 2000
*/
duration?: number
/**
* 多语言配置
*/
locales?: {
[localePath: string]: {
/**
* 复制按钮标题
*
* @default 'Copy code'
*/
title?: string
/**
* 复制成功提示
*
* @default 'Copied'
*/
copied?: string
}
}
}
```
### twoslash
- 类型: `boolean`
- 类型: `boolean | ShikiTwoslashOptions`
- 默认值: `false`
实验性功能,是否启用 对 `typescript``vue` 语言的 类型提示 支持。
是否启用 对 `typescript``vue` 语言的 类型提示 支持。
### whitespace
@ -151,7 +103,7 @@ interface CopyCodeOptions {
效果:
<!-- @include: ../../snippet/whitespace.snippet.md{18-24} -->
<!-- @include: ../../snippet/whitespace.snippet.md{17-23} -->
### collapseLines
@ -160,7 +112,7 @@ interface CopyCodeOptions {
将代码块折叠到指定行数。
### codeTransformers
### transformers
- 类型: `ShikiTransformer[]`
- 默认值: `[]`

View File

@ -1,6 +1,5 @@
---
title: 介绍
author: pengzhanbo
icon: ic:outline-code
createTime: 2024/04/04 10:35:45
permalink: /guide/code/intro/
@ -10,41 +9,22 @@ permalink: /guide/code/intro/
主题 使用 [Shiki](https://shiki.style/) 在 Markdown 代码块实现语法高亮。
:::info
主题默认 加载了 [Shiki](https://shiki.style/) 支持的超过 190+ 的 语言,这可能导致 在启用 vuepress 服务时,
需要多等待 **300ms ~ 600ms** 左右的时间来加载所有的 语言。
::: important 重要变更 <Badge type="danger" text="1.0.0-rc.136" />
因此,如果比较在意 vuepress 启动时间,建议修改配置为仅加载 您所需要的 语言。
从 ==1.0.0-rc.136== 版本开始,主题已将代码高亮插件从主题内部实现的 `@vuepress-plume/plugin-shikiji` 迁移
到了 [vuepress ecosystem](https://github.com/vuepress/ecosystem) 提供的 [@vuepress/plugin-shiki](https://ecosystem.vuejs.press/zh/plugins/markdown/shiki.html)。
示例:
_(无需担心变化太大,我也是 `@vuepress/plugin-shiki` 的主要开发者之一,它实现了与主题原插件一致的功能)_
::: code-tabs
@tab .vuepress/config.ts
其中涉及到部分配置项需要调整变更:
```ts
export default defineUserConfig({
theme: plumeTheme({
plugins: {
shiki: {
languages: ['javascript', 'typescript', 'vue', 'bash', 'sh'], // [!code highlight]
}
}
})
})
```
- `languages` 配置变更为 `langs` 选项。无需再手动添加你所使用的语言,插件将会自动识别并按需加载语言包。
- `themes` 配置变更为:
- 当使用单主题配置时,使用 `theme` 配置代码块主题
- 当使用双主题配置时,使用 `themes` 配置代码块主题。
:::
---
<Badge type="warning" text="v1.0.0-rc.105 更新" />
随着 `shiki` 支持的语言越来越丰富,默认加载全部的语言所花费的时间越来越多了,
因此 **强烈建议您 手动配置 `languages`** ,仅加载 您需要的 语言。
同时,在您未配置 `languages` 时,主题会在启动时尝试分析 `markdown` 文件中的代码块所使用的语言,
并将它们作为 `languages` 传入给 shiki这也极大的减少了加载时间。然而随着 项目 `markdown` 文件数量和内容的增长,
这种方式带来额外的 i/o 开销和解析开销也会越来越长,因此 **强烈建议您手动配置 `languages`**
## 语言
[Shiki](https://shiki.style/) 支持 超过 190+ 种语言,
@ -53,7 +33,8 @@ export default defineUserConfig({
你可以通过以下语法为你使用的 语言所编写的代码 实现高亮效果:
````md
```[lang]
``` [lang]
<!-- 代码内容 -->
```
````
@ -62,7 +43,8 @@ export default defineUserConfig({
示例:
````md
```js
// [!code word:js]
``` js
const a = 1
console.log(a)
```
@ -87,7 +69,7 @@ export default defineUserConfig({
theme: plumeTheme({
plugins: {
shiki: {
theme: { light: 'vitesse-light', dark: 'vitesse-dark' }, // [!code highlight]
themes: { light: 'vitesse-light', dark: 'vitesse-dark' }, // [!code highlight]
}
}
})

View File

@ -10,7 +10,6 @@
</book>
</catalog>
```
```
````
**输出:**
@ -57,7 +56,6 @@ function foo( ) {
return 'Hello World'
}
```
```
````
**输出:**

View File

@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (C) 2021 - PRESENT by pengzhanbo
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -1,83 +0,0 @@
# `@vuepress-plume/plugin-shikiji`
使用 [`shiki`](https://shiki.style/) 为 Markdown 代码块启用代码高亮。
> [!WARNING]
> 相比于 官方的 [@vuepress/plugin-shiki](https://ecosystem.vuejs.press/zh/plugins/shiki.html)
> 本插件做了很多各种各样的调整,你可以认为这是试验性的。
## Install
```sh
npm install @vuepress-plume/plugin-shikiji
# or
pnpm add @vuepress-plume/plugin-shikiji
# or
yarn add @vuepress-plume/plugin-shikiji
```
## Usage
``` js
// .vuepress/config.[jt]s
import { shikiPlugin } from '@vuepress-plume/plugin-shikiji'
export default {
// ...
plugins: [
shikiPlugin()
]
// ...
}
```
## Options
```ts
interface ShikijiOptions {
/**
* Custom theme for syntax highlighting.
*
* You can also pass an object with `light` and `dark` themes to support dual themes.
*
* @example { theme: 'github-dark' }
* @example { theme: { light: 'github-light', dark: 'github-dark' } }
*
* You can use an existing theme.
* @see https://shiki.style/themes
* Or add your own theme.
* @see https://shiki.style/guide/load-theme
*/
theme?: ThemeOptions
/**
* Languages for syntax highlighting.
* @see https://shiki.style/languages
*/
languages?: LanguageInput[]
/**
* Custom language aliases.
*
* @example { 'my-lang': 'js' }
* @see https://shiki.style/guide/load-lang#custom-language-aliases
*/
languageAlias?: Record<string, string>
/**
* Setup Shikiji instance
*/
shikiSetup?: (shikiji: Highlighter) => void | Promise<void>
/**
* Fallback language when the specified language is not available.
*/
defaultHighlightLang?: string
/**
* Transformers applied to code blocks
* @see https://shiki.style/guide/transformers
*/
codeTransformers?: ShikiTransformer[]
/**
* Enable transformerRenderWhitespace
* @default false
*/
whitespace?: boolean
}
```

View File

@ -1,456 +0,0 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`collapsedLinesPlugin > should not work with false 1`] = `
"<div class="language-"><pre><code>const line1 = 1
const line2 = 2
const line3 = 3
const line4 = 4
const line5 = 5
const line6 = 6
const line7 = 7
const line8 = 8
const line9 = 9
const line10 = 10
</code></pre>
</div><div class="language-"><pre><code>const line1 = 1
const line2 = 2
const line3 = 3
const line4 = 4
const line5 = 5
const line6 = 6
const line7 = 7
const line8 = 8
const line9 = 9
const line10 = 10
const line11 = 11
const line12 = 12
const line13 = 13
const line14 = 14
const line15 = 15
const line16 = 16
const line17 = 17
const line18 = 18
const line19 = 19
const line20 = 20
</code></pre>
</div><div class="language- has-collapsed collapsed" style="--vp-collapsed-lines:15;"><pre><code>const line1 = 1
const line2 = 2
const line3 = 3
const line4 = 4
const line5 = 5
const line6 = 6
const line7 = 7
const line8 = 8
const line9 = 9
const line10 = 10
const line11 = 11
const line12 = 12
const line13 = 13
const line14 = 14
const line15 = 15
const line16 = 16
const line17 = 17
const line18 = 18
const line19 = 19
const line20 = 20
</code></pre>
<div class="collapsed-lines"></div></div><div class="language- has-collapsed collapsed" style="--vp-collapsed-lines:10;"><pre><code>const line1 = 1
const line2 = 2
const line3 = 3
const line4 = 4
const line5 = 5
const line6 = 6
const line7 = 7
const line8 = 8
const line9 = 9
const line10 = 10
const line11 = 11
const line12 = 12
</code></pre>
<div class="collapsed-lines"></div></div><div class="language-"><pre><code>const line1 = 1
const line2 = 2
const line3 = 3
const line4 = 4
const line5 = 5
const line6 = 6
const line7 = 7
const line8 = 8
const line9 = 9
const line10 = 10
const line11 = 11
const line12 = 12
const line13 = 13
const line14 = 14
const line15 = 15
const line16 = 16
const line17 = 17
const line18 = 18
const line19 = 19
const line20 = 20
</code></pre>
</div>"
`;
exports[`collapsedLinesPlugin > should not work with includes styles 1`] = `
"<div class="language-" style="color: red;"><pre><code>const line1 = 1
const line2 = 2
const line3 = 3
const line4 = 4
const line5 = 5
const line6 = 6
const line7 = 7
const line8 = 8
const line9 = 9
const line10 = 10
</code></pre>
</div><div class="language- has-collapsed collapsed" style="--vp-collapsed-lines:15;color: red;"><pre><code>const line1 = 1
const line2 = 2
const line3 = 3
const line4 = 4
const line5 = 5
const line6 = 6
const line7 = 7
const line8 = 8
const line9 = 9
const line10 = 10
const line11 = 11
const line12 = 12
const line13 = 13
const line14 = 14
const line15 = 15
const line16 = 16
const line17 = 17
const line18 = 18
const line19 = 19
const line20 = 20
</code></pre>
<div class="collapsed-lines"></div></div><div class="language- has-collapsed collapsed" style="--vp-collapsed-lines:15;color: red;"><pre><code>const line1 = 1
const line2 = 2
const line3 = 3
const line4 = 4
const line5 = 5
const line6 = 6
const line7 = 7
const line8 = 8
const line9 = 9
const line10 = 10
const line11 = 11
const line12 = 12
const line13 = 13
const line14 = 14
const line15 = 15
const line16 = 16
const line17 = 17
const line18 = 18
const line19 = 19
const line20 = 20
</code></pre>
<div class="collapsed-lines"></div></div><div class="language- has-collapsed collapsed" style="--vp-collapsed-lines:10;color: red;"><pre><code>const line1 = 1
const line2 = 2
const line3 = 3
const line4 = 4
const line5 = 5
const line6 = 6
const line7 = 7
const line8 = 8
const line9 = 9
const line10 = 10
const line11 = 11
const line12 = 12
</code></pre>
<div class="collapsed-lines"></div></div><div class="language-" style="color: red;"><pre><code>const line1 = 1
const line2 = 2
const line3 = 3
const line4 = 4
const line5 = 5
const line6 = 6
const line7 = 7
const line8 = 8
const line9 = 9
const line10 = 10
const line11 = 11
const line12 = 12
const line13 = 13
const line14 = 14
const line15 = 15
const line16 = 16
const line17 = 17
const line18 = 18
const line19 = 19
const line20 = 20
</code></pre>
</div>"
`;
exports[`collapsedLinesPlugin > should work with default 1`] = `
"<div class="language-"><pre><code>const line1 = 1
const line2 = 2
const line3 = 3
const line4 = 4
const line5 = 5
const line6 = 6
const line7 = 7
const line8 = 8
const line9 = 9
const line10 = 10
</code></pre>
</div><div class="language-"><pre><code>const line1 = 1
const line2 = 2
const line3 = 3
const line4 = 4
const line5 = 5
const line6 = 6
const line7 = 7
const line8 = 8
const line9 = 9
const line10 = 10
const line11 = 11
const line12 = 12
const line13 = 13
const line14 = 14
const line15 = 15
const line16 = 16
const line17 = 17
const line18 = 18
const line19 = 19
const line20 = 20
</code></pre>
</div><div class="language- has-collapsed collapsed" style="--vp-collapsed-lines:15;"><pre><code>const line1 = 1
const line2 = 2
const line3 = 3
const line4 = 4
const line5 = 5
const line6 = 6
const line7 = 7
const line8 = 8
const line9 = 9
const line10 = 10
const line11 = 11
const line12 = 12
const line13 = 13
const line14 = 14
const line15 = 15
const line16 = 16
const line17 = 17
const line18 = 18
const line19 = 19
const line20 = 20
</code></pre>
<div class="collapsed-lines"></div></div><div class="language- has-collapsed collapsed" style="--vp-collapsed-lines:10;"><pre><code>const line1 = 1
const line2 = 2
const line3 = 3
const line4 = 4
const line5 = 5
const line6 = 6
const line7 = 7
const line8 = 8
const line9 = 9
const line10 = 10
const line11 = 11
const line12 = 12
</code></pre>
<div class="collapsed-lines"></div></div><div class="language-"><pre><code>const line1 = 1
const line2 = 2
const line3 = 3
const line4 = 4
const line5 = 5
const line6 = 6
const line7 = 7
const line8 = 8
const line9 = 9
const line10 = 10
const line11 = 11
const line12 = 12
const line13 = 13
const line14 = 14
const line15 = 15
const line16 = 16
const line17 = 17
const line18 = 18
const line19 = 19
const line20 = 20
</code></pre>
</div>"
`;
exports[`collapsedLinesPlugin > should work with number 1`] = `
"<div class="language- has-collapsed collapsed" style="--vp-collapsed-lines:10;"><pre><code>const line1 = 1
const line2 = 2
const line3 = 3
const line4 = 4
const line5 = 5
const line6 = 6
const line7 = 7
const line8 = 8
const line9 = 9
const line10 = 10
</code></pre>
<div class="collapsed-lines"></div></div><div class="language- has-collapsed collapsed" style="--vp-collapsed-lines:10;"><pre><code>const line1 = 1
const line2 = 2
const line3 = 3
const line4 = 4
const line5 = 5
const line6 = 6
const line7 = 7
const line8 = 8
const line9 = 9
const line10 = 10
const line11 = 11
const line12 = 12
const line13 = 13
const line14 = 14
const line15 = 15
const line16 = 16
const line17 = 17
const line18 = 18
const line19 = 19
const line20 = 20
</code></pre>
<div class="collapsed-lines"></div></div><div class="language- has-collapsed collapsed" style="--vp-collapsed-lines:15;"><pre><code>const line1 = 1
const line2 = 2
const line3 = 3
const line4 = 4
const line5 = 5
const line6 = 6
const line7 = 7
const line8 = 8
const line9 = 9
const line10 = 10
const line11 = 11
const line12 = 12
const line13 = 13
const line14 = 14
const line15 = 15
const line16 = 16
const line17 = 17
const line18 = 18
const line19 = 19
const line20 = 20
</code></pre>
<div class="collapsed-lines"></div></div><div class="language- has-collapsed collapsed" style="--vp-collapsed-lines:10;"><pre><code>const line1 = 1
const line2 = 2
const line3 = 3
const line4 = 4
const line5 = 5
const line6 = 6
const line7 = 7
const line8 = 8
const line9 = 9
const line10 = 10
const line11 = 11
const line12 = 12
</code></pre>
<div class="collapsed-lines"></div></div><div class="language-"><pre><code>const line1 = 1
const line2 = 2
const line3 = 3
const line4 = 4
const line5 = 5
const line6 = 6
const line7 = 7
const line8 = 8
const line9 = 9
const line10 = 10
const line11 = 11
const line12 = 12
const line13 = 13
const line14 = 14
const line15 = 15
const line16 = 16
const line17 = 17
const line18 = 18
const line19 = 19
const line20 = 20
</code></pre>
</div>"
`;
exports[`collapsedLinesPlugin > should work with true 1`] = `
"<div class="language-"><pre><code>const line1 = 1
const line2 = 2
const line3 = 3
const line4 = 4
const line5 = 5
const line6 = 6
const line7 = 7
const line8 = 8
const line9 = 9
const line10 = 10
</code></pre>
</div><div class="language- has-collapsed collapsed" style="--vp-collapsed-lines:15;"><pre><code>const line1 = 1
const line2 = 2
const line3 = 3
const line4 = 4
const line5 = 5
const line6 = 6
const line7 = 7
const line8 = 8
const line9 = 9
const line10 = 10
const line11 = 11
const line12 = 12
const line13 = 13
const line14 = 14
const line15 = 15
const line16 = 16
const line17 = 17
const line18 = 18
const line19 = 19
const line20 = 20
</code></pre>
<div class="collapsed-lines"></div></div><div class="language- has-collapsed collapsed" style="--vp-collapsed-lines:15;"><pre><code>const line1 = 1
const line2 = 2
const line3 = 3
const line4 = 4
const line5 = 5
const line6 = 6
const line7 = 7
const line8 = 8
const line9 = 9
const line10 = 10
const line11 = 11
const line12 = 12
const line13 = 13
const line14 = 14
const line15 = 15
const line16 = 16
const line17 = 17
const line18 = 18
const line19 = 19
const line20 = 20
</code></pre>
<div class="collapsed-lines"></div></div><div class="language- has-collapsed collapsed" style="--vp-collapsed-lines:10;"><pre><code>const line1 = 1
const line2 = 2
const line3 = 3
const line4 = 4
const line5 = 5
const line6 = 6
const line7 = 7
const line8 = 8
const line9 = 9
const line10 = 10
const line11 = 11
const line12 = 12
</code></pre>
<div class="collapsed-lines"></div></div><div class="language-"><pre><code>const line1 = 1
const line2 = 2
const line3 = 3
const line4 = 4
const line5 = 5
const line6 = 6
const line7 = 7
const line8 = 8
const line9 = 9
const line10 = 10
const line11 = 11
const line12 = 12
const line13 = 13
const line14 = 14
const line15 = 15
const line16 = 16
const line17 = 17
const line18 = 18
const line19 = 19
const line20 = 20
</code></pre>
</div>"
`;

View File

@ -1,68 +0,0 @@
import MarkdownIt from 'markdown-it'
import { describe, expect, it } from 'vitest'
import { collapsedLinesPlugin } from '../src/node/markdown/collapsedLinesPlugin.js'
const FENCE = '```'
function createMarkdown(collapsedLines?: boolean | number, hasStyles = false) {
const md = new MarkdownIt()
md.options.highlight = str => `<pre><code>${str}</code></pre>`
const fence = md.renderer.rules.fence!
md.renderer.rules.fence = (...args) =>
`<div class="language-"${hasStyles ? ' style="color: red;"' : ''}>${fence(...args)}</div>`
md.use(collapsedLinesPlugin, collapsedLines ? { collapsedLines } : undefined)
return md
}
describe('collapsedLinesPlugin', () => {
const genLines = (n: number) =>
Array.from({ length: n }).map((_, i) => `const line${i + 1} = ${i + 1}`).join('\n')
const code = `\
${FENCE}
${genLines(10)}
${FENCE}
${FENCE}js
${genLines(20)}
${FENCE}
${FENCE}js :collapsed-lines
${genLines(20)}
${FENCE}
${FENCE}js :collapsed-lines=10
${genLines(12)}
${FENCE}
${FENCE}js :no-collapsed-lines
${genLines(20)}
${FENCE}
`
it('should work with default', () => {
const md = createMarkdown()
expect(md.render(code)).toMatchSnapshot()
})
it('should work with true', () => {
const md = createMarkdown(true)
expect(md.render(code)).toMatchSnapshot()
})
it('should work with number', () => {
const md = createMarkdown(10)
expect(md.render(code)).toMatchSnapshot()
})
it('should not work with includes styles', () => {
const md = createMarkdown(true, true)
expect(md.render(code)).toMatchSnapshot()
})
it('should not work with false', () => {
const md = createMarkdown(false)
expect(md.render(code)).toMatchSnapshot()
})
})

View File

@ -1,49 +0,0 @@
import type { App } from 'vuepress/core'
import type { CopyCodeOptions } from '../src/node/types.js'
import MarkdownIt from 'markdown-it'
import { describe, expect, it } from 'vitest'
import { copyCodeButtonPlugin } from '../src/node/copy-code-button/index.js'
function createMarkdown(options: boolean | CopyCodeOptions = {}, lang = 'en-US') {
const md = new MarkdownIt()
md.options.highlight = str => `<pre><code>${str}</code></pre>`
const fence = md.renderer.rules.fence!
md.renderer.rules.fence = (...args) => `<div class="language-lang">${fence(...args)}</div>`
const app = {
env: { isDebug: false },
siteData: {
lang,
locales: { '/': { lang }, '/zh/': { lang: 'zh-CN' }, '/en/': { lang: 'en-US' }, '/xxx/': { lang: 'unknown' } },
},
} as unknown as App
copyCodeButtonPlugin(md, app, options)
return md
}
describe('copy code button plugin', () => {
it('should work with default', () => {
const md = createMarkdown()
expect(md.render('```js\nconst a = 1\n```', { filePathRelative: '/test.md' })).toContain('<button class="copy"')
expect(md.render('```js\nconst a = 1\n```', { filePathRelative: '/zh/test.md' })).toContain('<button class="copy"')
expect(md.render('```js\nconst a = 1\n```', { filePathRelative: '/xxx/test.md' })).toContain('<button class="copy"')
expect(md.render('```js\nconst a = 1\n```', {})).toContain('<button class="copy"')
})
it('should work with true', () => {
const md = createMarkdown(true)
expect(md.render('```js\nconst a = 1\n```', { filePathRelative: '/test.md' })).toContain('<button class="copy"')
expect(md.render('```js\nconst a = 1\n```', {})).toContain('<button class="copy"')
})
it('should not work with disable', () => {
const md = createMarkdown(false)
expect(md.render('```js\nconst a = 1\n```', { filePathRelative: '/test.js' })).not.toContain('<button class="copy"')
})
})

View File

@ -1,63 +0,0 @@
{
"name": "@vuepress-plume/plugin-shikiji",
"type": "module",
"version": "1.0.0-rc.135",
"description": "The Plugin for VuePress 2 - shiki",
"author": "pengzhanbo <volodymyr@foxmail.com>",
"license": "MIT",
"homepage": "https://github.com/pengzhanbo/vuepress-theme-plume#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/pengzhanbo/vuepress-theme-plume.git",
"directory": "plugins/plugin-shikiji"
},
"bugs": {
"url": "https://github.com/pengzhanbo/vuepress-theme-plume/issues"
},
"exports": {
".": {
"types": "./lib/node/index.d.ts",
"import": "./lib/node/index.js"
},
"./package.json": "./package.json"
},
"main": "lib/node/index.js",
"types": "./lib/node/index.d.ts",
"files": [
"lib"
],
"scripts": {
"build": "pnpm run copy && pnpm run tsup",
"clean": "rimraf --glob ./lib",
"copy": "cpx \"src/**/*.{d.ts,vue,css,scss,jpg,png}\" lib",
"tsup": "tsup --config tsup.config.ts"
},
"peerDependencies": {
"vuepress": "catalog:"
},
"dependencies": {
"@shikijs/transformers": "^3.1.0",
"@shikijs/twoslash": "^3.1.0",
"@types/hast": "^3.0.4",
"@vuepress/helper": "catalog:",
"@vueuse/core": "catalog:",
"fast-glob": "catalog:",
"floating-vue": "^5.2.2",
"mdast-util-from-markdown": "^2.0.2",
"mdast-util-gfm": "^3.1.0",
"mdast-util-to-hast": "^13.2.0",
"nanoid": "catalog:",
"shiki": "^3.1.0",
"twoslash": "^0.3.1",
"twoslash-vue": "^0.3.1"
},
"publishConfig": {
"access": "public"
},
"keyword": [
"VuePress",
"vuepress plugin",
"shiki",
"vuepress-plugin-shiki"
]
}

View File

@ -1,15 +0,0 @@
import { useEventListener } from '@vueuse/core'
export function useCollapsedLines({
selector = 'div[class*="language-"] > .collapsed-lines',
}: { selector?: string } = {}): void {
useEventListener('click', (e) => {
const el = e.target as HTMLElement
if (el.matches(selector)) {
const parent = el.parentElement
if (parent?.classList.toggle('collapsed')) {
parent.scrollIntoView({ block: 'center', behavior: 'instant' })
}
}
})
}

View File

@ -1,57 +0,0 @@
import { useClipboard, useEventListener } from '@vueuse/core'
const SHELL_RE = /language-(?:shellscript|shell|bash|sh|zsh)/
const IGNORE_NODES = ['.vp-copy-ignore', '.diff.remove']
interface CopyCodeOptions {
selector?: string
duration?: number
}
export function useCopyCode({
selector = 'div[class*="language-"] > button.copy',
duration = 2000,
}: CopyCodeOptions = {}): void {
if (__VUEPRESS_SSR__)
return
const timeoutIdMap = new WeakMap<HTMLElement, ReturnType<typeof setTimeout>>()
const { copy } = useClipboard({ legacy: true })
useEventListener('click', (e) => {
const el = e.target as HTMLElement
if (el.matches(selector)) {
const parent = el.parentElement
const sibling = el.nextElementSibling
if (!parent || !sibling)
return
const isShell = SHELL_RE.test(parent.className)
// Clone the node and remove the ignored nodes
const clone = sibling.cloneNode(true) as HTMLElement
clone
.querySelectorAll(IGNORE_NODES.join(','))
.forEach(node => node.remove())
let text = clone.textContent || ''
if (isShell)
text = text.replace(/^ *(\$|>) /gm, '').trim()
copy(text).then(() => {
if (duration <= 0)
return
el.classList.add('copied')
clearTimeout(timeoutIdMap.get(el))
const timeoutId = setTimeout(() => {
el.classList.remove('copied')
el.blur()
timeoutIdMap.delete(el)
}, duration)
timeoutIdMap.set(el, timeoutId)
})
}
})
}

View File

@ -1,50 +0,0 @@
import type { App } from 'vue'
import FloatingVue, { recomputeAllPoppers } from 'floating-vue'
import 'floating-vue/dist/style.css'
const isMobile = typeof navigator !== 'undefined' && /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)
export type FloatingVueConfig = Parameters<(typeof FloatingVue)['install']>[1]
export function enhanceTwoslash(app: App) {
if (typeof window !== 'undefined') {
// Recompute poppers when clicking on a tab
window.addEventListener('click', (e) => {
const path = e.composedPath()
if (path.some((el: any) => el?.classList?.contains?.('vp-code-group') || el?.classList?.contains?.('tabs')))
recomputeAllPoppers()
}, { passive: true })
}
app.use(FloatingVue, {
themes: {
'twoslash': {
$extend: 'dropdown',
triggers: isMobile ? ['touch'] : ['hover', 'touch'],
popperTriggers: isMobile ? ['touch'] : ['hover', 'touch'],
placement: 'bottom-start',
overflowPadding: 10,
delay: 0,
handleResize: false,
autoHide: true,
instantMove: true,
flip: false,
arrowPadding: 8,
autoBoundaryMaxSize: true,
},
'twoslash-query': {
$extend: 'twoslash',
triggers: ['click'],
popperTriggers: ['click'],
autoHide: false,
},
'twoslash-completion': {
$extend: 'twoslash-query',
triggers: ['click'],
popperTriggers: ['click'],
autoHide: false,
distance: 0,
arrowOverflow: true,
},
},
})
}

View File

@ -1,106 +0,0 @@
import type { LocaleConfig } from 'vuepress'
import type { CopyCodeLocaleOptions } from '../types.js'
/** Multi language config for copy code button */
export const copyCodeButtonLocales: LocaleConfig<CopyCodeLocaleOptions>
= {
'/en/': {
title: 'Copy code',
copied: 'Copied',
},
'/zh/': {
title: '复制代码',
copied: '已复制',
},
'/zh-tw/': {
title: '複製代碼',
copied: '已複製',
},
'/de/': {
title: 'Kopiere den Code.',
copied: 'Kopiert',
},
'/de-at/': {
title: 'Kopiere den Code.',
copied: 'Kopierter',
},
'/vi/': {
title: 'Sao chép code',
copied: 'Đã sao chép',
},
'/uk/': {
title: 'Скопіюйте код',
copied: 'Скопійовано',
},
'/ru/': {
title: 'Скопировать код',
copied: 'Скопировано',
},
'/br/': {
title: 'Copiar o código',
copied: 'Código',
},
'/pl/': {
title: 'Skopiuj kod',
copied: 'Skopiowane',
},
'/sk/': {
title: 'Skopíruj kód',
copied: 'Skopírované',
},
'/fr/': {
title: 'Copier le code',
copied: 'Copié',
},
'/es/': {
title: 'Copiar código',
copied: 'Copiado',
},
'/ja/': {
title: 'コードをコピー',
copied: 'コピーしました',
},
'/tr/': {
title: 'Kodu kopyala',
copied: 'Kopyalandı',
},
'/ko/': {
title: '코드 복사',
copied: '복사됨',
},
'/fi/': {
title: 'Kopioi koodi',
copied: 'Kopioitu',
},
'/hu/': {
title: 'Kód másolása',
copied: 'Másolva',
},
'/id/': {
title: 'Salin kode',
copied: 'Disalin',
},
'/nl/': {
title: 'Kopieer code',
copied: 'Gekopieerd',
},
}

View File

@ -1,28 +0,0 @@
import type { App } from 'vuepress'
import type { Markdown, MarkdownEnv } from 'vuepress/markdown'
import type { CopyCodeOptions } from '../types.js'
import { createCopyCodeButtonRender } from './createCopyCodeButtonRender.js'
/**
* This plugin should work after `preWrapperPlugin`,
* and if `preWrapper` is disabled, this plugin should not be called either.
*/
export function copyCodeButtonPlugin(md: Markdown, app: App, options?: boolean | CopyCodeOptions): void {
const render = createCopyCodeButtonRender(app, options)
if (!render)
return
const fence = md.renderer.rules.fence!
md.renderer.rules.fence = (...args) => {
const [, , , env] = args
const result = fence(...args)
const { filePathRelative } = env as MarkdownEnv
// resolve copy code button
const copyCodeButton = render(filePathRelative ?? '')
return result.replace('><pre', `>${copyCodeButton}<pre`)
}
}

View File

@ -1,39 +0,0 @@
import type { App, LocaleConfig } from 'vuepress'
import type {
CopyCodeLocaleOptions,
CopyCodeOptions,
} from '../types.js'
import {
getLocalePaths,
inferRootLocalePath,
isPlainObject,
} from '@vuepress/helper'
import { ensureLeadingSlash, resolveLocalePath } from 'vuepress/shared'
import { copyCodeButtonLocales } from './copyCodeButtonLocales.js'
export function createCopyCodeButtonRender(app: App, options?: boolean | CopyCodeOptions): ((filePathRelative: string) => string) | null {
if (options === false)
return null
const { className = 'copy', locales: userLocales = {} }
= isPlainObject(options) ? options : {}
const root = inferRootLocalePath(app)
const locales: LocaleConfig<CopyCodeLocaleOptions> = {
// fallback locale
'/': userLocales['/'] || copyCodeButtonLocales[root],
}
getLocalePaths(app).forEach((path) => {
locales[path]
= userLocales[path] || copyCodeButtonLocales[path === '/' ? root : path]
})
return (filePathRelative: string) => {
const relativePath = ensureLeadingSlash(filePathRelative)
const localePath = resolveLocalePath(locales, relativePath)
const { title, copied } = locales[localePath] || locales['/']
return `<button class="${className}" title="${title}" data-copied="${copied}"></button>`
}
}

View File

@ -1,3 +0,0 @@
export * from './copyCodeButtonLocales.js'
export * from './copyCodeButtonPlugin.js'
export * from './createCopyCodeButtonRender.js'

View File

@ -1,25 +0,0 @@
import { isPlainLang, isSpecialLang } from 'shiki'
import { colors as c, logger } from 'vuepress/utils'
import { resolveLanguage } from '../utils/index.js'
export function getLanguage(
loadedLanguages: string[],
language: string,
defaultLang: string,
): string {
let lang = resolveLanguage(language) || defaultLang
if (lang) {
const langLoaded = loadedLanguages.includes(lang as any)
if (!langLoaded && !isPlainLang(lang) && !isSpecialLang(lang)) {
logger.warn(
c.yellow(
`\nThe language '${lang}' is not loaded, falling back to '${defaultLang || 'txt'
}' for syntax highlighting.`,
),
)
lang = defaultLang
}
}
return lang
}

View File

@ -1,98 +0,0 @@
import type { HighlighterOptions, ThemeOptions } from '../types.js'
import { customAlphabet } from 'nanoid'
import { bundledLanguages, createHighlighter } from 'shiki'
import { colors, logger } from 'vuepress/utils'
import { getLanguage } from './getLanguage.js'
import { baseTransformers, getInlineTransformers } from './transformers.js'
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz', 10)
const mustacheRE = /\{\{.*?\}\}/g
export async function highlight(
theme: ThemeOptions,
options: HighlighterOptions,
): Promise<(str: string, lang: string, attrs: string) => string> {
const {
defaultHighlightLang: defaultLang = '',
codeTransformers: userTransformers = [],
whitespace = false,
languages = Object.keys(bundledLanguages),
} = options
const highlighter = await createHighlighter({
themes:
typeof theme === 'object' && 'light' in theme && 'dark' in theme
? [theme.light, theme.dark]
: [theme],
langs: languages,
langAlias: options.languageAlias,
})
await options.shikiSetup?.(highlighter)
const loadedLanguages = highlighter.getLoadedLanguages()
return (str: string, language: string, attrs: string = '') => {
const lang = getLanguage(loadedLanguages, language, defaultLang)
const enabledTwoslash = attrs.includes('twoslash') && !!options.twoslash
const mustaches = new Map<string, string>()
str = removeMustache(str, mustaches).trimEnd()
try {
const highlighted = highlighter.codeToHtml(str, {
lang,
transformers: [
...baseTransformers,
...getInlineTransformers({
attrs,
lang,
enabledTwoslash,
whitespace,
twoslash: options.twoslash,
}),
...userTransformers,
],
meta: { __raw: attrs },
...(typeof theme === 'object' && 'light' in theme && 'dark' in theme
? { themes: theme, defaultColor: false }
: { theme }),
})
const rendered = restoreMustache(highlighted, mustaches, enabledTwoslash)
return rendered
}
catch (e) {
logger.error(
(e as Error)?.message,
'\n',
(e as Error)?.stack ? colors.gray(String((e as Error)?.stack)) : '',
)
return str
}
}
}
function removeMustache(s: string, mustaches: Map<string, string>) {
return s.replace(mustacheRE, (match) => {
let marker = mustaches.get(match)
if (!marker) {
marker = nanoid()
mustaches.set(match, marker)
}
return marker
})
}
function restoreMustache(s: string, mustaches: Map<string, string>, twoslash: boolean) {
mustaches.forEach((marker, match) => {
s = s.replaceAll(marker, match)
})
if (twoslash)
s = s.replace(/\{/g, '&#123;')
return `${s}\n`
}

View File

@ -1,3 +0,0 @@
export * from './highlight.js'
export * from './resolveTsPaths.js'
export * from './scanLanguages.js'

View File

@ -1,28 +0,0 @@
import fs from 'node:fs/promises'
import path from 'node:path'
import process from 'node:process'
export async function resolveTsPaths(): Promise<Record<string, string[]> | undefined> {
const tsconfigPath = path.join(process.cwd(), 'tsconfig.json')
try {
const tsconfig = JSON.parse(await fs.readFile(tsconfigPath, 'utf-8'))
const paths = tsconfig.compilerOptions?.paths ?? undefined
const baseUrl = tsconfig.compilerOptions?.baseUrl
if (baseUrl && paths) {
const dirname = path.join(process.cwd(), baseUrl)
for (const key in paths) {
const value = paths[key]
if (Array.isArray(value))
paths[key] = value.map(v => path.resolve(dirname, v))
else if (value)
paths[key] = path.resolve(dirname, value)
}
}
return paths
}
catch {
return undefined
}
}

View File

@ -1,34 +0,0 @@
import type { App } from 'vuepress'
import fs from 'node:fs/promises'
import path from 'node:path'
import fg from 'fast-glob'
import { bundledLanguages } from 'shiki'
const languages = Object.keys(bundledLanguages)
const RE_FENCE = /`{3,}[^`]*?(\s|$)/g
export async function scanLanguages(app: App): Promise<string[]> {
const source = app.dir.source()
const pattern = ['**/*.md', '!.vuepress', '!node_modules']
const files = await fg(pattern, {
cwd: source,
})
const langs = new Set<string>()
for (const file of files) {
const filepath = path.join(source, file)
const content = await fs.readFile(filepath, 'utf-8')
const matched = content.match(RE_FENCE)
if (matched) {
for (const match of matched) {
let lang = match.replace(/`{3,}/, '').trim()
if (lang.includes(':'))
lang = lang.split(':')[0]
if (lang)
langs.add(lang)
}
}
}
return Array.from(langs).filter(lang => languages.includes(lang))
}

View File

@ -1,114 +0,0 @@
import type { TransformerTwoslashOptions } from '@shikijs/twoslash/core'
import type { ShikiTransformer } from 'shiki'
import type { VueSpecificOptions } from 'twoslash-vue'
import type { WhitespacePosition } from '../utils/index.js'
import process from 'node:process'
import {
transformerCompactLineOptions,
transformerNotationDiff,
transformerNotationErrorLevel,
transformerNotationFocus,
transformerNotationHighlight,
transformerNotationWordHighlight,
transformerRemoveNotationEscape,
transformerRenderWhitespace,
} from '@shikijs/transformers'
import { defaultTwoslashOptions } from '@shikijs/twoslash/core'
import { addClassToHast } from 'shiki'
import { isPlainObject } from 'vuepress/shared'
import { defaultHoverInfoProcessor, transformerTwoslash } from '../twoslash/rendererTransformer.js'
import { attrsToLines, resolveWhitespacePosition } from '../utils/index.js'
const decorationsRE = /^\/\/ @decorations:(.*)\n/
export const baseTransformers: ShikiTransformer[] = [
transformerNotationDiff({
matchAlgorithm: 'v3',
}),
transformerNotationFocus({
matchAlgorithm: 'v3',
classActiveLine: 'has-focus',
classActivePre: 'has-focused-lines',
}),
transformerNotationHighlight({
matchAlgorithm: 'v3',
}),
transformerNotationErrorLevel({
matchAlgorithm: 'v3',
}),
transformerNotationWordHighlight({
matchAlgorithm: 'v3',
}),
{
name: 'vuepress:add-class',
pre(node) {
addClassToHast(node, 'vp-code')
},
},
{
name: 'vuepress:clean-up',
pre(node) {
delete node.properties.tabindex
delete node.properties.style
},
},
{
name: 'shiki:inline-decorations',
preprocess(code, options) {
code = code.replace(decorationsRE, (match, decorations) => {
options.decorations ||= []
options.decorations.push(...JSON.parse(decorations))
return ''
})
return code
},
},
transformerRemoveNotationEscape(),
]
const vueRE = /-vue$/
export function getInlineTransformers({ attrs, lang, enabledTwoslash, whitespace, twoslash }: {
attrs: string
lang: string
enabledTwoslash: boolean
whitespace: boolean | WhitespacePosition
twoslash?: boolean | TransformerTwoslashOptions['twoslashOptions'] & VueSpecificOptions
}): ShikiTransformer[] {
const vPre = vueRE.test(lang) ? '' : 'v-pre'
const inlineTransformers: ShikiTransformer[] = [
transformerCompactLineOptions(attrsToLines(attrs)),
]
if (enabledTwoslash) {
const { compilerOptions, ...twoslashOptions } = isPlainObject(twoslash) ? twoslash : {}
const defaultOptions = defaultTwoslashOptions()
inlineTransformers.push(transformerTwoslash({
processHoverInfo(info) {
return defaultHoverInfoProcessor(info)
},
twoslashOptions: {
...defaultOptions,
...twoslashOptions,
compilerOptions: {
baseUrl: process.cwd(),
...compilerOptions,
},
},
}))
}
else {
inlineTransformers.push({
name: 'vuepress:v-pre',
pre(node) {
if (vPre)
node.properties['v-pre'] = ''
},
})
}
const position = resolveWhitespacePosition(attrs, whitespace)
if (position)
inlineTransformers.push(transformerRenderWhitespace({ position }))
return inlineTransformers
}

View File

@ -1,6 +0,0 @@
import { shikiPlugin } from './shikiPlugin.js'
export * from './shikiPlugin.js'
export * from './types.js'
export default shikiPlugin

View File

@ -1,61 +0,0 @@
import type { Markdown } from 'vuepress/markdown'
import { resolveCollapsedLines } from '../utils/collapsedLines.js'
export interface MarkdownItCollapsedLinesOptions {
/**
* Whether to collapse code blocks when they exceed a certain number of lines,
*
* - If `number`, collapse starts from line `number`.
* - If `true`, collapse starts from line 15 by default.
* - If `false`, disable collapse.
* @default false
*/
collapsedLines?: boolean | number
}
export function collapsedLinesPlugin(md: Markdown, {
collapsedLines: collapsedLinesOptions = false,
}: MarkdownItCollapsedLinesOptions = {}): void {
const rawFence = md.renderer.rules.fence!
md.renderer.rules.fence = (...args) => {
const [tokens, index] = args
const token = tokens[index]
// get token info
const info = token.info ? md.utils.unescapeAll(token.info).trim() : ''
const code = rawFence(...args)
// resolve collapsed-lines mark from token info
const collapsedLinesInfo
= resolveCollapsedLines(info) ?? collapsedLinesOptions
if (collapsedLinesInfo === false) {
return code
}
const lines
= code.slice(code.indexOf('<code>'), code.indexOf('</code>')).split('\n').length
const startLines
= typeof collapsedLinesInfo === 'number' ? collapsedLinesInfo : 15
if (lines < startLines) {
return code
}
const collapsedLinesCode = `<div class="collapsed-lines"></div>`
const styles = `--vp-collapsed-lines:${startLines};`
const finalCode = code
.replace(/<\/div>$/, `${collapsedLinesCode}</div>`)
.replace(/"(language-[^"]*)"/, '"$1 has-collapsed collapsed"')
.replace(/^<div[^>]*>/, (match) => {
if (!match.includes('style=')) {
return `${match.slice(0, -1)} style="${styles}">`
}
return match.replace(/(style=")/, `$1${styles}`)
})
return finalCode
}
}

View File

@ -1,32 +0,0 @@
// Modified from https://github.com/egoist/markdown-it-highlight-lines
// Now this plugin is only used to normalize line attrs.
// The else part of line highlights logic is in '../highlight.ts'.
import type { Markdown } from 'vuepress/markdown'
const HIGHLIGHT_LINES_REGEXP = /\{([\d,-]+)\}/
export function highlightLinesPlugin(md: Markdown): void {
const rawFence = md.renderer.rules.fence!
md.renderer.rules.fence = (...args) => {
const [tokens, idx] = args
const token = tokens[idx]
let lines: string | null = null
const rawInfo = token.info
const result = rawInfo?.match(HIGHLIGHT_LINES_REGEXP)
if (!result)
return rawFence(...args)
// ensure the next plugin get the correct lang
token.info = rawInfo.replace(HIGHLIGHT_LINES_REGEXP, '').trim()
lines = result[1]
token.info += ` ${lines}`
return rawFence(...args)
}
}

View File

@ -1,4 +0,0 @@
export * from './collapsedLinesPlugin.js'
export * from './highlightLinesPlugin.js'
export * from './lineNumberPlugin.js'
export * from './preWrapperPlugin.js'

View File

@ -1,63 +0,0 @@
// markdown-it plugin for generating line numbers.
// It depends on preWrapper plugin.
import type { Markdown } from 'vuepress/markdown'
import type { LineNumberOptions } from '../types.js'
const LINE_NUMBERS_REGEXP = /:line-numbers\b/
const NO_LINE_NUMBERS_REGEXP = /:no-line-numbers\b/
const LINE_NUMBERS_START_REGEXP = /:line-numbers=(\d+)\b/
export function lineNumberPlugin(md: Markdown, { lineNumbers = true }: LineNumberOptions = {}): void {
const rawFence = md.renderer.rules.fence!
md.renderer.rules.fence = (...args) => {
const rawCode = rawFence(...args)
const [tokens, idx] = args
const info = tokens[idx].info
const enableLineNumbers = LINE_NUMBERS_REGEXP.test(info)
const disableLineNumbers = NO_LINE_NUMBERS_REGEXP.test(info)
if (info.includes('twoslash'))
return rawCode
if (
(!lineNumbers && !enableLineNumbers)
|| (lineNumbers && disableLineNumbers)
) {
return rawCode
}
const code = rawCode.slice(
rawCode.indexOf('<code>'),
rawCode.indexOf('</code>'),
)
const lines = code.split('\n')
if (
typeof lineNumbers === 'number'
&& lines.length < lineNumbers
&& !enableLineNumbers
) {
return rawCode
}
const startNumbers
= Number(info.match(LINE_NUMBERS_START_REGEXP)?.[1] ?? 1) - 1
const lineNumbersStyle = `style="counter-reset:line-number ${startNumbers}"`
const lineNumbersCode = [...Array.from({ length: lines.length })]
.map(() => `<div class="line-number"></div>`)
.join('')
const lineNumbersWrapperCode = `<div class="line-numbers" aria-hidden="true" ${lineNumbersStyle}>${lineNumbersCode}</div>`
const finalCode = rawCode
.replace(/<\/div>$/, `${lineNumbersWrapperCode}</div>`)
.replace(/"(language-[^"]*)"/, '"$1 line-numbers-mode"')
return finalCode
}
}

View File

@ -1,39 +0,0 @@
// markdown-it plugin for generating line numbers.
// v-pre block logic is in `../highlight.ts`
import type { Markdown } from 'vuepress/markdown'
import type { PreWrapperOptions } from '../types.js'
import { resolveAttr, resolveLanguage } from '../utils/index.js'
export function preWrapperPlugin(
md: Markdown,
{ preWrapper = true }: PreWrapperOptions = {},
): void {
const rawFence = md.renderer.rules.fence!
md.renderer.rules.fence = (...args) => {
const [tokens, idx, options] = args
const token = tokens[idx]
// get token info
const info = token.info ? md.utils.unescapeAll(token.info).trim() : ''
const lang = resolveLanguage(info)
const title = resolveAttr(info, 'title') || lang
const classes: string[] = [`${options.langPrefix}${lang}`]
let result = rawFence(...args)
if (!preWrapper) {
// remove `<code>` attributes
result = result.replace(/<code[\s\S]*?>/, '<code>')
result = `<pre class="${classes.join(' ')}"${result.slice('<pre'.length)}`
return result
}
const attrs: string[] = [
`data-ext="${lang}"`,
`data-title="${title}"`,
]
return `<div class="${classes.join(' ')}" ${attrs.join(' ')}>${result}</div>`
}
}

View File

@ -1,40 +0,0 @@
import type { App } from 'vuepress'
import { ensureEndingSlash } from '@vuepress/helper'
import { getDirname, path } from 'vuepress/utils'
const __dirname = getDirname(import.meta.url)
const CLIENT_FOLDER = ensureEndingSlash(
path.resolve(__dirname, '../client'),
)
export async function prepareClientConfigFile(app: App, {
copyCode,
twoslash,
}: { copyCode: boolean, twoslash: boolean }): Promise<string> {
return await app.writeTemp(
'internal/plugin-shiki/client.js',
`\
${twoslash ? `import { enhanceTwoslash } from '${CLIENT_FOLDER}composables/twoslash.js'` : ''}
${copyCode ? `import { useCopyCode } from '${CLIENT_FOLDER}composables/copy-code.js'` : ''}
import { useCollapsedLines } from '${CLIENT_FOLDER}composables/collapsed-lines.js'
export default {
${twoslash
? `enhance({ app }) {
enhanceTwoslash(app)
},`
: ''}
setup() {
${copyCode
? `useCopyCode({
selector: __CC_SELECTOR__,
duration: __CC_DURATION__,
})`
: ''}
useCollapsedLines()
},
}
`,
)
}

View File

@ -1,116 +0,0 @@
import type { Plugin } from 'vuepress/core'
import type {
CopyCodeOptions,
HighlighterOptions,
LineNumberOptions,
PreWrapperOptions,
} from './types.js'
import { addViteOptimizeDepsInclude } from '@vuepress/helper'
import { isPlainObject } from 'vuepress/shared'
import { colors } from 'vuepress/utils'
import { copyCodeButtonPlugin } from './copy-code-button/index.js'
import { highlight, resolveTsPaths, scanLanguages } from './highlight/index.js'
import {
collapsedLinesPlugin,
highlightLinesPlugin,
lineNumberPlugin,
preWrapperPlugin,
} from './markdown/index.js'
import { prepareClientConfigFile } from './prepareClientConfigFile.js'
import { logger } from './utils/index.js'
export interface ShikiPluginOptions
extends HighlighterOptions, LineNumberOptions, PreWrapperOptions {
/**
* Add copy code button
*
* @default true
*/
copyCode?: boolean | CopyCodeOptions
}
export function shikiPlugin({
preWrapper = true,
lineNumbers = true,
copyCode = true,
collapsedLines = false,
...options
}: ShikiPluginOptions = {}): Plugin {
const copyCodeOptions: CopyCodeOptions = isPlainObject(copyCode) ? copyCode : {}
return {
name: '@vuepress-plume/plugin-shikiji',
define: {
__CC_DURATION__: copyCodeOptions.duration ?? 2000,
__CC_SELECTOR__: `div[class*="language-"] > button.${copyCodeOptions.className || 'copy'}`,
},
clientConfigFile: app => prepareClientConfigFile(app, {
copyCode: copyCode !== false,
twoslash: !!options.twoslash,
}),
extendsMarkdown: async (md, app) => {
const start = performance.now()
const theme = options.theme ?? { light: 'github-light', dark: 'github-dark' }
if (!options.languages || !options.languages.length) {
options.languages = await scanLanguages(app)
if (options.languages.length) {
logger.warn(`You have not configured \`${colors.cyan('plugins.shiki.languages')}\`. It has been detected that you are using \`${colors.green(JSON.stringify(options.languages))}\`. Please add it to the \`${colors.cyan('plugins.shiki.languages')}\` configuration.`)
}
if (app.env.isDebug) {
logger.info(`scan languages in: ${(performance.now() - start).toFixed(2)}ms`)
}
}
if (options.twoslash) {
const paths = await resolveTsPaths()
if (paths) {
options.twoslash = isPlainObject(options.twoslash) ? options.twoslash : {}
options.twoslash.compilerOptions ??= {}
options.twoslash.compilerOptions.paths = {
...paths,
...options.twoslash.compilerOptions.paths,
}
}
}
md.options.highlight = await highlight(theme, options)
if (app.env.isDebug) {
logger.info(`highlight Loaded in: ${(performance.now() - start).toFixed(2)}ms`)
}
md.use(highlightLinesPlugin)
md.use(preWrapperPlugin, { preWrapper })
if (preWrapper) {
copyCodeButtonPlugin(md, app, copyCode)
md.use(lineNumberPlugin, { lineNumbers })
md.use(collapsedLinesPlugin, { collapsedLines })
}
},
extendsMarkdownOptions: (options) => {
// 注入 floating-vue 后,需要关闭 代码块 的 v-pre 配置
if ((options as any).vPre !== false) {
const vPre = isPlainObject(options.vPre) ? options.vPre : { block: true }
if (vPre.block) {
options.vPre ??= {}
;(options as any).vPre.block = false
}
}
},
extendsBundlerOptions: (bundlerOptions, app) => {
if (options.twoslash) {
addViteOptimizeDepsInclude(
bundlerOptions,
app,
['floating-vue'],
)
}
},
}
}

View File

@ -1,199 +0,0 @@
import type { RendererRichOptions, TwoslashRenderer } from '@shikijs/twoslash'
import type { Element, ElementContent, Text } from 'hast'
import type { ShikiTransformerContextCommon } from 'shiki'
import { defaultHoverInfoProcessor, rendererRich } from '@shikijs/twoslash'
import { fromMarkdown } from 'mdast-util-from-markdown'
import { gfmFromMarkdown } from 'mdast-util-gfm'
import { defaultHandlers, toHast } from 'mdast-util-to-hast'
export { defaultHoverInfoProcessor }
export interface TwoslashFloatingVueOptions {
classCopyIgnore?: string
classFloatingPanel?: string
classCode?: string
classMarkdown?: string
floatingVueTheme?: string
floatingVueThemeQuery?: string
floatingVueThemeCompletion?: string
}
export interface TwoslashFloatingVueRendererOptions extends RendererRichOptions {
/**
* Class and themes for floating-vue specific nodes
*/
floatingVue?: TwoslashFloatingVueOptions
}
export function rendererFloatingVue(options: TwoslashFloatingVueRendererOptions = {}): TwoslashRenderer {
const {
classCopyIgnore = 'vp-copy-ignore',
classFloatingPanel = 'twoslash-floating',
classCode = 'vp-code',
classMarkdown = 'vp-doc',
floatingVueTheme = 'twoslash',
floatingVueThemeQuery = 'twoslash-query',
floatingVueThemeCompletion = 'twoslash-completion',
} = options.floatingVue || {}
const {
errorRendering = 'line',
} = options
const hoverBasicProps = {
'class': 'twoslash-hover',
'popper-class': ['shiki', classFloatingPanel, classCopyIgnore, classCode].join(' '),
'theme': floatingVueTheme,
}
const rich = rendererRich({
classExtra: classCopyIgnore,
...options,
renderMarkdown,
renderMarkdownInline,
hast: {
hoverToken: {
tagName: 'v-menu',
properties: hoverBasicProps,
},
hoverCompose: compose,
queryToken: {
tagName: 'v-menu',
properties: {
...hoverBasicProps,
':shown': 'true',
'theme': floatingVueThemeQuery,
},
},
queryCompose: compose,
popupDocs: {
class: `twoslash-popup-docs ${classMarkdown}`,
},
popupDocsTags: {
class: `twoslash-popup-docs twoslash-popup-docs-tags ${classMarkdown}`,
},
popupError: {
class: `twoslash-popup-error ${classMarkdown}`,
},
errorToken: errorRendering === 'line'
? undefined
: {
tagName: 'v-menu',
properties: {
...hoverBasicProps,
class: 'twoslash-error twoslash-error-hover',
},
},
errorCompose: compose,
completionCompose({ popup, cursor }) {
return [
<Element>{
type: 'element',
tagName: 'v-menu',
properties: {
'popper-class': ['shiki twoslash-completion', classCopyIgnore, classFloatingPanel],
'theme': floatingVueThemeCompletion,
':shown': 'true',
},
children: [
cursor,
{
type: 'element',
tagName: 'template',
properties: {
'v-slot:popper': '{}',
},
content: {
type: 'root',
children: [vPre(popup)],
},
},
],
},
]
},
},
})
return rich
}
function compose(parts: { token: Element | Text, popup: Element }): Element[] {
return [
{
type: 'element',
tagName: 'span',
properties: {},
children: [parts.token],
},
{
type: 'element',
tagName: 'template',
properties: {
'v-slot:popper': '{}',
},
content: {
type: 'root',
children: [vPre(parts.popup)],
},
children: [],
},
]
}
function vPre<T extends ElementContent>(el: T): T {
if (el.type === 'element') {
el.properties = el.properties || {}
el.properties['v-pre'] = ''
}
return el
}
function renderMarkdown(this: ShikiTransformerContextCommon, md: string): ElementContent[] {
const mdast = fromMarkdown(
md.replace(/\{@link ([^}]*)\}/g, '$1'), // replace jsdoc links
{ mdastExtensions: [gfmFromMarkdown()] },
)
return (toHast(
mdast,
{
handlers: {
code: (state, node) => {
const lang = node.lang || ''
if (lang) {
return <Element>{
type: 'element',
tagName: 'div',
properties: {
'class': `language-${lang}`,
'data-ext': lang,
},
children: this.codeToHast(
node.value,
{
...this.options,
transformers: [],
lang,
structure: node.value.trim().includes('\n') ? 'classic' : 'inline',
},
).children,
}
}
return defaultHandlers.code(state, node)
},
},
},
) as Element).children
}
function renderMarkdownInline(this: ShikiTransformerContextCommon, md: string, context?: string): ElementContent[] {
if (context === 'tag:param')
md = md.replace(/^([\w$-]+)/, '`$1` ')
const children = renderMarkdown.call(this, md)
if (children.length === 1 && children[0].type === 'element' && children[0].tagName === 'p')
return children[0].children
return children
}

View File

@ -1,84 +0,0 @@
import type { TransformerTwoslashOptions } from '@shikijs/twoslash/core'
import type { ShikiTransformer } from 'shiki'
import type { VueSpecificOptions } from 'twoslash-vue'
import type { TwoslashFloatingVueRendererOptions } from './renderer-floating-vue.js'
import process from 'node:process'
import { createTransformerFactory } from '@shikijs/twoslash/core'
import { removeTwoslashNotations } from 'twoslash'
import { createTwoslasher } from 'twoslash-vue'
import { rendererFloatingVue } from './renderer-floating-vue.js'
export * from './renderer-floating-vue.js'
interface TransformerTwoslashVueOptions extends TransformerTwoslashOptions {
twoslashOptions?: TransformerTwoslashOptions['twoslashOptions'] & VueSpecificOptions
}
export interface VuePressTwoslashOptions extends TransformerTwoslashVueOptions, TwoslashFloatingVueRendererOptions {
/**
* Requires adding `twoslash` to the code block explicitly to run twoslash
* @default true
*/
explicitTrigger?: TransformerTwoslashOptions['explicitTrigger']
}
/**
* Create a Shiki transformer for VuePress to enable twoslash integration
*/
export function transformerTwoslash(options: VuePressTwoslashOptions = {}): ShikiTransformer {
const {
explicitTrigger = true,
} = options
const onError = (error: any, code: string) => {
const isCI = typeof process !== 'undefined' && process?.env?.CI
const isDev = typeof process !== 'undefined' && process?.env?.NODE_ENV === 'development'
const shouldThrow = (options.throws || isCI || !isDev) && options.throws !== false
console.error(`\n\n--------\nTwoslash error in code:\n--------\n${code.split(/\n/g).slice(0, 15).join('\n').trim()}\n--------\n`)
if (shouldThrow)
throw error
else
console.error(error)
return removeTwoslashNotations(code)
}
const twoslash = createTransformerFactory(
createTwoslasher(options.twoslashOptions),
)({
langs: ['ts', 'tsx', 'js', 'jsx', 'json', 'vue'],
renderer: rendererFloatingVue(options),
onTwoslashError: onError,
onShikiError: onError,
...options,
explicitTrigger,
})
const trigger = explicitTrigger instanceof RegExp
? explicitTrigger
: /\btwoslash\b/
return {
...twoslash,
name: '@shiki/vuepress-twoslash',
preprocess(code, options) {
const cleanup = options.transformers?.find(i => i.name === 'vuepress:clean-up')
if (cleanup)
options.transformers?.splice(options.transformers.indexOf(cleanup), 1)
// Disable v-pre for twoslash, because we need render it with FloatingVue
if (!explicitTrigger || options.meta?.__raw?.match(trigger)) {
const vPre = options.transformers?.find(i => i.name === 'vuepress:v-pre')
if (vPre)
options.transformers?.splice(options.transformers.indexOf(vPre), 1)
}
return twoslash.preprocess!.call(this, code, options)
},
postprocess(html) {
if (this.meta.twoslash)
return html.replace(/\{/g, '&#123;')
return html
},
}
}

View File

@ -1,155 +0,0 @@
import type { TransformerTwoslashOptions } from '@shikijs/twoslash/core'
import type {
BuiltinTheme,
BundledLanguage,
Highlighter,
LanguageInput,
ShikiTransformer,
SpecialLanguage,
StringLiteralUnion,
ThemeRegistration,
} from 'shiki'
import type { VueSpecificOptions } from 'twoslash-vue'
import type { LocaleConfig } from 'vuepress/shared'
export type ShikiLang =
| LanguageInput
| StringLiteralUnion<BundledLanguage>
| SpecialLanguage
export type ThemeOptions =
| ThemeRegistration
| BuiltinTheme
| {
light: ThemeRegistration | BuiltinTheme
dark: ThemeRegistration | BuiltinTheme
}
export interface HighlighterOptions {
/**
* Custom theme for syntax highlighting.
*
* You can also pass an object with `light` and `dark` themes to support dual themes.
*
* You can use an existing theme.
*
* @see https://shiki.style/themes
*
* Or add your own theme.
*
* @see https://shiki.style/guide/load-theme
*
* @example { theme: 'github-dark' }
* @example { theme: { light: 'github-light', dark: 'github-dark' } }
*
*/
theme?: ThemeOptions
/**
* Languages for syntax highlighting.
* @see https://shiki.style/languages
*/
languages?: ShikiLang[]
/**
* Custom language aliases.
*
* @example { 'my-lang': 'js' }
* @see https://shiki.style/guide/load-lang#custom-language-aliases
*/
languageAlias?: Record<string, string>
/**
* Setup Shikiji instance
*/
shikiSetup?: (shiki: Highlighter) => void | Promise<void>
/**
* Fallback language when the specified language is not available.
*/
defaultHighlightLang?: string
/**
* Transformers applied to code blocks
* @see https://shiki.style/guide/transformers
*/
codeTransformers?: ShikiTransformer[]
/**
* @experiment
* Enable transformerTwoslash
* @default false
*/
twoslash?: boolean | TransformerTwoslashOptions['twoslashOptions'] & VueSpecificOptions
/**
* Enable transformerRenderWhitespace
* @default false
*/
whitespace?: boolean | 'all' | 'boundary' | 'trailing'
}
export interface LineNumberOptions {
/**
* Show line numbers in code blocks
* @default true
*/
lineNumbers?: boolean | number
}
export interface PreWrapperOptions {
/**
* Wrap the `<pre>` tag with an extra `<div>` or not. Do not disable it unless you
* understand what's it for
*
* - Required for `lineNumbers`
* - Required for title display of default theme
*/
preWrapper?: boolean
/**
* Hide extra rows when exceeding a specific number of lines.
*
* `true` is equivalent to `15` .
*
* @default false
*/
collapsedLines?: number | boolean
}
/**
* Options for copy code button
*
* `<button title="{title}" class="{className}"></button>`
*/
export interface CopyCodeOptions {
/**
* Class name of the button
*
* @default 'copy'
*/
className?: string
/**
* Duration of the copied text
*
* @default 2000
*/
duration?: number
/**
* Locale config for copy code button
*/
locales?: LocaleConfig<CopyCodeLocaleOptions>
}
export interface CopyCodeLocaleOptions {
/**
* Title of the button
*
* @default 'Copy code'
*/
title?: string
/**
* Copied text
*
* @default 'Copied!'
*/
copied?: string
}

View File

@ -1,38 +0,0 @@
import type { TransformerCompactLineOption } from '@shikijs/transformers'
/**
* 2 steps:
*
* 1. convert attrs into line numbers:
* {4,7-13,16,23-27,40} -> [4,7,8,9,10,11,12,13,16,23,24,25,26,27,40]
* 2. convert line numbers into line options:
* [{ line: number, classes: string[] }]
*/
export function attrsToLines(attrs: string): TransformerCompactLineOption[] {
// eslint-disable-next-line regexp/optimal-quantifier-concatenation, regexp/no-super-linear-backtracking
attrs = attrs.replace(/^(?:\[.*?\])?.*?([\d,-]+).*/, '$1').trim()
const result: number[] = []
if (!attrs)
return []
attrs
.split(',')
.map(v => v.split('-').map(v => Number.parseInt(v, 10)))
.forEach(([start, end]) => {
if (start && end) {
result.push(
...Array.from({ length: end - start + 1 }, (_, i) => start + i),
)
}
else {
result.push(start)
}
})
return result.map(line => ({
line,
classes: ['highlighted'],
}))
}

View File

@ -1,24 +0,0 @@
const COLLAPSED_LINES_REGEXP = /:collapsed-lines\b/
const COLLAPSED_LINES_START_REGEXP = /:collapsed-lines=(\d+)\b/
const NO_COLLAPSED_LINES_REGEXP = /:no-collapsed-lines\b/
/**
* Resolve the `:collapsed-lines` `:collapsed-lines=num` / `:no-collapsed-lines` mark from token info
*/
export function resolveCollapsedLines(info: string): boolean | number | null {
const lines = COLLAPSED_LINES_START_REGEXP.exec(info)?.[1]
if (lines) {
return Number(lines)
}
if (COLLAPSED_LINES_REGEXP.test(info)) {
return true
}
if (NO_COLLAPSED_LINES_REGEXP.test(info)) {
return false
}
return null
}

View File

@ -1,10 +0,0 @@
import { Logger } from '@vuepress/helper'
export const logger = new Logger('@vuepress-plume/plugin-shikiji')
export * from './attrsToLines.js'
export * from './collapsedLines.js'
export * from './lru.js'
export * from './resolveAttr.js'
export * from './resolveLanguage.js'
export * from './whitespace.js'

View File

@ -1,39 +0,0 @@
// adapted from https://stackoverflow.com/a/46432113/11613622
export class LRUCache<K, V> {
private max: number
private cache: Map<K, V>
constructor(max: number = 10) {
this.max = max
this.cache = new Map<K, V>()
}
get(key: K): V | undefined {
const item = this.cache.get(key)
if (item !== undefined) {
// refresh key
this.cache.delete(key)
this.cache.set(key, item)
}
return item
}
set(key: K, val: V): void {
// refresh key
if (this.cache.has(key))
this.cache.delete(key)
// evict oldest
else if (this.cache.size === this.max)
this.cache.delete(this.first()!)
this.cache.set(key, val)
}
first(): K | undefined {
return this.cache.keys().next().value
}
clear(): void {
this.cache.clear()
}
}

View File

@ -1,9 +0,0 @@
export function resolveAttr(info: string, attr: string): string | null {
// try to match specified attr mark
const pattern = `\\b${attr}\\s*=\\s*(?<quote>['"])(?<content>.+?)\\k<quote>(\\s|$)`
const regex = new RegExp(pattern, 'i')
const match = info.match(regex)
// return content if matched, null if not specified
return match?.groups?.content ?? null
}

View File

@ -1,43 +0,0 @@
const RE_ATTR_VALUE = /(?:^|\s+)(?<attr>[\w-]+)(?:=\s*(?<quote>['"])(?<value>.+?)\k<quote>)?(?:\s+|$)/
const RE_CODE_BLOCKS = /^[\w\-]*(\s*:[\w\-]*)?(\s*\{[\w\-,\s]+\})?\s*/
export function resolveAttrs(info: string): {
attrs: Record<string, string | boolean>
rawAttrs: string
} {
if (!info)
return { rawAttrs: '', attrs: {} }
info = info.replace(RE_CODE_BLOCKS, '').trim()
if (!info)
return { rawAttrs: '', attrs: {} }
const attrs: Record<string, string | boolean> = {}
const rawAttrs = info
let matched: RegExpMatchArray | null
// eslint-disable-next-line no-cond-assign
while (matched = info.match(RE_ATTR_VALUE)) {
const { attr, value } = matched.groups || {}
attrs[attr] = value ?? true
info = info.slice(matched[0].length)
}
Object.keys(attrs).forEach((key) => {
let value = attrs[key]
value = typeof value === 'string' ? value.trim() : value
if (value === 'true')
value = true
else if (value === 'false')
value = false
attrs[key] = value
if (key.includes('-')) {
const _key = key.replace(/-(\w)/g, (_, c) => c.toUpperCase())
attrs[_key] = value
}
})
return { attrs, rawAttrs }
}

View File

@ -1,8 +0,0 @@
const VUE_RE = /-vue$/
export function resolveLanguage(info: string): string {
return info
.match(/^([^ :[{]+)/)?.[1]
?.replace(VUE_RE, '')
.toLowerCase() ?? ''
}

View File

@ -1,18 +0,0 @@
export const WHITESPACE_REGEXP = /:whitespace(?:=(all|boundary|trailing)?)?\b/
export const NO_WHITESPACE_REGEXP = /:no-whitespace\b/
export type WhitespacePosition = 'all' | 'boundary' | 'trailing'
export function resolveWhitespacePosition(info: string, defaultPosition?: boolean | WhitespacePosition): WhitespacePosition | false {
if (NO_WHITESPACE_REGEXP.test(info)) {
return false
}
const position = defaultPosition === true ? undefined : defaultPosition
const match = info.match(WHITESPACE_REGEXP)
if (match) {
return (match[1] || position || 'all') as WhitespacePosition
}
return defaultPosition === true ? 'all' : defaultPosition ?? false
}

View File

@ -1,39 +0,0 @@
import type { Options } from 'tsup'
import { defineConfig } from 'tsup'
import { argv } from '../../scripts/tsup-args.js'
export default defineConfig(() => {
const DEFAULT_OPTIONS: Options = {
dts: true,
sourcemap: false,
splitting: false,
format: 'esm',
}
const options: Options[] = []
if (argv.node) {
options.push({
...DEFAULT_OPTIONS,
entry: ['./src/node/index.ts'],
outDir: './lib/node',
target: 'node18',
})
}
if (argv.client) {
options.push(...[
// client/composables/
{
...DEFAULT_OPTIONS,
entry: [
'copy-code.ts',
'twoslash.ts',
'collapsed-lines.ts',
].map(file => `./src/client/composables/${file}`),
outDir: './lib/client/composables',
external: [/.*\.css$/],
},
])
}
return options
})

190
pnpm-lock.yaml generated
View File

@ -21,6 +21,9 @@ catalogs:
'@vuepress/plugin-comment':
specifier: 2.0.0-rc.79
version: 2.0.0-rc.79
'@vuepress/plugin-copy-code':
specifier: 2.0.0-rc.79
version: 2.0.0-rc.79
'@vuepress/plugin-docsearch':
specifier: 2.0.0-rc.79
version: 2.0.0-rc.79
@ -51,12 +54,18 @@ catalogs:
'@vuepress/plugin-seo':
specifier: 2.0.0-rc.79
version: 2.0.0-rc.79
'@vuepress/plugin-shiki':
specifier: 2.0.0-rc.79
version: 2.0.0-rc.79
'@vuepress/plugin-sitemap':
specifier: 2.0.0-rc.79
version: 2.0.0-rc.79
'@vuepress/plugin-watermark':
specifier: 2.0.0-rc.79
version: 2.0.0-rc.79
'@vuepress/shiki-twoslash':
specifier: 2.0.0-rc.79
version: 2.0.0-rc.79
'@vueuse/core':
specifier: ^12.7.0
version: 12.7.0
@ -409,54 +418,6 @@ importers:
specifier: 'catalog:'
version: 2.0.0-rc.20(@vuepress/bundler-vite@2.0.0-rc.20(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.2)(sass-embedded@1.85.1)(sass@1.85.1)(stylus@0.64.0)(typescript@5.8.2)(yaml@2.7.0))(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2))
plugins/plugin-shikiji:
dependencies:
'@shikijs/transformers':
specifier: ^3.1.0
version: 3.1.0
'@shikijs/twoslash':
specifier: ^3.1.0
version: 3.1.0(typescript@5.8.2)
'@types/hast':
specifier: ^3.0.4
version: 3.0.4
'@vuepress/helper':
specifier: 'catalog:'
version: 2.0.0-rc.79(typescript@5.8.2)(vuepress@2.0.0-rc.20(@vuepress/bundler-vite@2.0.0-rc.20(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.2)(sass-embedded@1.85.1)(sass@1.85.1)(stylus@0.64.0)(typescript@5.8.2)(yaml@2.7.0))(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2)))
'@vueuse/core':
specifier: 'catalog:'
version: 12.7.0(typescript@5.8.2)
fast-glob:
specifier: 'catalog:'
version: 3.3.3
floating-vue:
specifier: ^5.2.2
version: 5.2.2(vue@3.5.13(typescript@5.8.2))
mdast-util-from-markdown:
specifier: ^2.0.2
version: 2.0.2
mdast-util-gfm:
specifier: ^3.1.0
version: 3.1.0
mdast-util-to-hast:
specifier: ^13.2.0
version: 13.2.0
nanoid:
specifier: 'catalog:'
version: 5.1.2
shiki:
specifier: ^3.1.0
version: 3.1.0
twoslash:
specifier: ^0.3.1
version: 0.3.1(typescript@5.8.2)
twoslash-vue:
specifier: ^0.3.1
version: 0.3.1(typescript@5.8.2)
vuepress:
specifier: 'catalog:'
version: 2.0.0-rc.20(@vuepress/bundler-vite@2.0.0-rc.20(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.2)(sass-embedded@1.85.1)(sass@1.85.1)(stylus@0.64.0)(typescript@5.8.2)(yaml@2.7.0))(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2))
theme:
dependencies:
'@iconify/utils':
@ -474,9 +435,6 @@ importers:
'@vuepress-plume/plugin-search':
specifier: workspace:*
version: link:../plugins/plugin-search
'@vuepress-plume/plugin-shikiji':
specifier: workspace:*
version: link:../plugins/plugin-shikiji
'@vuepress/helper':
specifier: 'catalog:'
version: 2.0.0-rc.79(typescript@5.8.2)(vuepress@2.0.0-rc.20(@vuepress/bundler-vite@2.0.0-rc.20(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.2)(sass-embedded@1.85.1)(sass@1.85.1)(stylus@0.64.0)(typescript@5.8.2)(yaml@2.7.0))(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2)))
@ -486,6 +444,9 @@ importers:
'@vuepress/plugin-comment':
specifier: 'catalog:'
version: 2.0.0-rc.79(typescript@5.8.2)(vuepress@2.0.0-rc.20(@vuepress/bundler-vite@2.0.0-rc.20(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.2)(sass-embedded@1.85.1)(sass@1.85.1)(stylus@0.64.0)(typescript@5.8.2)(yaml@2.7.0))(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2)))
'@vuepress/plugin-copy-code':
specifier: 'catalog:'
version: 2.0.0-rc.79(typescript@5.8.2)(vuepress@2.0.0-rc.20(@vuepress/bundler-vite@2.0.0-rc.20(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.2)(sass-embedded@1.85.1)(sass@1.85.1)(stylus@0.64.0)(typescript@5.8.2)(yaml@2.7.0))(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2)))
'@vuepress/plugin-docsearch':
specifier: 'catalog:'
version: 2.0.0-rc.79(@algolia/client-search@5.18.0)(search-insights@2.17.3)(typescript@5.8.2)(vuepress@2.0.0-rc.20(@vuepress/bundler-vite@2.0.0-rc.20(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.2)(sass-embedded@1.85.1)(sass@1.85.1)(stylus@0.64.0)(typescript@5.8.2)(yaml@2.7.0))(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2)))
@ -516,12 +477,18 @@ importers:
'@vuepress/plugin-seo':
specifier: 'catalog:'
version: 2.0.0-rc.79(typescript@5.8.2)(vuepress@2.0.0-rc.20(@vuepress/bundler-vite@2.0.0-rc.20(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.2)(sass-embedded@1.85.1)(sass@1.85.1)(stylus@0.64.0)(typescript@5.8.2)(yaml@2.7.0))(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2)))
'@vuepress/plugin-shiki':
specifier: 'catalog:'
version: 2.0.0-rc.79(@vuepress/shiki-twoslash@2.0.0-rc.79(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2))(vuepress@2.0.0-rc.20(@vuepress/bundler-vite@2.0.0-rc.20(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.2)(sass-embedded@1.85.1)(sass@1.85.1)(stylus@0.64.0)(typescript@5.8.2)(yaml@2.7.0))(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2))))(@vueuse/core@12.7.0(typescript@5.8.2))(typescript@5.8.2)(vuepress@2.0.0-rc.20(@vuepress/bundler-vite@2.0.0-rc.20(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.2)(sass-embedded@1.85.1)(sass@1.85.1)(stylus@0.64.0)(typescript@5.8.2)(yaml@2.7.0))(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2)))
'@vuepress/plugin-sitemap':
specifier: 'catalog:'
version: 2.0.0-rc.79(typescript@5.8.2)(vuepress@2.0.0-rc.20(@vuepress/bundler-vite@2.0.0-rc.20(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.2)(sass-embedded@1.85.1)(sass@1.85.1)(stylus@0.64.0)(typescript@5.8.2)(yaml@2.7.0))(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2)))
'@vuepress/plugin-watermark':
specifier: 'catalog:'
version: 2.0.0-rc.79(typescript@5.8.2)(vuepress@2.0.0-rc.20(@vuepress/bundler-vite@2.0.0-rc.20(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.2)(sass-embedded@1.85.1)(sass@1.85.1)(stylus@0.64.0)(typescript@5.8.2)(yaml@2.7.0))(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2)))
'@vuepress/shiki-twoslash':
specifier: 'catalog:'
version: 2.0.0-rc.79(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2))(vuepress@2.0.0-rc.20(@vuepress/bundler-vite@2.0.0-rc.20(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.2)(sass-embedded@1.85.1)(sass@1.85.1)(stylus@0.64.0)(typescript@5.8.2)(yaml@2.7.0))(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2)))
'@vueuse/core':
specifier: 'catalog:'
version: 12.7.0(typescript@5.8.2)
@ -2227,8 +2194,8 @@ packages:
'@vue/devtools-shared@7.7.2':
resolution: {integrity: sha512-uBFxnp8gwW2vD6FrJB8JZLUzVb6PNRG0B0jBnHsOH8uKyva2qINY8PTF5Te4QlTbMDqU5K6qtJDr6cNsKWhbOA==}
'@vue/language-core@2.2.4':
resolution: {integrity: sha512-eGGdw7eWUwdIn9Fy/irJ7uavCGfgemuHQABgJ/hU1UgZFnbTg9VWeXvHQdhY+2SPQZWJqWXvRWIg67t4iWEa+Q==}
'@vue/language-core@2.1.10':
resolution: {integrity: sha512-DAI289d0K3AB5TUG3xDp9OuQ71CnrujQwJrQnfuZDwo6eGNf0UoRlPuaVNO+Zrn65PC3j0oB2i7mNmVPggeGeQ==}
peerDependencies:
typescript: '*'
peerDependenciesMeta:
@ -2273,6 +2240,15 @@ packages:
peerDependencies:
vuepress: 2.0.0-rc.20
'@vuepress/highlighter-helper@2.0.0-rc.79':
resolution: {integrity: sha512-zfNZT2bmBLcYxm7TscW7J5KBDdW8MIrnIr+eclym1L5aQL0+oLrc+WtACctJ+TkkF7ZYBKv3U30piLUMfHPwCg==}
peerDependencies:
'@vueuse/core': ^12.7.0
vuepress: 2.0.0-rc.20
peerDependenciesMeta:
'@vueuse/core':
optional: true
'@vuepress/markdown@2.0.0-rc.20':
resolution: {integrity: sha512-Q/zsW9Kp1BDsLaTxP6J9sVUtH8FfIYkEb6vMT8HHhNvEcIcoLBQRWMZp0VG3yuYRW5sMzjMU5IPD3BdSi52ayw==}
@ -2296,6 +2272,11 @@ packages:
twikoo:
optional: true
'@vuepress/plugin-copy-code@2.0.0-rc.79':
resolution: {integrity: sha512-Jub18jjYW+EFPqxQfaG0BEL9Yg29IQV/H+nyjTSBWSR49ZJl5um+3H7ZwOLDzXDB1fv4ZIXYXIEfEybklqBXAg==}
peerDependencies:
vuepress: 2.0.0-rc.20
'@vuepress/plugin-docsearch@2.0.0-rc.79':
resolution: {integrity: sha512-9f9CNu0zYsw6pF550/7/S5PvLLH+EXUw7Ni1ugKR/jZB54opNJX+1XAvYc6xWZC/7DNcYH96N7zOQGnbEj9JEg==}
peerDependencies:
@ -2368,6 +2349,15 @@ packages:
peerDependencies:
vuepress: 2.0.0-rc.20
'@vuepress/plugin-shiki@2.0.0-rc.79':
resolution: {integrity: sha512-QpbNJwrTABjD+jVB/yPaU4M89dwaWRUFp0Keofl6p494QRbDc3vLsots6SXE/dqPU7gXoF5ujg5XyyrjM6t8kw==}
peerDependencies:
'@vuepress/shiki-twoslash': 2.0.0-rc.79
vuepress: 2.0.0-rc.20
peerDependenciesMeta:
'@vuepress/shiki-twoslash':
optional: true
'@vuepress/plugin-sitemap@2.0.0-rc.79':
resolution: {integrity: sha512-g0wrWNOCoah0aNPnPlKESbBkCecFuwnQ0ddCAnopHs6ctnMqU3OR6vsyG1/z09aPZtfHZGS+yv64eHEHYu41Pg==}
peerDependencies:
@ -2381,6 +2371,11 @@ packages:
'@vuepress/shared@2.0.0-rc.20':
resolution: {integrity: sha512-fMCJxO9tqEGZJ85cYLz4pIP6TnUpC7kUgGJtpSGivro0NA7tqTVv4MVQwQ5J3w4YkQfEJirhlAYEOTrlols52Q==}
'@vuepress/shiki-twoslash@2.0.0-rc.79':
resolution: {integrity: sha512-b0pPS6C6hfc6bpfr/ZTJ/ZBX+tiz8uDniYUQDB0SSGKO6lZr9jcSB3RX7ho9pftlskeBdnAVP8gVHpxdpichhg==}
peerDependencies:
vuepress: 2.0.0-rc.20
'@vuepress/utils@2.0.0-rc.20':
resolution: {integrity: sha512-X3KL2tQrmrnyzQeQhIx7E9j0ssvfddLNrEu8pqUYevuYH3xrnrIT5XBNiTqvnDEFYDYcD2R5gFBCGtLs3uYo6g==}
@ -2461,8 +2456,8 @@ packages:
resolution: {integrity: sha512-/tfpK2A4FpS0o+S78o3YSdlqXr0MavJIDlFK3XZrlXLy7vaRXJvW5jYg3v5e/wCaF8y0IpMjkYLhoV6QqfpOgw==}
engines: {node: '>= 14.0.0'}
alien-signals@1.0.4:
resolution: {integrity: sha512-DJqqQD3XcsaQcQ1s+iE2jDUZmmQpXwHiR6fCAim/w87luaW+vmLY8fMlrdkmRwzaFXhkxf3rqPCR59tKVv1MDw==}
alien-signals@0.2.2:
resolution: {integrity: sha512-cZIRkbERILsBOXTQmMrxc9hgpxglstn69zm+F1ARf4aPAzdAFYd6sBq87ErO0Fj3DV94tglcyHG5kQz9nDC/8A==}
ansi-escapes@4.3.2:
resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==}
@ -6102,13 +6097,21 @@ packages:
typescript:
optional: true
twoslash-protocol@0.2.12:
resolution: {integrity: sha512-5qZLXVYfZ9ABdjqbvPc4RWMr7PrpPaaDSeaYY55vl/w1j6H6kzsWK/urAEIXlzYlyrFmyz1UbwIt+AA0ck+wbg==}
twoslash-protocol@0.3.1:
resolution: {integrity: sha512-BMePTL9OkuNISSyyMclBBhV2s9++DiOCyhhCoV5Kaht6eaWLwVjCCUJHY33eZJPsyKeZYS8Wzz0h+XI01VohVw==}
twoslash-vue@0.3.1:
resolution: {integrity: sha512-9/PS0/iL2m8G6N2ILdI18sZ8l6ex+W2nN5jIaTpfFPlnY0MOX2G5UxEVs+AuNimM9SwEnwfiIuDY9ubDCIQpSQ==}
twoslash-vue@0.2.12:
resolution: {integrity: sha512-kxH60DLn2QBcN2wjqxgMDkyRgmPXsytv7fJIlsyFMDPSkm1/lMrI/UMrNAshNaRHcI+hv8x3h/WBgcvlb2RNAQ==}
peerDependencies:
typescript: ^5.5.0
typescript: '*'
twoslash@0.2.12:
resolution: {integrity: sha512-tEHPASMqi7kqwfJbkk7hc/4EhlrKCSLcur+TcvYki3vhIfaRMXnXjaYFgXpoZRbT6GdprD4tGuVBEmTpUgLBsw==}
peerDependencies:
typescript: '*'
twoslash@0.3.1:
resolution: {integrity: sha512-OGqMTGvqXTcb92YQdwGfEdK0nZJA64Aj/ChLOelbl3TfYch2IoBST0Yx4C0LQ7Lzyqm9RpgcpgDxeXQIz4p2Kg==}
@ -8277,13 +8280,13 @@ snapshots:
dependencies:
rfdc: 1.4.1
'@vue/language-core@2.2.4(typescript@5.8.2)':
'@vue/language-core@2.1.10(typescript@5.8.2)':
dependencies:
'@volar/language-core': 2.4.11
'@vue/compiler-dom': 3.5.13
'@vue/compiler-vue2': 2.7.16
'@vue/shared': 3.5.13
alien-signals: 1.0.4
alien-signals: 0.2.2
minimatch: 9.0.5
muggle-string: 0.4.1
path-browserify: 1.0.1
@ -8447,6 +8450,12 @@ snapshots:
transitivePeerDependencies:
- typescript
'@vuepress/highlighter-helper@2.0.0-rc.79(@vueuse/core@12.7.0(typescript@5.8.2))(vuepress@2.0.0-rc.20(@vuepress/bundler-vite@2.0.0-rc.20(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.2)(sass-embedded@1.85.1)(sass@1.85.1)(stylus@0.64.0)(typescript@5.8.2)(yaml@2.7.0))(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2)))':
dependencies:
vuepress: 2.0.0-rc.20(@vuepress/bundler-vite@2.0.0-rc.20(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.2)(sass-embedded@1.85.1)(sass@1.85.1)(stylus@0.64.0)(typescript@5.8.2)(yaml@2.7.0))(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2))
optionalDependencies:
'@vueuse/core': 12.7.0(typescript@5.8.2)
'@vuepress/markdown@2.0.0-rc.20':
dependencies:
'@mdit-vue/plugin-component': 2.1.3
@ -8484,6 +8493,15 @@ snapshots:
transitivePeerDependencies:
- typescript
'@vuepress/plugin-copy-code@2.0.0-rc.79(typescript@5.8.2)(vuepress@2.0.0-rc.20(@vuepress/bundler-vite@2.0.0-rc.20(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.2)(sass-embedded@1.85.1)(sass@1.85.1)(stylus@0.64.0)(typescript@5.8.2)(yaml@2.7.0))(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2)))':
dependencies:
'@vuepress/helper': 2.0.0-rc.79(typescript@5.8.2)(vuepress@2.0.0-rc.20(@vuepress/bundler-vite@2.0.0-rc.20(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.2)(sass-embedded@1.85.1)(sass@1.85.1)(stylus@0.64.0)(typescript@5.8.2)(yaml@2.7.0))(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2)))
'@vueuse/core': 12.7.0(typescript@5.8.2)
vue: 3.5.13(typescript@5.8.2)
vuepress: 2.0.0-rc.20(@vuepress/bundler-vite@2.0.0-rc.20(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.2)(sass-embedded@1.85.1)(sass@1.85.1)(stylus@0.64.0)(typescript@5.8.2)(yaml@2.7.0))(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2))
transitivePeerDependencies:
- typescript
'@vuepress/plugin-docsearch@2.0.0-rc.79(@algolia/client-search@5.18.0)(search-insights@2.17.3)(typescript@5.8.2)(vuepress@2.0.0-rc.20(@vuepress/bundler-vite@2.0.0-rc.20(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.2)(sass-embedded@1.85.1)(sass@1.85.1)(stylus@0.64.0)(typescript@5.8.2)(yaml@2.7.0))(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2)))':
dependencies:
'@docsearch/css': 3.9.0
@ -8602,6 +8620,21 @@ snapshots:
transitivePeerDependencies:
- typescript
'@vuepress/plugin-shiki@2.0.0-rc.79(@vuepress/shiki-twoslash@2.0.0-rc.79(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2))(vuepress@2.0.0-rc.20(@vuepress/bundler-vite@2.0.0-rc.20(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.2)(sass-embedded@1.85.1)(sass@1.85.1)(stylus@0.64.0)(typescript@5.8.2)(yaml@2.7.0))(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2))))(@vueuse/core@12.7.0(typescript@5.8.2))(typescript@5.8.2)(vuepress@2.0.0-rc.20(@vuepress/bundler-vite@2.0.0-rc.20(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.2)(sass-embedded@1.85.1)(sass@1.85.1)(stylus@0.64.0)(typescript@5.8.2)(yaml@2.7.0))(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2)))':
dependencies:
'@shikijs/transformers': 3.1.0
'@vuepress/helper': 2.0.0-rc.79(typescript@5.8.2)(vuepress@2.0.0-rc.20(@vuepress/bundler-vite@2.0.0-rc.20(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.2)(sass-embedded@1.85.1)(sass@1.85.1)(stylus@0.64.0)(typescript@5.8.2)(yaml@2.7.0))(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2)))
'@vuepress/highlighter-helper': 2.0.0-rc.79(@vueuse/core@12.7.0(typescript@5.8.2))(vuepress@2.0.0-rc.20(@vuepress/bundler-vite@2.0.0-rc.20(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.2)(sass-embedded@1.85.1)(sass@1.85.1)(stylus@0.64.0)(typescript@5.8.2)(yaml@2.7.0))(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2)))
nanoid: 5.1.2
shiki: 3.1.0
synckit: 0.9.2
vuepress: 2.0.0-rc.20(@vuepress/bundler-vite@2.0.0-rc.20(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.2)(sass-embedded@1.85.1)(sass@1.85.1)(stylus@0.64.0)(typescript@5.8.2)(yaml@2.7.0))(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2))
optionalDependencies:
'@vuepress/shiki-twoslash': 2.0.0-rc.79(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2))(vuepress@2.0.0-rc.20(@vuepress/bundler-vite@2.0.0-rc.20(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.2)(sass-embedded@1.85.1)(sass@1.85.1)(stylus@0.64.0)(typescript@5.8.2)(yaml@2.7.0))(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2)))
transitivePeerDependencies:
- '@vueuse/core'
- typescript
'@vuepress/plugin-sitemap@2.0.0-rc.79(typescript@5.8.2)(vuepress@2.0.0-rc.20(@vuepress/bundler-vite@2.0.0-rc.20(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.2)(sass-embedded@1.85.1)(sass@1.85.1)(stylus@0.64.0)(typescript@5.8.2)(yaml@2.7.0))(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2)))':
dependencies:
'@vuepress/helper': 2.0.0-rc.79(typescript@5.8.2)(vuepress@2.0.0-rc.20(@vuepress/bundler-vite@2.0.0-rc.20(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.2)(sass-embedded@1.85.1)(sass@1.85.1)(stylus@0.64.0)(typescript@5.8.2)(yaml@2.7.0))(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2)))
@ -8624,6 +8657,23 @@ snapshots:
dependencies:
'@mdit-vue/types': 2.1.0
'@vuepress/shiki-twoslash@2.0.0-rc.79(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2))(vuepress@2.0.0-rc.20(@vuepress/bundler-vite@2.0.0-rc.20(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.2)(sass-embedded@1.85.1)(sass@1.85.1)(stylus@0.64.0)(typescript@5.8.2)(yaml@2.7.0))(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2)))':
dependencies:
'@shikijs/twoslash': 3.1.0(typescript@5.8.2)
'@vuepress/helper': 2.0.0-rc.79(typescript@5.8.2)(vuepress@2.0.0-rc.20(@vuepress/bundler-vite@2.0.0-rc.20(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.2)(sass-embedded@1.85.1)(sass@1.85.1)(stylus@0.64.0)(typescript@5.8.2)(yaml@2.7.0))(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2)))
floating-vue: 5.2.2(vue@3.5.13(typescript@5.8.2))
mdast-util-from-markdown: 2.0.2
mdast-util-gfm: 3.1.0
mdast-util-to-hast: 13.2.0
twoslash: 0.2.12(typescript@5.8.2)
twoslash-vue: 0.2.12(typescript@5.8.2)
vuepress: 2.0.0-rc.20(@vuepress/bundler-vite@2.0.0-rc.20(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.2)(sass-embedded@1.85.1)(sass@1.85.1)(stylus@0.64.0)(typescript@5.8.2)(yaml@2.7.0))(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2))
transitivePeerDependencies:
- '@nuxt/kit'
- supports-color
- typescript
- vue
'@vuepress/utils@2.0.0-rc.20':
dependencies:
'@types/debug': 4.1.12
@ -8711,7 +8761,7 @@ snapshots:
'@algolia/requester-fetch': 5.18.0
'@algolia/requester-node-http': 5.18.0
alien-signals@1.0.4: {}
alien-signals@0.2.2: {}
ansi-escapes@4.3.2:
dependencies:
@ -12713,13 +12763,23 @@ snapshots:
- tsx
- yaml
twoslash-protocol@0.2.12: {}
twoslash-protocol@0.3.1: {}
twoslash-vue@0.3.1(typescript@5.8.2):
twoslash-vue@0.2.12(typescript@5.8.2):
dependencies:
'@vue/language-core': 2.2.4(typescript@5.8.2)
twoslash: 0.3.1(typescript@5.8.2)
twoslash-protocol: 0.3.1
'@vue/language-core': 2.1.10(typescript@5.8.2)
twoslash: 0.2.12(typescript@5.8.2)
twoslash-protocol: 0.2.12
typescript: 5.8.2
transitivePeerDependencies:
- supports-color
twoslash@0.2.12(typescript@5.8.2):
dependencies:
'@typescript/vfs': 1.6.1(typescript@5.8.2)
twoslash-protocol: 0.2.12
typescript: 5.8.2
transitivePeerDependencies:
- supports-color

View File

@ -9,6 +9,7 @@ catalog:
'@vuepress/helper': 2.0.0-rc.79
'@vuepress/plugin-cache': 2.0.0-rc.79
'@vuepress/plugin-comment': 2.0.0-rc.79
'@vuepress/plugin-copy-code': 2.0.0-rc.79
'@vuepress/plugin-docsearch': 2.0.0-rc.79
'@vuepress/plugin-git': 2.0.0-rc.79
'@vuepress/plugin-markdown-hint': 2.0.0-rc.79
@ -19,8 +20,10 @@ catalog:
'@vuepress/plugin-photo-swipe': 2.0.0-rc.79
'@vuepress/plugin-reading-time': 2.0.0-rc.79
'@vuepress/plugin-seo': 2.0.0-rc.79
'@vuepress/plugin-shiki': 2.0.0-rc.79
'@vuepress/plugin-sitemap': 2.0.0-rc.79
'@vuepress/plugin-watermark': 2.0.0-rc.79
'@vuepress/shiki-twoslash': 2.0.0-rc.79
'@vueuse/core': ^12.7.0
'@vueuse/integrations': ^12.7.0
chokidar: 3.6.0

View File

@ -63,6 +63,7 @@
},
"peerDependencies": {
"@iconify/json": "^2",
"@vuepress/shiki-twoslash": "catalog:",
"mathjax-full": "^3.2.2",
"sass": "^1.85.0",
"sass-embedded": "^1.85.0",
@ -74,6 +75,9 @@
"@iconify/json": {
"optional": true
},
"@vuepress/shiki-twoslash": {
"optional": true
},
"mathjax-full": {
"optional": true
},
@ -96,10 +100,10 @@
"@pengzhanbo/utils": "^1.2.0",
"@vuepress-plume/plugin-fonts": "workspace:*",
"@vuepress-plume/plugin-search": "workspace:*",
"@vuepress-plume/plugin-shikiji": "workspace:*",
"@vuepress/helper": "catalog:",
"@vuepress/plugin-cache": "catalog:",
"@vuepress/plugin-comment": "catalog:",
"@vuepress/plugin-copy-code": "catalog:",
"@vuepress/plugin-docsearch": "catalog:",
"@vuepress/plugin-git": "catalog:",
"@vuepress/plugin-markdown-hint": "catalog:",
@ -110,6 +114,7 @@
"@vuepress/plugin-photo-swipe": "catalog:",
"@vuepress/plugin-reading-time": "catalog:",
"@vuepress/plugin-seo": "catalog:",
"@vuepress/plugin-shiki": "catalog:",
"@vuepress/plugin-sitemap": "catalog:",
"@vuepress/plugin-watermark": "catalog:",
"@vueuse/core": "catalog:",

View File

@ -66,6 +66,9 @@ html:not([data-theme="dark"]) .vp-code span {
padding: 20px 0;
margin: 0;
overflow-x: auto;
font-family: inherit;
font-size: inherit;
line-height: inherit;
background: transparent;
}
@ -100,6 +103,10 @@ html:not([data-theme="dark"]) .vp-code span {
display: none;
}
.vp-doc div[class*="language-"].line-numbers-mode::after {
display: none;
}
.vp-doc div[class*="language-"].line-numbers-mode {
/* rtl:ignore */
padding-left: 32px;
@ -129,6 +136,7 @@ html:not([data-theme="dark"]) .vp-code span {
.vp-doc div[class*="language-"].line-numbers-mode .line-numbers .line-number {
position: relative;
z-index: 3;
font-family: var(--vp-font-family-mono);
user-select: none;
}
@ -160,7 +168,7 @@ html:not([data-theme="dark"]) .vp-code span {
padding: 0 24px;
margin: 0 -24px;
background-color: var(--vp-code-line-highlight-color);
transition: background-color 0.5s;
transition: background-color var(--vp-t-color);
}
.vp-doc div[class*="language-"] code .highlighted.error {
@ -201,7 +209,7 @@ html:not([data-theme="dark"]) .vp-code span {
width: calc(100% + 48px);
padding: 0 24px;
margin: 0 -24px;
transition: background-color 0.5s;
transition: background-color var(--vp-t-color);
}
.vp-doc div[class*="language-"] code .diff::before {
@ -217,7 +225,6 @@ html:not([data-theme="dark"]) .vp-code span {
.vp-doc div[class*="language-"] code .diff.remove::before {
color: var(--vp-code-line-diff-remove-symbol-color);
content: "-";
transform: translateX(-6px);
}
.vp-doc div[class*="language-"] code .diff.add {
@ -227,96 +234,21 @@ html:not([data-theme="dark"]) .vp-code span {
.vp-doc div[class*="language-"] code .diff.add::before {
color: var(--vp-code-line-diff-add-symbol-color);
content: "+";
transform: translateX(-6px);
}
.vp-doc div[class*="language-"] .has-focused-lines .line:not(.has-focus) {
filter: blur(0.095rem);
opacity: 0.7;
transition: filter 0.35s, opacity 0.35s;
/**
* Copy Code Button
*/
.vp-copy-code-button {
--copy-code-c-text: var(--vp-code-block-color);
top: 1em;
line-height: initial;
transition: opacity var(--vp-t-color), background-color var(--vp-t-color);
}
.vp-doc div[class*="language-"]:hover .has-focused-lines .line:not(.has-focus) {
filter: blur(0);
opacity: 1;
}
.vp-doc div[class*="language-"] button.copy {
position: absolute;
top: 12px;
/* rtl:ignore */
right: 12px;
z-index: 3;
width: 40px;
height: 40px;
cursor: pointer;
background-color: var(--vp-code-copy-code-bg);
background-image: var(--vp-icon-copy);
background-repeat: no-repeat;
background-position: 50%;
background-size: 20px;
border: 1px solid var(--vp-code-copy-code-border-color);
border-radius: 4px;
opacity: 0;
transition: border-color 0.25s, background-color 0.25s, opacity 0.25s;
/* rtl:ignore */
direction: ltr;
}
.vp-doc div[class*="language-"]:hover > button.copy,
.vp-doc div[class*="language-"] > button.copy:focus,
.vp-doc div[class*="language-"] > button.copy.copied {
opacity: 1;
}
.vp-doc div[class*="language-"] > button.copy:hover,
.vp-doc div[class*="language-"] > button.copy.copied {
background-color: var(--vp-code-copy-code-hover-bg);
border-color: var(--vp-code-copy-code-hover-border-color);
}
.vp-doc div[class*="language-"] > button.copy.copied,
.vp-doc div[class*="language-"] > button.copy:hover.copied {
background-color: var(--vp-code-copy-code-hover-bg);
background-image: var(--vp-icon-copied);
/* rtl:ignore */
border-radius: 0 4px 4px 0;
}
.vp-doc div[class*="language-"] > button.copy.copied::before,
.vp-doc div[class*="language-"] > button.copy:hover.copied::before {
position: relative;
top: -1px;
display: flex;
align-items: center;
justify-content: center;
width: fit-content;
height: 40px;
padding: 0 10px;
font-size: 12px;
font-weight: 500;
color: var(--vp-code-copy-code-active-text);
text-align: center;
white-space: nowrap;
content: attr(data-copied);
background-color: var(--vp-code-copy-code-hover-bg);
border: 1px solid var(--vp-code-copy-code-hover-border-color);
/* rtl:ignore */
border-right: 0;
border-radius: 4px 0 0 4px;
/* rtl:ignore */
transform: translateX(calc(-100% - 1px));
}
@media (max-width: 419px) {
.vp-doc div[class*="language-"] > button.copy {
display: none;
}
.vp-copy-code-button.copied::after {
height: 2.5rem;
}
/*
@ -324,79 +256,27 @@ html:not([data-theme="dark"]) .vp-code span {
--------------------------------------------------------------------------
*/
.vp-doc div[class*="language-"].has-collapsed.collapsed {
height: calc(var(--vp-collapsed-lines) * var(--vp-code-line-height) * var(--vp-code-font-size) + 62px);
overflow-y: hidden;
}
.vp-doc div[class*="language-"].has-collapsed-lines .collapsed-lines,
[data-theme="dark"] .vp-doc div[class*="language-"].has-collapsed-lines .collapsed-lines {
--vp-collapsed-lines-bg: var(--vp-code-block-bg);
@property --vp-code-bg-collapsed-lines {
inherits: false;
initial-value: #fff;
syntax: "<color>";
}
.vp-doc div[class*="language-"].has-collapsed .collapsed-lines {
--vp-code-bg-collapsed-lines: var(--vp-code-block-bg);
position: absolute;
right: 0;
bottom: 0;
left: 0;
z-index: 4;
display: flex;
align-items: center;
justify-content: center;
height: 44px;
cursor: pointer;
background: linear-gradient(to bottom, transparent 0%, var(--vp-code-bg-collapsed-lines) 50%, var(--vp-code-bg-collapsed-lines) 100%);
transition: --vp-code-bg-collapsed-lines var(--vp-t-color);
background: linear-gradient(to bottom, transparent 0%, var(--vp-collapsed-lines-bg) 50%, var(--vp-collapsed-lines-bg) 100%);
}
.vp-doc div[class*="language-"].has-collapsed .collapsed-lines:hover {
--vp-code-bg-collapsed-lines: var(--vp-c-default-soft);
.vp-doc div[class*="language-"].has-collapsed-lines .collapsed-lines:hover,
[data-theme="dark"] .vp-doc div[class*="language-"].has-collapsed-lines .collapsed-lines:hover {
--vp-collapsed-lines-bg: var(--vp-c-default-soft);
}
.vp-doc div[class*="language-"].has-collapsed .collapsed-lines::before {
--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1em' height='1em' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='%23000' stroke-width='2' d='m18 12l-6 6l-6-6m12-6l-6 6l-6-6'/%3E%3C/svg%3E");
--trans-rotate: 0deg;
display: inline-block;
width: 24px;
height: 24px;
pointer-events: none;
content: "";
background-color: var(--vp-code-block-color);
-webkit-mask-image: var(--icon);
mask-image: var(--icon);
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
-webkit-mask-position: 50%;
mask-position: 50%;
-webkit-mask-size: 20px;
mask-size: 20px;
animation: code-collapsed-lines 1.2s infinite alternate-reverse ease-in-out;
.vp-doc div[class*="language-"].has-collapsed-lines.collapsed {
height: calc(var(--vp-collapsed-lines) * var(--vp-code-line-height) * var(--vp-code-font-size) + 62px);
}
.vp-doc div[class*="language-"].has-collapsed:not(.collapsed) code {
.vp-doc div[class*="language-"].has-collapsed-lines:not(.collapsed) code {
padding-bottom: 20px;
}
.vp-doc div[class*="language-"].has-collapsed:not(.collapsed) .collapsed-lines:hover {
--vp-code-bg-collapsed-lines: transparent;
}
.vp-doc div[class*="language-"].has-collapsed:not(.collapsed) .collapsed-lines::before {
--trans-rotate: 180deg;
}
@keyframes code-collapsed-lines {
0% {
opacity: 0.3;
transform: translateY(-2px) rotate(var(--trans-rotate));
}
100% {
opacity: 1;
transform: translateY(2px) rotate(var(--trans-rotate));
}
.vp-doc div[class*="language-"].has-collapsed-lines:not(.collapsed) .collapsed-lines:hover {
--vp-collapsed-lines-bg: transparent;
}

View File

@ -26,282 +26,3 @@
--twoslash-matched-color: var(--vp-c-brand-1);
--twoslash-unmatched-color: var(--vp-c-text-2);
}
/* Respect people's wishes to not have animations */
@media (prefers-reduced-motion: reduce) {
.twoslash * {
transition: none !important;
}
}
/* ===== Hover Info ===== */
.twoslash:hover .twoslash-hover {
border-color: var(--twoslash-underline-color);
}
.twoslash .twoslash-hover {
position: relative;
border-bottom: 1px dotted transparent;
transition: border-color 0.3s;
transition-timing-function: ease;
}
/* ===== Error Line ===== */
.twoslash .twoslash-error-line {
position: relative;
padding: 6px;
margin: 0.2em 0;
color: var(--twoslash-error-color);
background-color: var(--twoslash-error-bg);
border-left: 3px solid var(--twoslash-error-color);
}
.twoslash .twoslash-error {
padding-bottom: 2px;
background: url("data:image/svg+xml,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%206%203'%20enable-background%3D'new%200%200%206%203'%20height%3D'3'%20width%3D'6'%3E%3Cg%20fill%3D'%23c94824'%3E%3Cpolygon%20points%3D'5.5%2C0%202.5%2C3%201.1%2C3%204.1%2C0'%2F%3E%3Cpolygon%20points%3D'4%2C0%206%2C2%206%2C0.6%205.4%2C0'%2F%3E%3Cpolygon%20points%3D'0%2C2%201%2C3%202.4%2C3%200%2C0.6'%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E") repeat-x bottom left;
}
/* ===== Completeions ===== */
.twoslash .twoslash-completion-cursor {
position: relative;
}
.twoslash .twoslash-completion-cursor .twoslash-completion-list {
position: absolute;
top: 0;
left: 0;
z-index: 8;
display: inline-block;
margin: 3px 0 0 -1px;
user-select: none;
background: var(--twoslash-popup-bg);
border: 1px solid var(--twoslash-border-color);
box-shadow: var(--twoslash-popup-shadow);
transform: translate(0, 1.2em);
}
.twoslash-completion-list {
display: flex;
flex-direction: column;
gap: 4px;
width: 240px;
padding: 4px;
font-size: 0.8rem;
}
.twoslash-completion-list:hover {
user-select: auto;
}
.twoslash-completion-list::before {
position: absolute;
top: -1.6em;
left: -1px;
width: 2px;
height: 1.4em;
content: " ";
background-color: var(--twoslash-cursor-color);
}
.twoslash-completion-list li {
display: flex;
gap: 0.25em;
align-items: center;
overflow: hidden;
line-height: 1em;
}
.twoslash-completion-list li span.twoslash-completions-unmatched {
color: var(--twoslash-unmatched-color);
}
.twoslash-completion-list .deprecated {
text-decoration: line-through;
opacity: 0.5;
}
.twoslash-completion-list li span.twoslash-completions-matched {
color: var(--twoslash-matched-color);
}
/* Highlights */
.twoslash-highlighted {
padding: 1px 2px;
margin: -1px -3px;
background-color: var(--twoslash-highlighted-bg);
border: 1px solid var(--twoslash-highlighted-border);
border-radius: 4px;
}
/* Icons */
.twoslash-completion-list .twoslash-completions-icon {
flex: none;
width: 1em;
color: var(--twoslash-unmatched-color);
}
/* Custom Tags */
.twoslash .twoslash-tag-line {
position: relative;
display: flex;
gap: 0.3em;
align-items: center;
padding: 6px 10px;
margin: 0.2em 0;
color: var(--twoslash-tag-color);
background-color: var(--twoslash-tag-bg);
border-left: 3px solid var(--twoslash-tag-color);
}
.twoslash .twoslash-tag-line .twoslash-tag-icon {
width: 1.1em;
color: inherit;
}
.twoslash .twoslash-tag-line.twoslash-tag-error-line {
color: var(--twoslash-error-color);
background-color: var(--twoslash-error-bg);
border-left: 3px solid var(--twoslash-error-color);
}
.twoslash .twoslash-tag-line.twoslash-tag-warn-line {
color: var(--twoslash-tag-warn-color);
background-color: var(--twoslash-tag-warn-bg);
border-left: 3px solid var(--twoslash-tag-warn-color);
}
.twoslash .twoslash-tag-line.twoslash-tag-annotate-line {
color: var(--twoslash-tag-annotate-color);
background-color: var(--twoslash-tag-annotate-bg);
border-left: 3px solid var(--twoslash-tag-annotate-color);
}
/* ========== floating vue ================== */
.v-popper--theme-twoslash {
z-index: calc(var(--vp-z-index-local-nav) - 1);
}
.v-popper--theme-twoslash .v-popper__inner {
color: var(--twoslash-popup-color);
background: var(--twoslash-popup-bg);
border-color: var(--twoslash-border-color);
}
.v-popper--theme-twoslash .v-popper__arrow-outer {
border-color: var(--twoslash-border-color);
}
.v-popper--theme-twoslash .v-popper__arrow-inner {
border-color: var(--twoslash-popup-bg);
}
.twoslash-popup-container {
transform: translateY(1.5em);
}
.twoslash-query-presisted .twoslash-popup-container {
transform: translateY(1.8em);
}
.twoslash .v-popper {
display: inline-block;
}
.twoslash-completion-list .twoslash-completions-icon {
color: var(--twoslash-unmatched-color) !important;
}
.twoslash-floating .twoslash-popup-code {
display: block;
width: fit-content;
min-width: 100%;
max-width: 600px;
padding: 6px 12px;
font-size: var(--twoslash-code-size);
line-height: var(--vp-code-line-height);
white-space: pre-wrap;
transition: color 0.5s;
}
.twoslash-floating .twoslash-popup-docs,
.twoslash-floating .twoslash-popup-error {
max-width: 700px;
max-height: 500px;
padding: 12px !important;
overflow: hidden auto;
font-family: var(--twoslash-docs-font);
font-size: 0.9em;
text-wrap: balance;
}
.twoslash-floating .twoslash-popup-docs p:first-child,
.twoslash-floating .twoslash-popup-error p:first-child {
margin-top: 0;
}
.twoslash-floating .twoslash-popup-docs p:last-child,
.twoslash-floating .twoslash-popup-error p:last-child {
margin-bottom: 0;
}
.twoslash-floating .twoslash-popup-docs {
color: var(--twoslash-docs-color);
border-top: 1px solid var(--twoslash-border-color);
}
.twoslash-floating .twoslash-popup-error {
color: var(--twoslash-error-color);
}
.twoslash-floating .twoslash-popup-error.twoslash-error-level-warning {
color: var(--twoslash-warn-color);
}
.twoslash-floating .twoslash-popup-docs p,
.twoslash-floating .twoslash-popup-error p {
margin: 6px 0;
text-wrap: balance;
}
.twoslash-floating .twoslash-popup-docs pre .twoslash-floating .twoslash-popup-error pre {
padding: 12px;
margin: 6px -2px;
overflow-x: auto;
background-color: var(--vp-code-block-bg);
border-radius: 8px;
}
.twoslash-floating .twoslash-popup-docs-tags {
display: flex;
flex-direction: column;
padding: 8px 12px !important;
}
.twoslash-floating .twoslash-popup-docs-tags .twoslash-popup-docs-tag-name {
margin-right: 0.5em;
font-family: var(--twoslash-code-font);
color: var(--twoslash-unmatched-color);
}
.twoslash-completion-cursor {
display: inline-block;
width: 2px;
height: 1.2em;
margin-bottom: -0.2em;
user-select: none;
background: var(--twoslash-cursor-color);
}
.twoslash-floating.twoslash-completion .v-popper__arrow-container {
display: none;
}
.twoslash-floating.twoslash-completion .twoslash-completion-list {
padding: 6px;
font-family: var(--twoslash-code-font);
font-size: var(--twoslash-code-size) !important;
}
.twoslash-floating.twoslash-completion .twoslash-completion-list li {
padding: 3px 0;
}

View File

@ -376,14 +376,6 @@
--vp-code-line-warning-color: var(--vp-c-yellow-soft);
--vp-code-line-error-color: var(--vp-c-red-soft);
--vp-icon-copy: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' height='20' width='20' stroke='rgba(128,128,128,1)' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2M9 5a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2M9 5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2'/%3E%3C/svg%3E");
--vp-icon-copied: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' height='20' width='20' stroke='rgba(128,128,128,1)' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2M9 5a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2M9 5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2m-6 9 2 2 4-4'/%3E%3C/svg%3E");
--vp-code-copy-code-border-color: var(--vp-c-divider);
--vp-code-copy-code-bg: var(--vp-c-bg-soft);
--vp-code-copy-code-hover-border-color: var(--vp-c-divider);
--vp-code-copy-code-hover-bg: var(--vp-c-bg);
--vp-code-copy-code-active-text: var(--vp-c-text-2);
--vp-code-tab-divider: var(--vp-code-block-divider-color);
--vp-code-tab-text-color: var(--vp-c-text-2);
--vp-code-tab-bg: var(--vp-code-block-bg);

View File

@ -1,13 +1,15 @@
import type { SeoPluginOptions } from '@vuepress/plugin-seo'
import type { SitemapPluginOptions } from '@vuepress/plugin-sitemap'
import type { ThemeOptions } from 'vuepress-plugin-md-power'
import type { App, PluginConfig } from 'vuepress/core'
import type { PlumeThemePluginOptions } from '../../shared/index.js'
import { uniq } from '@pengzhanbo/utils'
import { fontsPlugin } from '@vuepress-plume/plugin-fonts'
import { searchPlugin } from '@vuepress-plume/plugin-search'
import { shikiPlugin } from '@vuepress-plume/plugin-shikiji'
import { isPlainObject } from '@vuepress/helper'
import { cachePlugin } from '@vuepress/plugin-cache'
import { commentPlugin } from '@vuepress/plugin-comment'
import { copyCodePlugin } from '@vuepress/plugin-copy-code'
import { docsearchPlugin } from '@vuepress/plugin-docsearch'
import { gitPlugin } from '@vuepress/plugin-git'
import { markdownHintPlugin } from '@vuepress/plugin-markdown-hint'
@ -18,6 +20,7 @@ import { nprogressPlugin } from '@vuepress/plugin-nprogress'
import { photoSwipePlugin } from '@vuepress/plugin-photo-swipe'
import { readingTimePlugin } from '@vuepress/plugin-reading-time'
import { seoPlugin } from '@vuepress/plugin-seo'
import { shikiPlugin } from '@vuepress/plugin-shiki'
import { sitemapPlugin } from '@vuepress/plugin-sitemap'
import { watermarkPlugin } from '@vuepress/plugin-watermark'
import { mdEnhancePlugin } from 'vuepress-plugin-md-enhance'
@ -102,13 +105,39 @@ export function getPlugins({
plugins.push(searchPlugin(pluginOptions.search || {}))
}
const shikiOption = pluginOptions.shiki
let shikiTheme: any = { light: 'vitesse-light', dark: 'vitesse-dark' }
if (shikiOption !== false) {
shikiTheme = shikiOption?.theme ?? shikiTheme
if (pluginOptions.copyCode !== false) {
const { ignoreSelector = [], ...copyCodeOptions } = pluginOptions.copyCode || {}
plugins.push(copyCodePlugin({
ignoreSelector: uniq(['.vp-copy-ignore', '.diff.remove', ...ignoreSelector]),
...copyCodeOptions,
}))
}
const shikiOptions = pluginOptions.shiki
const shikiTheme = shikiOptions && 'theme' in shikiOptions ? shikiOptions.theme : shikiOptions && 'themes' in shikiOptions ? shikiOptions.themes : { light: 'vitesse-light', dark: 'vitesse-dark' }
if (shikiOptions !== false) {
const { twoslash, ...restShikiOptions } = isPlainObject(shikiOptions) ? shikiOptions : {}
const twoslashOptions = twoslash === true ? {} : twoslash
plugins.push(shikiPlugin({
theme: shikiTheme,
...(shikiOption ?? {}),
// enable some default features
notationDiff: true,
notationErrorLevel: true,
notationFocus: true,
notationHighlight: true,
notationWordHighlight: true,
highlightLines: true,
collapsedLines: false,
twoslash: isPlainObject(twoslashOptions)
? {
...twoslashOptions,
// inject markdown class
floatingVue: { classMarkdown: 'vp-doc', ...twoslashOptions.floatingVue },
}
: twoslashOptions,
...('theme' in restShikiOptions ? {} : { themes: { light: 'vitesse-light', dark: 'vitesse-dark' } }),
...restShikiOptions,
}))
}
@ -121,8 +150,9 @@ export function getPlugins({
plot: true,
icons: true,
...pluginOptions.markdownPower || {},
// TODO: repl 代码主题 配置仅支持 主题名,不支持注册自定义主题
repl: pluginOptions.markdownPower?.repl
? { theme: shikiTheme, ...pluginOptions.markdownPower?.repl }
? { theme: shikiTheme as ThemeOptions, ...pluginOptions.markdownPower?.repl }
: pluginOptions.markdownPower?.repl,
}))
}

View File

@ -1,12 +1,13 @@
import type { SearchPluginOptions } from '@vuepress-plume/plugin-search'
import type { ShikiPluginOptions } from '@vuepress-plume/plugin-shikiji'
import type { CommentPluginOptions } from '@vuepress/plugin-comment'
import type { CopyCodePluginOptions } from '@vuepress/plugin-copy-code'
import type { DocSearchOptions } from '@vuepress/plugin-docsearch'
import type { MarkdownImagePluginOptions } from '@vuepress/plugin-markdown-image'
import type { MarkdownIncludePluginOptions } from '@vuepress/plugin-markdown-include'
import type { MarkdownMathPluginOptions } from '@vuepress/plugin-markdown-math'
import type { ReadingTimePluginOptions } from '@vuepress/plugin-reading-time'
import type { SeoPluginOptions } from '@vuepress/plugin-seo'
import type { ShikiPluginOptions } from '@vuepress/plugin-shiki'
import type { SitemapPluginOptions } from '@vuepress/plugin-sitemap'
import type { WatermarkPluginOptions } from '@vuepress/plugin-watermark'
import type { MarkdownEnhancePluginOptions } from 'vuepress-plugin-md-enhance'
@ -23,6 +24,11 @@ export interface PlumeThemePluginOptions {
*/
docsearch?: false | DocSearchOptions
/**
*
*/
copyCode?: false | CopyCodePluginOptions
/**
*
*/