feat(theme)!: deprecate old icon syntax and use ::icon:: syntax instead (#563)

This commit is contained in:
pengzhanbo 2025-04-26 01:20:05 +08:00 committed by GitHub
parent b07426dfc6
commit a023ca8654
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 269 additions and 147 deletions

View File

@ -118,7 +118,7 @@ export default defineUserConfig({
// youtube: true, // 启用嵌入 youtube视频 语法 @[youtube](video_id)
// artPlayer: true, // 启用嵌入 artPlayer 本地视频 语法 @[artPlayer](url)
// audioReader: true, // 启用嵌入音频朗读功能 语法 @[audioReader](url)
// icons: true, // 启用内置图标语法 :[icon-name]:
// icons: true, // 启用内置图标语法 ::icon-name::
// codepen: true, // 启用嵌入 codepen 语法 @[codepen](user/slash)
// replit: true, // 启用嵌入 replit 语法 @[replit](user/repl-name)
// codeSandbox: true, // 启用嵌入 codeSandbox 语法 @[codeSandbox](id)

View File

@ -3,15 +3,34 @@ title: Icons
createTime: 2025/03/23 14:24:45
icon: grommet-icons:emoji
permalink: /en/guide/markdown/iconify/
badge:
text: Change
type: warning
---
::: warning The icon syntax underwent a breaking change in version `1.0.0-rc.144`.
The `:[collect:name size/color]:` syntax has been deprecated. Please use `::collect:name =size /color::` instead.
The theme plans to support icons from libraries such as `iconfont`, `fontawesome`, and `lucide` in future versions.
The original syntax was insufficient to accommodate these new extensions, making this breaking change necessary.
The old syntax will remain supported in recent versions but is no longer recommended and will be removed in the future.
The theme will detect if the old syntax is used. If so, a warning message and modification suggestions will be displayed in the console.
Please adjust accordingly based on these suggestions.
:::
## Overview
Use [iconify](https://iconify.design/) icons in Markdown files.
The theme provides an [`<Icon />`](../components/icon.md) component for using icons in Markdown and a simplified Markdown syntax for easier icon usage.
The theme provides an [`<Icon />`](../components/icon.md) component for using icons in Markdown
and a simplified Markdown syntax for easier icon usage.
To enhance this feature, the theme recommends installing the `@iconify/json` dependency. It automatically parses icon data from `@iconify/json`, packs used icons as local resources for better access.
To enhance this feature, the theme recommends installing the `@iconify/json` dependency.
It automatically parses icon data from `@iconify/json`, packs used icons as local resources for better access.
::: npm-to
@ -24,15 +43,15 @@ npm install @iconify/json
## Syntax
```md
:[collect:name]:
::collect:name::
```
To set icon size and color:
```md
:[collect:name size]:
:[collect:name /color]:
:[collect:name size/color]:
::collect:name =size::
::collect:name /color::
::collect:name =size /color::
```
Iconify has numerous icons grouped into different `collect` categories. Each `collect` has its own set of icons.
@ -44,31 +63,31 @@ You can find `collect` and `name` at <https://icon-sets.iconify.design/>.
Input:
```md
:[ion:logo-markdown]:
::ion:logo-markdown::
```
Output:
:[ion:logo-markdown]:
::ion:logo-markdown::
This is an inline syntax, allowing use with other Markdown elements in the same line.
Input:
```md
github: :[tdesign:logo-github-filled]:
Change color: :[tdesign:logo-github-filled /#f00]:
Change size: :[tdesign:logo-github-filled 36px]:
Change size and color: :[tdesign:logo-github-filled 36px/#f00]:
github: ::tdesign:logo-github-filled::
Change color: ::tdesign:logo-github-filled /#f00::
Change size: ::tdesign:logo-github-filled =36px::
Change size and color: ::tdesign:logo-github-filled =36px /#f00::
Colored icon: :[skill-icons:vscode-dark 36px]:
Colored icon: ::skill-icons:vscode-dark =36px::
```
Output:
github: :[tdesign:logo-github-filled]:
Change color: :[tdesign:logo-github-filled /#f00]:
Change size: :[tdesign:logo-github-filled 36px]:
Change size and color: :[tdesign:logo-github-filled 36px/#f00]:
github: ::tdesign:logo-github-filled::
Change color: ::tdesign:logo-github-filled /#f00::
Change size: ::tdesign:logo-github-filled =36px::
Change size and color: ::tdesign:logo-github-filled =36px /#f00::
Colored icon: :[skill-icons:vscode-dark 36px]:
Colored icon: ::skill-icons:vscode-dark =36px::

View File

@ -10,14 +10,19 @@ permalink: /en/guide/optimize-build/
When we embed images in markdown using `[alt](url)` or `<img src="url">`, the page displays the images as expected.
However, due to different image sizes, when images are small or network conditions are good, we may not notice significant layout shifts. When images are large or network conditions are poor, the layout can change noticeably as images load, causing other content to shift.
However, due to different image sizes, when images are small or network conditions are good,
we may not notice significant layout shifts.
When images are large or network conditions are poor, the layout can change noticeably as images load,
causing other content to shift.
This experience is not user-friendly, especially with many images on a page. Frequent layout changes can cause noticeable jitter.
To stabilize the layout, images must be optimized. According to [MDN > img](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/img#height):
::: info
`<img>` with both `height` and `width` allows the browser to calculate the image's aspect ratio before loading. This reserves space for the image, reducing or preventing layout shifts during download and rendering. Reducing layout shifts is crucial for good user experience and web performance.
`<img>` with both `height` and `width` allows the browser to calculate the image's aspect ratio before loading.
This reserves space for the image, reducing or preventing layout shifts during download and rendering.
Reducing layout shifts is crucial for good user experience and web performance.
:::
Our theme provides a solution: automatically adding `width` and `height` attributes to `[alt](url)` or `<img src="url">` in markdown files.
@ -47,21 +52,37 @@ For performance reasons, this feature only takes effect during production build.
:::
::: important
Use `'all'` cautiously. It requests all remote images during production build, which can increase build time for sites with many images. The theme optimizes this by only requesting a few KB of data to analyze dimensions and processing images concurrently.
Use `'all'` cautiously. It requests all remote images during production build,
which can increase build time for sites with many images.
The theme optimizes this by only requesting a few KB of data to analyze dimensions and processing images concurrently.
:::
## Icon Optimization
Thanks to the open-source project [iconify](https://icon-sets.iconify.design/), you can use approximately 200,000 icons in our theme.
However, this doesn't mean the theme needs to load all icons. You may have noticed the theme recommends installing the `@iconify/json` package locally, which requires downloading a 70Mb resource pack. Loading all icons into the documentation site would be excessively large.
However, this doesn't mean the theme needs to load all icons.
You may have noticed the theme recommends installing the `@iconify/json` package locally,
which requires downloading a 70Mb resource pack.
Loading all icons into the documentation site would be excessively large.
But rest assured, the theme only loads the icon resources you actually use. This includes Iconify icons in navigation, sidebar, homepage Features, and icons used via the `:[collect:name]:` syntax or `<Icon name="icon_name" />` component.
But rest assured, the theme only loads the icon resources you actually use.
This includes Iconify icons in navigation, sidebar, homepage Features,
and icons used via the `::collect:name::` syntax or `<Icon name="icon_name" />` component.
When loading icons from local `@iconify/json`, iconify names icons by `[collect]:[name]`, with each collection containing 100 to 1000+ icons in a `json` file. Using many different `collect` icons can slow down initial loading and parsing. For example, our theme uses 54 `collect` collections with over 160 icons, taking about `500ms` to load and parse initially.
When loading icons from local `@iconify/json`, iconify names icons by `[collect]:[name]`,
with each collection containing 100 to 1000+ icons in a `json` file.
Using many different `collect` icons can slow down initial loading and parsing.
For example, our theme uses 54 `collect` collections with over 160 icons,
taking about `500ms` to load and parse initially.
To optimize, the theme caches used icons on first launch. On subsequent launches, it优先从缓存加载图标 which is much faster than parsing different `collect` collections. This reduces loading time from `500ms` to `20ms` or less, also improving development server startup time!
In response to this situation, the theme caches the used icon resources upon the first launch.
During subsequent launches, icons are preferentially loaded from the cache.
Since only the utilized icon resources are cached, loading these resources is significantly
faster than repeatedly parsing icon resources under different `collect` sections,
and it also results in higher resource utilization efficiency.
::: info
Using 54 `collect` collections is extreme. While `500ms` for 54 I/O reads and JSON parses seems normal, it's unexpected for only 160+ icons. Caching these icons is a good solution.
Using 54 `collect` collections is extreme. While `500ms` for 54 I/O reads and JSON parses seems normal,
it's unexpected for only 160+ icons. Caching these icons is a good solution.
:::

View File

@ -80,7 +80,7 @@ export default defineUserConfig({
然后站点进行全量刷新,这可能需要等待一段时间才能恢复, 当站点内容较少时,这个过程很快,
而对于一些较大的站点,可能需要等待较长的时间。
特别是频繁修改时,很容易使 VuePress ==服务崩溃=={.caution} :[twemoji:angry-face]:,不得不手动重启。
特别是频繁修改时,很容易使 VuePress ==服务崩溃=={.caution} ::twemoji:angry-face::,不得不手动重启。
**这给我们在编写站点内容时带来的极大的不便。**
@ -88,7 +88,7 @@ export default defineUserConfig({
比如 `navbar``profile` 等字段,可以通过 热更新 的方式实时生效。
主题添加主题配置文件 `plume.config.ts` 的支持,
==对该文件的修改将通过热更新的方式实时生效。=={.tip} :[twemoji:confetti-ball]:
==对该文件的修改将通过热更新的方式实时生效。=={.tip} ::twemoji:confetti-ball::
你可以在这个文件中配置支持热更新的字段,比如 `navbar``profile` 等字段。

View File

@ -23,7 +23,7 @@ export default defineUserConfig({
markdown: {
fileTree: true, // :::file-tree 文件树容器
plot: true, // !!plot!! 隐秘文本
icons: true, // :[collect:name]: 内联 iconify 图标
icons: true, // ::collect:name:: 内联 iconify 图标
// 默认不启用以下功能,你需要手动开启它们
// npmTo: true, // :::npm-to
// demo: true, // :::demo
@ -69,7 +69,7 @@ __语法:__
__语法:__
```md
:[collect:name]:
::collect:name::
```
请查看 [完整使用文档](../../guide/markdown/icons.md)

View File

@ -3,8 +3,24 @@ title: 图标
createTime: 2024/09/30 14:49:39
icon: grommet-icons:emoji
permalink: /guide/markdown/iconify/
badge:
text: 变更
type: warning
---
::: warning 图标语法糖在 `1.0.0-rc.144` 版本中进行了破坏性变更。
`:[collect:name size/color]:` 语法糖已弃用,请使用 `::collect:name =size /color::` 代替。
主题计划在未来的版本中,支持如 `iconfont` / `fontawesome` / `lucide` 等图标库的图标,原有语法糖
不足以支持新的扩展,因此此破坏性变更是必要的。
旧的语法在近期的版本中依然支持,但不再推荐使用,且在未来会删除。
主题会检测是否使用旧的语法,如果使用,会在控制台输出警告信息和修改建议,请根据修改建议进行调整。
:::
## 概述
在 Markdown 文件中使用 [iconify](https://iconify.design/) 的图标。
@ -26,15 +42,15 @@ npm install @iconify/json
## 语法
```md
:[collect:name]:
::collect:name::
```
设置图标大小和颜色
```md
:[collect:name size]:
:[collect:name /color]:
:[collect:name size/color]:
::collect:name =size::
::collect:name /color::
::collect:name =size /color::
```
`iconify` 拥有非常多的图标,这些图标被分组为不同的 `collect`,每个 `collect` 都有自己的
@ -47,31 +63,31 @@ npm install @iconify/json
输入:
```md
:[ion:logo-markdown]:
::ion:logo-markdown::
```
输出:
:[ion:logo-markdown]:
::ion:logo-markdown::
该语法为行内语法,因此,你可以在同一行中跟其他 markdown 语法 一起使用。
输入:
```md
github: :[tdesign:logo-github-filled]:
修改颜色::[tdesign:logo-github-filled /#f00]:
修改大小::[tdesign:logo-github-filled 36px]:
修改大小颜色::[tdesign:logo-github-filled 36px/#f00]:
github: ::tdesign:logo-github-filled::
修改颜色:::tdesign:logo-github-filled /#f00::
修改大小:::tdesign:logo-github-filled =36px::
修改大小颜色:::tdesign:logo-github-filled =36px /#f00::
彩色图标 :[skill-icons:vscode-dark 36px]:
彩色图标 ::skill-icons:vscode-dark =36px::
```
输出:
github: :[tdesign:logo-github-filled]:
修改颜色::[tdesign:logo-github-filled /#f00]:
修改大小::[tdesign:logo-github-filled 36px]:
修改大小颜色::[tdesign:logo-github-filled 36px/#f00]:
github: ::tdesign:logo-github-filled::
修改颜色:::tdesign:logo-github-filled /#f00::
修改大小:::tdesign:logo-github-filled =36px::
修改大小颜色:::tdesign:logo-github-filled =36px /#f00::
彩色图标 :[skill-icons:vscode-dark 36px]:
彩色图标 ::skill-icons:vscode-dark =36px::

View File

@ -72,7 +72,7 @@ export default defineUserConfig({
下载大约 __70Mb__ 的资源包,如果加载全部的图标到文档站点中,这太大太大了。
但请放心,主题仅会加载您有使用到的图标资源,这包括 导航栏、侧边栏、首页 Features 等配置中的 iconify 图标,
以及通过语法糖 `:[collect:name]:` 和 组件 `<Icon name="icon_name" />` 等各种方式使用的图标。
以及通过语法糖 `::collect:name::` 和 组件 `<Icon name="icon_name" />` 等各种方式使用的图标。
当从本地 `@iconify/json` 中加载图标时, iconify 通过 `[collect]:[name]` 的形式为图标命名,其中根据
`collect` 来区分图标所属的集合,每个集合拥有 100 ~ 1000+ 数量不等的图标,保存在以 `collect` 为维度的 `json`

View File

@ -22,7 +22,7 @@ search: false
![cat](/images/sponsor/cute-cat.jpg){width="64px"}
:::
| :[bi:alipay]: AliPay | :[fa:wechat]: WeChat |
| ::bi:alipay:: AliPay | ::fa:wechat:: WeChat |
| -------------------------------------- | ----------------------------------------- |
| ![Alipay](https://static.pengzhanbo.cn/images/sponsor/ali_pay.jpg){width="300" height="300" style="width:150px"} | ![WeChat](https://static.pengzhanbo.cn/images/sponsor/wechat_pay.jpg){width="300" height="300" style="width:150px"} |

View File

@ -95,10 +95,10 @@ pnpm add @iconify/json
#### 语法
```md
:[collect:icon]:
:[collect:icon size]:
:[collect:icon /color]:
:[collect:icon size/color]:
::collect:icon::
::collect:icon =size::
::collect:icon /color::
::collect:icon =size /color::
```
你可以从 [icon-sets.iconify](https://icon-sets.iconify.design/) 获取 图标集。
@ -106,25 +106,25 @@ pnpm add @iconify/json
显示 `logos` 图标集合下的 `vue` 图标
```md
:[logos:vue]:
::logos:vue::
```
图标默认大小为 `1em` ,你可以通过 `size` 设置图标大小
图标默认大小为 `1em` ,你可以通过 `=size` 设置图标大小
```md
:[logos:vue 1.2em]:
::logos:vue =1.2em::
```
图标默认颜色为 `currentColor` 你可以通过 `/color` 设置图标颜色
```md
:[logos:vue /blue]:
::logos:vue /blue::
```
也可以通过 `size/color` 设置图标大小和颜色
也可以通过 `=size /color` 设置图标大小和颜色
```md
:[logos:vue 1.2em/blue]:
::logos:vue =1.2em /blue::
```
### bilibili

View File

@ -1,108 +1,108 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`iconsPlugin > should not work with invalid icon 1`] = `
"<p>:[ mdi:11 ]:</p>
"<p>:: mdi:11 ::</p>
"
`;
exports[`iconsPlugin > should not work with invalid icon 2`] = `
"<p>:[]:</p>
"<p>::::</p>
"
`;
exports[`iconsPlugin > should not work with invalid icon 3`] = `
"<p>:[]&amp;</p>
"<p>::]&amp;</p>
"
`;
exports[`iconsPlugin > should not work with invalid icon 4`] = `
"<p>:[:[</p>
"<p>::::</p>
"
`;
exports[`iconsPlugin > should not work with invalid icon 5`] = `
"<p>:[mdi:11</p>
"<p>::mdi:11</p>
"
`;
exports[`iconsPlugin > should work 1`] = `
"<p><VPIcon name="mdi:11"></VPIcon></p>
"<p><VPIcon name="mdi:11" /></p>
"
`;
exports[`iconsPlugin > should work 2`] = `
"<p><strong>strong</strong> <VPIcon name="mdi:11"></VPIcon> <VPIcon name="mdi:11"></VPIcon></p>
"<p><strong>strong</strong> <VPIcon name="mdi:11" /> <VPIcon name="mdi:11" /></p>
"
`;
exports[`iconsPlugin > should work 3`] = `
"<p><strong>strong</strong>
<VPIcon name="mdi:11"></VPIcon>
<VPIcon name="mdi:11"></VPIcon></p>
<VPIcon name="mdi:11" />
<VPIcon name="mdi:11" /></p>
"
`;
exports[`iconsPlugin > should work with options 1`] = `
"<p><VPIcon name="mdi:11" size="1.25em" color="#ccc"></VPIcon></p>
"<p><VPIcon name="mdi:11" size="1.25em" color="#ccc" /></p>
"
`;
exports[`iconsPlugin > should work with options 2`] = `
"<p><strong>strong</strong> <VPIcon name="mdi:11" size="1.25em" color="#ccc"></VPIcon> <VPIcon name="mdi:11" size="1.25em" color="#ccc"></VPIcon></p>
"<p><strong>strong</strong> <VPIcon name="mdi:11" size="1.25em" color="#ccc" /> <VPIcon name="mdi:11" size="1.25em" color="#ccc" /></p>
"
`;
exports[`iconsPlugin > should work with single icon options 1`] = `
"<p><VPIcon name="mdi:11" size="36px"></VPIcon></p>
"<p><VPIcon name="mdi:11" size="36px" /></p>
"
`;
exports[`iconsPlugin > should work with single icon options 2`] = `
"<p><VPIcon name="mdi:11" size="32px" color="#eee"></VPIcon></p>
"<p><VPIcon name="mdi:11" size="32px" color="#eee" /></p>
"
`;
exports[`iconsPlugin > should work with single icon options 3`] = `
"<p><VPIcon name="mdi:11" color="#eee"></VPIcon></p>
"<p><VPIcon name="mdi:11" color="#eee" /></p>
"
`;
exports[`iconsPlugin > should work with single icon options 4`] = `
"<p><VPIcon name="mdi:11" size="32px"></VPIcon></p>
"<p><VPIcon name="mdi:11" size="32px/" /></p>
"
`;
exports[`iconsPlugin > should work with single icon options 5`] = `
"<p><VPIcon name="mdi:11"></VPIcon></p>
"<p><VPIcon name="mdi:11" class="/" /></p>
"
`;
exports[`iconsPlugin > should work with single icon options 6`] = `
"<p><VPIcon name="mdi:11" size="1.25em" color="#ccc"></VPIcon></p>
"<p><VPIcon name="mdi:11" size="1.25em" color="#ccc" /></p>
"
`;
exports[`iconsPlugin > should work with single icon options 7`] = `
"<p><VPIcon name="mdi:11" size="36px" color="#ccc"></VPIcon></p>
"<p><VPIcon name="mdi:11" size="36px" color="#ccc" /></p>
"
`;
exports[`iconsPlugin > should work with single icon options 8`] = `
"<p><VPIcon name="mdi:11" size="32px" color="#eee"></VPIcon></p>
"<p><VPIcon name="mdi:11" size="32px/#eee" color="#ccc" /></p>
"
`;
exports[`iconsPlugin > should work with single icon options 9`] = `
"<p><VPIcon name="mdi:11" size="1.25em" color="#eee"></VPIcon></p>
"<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"></VPIcon></p>
"<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"></VPIcon></p>
"<p><VPIcon name="mdi:11" size="1.25em" color="#ccc" class="/" /></p>
"
`;

View File

@ -1,48 +1,48 @@
import MarkdownIt from 'markdown-it'
import { describe, expect, it } from 'vitest'
import { iconsPlugin } from '../src/node/inline/icons.js'
import { iconPlugin } from '../src/node/inline/icons.js'
describe('iconsPlugin', () => {
it('should work', () => {
const md = MarkdownIt().use(iconsPlugin)
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::')).toMatchSnapshot()
expect(md.render('**strong** ::mdi:11:: ::mdi:11::')).toMatchSnapshot()
expect(md.render('**strong**\n::mdi:11::\n ::mdi:11::')).toMatchSnapshot()
})
it('should work with options', () => {
const md = MarkdownIt().use(iconsPlugin, { size: '1.25em', color: '#ccc' })
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::')).toMatchSnapshot()
expect(md.render('**strong** ::mdi:11:: ::mdi:11::')).toMatchSnapshot()
})
it('should work with single icon options', () => {
const md = MarkdownIt().use(iconsPlugin)
const md = MarkdownIt().use(iconPlugin)
expect(md.render(':[mdi:11 36px]:')).toMatchSnapshot()
expect(md.render(':[mdi: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('::mdi:11 =36px::')).toMatchSnapshot()
expect(md.render('::mdi:11 =32px /#eee::')).toMatchSnapshot()
expect(md.render('::mdi:11 /#eee::')).toMatchSnapshot()
expect(md.render('::mdi:11 =32px/::')).toMatchSnapshot()
expect(md.render('::mdi:11 /::')).toMatchSnapshot()
const md2 = MarkdownIt().use(iconsPlugin, { 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()
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 not work with invalid icon', () => {
const md = MarkdownIt().use(iconsPlugin)
const md = MarkdownIt().use(iconPlugin)
expect(md.render(':[ mdi:11 ]:')).toMatchSnapshot()
expect(md.render(':[]:')).toMatchSnapshot()
expect(md.render(':[]&')).toMatchSnapshot()
expect(md.render(':[:[')).toMatchSnapshot()
expect(md.render(':[mdi:11')).toMatchSnapshot()
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,46 +1,59 @@
/**
* :[mdi:11]:
* :[mdi:11 24px]:
* :[mid:11 /#ccc]:
* :[fluent-mdl2:toggle-filled 128px/#fff]:
* ::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'
export const iconsPlugin: PluginWithOptions<IconsOptions> = (md, options = {}) =>
md.inline.ruler.before('emphasis', 'iconify', createTokenizer(options))
function createTokenizer(options: IconsOptions): RuleInline {
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
// :[
if (state.src.charCodeAt(start) !== 0x3A
|| state.src.charCodeAt(start + 1) !== 0x5B) {
// ::xxx
// ^^
if (
state.src.charCodeAt(start) !== l1
|| state.src.charCodeAt(start + 1) !== l2
) {
return false
}
// :[ xxx
// ^
if (state.src.charCodeAt(start + 2) === 0x20)
const next = state.src.charCodeAt(start + 2)
// :: xxx | :::xxx
// ^ | ^
if (next === 0x20 || next === 0x3A)
return false
if (silent)
return false
// :[]:
// ::::
if (max - start < 5)
return false
state.pos = start + 2
while (state.pos < max) {
// ]:
if (state.src.charCodeAt(state.pos) === 0x5D
&& state.src.charCodeAt(state.pos + 1) === 0x3A) {
// ::xxx::
// ^^
if (
state.src.charCodeAt(state.pos) === r1
&& state.src.charCodeAt(state.pos + 1) === r2
) {
found = true
break
}
@ -48,8 +61,10 @@ function createTokenizer(options: IconsOptions): RuleInline {
state.md.inline.skipToken(state)
}
if (!found || start + 2 === state.pos
// :[xxx ]:
if (
!found
|| start + 2 === state.pos
// ::xxx ::
// ^
|| state.src.charCodeAt(state.pos - 1) === 0x20
) {
@ -57,26 +72,18 @@ function createTokenizer(options: IconsOptions): RuleInline {
return false
}
const content = state.src.slice(start + 2, state.pos)
// found!
const info = state.src.slice(start + 2, state.pos)
// found
state.posMax = state.pos
state.pos = start + 2
const [name, opt = ''] = content.split(' ')
const [size, color = options.color] = opt.trim().split('/')
const icon = state.push('icon', 'i', 0)
const icon = state.push('vp_icon_open', 'VPIcon', 1)
icon.markup = ':['
icon.attrs = [['name', name]]
if (size || options.size)
icon.attrs.push(['size', String(size || options.size)])
if (color)
icon.attrs.push(['color', color])
const close = state.push('vp_icon_close', 'VPIcon', -1)
close.markup = ']:'
icon.markup = '::'
icon.content = info
icon.meta = { deprecated }
state.pos = state.posMax + 2
state.posMax = max
@ -84,3 +91,62 @@ function createTokenizer(options: IconsOptions): RuleInline {
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
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,7 @@ import { tasklist } from '@mdit/plugin-tasklist'
import { isPlainObject } from '@vuepress/helper'
import { abbrPlugin } from './abbr.js'
import { annotationPlugin } from './annotation.js'
import { iconsPlugin } from './icons.js'
import { iconPlugin } from './icons.js'
import { plotPlugin } from './plot.js'
export function inlineSyntaxPlugin(
@ -42,8 +42,8 @@ export function inlineSyntaxPlugin(
}
if (options.icons) {
// :[collect:name]:
md.use(iconsPlugin, isPlainObject(options.icons) ? options.icons : {})
// ::collect:name::
md.use(iconPlugin, isPlainObject(options.icons) ? options.icons : {})
}
if (

View File

@ -42,7 +42,7 @@ export interface MarkdownPowerPluginOptions {
/**
* iconify
*
* `:[collect:icon_name]:`
* `::collect:icon_name::`
*
* @default false
*/