Markdown Preview Enhanced 将掘金MD主题搬过来

3,221 阅读4分钟

我们都知道,一些使用markdown语法的博客系统喜欢用YAML front matter表达文章信息。

---
title: "Markdown Preview Enhanced  将掘金MD主题搬过来"
author: "Bambi"
date: 2023-06-11
theme: fancy
---

就像上面这样,其中theme字段定义了markdown文档的主题样式。掘金的编辑器就有这个功能,而且掘金的主题集众多优秀掘友的创意于一身,让我十分爱不释手。

把掘金MD主题搬到我的编辑器

不过相较于五花八门的博客系统,平时写笔记我用的更多的还是vscode编辑器。而且除了写技术笔记之外,我还会用markdown语法去写一些需要对外展示的内容,比如简历文章。这时掘金的主题更能显示出优势。毕竟如果只是写笔记,普通的黑色主题足矣。

于是,我就想能不能把掘金里面这些好看的主题和方便的front matter搬到我的编辑器里面随时取用。通过比对我选择了Markdown Preview Enhanced这款预览插件,因为他有比较丰富的自定义功能,并且可以方便的右键进行PDF的导出。不过想要享用掘金的主题效果,还要稍微动手自定义一番。

目标效果

这就是在vscode中的最终效果,可以像在掘金编辑器里面一样,通过front matter配置theme实时享用鲜美的掘金主题。 demo.gif

Markdown Preview Enhanced

目前MPE(简称)是可以支持通过Settings切换多套MD主题的,可是这些主题我认为都没有掘金的主题香。于是咱们继续看他的自定义功能:

image.png 显然这个功能可以满足我们配置多套css的需求,只需要将喜欢的掘金主题从GitHub上整理到自己的电脑即可。

但是找遍MPE的文档,也没有看见更加配置化的介入方法了。也就是说我们没办法像掘金编辑器一样,用front mattertheme字段优雅的配置这个样式。而必须要像写代码一样@import来实现,这是违背markdown易读易写的宗旨的。

在自己的编辑器中,实现用front matter一键切换掘金主题

我需要像下面这样,输入主题名即可配置主题

---
title: "Markdown Preview Enhanced  将掘金MD主题搬过来"
author: "Bambi"
date: 2023-06-11
theme: fancy
---

目前,MPE本身是不能识别theme字段的,但是他预留了定制化markdown效果的Extend Parser!使用其中的onWillParseMarkdown接口,就可以在parser.js自定义markdown的写法了!

image.png

下面就是我基于onWillParseMarkdown接口的实现,只需要识别出theme配置,再插入插件看得懂的@import语法即可。

parser.js

module.exports = {
  onWillParseMarkdown: function(markdown) {
    return new Promise((resolve, reject)=> {
      // YAMLCfgList 整个YAML的配置的数组
      const YAMLCfgList = markdown?.match(/---\n([\s\S]*?)\n---/)?.[1]?.split('\n'); 
      // ThemeYAMLCfg theme字段在YAML中的配置字符串
      const ThemeYAMLCfg = YAMLCfgList?.find(one => /^[^:]*theme\:[\s\S]*/.test(one));
      // theme 主题名
      const theme = ThemeYAMLCfg?.split(':')[1]?.trim();
      // 如果配置了theme,就在YAML配置后面换行插入@import语句,URL是less文件所在的相对路径
      if (theme) {
        const relativePath = `../_style/${theme}.less`;
        const md = markdown.replace(/(---\n([\s\S]*?)\n---)/, `$1\n@import \"${relativePath}\" \n`)
        return resolve(md)
      }
      // 如果没有配置theme,正常输出原文
      return resolve(markdown)
    })
  },
  ...
}

这里要注意几个小问题:

  1. 目前MPE@import语法是不支持绝对路径的,只支持相对路径。所以主题最好是和.md文件同目录。我这里因为需要实现一个日常配置,不可能每次都把.less丢进去。我这里的解决办法是建一个指定的目录来存放md文档,主题文件也固定放在这里:

    • 在用户目录下面新建一个doc目录,作为日常写文档的指定路径。
    • 在doc目录下建一个_style文件,把喜欢的掘金主题丢进去即可。
    • 在doc目录下建一个你需要的分类目录,存放相应的.md文件,所以代码中固定的路径是这样的:../_style/${theme}.less。如果以后想给文档更灵活分类,层级变多的话,需要需要再优化一下,我这里是初版就不拓展了。
    ~/
    └── doc/
        ├── _style/
        │   └── fancy.less
        │   └── ...
        └── example/
            └── demo.md
            └── ...

2. @import语句一定要插入到front matter后面,front matter不在头部会导致md预览不能识别。所以我这里:$1\n@import \"${relativePath}\" \n,代表在$1选中的语句后面换行插入@import。

const md = markdown.replace(/(---\n(\[\s\S]\*?)\n---)/, `$1\n@import \"${relativePath}\" \n`)
  1. 目前MPE喜爱的样式语法是.less,而掘金的主题在GitHub上实现为.scss,所以这里需要做一下语言转换,我是使用verytoolz的在线工具直接转换的。

解决报错

嗯,到此为止,这个输入掘金主题名可以在vscode使用掘金主题的插件效果就实现了。不过还有个别扭的地方,就是主题名输入的不正确的时候,预览顶部会出现一行获取不到文件的报错信息。

zadrfs.jpg

这个加入一个文件是否存在的判断即可解决。

const absolutePath = `/Users/你的用户目录/doc/_style/${theme}.less`;
const fs = require('fs');
// 判断这个主题在_style目录是否存在,如果存在就做转换处理
if (fs.existsSync(absolutePath)) {
  const md = markdown.replace...
  return resolve(md)
}
  • 这里要注意的是,MPE的这个parser.js文件,并不在我们的doc目录下,他在MPE自己的目录里面,所以我们在这里使用fs模块,一定要使用绝对路径哈。

局限

此方法暂不支持设置预览样式,请将预览样式设置为none,并且使用Markdown Preview Enhanced: Customize CSS访问此插件全局的基础样式style.less,在里面补充你需要的背景色基调,对应掘金的深色模式和浅色模式:

image.png

深色模式深色背景浅色字,浅色背景反之。我这里用的最普通的白背景黑字。

最终parser.js完整代码

module.exports = {
  onWillParseMarkdown: function(markdown) {
    return new Promise((resolve, reject)=> {
      const YAMLCfgList = markdown?.match(/---\n([\s\S]*?)\n---/)?.[1]?.split('\n');
      const ThemeYAMLCfg = YAMLCfgList?.find(one => /^[^:]*theme\:[\s\S]*/.test(one));
      const theme = ThemeYAMLCfg?.split(':')[1]?.trim();
      if (theme) {
        const absolutePath = `/Users/你的用户目录/doc/_style/${theme}.less`;
        const relativePath = `../_style/${theme}.less`;
        const fs = require('fs');
        if (fs.existsSync(absolutePath)) {
          const md = markdown.replace(/(---\n([\s\S]*?)\n---)/, `$1\n@import \"${relativePath}\" \n`)
          return resolve(md)
        }
      }
      return resolve(markdown)
    })
  },
  ...
}