feat: add multiple provider support for icon, close #568 (#596)

* feat: add multiple provider support for icon

* chore: tweak

* chore: tweak
This commit is contained in:
pengzhanbo 2025-05-16 11:03:41 +08:00 committed by GitHub
parent 0425046e9e
commit 149732520a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
51 changed files with 1506 additions and 575 deletions

View File

@ -64,9 +64,11 @@
"composables",
"Docsearch",
"esbuild",
"fontawesome",
"frontmatter",
"gsap",
"iarna",
"iconfont",
"iconify",
"katex",
"leancloud",

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

View File

@ -137,11 +137,13 @@ export default defineUserConfig({
- **默认值**: `false`
- **详情**: 是否启用 `npm-to` 容器
### icons
### icon
- **类型**: `boolean | IconsOptions`
- **默认值**: `true`
- **详情**: 是否启用图标语法
- **类型**: `IconOptions`
- **默认值**: `{ provider: 'iconify' }`
- **详情**: 图标配置
[查看 **icon** 使用说明](../../theme/guide/features/icon.md){.read-more}
### plot

View File

@ -123,11 +123,7 @@ type NavItem = string | {
*/
link: string
/**
* 支持 iconify 图标,直接使用 iconify name 即可自动加载
* @see https://icon-sets.iconify.design/
*
* - 如果 iconify 图标不满足您的需求,也可以支持传入 svg 字符串。
* - 还支持使用 本地图片 或 远程图片,本地图片的路径需要以 `/` 开头。
* 图标名称,或者 svg 图标
*/
icon?: string | { svg: string }
@ -225,6 +221,20 @@ export default defineUserConfig({
这表示,以 `/blog/``/article/` 开头的页面链接地址,该链接元素将被激活。
### 图标
支持通过 [markdown -> icon](../guide/features/icon.md) 配置的来源的图标。
- 当 `markdown.icon.provide``iconify` 时,支持 [iconify](https://iconify.design/) 图标
- 当 `markdown.icon.provide``iconfont` 时,支持 [iconfont](https://www.iconfont.cn/) 图标
- 当 `markdown.icon.provide``fontawesome` 时,支持 [fontawesome](https://fontawesome.com/) 图标
`markdown.icon.provide` 为非 `iconify` 值时,可以在 图标名称前加上 `iconify` 前缀,强制使用 [iconify](https://iconify.design/) 图标。
```ts
const item = { text: '首页', link: '/', icon: 'iconify carbon:home' }
```
## 配置帮助函数
主题提供了 `defineNavbarConfig(config)` 函数,为主题使用者提供导航栏配置的类型帮助。

View File

@ -7,16 +7,53 @@ permalink: /guide/components/icon/
## 概述
支持 iconify 所有图标,通过 icon name 加载图标。
图标组件 `<Icon />`,根据 `markdown.icon` 配置,从不同的图标库加载图标。
可在 [iconify search](https://icon-sets.iconify.design/) 搜索图标使用。
[主题还提供了 markdown 语法支持,点击了解更多](../markdown/icons.md){.read-more}
## 配置
```ts title=".vuepress/config.ts" twoslash
import { defineUserConfig } from 'vuepress'
import { plumeTheme } from 'vuepress-theme-plume'
export default defineUserConfig({
theme: plumeTheme({
markdown: { // [!code ++:3]
icon: { provider: 'iconify' } // 默认支持
}
})
})
```
```ts
interface IconOptions {
/**
* 图标提供商
*/
provider: 'iconify' | 'iconfont' | 'fontawesome'
/**
* 图标默认前缀,不同的提供商默认前缀不同
* - iconify - 默认为 `''`
* - iconfont - 默认为 `iconfont icon-`
* - fontawesome - 默认为 `fas`
*/
prefix?: string
/**
* 图标资源地址
*/
assets?: IconAssetLink | IconAssetLink[]
size?: string | number
color?: string
}
```
## Props
:::: field-group
::: field name="name" type="string" default="''" optional
图标名称
图标名称,当 `markdown.icon.prefix` 有值时,`name` 中的前缀可以省略
:::
::: field name="color" type="string" default="'currentcolor'" optional

View File

@ -18,7 +18,7 @@ permalink: /guide/components/link-card/
:::
::: field name="icon" type="string | { svg: string }" default="''" optional
显示在标题左侧的图标,支持 iconify 所有图标,也可以使用 图片链接
显示在标题左侧的图标,支持 [markdown.icon](../features/icon.md) 配置的提供商的图标,也可以使用 图片链接
:::
::: field name="href" type="string" default="''" optional

View File

@ -7,80 +7,110 @@ permalink: /guide/features/icon/
## 概述
主题支持 [iconify](https://icon-sets.iconify.design/) 的所有图标,并提供了不同的方式来使用它们:
主题支持以下来源的图标:
- [iconify](https://iconify.design/) - 默认支持
- [iconfont](https://www.iconfont.cn/) - 可选
- [fontawesome](https://fontawesome.com/) - 可选
在主题的以下功能中以相同的方式使用图标:
- [导航栏图标](../../config/navbar.md#配置)
- [侧边栏图标](../../guide/document.md#侧边栏图标)
- [图标组件](#图标组件)
- [图标语法糖](../../guide/markdown/icons.md)
- [文件树图标](../../guide/markdown/file-tree.md)
- [代码分组标题图标](../code/code-tabs.md#分组标题图标)
提供语法糖和组件支持:
[Markdown 图标语法糖支持](../markdown/icons.md){.read-more}
[图标组件支持](../components/icon.md){.read-more}
::: tip 主题对图标的优化
上述的不同的使用图标的方式,主题在内部都采取了相同的解析策略,即使您在不同的位置使用了相同的图标,
也不会重复加载相同的图标资源。
图标默认是通过远程请求加载,主题也非常建议您在本地项目中安装 `@iconify/json` 包,以便主题能够将图标全部解析为本地资源,
这可以有效的提高页面的访问体验。
:::
## 图标组件
## 配置
通过 `<Icon />` 组件来使用图标。
```ts title=".vuepress/config.ts" twoslash
import { defineUserConfig } from 'vuepress'
import { plumeTheme } from 'vuepress-theme-plume'
你可以在 markdown 文件中使用该 组件。
### 属性
`<Icon />` 组件接受一个 `name` 属性,用于指定图标的名称。还支持传入 `color``size` 属性来设置图标的颜色和大小。
但对于 彩色图标,`color` 属性设置无效。
| 属性 | 类型 | 描述 |
| ----- | ------------------ | -------------------------------------------------------------------------- |
| name | `string` | 图标名称,在 [iconify](https://icon-sets.iconify.design/) 可获取对应的名称 |
| color | `string` | 图标颜色,仅纯色图标支持设置颜色 |
| size | `number \| string` | 设置图标大小,默认单位为 `px` ,可自定义单位 |
**示例:**
````md
- 纯色图标:<Icon name="octicon:smiley-16" />
- 定义纯色图标的颜色和大小:<Icon name="octicon:smiley-16" color="red" size="2em" />
- 彩色图标:<Icon name="noto:smiling-face-with-open-hands" />
- 定义彩色图标的大小:<Icon name="noto:smiling-face-with-open-hands" size="2em" />
````
- 纯色图标:<Icon name="octicon:smiley-16" />
- 定义纯色图标的颜色和大小:<Icon name="octicon:smiley-16" color="red" size="2em" />
- 彩色图标:<Icon name="noto:smiling-face-with-open-hands" />
- 定义彩色图标的大小:<Icon name="noto:smiling-face-with-open-hands" size="2em" />
### 加载图标
`<Icon />` 组件默认通过 远程请求 `CDN` 获取图标资源,但这可能受到网络环境的影响,出现加载失败
或者延迟显示的情况。
为了解决这一问题,主题建议 在你的站点项目中安装 `@iconify/json` 包。
主题会检查当前项目是否安装了 `@iconify/json`,如果安装了该包,则主题自动解析所使用到的图标,
并处理为本地图标资源,在构建时打包到 `dist` 目录中。
由于 `@iconify/json` 包比较大,需要手动进行安装:
::: npm-to
```sh
npm install @iconify/json
export default defineUserConfig({
theme: plumeTheme({
markdown: { // [!code ++:3]
icon: { provider: 'iconify' } // 默认支持
}
})
})
```
:::
## 配置项
## markdown 语法糖
### provider
相关内容请查看 [iconify-图标 语法糖](../markdown/icons.md)
- **类型**: `'iconify' | 'iconfont' | 'fontawesome'`
- **默认值**: `'iconify'`
---
图标提供商
::: tip 说明
[navbar](../../config/theme.md#navbar) 配置和 [notes](../../config/theme.md#notes) 配置
中的 `icon` 选项,也支持传入 iconify 图标名,并且当安装了 `@iconify/json`,也会自动解析为本地图标资源。
:::
### prefix
- **类型**: `string`
- **默认值**: `''` (不同的图标提供商有不同的默认值)
- **详情**:
图标前缀
- 提供商为 `iconify` 时,默认为 `''`,设置 `iconify` 的图标集作为前缀,比如 `mdi`
- 提供商为 `iconfont` 时,默认为 `'iconfont icon-'`
- 提供商为 `fontawesome` 时默认为 `'fas'`,可选值如下:
```ts
type FontAwesomePrefix =
| 'fas' | 's' // fa-solid fa-name
| 'far' | 'r' // fa-regular fa-name
| 'fal' | 'l' // fa-light fa-name
| 'fat' | 't' // fa-thin fa-name
| 'fads' | 'ds' // fa-duotone fa-solid fa-name
| 'fass' | 'ss' // fa-sharp fa-solid fa-name
| 'fasr' | 'sr' // fa-sharp fa-regular fa-name
| 'fasl' | 'sl' // fa-sharp fa-light fa-name
| 'fast' | 'st' // fa-sharp fa-thin fa-name
| 'fasds' | 'sds' // fa-sharp-duotone fa-solid fa-name
| 'fab' | 'b' // fa-brands fa-name
```
### assets
- **类型**: `(string | FontAwesomeAssetBuiltin)[] | string | FontAwesomeAssetBuiltin`
```ts
type FontAwesomeAssetBuiltin = 'fontawesome' | 'fontawesome-with-brands'
```
- **默认值**: `undefined`
- **详情**:
- `iconify` 时,不需要设置;
- `iconfont` 时,设置为 iconfont 的资源地址;
- `fontawesome` 时,设置为 `fontawesome` 的资源地址,可选值为 `fontawesome``fontawesome-with-brands`
或者自定义资源地址。
### size
- **类型**: `string | number`
- **默认值**: `1em`
- **详情**:
图标的默认尺寸
### color
- **类型**: `string`
- **默认值**: `'currentColor'`
- **详情**:
图标的默认颜色

View File

@ -2,12 +2,33 @@
title: 图标
createTime: 2024/09/30 14:49:39
icon: grommet-icons:emoji
permalink: /guide/markdown/iconify/
permalink: /guide/markdown/icon/
badge:
text: 变更
type: warning
---
<script setup>
import { useStyleTag, useScriptTag } from '@vueuse/core'
useStyleTag(`@import url("//at.alicdn.com/t/c/font_4920010_cm826bt13ke.css");`)
useScriptTag(
'https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6/js/solid.min.js',
() => {},
{ attrs: { "data-auto-replace-svg": "nest" } }
)
useScriptTag(
'https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6/js/regular.min.js',
() => {},
{ attrs: { "data-auto-replace-svg": "nest" } }
)
useScriptTag(
'https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6/js/fontawesome.min.js',
() => {},
{ attrs: { "data-auto-replace-svg": "nest" } }
)
</script>
::: warning 图标语法糖在 `1.0.0-rc.144` 版本中进行了破坏性变更。
`:[collect:name size/color]:` 语法糖已弃用,请使用 `::collect:name =size /color::` 代替。
@ -23,13 +44,127 @@ badge:
## 概述
在 Markdown 文件中使用 [iconify](https://iconify.design/) 的图标。
主题支持在 Markdown 文件中以下来源的图标:
主题一方面提供了[`<Icon />`](../components/icon.md) 组件来支持在 markdown 中使用图标;
另一方面,主题还提供了图标的 markdown 语法,以更简单的方式,在 Markdown 中使用图标。
- [iconify](https://iconify.design/) - 默认支持
- [iconfont](https://www.iconfont.cn/) - 可选
- [fontawesome](https://fontawesome.com/) - 可选
为了更好的使用该功能,主题推荐你安装 `@iconify/json` 依赖。主题会自动解析 `@iconify/json` 中的图标数据,
将有使用的图标打包为本地资源,以获得更好的访问体验。
主题提供图标 markdown 语法支持,使用简单灵活的方式在 Markdown 中插入图标。
[主题还提供了 `<Icon />` 组件支持,点击了解更多](../components/icon.md){.read-more}
## 语法
图标语法为行内语法,可以在段落中与其他 markdown 语法混合使用。
**基础语法**,使用 `::` 包裹图标名称:
```md
::name::
```
**设置图标大小和颜色**(注意空格不可缺少)
```md
::name =size::
::name /color::
::name =size /color::
```
- `=size` 设置图标大小
- `=16` 图标的宽高为 `16px`
- `=24x16` 图标的宽为 `24px`,高为 `16px`
- `=x16` 图标的高为 `16px`,宽为自适应
- `=1.2em` 图标的宽高为 `1.2em`
- `=1.2emx1.5em` 图标的宽为 `1.2em`,高为 `1.5em`
- `/color` 设置图标颜色,支持 `hex`/ `rgb` / `rgba` / `hsl` / `hsla` 等合法颜色色值
- `/#fff` 图标的颜色为 `#fff`
- `/rgb(255,0,0)` 图标的颜色为 `rgb(255,0,0)`
## Iconify
[iconify](https://iconify.design/) 提供了 **200K +** 的开源图标支持,足以满足大多数项目的需求。
主题将 **iconify** 作为默认的图标来源支持。
在 Markdown 中使用 `::collect:name` 语法来插入图标。
### 配置
```ts title=".vuepress/config.ts" twoslash
import { defineUserConfig } from 'vuepress'
import { plumeTheme } from 'vuepress-theme-plume'
export default defineUserConfig({
theme: plumeTheme({
markdown: { // [!code ++:3]
icon: { provider: 'iconify' } // 默认支持
}
})
})
```
```ts
interface IconOptions {
provider: 'iconify'
prefix?: string
}
```
### 使用
::: steps
- 从 [iconify search](https://icon-sets.iconify.design/) 搜索想要使用的图标,复制图标名称:
![iconify](/images/icon/iconify-1.png)
- 在 Markdown 中使用 `::collect:name` 语法来插入图标
```md
::carbon:home::
```
**输出:** ::carbon:home::
:::
在 Iconify 中,图标被分为不同的 `collect`,每个 `collect` 下有若干个图标。
`::collect:name` 语法中,使用 `:` 来分隔 `collect``name`
如果你主要使用某个 `collect` 下的图标,可以在配置中指定 `prefix`, 以便在 `::collect:name` 语法中省略 `collect` 前缀:
```ts title=".vuepress/config.ts" twoslash
import { defineUserConfig } from 'vuepress'
import { plumeTheme } from 'vuepress-theme-plume'
export default defineUserConfig({
theme: plumeTheme({
markdown: {
icon: {
provider: 'iconify',
prefix: 'carbon', // 默认使用 `collect` 图标集合 // [!code ++]
}
}
})
})
```
```md
::home:: <!-- 默认使用 `carbon` 图标集,自动补全为 `carbon:home` -->
::solar:user-bold:: <!-- 显式的使用其他图标集 -->
```
**输出:** ::carbon:home:: ::solar:user-bold::
### 安装
对于企业内部项目,或无法访问外部网络资源的情况下,主题推荐安装 `@iconify/json` 依赖。
主题自动解析 `@iconify/json` 中的图标数据,将已使用的图标打包为本地资源。
::: npm-to
@ -39,38 +174,7 @@ npm install @iconify/json
:::
## 语法
```md
::collect:name::
```
设置图标大小和颜色
```md
::collect:name =size::
::collect:name /color::
::collect:name =size /color::
```
`iconify` 拥有非常多的图标,这些图标被分组为不同的 `collect`,每个 `collect` 都有自己的
图标。
你可以从 <https://icon-sets.iconify.design/> 中获取 collect 和 name。
## 示例
输入:
```md
::ion:logo-markdown::
```
输出:
::ion:logo-markdown::
该语法为行内语法,因此,你可以在同一行中跟其他 markdown 语法 一起使用。
### 示例
输入:
@ -91,3 +195,243 @@ github: ::tdesign:logo-github-filled::
修改大小颜色:::tdesign:logo-github-filled =36px /#f00::
彩色图标 ::skill-icons:vscode-dark =36px::
## Iconfont
[iconfont](https://www.iconfont.cn/) 是提供了海量的图标支持。
### 配置
```ts title=".vuepress/config.ts" twoslash
import { defineUserConfig } from 'vuepress'
import { plumeTheme } from 'vuepress-theme-plume'
export default defineUserConfig({
theme: plumeTheme({
markdown: { // [!code ++:3]
icon: {
provider: 'iconfont',
assets: 'https://at.alicdn.com/t/c/xxxx.css' // 示例地址
}
}
})
})
```
```ts
interface IconOptions {
provider: 'iconfont'
/**
* 图标前缀
* @default 'iconfont icon-'
*/
prefix?: string
/**
* iconfont 资源地址
*/
assets: string | string[]
}
```
### 使用
[前往 **iconfont 帮助中心** 了解 **创建项目**和 **添加图标**](https://www.iconfont.cn/help/detail){.read-more}
:::steps
- 从 iconfont 获取项目的资源地址,复制并粘贴到 `assets` 配置中:
![iconfont assets](/images/icon/iconfont-1.png)
```ts title=".vuepress/config.ts"
export default defineUserConfig({
theme: plumeTheme({
markdown: {
icon: {
provider: 'iconfont',
assets: 'https://at.alicdn.com/t/c/xxxx.css' // 示例地址 // [!code ++]
}
}
})
})
```
也可以选择下载至本地,将资源存放到 `.vuepress/public` 目录中,然后在 `assets` 配置中填写本地文件路径。
- 检查 iconfont 的项目配置,获取 `prefix` 配置:
![iconfont prefix](/images/icon/iconfont-2.png)
其中 `prefix` 配置由 `font family``font class` 前缀组成,如果 iconfont 的项目配置为默认配置,
`prefix``iconfont icon-`,此时你可以忽略此步骤。
```ts title=".vuepress/config.ts"
export default defineUserConfig({
theme: plumeTheme({
markdown: {
icon: {
provider: 'iconfont',
prefix: 'iconfont icon-', // 默认值 // [!code ++]
}
}
})
})
```
- 在 Markdown 中使用 `::name::` 语法来插入图标:
![iconfont name](/images/icon/iconfont-3.png)
将图片中的 `font class` 填入 `::name::` 语法中:
```md
::hot::
::hot =24px::
::hot =24px /#f00::
```
输出:
<VPIcon provider="iconfont" name="hot" />
<VPIcon provider="iconfont" name="hot" size="24px" />
<VPIcon provider="iconfont" name="hot" size="24px" color="#f00" />
:::
## Fontawesome
[Fontawesome](https://fontawesome.com/) 提供了 免费 和 付费 的图标支持,付费图标需要进行订阅。
[查看 **Fontawesome** 官方文档](https://docs.fontawesome.com/web/setup/get-started){.read-more}
### 配置
```ts title=".vuepress/config.ts" twoslash
import { defineUserConfig } from 'vuepress'
import { plumeTheme } from 'vuepress-theme-plume'
export default defineUserConfig({
theme: plumeTheme({
markdown: { // [!code ++:3]
icon: {
provider: 'fontawesome',
assets: 'fontawesome' // 预设资源地址,从 CDN 加载
}
}
})
})
```
```ts
interface IconOptions {
provider: 'fontawesome'
/**
* 图标前缀
* @default 'fas'
*/
prefix?: FontAwesomePrefix
/**
* iconfont 资源地址
*/
assets: (FontAwesomeAssetBuiltin | string)[]
}
type FontAwesomeAssetBuiltin = 'fontawesome' | 'fontawesome-with-brands'
type FontAwesomePrefix =
| 'fas' | 's' // fa-solid fa-name
| 'far' | 'r' // fa-regular fa-name
| 'fal' | 'l' // fa-light fa-name
| 'fat' | 't' // fa-thin fa-name
| 'fads' | 'ds' // fa-duotone fa-solid fa-name
| 'fass' | 'ss' // fa-sharp fa-solid fa-name
| 'fasr' | 'sr' // fa-sharp fa-regular fa-name
| 'fasl' | 'sl' // fa-sharp fa-light fa-name
| 'fast' | 'st' // fa-sharp fa-thin fa-name
| 'fasds' | 'sds' // fa-sharp-duotone fa-solid fa-name
| 'fab' | 'b' // fa-brands fa-name
```
[在 **Fontawesome** 中,图标通过 families + styles 控制样式,**查看详情**](https://docs.fontawesome.com/web/add-icons/how-to#setting-different-families--styles){.read-more}
为便于管理,可以通过 `::prefix:name` 语法,通过前缀设置 families + styles默认前缀为 `fas`,可以省略它:
```md
::name:: <!-- prefix = fas -->
::fas:name:: <!-- prefix = fas -->
::s:name:: <!-- prefix = fas s 为 fas 的缩写 -->
```
可以通过配置 `markdown.icon.prefix` 修改默认前缀。
::: tip
Fontawesome 的免费图标仅支持 `solid` 、部分 `regular` 以及 `brands`
对于免费版本,前缀仅支持 `fas` / `far` / `fab` 和它们的缩写前缀。
:::
### 使用
[前往 **https://fontawesome.com/search?ic=free** 搜索免费图标](https://fontawesome.com/search?ic=free){.read-more}
:::steps
- 复制图标名称:
![fontawesome name](/images/icon/fontawesome-1.png)
- 在 Markdown 中使用 `::prefix:name` 语法插入图标:
```md
::circle-user:: <!-- prefix = fas -->
::fas:circle-user:: <!-- prefix = fas -->
::s:circle-user:: <!-- prefix = fas s 为 fas 的缩写 -->
```
:::
### 示例
```md
::circle-user::
::circle-user =24px::
::circle-user =24px /#f00::
```
输出:
<VPIcon provider="fontawesome" name="circle-user" />
<VPIcon provider="fontawesome" name="circle-user" size="24px" />
<VPIcon provider="fontawesome" name="circle-user" size="24px" color="#f00" />
[为 Fontawesome 添加更多样式支持](https://docs.fontawesome.com/web/style/styling){.read-more}
```
::circle-user 2xl:: <!-- 2xl 为 fa-2xl 的缩写, 图标大小为 2em -->
::circle-user rotate-90:: <!-- 图标旋转 90 度 -->
::circle-user beat:: <!-- 图标动画 -->
::circle-user border:: <!-- 图标边框 -->
::circle-user 2xl beat:: <!-- 混合多种样式 -->
```
输出:
<VPIcon provider="fontawesome" name="circle-user" extra="2xl" />
<VPIcon provider="fontawesome" name="circle-user" extra="rotate-90" />
<VPIcon provider="fontawesome" name="circle-user" extra="beat" />
<VPIcon provider="fontawesome" name="circle-user" extra="border" />
<VPIcon provider="fontawesome" name="circle-user" extra="2xl beat" />
## 其它说明
`markdown.icon.provider` 设置为非 `iconify` 时,`iconify` 作为默认支持,依然可以在
markdown 中插入 iconify 图标:
`::collect:name::` 语法中,在开始位置添加 `iconify`
```md /iconify /
::iconify carbon:home::
```
输出:
::iconify carbon:home::

View File

@ -481,8 +481,19 @@ const typescript = defineNoteConfig({
### 侧边栏图标
为侧边栏添加 ==图标== 有助于 侧边栏更好的呈现。得益于 [iconify](https://iconify.design/) 这个强大的开源图标库,
你可以使用超过 `200k` 的图标,仅需要添加 `icon` 配置即可。
为侧边栏添加 ==图标== 有助于 侧边栏更好的呈现。
支持通过 [markdown -> icon](../guide/features/icon.md) 配置的来源的图标。
- 当 `markdown.icon.provide``iconify` 时,支持 [iconify](https://iconify.design/) 图标
- 当 `markdown.icon.provide``iconfont` 时,支持 [iconfont](https://www.iconfont.cn/) 图标
- 当 `markdown.icon.provide``fontawesome` 时,支持 [fontawesome](https://fontawesome.com/) 图标
`markdown.icon.provide` 为非 `iconify` 值时,可以在 图标名称前加上 `iconify` 前缀,强制使用 [iconify](https://iconify.design/) 图标。
```ts
const item = { text: '首页', link: '/', icon: 'iconify carbon:home' }
```
```ts title=".vuepress/notes.ts" twoslash
import { defineNoteConfig } from 'vuepress-theme-plume'

View File

@ -7,13 +7,13 @@ exports[`codeTabsPlugin > should work with default 1`] = `
</code></pre>
</div></template><template #tab2="{ value, isActive }"><div class="language-lang"><pre><code>const c = 3
</code></pre>
</div></template></CodeTabs><CodeTabs id="11" :data='[{"id":"npm"},{"id":"pnpm"},{"id":"yarn"}]' :active="1"><template #title0="{ value, isActive }"><VPIcon name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #title2="{ value, isActive }"><VPIcon name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>npm i
</div></template></CodeTabs><CodeTabs id="11" :data='[{"id":"npm"},{"id":"pnpm"},{"id":"yarn"}]' :active="1"><template #title0="{ value, isActive }"><VPIcon provider="iconify" name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #title2="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>npm i
</code></pre>
</div></template><template #tab1="{ value, isActive }"><div class="language-lang"><pre><code>pnpm i
</code></pre>
</div></template><template #tab2="{ value, isActive }"><div class="language-lang"><pre><code>yarn
</code></pre>
</div></template></CodeTabs><CodeTabs id="22" :data='[{"id":"a.ts"},{"id":"a.js"}]' tab-id="pm"><template #title0="{ value, isActive }"><VPIcon name="vscode-icons:file-type-typescript"/><span>a.ts</span></template><template #title1="{ value, isActive }"><VPIcon name="vscode-icons:file-type-js"/><span>a.js</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>const a = 1
</div></template></CodeTabs><CodeTabs id="22" :data='[{"id":"a.ts"},{"id":"a.js"}]' tab-id="pm"><template #title0="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-typescript"/><span>a.ts</span></template><template #title1="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-js"/><span>a.js</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>const a = 1
</code></pre>
</div></template><template #tab1="{ value, isActive }"><div class="language-lang"><pre><code>const a = 1
</code></pre>
@ -67,13 +67,13 @@ exports[`codeTabsPlugin > should work with options: { named: [npm,pnpm,yarn], ex
</code></pre>
</div></template><template #tab2="{ value, isActive }"><div class="language-lang"><pre><code>const c = 3
</code></pre>
</div></template></CodeTabs><CodeTabs id="11" :data='[{"id":"npm"},{"id":"pnpm"},{"id":"yarn"}]' :active="1"><template #title0="{ value, isActive }"><VPIcon name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #title2="{ value, isActive }"><VPIcon name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>npm i
</div></template></CodeTabs><CodeTabs id="11" :data='[{"id":"npm"},{"id":"pnpm"},{"id":"yarn"}]' :active="1"><template #title0="{ value, isActive }"><VPIcon provider="iconify" name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #title2="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>npm i
</code></pre>
</div></template><template #tab1="{ value, isActive }"><div class="language-lang"><pre><code>pnpm i
</code></pre>
</div></template><template #tab2="{ value, isActive }"><div class="language-lang"><pre><code>yarn
</code></pre>
</div></template></CodeTabs><CodeTabs id="22" :data='[{"id":"a.ts"},{"id":"a.js"}]' tab-id="pm"><template #title0="{ value, isActive }"><VPIcon name="vscode-icons:file-type-typescript"/><span>a.ts</span></template><template #title1="{ value, isActive }"><VPIcon name="vscode-icons:file-type-js"/><span>a.js</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>const a = 1
</div></template></CodeTabs><CodeTabs id="22" :data='[{"id":"a.ts"},{"id":"a.js"}]' tab-id="pm"><template #title0="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-typescript"/><span>a.ts</span></template><template #title1="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-js"/><span>a.js</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>const a = 1
</code></pre>
</div></template><template #tab1="{ value, isActive }"><div class="language-lang"><pre><code>const a = 1
</code></pre>

View File

@ -72,91 +72,91 @@ exports[`fileTree > parseFileTreeRawContent > should work 1`] = `
exports[`fileTreePlugin > should work with default options 1`] = `
"<div class="vp-file-tree"><FileTreeNode expanded type="folder" filename="docs" :level="0">
<template #icon><VPIcon name="vscode-icons:folder-type-docs" /></template><FileTreeNode type="file" filename="README.md" :level="1">
<template #icon><VPIcon name="flat-color-icons:info" /></template>
<template #icon><VPIcon provider="iconify" name="vscode-icons:folder-type-docs" /></template><FileTreeNode type="file" filename="README.md" :level="1">
<template #icon><VPIcon provider="iconify" name="flat-color-icons:info" /></template>
</FileTreeNode>
<FileTreeNode type="file" filename="foo.md" :level="1">
<template #icon><VPIcon name="vscode-icons:file-type-markdown" /></template>
<template #icon><VPIcon provider="iconify" name="vscode-icons:file-type-markdown" /></template>
</FileTreeNode>
</FileTreeNode>
<FileTreeNode expanded type="folder" filename="src" :level="0">
<template #icon><VPIcon name="vscode-icons:folder-type-src" /></template><FileTreeNode expanded type="folder" filename="client" :level="1">
<template #icon><VPIcon name="vscode-icons:folder-type-client" /></template><FileTreeNode expanded type="folder" filename="components" :level="2">
<template #icon><VPIcon name="vscode-icons:folder-type-component" /></template><FileTreeNode focus type="file" filename="Navbar.vue" :level="3">
<template #icon><VPIcon name="vscode-icons:file-type-vue" /></template>
<template #icon><VPIcon provider="iconify" name="vscode-icons:folder-type-src" /></template><FileTreeNode expanded type="folder" filename="client" :level="1">
<template #icon><VPIcon provider="iconify" name="vscode-icons:folder-type-client" /></template><FileTreeNode expanded type="folder" filename="components" :level="2">
<template #icon><VPIcon provider="iconify" name="vscode-icons:folder-type-component" /></template><FileTreeNode focus type="file" filename="Navbar.vue" :level="3">
<template #icon><VPIcon provider="iconify" name="vscode-icons:file-type-vue" /></template>
</FileTreeNode>
</FileTreeNode>
<FileTreeNode type="file" filename="index.ts" :level="2">
<template #icon><VPIcon name="vscode-icons:file-type-typescript" /></template><template #comment># comment</template>
<template #icon><VPIcon provider="iconify" name="vscode-icons:file-type-typescript" /></template><template #comment># comment</template>
</FileTreeNode>
</FileTreeNode>
<FileTreeNode expanded type="folder" filename="node" :level="1">
<template #icon><VPIcon name="vscode-icons:default-folder" /></template><FileTreeNode type="file" filename="index.ts" :level="2">
<template #icon><VPIcon name="vscode-icons:file-type-typescript" /></template>
<template #icon><VPIcon provider="iconify" name="vscode-icons:default-folder" /></template><FileTreeNode type="file" filename="index.ts" :level="2">
<template #icon><VPIcon provider="iconify" name="vscode-icons:file-type-typescript" /></template>
</FileTreeNode>
</FileTreeNode>
</FileTreeNode>
<FileTreeNode type="file" filename=".gitignore" :level="0">
<template #icon><VPIcon name="vscode-icons:file-type-git" /></template>
<template #icon><VPIcon provider="iconify" name="vscode-icons:file-type-git" /></template>
</FileTreeNode>
<FileTreeNode type="file" filename="package.json" :level="0">
<template #icon><VPIcon name="vscode-icons:file-type-node" /></template>
<template #icon><VPIcon provider="iconify" name="vscode-icons:file-type-node" /></template>
</FileTreeNode></div>
<div class="vp-file-tree"><p class="vp-file-tree-title">files</p><FileTreeNode expanded type="folder" filename="src" :level="0">
<template #icon><VPIcon name="vscode-icons:folder-type-src" /></template><FileTreeNode expanded type="folder" filename="js" :level="1">
<template #icon><VPIcon name="vscode-icons:default-folder" /></template><FileTreeNode type="file" filename="…" :level="2">
<template #icon><VPIcon provider="iconify" name="vscode-icons:folder-type-src" /></template><FileTreeNode expanded type="folder" filename="js" :level="1">
<template #icon><VPIcon provider="iconify" name="vscode-icons:default-folder" /></template><FileTreeNode type="file" filename="…" :level="2">
</FileTreeNode>
</FileTreeNode>
<FileTreeNode type="folder" filename="vue" :level="1">
<template #icon><VPIcon name="vscode-icons:default-folder" /></template><FileTreeNode type="file" filename="…" :level="2">
<template #icon><VPIcon provider="iconify" name="vscode-icons:default-folder" /></template><FileTreeNode type="file" filename="…" :level="2">
</FileTreeNode>
</FileTreeNode>
<FileTreeNode type="folder" filename="css" :level="1">
<template #icon><VPIcon name="vscode-icons:folder-type-css" /></template><FileTreeNode type="file" filename="…" :level="2">
<template #icon><VPIcon provider="iconify" name="vscode-icons:folder-type-css" /></template><FileTreeNode type="file" filename="…" :level="2">
</FileTreeNode>
</FileTreeNode>
</FileTreeNode>
<FileTreeNode type="file" filename="README.md" :level="0">
<template #icon><VPIcon name="flat-color-icons:info" /></template>
<template #icon><VPIcon provider="iconify" name="flat-color-icons:info" /></template>
</FileTreeNode></div>
<div class="vp-file-tree"><FileTreeNode type="file" filename="docs" :level="0">
<template #icon><VPIcon name="vscode-icons:default-file" /></template>
<template #icon><VPIcon provider="iconify" name="vscode-icons:default-file" /></template>
</FileTreeNode>
<FileTreeNode expanded type="folder" filename="src" :level="0">
<template #icon><VPIcon name="vscode-icons:default-folder" /></template><FileTreeNode type="file" filename="a.js" :level="1">
<template #icon><VPIcon name="vscode-icons:default-file" /></template>
<template #icon><VPIcon provider="iconify" name="vscode-icons:default-folder" /></template><FileTreeNode type="file" filename="a.js" :level="1">
<template #icon><VPIcon provider="iconify" name="vscode-icons:default-file" /></template>
</FileTreeNode>
<FileTreeNode type="file" filename="b.ts" :level="1">
<template #icon><VPIcon name="vscode-icons:default-file" /></template>
<template #icon><VPIcon provider="iconify" name="vscode-icons:default-file" /></template>
</FileTreeNode>
</FileTreeNode>
<FileTreeNode type="file" filename="README.md" :level="0">
<template #icon><VPIcon name="vscode-icons:default-file" /></template>
<template #icon><VPIcon provider="iconify" name="vscode-icons:default-file" /></template>
</FileTreeNode></div>
<div class="vp-file-tree"><FileTreeNode type="file" filename="" :level="0">
<template #icon><VPIcon name="vscode-icons:default-file" /></template>
<template #icon><VPIcon provider="iconify" name="vscode-icons:default-file" /></template>
</FileTreeNode>
<FileTreeNode expanded type="folder" filename="" :level="0">
<template #icon><VPIcon name="vscode-icons:default-folder" /></template><FileTreeNode type="file" filename="" :level="1">
<template #icon><VPIcon name="vscode-icons:default-file" /></template>
<template #icon><VPIcon provider="iconify" name="vscode-icons:default-folder" /></template><FileTreeNode type="file" filename="" :level="1">
<template #icon><VPIcon provider="iconify" name="vscode-icons:default-file" /></template>
</FileTreeNode>
</FileTreeNode></div>
<div class="vp-file-tree"><FileTreeNode expanded type="folder" filename="docs" :level="0">
<template #icon><VPIcon name="vscode-icons:folder-type-docs" /></template><FileTreeNode type="file" diff="add" filename="added.md" :level="1">
<template #icon><VPIcon name="vscode-icons:file-type-markdown" /></template>
<template #icon><VPIcon provider="iconify" name="vscode-icons:folder-type-docs" /></template><FileTreeNode type="file" diff="add" filename="added.md" :level="1">
<template #icon><VPIcon provider="iconify" name="vscode-icons:file-type-markdown" /></template>
</FileTreeNode>
<FileTreeNode type="file" diff="remove" filename="remove.md" :level="1">
<template #icon><VPIcon name="vscode-icons:file-type-markdown" /></template>
<template #icon><VPIcon provider="iconify" name="vscode-icons:file-type-markdown" /></template>
</FileTreeNode>
</FileTreeNode>
<FileTreeNode type="file" diff="add" filename="src" :level="0">
<template #icon><VPIcon name="vscode-icons:default-file" /></template>
<template #icon><VPIcon provider="iconify" name="vscode-icons:default-file" /></template>
</FileTreeNode>
<FileTreeNode type="file" diff="remove" filename="source" :level="0">
<template #icon><VPIcon name="vscode-icons:default-file" /></template>
<template #icon><VPIcon provider="iconify" name="vscode-icons:default-file" /></template>
</FileTreeNode></div>
<div class="vp-file-tree"></div>
"

View File

@ -1,108 +1,91 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`iconsPlugin > should not work with invalid icon 1`] = `
exports[`iconPlugin > should not work with invalid icon 1`] = `
"<p>:: mdi:11 ::</p>
"
`;
exports[`iconsPlugin > should not work with invalid icon 2`] = `
exports[`iconPlugin > should not work with invalid icon 2`] = `
"<p>::::</p>
"
`;
exports[`iconsPlugin > should not work with invalid icon 3`] = `
exports[`iconPlugin > should not work with invalid icon 3`] = `
"<p>::]&amp;</p>
"
`;
exports[`iconsPlugin > should not work with invalid icon 4`] = `
"<p>::::</p>
"
`;
exports[`iconsPlugin > should not work with invalid icon 5`] = `
exports[`iconPlugin > should not work with invalid icon 4`] = `
"<p>::mdi:11</p>
"
`;
exports[`iconsPlugin > should work 1`] = `
"<p><VPIcon name="mdi:11" /></p>
exports[`iconPlugin > should work with options -> { provider: "fontawesome", prefix } 1`] = `
"<p><VPIcon provider="fontawesome" name="home" /></p>
"
`;
exports[`iconsPlugin > should work 2`] = `
"<p><strong>strong</strong> <VPIcon name="mdi:11" /> <VPIcon name="mdi:11" /></p>
exports[`iconPlugin > should work with options -> { provider: "fontawesome", prefix } 2`] = `
"<p><VPIcon provider="fontawesome" size="32px" name="home" /></p>
"
`;
exports[`iconsPlugin > should work 3`] = `
"<p><strong>strong</strong>
<VPIcon name="mdi:11" />
<VPIcon name="mdi:11" /></p>
exports[`iconPlugin > should work with options -> { provider: "fontawesome", prefix } 3`] = `
"<p><VPIcon provider="fontawesome" color="#eee" name="home" /></p>
"
`;
exports[`iconsPlugin > should work with options 1`] = `
"<p><VPIcon name="mdi:11" size="1.25em" color="#ccc" /></p>
exports[`iconPlugin > should work with options -> { provider: "fontawesome", prefix } 4`] = `
"<p><VPIcon provider="fontawesome" size="32px" color="#eee" name="home" /></p>
"
`;
exports[`iconsPlugin > should work with options 2`] = `
"<p><strong>strong</strong> <VPIcon name="mdi:11" size="1.25em" color="#ccc" /> <VPIcon name="mdi:11" size="1.25em" color="#ccc" /></p>
exports[`iconPlugin > should work with options -> { provider: "fontawesome", prefix } 5`] = `
"<p><VPIcon provider="fontawesome" name="fas:home" /></p>
"
`;
exports[`iconsPlugin > should work with single icon options 1`] = `
"<p><VPIcon name="mdi:11" size="36px" /></p>
exports[`iconPlugin > should work with options -> { provider: "fontawesome", prefix } 6`] = `
"<p><VPIcon provider="fontawesome" name="fas:home" extra="2xl" data-fa-transform="shrink-8" /></p>
"
`;
exports[`iconsPlugin > should work with single icon options 2`] = `
"<p><VPIcon name="mdi:11" size="32px" color="#eee" /></p>
exports[`iconPlugin > should work with options -> { provider: "iconfont", prefix } 1`] = `
"<p><VPIcon provider="iconfont" name="home" /></p>
"
`;
exports[`iconsPlugin > should work with single icon options 3`] = `
"<p><VPIcon name="mdi:11" color="#eee" /></p>
exports[`iconPlugin > should work with options -> { provider: "iconfont", prefix } 2`] = `
"<p><VPIcon provider="iconfont" size="32px" name="home" /></p>
"
`;
exports[`iconsPlugin > should work with single icon options 4`] = `
"<p><VPIcon name="mdi:11" size="32px/" /></p>
exports[`iconPlugin > should work with options -> { provider: "iconfont", prefix } 3`] = `
"<p><VPIcon provider="iconfont" color="#eee" name="home" /></p>
"
`;
exports[`iconsPlugin > should work with single icon options 5`] = `
"<p><VPIcon name="mdi:11" class="/" /></p>
exports[`iconPlugin > should work with options -> { provider: "iconfont", prefix } 4`] = `
"<p><VPIcon provider="iconfont" size="32px" color="#eee" name="home" /></p>
"
`;
exports[`iconsPlugin > should work with single icon options 6`] = `
"<p><VPIcon name="mdi:11" size="1.25em" color="#ccc" /></p>
exports[`iconPlugin > should work with options -> { provider: "iconify", prefix } 1`] = `
"<p><VPIcon provider="iconify" name="11" /></p>
"
`;
exports[`iconsPlugin > should work with single icon options 7`] = `
"<p><VPIcon name="mdi:11" size="36px" color="#ccc" /></p>
exports[`iconPlugin > should work with options -> { provider: "iconify", prefix } 2`] = `
"<p><VPIcon provider="iconify" size="32px" color="#eee" name="11" /></p>
"
`;
exports[`iconsPlugin > should work with single icon options 8`] = `
"<p><VPIcon name="mdi:11" size="32px/#eee" color="#ccc" /></p>
exports[`iconPlugin > should work with options -> { provider: "iconify", prefix } 3`] = `
"<p><VPIcon provider="iconify" color="#eee" name="mdi:11" /></p>
"
`;
exports[`iconsPlugin > should work with single icon options 9`] = `
"<p><VPIcon name="mdi:11" size="1.25em" color="#eee" /></p>
"
`;
exports[`iconsPlugin > should work with single icon options 10`] = `
"<p><VPIcon name="mdi:11" size="32px/" color="#ccc" /></p>
"
`;
exports[`iconsPlugin > should work with single icon options 11`] = `
"<p><VPIcon name="mdi:11" size="1.25em" color="#ccc" class="/" /></p>
exports[`iconPlugin > should work with options -> { provider: "iconify", prefix } 4`] = `
"<p><VPIcon provider="iconify" size="32px/" name="fas:11" /></p>
"
`;

View File

@ -1,13 +1,13 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`npmToPlugin > should work width options: [npm, yarn, pnpm] 1`] = `
"<CodeTabs id="0" :data='[{"id":"npm"},{"id":"yarn"},{"id":"pnpm"}]' tab-id="npm-to-npm-yarn-pnpm"><template #title0="{ value, isActive }"><VPIcon name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #title2="{ value, isActive }"><VPIcon name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>
"<CodeTabs id="0" :data='[{"id":"npm"},{"id":"yarn"},{"id":"pnpm"}]' tab-id="npm-to-npm-yarn-pnpm"><template #title0="{ value, isActive }"><VPIcon provider="iconify" name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #title2="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>
</code></pre>
</div></template><template #tab1="{ value, isActive }"><div class="language-lang"><pre><code>
</code></pre>
</div></template><template #tab2="{ value, isActive }"><div class="language-lang"><pre><code>
</code></pre>
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"yarn"},{"id":"pnpm"}]' tab-id="npm-to-npm-yarn-pnpm"><template #title0="{ value, isActive }"><VPIcon name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #title2="{ value, isActive }"><VPIcon name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>npm install
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"yarn"},{"id":"pnpm"}]' tab-id="npm-to-npm-yarn-pnpm"><template #title0="{ value, isActive }"><VPIcon provider="iconify" name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #title2="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>npm install
</code></pre>
</div></template><template #tab1="{ value, isActive }"><div class="language-lang"><pre><code>yarn
@ -16,7 +16,7 @@ exports[`npmToPlugin > should work width options: [npm, yarn, pnpm] 1`] = `
</div></template><template #tab2="{ value, isActive }"><div class="language-lang"><pre><code>pnpm install
</code></pre>
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"yarn"},{"id":"pnpm"}]' tab-id="npm-to-npm-yarn-pnpm"><template #title0="{ value, isActive }"><VPIcon name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #title2="{ value, isActive }"><VPIcon name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>npm install
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"yarn"},{"id":"pnpm"}]' tab-id="npm-to-npm-yarn-pnpm"><template #title0="{ value, isActive }"><VPIcon provider="iconify" name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #title2="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>npm install
</code></pre>
</div></template><template #tab1="{ value, isActive }"><div class="language-lang"><pre><code>yarn
@ -25,7 +25,7 @@ exports[`npmToPlugin > should work width options: [npm, yarn, pnpm] 1`] = `
</div></template><template #tab2="{ value, isActive }"><div class="language-lang"><pre><code>pnpm install
</code></pre>
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"yarn"},{"id":"pnpm"}]' tab-id="npm-to-npm-yarn-pnpm"><template #title0="{ value, isActive }"><VPIcon name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #title2="{ value, isActive }"><VPIcon name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>npm install
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"yarn"},{"id":"pnpm"}]' tab-id="npm-to-npm-yarn-pnpm"><template #title0="{ value, isActive }"><VPIcon provider="iconify" name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #title2="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>npm install
</code></pre>
</div></template><template #tab1="{ value, isActive }"><div class="language-lang"><pre><code>yarn
@ -34,7 +34,7 @@ exports[`npmToPlugin > should work width options: [npm, yarn, pnpm] 1`] = `
</div></template><template #tab2="{ value, isActive }"><div class="language-lang"><pre><code>pnpm install
</code></pre>
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"yarn"},{"id":"pnpm"}]' tab-id="npm-to-npm-yarn-pnpm"><template #title0="{ value, isActive }"><VPIcon name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #title2="{ value, isActive }"><VPIcon name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>cross-env NODE_ENV=production npm run docs
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"yarn"},{"id":"pnpm"}]' tab-id="npm-to-npm-yarn-pnpm"><template #title0="{ value, isActive }"><VPIcon provider="iconify" name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #title2="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>cross-env NODE_ENV=production npm run docs
</code></pre>
</div></template><template #tab1="{ value, isActive }"><div class="language-lang"><pre><code>cross-env NODE_ENV=production yarn docs
@ -43,7 +43,7 @@ exports[`npmToPlugin > should work width options: [npm, yarn, pnpm] 1`] = `
</div></template><template #tab2="{ value, isActive }"><div class="language-lang"><pre><code>cross-env NODE_ENV=production pnpm docs
</code></pre>
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"yarn"},{"id":"pnpm"}]' tab-id="npm-to-npm-yarn-pnpm"><template #title0="{ value, isActive }"><VPIcon name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #title2="{ value, isActive }"><VPIcon name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>npm i -D package1 package2
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"yarn"},{"id":"pnpm"}]' tab-id="npm-to-npm-yarn-pnpm"><template #title0="{ value, isActive }"><VPIcon provider="iconify" name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #title2="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>npm i -D package1 package2
npm i --save-peer package3
npm run docs
@ -58,7 +58,7 @@ pnpm add --save-peer package3
pnpm docs
</code></pre>
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"yarn"},{"id":"pnpm"}]' tab-id="npm-to-npm-yarn-pnpm"><template #title0="{ value, isActive }"><VPIcon name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #title2="{ value, isActive }"><VPIcon name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>npm install && npm run docs
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"yarn"},{"id":"pnpm"}]' tab-id="npm-to-npm-yarn-pnpm"><template #title0="{ value, isActive }"><VPIcon provider="iconify" name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #title2="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>npm install && npm run docs
mkdir foo
</code></pre>
@ -70,7 +70,7 @@ mkdir foo
mkdir foo
</code></pre>
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"yarn"},{"id":"pnpm"}]' tab-id="npm-to-npm-yarn-pnpm"><template #title0="{ value, isActive }"><VPIcon name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #title2="{ value, isActive }"><VPIcon name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>npm run docs -- --clean-cache --clean-temp
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"yarn"},{"id":"pnpm"}]' tab-id="npm-to-npm-yarn-pnpm"><template #title0="{ value, isActive }"><VPIcon provider="iconify" name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #title2="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>npm run docs -- --clean-cache --clean-temp
</code></pre>
@ -82,7 +82,7 @@ mkdir foo
</code></pre>
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"pnpm"},{"id":"yarn"}]' tab-id="npm-to-npm-pnpm-yarn"><template #title0="{ value, isActive }"><VPIcon name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #title2="{ value, isActive }"><VPIcon name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>npm create vuepress-theme-plume@latest
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"pnpm"},{"id":"yarn"}]' tab-id="npm-to-npm-pnpm-yarn"><template #title0="{ value, isActive }"><VPIcon provider="iconify" name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #title2="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>npm create vuepress-theme-plume@latest
</code></pre>
</div></template><template #tab1="{ value, isActive }"><div class="language-lang"><pre><code>pnpm create vuepress-theme-plume@latest
@ -91,7 +91,7 @@ mkdir foo
</div></template><template #tab2="{ value, isActive }"><div class="language-lang"><pre><code>yarn create vuepress-theme-plume@latest
</code></pre>
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"pnpm"},{"id":"yarn"},{"id":"bun"},{"id":"deno"}]' tab-id="npm-to-npm-pnpm-yarn-bun-deno"><template #title0="{ value, isActive }"><VPIcon name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #title2="{ value, isActive }"><VPIcon name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #title3="{ value, isActive }"><VPIcon name="vscode-icons:file-type-bun"/><span>bun</span></template><template #title4="{ value, isActive }"><VPIcon name="vscode-icons:file-type-light-deno"/><span>deno</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>npx vp-update
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"pnpm"},{"id":"yarn"},{"id":"bun"},{"id":"deno"}]' tab-id="npm-to-npm-pnpm-yarn-bun-deno"><template #title0="{ value, isActive }"><VPIcon provider="iconify" name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #title2="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #title3="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-bun"/><span>bun</span></template><template #title4="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-light-deno"/><span>deno</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>npx vp-update
</code></pre>
</div></template><template #tab1="{ value, isActive }"><div class="language-lang"><pre><code>pnpm dlx vp-update
@ -106,7 +106,7 @@ mkdir foo
</div></template><template #tab4="{ value, isActive }"><div class="language-lang"><pre><code>deno run -A vp-update
</code></pre>
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"yarn"},{"id":"pnpm"}]' tab-id="npm-to-npm-yarn-pnpm"><template #title0="{ value, isActive }"><VPIcon name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #title2="{ value, isActive }"><VPIcon name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>mkdir foo
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"yarn"},{"id":"pnpm"}]' tab-id="npm-to-npm-yarn-pnpm"><template #title0="{ value, isActive }"><VPIcon provider="iconify" name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #title2="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>mkdir foo
</code></pre>
</div></template><template #tab1="{ value, isActive }"><div class="language-lang"><pre><code>mkdir foo
@ -121,13 +121,13 @@ mkdir foo
`;
exports[`npmToPlugin > should work width options: { tabs: [npm, yarn, pnpm] } 1`] = `
"<CodeTabs id="0" :data='[{"id":"npm"},{"id":"yarn"},{"id":"pnpm"}]' tab-id="npm-to-npm-yarn-pnpm"><template #title0="{ value, isActive }"><VPIcon name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #title2="{ value, isActive }"><VPIcon name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>
"<CodeTabs id="0" :data='[{"id":"npm"},{"id":"yarn"},{"id":"pnpm"}]' tab-id="npm-to-npm-yarn-pnpm"><template #title0="{ value, isActive }"><VPIcon provider="iconify" name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #title2="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>
</code></pre>
</div></template><template #tab1="{ value, isActive }"><div class="language-lang"><pre><code>
</code></pre>
</div></template><template #tab2="{ value, isActive }"><div class="language-lang"><pre><code>
</code></pre>
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"yarn"},{"id":"pnpm"}]' tab-id="npm-to-npm-yarn-pnpm"><template #title0="{ value, isActive }"><VPIcon name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #title2="{ value, isActive }"><VPIcon name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>npm install
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"yarn"},{"id":"pnpm"}]' tab-id="npm-to-npm-yarn-pnpm"><template #title0="{ value, isActive }"><VPIcon provider="iconify" name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #title2="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>npm install
</code></pre>
</div></template><template #tab1="{ value, isActive }"><div class="language-lang"><pre><code>yarn
@ -136,7 +136,7 @@ exports[`npmToPlugin > should work width options: { tabs: [npm, yarn, pnpm] } 1`
</div></template><template #tab2="{ value, isActive }"><div class="language-lang"><pre><code>pnpm install
</code></pre>
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"yarn"},{"id":"pnpm"}]' tab-id="npm-to-npm-yarn-pnpm"><template #title0="{ value, isActive }"><VPIcon name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #title2="{ value, isActive }"><VPIcon name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>npm install
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"yarn"},{"id":"pnpm"}]' tab-id="npm-to-npm-yarn-pnpm"><template #title0="{ value, isActive }"><VPIcon provider="iconify" name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #title2="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>npm install
</code></pre>
</div></template><template #tab1="{ value, isActive }"><div class="language-lang"><pre><code>yarn
@ -145,7 +145,7 @@ exports[`npmToPlugin > should work width options: { tabs: [npm, yarn, pnpm] } 1`
</div></template><template #tab2="{ value, isActive }"><div class="language-lang"><pre><code>pnpm install
</code></pre>
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"yarn"},{"id":"pnpm"}]' tab-id="npm-to-npm-yarn-pnpm"><template #title0="{ value, isActive }"><VPIcon name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #title2="{ value, isActive }"><VPIcon name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>npm install
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"yarn"},{"id":"pnpm"}]' tab-id="npm-to-npm-yarn-pnpm"><template #title0="{ value, isActive }"><VPIcon provider="iconify" name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #title2="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>npm install
</code></pre>
</div></template><template #tab1="{ value, isActive }"><div class="language-lang"><pre><code>yarn
@ -154,7 +154,7 @@ exports[`npmToPlugin > should work width options: { tabs: [npm, yarn, pnpm] } 1`
</div></template><template #tab2="{ value, isActive }"><div class="language-lang"><pre><code>pnpm install
</code></pre>
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"yarn"},{"id":"pnpm"}]' tab-id="npm-to-npm-yarn-pnpm"><template #title0="{ value, isActive }"><VPIcon name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #title2="{ value, isActive }"><VPIcon name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>cross-env NODE_ENV=production npm run docs
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"yarn"},{"id":"pnpm"}]' tab-id="npm-to-npm-yarn-pnpm"><template #title0="{ value, isActive }"><VPIcon provider="iconify" name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #title2="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>cross-env NODE_ENV=production npm run docs
</code></pre>
</div></template><template #tab1="{ value, isActive }"><div class="language-lang"><pre><code>cross-env NODE_ENV=production yarn docs
@ -163,7 +163,7 @@ exports[`npmToPlugin > should work width options: { tabs: [npm, yarn, pnpm] } 1`
</div></template><template #tab2="{ value, isActive }"><div class="language-lang"><pre><code>cross-env NODE_ENV=production pnpm docs
</code></pre>
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"yarn"},{"id":"pnpm"}]' tab-id="npm-to-npm-yarn-pnpm"><template #title0="{ value, isActive }"><VPIcon name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #title2="{ value, isActive }"><VPIcon name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>npm i -D package1 package2
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"yarn"},{"id":"pnpm"}]' tab-id="npm-to-npm-yarn-pnpm"><template #title0="{ value, isActive }"><VPIcon provider="iconify" name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #title2="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>npm i -D package1 package2
npm i --save-peer package3
npm run docs
@ -178,7 +178,7 @@ pnpm add --save-peer package3
pnpm docs
</code></pre>
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"yarn"},{"id":"pnpm"}]' tab-id="npm-to-npm-yarn-pnpm"><template #title0="{ value, isActive }"><VPIcon name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #title2="{ value, isActive }"><VPIcon name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>npm install && npm run docs
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"yarn"},{"id":"pnpm"}]' tab-id="npm-to-npm-yarn-pnpm"><template #title0="{ value, isActive }"><VPIcon provider="iconify" name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #title2="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>npm install && npm run docs
mkdir foo
</code></pre>
@ -190,7 +190,7 @@ mkdir foo
mkdir foo
</code></pre>
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"yarn"},{"id":"pnpm"}]' tab-id="npm-to-npm-yarn-pnpm"><template #title0="{ value, isActive }"><VPIcon name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #title2="{ value, isActive }"><VPIcon name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>npm run docs -- --clean-cache --clean-temp
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"yarn"},{"id":"pnpm"}]' tab-id="npm-to-npm-yarn-pnpm"><template #title0="{ value, isActive }"><VPIcon provider="iconify" name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #title2="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>npm run docs -- --clean-cache --clean-temp
</code></pre>
@ -202,7 +202,7 @@ mkdir foo
</code></pre>
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"pnpm"},{"id":"yarn"}]' tab-id="npm-to-npm-pnpm-yarn"><template #title0="{ value, isActive }"><VPIcon name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #title2="{ value, isActive }"><VPIcon name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>npm create vuepress-theme-plume@latest
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"pnpm"},{"id":"yarn"}]' tab-id="npm-to-npm-pnpm-yarn"><template #title0="{ value, isActive }"><VPIcon provider="iconify" name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #title2="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>npm create vuepress-theme-plume@latest
</code></pre>
</div></template><template #tab1="{ value, isActive }"><div class="language-lang"><pre><code>pnpm create vuepress-theme-plume@latest
@ -211,7 +211,7 @@ mkdir foo
</div></template><template #tab2="{ value, isActive }"><div class="language-lang"><pre><code>yarn create vuepress-theme-plume@latest
</code></pre>
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"pnpm"},{"id":"yarn"},{"id":"bun"},{"id":"deno"}]' tab-id="npm-to-npm-pnpm-yarn-bun-deno"><template #title0="{ value, isActive }"><VPIcon name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #title2="{ value, isActive }"><VPIcon name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #title3="{ value, isActive }"><VPIcon name="vscode-icons:file-type-bun"/><span>bun</span></template><template #title4="{ value, isActive }"><VPIcon name="vscode-icons:file-type-light-deno"/><span>deno</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>npx vp-update
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"pnpm"},{"id":"yarn"},{"id":"bun"},{"id":"deno"}]' tab-id="npm-to-npm-pnpm-yarn-bun-deno"><template #title0="{ value, isActive }"><VPIcon provider="iconify" name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #title2="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #title3="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-bun"/><span>bun</span></template><template #title4="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-light-deno"/><span>deno</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>npx vp-update
</code></pre>
</div></template><template #tab1="{ value, isActive }"><div class="language-lang"><pre><code>pnpm dlx vp-update
@ -226,7 +226,7 @@ mkdir foo
</div></template><template #tab4="{ value, isActive }"><div class="language-lang"><pre><code>deno run -A vp-update
</code></pre>
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"yarn"},{"id":"pnpm"}]' tab-id="npm-to-npm-yarn-pnpm"><template #title0="{ value, isActive }"><VPIcon name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #title2="{ value, isActive }"><VPIcon name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>mkdir foo
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"yarn"},{"id":"pnpm"}]' tab-id="npm-to-npm-yarn-pnpm"><template #title0="{ value, isActive }"><VPIcon provider="iconify" name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #title2="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>mkdir foo
</code></pre>
</div></template><template #tab1="{ value, isActive }"><div class="language-lang"><pre><code>mkdir foo
@ -241,13 +241,13 @@ mkdir foo
`;
exports[`npmToPlugin > should work with default options 1`] = `
"<CodeTabs id="0" :data='[{"id":"npm"},{"id":"pnpm"},{"id":"yarn"}]' tab-id="npm-to-npm-pnpm-yarn"><template #title0="{ value, isActive }"><VPIcon name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #title2="{ value, isActive }"><VPIcon name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>
"<CodeTabs id="0" :data='[{"id":"npm"},{"id":"pnpm"},{"id":"yarn"}]' tab-id="npm-to-npm-pnpm-yarn"><template #title0="{ value, isActive }"><VPIcon provider="iconify" name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #title2="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>
</code></pre>
</div></template><template #tab1="{ value, isActive }"><div class="language-lang"><pre><code>
</code></pre>
</div></template><template #tab2="{ value, isActive }"><div class="language-lang"><pre><code>
</code></pre>
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"pnpm"},{"id":"yarn"}]' tab-id="npm-to-npm-pnpm-yarn"><template #title0="{ value, isActive }"><VPIcon name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #title2="{ value, isActive }"><VPIcon name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>npm install
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"pnpm"},{"id":"yarn"}]' tab-id="npm-to-npm-pnpm-yarn"><template #title0="{ value, isActive }"><VPIcon provider="iconify" name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #title2="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>npm install
</code></pre>
</div></template><template #tab1="{ value, isActive }"><div class="language-lang"><pre><code>pnpm install
@ -256,7 +256,7 @@ exports[`npmToPlugin > should work with default options 1`] = `
</div></template><template #tab2="{ value, isActive }"><div class="language-lang"><pre><code>yarn
</code></pre>
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"pnpm"},{"id":"yarn"}]' tab-id="npm-to-npm-pnpm-yarn"><template #title0="{ value, isActive }"><VPIcon name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #title2="{ value, isActive }"><VPIcon name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>npm install
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"pnpm"},{"id":"yarn"}]' tab-id="npm-to-npm-pnpm-yarn"><template #title0="{ value, isActive }"><VPIcon provider="iconify" name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #title2="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>npm install
</code></pre>
</div></template><template #tab1="{ value, isActive }"><div class="language-lang"><pre><code>pnpm install
@ -265,7 +265,7 @@ exports[`npmToPlugin > should work with default options 1`] = `
</div></template><template #tab2="{ value, isActive }"><div class="language-lang"><pre><code>yarn
</code></pre>
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"pnpm"},{"id":"yarn"}]' tab-id="npm-to-npm-pnpm-yarn"><template #title0="{ value, isActive }"><VPIcon name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #title2="{ value, isActive }"><VPIcon name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>npm install
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"pnpm"},{"id":"yarn"}]' tab-id="npm-to-npm-pnpm-yarn"><template #title0="{ value, isActive }"><VPIcon provider="iconify" name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #title2="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>npm install
</code></pre>
</div></template><template #tab1="{ value, isActive }"><div class="language-lang"><pre><code>pnpm install
@ -274,7 +274,7 @@ exports[`npmToPlugin > should work with default options 1`] = `
</div></template><template #tab2="{ value, isActive }"><div class="language-lang"><pre><code>yarn
</code></pre>
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"pnpm"},{"id":"yarn"}]' tab-id="npm-to-npm-pnpm-yarn"><template #title0="{ value, isActive }"><VPIcon name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #title2="{ value, isActive }"><VPIcon name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>cross-env NODE_ENV=production npm run docs
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"pnpm"},{"id":"yarn"}]' tab-id="npm-to-npm-pnpm-yarn"><template #title0="{ value, isActive }"><VPIcon provider="iconify" name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #title2="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>cross-env NODE_ENV=production npm run docs
</code></pre>
</div></template><template #tab1="{ value, isActive }"><div class="language-lang"><pre><code>cross-env NODE_ENV=production pnpm docs
@ -283,7 +283,7 @@ exports[`npmToPlugin > should work with default options 1`] = `
</div></template><template #tab2="{ value, isActive }"><div class="language-lang"><pre><code>cross-env NODE_ENV=production yarn docs
</code></pre>
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"pnpm"},{"id":"yarn"}]' tab-id="npm-to-npm-pnpm-yarn"><template #title0="{ value, isActive }"><VPIcon name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #title2="{ value, isActive }"><VPIcon name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>npm i -D package1 package2
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"pnpm"},{"id":"yarn"}]' tab-id="npm-to-npm-pnpm-yarn"><template #title0="{ value, isActive }"><VPIcon provider="iconify" name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #title2="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>npm i -D package1 package2
npm i --save-peer package3
npm run docs
@ -298,7 +298,7 @@ yarn add --peer package3
yarn docs
</code></pre>
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"pnpm"},{"id":"yarn"}]' tab-id="npm-to-npm-pnpm-yarn"><template #title0="{ value, isActive }"><VPIcon name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #title2="{ value, isActive }"><VPIcon name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>npm install && npm run docs
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"pnpm"},{"id":"yarn"}]' tab-id="npm-to-npm-pnpm-yarn"><template #title0="{ value, isActive }"><VPIcon provider="iconify" name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #title2="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>npm install && npm run docs
mkdir foo
</code></pre>
@ -310,7 +310,7 @@ mkdir foo
mkdir foo
</code></pre>
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"pnpm"},{"id":"yarn"}]' tab-id="npm-to-npm-pnpm-yarn"><template #title0="{ value, isActive }"><VPIcon name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #title2="{ value, isActive }"><VPIcon name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>npm run docs -- --clean-cache --clean-temp
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"pnpm"},{"id":"yarn"}]' tab-id="npm-to-npm-pnpm-yarn"><template #title0="{ value, isActive }"><VPIcon provider="iconify" name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #title2="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>npm run docs -- --clean-cache --clean-temp
</code></pre>
@ -322,7 +322,7 @@ mkdir foo
</code></pre>
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"pnpm"},{"id":"yarn"}]' tab-id="npm-to-npm-pnpm-yarn"><template #title0="{ value, isActive }"><VPIcon name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #title2="{ value, isActive }"><VPIcon name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>npm create vuepress-theme-plume@latest
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"pnpm"},{"id":"yarn"}]' tab-id="npm-to-npm-pnpm-yarn"><template #title0="{ value, isActive }"><VPIcon provider="iconify" name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #title2="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>npm create vuepress-theme-plume@latest
</code></pre>
</div></template><template #tab1="{ value, isActive }"><div class="language-lang"><pre><code>pnpm create vuepress-theme-plume@latest
@ -331,7 +331,7 @@ mkdir foo
</div></template><template #tab2="{ value, isActive }"><div class="language-lang"><pre><code>yarn create vuepress-theme-plume@latest
</code></pre>
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"pnpm"},{"id":"yarn"},{"id":"bun"},{"id":"deno"}]' tab-id="npm-to-npm-pnpm-yarn-bun-deno"><template #title0="{ value, isActive }"><VPIcon name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #title2="{ value, isActive }"><VPIcon name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #title3="{ value, isActive }"><VPIcon name="vscode-icons:file-type-bun"/><span>bun</span></template><template #title4="{ value, isActive }"><VPIcon name="vscode-icons:file-type-light-deno"/><span>deno</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>npx vp-update
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"pnpm"},{"id":"yarn"},{"id":"bun"},{"id":"deno"}]' tab-id="npm-to-npm-pnpm-yarn-bun-deno"><template #title0="{ value, isActive }"><VPIcon provider="iconify" name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #title2="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #title3="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-bun"/><span>bun</span></template><template #title4="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-light-deno"/><span>deno</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>npx vp-update
</code></pre>
</div></template><template #tab1="{ value, isActive }"><div class="language-lang"><pre><code>pnpm dlx vp-update
@ -346,7 +346,7 @@ mkdir foo
</div></template><template #tab4="{ value, isActive }"><div class="language-lang"><pre><code>deno run -A vp-update
</code></pre>
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"pnpm"},{"id":"yarn"}]' tab-id="npm-to-npm-pnpm-yarn"><template #title0="{ value, isActive }"><VPIcon name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #title2="{ value, isActive }"><VPIcon name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>mkdir foo
</div></template></CodeTabs><CodeTabs id="0" :data='[{"id":"npm"},{"id":"pnpm"},{"id":"yarn"}]' tab-id="npm-to-npm-pnpm-yarn"><template #title0="{ value, isActive }"><VPIcon provider="iconify" name="logos:npm-icon"/><span>npm</span></template><template #title1="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-light-pnpm"/><span>pnpm</span></template><template #title2="{ value, isActive }"><VPIcon provider="iconify" name="vscode-icons:file-type-yarn"/><span>yarn</span></template><template #tab0="{ value, isActive }"><div class="language-lang"><pre><code>mkdir foo
</code></pre>
</div></template><template #tab1="{ value, isActive }"><div class="language-lang"><pre><code>mkdir foo

