跳到正文

我所理解的 Markdown 最佳实践(6):善用 Markdown 扩展语法

我所理解的 Markdown 最佳实践

  1. 中文加粗为什么会露出星号
  2. 给 Markdown 文档接入 textlint
  3. Markdown 里的代码也要检查
  4. 给 AI 写的 Markdown 做批注
  5. Markdown 怎样交付到网页和富文本编辑器
  6. 善用 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
$$
a2+b2=c2a^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 的 ![说明](chart.png){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 扩展语法》。

脚注

  1. GFM 的公开规范与 GitHub 网页的写作功能清单分别维护。判断一项功能能否跨工具使用时,可以分别检查语法规范和实际产品入口。 ↩