五花八门的 Markdown Admonitions

2022-07-18 · 1,172 chars · 6 min read

背景#

最近写博客的时候,感觉在文章样式上,表现力还不是特别足。例如,我想加个说明、提示、警告、引用,在 markdown 里全部都是 >,如下:

就是 <blockquote>,块级引用元素

样式比较单调,承载太多功能,直接导致表意不明。

五花八门的 Admonitions#

为了更好的区分 Note、Tip、Warning、Blockquote,打算把 Admonitions 功能加上,或者也可以叫 Alert,就是下面这样的东西,功能已经做好了大家直接看:

Note

这是一条 Note

Tip

这是一条 Tip

Warning

这是一条 Warning

这样是不是更清晰易懂了?其实这种功能在中后台系统、文档类的站点里经常看到。

但就是这么一个简单的功能,在 markdown 里面,实现的五花八门。主要原因还是 markdown 没有明确统一的标准,变体太多,加上功能实现比较简单。例如我这里就是直接用 remark-directive,基于通用指令实现的。我们先来看看各路选手长什么样子

Github 方案#

目前 Github 实现了这样一个版本:

> **Note**
> This is a note

> **Warning**
> This is a warning

还是基于引用来做的,效果可以看这里

微软方案#

微软的方案,语法是这样的:

> [!NOTE]
> Information the user should notice even if skimming.

> [!TIP]
> Optional information to help a user be more successful.

> [!IMPORTANT]
> Essential information required for user success.

> [!CAUTION]
> Negative potential consequences of an action.

> [!WARNING]
> Dangerous certain consequences of an action.

它实现的类型还挺多,效果可以看这里,我就不截图了

加个 blockquote 小声说一下,我的样式就是“参考”的微软

MDN 方案#

MDN 语法长这样

> **Note:** This is how you write a note.
>
> It can have multiple lines.


> **Warning:** This is how you write a warning.
>
> It can have multiple paragraphs.

效果可以在这里看,大家看 MDN 文档的时候应该也能看到

VuePress 方案#

VuePress 的没用过,简单看了下底层应该是 markdown-it,它的语法是这样的

::: tip
这是一个提示
:::

::: warning
这是一个警告
:::

::: danger
这是一个危险警告
:::

::: details
这是一个详情块,在 IE / Edge 中不生效
:::

Docusaurus 方案#

Docusaurus 我特别喜欢,它和我的博客一样是使用 mdx 的,通过 remark 插件实现 Admonitions,不过用了一个挺老的库 remark-admonitions

:::note 你的标题

note

:::

:::tip

tip

:::

:::

:::danger

danger

:::

在这里看效果,Docusaurus 的实现非常完善,毕竟是专业的文档工具。

我的实现#

综合看下来,Github 的实现非常保守和收敛,没太多功能。微软支持的类型稍微丰富一些。MDN 的功能会更多,可以自定义标题。Docusaurus 其实是最完善的,但是插件有点老,看着好久没更新了。

我简单比较了下,还是喜欢基于通用指令的方式做,因为在内容前后分别加 :::Note:::,中间可以写任何东西,而且写起来要比 > 方便的多。

比如我可以写成这样:

这里的文案还可以自定义!!!

这是一个 Tip

const str = 'hello world!'
console.log(str)

具体的实现方案,还是基于 remark 的那一套,首先添加 remark-directive,然后是自己写的插件

{
  loader: '@mdx-js/loader', // webpack 里用 mdx loader 加载
  options: {
    remarkPlugins: [
      remarkGfm,
      remarkMdxImages,
      remarkDirective, // 这是 remark-directive
      admonitionsPlugin, // 这是自己写的插件
    ],
    providerImportSource: '@mdx-js/react',
  },
},

admonitions plugin 的代码如下:

import { visit } from 'unist-util-visit'

function admonitionsPlugin() {
  return (tree) => {
    visit(tree, (node) => {
      if (node.type === 'containerDirective') {
        // 仅仅支持 Note Warning Tip
        if (node.name !== 'Note' && node.name !== 'Warning' && node.name !== 'Tip') {
          return
        }

        const data = node.data || (node.data = {})

        // title 的文案优先使用自定义的
        const title = node.attributes.title || node.name;

        data.hName = 'div'

        // 加两个 class,控制样式
        data.hProperties = {
          className: `admonition admonition-${node.name.toLowerCase()}`,
        }

        // 插入 title
        node.children.unshift({
          type: 'paragraph',
          data: {
            hProperties: {
              // 设置 title 的样式
              className: 'admonition-title',
            },
          },
          // 插入 title 内容
          children: [{ type: 'text', value: title }],
        })
      }
    })
  }
}

export default admonitionsPlugin

最终的效果是输入:

:::Note{title="这里可以添加可选的title"}
这是一条 note
:::

输出:

<div class="admonition admonition-note">
  <p class="admonition-title">这里可以添加可选的title</p>
  <p>这是一条 note</p>
</div>

剩下的就是 CSS 的事情了。

有点尴尬

开头就说我是在写博客的,要写的博客没写完,反倒是开发了一个新功能,果然埋头干活比动脑思考更容易...

赞赏

微信