vuepress2.0使用教程(8)-扩展MD功能(Section扩展及自定义语法)

       百家饭团队开发的百家饭OpenAPI平台是用vuepress2.0搭建的,搭建的时候不知道2.0还处在beta状态,所以导致后来踩了一些坑,使用过程中vuepress2.0也从2.0.0-beta.18升到了2.0.0-beta.48,有很多的变化,所以想写一个教程介绍一下vuepress2.0的情况以及使用经验。

大致计划写这些内容吧:

  1. vuepress介绍、选型原因及使用感受(先介绍一下自己用的情况吧)
  2. vuepress2.0使用(搭建一个vuepress支撑的网站)-第一部分
  3. vuepress2.0使用(搭建一个vuepress支撑的网站)-修改默认模板配置
  4. vuepress2.0使用(搭建一个vuepress支撑的网站)-详细配置默认模板
  5. vuepress2.0使用(搭建一个vuepress支撑的网站)-md文件的玩法
  6. vuepress2.0使用(扩展默认主题)- 准备及扩展页面组件
  7. vuepress2.0使用(扩展默认主题)- 提供更多页面模板并引入ElementUI
  8. vuepress2.0使用(扩展默认主题)- 扩展MD页面功能(Section扩展及自定义语法)
  9. vuepress2.0使用(扩展默认主题)- 引入百度搜索支持及其他库的引用
  10. 编写vuepress主题(自定义自己的框架,开发复杂程序)
  11. vuepress2.0使用——编译及站点部署
  12. vuepress2.0当前的状态(版本变化,方便大家升级,占位中,未书写)
  13. 更多读者发起话题点击这里进专题查看,就不更新标题了

这里先给搜索进来的同学提个醒:凡是网上搜索到需要修改clientAppEnhance.js 文件的教程都已经过时,最新版本已不再使用。另外说个题外话,我查了下,我为数不多的搜索关键词里面,vuepress的占了大部分,这里感谢大家的关注,这个教程肯定有不合理的地方,如果有任何需要帮助的,大家都可以留言,我尽力解答。

本周,百家饭OpenAPI平台已经初步完成了以API工作台为核心功能的0.6.0版本的初步开发测试,下周基本可以发布了,希望到时候给大家能够提供更好的API开发服务。

本周继续来讲我们的Vuepress教程。

vuepress 2.0中的md扩展

md扩展对于希望文章能够更精彩的博主来讲,是非常重要的功能,比如百家饭就用了Element-Plus的时间线来显示编辑器的更新日志。

那如何将这样的功能放到MD文件中,使得编辑人员可以自由使用呢。通过学习,我们觉得大概有这么两种方案:

扩展MD中的容器

vuepress中使用到了容器(container)的概念,来在md文件中提供复杂功能。

什么是容器?

vuepress的容器是以:::括起来的内容,默认主题提供三种容器(tip/warning/danger),调用的方式就是在开头的:::后面跟名称 

比如

::: tip
# 微服务版下载

隐私计算企业服务请拉取和使用微服务版。**微服务版本暂未提供给公共使用,请联系百家饭团队获得相关帮助**

> 百家饭OpenAPI微服务版为用户提供基于开发脚本的内部Web服务能力,将脚本的计算能力开放为Web Service接口,供其他业务系统调用。

微服务版采用Docker安装模式,请安装Docker服务之后,采用以下方式安装微服务版:

:::

显示为

 因此,如果要扩展不同的容器,以我们用的timeline为例,那我们就可以增加一种名为timeline的容器

为主题增加自定义容器

自定义容器的增加,我们需要在主题的index.js里增加如下的内容:

const { containerPlugin } = require('@vuepress/plugin-container')

