你可能不知道的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 渲染规则和使用正则表达式,我们为用户提供了更加简洁的写作体验。

相关推荐
wordbaby7 分钟前
搞不懂 px、dpi 和 dp?看这一篇就够了:图解 RN 屏幕适配逻辑
前端
程序员爱钓鱼10 分钟前
使用 Node.js 批量导入多语言标签到 Strapi
前端·node.js·trae
鱼樱前端11 分钟前
uni-app开发app之前提须知(IOS/安卓)
前端·uni-app
V***u45311 分钟前
【学术会议论文投稿】Spring Boot实战:零基础打造你的Web应用新纪元
前端·spring boot·后端
i听风逝夜1 小时前
Web 3D地球实时统计访问来源
前端·后端
iMonster1 小时前
React 组件的组合模式之道 (Composition Pattern)
前端
呐呐呐呐呢1 小时前
antd渐变色边框按钮
前端
元直数字电路验证1 小时前
Jakarta EE Web 聊天室技术梳理
前端
wadesir1 小时前
Nginx配置文件CPU优化(从零开始提升Web服务器性能)
服务器·前端·nginx
牧码岛1 小时前
Web前端之canvas实现图片融合与清晰度介绍、合并
前端·javascript·css·html·web·canvas·web前端