你可能不知道的vitepress扩展-1

前言

在我编写的在线字典网站中,我碰到了如下一个需求,如下图所示:

让我为你叙述这个需求需要做什么。

需求分析

在 Markdown 中,我们希望能够渲染一个特定的字符(如汉字),并且它会被一个圆圈包围。为了满足这一需求,我们需要做的工作是:

  • 使用正则表达式匹配 Markdown 中的特定符号(如:o汉字o)。
  • 将匹配到的字符替换为 HTML 元素(如:<span class="char-circle">汉字</span>)。
  • 使用 CSS 为这个 HTML 元素添加样式,使得字符被圆圈包围。

对于这个需求,我们只需要扩展一下markdown语法即可,我们先来看看需求的实现。

实现思路

  1. 扩展 Markdown 配置:
    我们可以通过修改 vitepress 的 Markdown 配置来实现这一功能。在 vitepress 中,我们可以使用 config 函数来扩展 Markdown 渲染规则,修改默认的文本渲染方式。
  2. CSS 样式:
    通过为匹配到的字符添加一个 span 标签,并为这个标签设置圆形边框和其他样式,实现字符的圆圈效果。
  3. 使用正则表达式:
    我们通过正则表达式来匹配包裹在 o 字符中的汉字,并将其转换为带有样式的 HTML 标签。

具体实现

如何扩展markdown配置

查阅官方文档,我们发现对于扩展markdown的描述很简单,只是提供了一个markdown属性,而我们就需要根据这个属性来做文章。

根据ts定义文件,这个属性提供了很多配置属性,接口定义如下:

ts 复制代码
interface Options$2 {
    html?: boolean | undefined;
    xhtmlOut?: boolean | undefined;
    breaks?: boolean | undefined;
    langPrefix?: string | undefined;
    linkify?: boolean | undefined;
    typographer?: boolean | undefined;
    quotes?: string | string[];
    highlight?: ((str: string, lang: string, attrs: string) => string) | null | undefined;
}
interface MarkdownOptions extends Options$2 {
    preConfig?: (md: MarkdownIt) => void;
    config?: (md: MarkdownIt) => void;
    cache?: boolean;
    externalLinks?: Record<string, string>;
    theme?: ThemeOptions;
    languages?: LanguageInput[];
    languageAlias?: Record<string, string>;
    lineNumbers?: boolean;
    defaultHighlightLang?: string;
    codeTransformers?: ShikiTransformer[];
    shikiSetup?: (shiki: Highlighter) => void | Promise<void>;
    codeCopyButtonTitle?: string;
    anchor?: anchor.AnchorOptions;
    attrs?: {
        leftDelimiter?: string;
        rightDelimiter?: string;
        allowedAttributes?: Array<string | RegExp>;
        disable?: boolean;
    };
    emoji?: {
        defs?: Record<string, string>;
        enabled?: string[];
        shortcuts?: Record<string, string | string[]>;
    };
    frontmatter?: FrontmatterPluginOptions;
    headers?: HeadersPluginOptions | boolean;
    sfc?: SfcPluginOptions;
    toc?: TocPluginOptions;
    component?: ComponentPluginOptions;
    container?: ContainerOptions;
    math?: boolean | any;
    image?: Options$1;
    gfmAlerts?: boolean;
}

如果将每一个属性都解释一下,那就可以另外写一篇文章了,所以这里暂时打住,这里我们需要用到的是config属性,它是一个函数,函数的第一个参数是一个md实例。由于vitepress底层采用的是markdown-it插件,因此想要了解md实例的具体属性,也需要我们去查看这个插件的文档了解api属性,这同样不在本文的范畴,因此这里只是固定告诉大家,我们需要用到md.renderer.rules.text属性,该属性接受一个返回格式化后的文本字符串。

相信读者能够猜到怎么回事了,对,没错,我将使用正则表达式来匹配这个字符,然后为这个字符转换成一个带有样式的html字符串,然后返回即可。代码结构如下:

ts 复制代码
// .vitepress/config.mts中

import { defineConfig } from 'vitepress'

export default defineConfig({
      markdown: {
    config: (md) => {
      md.renderer.rules.text = (tokens, idx) => {
        const text = tokens[idx].content;
        // 核心代码
        return text;
      };
    },
  },
})

css样式的实现

通过上图所示,接下来我们需要实现一个带圆圈的汉字,这很简单,我们利用边框属性和圆角属性调整一下这个元素即可,具体说来就是,我们将这个字用一个html标签包住,然后给这个标签添加一个class类名,再调整样式即可。html代码和css代码分别如下:

html 复制代码
<span class="char-circle">又</span>
css 复制代码
.char-circle {
    display: inline-block;
    width: 18px;
    height: 18px;
    text-align: center;
    line-height: 14px;
    font-size: 12px;
    padding: 1px;
    border-radius: 50%;
    border: 1px solid #000000;
    vertical-align: text-top;
    font-weight: bold;
}