View File

@ -16,7 +16,7 @@ exports[`timeline > timelinePlugin() > should work 1`] = `
</li>
</ul>
</VPTimelineItem><VPTimelineItem time="q2" color="red"><template #title>这是标题</template><p>这是内容</p>
</VPTimelineItem></VPTimeline><VPTimeline placement="right" :card="undefined"><VPTimelineItem icon="xxx" card type="warning"><template #icon><VPIcon name="xxx"/></template><template #title>这是标题</template><p>这是内容</p>
</VPTimelineItem></VPTimeline><VPTimeline placement="right" :card="undefined"><VPTimelineItem icon="xxx" card type="warning"><template #icon><VPIcon provider="iconify" name="xxx"/></template><template #title>这是标题</template><p>这是内容</p>
</VPTimelineItem><VPTimelineItem type="danger" line="dotted" :card="undefined"><template #title>这是标题</template><p>这是内容</p>
</VPTimelineItem></VPTimeline><VPTimeline placement="between" :card="undefined"><VPTimelineItem card placement="right"><template #title>这是标题</template><p>这是内容</p>
</VPTimelineItem><VPTimelineItem card placement="left"><template #title>这是标题</template><p>这是内容</p>

View File

@ -1,39 +1,58 @@
import MarkdownIt from 'markdown-it'
import { describe, expect, it } from 'vitest'
import { iconPlugin } from '../src/node/inline/icons.js'
import { iconPlugin } from '../src/node/icon/icon.js'
describe('iconsPlugin', () => {
it('should work', () => {
describe('iconPlugin', () => {
it('should work with default', () => {
const md = MarkdownIt().use(iconPlugin)
expect(md.render('::mdi:11::')).toMatchSnapshot()
expect(md.render('**strong** ::mdi:11:: ::mdi:11::')).toMatchSnapshot()
expect(md.render('**strong**\n::mdi:11::\n ::mdi:11::')).toMatchSnapshot()
expect(md.render('::mdi:11::')).toContain('<VPIcon provider="iconify" name="mdi:11" />')
expect(md.render('::mdi:11 =32px::')).toContain('<VPIcon provider="iconify" size="32px" name="mdi:11" />')
expect(md.render('::mdi:11 /#fff::')).toContain('<VPIcon provider="iconify" color="#fff" name="mdi:11" />')
expect(md.render('::mdi:11 =32px /#fff::')).toContain('<VPIcon provider="iconify" size="32px" color="#fff" name="mdi:11" />')
expect(md.render('::mdi:11 =32px /#fff fa data-fa-transform="shrink-8::"'))
.toContain('<VPIcon provider="iconify" size="32px" color="#fff" name="mdi:11" extra="fa" data-fa-transform=""shrink-8" />')
expect(md.render('::iconify mdi:11::')).toContain('<VPIcon provider="iconify" name="mdi:11" />')
})
it('should work with options', () => {
it('should work with options -> { size, color }', () => {
const md = MarkdownIt().use(iconPlugin, { size: '1.25em', color: '#ccc' })
expect(md.render('::mdi:11::')).toMatchSnapshot()
expect(md.render('**strong** ::mdi:11:: ::mdi:11::')).toMatchSnapshot()
expect(md.render('::mdi:11::')).toContain('<VPIcon provider="iconify" size="1.25em" color="#ccc" name="mdi:11" />')
expect(md.render('::mdi:11 =32px::')).toContain('<VPIcon provider="iconify" size="32px" color="#ccc" name="mdi:11" />')
expect(md.render('::mdi:11 /#fff::')).toContain('<VPIcon provider="iconify" size="1.25em" color="#fff" name="mdi:11" />')
expect(md.render('::mdi:11 =32px /#fff::')).toContain('<VPIcon provider="iconify" size="32px" color="#fff" name="mdi:11" />')
})
it('should work with single icon options', () => {
const md = MarkdownIt().use(iconPlugin)
it('should work with options -> { provider: "iconify", prefix } ', () => {
const md = MarkdownIt().use(iconPlugin, { prefix: 'mdi' })
expect(md.render('::mdi:11 =36px::')).toMatchSnapshot()
expect(md.render('::mdi:11 =32px /#eee::')).toMatchSnapshot()
expect(md.render('::11::')).toMatchSnapshot()
expect(md.render('::11 =32px /#eee::')).toMatchSnapshot()
expect(md.render('::mdi:11 /#eee::')).toMatchSnapshot()
expect(md.render('::mdi:11 =32px/::')).toMatchSnapshot()
expect(md.render('::mdi:11 /::')).toMatchSnapshot()
expect(md.render('::fas:11 =32px/::')).toMatchSnapshot()
})
const md2 = MarkdownIt().use(iconPlugin, { size: '1.25em', color: '#ccc' })
expect(md2.render('::mdi:11::')).toMatchSnapshot()
expect(md2.render('::mdi:11 =36px::')).toMatchSnapshot()
expect(md2.render('::mdi:11 =32px/#eee::')).toMatchSnapshot()
expect(md2.render('::mdi:11 /#eee::')).toMatchSnapshot()
expect(md2.render('::mdi:11 =32px/::')).toMatchSnapshot()
expect(md2.render('::mdi:11 /::')).toMatchSnapshot()
it('should work with options -> { provider: "iconfont", prefix } ', () => {
const md = MarkdownIt().use(iconPlugin, { provider: 'iconfont', prefix: 'iconfont icon-' })
expect(md.render('::home::')).toMatchSnapshot()
expect(md.render('::home =32px::')).toMatchSnapshot()
expect(md.render('::home /#eee::')).toMatchSnapshot()
expect(md.render('::home =32px /#eee::')).toMatchSnapshot()
})
it('should work with options -> { provider: "fontawesome", prefix } ', () => {
const md = MarkdownIt().use(iconPlugin, { provider: 'fontawesome', prefix: 'iconfont icon-' })
expect(md.render('::home::')).toMatchSnapshot()
expect(md.render('::home =32px::')).toMatchSnapshot()
expect(md.render('::home /#eee::')).toMatchSnapshot()
expect(md.render('::home =32px /#eee::')).toMatchSnapshot()
expect(md.render('::fas:home::')).toMatchSnapshot()
expect(md.render('::fas:home 2xl data-fa-transform="shrink-8"::')).toMatchSnapshot()
})
it('should not work with invalid icon', () => {
@ -42,7 +61,6 @@ describe('iconsPlugin', () => {
expect(md.render(':: mdi:11 ::')).toMatchSnapshot()
expect(md.render('::::')).toMatchSnapshot()
expect(md.render('::]&')).toMatchSnapshot()
expect(md.render('::::')).toMatchSnapshot()
expect(md.render('::mdi:11')).toMatchSnapshot()
})
})

View File

@ -1,10 +1,5 @@
import type { MarkdownPowerPluginOptions } from '../shared/index.js'
declare const __MD_POWER_INJECT_OPTIONS__: MarkdownPowerPluginOptions
declare const __MD_POWER_DASHJS_INSTALLED__: boolean
declare const __MD_POWER_HLSJS_INSTALLED__: boolean
declare const __MD_POWER_MPEGTSJS_INSTALLED__: boolean
export const pluginOptions: MarkdownPowerPluginOptions = __MD_POWER_INJECT_OPTIONS__
export const installed: {

View File

@ -12,3 +12,12 @@ declare module '@internal/md-power/replEditorData' {
const res: ReplEditorData
export default res
}
declare global {
const __MD_POWER_INJECT_OPTIONS__: MarkdownPowerPluginOptions
const __MD_POWER_DASHJS_INSTALLED__: boolean
const __MD_POWER_HLSJS_INSTALLED__: boolean
const __MD_POWER_MPEGTSJS_INSTALLED__: boolean
}

View File

@ -52,7 +52,7 @@ export const codeTabs: PluginWithOptions<CodeTabsOptions> = (md, options: CodeTa
const titlesContent = titles.map((title, index) => {
const icon = getIcon(title)
return `<template #title${index}="{ value, isActive }">${icon ? `<VPIcon name="${icon}"/>` : ''}<span>${title}</span></template>`
return `<template #title${index}="{ value, isActive }">${icon ? `<VPIcon provider="iconify" name="${icon}"/>` : ''}<span>${title}</span></template>`
}).join('')
return `<CodeTabs id="${index}" :data='${stringifyProp(tabsData)}'${active === -1 ? '' : ` :active="${active}"`}${meta.id ? ` tab-id="${meta.id as string}"` : ''}>${titlesContent}`