module.exports = ({ themePlugins = {}, ...localeOptions }) => {
  // ctx.siteData.docs = option.sidebar
  return {
    ……
    plugins: [
      ……
      containerPlugin({
        type: 'timeline',
        before: (info) =>
          `<ClientOnly><el-timeline>\n`,
        after: () => '</el-timeline></ClientOnly>\n'
      }),

也就是在exports返回配置对象的plugins属性中增加一个containerPlugin,注意,该方法在旧版本中形式如下:

 ['@vuepress/container', {
        type: 'timeline',
        before: (info) =>
          `<ClientOnly><el-timeline>\n`,
        after: () => '</el-timeline></ClientOnly>\n'
      }],

如果搜索到按上述方法进行容器扩展的,应该已经过期了。

我们可以看出,容器其实就是给md中的内容增加一个前(通过before)后(提供after)的内容,共同形成html的方式。

掌握了这个方式,我们就可以使用容器把我们想要的各种内容在md中组织起来。

添加自定义MD语法

上面的方法里,我们扩展了容器的用法,最后可以通过不同的容器实现各种自定义组件,类似:

:::: timeline

::: timeline-item 2022-06-13
### v0.2.0

* 更新了对于复杂属性的定义界面
* 提升了复杂接口的渲染速度
* 提升了Json编辑器的打开速度
* 支持array类型参数的详细字段定义

:::

::::

上面的代码里,我们定义了两级的容器(为了区分级别,md中的容器要求外层比里层至少多一个":"),但是这种结构用多了显得很麻烦,md里到处都是:。

那还有没有更高级的方式呢,有,就是自定义md语法。比如容器的语法就是通过三个以上的:来触发。那我们也可以自定义自己的语法。

比如我们定义了一个语法结构!{}}来表示中间的内容是纯html变量。

引用自定义组件

上述的方式是通过扩展md的方式完成的,同样,我们需要扩展index.js,引入以下内容

const plugin1 = require("./md_html_plugin")

module.exports = ({ themePlugins = {}, ...localeOptions }) => {
  // ctx.siteData.docs = option.sidebar
  return {
    ……
    extendsMarkdown: (md) => {
      md.use(plugin1)
    },

 在配置的extendsMarkdown属性中,告诉md组件去使用一个新的plugin,而这个plugin由我们自己进行定义即可。

定义组件

那如何定义这个组件呢,vuepress的md解析使用的是markdown-it,这里定义组件的方式就是定义markdown-it扩展的方式。

markdown-it扩展的定义有些复杂,这个部分可能不能覆盖所有的逻辑,有复杂需求的需要另行搜索。

定义组件的exports

组件定义的exports部分逻辑如下:

module.exports = function vue_html_plugin(md) {
    md.block.ruler.before('heading', 'vue_html', container);
    md.renderer.rules['vue_html_open'] = (tokens, index, opts, env) => {
        var token = tokens[index];
        return `<div v-html='${token.info}'>`;
    };
    md.renderer.rules['vue_html_close'] = () => "</div>";
}

这个部分的包含多个部分(我们的html渲染例子中为三个部分,复杂的插件可能更多),第一个部分,定义了我们这个插件所处的层级,这和md的解析有关系,我们定义的插件定义在解析heading之前,名称定为了vue_html,具体的解析函数是名为container的函数。

那第二个和第三个部分则对应处理我们插件增加的新的语法结构,在我们的例子中,我们定义的插件逻辑较为简单,把插件内部的内容作为v-html传给一个div即可,因此,层级简单,只需要把!{ content }}解析为<div v-html="content"></div>即可,因此我们只需要定义两个新的规则,一个是vue_html_open,一个是vue_html_close,分别用于渲染div定义的开始部分和结束部分即可。

定义组件的解析函数。

接下来就要具体写container函数了,具体如下:

var marker_str = '!{',
    marker_char = marker_str.charCodeAt(0),
    marker_len = 2,
    marker_end_str = '}}',
    marker_end_char = marker_end_str.charCodeAt(0)

function container(state, startLine, endLine, silent) {
    var pos, nextLine, marker_count, markup, params, token,
        old_parent, old_line_max,
        auto_closed = false,
        start = state.bMarks[startLine] + state.tShift[startLine],
        max = state.eMarks[startLine];

    // 如果传进来的内容的第一个字符不是我们需要的"!",直接返回
    if (marker_char !== state.src.charCodeAt(start)) { return false; }

    // 再继续匹配"!{"的其他部分,我们的例子里,开头是!{,可以不用循环,如果你定义的符号更为复杂,可以参考该循环的写法
    for (pos = start + 1; pos <= max; pos++) {
        if (marker_str[(pos - start) % marker_len] !== state.src[pos]) {
            break;
        }
    }

    marker_count = pos - start;
    if (marker_count < marker_len) { return false; } //如果匹配长度不够,直接返回

    markup = state.src.slice(start, pos - marker_len);

    //内容初始位置指向开始匹配符后面
    start = pos


    //循环匹配内容,直到遇到结束符为止
    for (; ;) {
        pos++
        if (marker_end_char !== state.src.charCodeAt(pos)) { continue; }
        for (pos1 = pos + 1; pos1 <= max; pos1++) {
            if (marker_end_str[pos1 - pos] !== state.src[pos1]) {
                break;
            }
        }
        break;
    }

    //将内容存储
    params = state.src.slice(start, pos);

    //缓存旧的状态情况
    old_parent = state.parentType;
    old_line_max = state.lineMax;
    state.parentType = 'vue_html';


    //将vue_html_open推送到解析栈中,传入内容作为info参数
    token = state.push('vue_html_open', 'div', 1);
    token.markup = markup;
    token.block = true;
    token.info = params;
    token.map = [startLine, startLine];

    //将vue_html_close推送到解析栈中
    token = state.push('vue_html_close', 'div', -1);
    token.markup = state.src.slice(start, pos);
    token.block = true;

    //将原有堆栈情况复原
    state.parentType = old_parent;
    state.lineMax = old_line_max;
    state.line = startLine + (auto_closed ? 1 : 0);

    return true;
}

注意,本例子只用于单行内容插件,多行内容插件可以参考vscode-markdown-extended/markdownItAdmonition.ts at master · qjebbs/vscode-markdown-extended · GitHub

插件的书写要求较高,需要有语法树解析的基础认知,要不理解比较困难。 总之,如果有复杂的需求,可以参考来写,这章写的也没啥底气,呵呵,有错误或者有问题的,欢迎提问,共同探讨。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

百家饭AI

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值