解释一下如上css代码含义:

  • display: inline-block;:使得 span 标签在同一行显示,并允许设置宽度和高度。
  • widthheight:设置圆圈的宽度和高度。
  • text-align: center;line-height: 14px;:使得字符在圆圈内居中显示。
  • font-size:设置字体大小。
  • border-radius: 50%;:设置圆角效果,使得 span 标签变成圆形。
  • border:设置圆圈的边框。
  • vertical-align: text-top;:调整文本的垂直位置,使其与其他文本对齐。

这里也许有人好奇,为什么我不直接在每个md文档里面直接写这个html标签元素即可,一篇md文档就不止一个汉字,基本上每个汉字都会有1到2个这种特殊的字符,难道我每一篇文档每一个字的特殊字符都要去写html标签吗?这显然是不合适的,因此我们就需要定义一个规则,然后通过正则表达式来匹配,当满足这个匹配条件,就将这个结果给替换。

正则表达式的实现

这里我使用的是2个o字符来包裹它,如下所示:

md 复制代码
// md文档

o又o。

当匹配到如上的字符文档,我们就需要将这个字符转换成如下所示的html标签:

html 复制代码
<span class="char-circle">又</span>

接下来就是如何定义这个正则表达式了,第一步我们可以使用如下的正则表达式:

ts 复制代码
const rule = /o(.*?)o/g;

解释一下这个正则表达式,其实主要核心在于(.*?),这是一个捕获组,其中.*?表示匹配任意字符。

这个正则表达式能处理大多数情况,但有一种特殊情况不行,例如如下结构的文档:

md 复制代码
诰(gao):xxxx。o喻o: xxxx。

这里会将gao拼音中的o字母作为开始字符,然后喻前面的o字符作为结束字符匹配,这显然不符合我们想要的结果,也就是只匹配o喻o这三个字符。

让我们更精细的优化一下这个正则表达式,也就是说我们要规定两个o字符之间匹配的一定是一个中文字符,因为只有类似又,喻,特这样的字符才会是这种特殊字符。那么匹配中文字符的正则表达式是什么?很显然我们需要用到unicode编码,代码如下所示:

ts 复制代码
[\u4e00-\u9fa5]

调整过后的正则表达式就变成了如下这样:

ts 复制代码
const rule = /o([\u4e00-\u9fa5])o/g;

注意这里一定有加上()表示这是一个捕获组,这样方便我们通过捕获组的属性来访问这个匹配到的字符。

接下来我们就需要使用字符串的replace方法来匹配字符串,然后将匹配到的字符转换成我们需要渲染的html标签即可,代码如下所示:

ts 复制代码
 const transformedText = text.replace(/o([\u4e00-\u9fa5])o/g, (_match, p1) => `<span class="char-circle">${p1}</span>`);

可以看到,这里我们通过了第二个参数,也就是捕获组1,来访问匹配到的字符,这就是添加()的意义所在,至于_match,就是一个忽略的参数,_可以理解为占位符,表示这个参数必须要存在,但是不会被后续用到。最后,我们完善一下config函数,如下所示:

ts 复制代码
// .vitepress/config.mts中

import { defineConfig } from 'vitepress'

export default defineConfig({
      markdown: {
    config: (md) => {
      md.renderer.rules.text = (tokens, idx) => {
        const text = tokens[idx].content;
         const transformedText = text.replace(/o([\u4e00-\u9fa5])o/g, (_match, p1) => `<span class="char-circle">${p1}</span>`);
        return transformedText;
      };
    },
  },
})

如此一来,我们就完成了这个需求。

总结

通过以上步骤,我们实现了在 Markdown 中渲染带圆圈的汉字字符。这个方案非常灵活,因为它能够根据需求自动处理不同的文本,而不需要在每个 Markdown 文档中手动插入 HTML 标签。通过扩展 Markdown 渲染规则和使用正则表达式,我们为用户提供了更加简洁的写作体验。

相关推荐
前端 贾公子19 分钟前
axios如何利用promise无痛刷新token
前端
新生派2 小时前
HTML<hgroup>标签
前端·html
timer_0172 小时前
Tailwind CSS 正式发布了 4.0 版本
前端·css
答题卡上的情书3 小时前
uniapp版本升级
前端·javascript·uni-app
枫叶丹44 小时前
【HarmonyOS之旅】基于ArkTS开发(三) -> 兼容JS的类Web开发(三)
开发语言·前端·javascript·华为·harmonyos
酷爱码4 小时前
HTML5+SVG+CSS3实现雪中点亮的圣诞树动画效果源码
前端·css3·html5
有杨既安然4 小时前
Vue.js组件开发深度指南:从零到可复用的艺术
前端·javascript·vue.js·npm
步、步、为营7 小时前
C#牵手Blazor,解锁跨平台Web应用开发新姿势
开发语言·前端·c#
i7i8i9com7 小时前
node-sass已经废弃了,需要替换成以下方式
前端·css·sass
我命由我123458 小时前
脚本运行禁止:npm 无法加载文件,因为在此系统上禁止运行脚本
前端·javascript·前端框架·npm·node.js·html·js