markdown 支持 Mermaid 流程图的方案
前几天使用 Kimi 生成了几个流程文档,文档中有流程图,但是我发现流程图代码可以在语雀上面渲染成流程,而我自己的博客只能显示源码,当时也只觉得不支持这种语法就没管,今天心血来潮查了一下这种流程图如何在 markdown 中渲染成功图片。经过短暂的知识吸收,我就完成了博客改造。
认识 Mermaid
现在的 AI 输出的内容都是 markdown 格式的,而 Mermaid 可以将 markdown 中的图标格式的代码块渲染成图标。
比如一个流程图的代码格式如下,只需要把代码块语法设置成 Mermaid 就行:
flowchart TD
A[Christmas] -->|Get money| B(Go shopping)
B --> C{Let me think}
C -->|One| D[Laptop]
C -->|Two| E[iPhone]
C -->|Three| F[fa:fa-car Car]
下面是中文文档:https://mermaid.nodejs.cn/
然后是官方提供的在线渲染工具:https://mermaid-live.nodejs.cn/
html 中使用 Mermaid
在 html 中首选需要将 markdown 的流程块渲染成下面这种格式:
<pre class='mermaid'>
flowchart TD
A[Christmas] -->|Get money| B(Go shopping)
B --> C{Let me think}
C -->|One| D[Laptop]
C -->|Two| E[iPhone]
C -->|Three| F[fa:fa-car Car]
</pre>
这里经过验证,使用 pre
标签或者 div
标签都可以,只要设置成 class="mermaid"
就行。
然后就是引入 Mermaid 的 js 代码,使用如下:
<body>
<script type="module">
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
mermaid.initialize({ startOnLoad: true });
</script>
</body>
这样,页面只要有流程块,就自动会渲染成流程图。比如像这样:
flowchart TD
A[Christmas] -->|Get money| B(Go shopping)
B --> C{Let me think}
C -->|One| D[Laptop]
C -->|Two| E[iPhone]
C -->|Three| F[fa:fa-car Car]
Django 中使用
下面直接分享我这次对博客的改造,让我的博客支持了 markdown 中渲染流程图。
markdown 渲染优化
首先这里的目的是为了让 markdown 把流程图代码块渲染成 html 块,但是我发现 markdown 会把流程块识别成代码块。
于是我的方案是在进行 markdown 渲染之前先将要渲染的内容中的流程图块处理成 html 格式,这样就不会被识别成代码块了。下面是我的一个处理函数:
def preprocess_mermaid_blocks(md_content):
"""
处理 Markdown 内容,将 mermaid 代码块转换为 HTML div,并判断是否包含 mermaid 代码块。
:param md_content: str,原始 Markdown 文本
:return: (str, bool) -> 处理后的 Markdown 内容 & 是否包含 mermaid 代码块
"""
# 允许 mermaid 代码块前有 0 个或多个空格 + 可选的换行
mermaid_pattern = re.compile(r'^\s*```mermaid\s*\n(.*?)\n```', re.DOTALL | re.MULTILINE)
has_mermaid = False # 是否包含 mermaid 代码块
def replace_mermaid_block(match):
nonlocal has_mermaid
content = match.group(1).strip()
if content: # 仅转换非空 Mermaid 代码块
has_mermaid = True
return f"<pre class='mermaid'>\n{content}\n</pre>"
return match.group(0) # 保留原始 Markdown
processed_content = mermaid_pattern.sub(replace_mermaid_block, md_content)
return processed_content, has_mermaid
这个函数会正则匹配内容中的流程块,然后如果有就转换成 html 内容,并返回一个布尔值(这个布尔值后面有用处)。
然后在文章渲染的逻辑中修改了一下代码大概是这样的:
if cache_md and settings.DEBUG is False:
if len(cache_md) == 3:
obj.body, obj.toc, obj.has_mermaid = cache_md
else:
obj.body, obj.toc = cache_md
obj.has_mermaid = False
else:
md = make_markdown()
processed_content, has_mermaid = preprocess_mermaid_blocks(obj.body)
obj.body = md.convert(processed_content)
obj.has_mermaid = has_mermaid
obj.toc = md.toc
cache.set(md_key, (obj.body, obj.toc, obj.has_mermaid), 3600 * 24 * 7)
这里的关键在于我给文章传入了一个字段 has_mermaid
,用来判断文章是否涉及流程图。
模板中引入 mermaid.js
模板的改造很简单,目的就是引入 mermaid.js,代码如下:
{% if article.has_mermaid %}
<script src="{% static 'blog/js/mermaid/11.4.1/mermaid.min.js' %}"></script>
<script>mermaid.initialize({ startOnLoad: true });</script>
{% endif %}
看到我设置 has_mermaid 的意图了吧,我是会判断当前文章是否涉及流程图,只有涉及流程图才会去加载 mermaid.min.js
并渲染页面,这样可以减少文件的加载和没必要的加载。