我所理解的 Markdown 最佳实践(6):善用 Markdown 扩展语法
- 中文加粗为什么会露出星号
- 给 Markdown 文档接入 textlint
- Markdown 里的代码也要检查
- 给 AI 写的 Markdown 做批注
- Markdown 怎样交付到网页和富文本编辑器
- 善用 Markdown 扩展语法(当前)
一份安装说明可以同时放进 GitHub README、项目文档站和本地审阅工具。围栏里的命令能在三处阅读,命令上方的 > [!WARNING] 却可能显示成带图标的警告框,也可能只是一段以 [!WARNING] 开头的引用。把操作条件写在提示框的文字里,即使它退回普通引用,读者仍然知道什么时候能执行下一条命令。
从表格到提示框
CommonMark 规定段落、列表、引用、链接和代码围栏等基础语法。GFM 在此基础上加入竖线表格、任务列表、删除线和裸网址自动链接。GitHub 网站又进一步支持脚注和 Alerts。一个库的 gfm: true 能开哪些功能,要看它自己的说明。可以先用表格和任务列表核对基础兼容,再分别试脚注和提示框。见 GFM 规范 和 GitHub 写作语法。
| 检查项 | 结果 |
| --- | --- |
| 站点生成 | 通过 |
| 浏览器预览 | 待核对 |
- [x] 生成静态页面
- [ ] 打开页面验证
~~旧端口 4000~~,预览改用 4350。
表格适合比较有相同字段的项目。如果每格都要放多段说明或代码,用小标题和段落会更容易维护。静态页面上的任务复选框通常只展示状态,需要读者执行的步骤仍可用有序列表,把前后依赖写清楚。
提示框里的内容要在操作前读到
> [!TIP]
> 先用一篇文章试写,再决定是否在整个文档库推广。
Tip
先用一篇文章试写,再决定是否在整个文档库推广。
| 标记 | 适合放什么 | 安装文档里的例子 |
|---|---|---|
NOTE |
补充背景 | 示例配置使用独立的预览端口。 |
TIP |
能马上用上的技巧 | 每次只改一项配置,更容易定位生成结果的变化。 |
IMPORTANT |
必须满足的条件 | 安装依赖时使用项目锁定的包管理器版本。 |
WARNING |
动手前需要检查的事项 | 执行发布命令前核对目标环境。 |
CAUTION |
操作可能带来的后果 | 清理生成目录期间,其他进程会暂时读不到产物。 |
跨平台写法可以从两行开始。标记独占首行,正文每行继续写 >,分段时留一个只有 > 的空引用行。提示框应靠近它影响的步骤。比如要说明清理目录的后果,就把它放在清理命令上方。文档里如果要示范 [!TIP] 本身,可以用代码围栏保留源码。
给长文加脚注、定义和折叠
脚注适合放来源、术语细节或旁证。给脚注取一个与内容有关的名字,调整段落顺序后仍能看出它指向哪里。同一个名字可以引用多次,页面上的序号由渲染器安排。1
先检查生成页的标题链接。[^anchors]
改名后,再跑一次同样的检查。[^anchors]
[^anchors]: 标题改名后,自动生成的锚点可能随之变化。
如果页面里的第二处引用能跳到脚注,从脚注也应当能回到对应引用。多段定义的缩进和行内脚注的写法因工具而异,跨平台时可以先用一段定义的命名脚注。会影响读者判断的前提应留在正文里,脚注更适合放可选的来源和细节。
术语表可以直接用「术语 + 定义」表达。下面两行在本站会生成 <dl>、<dt> 和 <dd>。Pandoc 还允许一项定义含多个块,迁移时要核对空行和缩进规则。见 Pandoc 定义列表。
渲染器
: 将 Markdown 结构转换为 HTML 或其他输出的程序。
- 渲染器
- 将 Markdown 结构转换为 HTML 或其他输出的程序。
折叠的三种写法
较长的命令输出、复现记录或版本清单可以折叠。标题写清打开后能看到什么,例如「查看复现条件」。GitHub 支持原生 HTML 折叠。<summary> 后面留空行,让里面的 Markdown 形成独立的块。加 open 属性即可默认展开。见 GitHub 折叠区块。
<details>
<summary>查看复现条件</summary>
- 使用独立的预览目录
- 验证生成后的页面
</details>
VitePress 可以用 ::: details 达到相同的阅读效果。
::: details 查看复现条件
样例使用本地静态生成的 HTML。
:::
查看复现条件
样例使用本地静态生成的 HTML。
提示容器也能带标题。VitePress 用 ::: tip 自定义标题,Docusaurus 用 :::tip[自定义标题]。Obsidian 直接在引用首行加标题,- 表示默认折叠,+ 表示默认展开。前两种靠冒号行划定范围,后一种靠每行的引用标记。见 VitePress 容器、Docusaurus Admonitions 和 Obsidian Callouts。
> [!tip]- 查看复现条件
> 样例使用本地静态生成的 HTML。
公式、图表、目录和双链
公式和图表要把源文本交给排版引擎。目录和双链也需要额外信息,不过它们查的是页面标题和文档索引。
公式和图表各有渲染引擎
行内公式常写成 $x^2$,独立公式用两行 $$ 包围。Markdown 解析器识别边界,KaTeX 或 MathJax 负责排版。文章里如果也有美元金额,可以把金额和公式写在同一段测试,核对只有公式进入了排版引擎。见 GitHub 数学表达式。
行内可以写 $x^2$。
$$
a^2 + b^2 = c^2
$$
Mermaid 借用围栏的语言标记。Markdown 可以照常输出代码块,图表引擎再从里面读出源文本。下面这段在本站会显示为图,只做语法高亮的预览器会显示源码。图里的关系如果影响后面的操作,也在正文留一句,例如「Markdown 先由解析器转换,再交给网页展示」。见 GitHub 图表文档。
flowchart LR
Source[Markdown] --> Parser[解析器]
Parser --> Page[网页]
目录、双链和短码需要宿主解释
VitePress 会把独占一行的 [[toc]] 换成正文目录,GitLab 有 [[_TOC_]] 和 [TOC]。GitHub README 的标题大纲由界面提供。自动目录要使用输出页里真正的标题 ID,遇到同名标题时可以点击第二项核对。见 VitePress 正文目录 和 GitLab 正文目录。
在 Obsidian 一类知识库里,[[文章名]] 让工具到库内查找文件,[[目标|显示文字]] 可以给链接换一个适合上下文的名字。博客也能按标题或文章标识查找,例如 让 Hexo 支持 Markdown 扩展语法。把链接发给知识库之外的读者时,普通 Markdown 链接可以直接写出目的地址。见 Obsidian 内部链接。
:tada: 要查到对应的字典条目,才能显示成 🎉。正文经常要粘贴到纯文本窗口时,可以直接输入 Unicode Emoji,让接收端按自己的字体显示。
同一份文件放到不同平台会怎样
| 写在哪里 | 可以从哪些扩展试起 |
|---|---|
| GitHub | GFM 表格、任务列表、脚注、五类 Alerts、Emoji 短码,另有数学和图表入口 |
| GitLab | 表格、任务项、脚注、Alerts、目录和 Mermaid,问题、合并请求和评论可用的功能有区别 |
| VitePress | GitHub 风格 Alerts、::: 容器、脚注、Emoji 和 [[toc]],公式可配置 |
| Docusaurus | :::note 一类的提示框,公式和图表有各自的配置 |
| Obsidian | Callouts、双链、脚注、数学和 Mermaid,笔记预览与发布环境各有设置 |
| Pandoc | 定义列表、脚注、公式、文献引用和导出目录,具体写法取决于输入格式和扩展选项 |
同一平台也要看入口。GitHub 的脚注在 Wiki 中不可用,GitLab 的正文目录可以放在问题和合并请求的描述里,评论则没有这个入口。写仓库 README 和写评论时,最好分别验证。见 GitHub 脚注 和 GitLab 正文目录。
把 README 搬到文档站时,可以用一个小样本试三件事。提示框里面的列表还能不能继续换行,第二处脚注的回链会不会跳回第一处,同名标题的目录能不能各自定位。如果还要交付 PDF,也打开导出的文件看看折叠内容和图表。
让读者找到要看的文字和代码
一篇安装教程常被其他文章引用,可以给安装步骤留一个稳定链接。如果教程贴了一整份配置,也可以单独标出需要读者修改的几行。下面这三种写法都可以在本站和 Markdown 修订工具里使用。
安装步骤与固定锚点
把 {#install-example} 放在标题末尾,标题前面的文字以后可以改成「安装与升级」,已有链接仍然指向同一个 ID。本站的目录也会读取这个 ID。修订工具打开 [[文章名#install-example|安装步骤]] 时,会用同一个 ID 查找目标标题。给每一节留一个独立 ID,写成以英文字母开头的短词,后面可用字母、数字、下划线和连字符。VitePress 和 Pandoc 也支持这种标题写法。见 VitePress 自定义锚点 和 Pandoc 标题属性。
### 安装步骤与固定锚点 {#install-example}
[跳到安装步骤](#install-example)
正文里的几个字需要显眼一点
例如「发布前先确认 预览环境 使用独立的数据库」。源码把这几个字写成 ==预览环境==,渲染后生成 HTML 的 <mark>。在修订工具里,{==预览环境==} 则是一条 CriticMarkup 高亮批注,可以在审阅时删除或回写。两种标记放在同一段也可以各自生效。Obsidian 支持 ==…==,自建渲染器可以使用 markdown-it 的 @mdit/plugin-mark。见 Obsidian 高亮 和 Markdown It Plugins 的 mark 扩展。
标出读者要改的代码行
围栏首行写成 js{2},下面的代码仍按 JavaScript 着色,第二行会多一层背景。想标几处时可以写 js{2,4-6},标出第二行和第四至第六行。Hexo 风格的等价写法是 js mark:2,4-6。两种都只改变展示,复制按钮返回围栏里的代码。VitePress 支持花括号写法。见 VitePress 行高亮 和 Hexo 代码块选项。
const host = '127.0.0.1';
const port = 4350;
console.log(host, port);
两种围栏首行写法可以互换。
```js{2}
const host = '127.0.0.1';
const port = 4350;
console.log(host, port);
```
```js mark:2
const host = '127.0.0.1';
const port = 4350;
console.log(host, port);
```
还有哪些扩展值得认识
写化学笔记会碰到下标,写安装教程则常常需要文件名或代码选项卡。迁移这类内容时,可以先确认读者使用的工具能读懂原来的写法。
上下标、文件名和代码选项卡
写化学式和单位时,Pandoc 可以用 H~2~O 和 m^2^,GitHub 文档则提供 HTML <sub> 和 <sup>。单个波浪线已经被一些渲染器用作删除线,例如本站的 Marked 会给 H~2~O 中的数字 2 加删除线,接入下标前要先约定这个符号的优先级。见 Pandoc 扩展语法 和 GitHub 文本样式。
Docusaurus 可以在围栏首行写 js title="config.js",在代码块上方标出文件名。见 Docusaurus 代码块。
给 npm、pnpm 和 Yarn 各放一段等价命令时,VitePress 可以用 ::: code-group 把围栏放在一起,再用 bash [pnpm] 一类的标签命名选项卡。切换选项只改变可见的代码段。教程需要读者一起修改和运行多个文件时,可以提供单独的源码工作区。见 VitePress 代码分组。
图片、包含和自动编号需要额外数据
| 扩展 | 典型写法 | 还需要什么 |
|---|---|---|
| 图片属性与图注 | Pandoc 的 {width=60%},Obsidian 的 ![[chart.png|320]] |
宿主要解析图片路径,渲染器处理尺寸和图注,替代文字保留图像描述。 Pandoc 图片属性 · Obsidian 嵌入 |
| 包含文件与源码片段 | VitePress 的 <!--@include: ./intro.md--> 或 <<< ./example.ts |
构建器决定相对路径基准、可读取的目录和文件变更后的重建方式。 VitePress 文件包含 |
| 文献引用 | Pandoc / Quarto 的 [@paper] |
引用键要能在 BibTeX 等书目数据里找到,引用处理器负责作者名、排序和参考文献表。 Quarto 文献引用 |
| 图表、公式交叉引用 | Quarto 用 {#fig-example} 标图,正文写 @fig-example |
文档编译器先收集标签和编号,再生成链接。图调了顺序,正文里的「图 2」也随之重算。 Quarto 交叉引用 |
这些写法能在各自的工具里解决实际问题。要移到自己的站点时,可以先问这段 Markdown 自己能不能提供生成结果所需的信息。正文高亮只要认识两个边界符。文件包含要真的读文件,文献引用要查书目,自动编号要等同一份文档的标签都收集完。
给自己的渲染器选库
只需输出 HTML 时,可以从 Marked 或 markdown-it 开始。让库先解析围栏、列表、引用和转义,再按需要注册扩展。Marked 有 Alerts、脚注和 KaTeX 扩展,markdown-it 的生态里有容器、脚注和锚点插件。CSS、站内文件查找和页面交互仍由宿主负责。见 Marked 扩展 和 Markdown It Plugins。
先用独立的 Marked 实例试一个提示框。安装 marked 和 marked-alert 后,下面的脚本会输出带 markdown-alert-tip 的 HTML。样式要由实际页面加载。
import { Marked } from 'marked';
import markedAlert from 'marked-alert';
const renderer = new Marked({ gfm: true }).use(markedAlert());
const source = ['> [!TIP]', '> 先生成,再打开页面。'].join('\n');
console.log(renderer.parse(source));
如果还要做审阅、批注或大纲,可以用 unified 的 remark / rehype 链路。remark 先生成带源码位置的 Markdown 树,remark-gfm 补表格、任务列表和脚注,remark-math 识别公式。把 Markdown 树转换成 HTML 树以后,再由 rehype-katex 处理公式。这样在生成 HTML 之前,就可以判断一段文字属于正文还是代码示例。见 unified、remark-gfm 和 remark-math 与 rehype-katex。
批注工具还要回答「读者选中的字在原文里是哪一段」。屏幕上的 🎉 是一个可见字符,源文件却存着 :tada:。按屏幕文字的长度回写会截断短码。修订工具给这个可见字符保留整段短码的起止位置,保存时按该范围操作。双链也一样,解析器能识别目标和显示文字,最终定位由文章索引完成。
修订工具用 @ai-markdown/remark-mark-highlight 为 micromark 和 mdast 注册高亮语法,保留 <mark> 内每段文字的原文位置。固定锚点从标题末尾的纯文本节点提取,目录和全文索引共用同一套 ID 分配规则。代码重点行则从代码节点的语言与元数据读取,换成 Prism Line Highlight 接受的 data-line。这样阅读页和链接预览可以用同一个浏览器插件定位行背景。见 remark-mark-highlight 和 Prism Line Highlight。
验证扩展时,可以留一组内容相同的正反例。提示框里的 [!TIP] 应被识别,同样的文字进围栏后应保留为源码。再到实际交付的网页、链接预览或 PDF 里各走一次。Hexo 的接入、资源发布和插件打包见《让 Hexo 支持 Markdown 扩展语法》。
脚注
-
GFM 的公开规范与 GitHub 网页的写作功能清单分别维护。判断一项功能能否跨工具使用时,可以分别检查语法规范和实际产品入口。 ↩