From a023ca8654797eae18ab7264e100e8f9bec969bb Mon Sep 17 00:00:00 2001 From: pengzhanbo Date: Sat, 26 Apr 2025 01:20:05 +0800 Subject: [PATCH] feat(theme)!: deprecate old icon syntax and use `::icon::` syntax instead (#563) --- cli/templates/.vuepress/config.ts.handlebars | 2 +- docs/en/notes/theme/guide/markdown/icons.md | 55 ++++--- .../theme/guide/quick-start/optimize-build.md | 37 +++-- docs/notes/theme/config/intro.md | 4 +- .../theme/config/plugins/markdown-power.md | 4 +- docs/notes/theme/guide/markdown/icons.md | 48 ++++--- .../theme/guide/quick-start/optimize-build.md | 2 +- docs/sponsor.md | 2 +- plugins/plugin-md-power/README.md | 20 +-- .../__snapshots__/iconsPlugin.spec.ts.snap | 44 +++--- .../__test__/iconsPlugin.spec.ts | 54 +++---- .../plugin-md-power/src/node/inline/icons.ts | 136 +++++++++++++----- .../plugin-md-power/src/node/inline/index.ts | 6 +- plugins/plugin-md-power/src/shared/plugin.ts | 2 +- 14 files changed, 269 insertions(+), 147 deletions(-) diff --git a/cli/templates/.vuepress/config.ts.handlebars b/cli/templates/.vuepress/config.ts.handlebars index c3eee397..889ea84d 100644 --- a/cli/templates/.vuepress/config.ts.handlebars +++ b/cli/templates/.vuepress/config.ts.handlebars @@ -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) diff --git a/docs/en/notes/theme/guide/markdown/icons.md b/docs/en/notes/theme/guide/markdown/icons.md index 5da9cb10..34617d02 100644 --- a/docs/en/notes/theme/guide/markdown/icons.md +++ b/docs/en/notes/theme/guide/markdown/icons.md @@ -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 [``](../components/icon.md) component for using icons in Markdown and a simplified Markdown syntax for easier icon usage. +The theme provides an [``](../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 . 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:: diff --git a/docs/en/notes/theme/guide/quick-start/optimize-build.md b/docs/en/notes/theme/guide/quick-start/optimize-build.md index efd86df5..69e9f1d0 100644 --- a/docs/en/notes/theme/guide/quick-start/optimize-build.md +++ b/docs/en/notes/theme/guide/quick-start/optimize-build.md @@ -10,14 +10,19 @@ permalink: /en/guide/optimize-build/ When we embed images in markdown using `[alt](url)` or ``, 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 -`` 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. +`` 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 `` 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 `` 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 `` 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. ::: diff --git a/docs/notes/theme/config/intro.md b/docs/notes/theme/config/intro.md index 2ce59c03..0b22888a 100644 --- a/docs/notes/theme/config/intro.md +++ b/docs/notes/theme/config/intro.md @@ -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` 等字段。 diff --git a/docs/notes/theme/config/plugins/markdown-power.md b/docs/notes/theme/config/plugins/markdown-power.md index 72012ce7..3f674c10 100644 --- a/docs/notes/theme/config/plugins/markdown-power.md +++ b/docs/notes/theme/config/plugins/markdown-power.md @@ -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) diff --git a/docs/notes/theme/guide/markdown/icons.md b/docs/notes/theme/guide/markdown/icons.md index 99739027..acac63a8 100644 --- a/docs/notes/theme/guide/markdown/icons.md +++ b/docs/notes/theme/guide/markdown/icons.md @@ -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:: diff --git a/docs/notes/theme/guide/quick-start/optimize-build.md b/docs/notes/theme/guide/quick-start/optimize-build.md index d44b63f2..3f46bf79 100644 --- a/docs/notes/theme/guide/quick-start/optimize-build.md +++ b/docs/notes/theme/guide/quick-start/optimize-build.md @@ -72,7 +72,7 @@ export default defineUserConfig({ 下载大约 __70Mb__ 的资源包,如果加载全部的图标到文档站点中,这太大太大了。 但请放心,主题仅会加载您有使用到的图标资源,这包括 导航栏、侧边栏、首页 Features 等配置中的 iconify 图标, -以及通过语法糖 `:[collect:name]:` 和 组件 `` 等各种方式使用的图标。 +以及通过语法糖 `::collect:name::` 和 组件 `` 等各种方式使用的图标。 当从本地 `@iconify/json` 中加载图标时, iconify 通过 `[collect]:[name]` 的形式为图标命名,其中根据 `collect` 来区分图标所属的集合,每个集合拥有 100 ~ 1000+ 数量不等的图标,保存在以 `collect` 为维度的 `json` diff --git a/docs/sponsor.md b/docs/sponsor.md index 50b2475a..aa691b78 100644 --- a/docs/sponsor.md +++ b/docs/sponsor.md @@ -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"} | diff --git a/plugins/plugin-md-power/README.md b/plugins/plugin-md-power/README.md index 2b4b0d35..68d755b7 100644 --- a/plugins/plugin-md-power/README.md +++ b/plugins/plugin-md-power/README.md @@ -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 diff --git a/plugins/plugin-md-power/__test__/__snapshots__/iconsPlugin.spec.ts.snap b/plugins/plugin-md-power/__test__/__snapshots__/iconsPlugin.spec.ts.snap index d9a16e9e..1a628b6c 100644 --- a/plugins/plugin-md-power/__test__/__snapshots__/iconsPlugin.spec.ts.snap +++ b/plugins/plugin-md-power/__test__/__snapshots__/iconsPlugin.spec.ts.snap @@ -1,108 +1,108 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`iconsPlugin > should not work with invalid icon 1`] = ` -"

:[ mdi:11 ]:

+"

:: mdi:11 ::

" `; exports[`iconsPlugin > should not work with invalid icon 2`] = ` -"

:[]:

+"

::::

" `; exports[`iconsPlugin > should not work with invalid icon 3`] = ` -"

:[]&

+"

::]&