View File

@ -129,7 +129,7 @@ export function codeTreePlugin(md: Markdown, app: App, options: CodeTreeOptions
filepath: node.filepath,
}
return `<FileTreeNode${stringifyAttrs(props)}>
<template #icon><VPIcon name="${getIcon(node.filename, props.type, mode)}" /></template>
<template #icon><VPIcon provider="iconify" name="${getIcon(node.filename, props.type, mode)}" /></template>
${node.children?.length ? renderFileTree(node.children, mode) : ''}
</FileTreeNode>`
})

View File

@ -117,7 +117,7 @@ export function fileTreePlugin(md: Markdown, options: FileTreeOptions = {}): voi
? `<template #comment>${md.renderInline(comment.replaceAll('#', '\#'))}</template>`
: ''
const renderedIcon = !isOmit
? `<template #icon><VPIcon name="${getIcon(filename, nodeType, meta.icon)}" /></template>`
? `<template #icon><VPIcon provider="iconify" name="${getIcon(filename, nodeType, meta.icon)}" /></template>`
: ''
const props: FileTreeNodeProps = {
expanded: nodeType === 'folder' ? expanded : false,

View File

@ -57,7 +57,7 @@ export function timelinePlugin(md: Markdown): void {
const attrs = token.meta as TimelineItemMeta
attrs.card ??= undefined
const icon = attrs.icon
return `<VPTimelineItem${stringifyAttrs(attrs, true)}>${icon ? `<template #icon><VPIcon name="${icon}"/></template>` : ''}`
return `<VPTimelineItem${stringifyAttrs(attrs, true)}>${icon ? `<template #icon><VPIcon provider="iconify" name="${icon}"/></template>` : ''}`
}
md.renderer.rules.timeline_item_close = () => '</VPTimelineItem>'

View File

@ -0,0 +1,4 @@
# 图标插件
- 语法支持
- 其他跟图标关联的功能增强

View File

@ -0,0 +1,81 @@
import type { RuleInline } from 'markdown-it/lib/parser_inline.mjs'
export function createIconRule(
[l1, l2, r1, r2]: readonly [number, number, number, number],
deprecated?: boolean,
): RuleInline {
return (state, silent) => {
let found = false
const max = state.posMax
const start = state.pos
// ::xxx
// ^^
if (
state.src.charCodeAt(start) !== l1
|| state.src.charCodeAt(start + 1) !== l2
) {
return false
}
const next = state.src.charCodeAt(start + 2)
// :: xxx | :::xxx
// ^ | ^
if (next === 0x20 || next === 0x3A)
return false
/* istanbul ignore if -- @preserve */
if (silent)
return false
// ::::
if (max - start < 5)
return false
state.pos = start + 2
while (state.pos < max) {
// ::xxx::
// ^^
if (
state.src.charCodeAt(state.pos) === r1
&& state.src.charCodeAt(state.pos + 1) === r2
) {
found = true
break
}
state.md.inline.skipToken(state)
}
if (
!found
|| start + 2 === state.pos
// ::xxx ::
// ^
|| state.src.charCodeAt(state.pos - 1) === 0x20
) {
state.pos = start
return false
}
const info = state.src.slice(start + 2, state.pos)
// found
state.posMax = state.pos
state.pos = start + 2
const icon = state.push('icon', 'i', 0)
icon.markup = '::'
icon.content = info
icon.meta = { deprecated }
state.pos = state.posMax + 2
state.posMax = max
return true
}
}

View File

@ -0,0 +1,50 @@
import type { PluginWithOptions } from 'markdown-it'
import type { MarkdownEnv } from 'vuepress/markdown'
import type { IconOptions } from '../../shared/index.js'
import { colors } from 'vuepress/utils'
import { stringifyAttrs } from '../utils/stringifyAttrs.js'
import { createIconRule } from './createIconRule.js'
import { resolveIcon } from './resolveIcon.js'
function iconRender(content: string, options: IconOptions): string {
const icon = resolveIcon(content, options)
return `<VPIcon${stringifyAttrs(icon)} />`
}
export const iconPlugin: PluginWithOptions<IconOptions> = (md, options = {}) => {
/**
* ::collect:icon_name =size /color::
*/
md.inline.ruler.before(
'link',
'icon',
// : : : :
createIconRule([0x3A, 0x3A, 0x3A, 0x3A]),
)
/**
* :[collect:icon_name size/color]:
* @deprecated
*/
md.inline.ruler.before(
'link',
'icon_deprecated',
// : [ ] :
createIconRule([0x3A, 0x5B, 0x5D, 0x3A], true),
)
md.renderer.rules.icon = (tokens, idx, _, env: MarkdownEnv) => {
const { content, meta } = tokens[idx]
let icon = content
/* istanbul ignore if -- @preserve */
if (meta.deprecated) {
const [name, opt = ''] = content.split(' ')
const [size, color] = opt.trim().split('/')
icon = `${name}${size ? ` =${size}` : ''}${color ? ` /${color}` : ''}`
console.warn(`The icon syntax of \`${colors.yellow(`:[${content}]:`)}\` is deprecated, please use \`${colors.green(`::${icon}::`)}\` instead. (${colors.gray(env.filePathRelative || env.filePath)})`)
}
return iconRender(icon, options)
}
}

View File

@ -0,0 +1,46 @@
/**
* # Icon
*
* ## syntax
*
* ::name::
* ::provide name::
* ::provide name =size /color extra::
*
* ## options
*
* - provide: iconify | iconfont | fontawesome
* - prefix:
* - iconify: collect:name - prefix = "collect"
* - iconfont: iconfont icon-name - prefix = "iconfont icon-"
* - fontawesome: fa-solid fa-name -> fas:name - prefix = "fas"
*
* - assets: url[]
*
* ## iconify
*
* - full syntax
* ::fluent-mdl2:toggle-filled::
* ::fluent-mdl2:toggle-filled =128px /#fff::
*
* - prefix: fluent-mdl2
* ::toggle-filled::
* ::toggle-filled =128px /#fff::
*
* ### iconfont
*
* ::name::
* ::name =size /color::
*
* ### fontawesome
*
* ::fa-solid:name:: -> ::fas:name:: -> ::s:name:: -> ::name::
* ::fa-brands:name:: -> ::fab:name:: -> ::b:name::
* ::fa-regular:name:: -> ::far:name:: -> ::r:name::
*
* ::name fa-sm::
*
* @deprecated :[fluent-mdl2:toggle-filled 128px/#fff]:
*/
export * from './icon.js'
export * from './prepareIcon.js'

View File

@ -0,0 +1,88 @@
import type { IconOptions } from '../../shared/index.js'
import { notNullish, toArray, uniqueBy } from '@pengzhanbo/utils'
import { isLinkAbsolute } from '@vuepress/helper'
import { isLinkHttp } from 'vuepress/shared'
interface AssetInfo {
type: 'style' | 'script'
link: string
provide?: string
}
function getFontAwesomeCDNLink(type: string): string {
return `https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6/js/${type}.min.js`
}
export function prepareIcon(
imports: Set<string>,
options: IconOptions = {},
): string {
const setupContent: string[] = []
const assets: AssetInfo[] = []
if (options.provider === 'iconfont') {
assets.push(
...toArray(options.assets)
.map(asset => normalizeAsset(asset))
.filter(notNullish),
)
}
else if (options.provider === 'fontawesome') {
assets.push(...toArray(options.assets || 'fontawesome').map((asset) => {
if (asset === 'fontawesome') {
return ['solid', 'regular', 'fontawesome']
.map(getFontAwesomeCDNLink)
.map(asset => normalizeAsset(asset, 'fontawesome'))
}
if (asset === 'fontawesome-with-brands') {
return normalizeAsset(getFontAwesomeCDNLink('brands'), 'fontawesome')
}
return null
}).flat().filter(notNullish))
}
let hasStyle = false
let hasScript = false
for (const asset of uniqueBy(assets, (a, b) => a.link === b.link)) {
if (asset.type === 'style') {
hasStyle = true
setupContent.push(`useStyleTag('@import url("${asset.link}");')`)
}
else if (asset.type === 'script') {
hasScript = true
setupContent.push(asset.provide === 'fontawesome'
? `useScriptTag("${asset.link}", () => {}, { attrs: { "data-auto-replace-svg": "nest" } })`
: `useScriptTag("${asset.link}")`,
)
}
}
if (hasScript || hasStyle) {
const exports: string[] = []
if (hasScript)
exports.push('useScriptTag')
if (hasStyle)
exports.push('useStyleTag')
imports.add(`import { ${exports.join(', ')} } from '@vueuse/core'`)
}
return setupContent.join('\n ')
}
function normalizeAsset(asset: string, provide?: string): AssetInfo | null {
const link = normalizeLink(asset)
if (asset.endsWith('.js')) {
return { type: 'script', link, provide }
}
if (asset.endsWith('.css')) {
return { type: 'style', link, provide }
}
console.error(`[vuepress:icon] Can not recognize icon link: "${asset}"`)
return null
}
function normalizeLink(link: string): string {
if (isLinkHttp(link) || isLinkAbsolute(link))
return link
return `//${link}`
}

View File

@ -0,0 +1,57 @@
import type { IconOptions } from '../../shared/index.js'
import { kebabCase, omit } from '@pengzhanbo/utils'
import { resolveAttrs } from '../utils/resolveAttrs.js'
export interface ResolvedIcon {
provider: Exclude<IconOptions['provider'], undefined>
size?: string | number
color?: string
name: string
extra?: string
}
const RE_SIZE = /(?<=\s|^)=(.+?)(?:\s|$)/
const RE_COLOR = /(?<=\s|^)\/(.+?)(?:\s|$)/
const RE_PROVIDER = /^(iconify|iconfont|fontawesome)\s+/
const RE_EXTRA_KEY = /(?:^|-)\d-/g
export function resolveIcon(content: string, options: IconOptions): ResolvedIcon {
let size = options.size
let color = options.color
let provider = options.provider || 'iconify'
content = content
.replace(RE_PROVIDER, (_, p) => {
provider = p
return ''
})
.replace(RE_SIZE, (_, s) => {
size = s
return ''
})
.replace(RE_COLOR, (_, c) => {
color = c
return ''
})
.trim()
const index = content.indexOf(' ')
const name = index === -1 ? content : content.slice(0, index)
const extra = index === -1 ? '' : content.slice(index + 1)
const props = { provider, size, color, name }
if (!extra) {
return props
}
const { attrs } = resolveAttrs(extra)
const info: string[] = []
const excludes: string[] = []
for (const key in attrs) {
if (attrs[key] === true) {
excludes.push(key)
info.push(kebabCase(key).replace(RE_EXTRA_KEY, m => `${m.slice(0, -1)}`))
}
}
return { ...props, extra: info.join(' '), ...omit(attrs, excludes) }
}

View File

@ -1,155 +0,0 @@
/**
* ::fluent-mdl2:toggle-filled::
* ::fluent-mdl2:toggle-filled /#fff::
* ::fluent-mdl2:toggle-filled =128px /#fff::
*
* @deprecated :[fluent-mdl2:toggle-filled 128px/#fff]:
*/
import type { PluginWithOptions } from 'markdown-it'
import type { RuleInline } from 'markdown-it/lib/parser_inline.mjs'
import type { MarkdownEnv } from 'vuepress/markdown'
import type { IconsOptions } from '../../shared/index.js'
import { colors } from 'vuepress/utils'
import { stringifyAttrs } from '../utils/stringifyAttrs.js'
function createIconRule(
[l1, l2, r1, r2]: readonly [number, number, number, number],
deprecated?: boolean,
): RuleInline {
return (state, silent) => {
let found = false
const max = state.posMax
const start = state.pos
// ::xxx
// ^^
if (
state.src.charCodeAt(start) !== l1
|| state.src.charCodeAt(start + 1) !== l2
) {
return false
}
const next = state.src.charCodeAt(start + 2)
// :: xxx | :::xxx
// ^ | ^
if (next === 0x20 || next === 0x3A)
return false
/* istanbul ignore if -- @preserve */
if (silent)
return false
// ::::
if (max - start < 5)
return false
state.pos = start + 2
while (state.pos < max) {
// ::xxx::
// ^^
if (
state.src.charCodeAt(state.pos) === r1
&& state.src.charCodeAt(state.pos + 1) === r2
) {
found = true
break
}
state.md.inline.skipToken(state)
}
if (
!found
|| start + 2 === state.pos
// ::xxx ::
// ^
|| state.src.charCodeAt(state.pos - 1) === 0x20
) {
state.pos = start
return false
}
const info = state.src.slice(start + 2, state.pos)
// found
state.posMax = state.pos
state.pos = start + 2
const icon = state.push('icon', 'i', 0)
icon.markup = '::'
icon.content = info
icon.meta = { deprecated }
state.pos = state.posMax + 2
state.posMax = max
return true
}
}
const RE_SIZE = /(?<=\s|^)=(.+?)(?:\s|$)/
const RE_COLOR = /(?<=\s|^)\/(.+?)(?:\s|$)/
function iconRender(content: string, options: IconsOptions): string {
let size = options.size
let color = options.color
content = content
.replace(RE_SIZE, (_, s) => {
size = s
return ''
})
.replace(RE_COLOR, (_, c) => {
color = c
return ''
})
.trim()
const [name, ...extra] = content.split(/\s+/)
return `<VPIcon${stringifyAttrs({ name, size, color, class: extra.length ? extra.join(' ') : undefined })} />`
}
export const iconPlugin: PluginWithOptions<IconsOptions> = (md, options = {}) => {
/**
* ::collect:icon_name =size /color::
*/
md.inline.ruler.before(
'link',
'icon',
// : : : :
createIconRule([0x3A, 0x3A, 0x3A, 0x3A]),
)
/**
* :[collect:icon_name size/color]:
* @deprecated
*/
md.inline.ruler.before(
'link',
'icon_deprecated',
// : [ ] :
createIconRule([0x3A, 0x5B, 0x5D, 0x3A], true),
)
md.renderer.rules.icon = (tokens, idx, _, env: MarkdownEnv) => {
const { content, meta } = tokens[idx]
let icon = content
/* istanbul ignore if -- @preserve */
if (meta.deprecated) {
const [name, opt = ''] = content.split(' ')
const [size, color] = opt.trim().split('/')
icon = `${name}${size ? ` =${size}` : ''}${color ? ` /${color}` : ''}`
console.warn(`The icon syntax of \`${colors.yellow(`:[${content}]:`)}\` is deprecated, please use \`${colors.green(`::${icon}::`)}\` instead. (${colors.gray(env.filePathRelative || env.filePath)})`)
}
return iconRender(icon, options)
}
}

View File

@ -9,7 +9,6 @@ import { tasklist } from '@mdit/plugin-tasklist'
import { isPlainObject } from '@vuepress/helper'
import { abbrPlugin } from './abbr.js'
import { annotationPlugin } from './annotation.js'
import { iconPlugin } from './icons.js'
import { plotPlugin } from './plot.js'
export function inlineSyntaxPlugin(
@ -41,11 +40,6 @@ export function inlineSyntaxPlugin(
md.use(abbrPlugin)
}
if (options.icons) {
// ::collect:name::
md.use(iconPlugin, isPlainObject(options.icons) ? options.icons : {})
}
if (
options.plot === true
|| (isPlainObject(options.plot) && options.plot.tag !== false)

View File

@ -1,15 +1,17 @@
import type { Plugin } from 'vuepress/core'
import type { MarkdownPowerPluginOptions } from '../shared/index.js'
import { isPlainObject } from '@pengzhanbo/utils'
import { addViteOptimizeDepsInclude } from '@vuepress/helper'
import { isPackageExists } from 'local-pkg'
import { extendsPageWithCodeTree } from './container/codeTree.js'
import { containerPlugin } from './container/index.js'
import { demoPlugin, demoWatcher, extendsPageWithDemo, waitDemoRender } from './demo/index.js'
import { embedSyntaxPlugin } from './embed/index.js'
import { docsTitlePlugin } from './enhance/docsTitle.js'
import { imageSizePlugin } from './enhance/imageSize.js'
import { iconPlugin } from './icon/index.js'
import { inlineSyntaxPlugin } from './inline/index.js'
import { prepareConfigFile } from './prepareConfigFile.js'
import { provideData } from './provideData.js'
export function markdownPowerPlugin(
options: MarkdownPowerPluginOptions = {},
@ -19,12 +21,7 @@ export function markdownPowerPlugin(
clientConfigFile: app => prepareConfigFile(app, options),
define: {
__MD_POWER_INJECT_OPTIONS__: options,
__MD_POWER_DASHJS_INSTALLED__: isPackageExists('dashjs'),
__MD_POWER_HLSJS_INSTALLED__: isPackageExists('hls.js'),
__MD_POWER_MPEGTSJS_INSTALLED__: isPackageExists('mpegts.js'),
},
define: provideData(options),
extendsBundlerOptions(bundlerOptions, app) {
if (options.repl) {
@ -47,6 +44,7 @@ export function markdownPowerPlugin(
docsTitlePlugin(md)
embedSyntaxPlugin(md, options)
inlineSyntaxPlugin(md, options)
iconPlugin(md, options.icon ?? (isPlainObject(options.icons) ? options.icons : {}))
if (options.demo)
demoPlugin(app, md)

View File

@ -2,6 +2,7 @@ import type { App } from 'vuepress/core'
import type { MarkdownPowerPluginOptions } from '../shared/index.js'
import { ensureEndingSlash } from '@vuepress/helper'
import { getDirname, path } from 'vuepress/utils'
import { prepareIcon } from './icon/index.js'
const { url: filepath } = import.meta
const __dirname = getDirname(filepath)
@ -130,6 +131,8 @@ export async function prepareConfigFile(app: App, options: MarkdownPowerPluginOp
enhances.add(`app.component('VPField', VPField)`)
}
const setupIcon = prepareIcon(imports, options.icon)
return app.writeTemp(
'md-power/config.js',
`\
@ -143,6 +146,9 @@ export default defineClientConfig({
${Array.from(enhances.values())
.map(item => ` ${item}`)
.join('\n')}
},
setup() {
${setupIcon}
}
})
`,

View File

@ -0,0 +1,19 @@
import type { MarkdownPowerPluginOptions } from '../shared/index.js'
import { isPackageExists } from 'local-pkg'
export function provideData(options: MarkdownPowerPluginOptions): Record<string, unknown> {
const mardownOptions = {
plot: options.plot,
pdf: options.pdf,
}
const icon = options.icon ?? { provider: 'iconify' }
return {
__MD_POWER_INJECT_OPTIONS__: mardownOptions,
__MD_POWER_DASHJS_INSTALLED__: isPackageExists('dashjs'),
__MD_POWER_HLSJS_INSTALLED__: isPackageExists('hls.js'),
__MD_POWER_MPEGTSJS_INSTALLED__: isPackageExists('mpegts.js'),
__MD_POWER_ICON_PROVIDER__: icon.provider || 'iconify',
__MD_POWER_ICON_PREFIX__: icon.prefix || '',
}
}

View File

@ -0,0 +1,87 @@
export type IconOptions = IconifyProvider | IconFontProvider | FontAwesomeProvider
export interface IconProviderBase {
/**
* The provider of the icon
*
*
* @default 'iconify'
*/
provider?: 'iconify' | 'iconfont' | 'fontawesome'
/**
* The size of the icon
* @default '1em'
*/
size?: string | number
/**
* The color of the icon
* @default 'currentColor'
*/
color?: string
}
export interface IconFontProvider extends IconProviderBase {
provider?: 'iconfont'
/**
* The prefix of the iconfont
* @default 'iconfont icon-'
*/
prefix?: string
/**
* The assets of the iconfont
*/
assets?: IconAssetLink | IconAssetLink[]
}
export interface FontAwesomeProvider extends IconProviderBase {
provider?: 'fontawesome'
/**
* The prefix of the fontawesome icon
* @default 'fas'
*/
prefix?: LiteralUnion<FontAwesomePrefix>
/**
* The assets of the fontawesome
* @default 'fontawesome'
*/
assets?: FontAwesomeAssetBuiltIn | IconAssetLink | (IconAssetLink | FontAwesomeAssetBuiltIn)[]
}
export interface IconifyProvider extends IconProviderBase {
provider?: 'iconify'
/**
* The prefix of the icon
* @default ''
*/
prefix?: LiteralUnion<IconifyPrefix>
}
export type FontAwesomeAssetBuiltIn = 'fontawesome' | 'fontawesome-with-brands'
export type IconAssetLink = `//${string}` | `//${string}` | `https://${string}` | `http://${string}`
export type FontAwesomePrefix =
| 'fas' | 's' // fa-solid fa-name
| 'far' | 'r' // fa-regular fa-name
| 'fal' | 'l' // fa-light fa-name
| 'fat' | 't' // fa-thin fa-name
| 'fads' | 'ds' // fa-duotone fa-solid fa-name
| 'fass' | 'ss' // fa-sharp fa-solid fa-name
| 'fasr' | 'sr' // fa-sharp fa-regular fa-name
| 'fasl' | 'sl' // fa-sharp fa-light fa-name
| 'fast' | 'st' // fa-sharp fa-thin fa-name
| 'fasds' | 'sds' // fa-sharp-duotone fa-solid fa-name
| 'fab' | 'b' // fa-brands fa-name
export type IconifyPrefix = 'material-symbols' | 'material-symbols-light' | 'ic' | 'mdi' | 'mdi-light' | 'line-md' | 'solar' | 'tabler' | 'hugeicons' | 'mingcute' | 'ri' | 'mynaui' | 'iconamoon' | 'iconoir' | 'lucide' | 'lucide-lab' | 'uil' | 'tdesign' | 'si' | 'bx' | 'bxs' | 'majesticons' | 'gg' | 'flowbite' | 'basil' | 'pixelarticons' | 'pixel' | 'akar-icons' | 'ci' | 'proicons' | 'typcn' | 'meteor-icons' | 'prime' | 'circum' | 'fe' | 'eos-icons' | 'bitcoin-icons' | 'humbleicons' | 'uim' | 'uit' | 'uis' | 'gridicons' | 'mi' | 'cuida' | 'weui' | 'duo-icons' | 'svg-spinners' | 'lets-icons' | 'mage' | 'stash' | 'lineicons' | 'icon-park-outline' | 'icon-park-solid' | 'icon-park-twotone' | 'jam' | 'guidance' | 'carbon' | 'ion' | 'famicons' | 'ant-design' | 'lsicon' | 'gravity-ui' | 'cil' | 'ep' | 'charm' | 'quill' | 'bytesize' | 'bi' | 'rivet-icons' | 'nimbus' | 'formkit' | 'fluent' | 'ph' | 'teenyicons' | 'clarity' | 'ix' | 'octicon' | 'memory' | 'system-uicons' | 'radix-icons' | 'zondicons' | 'uiw' | 'maki' | 'codex' | 'ei' | 'heroicons' | 'pepicons-pop' | 'pepicons-print' | 'pepicons-pencil' | 'f7' | 'pajamas' | 'garden' | 'streamline' | 'fa6-solid' | 'fa6-regular' | 'picon' | 'ooui' | 'oui' | 'nrk' | 'qlementine-icons' | 'fluent-color' | 'icon-park' | 'marketeq' | 'vscode-icons' | 'codicon' | 'material-icon-theme' | 'file-icons' | 'devicon' | 'devicon-plain' | 'catppuccin' | 'skill-icons' | 'unjs' | 'simple-icons' | 'logos' | 'cib' | 'fa6-brands' | 'bxl' | 'nonicons' | 'arcticons' | 'cbi' | 'brandico' | 'entypo-social' | 'token' | 'token-branded' | 'cryptocurrency' | 'cryptocurrency-color' | 'openmoji' | 'twemoji' | 'noto' | 'fluent-emoji' | 'fluent-emoji-flat' | 'fluent-emoji-high-contrast' | 'noto-v1' | 'emojione' | 'emojione-monotone' | 'emojione-v1' | 'fxemoji' | 'streamline-emojis' | 'circle-flags' | 'flag' | 'flagpack' | 'cif' | 'gis' | 'map' | 'geo' | 'game-icons' | 'fad' | 'academicons' | 'wi' | 'meteocons' | 'healthicons' | 'medical-icon' | 'covid' | 'la' | 'eva' | 'dashicons' | 'flat-color-icons' | 'entypo' | 'foundation' | 'raphael' | 'icons8' | 'iwwa' | 'gala' | 'heroicons-outline' | 'heroicons-solid' | 'fa-solid' | 'fa-regular' | 'fa-brands' | 'fa' | 'fluent-mdl2' | 'fontisto' | 'icomoon-free' | 'subway' | 'oi' | 'wpf' | 'simple-line-icons' | 'et' | 'el' | 'vaadin' | 'grommet-icons' | 'whh' | 'si-glyph' | 'zmdi' | 'ls' | 'bpmn' | 'flat-ui' | 'vs' | 'topcoat' | 'il' | 'websymbol' | 'fontelico' | 'ps' | 'feather' | 'mono-icons' | 'pepicons'
export type LiteralUnion<Union extends Base, Base = string> =
| Union
| (Base & { zz_IGNORE_ME?: never })

View File

@ -1,13 +0,0 @@
export interface IconsOptions {
/**
* The size of the icon
* @default '1em'
*/
size?: string | number
/**
* The color of the icon
* @default 'currentColor'
*/
color?: string
}

View File

@ -4,7 +4,7 @@ export * from './codeSandbox.js'
export * from './codeTabs.js'
export * from './demo.js'
export * from './fileTree.js'
export * from './icons.js'
export * from './icon.js'
export * from './jsfiddle.js'
export * from './npmTo.js'
export * from './pdf.js'

View File

@ -2,7 +2,7 @@ import type { CanIUseOptions } from './caniuse.js'
import type { CodeTabsOptions } from './codeTabs.js'
import type { CodeTreeOptions } from './codeTree.js'
import type { FileTreeOptions } from './fileTree.js'
import type { IconsOptions } from './icons.js'
import type { IconOptions } from './icon.js'
import type { NpmToOptions } from './npmTo.js'
import type { PDFOptions } from './pdf.js'
import type { PlotOptions } from './plot.js'
@ -40,14 +40,25 @@ export interface MarkdownPowerPluginOptions {
pdf?: boolean | PDFOptions
// new syntax
/**
*
* - iconify - `::collect:icon_name::` => `<VPIcon name="collect:icon_name" />`
* - iconfont - `::name::` => `<i class="iconfont icon-name"></i>`
* - fontawesome - `::fas:name::` => `<i class="fa-solid fa-name"></i>`
*
* @default false
*/
icon?: IconOptions
/**
* iconify
*
* `::collect:icon_name::`
*
* @default false
* @deprecated use `icon` instead 使 `icon`
*/
icons?: boolean | IconsOptions
icons?: boolean | IconOptions
/**
*
*

View File

@ -1,98 +1,85 @@
<script setup lang="ts">
import VPIconFa from '@theme/VPIconFa.vue'
import VPIconfont from '@theme/VPIconfont.vue'
import VPIconify from '@theme/VPIconify.vue'
import VPIconImage from '@theme/VPIconImage.vue'
import { computed } from 'vue'
import { withBase } from 'vuepress/client'
import { isLinkHttp } from 'vuepress/shared'
import { useIconsData } from '../composables/index.js'
const props = defineProps<{
provider?: 'iconify' | 'iconfont' | 'fontawesome'
name: string | { svg: string }
size?: string | number
color?: string
extra?: string
}>()
const iconsData = useIconsData()
declare const __MD_POWER_ICON_PROVIDER__: 'iconify' | 'iconfont' | 'fontawesome'
declare const __MD_POWER_ICON_PREFIX__: string
const type = computed(() => {
const provider = props.provider || __MD_POWER_ICON_PROVIDER__
// name -> https://example.com/icon.svg
// name -> /icon.svg
if (typeof props.name === 'string' && (isLinkHttp(props.name) || props.name[0] === '/')) {
return 'link'
}
// name -> { svg: '<svg></svg>' }
if (typeof props.name === 'object' && !!props.name.svg) {
return 'svg'
}
if (typeof props.name === 'string' && iconsData.value[props.name]) {
return 'local'
if (provider === 'iconfont' || provider === 'fontawesome') {
return provider
}
return 'remote'
return 'iconify'
})
const svg = computed(() => {
if (type.value === 'svg')
return (props.name as { svg: string }).svg
return ''
})
const link = computed(() => {
if (type.value === 'link') {
const link = props.name as string
return isLinkHttp(link) ? link : withBase(link)
}
return ''
})
const className = computed(() => {
if (type.value === 'local') {
const name = props.name as string
return iconsData.value[name] || ''
}
return ''
})
function parseSize(size: string | number): string {
if (String(Number(size)) === String(size))
return `${size}px`
return String(size)
}
const size = computed(() => {
const size = props.size
if (!size)
return undefined
if (String(Number(size)) === size)
return `${size}px`
return size
const [width, height] = String(size)
.replaceAll('px', '[UNIT]')
.split('x')
.map(s => parseSize(s.replaceAll('[UNIT]', 'px').trim()))
return { width, height: height || width }
})
const style = computed(() => ({
'background-color': props.color,
'width': size.value,
'height': size.value,
const binding = computed(() => ({
name: props.name as string,
color: props.color,
size: size.value,
prefix: __MD_POWER_ICON_PREFIX__ as any,
}))
</script>
<template>
<span v-if="type === 'link'" class="vp-icon-img">
<img :src="link" alt="" :style="{ height: size }">
</span>
<span
v-else-if="type === 'svg'"
class="vp-icon"
:style="style"
v-html="svg"
<VPIconImage
v-if="type === 'link' || type === 'svg'"
:type="type" :name="name" :color="color" :size="size"
/>
<span
v-else-if="type === 'local' && className"
class="vp-icon" :class="[className]"
:style="style"
<VPIconfont
v-else-if="type === 'iconfont'"
v-bind="binding"
/>
<VPIconFa
v-else-if="type === 'fontawesome'"
:extra="extra"
v-bind="{ ...binding, ...$attrs }"
/>
<VPIconify
v-else-if="type === 'iconify'"
:extra="extra"
v-bind="binding"
/>
<VPIconify v-else :name="(name as string)" :size="size" :color="color" />
</template>
<style scoped>
.vp-icon-img {
display: inline-block;
width: max-content;
height: 1em;
margin: 0 0.3em;
vertical-align: middle;
}
.vp-icon-img img {
height: 100%;
}
</style>

View File

@ -0,0 +1,62 @@
<script setup lang="ts">
import type { FontAwesomePrefix } from 'vuepress-plugin-md-power/client'
import { computed } from 'vue'
const props = defineProps<{
name: string
size?: { width?: string, height?: string }
color?: string
prefix?: FontAwesomePrefix
extra?: string
}>()
const configs: Record<string, FontAwesomePrefix[]> = {
'solid': ['fas', 's'],
'regular': ['far', 'r'],
'light': ['fal', 'l'],
'thin': ['fat', 't'],
'duotone solid': ['fads', 'ds'],
'sharp solid': ['fass', 'ss'],
'sharp regular': ['fasr', 'sr'],
'sharp light': ['fasl', 'sl'],
'sharp thin': ['fast', 'st'],
'sharp-duotone solid': ['fasds', 'sds'],
'brands': ['fab', 'b'],
}
const iconName = computed(() => {
const icon = props.name.includes(':') ? props.name : `${props.prefix || 'fas'}:${props.name}`
const [type, name] = icon.split(':')
let prefix = 'solid'
for (const [key, alias] of Object.entries(configs)) {
if (alias.includes(type as FontAwesomePrefix)) {
prefix = key
break
}
}
return `${prefix.split(' ').map(v => `fa-${v.trim()}`).join(' ')} fa-${name}`
})
const extraClasses = computed(() => {
const extra = props.extra
if (!extra)
return []
return extra.split(' ').map(v => v.trim().startsWith('fa-') ? v : `fa-${v}`)
})
</script>
<template>
<i
class="vp-icon fontawesome" :class="[iconName, ...extraClasses]"
data-provider="fontawesome" aria-hidden
:style="{ color, ...size }"
/>
</template>
<style>
.vp-icon.fontawesome {
display: inline-block;
line-height: inherit;
vertical-align: middle;
}
</style>

View File

@ -0,0 +1,53 @@
<script setup lang="ts">
import { computed } from 'vue'
import { withBase } from 'vuepress/client'
import { isLinkHttp } from 'vuepress/shared'
const props = defineProps<{
type: 'link' | 'svg'
name: string | { svg: string }
color?: string
size?: { width?: string, height?: string }
}>()
const svg = computed(() => {
if (props.type === 'svg' && typeof props.name === 'object' && 'svg' in props.name) {
return props.name.svg
}
return ''
})
const link = computed(() => {
if (props.type === 'link') {
const link = props.name as string
return isLinkHttp(link) ? link : withBase(link)
}
return ''
})
</script>
<template>
<span v-if="type === 'link'" class="vp-icon-img" aria-hidden>
<img :src="link" alt="" :style="{ height: size?.height }">
</span>
<span
v-else-if="type === 'svg'"
class="vp-icon"
:style="{ color, ...size }"
aria-hidden
v-html="svg"
/>
</template>
<style scoped>
.vp-icon-img {
display: inline-block;
width: max-content;
height: 1em;
margin: 0 0.3em;
vertical-align: middle;
}
.vp-icon-img img {
height: 100%;
}
</style>

View File

@ -0,0 +1,22 @@
<script setup lang="ts">
import { computed } from 'vue'
const props = defineProps<{
name: string
size?: { width?: string, height?: string }
color?: string
prefix?: string
}>()
const prefix = computed(() => props.prefix || 'iconfont icon-')
</script>
<template>
<i
class="vp-icon"
:class="`${prefix}${name}`"
:style="{ color, 'font-size': size?.height || '1em' }"
data-provider="iconfont"
aria-hidden
/>
</template>

View File

@ -3,61 +3,63 @@ import type { IconifyIcon } from '@iconify/vue/offline'
import { loadIcon } from '@iconify/vue'
import { Icon as OfflineIcon } from '@iconify/vue/offline'
import { computed, ref, watch } from 'vue'
import { useIconsData } from '../composables/index.js'
const props = withDefaults(
defineProps<{
name?: string
size?: string | number
color?: string
}>(),
{
name: '',
size: '',
color: '',
},
)
const props = defineProps<{
name: string
size?: { width?: string, height?: string }
color?: string
prefix?: string
extra?: string
}>()
const icon = ref<IconifyIcon | null>(null)
const loaded = ref(false)
async function loadIconComponent() {
const iconsData = useIconsData()
const iconName = computed(() => {
const name = props.name
if (name.includes(':'))
return name
return props.prefix ? `${props.prefix}:${name}` : name
})
const localIconName = computed(() => iconsData.value[iconName.value])
async function loadRemoteIcon() {
if (icon.value)
return
if (!__VUEPRESS_SSR__) {
try {
loaded.value = false
icon.value = await loadIcon(props.name)
}
finally {
loaded.value = true
}
}
else {
loaded.value = true
if (!localIconName.value) {
loaded.value = false
icon.value = await loadIcon(props.name)
}
loaded.value = true
}
watch(() => props.name, loadIconComponent, { immediate: true })
const size = computed(() => {
const size = props.size || '1em'
if (String(Number(size)) === size)
return `${size}px`
return size
})
const color = computed(() => props.color || 'currentColor')
if (!__VUEPRESS_SSR__)
watch(() => props.name, loadRemoteIcon, { immediate: true })
</script>
<template>
<ClientOnly>
<span v-if="!loaded" class="vp-icon iconify" :style="{ color, width: size, height: size }" />
<span
v-if="localIconName"
class="vp-icon" :class="[localIconName, extra]"
:style="{ color, ...size }"
aria-hidden
data-provider="iconify"
/>
<ClientOnly v-else>
<span v-if="!loaded" class="vp-icon iconify" :style="{ color, ...size }" />
<OfflineIcon
v-else-if="icon"
class="vp-icon iconify"
:class="[extra]"
:icon="icon"
:style="{ color, width: size, height: size }"
:style="{ color, ...size }"
aria-hidden
data-provider="iconify"
/>
</ClientOnly>
</template>

View File

@ -288,6 +288,10 @@ function onCaretClick() {
margin: 0 0.25rem 0 0;
}
.item :deep(.vp-icon.fontawesome) {
line-height: 1;
}
.item:hover .caret {
color: var(--vp-c-text-2);
}

View File

@ -1,6 +1,5 @@
[class^="vpi-"],
[class*=" vpi-"],
.vp-icon {
[class*=" vpi-"] {
display: inline-block;
width: 1em;
height: 1em;
@ -8,8 +7,7 @@
}
[class^="vpi-"].bg,
[class*=" vpi-"].bg,
.vp-icon.bg {
[class*=" vpi-"].bg {
background-color: transparent;
background-image: var(--icon);
background-repeat: no-repeat;
@ -17,8 +15,7 @@
}
[class^="vpi-"]:not(.bg),
[class*=" vpi-"]:not(.bg),
.vp-icon:not(.bg) {
[class*=" vpi-"]:not(.bg) {
color: inherit;
background-color: currentcolor;
-webkit-mask: var(--icon) no-repeat;

View File

@ -48,3 +48,70 @@
opacity: 0 !important;
transform: translateX(-10px) !important;
}
/* ----------------- Read More Link ------------------ */
.vp-doc a.read-more,
.vp-doc a.readmore {
position: relative;
display: block;
padding: 8px 22px 8px calc(1.25em + 16px);
margin: 16px 0;
font-size: inherit;
font-size: 14px;
font-weight: inherit;
color: currentcolor;
text-decoration: none;
background-color: var(--vp-c-bg-safe);
border: dashed 1px var(--vp-c-divider);
border-radius: 8px;
transition: border-color var(--vp-t-color), background-color var(--vp-t-color);
}
.vp-doc a.read-more:hover,
.vp-doc a.readmore:hover {
color: currentcolor;
background-color: var(--vp-c-bg-soft);
border: solid 1px var(--vp-c-brand-2);
}
.vp-doc a.read-more::before,
.vp-doc a.readmore::before {
--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' d='M20 22H4a1 1 0 0 1-1-1V3a1 1 0 0 1 1-1h16a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1M7 4H5v16h14V4h-5v9l-3.5-2L7 13z'/%3E%3C/svg%3E");
position: absolute;
top: 50%;
left: 16px;
display: inline-block;
width: 1em;
height: 1em;
color: var(--vp-c-brand-1);
vertical-align: middle;
content: "";
background-color: currentcolor;
-webkit-mask: var(--icon) no-repeat;
mask: var(--icon) no-repeat;
-webkit-mask-size: 100% 100%;
mask-size: 100% 100%;
transform: translateY(-50%);
}
.vp-doc a.read-more[href*="://"]::after,
.vp-doc a.readmore[target=_blank]::after {
position: absolute;
top: 8px;
right: 8px;
width: 14px !important;
height: 14px !important;
margin: 0 !important;
color: var(--vp-c-text-3) !important;
}
.vp-doc a.read-more[href*="://"]:hover::after,
.vp-doc a.readmore[target=_blank]:hover::after {
color: var(--vp-c-brand-2) !important;
}
.vp-doc a.read-more :where(strong),
.vp-doc a.readmore :where(strong) {
color: var(--vp-c-brand-1);
}

View File

@ -52,7 +52,8 @@ export const MARKDOWN_POWER_FIELDS: (keyof MarkdownPowerPluginOptions)[] = [
'demo',
'fileTree',
'field',
'icons',
'icons', // deprecated
'icon',
'imageSize',
'jsfiddle',
'npmTo',

View File

@ -43,7 +43,7 @@ export function codePlugins(pluginOptions: ThemeBuiltinPlugins): PluginConfig {
langs: uniq([...twoslash ? ['ts', 'js', 'vue', 'json', 'bash', 'sh'] : [], ...langs]),
codeBlockTitle: (title, code) => {
const icon = getIcon(title)
return `<div class="code-block-title" data-title="${title}"><div class="code-block-title-bar"><span class="title">${icon ? `<VPIcon name="${icon}"/>` : ''}${title}</span></div>${code}</div>`
return `<div class="code-block-title" data-title="${title}"><div class="code-block-title-bar"><span class="title">${icon ? `<VPIcon provider="iconify" name="${icon}"/>` : ''}${title}</span></div>${code}</div>`
},
twoslash: isPlainObject(twoslashOptions)
? {

View File

@ -1,4 +1,5 @@
import type { App, Page } from 'vuepress'
import type { IconOptions } from 'vuepress-plugin-md-power'
import type { ThemeHomeConfig, ThemeNavItem, ThemeOptions, ThemeSidebar } from '../../shared/index.js'
import type { FsCache } from '../utils/index.js'
import { getIconContentCSS, getIconData } from '@iconify/utils'
@ -22,6 +23,7 @@ const ICON_REGEXP = /<(?:VP)?(Icon|Card|LinkCard|Button)([^>]*)>/g
const ICON_NAME_REGEXP = /(?:name|icon|suffix-icon)="([^"]+)"/
const URL_CONTENT_REGEXP = /(url\([\s\S]+\))/
const ICONIFY_NAME = /^[\w-]+:[\w-]+$/
const JS_FILENAME = 'internal/iconify.js'
const CSS_FILENAME = 'internal/iconify.css'
@ -32,12 +34,6 @@ let fsCache: FsCache<IconDataMap> | null = null
// { iconName: { className, content } }
const cache: IconDataMap = {}
function isIconify(icon: any): icon is string {
if (!icon || typeof icon !== 'string' || isLinkAbsolute(icon) || isLinkHttp(icon))
return false
return icon[0] !== '{' && ICONIFY_NAME.test(icon)
}
export async function prepareIcons(app: App): Promise<void> {
perf.mark('prepare:icons:total')
const options = getThemeConfig()
@ -51,9 +47,11 @@ export async function prepareIcons(app: App): Promise<void> {
}
perf.mark('prepare:pages:icons')
const iconOptions = options.markdown?.icon || {}
const iconList: string[] = []
app.pages.forEach(page => iconList.push(...getIconsWithPage(page)))
iconList.push(...getIconWithThemeConfig(options))
app.pages.forEach(page => iconList.push(...getIconsWithPage(page, iconOptions)))
iconList.push(...getIconWithThemeConfig(options, iconOptions))
const collectMap: CollectMap = {}
uniq(iconList).filter((icon) => {
@ -108,31 +106,49 @@ export async function prepareIcons(app: App): Promise<void> {
perf.log('prepare:icons:total')
}
function getIconsWithPage(page: Page): string[] {
const list = page.contentRendered
.match(ICON_REGEXP)
?.map(match => match.match(ICON_NAME_REGEXP)?.[1])
.filter(isIconify) as string[] || []
function isIconify(icon: any): icon is string {
if (!icon || typeof icon !== 'string' || isLinkAbsolute(icon) || isLinkHttp(icon))
return false
return icon[0] !== '{' && ICONIFY_NAME.test(icon)
}
function withPrefix(icon: string, prefix?: string): string {
if (!prefix)
return icon
return icon.includes(':') ? icon : `${prefix}:${icon}`
}
function getIconsWithPage(page: Page, { provider = 'iconify', prefix }: IconOptions): string[] {
const list: string[] = []
const matches = page.contentRendered.match(ICON_REGEXP) || []
for (const matched of matches) {
if (provider === 'iconify' || matched.includes('provider="iconify"')) {
const icon = matched.match(ICON_NAME_REGEXP)?.[1]
if (isIconify(icon))
list.push(withPrefix(icon, prefix))
}
}
const addIcon = (icon: unknown): void => {
if (isIconify(icon) && (provider === 'iconify' || icon.startsWith('iconify'))) {
list.push(withPrefix(icon.replace(/^iconify /, ''), prefix))
}
}
const fm = page.frontmatter
if (fm.icon && isIconify(fm.icon)) {
list.push(fm.icon)
}
addIcon(fm.icon)
if ((fm.home || fm.pageLayout === 'home') && (fm.config as ThemeHomeConfig[])?.length) {
for (const config of (fm.config as ThemeHomeConfig[])) {
if (config.type === 'features' && config.features.length) {
for (const feature of config.features) {
if (feature.icon && isIconify(feature.icon))
list.push(feature.icon)
addIcon(feature.icon)
}
}
if (config.type === 'hero' && config.hero?.actions?.length) {
for (const action of config.hero.actions) {
if (action.icon && isIconify(action.icon))
list.push(action.icon)
if (action.suffixIcon && isIconify(action.suffixIcon))
list.push(action.suffixIcon)
addIcon(action.icon)
addIcon(action.suffixIcon)
}
}
}
@ -141,7 +157,7 @@ function getIconsWithPage(page: Page): string[] {
return list
}
function getIconWithThemeConfig(options: ThemeOptions): string[] {
function getIconWithThemeConfig(options: ThemeOptions, { provider = 'iconify', prefix }: IconOptions): string[] {
const list: string[] = []
// navbar notes sidebar
const locales = options.locales || {}
@ -159,7 +175,13 @@ function getIconWithThemeConfig(options: ThemeOptions): string[] {
sidebarList.forEach(sidebar => list.push(...getIconWithSidebar(sidebar)))
})
return list.filter(isIconify)
const addIcon = (icon: unknown): string | void => {
if (isIconify(icon) && (provider === 'iconify' || icon.startsWith('iconify'))) {
return withPrefix(icon.replace(/^iconify /, ''), prefix)
}
}
return list.map(addIcon).filter(Boolean) as string[]
}
function getIconWithNavbar(navbar: ThemeNavItem[]): string[] {