" `; exports[`iconsPlugin > should not work with invalid icon 4`] = ` -"

:[:[

+"

::::

" `; exports[`iconsPlugin > should not work with invalid icon 5`] = ` -"

:[mdi:11

+"

::mdi:11

" `; exports[`iconsPlugin > should work 1`] = ` -"

+"

" `; exports[`iconsPlugin > should work 2`] = ` -"

strong

+"

strong

" `; exports[`iconsPlugin > should work 3`] = ` "

strong - -

+ +

" `; exports[`iconsPlugin > should work with options 1`] = ` -"

+"

" `; exports[`iconsPlugin > should work with options 2`] = ` -"

strong

+"

strong

" `; exports[`iconsPlugin > should work with single icon options 1`] = ` -"

+"

" `; exports[`iconsPlugin > should work with single icon options 2`] = ` -"

+"

" `; exports[`iconsPlugin > should work with single icon options 3`] = ` -"

+"

" `; exports[`iconsPlugin > should work with single icon options 4`] = ` -"

+"

" `; exports[`iconsPlugin > should work with single icon options 5`] = ` -"

+"

" `; exports[`iconsPlugin > should work with single icon options 6`] = ` -"

+"

" `; exports[`iconsPlugin > should work with single icon options 7`] = ` -"

+"

" `; exports[`iconsPlugin > should work with single icon options 8`] = ` -"

+"

" `; exports[`iconsPlugin > should work with single icon options 9`] = ` -"

+"

" `; exports[`iconsPlugin > should work with single icon options 10`] = ` -"

+"

" `; exports[`iconsPlugin > should work with single icon options 11`] = ` -"

+"

" `; diff --git a/plugins/plugin-md-power/__test__/iconsPlugin.spec.ts b/plugins/plugin-md-power/__test__/iconsPlugin.spec.ts index eddb32a6..b2ee7234 100644 --- a/plugins/plugin-md-power/__test__/iconsPlugin.spec.ts +++ b/plugins/plugin-md-power/__test__/iconsPlugin.spec.ts @@ -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() }) }) diff --git a/plugins/plugin-md-power/src/node/inline/icons.ts b/plugins/plugin-md-power/src/node/inline/icons.ts index b2763827..d9f7762b 100644 --- a/plugins/plugin-md-power/src/node/inline/icons.ts +++ b/plugins/plugin-md-power/src/node/inline/icons.ts @@ -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 = (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 `` +} + +export const iconPlugin: PluginWithOptions = (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) + } +} diff --git a/plugins/plugin-md-power/src/node/inline/index.ts b/plugins/plugin-md-power/src/node/inline/index.ts index 092a59ec..6da53684 100644 --- a/plugins/plugin-md-power/src/node/inline/index.ts +++ b/plugins/plugin-md-power/src/node/inline/index.ts @@ -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 ( diff --git a/plugins/plugin-md-power/src/shared/plugin.ts b/plugins/plugin-md-power/src/shared/plugin.ts index 79a783d4..1a004cc5 100644 --- a/plugins/plugin-md-power/src/shared/plugin.ts +++ b/plugins/plugin-md-power/src/shared/plugin.ts @@ -42,7 +42,7 @@ export interface MarkdownPowerPluginOptions { /** * 是否启用 iconify 图标嵌入语法 * - * `:[collect:icon_name]:` + * `::collect:icon_name::` * * @default false */