跳转到主要内容

这是本节的多页打印视图。 .

返回本页常规视图.

组件总览

写文档时可用的全部组件,一个组件一页,例子由浅入深,参数表在页尾。

这一栏回答一个问题:某个组件在 Markdown 里怎么写。每页的顺序相同:最简例子、逐步深入的例子、输出形态、参数表、限制。查语法见下面的速查表。

两种形态

组件的第一形态是 Markdown 语法本身:块引用、列表、表格、图片、围栏,加上紧跟其后的一行 {…} 属性。原生形态在 GitHub 与任意 Markdown 编辑器中仍然可读,Markdown 输出保留的也是源码。

原生形态表达不了的场景使用 shortcode:正文标签页、带块级描述的参数表、带图标与徽章的卡片、终端录像。规则有五条:

  • 所有 shortcode 都写 {{</* 名字 */>}},只有 {{%/* steps */%}}% 分隔符,因为它的正文是页面级 Markdown。
  • 嵌套名字(tabcardfield)只在各自的父 shortcode 里有效。
  • 参数写错不会静悄悄降级,构建失败,报错带文件名与行号。
  • 公开字符串参数(图注、标签、标题)一律是纯文本,不解析 Markdown。只有正文是 Markdown:tabcardfield 的正文,include 引入的文件,以及 Book 的 figtbleg 正文。
  • 页面没用到的组件不下发运行时。脚本按这一页实际用到的组件拼成一个包,打印、Markdown 与 RSS 输出不加载任何脚本。

站点前置配置

组件依赖三项 Goldmark 设置。克隆本站起步时它们已经配好,从零建站照抄以下片段:

hugo.yml
markup:
  goldmark:
    renderer:
      unsafe: true # 内容里的 HTML 不被剥掉
    parser:
      attribute:
        block: true # 启用 {…} 属性行
      wrapStandAloneImageWithinParagraph: false # 独立图片不再包进 <p>
  • renderer.unsafe: true:Goldmark 默认丢弃内容里的原始 HTML,关闭时组件正文里嵌套的 HTML 会消失。
  • parser.attribute.block: true:属性行的总开关。关闭时 {.steps}{caption="…"} 只是正文里的一行字符串。
  • parser.wrapStandAloneImageWithinParagraph: false:独立成段的图片不再包进 <p>,图片才能成为带图注的 figure,属性行才跟得上去。

个别组件另有前置条件:公式需要开启 Goldmark 的 passthrough,PlantUML 与 Draw.io 需要自建渲染服务,各页分别说明。完整的配置键见配置总览

速查表

「形态」列的取值:原生 = Markdown 语法加属性行;围栏 = 带语言标记的代码围栏;shortcode = {{</* … */>}}。「运行时」列说明这个组件是否往页面上下发 JavaScript。

组件一句话最短写法形态运行时
提示块把前提、警告与折叠说明从正文中分离> [!NOTE]原生
图片图注、尺寸、缩放、编号与构建期图片处理![说明](oink.webp)原生需站点开关
代码块高亮、标题、复制、折叠、行链接```sh围栏按页加载
标签页同一件事的多个平台或语言版本属性行 {tab="Linux"}原生 + shortcode按页加载
表格普通表格,加满宽、矩阵、标题与编号{.full-width}原生
参数表参数清单,带类型 / 必填 / 默认值芯片{.fields meta="type default"}原生 + shortcode
步骤有先后的流程{.steps}原生 + shortcode
卡片一组并列的去处{.cards}原生 + shortcode
文件树目录结构与对齐的注释列```filetree围栏按页加载
公式KaTeX 行内与块级公式$$ … $$原生按页加载
Mermaid流程图、时序图、甘特图```mermaid围栏按页加载
PlantUMLUML 图;需要自建渲染服务```plantuml围栏需站点开关
思维导图Markdown 列表变成思维导图```markmap围栏需站点开关
Draw.io可回编辑的图;需要自建服务![说明](arch.drawio.svg)原生需站点开关
ECharts声明式数据图表```echarts围栏按页加载
InfographicAntV 信息图```infographic围栏按页加载
画廊一组图片共用一个缩放对话框```gallery围栏需站点开关
徽章行内状态标记{{</* badge text="Beta" */>}}shortcode
按键键位与组合键{{</* kbd "Ctrl" "K" */>}}shortcode
引用引入文件、插入站点参数、构建期注释{{</* include file="parts/x.md" */>}}shortcode
Asciinema终端录像{{</* asciinema file="images/x.cast" */>}}shortcode按页加载

「运行时」列的四条细则:

  • 代码块只在块上有复制或折叠按钮时加载 code-block.js;文件树只在树带注释列时加载 filetree.js,它负责拖动那条分栏线。
  • 图片与画廊共用一个缩放对话框运行时,需要站点开启 ui.image_zoom,且页面上确有候选图。
  • 公式在构建期由 KaTeX 渲染成 HTML 与 MathML,页面上只多一份 KaTeX 样式表与字体,没有脚本。
  • Draw.io 只在渲染内容含 PNG 或 SVG 候选图的页面加载,并且每个不同的图片 URL 只检查一次。

每个组件在 HTML、打印、Markdown、RSS 四种输出下都有确定形态,见各页的「输出形态」一节。

1 - 提示块

> [!NOTE] 这样的块引用写出带颜色、图标与标题的提示、警告与折叠块,不需要短代码。

提示块(Callout)是 GitHub / Obsidian 风格的块引用:> [!TYPE] 起头,正文跟在后面。用于把提示、警告、前提条件从正文中分离出来;正文一句话能说清的内容不必使用提示块。

最简例子

源码
> [!NOTE]
> Hugo Module 需要本机安装 Go;只用离线归档时不需要。
说明

Hugo Module 需要本机安装 Go;只用离线归档时不需要。

不写标题时使用本地化的类型名(中文站显示「注意」,英文站显示 “Note”)。源码在 GitHub 上按 GitHub 的提示块渲染,在普通 Markdown 阅读器中显示为块引用,内容都不会丢失。

十种类型

前五种与 GitHub 一致,后五种是 OINK 追加的语义类型。每种类型有默认图标与强调色。

源码
> [!TIP]
> 用 `hugo server -D` 可以预览草稿。

> [!IMPORTANT]
> 主题下限是 Hugo Extended 0.160.1,低于它构建直接失败。

> [!WARNING]
> `hugo --cleanDestinationDir` 会清空 `public/`。

> [!CAUTION]
> 删除 `resources/_gen` 后第一次构建会慢很多。

> [!SUCCESS]
> 构建通过、零告警——可以推上线了。

> [!DANGER]
> 不要把 `go.work` 提交进仓库。

> [!QUESTION]
> 站点要不要开评论?看[启用评论](/zh/docs/admin/comments/)。

> [!EXAMPLE]
> `pgsty.com` 就是一个只用了提示块与表格的纯文档站。

> [!QUOTE]
> Documentation is a love letter that you write to your future self.
提示

hugo server -D 可以预览草稿。

重要

主题下限是 Hugo Extended 0.160.1,低于它构建直接失败。

警告

hugo --cleanDestinationDir 会清空 public/

注意

删除 resources/_gen 后第一次构建会慢很多。

成功

构建通过、零告警——可以推上线了。

危险

不要把 go.work 提交进仓库。

问题

站点要不要开评论?看启用评论

示例

pgsty.com 就是一个只用了提示块与表格的纯文档站。

引用

Documentation is a love letter that you write to your future self.

类型名不区分大小写。

自定义标题

标记同一行的后续文字是标题,支持行内 Markdown(代码、粗体、链接)。

源码
> [!WARNING] 会改写 `public/`
> 生产构建前先确认 `baseURL` 指向正式域名,否则所有绝对链接都会指错。
会改写 public/

生产构建前先确认 baseURL 指向正式域名,否则所有绝对链接都会指错。

正文内容

正文是页面级 Markdown:列表、代码围栏、表格、图片、嵌套的提示块。每一行都以 > 开头,围栏也不例外。

源码
> [!TIP] 三条命令启动预览
>
> 1. 克隆:`git clone https://github.com/pgsty/oink.pgsty.com my-docs`
> 2. 进入目录并预览:
>    ```bash
>    cd my-docs && hugo server
>    ```
> 3. 打开 <http://localhost:1313/>
>
> | 端口 | 用途 |
> | --- | --- |
> | 1313 | Hugo 开发服务器 |
三条命令启动预览
  1. 克隆:git clone https://github.com/pgsty/oink.pgsty.com my-docs
  2. 进入目录并预览:
    cd my-docs && hugo server
  3. 打开 http://localhost:1313/
端口用途
1313Hugo 开发服务器

折叠

类型后加 - 默认收起,加 + 默认展开;两者都渲染为原生 <details>,不加载 JavaScript。适用于完整输出、备选方案、背景说明这类不必默认展示的内容。

源码
> [!NOTE]- 为什么需要 Go?
> Hugo 通过 Go 的模块系统下载主题(`hugo mod get`)。用 submodule 或离线归档时可以不装 Go。

> [!TIP]+ 默认展开,但读者可以收起
> 收起状态不会被记住,刷新后回到默认。
为什么需要 Go?

Hugo 通过 Go 的模块系统下载主题(hugo mod get)。用 submodule 或离线归档时可以不装 Go。

默认展开,但读者可以收起

收起状态不会被记住,刷新后回到默认。

中性折叠块 DETAILS

[!DETAILS] 是没有语义颜色的折叠块:不加符号默认收起,[!DETAILS]+ 默认展开。用于冗长输出、完整配置文件等需要折叠的内容。

源码
> [!DETAILS] 完整的 `hugo version` 输出
> ```text
> hugo v0.164.0+extended+withdeploy darwin/arm64 BuildDate=2026-07-06T16:39:30Z VendorInfo=Homebrew
> ```
完整的 hugo version 输出
hugo v0.164.0+extended+withdeploy darwin/arm64 BuildDate=2026-07-06T16:39:30Z VendorInfo=Homebrew

自定义图标

块引用结束后的下一行写属性 {icon="fa-solid fa-xxx"}(一对 Font Awesome class),替换该类型的默认图标。属性行紧接块引用,中间不能有空行。

源码
> [!TIP] PostgreSQL 18 已支持
> 从 Pigsty v4 起默认安装 PostgreSQL 18。
{icon="fa-solid fa-database"}
PostgreSQL 18 已支持

从 Pigsty v4 起默认安装 PostgreSQL 18。

嵌套

提示块可以嵌套(每层多一个 >),也可以放在列表项或步骤中。建议最多嵌套一层。

源码
> [!WARNING] 升级前先备份
> 升级主题版本可能改变渲染结果。
>
> > [!TIP]- 怎么备份
> > `git tag pre-upgrade` 就够了——回滚只是 `git checkout pre-upgrade`
升级前先备份

升级主题版本可能改变渲染结果。

怎么备份

git tag pre-upgrade 就够了——回滚只是 git checkout pre-upgrade

未知类型与易错写法

未知的类型名不会导致构建失败,也不会丢失内容:该块渲染为普通块引用,[!TYPE] 标记原样可见。

源码
> [!NOTICE] 这不是合法类型
> 标记会保留在页面上提醒你。

[!NOTICE] 这不是合法类型

标记会保留在页面上提醒你。

其它常见问题:

  • 正文与标题合并:经过 Prettier 等格式化工具的文件,在标题行下保留一个空的 > 行,否则工具会把标题并入正文。
  • 属性行被格式化工具移动:把 {icon=…} 这类标记行放在 <!-- prettier-ignore-start --> / <!-- prettier-ignore-end --> 之间。
  • styleonclick 等属性导致构建失败:属性行只接受 iconclass(见下表)。

输出形态

输出呈现
HTML静态类型是 <div class="td-callout" role="note">;折叠类型是原生 <details> + <summary>
打印全部静态展开,折叠块带 data-td-callout-collapsible 标记
Markdown保留源码块引用(含 [!TYPE] 标记与标题)
RSS与打印相同,静态展开

提示块不加载脚本。

参数参考

标记行 > [!TYPE]± 标题

TYPE , 枚举 , default
NOTE TIP IMPORTANT WARNING CAUTION SUCCESS DANGER QUESTION EXAMPLE QUOTE DETAILS;大小写不敏感;未知值渲染为普通块引用
± , - / + / 无 , default
- 折叠默认收起,+ 折叠默认展开;DETAILS 不加符号即收起
标题 , 行内 Markdown , default类型的本地化名称
与标记同一行

属性行 {…}(块引用之后紧接的一行):

icon , Font Awesome class 对 , default类型默认图标
例如 fa-solid fa-databaseDETAILS 默认无图标
class , 空格分隔的 class , default
原样透传给站点 CSS

styleon* 与其它任何键都会让构建失败。

限制与常见问题

  • 不能自定义颜色:颜色由类型决定,需要新语义时选最接近的类型并自定义标题。
  • 折叠状态不持久化。
  • 提示块可以放在 {.steps} 列表项与 {{%/* steps */%}} 步骤中(见步骤),块引用的每一行都以 > 开头,缩进与列表项对齐。

2 - 图片

用普通 Markdown 图片语法写图,加一行属性就得到图注、尺寸、缩放、链接、编号与 Hugo 图片处理。

图片只有一种写法:Markdown 的 ![替代文字](来源 "标题")。独立成段的图片可以在下一行跟一行 {…} 属性,成为带图注的 figure、缩放候选、编号图或经 Hugo 处理的派生图。主题没有图片 shortcode。

最简例子

源码
![OINK 文档外壳:侧栏、正文与目录三栏](oink-shell.webp)
OINK 文档外壳:侧栏、正文与目录三栏

这张图与本页放在同一目录(页面包)中,主题读取它的固有尺寸并写入 width/height,页面加载时不发生跳版;所有图片懒加载。替代文字供屏幕阅读器与搜索引擎使用,应当始终填写;空 alt 表示装饰性图片,缩放会跳过它。

图片来源

来源按以下顺序解析,写法相同:

放法源码里怎么写适合
与页面同目录(页面包 index.md + 图片)![…](oink-shell.webp)只有这一页用的截图;随页面一起移动、翻译共用
全局资源 assets/images/…![…](images/logo/oink.webp)多页共用、还要做处理(缩放 / 裁切)的图
静态目录 static/images/…![…](/images/hero-light.webp)不需要处理的大图、下载物;主题拿不到尺寸时可以用 width/height
远程 URL![…](https://example.com/a.png)少用:构建期不会下载,也不能处理

相对路径先按页面资源、再按全局资源查找,都找不到时按静态路径原样输出;主题不检查静态路径与远程 URL 是否存在。只有要求处理(command=)的图找不到资源时才构建失败。

行内与块级

位于文字中间的是行内图片,渲染为一个 <img>,不能带属性;独立成段的是块级图片,可以带属性行。

源码
这一枚小图 ![文档外壳缩略图](oink-mini.webp) 夹在句子里,是行内图片。

![文档外壳缩略图](oink-mini.webp)
{width="100" height="64"}

这一枚小图 文档外壳缩略图 夹在句子里,是行内图片。

文档外壳缩略图

行内图片按自身尺寸显示(这里是 50×32)。没有固有尺寸的 SVG 行内插入时会被拉伸到容器宽度,SVG 应作为块级图片使用并给出 width/height

说明

块级图片依赖站点设置 markup.goldmark.parser.wrapStandAloneImageWithinParagraph: false(本站已配置;见配置总览)。缺少它时 Goldmark 会把独立图片包进 <p>,属性行也会被当作正文。

图注

属性行加 caption="…",图片渲染为 <figure> + <figcaption>。图注是纯文本,不解析 Markdown。

源码
![发布卡片:版本号、发布日期与资产按钮](release-note.webp)
{caption="发布卡片由 data/download 与页面的 release 记录生成"}
发布卡片:版本号、发布日期与资产按钮
发布卡片由 data/download 与页面的 release 记录生成

Markdown 里的 "标题" 保持原义(悬停提示),不会成为图注。

尺寸

width/height 是正整数,覆盖资源自身的尺寸:为静态或远程图片提供占位框以避免跳版,或把大图缩小显示(浏览器缩放,不改文件)。

源码
![OINK 首页插画(浅色)](/images/hero-light.webp)
{width="450" height="300" caption="static/images/ 里的 900×600 插画按一半显示"}
OINK 首页插画(浅色)
static/images/ 里的 900×600 插画按一半显示

处理型图片

页面资源与全局资源可以在构建期由 Hugo 处理:commandoptions 必须同时给出,命令是 Fit Resize Fill Crop 之一,选项是 Hugo 的图片处理字符串。渲染出的 src 是派生图;启用缩放时对话框打开原图。

源码
![文档外壳缩略图](oink-shell.webp)
{command="Fit" options="300x150" caption="Fit 300x150:按比例装进 300×150 的框"}

![文档外壳左半边](oink-shell.webp)
{command="Fill" options="300x150 Left" caption="Fill 300x150 Left:填满框,从左侧裁"}
文档外壳缩略图
Fit 300x150:按比例装进 300×150 的框
文档外壳左半边
Fill 300x150 Left:填满框,从左侧裁

静态路径、远程 URL 与 SVG 不能处理,对它们写 command 会构建失败。选项语法(锚点、质量、格式转换,如 300x150 webp q80)见 Hugo 图片处理

两种写法,用途不同:

  • 没有图注、图片本身是链接:用 Markdown 的链接包图 [![alt](src)](href)
  • 有图注的 figure 整体可点:属性行加 link="…"(必须同时有 captionnum)。
源码
[![点击进入亮点特性页](oink-shell.webp)](/zh/docs/about/features/)

![发布卡片](release-note.webp)
{caption="点击图片查看发布与下载页的说明" link="/zh/docs/write/releases/"}

点击进入亮点特性页

发布卡片
点击图片查看发布与下载页的说明

带链接的图不参与缩放。没有图注只写 link= 会构建失败,报错中提示改用 [![…](…)](…)

编号图

编号图用于书籍与长篇手册:属性行加 num,可选 #id。编号是作者书写的字符串(2-13.4),主题不自动计数;图注前加本地化的「图 2-1」前缀,#id 缺省为 fig-<num>。正文用普通链接 [图 2-1](#fig-2-1)xref shortcode 引用;全书图目录见书籍出版

源码
![发布卡片](release-note.webp)
{#fig-release num="2-1" caption="发布卡片:版本、日期与资产"}

见[图 2-1](#fig-release)。
发布卡片
图 2-1 发布卡片:版本、日期与资产

图 2-1

编号图可以同时是处理型图片(num + command),也可以带 link

缩放

图片缩放默认关闭。站点开启后,块级图片、figure、画廊中带 alt 的图成为可点击的按钮,在原生 <dialog> 中查看大图(Esc 关闭,焦点回到原处)。本页在 front matter 中开启了它,上面的图都可以点击。

hugo.yml
params:
  ui:
    image_zoom: true
某一页的 front matter:只关这一页
image_zoom: false

不缩放的图:行内图、alt 为空的装饰图、带链接的图、data-no-zoom 标记的图。运行时只在页面确有候选图时加载;打印 / Markdown / RSS 中没有对话框。

源码:装饰图不缩放
![](oink-shell.webp)
{width="150" height="75"}

深浅色图片

主题没有按深浅色切换图片的参数。需要两张图时,各写一个 class,在站点 CSS 中按 [data-bs-theme="dark"] 显示其一:

源码
![侧栏(浅色)](oink-shell.webp)
{class="only-light"}

![侧栏(深色)](oink-shell.webp)
{class="only-dark"}
assets/scss/_styles_project.scss
[data-bs-theme="dark"] .only-light,
:not([data-bs-theme="dark"]) .only-dark { display: none; }

class 由主题原样透传,供站点 CSS 使用。

输出形态

输出呈现
HTML行内 <img>;块级 <img class="td-image">;有图注 / 编号时 <figure class="td-figure"> + <figcaption>;缩放候选带 data-td-image-zoom
打印同 HTML,去掉缩放控件
Markdown原样输出 ![alt](src) 与属性行
RSS图片 src 改为绝对地址;无缩放

参数参考

属性行 {…}(块级图片之后紧接的一行):

caption , 纯文本 , default
有它就渲染成 figure;不解析 Markdown
#id , 标识符 , defaultnumfig-<num>
[A-Za-z][A-Za-z0-9_.:-]*;作为锚点与 Book 目标 ID
num , 字符串 , default
[0-9A-Za-z.-]+;注册为 Book 图目标,图注加「图 N.」前缀
width / height , 正整数 , default资源固有尺寸
覆盖尺寸;静态 / 远程图靠它避免跳版
command , 枚举 , default
Fit Resize Fill Crop;必须与 options 同给;仅页面 / 全局资源
options , 字符串 , default
Hugo 图片处理选项,如 600x300300x150 Left800x webp q80
link , URL , default
把 figure 包进链接;需要 captionnum;带链接的图不缩放
class , class 列表 , default
透传给站点 CSS
data-* / aria-* , 字符串 , default
透传

styleon*alttitlesrc 与其它任何键出现在属性行都会构建失败(alt、title、src 属于 Markdown 图片本身)。

限制与常见问题

  • 图注不含 Markdown:所有公开字符串参数都是纯文本;富文本说明写在图片下方的段落中。
  • title 不是图注:![a](b "c")c 是悬停提示。
  • 处理型图片只对资源生效:static/ 中的图需要处理时移到页面包或 assets/
  • 构建期不下载远程图片。
  • 缩放不支持拖拽、平移、上一张 / 下一张;一组相关图片使用画廊

3 - 代码块

普通 Markdown 围栏加一行属性,就得到文件名标题、精确复制、行号、高亮、换行、折叠与可链接的行。

代码块是普通的 Markdown 围栏,高亮由 Hugo 内置的 Chroma 在构建期完成,浏览器里没有高亮器。用于命令、配置片段与源码:围栏信息行上的 {…} 属性决定标题栏、复制行为、行号与行锚点。图示类围栏(mermaidechartsfiletree 等)不走这条路径,它们各有渲染钩子。

最简例子

源码
```sql
SELECT datname, numbackends FROM pg_stat_database ORDER BY numbackends DESC;
```
SELECT datname, numbackends FROM pg_stat_database ORDER BY numbackends DESC;

没有属性的围栏同样有完整外壳与复制按钮。无标题栏时不渲染空白横条,复制按钮浮在右上角,鼠标悬停或焦点进入块内时出现,触屏设备上始终可见。外壳不显示语言名,lexer 名字只写入 data-language,供样式表与测试使用。

语言标记就是 Chroma 的 lexer 名。diff 围栏用 Chroma 的增删行样式呈现补丁,不需要额外组件:

源码
```diff {title="hugo.yml 的改动"}
 params:
   ui:
-    sidebar_menu_compact: true
+    sidebar_menu_compact: false
     sidebar_menu_foldable: true
```
hugo.yml 的改动
 params:
   ui:
-    sidebar_menu_compact: true
+    sidebar_menu_compact: false
     sidebar_menu_foldable: true

文件名标题

title 给块加一条可见标题栏,通常写文件名或路径。它同时成为这个块的无障碍名称。

源码
```yaml {title="hugo.yml"}
markup:
  goldmark:
    parser:
      attribute:
        block: true
    renderer:
      unsafe: true
```
hugo.yml
markup:
  goldmark:
    parser:
      attribute:
        block: true
    renderer:
      unsafe: true

filenametitle 的历史别名,两个一起写会构建失败。

行号、起始行与高亮

lineNosinline(行号与代码同一列)或 table(行号独立成列,可单独选中不被复制)。lineNoStart 改显示的起始编号。hl_lines 标记要强调的行,计数按围栏内的源码行,从 1 开始,与 lineNoStart 无关。

源码
```ini {title="postgresql.conf" lineNos="inline" lineNoStart=120 hl_lines="2 4-5"}
shared_buffers = 8GB
max_connections = 200
work_mem = 64MB
wal_level = replica
max_wal_senders = 10
```
postgresql.conf
120shared_buffers = 8GB
121max_connections = 200
122work_mem = 64MB
123wal_level = replica
124max_wal_senders = 10

lineNos="table" 把行号放进独立的一列(两种模式下复制按钮都会剔除行号):

源码
```bash {title="部署三条命令" lineNos="table"}
./configure -c rich
./install.yml
pig ext install pg_duckdb
```
部署三条命令
1
2
3
./configure -c rich
./install.yml
pig ext install pg_duckdb

tabWidth 决定制表符展开成几个空格,与 style 一样原样转交 Chroma。本站使用基于 class 的 Chroma 调色板(深浅色各一套),style 只在把 Hugo 切回内联样式模式时才生效。

长行换行

wrap=true 只改变显示:源码不变,复制出来的文本也不变。不加它时长行横向滚动。

源码
```text {title="config/artifacts.env" wrap=true}
ARTIFACT_URL=https://repo.pigsty.io/pkg/infra/v3.6.0/infra-pkg-v3.6.0.el9.x86_64.tgz
CHECKSUM=sha256:6d3dce4f7acb18f586469adcb80ab35f3e859f9837786e151cfbc2b3c0f587b2
```
config/artifacts.env
ARTIFACT_URL=https://repo.pigsty.io/pkg/infra/v3.6.0/infra-pkg-v3.6.0.el9.x86_64.tgz
CHECKSUM=sha256:6d3dce4f7acb18f586469adcb80ab35f3e859f9837786e151cfbc2b3c0f587b2

wrap=true 与表格行号不能共存:行号列与代码列是两个表格单元格,换行后会错位。写在一起构建失败,报错提示改用 lineNos="inline" 或去掉换行。

折叠长代码

collapse=N 让块初始只显示 N 行,底部给一个「显示全部 N 行」按钮。服务器输出完整代码,折叠是浏览器量出第 N 行位置后的视觉裁切:没有 JavaScript 时、读屏器中、打印时代码都是完整的。

源码
```yaml {title="hugo.yml" collapse=8}
baseURL: https://oink.pgsty.com/
title: OINK
defaultContentLanguage: en
languages:
  en:
    languageName: English
    weight: 1
  zh:
    languageName: 简体中文
    weight: 2
params:
  offline_search: true
  ui:
    sidebar_menu_foldable: true
```
hugo.yml
baseURL: https://oink.pgsty.com/
title: OINK
defaultContentLanguage: en
languages:
  en:
    languageName: English
    weight: 1
  zh:
    languageName: 简体中文
    weight: 2
params:
  offline_search: true
  ui:
    sidebar_menu_foldable: true

行数不超过 collapse 时按钮不出现。换行与折叠可以一起用:折叠测量的是第 N 个源码行节点的底边,换行的行不会被截断。

复制内容

默认复制整块源码。终端会话(consoleshell-session 两个 lexer)默认只复制命令:带提示符的行留下,提示符本身与输出行去掉。下面这个块复制出来只有两条命令,没有 $ 也没有输出。

源码
```console
$ pig ext list duckdb
name       version  category
pg_duckdb  1.0.0    OLAP
$ pig ext install pg_duckdb
INFO installing pg_duckdb
```
$ pig ext list duckdb
name       version  category
pg_duckdb  1.0.0    OLAP
$ pig ext install pg_duckdb
INFO installing pg_duckdb

要连提示符与输出一起复制就写 copy="all"。把 copy="command" 用在 bashsh 之类普通 lexer 上会构建失败,因为它们分不出提示符、命令与输出。多行命令请在续行里写出续行提示符(通常是 >),否则那一行会被当成输出而排除。

会话 lexer 的块里一行提示符都没有时,复制按钮报失败:图标转为错误状态,控制台留一条错误,剪贴板不变。它不会退化成复制全文。

copy=false 关掉这一块的复制按钮,用于不应被抄走的反例片段:

源码
```yaml {title="反例:属性行离开了它的块" copy=false}
params:
  ui:
    image_zoom: true   # 错:image_zoom 是一张表,不是布尔值
```
反例:属性行离开了它的块
params:
  ui:
    image_zoom: true   # 错:image_zoom 是一张表,不是布尔值

整站关掉复制用 params.ui.code_copy: false,它优先于每个块自己写的 copy(见配置总览)。复制按钮只有图标,成功与失败会换图标并播报本地化状态;复制内容保留缩进、空行与 Unicode,去掉行号,末尾只留一个换行。

把「看第 3 行」做成链接需要两步:给围栏一个明确的 id,再打开 anchorLineNos=true。行号随即变成锚点链接,锚点是 #<id>-<行号>

源码
```sql {id="ex-explain" title="explain.sql" lineNos="table" anchorLineNos=true}
EXPLAIN (ANALYZE, BUFFERS)
SELECT relname, n_live_tup
FROM pg_stat_user_tables
WHERE n_live_tup > 1000
ORDER BY n_live_tup DESC;
```

跳到 [第 4 行](#ex-explain-4)。
explain.sql
1
2
3
4
5
EXPLAIN (ANALYZE, BUFFERS)
SELECT relname, n_live_tup
FROM pg_stat_user_tables
WHERE n_live_tup > 1000
ORDER BY n_live_tup DESC;

跳到 第 4 行

不写 id 时主题也会生成一个页面内唯一的 ID,但它依赖围栏在页面里的顺序,前面插入一个新围栏就会变。只有作者书写的 id 才是永久链接。ID 不能含空白与控制字符,也不能与页面上其它块的 viewport、标签、面板、标题、行锚点 ID 重复,重复即构建失败。

编号例

写书或长手册时给代码片段编号:numcaption,这个围栏就成了一条 Book「示例」目标,可以被 xref 引用,也会进入全书的示例目录。编号由作者书写,主题不自动计数;id 默认是 eg-<num>

源码
```sql {num="4-1" caption="按表统计膨胀率" #eg-bloat}
SELECT schemaname, relname, n_dead_tup, n_live_tup
FROM pg_stat_user_tables
WHERE n_dead_tup > n_live_tup * 0.2;
```

参见 {{< xref eg="4-1" anchor="eg-bloat" >}}。
示例 4-1 按表统计膨胀率
SELECT schemaname, relname, n_dead_tup, n_live_tup
FROM pg_stat_user_tables
WHERE n_dead_tup > n_live_tup * 0.2;

参见 示例 4-1

numcaption 必须成对出现,只写一个会构建失败;num 与标签页属性 tab 互斥。图、表、公式的编号写法与索引见书籍出版

一组围栏做成标签页

连续几个带 tab 的围栏会在浏览器里合成一个标签页集,第一个围栏上的 group 让它可分享、可同步、可记住选择。

源码
```bash {tab="Homebrew" group="oink-install" value="brew"}
brew install hugo
```
```bash {tab="APT" value="apt"}
sudo apt install hugo
```
Homebrew
brew install hugo
APT
sudo apt install hugo

完整规则(分组语法、URL hash、跨组同步、正文标签页)在标签页

易错写法

  • 在文档里展示 shortcode:围栏不阻止 Hugo 解析,写在代码块里的 {{< tabs >}} 仍会执行。要让它原样显示,在两侧定界符的内侧各加一对注释符号,写成 {{</* tabs */>}},百分号形式对应 {{%/* steps */%}}。本页每一处展示 shortcode 的地方都是这么写的。
  • 围栏里套围栏:外层用四个反引号、内层三个,本页每一段「源码」都是这么写的;内层还有围栏时外层再加一个。
  • 属性写在信息行上:围栏的属性跟在开栏那一行的语言后面,表格与图片的属性才写在块的下一行。写到下一行会变成正文里一段可见的花括号。
  • 未知属性会失败,不会被忽略,错误信息里列出允许的名字。stylesrcdocon* 被拒绝;data-td-code* 前缀以及 data-languagedata-line-countdata-collapse-lines 是主题的保留名,写上去同样构建失败。
  • 列表项里的围栏:缩进要与列表项内容对齐(1. 之后恒定三个空格),否则围栏会脱离列表。

输出形态

输出呈现
HTML<div class="td-code"> 外壳 + Chroma 的 .highlight/.chroma;复制、折叠按钮在服务器输出里是 hidden,脚本确认可用后才显示
打印完整代码,去掉复制、折叠、渐隐;长块允许跨页;标题栏保留
Markdown原样输出源码围栏,连 {…} 属性一起
RSS静态代码块,无按钮

没有复制或折叠控件的页面不加载 code-block.js;打印、Markdown 与 RSS 输出不加载。

参数参考

开栏那一行、语言之后的 {…} 里,OINK 自己的属性:

title , 非空字符串 , default
可见标题栏(通常是文件名),同时是无障碍名称
filename , 非空字符串 , default
title 的历史别名;两者同时出现构建失败
copy , all command true false , default会话 lexer 为 command,其余为 all
true 等价于 allcommand 只允许 console/shell-session
wrap , 布尔 , defaultfalse
视觉换行,不改源码;与表格行号互斥
collapse , 正整数 , default
初始显示的最大行数;行数不足时不生效
label , 非空字符串 , default由标题派生
无障碍名称,不显示在页面上;与 aria-label 互斥
id , 非空 token , default自动生成
稳定的块 ID 与行锚点前缀;不能含空白
tab , 非空字符串 , default
标签名,见标签页;与 num 互斥
group , ^[a-z][a-z0-9_-]*$ , default
写在一组的第一个围栏上,启用 hash / 同步 / 持久化;需要 tab
value , ^[a-z0-9][a-z0-9_-]*$ , default
分组内每个围栏必填,无分组时禁止;需要 tab
num , [0-9A-Za-z.-]+ , default
编号示例(Book eg);必须与 caption 同时出现
caption , 纯文本 , default
编号示例的说明;必须与 num 同时出现
class , class 列表 , default
追加到 .td-code 根元素
data-* / aria-* / role , 字符串 , default
透传到根元素

titlefilenamelabel 已经为块生成了无障碍名称与 role="group"。它们中的任意一个与 aria-labelaria-labelledbyrole 同时出现都会构建失败;这三个属性只在块没有标题也没有 label 时可以透传。

同一行还能写 Chroma 选项,主题原样转交 Hugo:

lineNos , false inline table , defaultfalse
行号形态;tablewrap=true 互斥
lineNoStart , 正整数 , default1
显示的起始行号,不影响 hl_lines 的计数
hl_lines , 行号与区间 , default
"2 4-5",按围栏内源码行计数
anchorLineNos , 布尔 , defaultfalse
行号变成锚点链接,前缀取自块的 id
tabWidth , 正整数 , defaultHugo 默认
制表符展开的空格数

限制与常见问题

  • 不换高亮器:没有 Shiki、Twoslash、浏览器端高亮,也没有可执行的代码演练场。补丁用 diff 围栏,Chroma 的 .gi/.gd 就是增删行的样式。
  • copy="command" 只认会话 lexer:写在别的语言上是构建错误,不会退化成复制全部。
  • 自动生成的 ID 不是永久链接:要发链接就写 id
  • mermaidmathchemmarkmapplantumlechartsinfographicchecksumsfiletreegallery 不是代码块:它们有各自的渲染钩子,不套这层外壳,也没有复制按钮。
  • 标签页 — 相邻围栏合成标签页的完整规则
  • 引用 — 把仓库里的真实文件当代码块插进来
  • 书籍出版 — 编号示例、交叉引用与示例目录
  • 打印支持 — 长代码在打印里的形态

4 - 标签页

给相邻的围栏或表格加一个 {tab=} 属性就得到标签页;加上 group 之后可分享链接、跨组同步、记住读者的选择。

标签页并列等价的几种写法:包管理器、发行版、YAML / TOML / JSON、环境变量与配置项。有先后的步骤、互不相关的内容不适合标签页,读者一次只看见其中一个。

原生形态是给相邻的块加 tab 属性。正文(多个段落、列表、提示块)要做成标签页时才用 tabs/tab shortcode。两种形态共用一个运行时、一套 DOM 与一样的键盘行为。

最简例子

连着写两个带 tab 的围栏,中间只隔空行。

源码
```bash {tab="Homebrew"}
brew install hugo
```
```bash {tab="Debian / Ubuntu"}
sudo apt install hugo
```
Homebrew
brew install hugo
Debian / Ubuntu
sudo apt install hugo

服务器输出两个带标题的代码块,没有面板被隐藏;页面加载后运行时把相邻的同类块重组为标签页。在 GitHub 上、打印时、关闭 JavaScript 时,读者看到的是连续两块完整内容。

分组:链接、同步与记忆

只在第一个块上写 group,这一组就有了公开的 URL hash #<group>-<value>、页内同步与浏览器持久化;分组内的每个块都要写 value

源码
```bash {tab="npm" group="pkgmgr" value="npm"}
npm create hugo-site@latest
```
```bash {tab="pnpm" value="pnpm"}
pnpm create hugo-site
```
```bash {tab="Yarn" value="yarn"}
yarn create hugo-site
```
npm
npm create hugo-site@latest
pnpm
pnpm create hugo-site
Yarn
yarn create hugo-site

value 是机器值(^[a-z0-9][a-z0-9_-]*$),tab 是给人看的标签名,两者互不相干。上面这组的 pnpm 面板对应的 hash 是 #pkgmgr-pnpm,带这个 hash 访问本页会直接选中它。

同组联动

下面这组用了同一个 group="pkgmgr"。在上面那组切换包管理器,这组会跟着切;在这组切换,上面那组也跟着切。选择写入 localStoragetd-tabs:v1:pkgmgr 键,在其它页面同组的标签页上仍然生效。

源码
```bash {tab="npm" group="pkgmgr" value="npm"}
npm run build
```
```bash {tab="pnpm" value="pnpm"}
pnpm build
```
npm
npm run build
pnpm
pnpm build

这组没有 yarn 面板。同步时缺哪个值就保持不动,不会出现「一组没有选中项」的状态。初始选哪个的优先级是:URL hash,存储的值,shortcode 的 default 或第一个块,第一个标签。带 hash 打开页面只切换,不覆盖读者已经存下的偏好。

表格也能做标签页

同一套属性写在表格的属性行上,连着的表格就组成一组标签页。

源码
| 参数 | 默认值 |
| --- | --- |
| `shared_buffers` | 25% RAM |
| `max_connections` | 100 |
{tab="PostgreSQL 18" group="pgver" value="pg18"}

| 参数 | 默认值 |
| --- | --- |
| `shared_buffers` | 128MB |
| `max_connections` | 100 |
{tab="PostgreSQL 13" value="pg13"}
PostgreSQL 18
参数默认值
shared_buffers25% RAM
max_connections100
PostgreSQL 13
参数默认值
shared_buffers128MB
max_connections100

围栏与表格是两种块类型,相邻也不会合成同一组:一组标签页里只能全是围栏或全是表格。两者混排使用下面的 shortcode 形态。

标签名与文件名共存

围栏的 tabtitle 可以一起写:标签名进标签栏,文件名标题栏留在面板里。

源码
```yaml {tab="YAML" title="hugo.yml" group="conffmt" value="yaml"}
params:
  ui:
    sidebar_menu_foldable: true
```
```toml {tab="TOML" title="hugo.toml" value="toml"}
[params.ui]
sidebar_menu_foldable = true
```
YAML
hugo.yml
params:
  ui:
    sidebar_menu_foldable: true
TOML
hugo.toml
[params.ui]
sidebar_menu_foldable = true

单独一个块只是带标题的块

一个块要凑够两个相邻的同类块才会变成标签页。落单的块保留标题,不会变成只有一个标签的标签栏。

源码
```ini {tab="只有这一块"}
listen_addresses = '*'
```
只有这一块
listen_addresses = '*'

块之间只允许空行。三种情况会断开一组:中间隔了正文(段落、标题、列表都算);中间有一条 HTML 注释,<!-- prettier-ignore-end --> 是常见的一处;后一个块自己写了 group,一组里只有第一个块可以带 group

正文标签页

面板里要放段落、列表、提示块或多个块时,用 tabs/tab shortcode。正文是完整的 Markdown。

源码
{{< tabs group="deploy" default="pages" label="部署方式" >}}
{{< tab label="GitHub Pages" value="pages" >}}
仓库自带 `.github/workflows/`,推到 `main` 就会构建并发布。

> [!NOTE]
> `baseURL` 要写成仓库的 Pages 地址。
{{< /tab >}}
{{< tab label="Cloudflare Pages" value="cloudflare" >}}
在 Cloudflare 控制台里连接仓库,构建命令:

```bash
hugo --gc --minify
```
{{< /tab >}}
{{< /tabs >}}

仓库自带 .github/workflows/,推到 main 就会构建并发布。

说明

baseURL 要写成仓库的 Pages 地址。

在 Cloudflare 控制台里连接仓库,构建命令:

hugo --gc --minify

default 指定初始选中的面板,它必须是某个子项的 value,并且需要 group。没有 group 时不能写 value,主题自动生成 tab1tab2 等值,这组标签页只在本地切换,不动 URL 也不写存储。shortcode 形态比属性形态严格:写错的地方在构建期就报出来,不留到浏览器里。

输出形态

输出呈现
HTML<div class="td-tabs"> + role="tablist" 的按钮与面板;运行时接管前所有面板都可见
打印连续的带标题静态分节,没有标签栏
Markdown围栏形态保持源码围栏(含 {tab=} 属性);shortcode 形态输出 **标签名** 加正文
RSS与打印相同,堆叠的带标题分节

只有用到标签页的页面才加载 tabs.js;打印、Markdown 与 RSS 输出不加载。

参数参考

写在围栏信息行或表格属性行上的属性:

tab , 非空字符串 , default
可见标签名;单独出现时就是这个块的标题
group , ^[a-z][a-z0-9_-]*$ , default
写在一组的第一个块上,启用 hash、页内同步与持久化;需要 tab
value , ^[a-z0-9][a-z0-9_-]*$ , default
分组内每个块必填,无分组时禁止;需要 tab

tabs shortcode:

group , ^[a-z][a-z0-9_-]*$ , default
同上,启用 hash、同步与持久化
default , 某个子项的 value , default第一个子项
初始选中的面板;需要 group
label , 纯文本 , default本地化的「选项卡」
标签栏的无障碍名称,不显示在页面上

tab shortcode:

label , 纯文本 , required
可见标签名
value , ^[a-z0-9][a-z0-9_-]*$ , required
无分组时禁止书写,自动生成 tab1tab2 等值

行为约定:面板 ID 在分组里是 <group>-<value>,同一页出现第二组同名 group 时后续各组的 ID 加 -2-3 后缀(深链目标始终是第一组),未分组时由主题生成;存储键是 td-tabs:v1:<group>;用户点击或按键会用 replaceState 更新 hash 并写入存储,带 hash 访问只切换不写入。键盘上左右方向键(感知 RTL)与 Home/End 移动并激活标签,焦点停留在标签上。

限制与常见问题

  • 构建失败的写法:属性形态里 valuegroupgroupvaluetabtab 与编号属性 num 同时出现;shortcode 形态里一组内 value 重复、tabs 没有 tab 子项、子项之间夹着正文、default 不匹配任何子项的 value
  • 属性形态的分组错误不中断构建,只在浏览器控制台留警告:分组内漏写 value 时整组丢掉 group,退化成只在本地切换的标签页,hash、同步与持久化都没有;value 重复时整组跳过,那几个块保持为各自带标题的块。
  • 围栏与表格不会混成一组,正文与代码混排请用 shortcode 形态。
  • 标签页不是折叠块。只想收起长输出用 > [!DETAILS](见提示块)。
  • 同名 group 是全站共享的:读者在 A 页选了 pnpm,B 页同组的标签页也会是 pnpm。这是它的用途,也意味着 group 名要按含义取,不用 tabs1 这种。
  • 代码块 — 围栏的其余属性(标题、复制、行号、折叠)
  • 表格 — 表格属性行的其余取值
  • 提示块 — 折叠而不是并列时用它
  • 步骤 — 步骤里可以放标签页

5 - 表格

普通 GFM 表格加一行属性,就得到标题、兼容矩阵、参数表、编号表或标签页;宽表格自己横向滚动,不撑宽页面。

表格是普通的 GFM 管道表格。主题的表格渲染钩子把每张表包进一块可横向滚动的区域,表格下面那一行 {…} 属性决定它是哪一种表:带标题的表、兼容矩阵、参数表、编号表或标签页。合并单元格、排序与筛选不在能力范围内,需要它们的场景请改换呈现方式。

最简例子

不写属性行就是一张普通表。对齐方式照旧来自分隔行,表头单元格是 th scope="col"

源码
| 组件 | 端口 | 用途 |
| --- | :---: | --- |
| PostgreSQL | 5432 | 数据库 |
| Pgbouncer | 6432 | 连接池 |
| Patroni | 8008 | 高可用编排 |
组件端口用途
PostgreSQL5432数据库
Pgbouncer6432连接池
Patroni8008高可用编排

宽表格自己滚动

列太多的表不会把页面撑宽,它在自己的区域里横向滚动。这块区域可以用键盘聚焦:Tab 停入后方向键滚动,无障碍名称是本地化的「可横向滚动的表格」。

源码
| 集群 | 角色 | 版本 | 状态 | 延迟 | 连接数 | 大小 | 备份 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| pg-meta | primary | 18.1 | running | — | 42 | 12 GB | 2026-08-17 |
| pg-test | replica | 18.1 | streaming | 12 ms | 8 | 12 GB | 2026-08-17 |
集群角色版本状态延迟连接数大小备份
pg-metaprimary18.1running4212 GB2026-08-17
pg-testreplica18.1streaming12 ms812 GB2026-08-17

表格标题

{caption="…"} 加一个可见的 <caption>,纯文本,不给表编号。

源码
| 条目 | 取值 |
| --- | --- |
| 主题版本 | v0.6.0 |
| Hugo 下限 | 0.160.1 Extended |
| 许可证 | Apache-2.0 |
{caption="本站当前使用的主题事实"}
本站当前使用的主题事实
条目取值
主题版本v0.6.0
Hugo 下限0.160.1 Extended
许可证Apache-2.0

兼容矩阵

{.matrix} 用于「行 × 列 = 支持与否」的对照表:第一列成为行表头(th scope="row"),滚动时表头行与第一列吸附不动,其余单元格居中,分隔行另有对齐时以分隔行为准。✅ 与 ❌ 是作者写的字符,主题不解析它们。

源码
| OS / PG | PG18 | PG17 | PG16 | PG15 | PG14 |
| --- | :---: | :---: | :---: | :---: | :---: |
| EL 9 | ✅ | ✅ | ✅ | ✅ | ✅ |
| EL 8 | ✅ | ✅ | ✅ | ✅ | ✅ |
| Debian 13 | ✅ | ✅ | ✅ | ❌ | ❌ |
| Ubuntu 24.04 | ✅ | ✅ | ✅ | ✅ | ❌ |
{.matrix}
OS / PGPG18PG17PG16PG15PG14
EL 9
EL 8
Debian 13
Ubuntu 24.04

用整个画布

{.full-width} 让表格越出正文栏宽,占满文章可用的宽度。适合列多但每列都短的表。

源码
| 语言 | 代码 | 侧栏 | 搜索 | 目录 | 打印 | 状态 |
| --- | --- | --- | --- | --- | --- | --- |
| 简体中文 | `zh` | ✅ | ✅ | ✅ | ✅ | 已审校 |
| English | `en` | ✅ | ✅ | ✅ | ✅ | 已审校 |
{.full-width}
语言代码侧栏搜索目录打印状态
简体中文zh已审校
Englishen已审校

参数表

{.fields} 把表格变成定义列表:第一列是名称,最后一列是说明,中间列是元数据。它是记录配置项、命令参数、API 字段的形态,写法见参数表

源码
| 参数 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `offline_search` | boolean | `false` | 构建本地搜索索引 |
| `page_width` | string | `normal` | 正文栏宽度 |
{.fields meta="type default"}
offline_search , boolean , defaultfalse
构建本地搜索索引
page_width , string , defaultnormal
正文栏宽度

编号表

写书或长手册时给表编号:num 加可选的 #idcaption。表格会被包进一个带本地化「表 N.」标签的 <figure>,并注册成 Book 目标,可以被 xref 引用、进入全书表格目录。编号由作者书写,主题不自动计数;id 缺省是 tbl-<num>

源码
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
| --- | --- | --- | --- |
| 读已提交 | 否 | 是 | 是 |
| 可重复读 | 否 | 否 | 是 |
| 可串行化 | 否 | 否 | 否 |
{#tbl-iso num="9-1" caption="PostgreSQL 各隔离级别允许的异象"}

参见 {{< xref tbl="9-1" anchor="tbl-iso" >}}。
隔离级别脏读不可重复读幻读
读已提交
可重复读
可串行化
表 9-1 PostgreSQL 各隔离级别允许的异象

参见 表 9-1

表格做成标签页

连着的表格加 {tab="…"} 就组成一组标签页,规则与相邻围栏一致:第一张表上的 group 启用 hash、同步与持久化,此后每张表都要 value。完整规则见标签页

源码
| 目录 | 内容 |
| --- | --- |
| `content/` | 页面 |
| `data/` | 首页与发布数据 |
{tab="内容" group="repo-layout" value="content"}

| 目录 | 内容 |
| --- | --- |
| `assets/` | SCSS 与图片资源 |
| `static/` | 原样拷贝的文件 |
{tab="资源" value="assets"}
内容
目录内容
content/页面
data/首页与发布数据
资源
目录内容
assets/SCSS 与图片资源
static/原样拷贝的文件

输出形态

输出呈现
HTML<div class="td-table-scroll"> 可聚焦滚动区 + <table>;矩阵与全宽是这个包装器上的修饰 class
打印完整表格按页宽排版;包装器仍在,但标成 td-table-scroll--static,不再是可聚焦视口
Markdown原样输出源码表格与属性行
RSS完整静态表格

表格不加载任何脚本。

参数参考

表格下一行的属性行:

.full-width , 标记 , default
越出正文栏宽,占满文章画布
.matrix , 标记 , default
第一列作行表头,表头与首列吸附,其余单元格居中
.fields , 标记 , default
渲染成定义列表,见参数表
caption , 纯文本 , default
可见表格标题;在 .fields 上是列表的标签
meta , 角色列表 , default
命名 .fields 中间列的语义,取值 type required default -;必须与 .fields 同用
#id , 标识符 , defaultnum 时为 tbl-<num>
[A-Za-z][A-Za-z0-9_.:-]*;写在 <table>(编号表则写在 <figure>)上
num , 字符串 , default
[0-9A-Za-z.-]+;注册为 Book 表目标,标题前加「表 N.」
tab / group / value , 标签页 , default
相邻表格组成标签页
class , class 列表 , default
站点 CSS 用,原样留在 <table>
data-* / aria-* , 字符串 , default
透传

styleon* 与任何其它键都会让构建失败。

限制与常见问题

  • 互斥规则:.fields 不能和 .matrix.full-widthnum 一起用;numtab 互斥;group/value 需要 tabmeta 需要 .fields
  • 属性行必须紧贴表格:中间空一行,它就变成正文里一段可见的花括号。Markdown 格式化工具常移动这一行,把它包进 <!-- prettier-ignore-start --> / <!-- prettier-ignore-end -->
  • 没有合并单元格、没有排序、没有筛选:GFM 管道表格能表达的就是全部。需要合并表头的复杂表请拆成两张表或改成一张矩阵。
  • 单元格里放不下块内容:多段说明、列表、围栏要用 fields/field shortcode。
  • .matrix 的居中由 CSS 实现:分隔行里写了对齐就以分隔行为准。

6 - 参数表

用一张普通表格加 {.fields} 记录配置项、命令参数与 API 字段:名称、类型、默认值、说明各就各位,窄屏不挤,每条都能单独链接。

参数表(Fields)把「一串具名值 + 元数据 + 说明」渲染成响应式定义列表:名称独占一行,类型、是否必填、默认值是名称旁边的小字,说明另起一行,每一条自带锚点。用于配置项、命令参数与 API 字段。要按同一批列横向比较很多行时用普通表格,内容是操作顺序时用步骤。

写法有两种:普通表格加 {.fields}(默认选它),以及 fields/field shortcode(说明需要多个段落、列表或代码块时才用)。两种形态渲染出相同的条目。

最简例子

一张至少两列的管道表格,下一行写 {.fields}。第一列是名称,最后一列是说明,中间每一列都是元数据,标签就是表头文字本身。

源码
| 参数 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `offline_search` | boolean | `false` | 构建本地搜索索引并启用命令面板 |
| `offline_search_max_results` | integer | `10` | 搜索结果条数上限 |
| `page_width` | string | `normal` | 正文栏宽度,可选 `narrow` `normal` `wide` |
{.fields}
offline_search , 类型boolean , 默认值false
构建本地搜索索引并启用命令面板
offline_search_max_results , 类型integer , 默认值10
搜索结果条数上限
page_width , 类型string , 默认值normal
正文栏宽度,可选 narrow normal wide

这里的元数据显示成「表头: 值」。主题不推断表头的含义,类型 只是一个标签;要让它变成标准芯片见下一节。单元格接受行内 Markdown(代码、强调、链接),空的中间单元格省略。

语义列 meta=

meta 按顺序说明每一个中间列扮演什么角色:type(类型)、required(必填)、default(默认值),或者 -(保留表头当标签)。有了它,表格形态渲染出的芯片与 shortcode 形态一致。

源码
| 参数 | 类型 | 必填 | 默认值 | 说明 |
| --- | --- | --- | --- | --- |
| `baseURL` | string | 是 | | 站点地址,含子路径 |
| `title` | string | 是 | | 站点名,出现在顶栏与页签 |
| `defaultContentLanguage` | string | | `en` | 默认语言,决定无前缀路径属于哪种语言 |
{.fields meta="type required default"}
baseURL , string , required
站点地址,含子路径
title , string , required
站点名,出现在顶栏与页签
defaultContentLanguage , string , defaulten
默认语言,决定无前缀路径属于哪种语言

规则:

  • meta 必须为每一个中间列写一个角色,个数等于总列数减二;写多写少都构建失败。
  • required 列是「非空即真」:单元格里写「是」「yes」「✔」都一样,渲染出来的是不翻译的 required 芯片;留空就不显示。
  • typedefault 单元格如果本身没有行内标记,会自动套上代码格式,与 shortcode 形态对齐。
  • 三种语义芯片按 typerequireddefault 的顺序显示,与列的顺序无关;- 列跟在后面,按列顺序排。

- 可以和语义角色混用,用来保留一列自定义标签:

源码
| 环境变量 | 类型 | 作用域 | 说明 |
| --- | --- | --- | --- |
| `HUGO_MODULE_WORKSPACE` | string | 构建 | 指向 `go.work`,让主题从本地 checkout 解析 |
| `HUGO_ENV` | string | 构建 | 设为 `production` 时启用压缩与指纹 |
{.fields meta="type -"}
HUGO_MODULE_WORKSPACE , string , 作用域构建
指向 go.work,让主题从本地 checkout 解析
HUGO_ENV , string , 作用域构建
设为 production 时启用压缩与指纹

标签与容器 ID

caption 给整张表加一个可见标签(同时是无障碍名称),id 命名外层容器,方便从别处链接过来或写站点 CSS。

源码
| 参数 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `enable` | boolean | `false` | 打开图片缩放 |
| `selector` | string | `.td-content` | 扫描候选图片的根选择器 |
{.fields caption="params.ui.image_zoom" id="zoom-params" meta="type default"}

params.ui.image_zoom

enable , boolean , defaultfalse
打开图片缩放
selector , string , default.td-content
扫描候选图片的根选择器

每一条都能单独链接

每个条目获得一个 field-<名称> 形式的锚点,鼠标移上去时名称右边出现自链接图标。上面第一张表里的 page_width 就是 #field-page_width,回答问题时可以把这一行的链接单独发出去。

同一页里重名的字段按 -2-3 顺延,规则与 Goldmark 处理重名标题一致。锚点只在 HTML 里生成:打印和 RSS 会把很多页拼成一个文档,页内锚点在那里会冲突。

shortcode 形态

说明需要多个段落、列表或代码块时,表格单元格装不下,改用 fields/field

源码
{{< fields label="pig 命令常用参数" >}}
{{< field name="--config" type="path" required=true >}}
配置文件路径。相对路径按当前工作目录解析。

如果同时设置了 `PIG_CONFIG` 环境变量,命令行参数优先。
{{< /field >}}
{{< field name="--log-level" type="string" default="info" >}}
日志级别,从低到高:

- `debug`:打印每一次远程调用
- `info`:默认值
- `error`:只在失败时输出
{{< /field >}}
{{< field name="--dry-run" type="boolean" default=false >}}
只打印将要执行的动作,不改任何东西:

```bash
pig ext install pg_duckdb --dry-run
```
{{< /field >}}
{{< /fields >}}

pig 命令常用参数

--config , path , required

配置文件路径。相对路径按当前工作目录解析。

如果同时设置了 PIG_CONFIG 环境变量,命令行参数优先。

--log-level , string , defaultinfo

日志级别,从低到高:

  • debug:打印每一次远程调用
  • info:默认值
  • error:只在失败时输出
--dry-run , boolean , defaultfalse

只打印将要执行的动作,不改任何东西:

pig ext install pg_duckdb --dry-run

required=truedefault=false 是布尔值,不加引号。default 接受任何标量:default=0default="" 都会如实显示(空字符串显示成 ""),不写 default 就不显示这一项。每个 field 必须有非空正文,并且必须是 fields 的直接子项。

两种形态的选择

情况用法
每条说明一句话,能放进表格单元格表格 + {.fields}
说明要分段、带列表或代码块fields/field shortcode
读者需要按同一批列横向比较很多行用普通表格,不转成参数表
内容是操作顺序步骤

表格形态在 GitHub 上仍然是一张可读的表,OINK 的 Markdown 输出也保持表格原样,这是默认选它的理由。

输出形态

输出呈现
HTML<div class="td-fields"> + 语义 <dl>;条目带 #field-<名称> 锚点与自链接
打印完整定义列表,不带条目锚点
Markdown表格形态保留源码表格;shortcode 形态输出「名称 — 类型;required;default: 值」加缩进说明的项目符号列表
RSS完整静态 <dl>,不带条目锚点

不加载任何脚本。

参数参考

表格属性行(写在表格下一行):

.fields , 标记 , default
必需;把表格渲染成参数表
meta , 角色列表 , default
空格分隔,取值 type required default -;个数等于中间列数;语义角色不可重复
caption , 纯文本 , default
可见标签,同时是列表的无障碍名称
id , 标识符 , default
外层容器的 ID
class , class 列表 , default
透传给站点 CSS
data-* / aria-* , 字符串 , default
透传

fields shortcode:

label , 非空字符串 , required
可见标签,作用同表格的 caption
id , 标识符 , required
外层容器 ID;不能含空白、引号、<>&
class / data-* / aria-* , 字符串 , required
与表格属性行同一套策略

field shortcode:

name , 非空字符串 , required
字段名
type , 非空字符串 , required
类型标签,如 boolean string[] duration
required , 布尔 , required
true 时显示不翻译的 required 芯片,默认 false
default , 标量 , required
字符串 / 布尔 / 整数 / 浮点;false0"" 都会显示

限制与常见问题

  • 第一列必须非空,且在同一张表内唯一:重名或空名构建失败。
  • .fields 不能与 .matrix.full-widthnum 组合,meta 不能用在没有 .fields 的表上。
  • 表格单元格里放不下块内容:需要段落、列表、围栏就换 shortcode 形态。
  • requireddefault 是不翻译的 API 词汇,在所有语言下都显示英文,它们是契约词,不是界面文案。
  • 暂不支持 kindsincedeprecatedlocation、字段级链接与嵌套结构,也不会在构建时解析 TypeScript 或 OpenAPI schema。
  • 表格 — 属性行的其它取值与互斥规则
  • 配置总览 — 站点参数全表就是用参数表写的
  • 页面参数 — front matter 全表
  • 步骤 — 顺序动作不要写成参数表

7 - 步骤

有序列表加 {.steps} 就是带编号圆点与竖线的操作步骤;步骤要带标题、要进目录时改用 steps shortcode。

步骤(Steps)是带编号圆点与竖线的有序列表:一个普通有序列表,加一行 {.steps} 标记,编号圆点与串起它们的竖线由 CSS 绘制,不加载脚本。用于有先后的操作流程。并列而无先后的内容用普通列表或卡片。

写法有两种:有序列表加 {.steps}(默认选它),以及 {{% steps %}} shortcode,每一步要有自己的标题、标题还要进右侧目录时用它。

最简例子

每一项都写 1.,让 Markdown 自己数。这样插入、删除、调换步骤都不用手改编号,而且内容缩进恒定是三个空格。

源码
1. 安装 Hugo Extended
1. 克隆文档站
1. 启动本地预览
{.steps}
  1. 安装 Hugo Extended
  2. 克隆文档站
  3. 启动本地预览

{.steps} 必须紧贴列表最后一行,中间空一行它就会变成正文里一段可见的花括号。

步骤内容

列表项里可以放任何块级内容:段落、代码围栏、提示块、表格、嵌套列表、图片。缩进对齐到列表项的内容列(三个空格)即可。

源码
1. 克隆文档站,它本身就是主题的完整示例。

   ```bash
   git clone https://github.com/pgsty/oink.pgsty.com my-docs
   cd my-docs
   ```

1. 启动本地服务器。

   ```bash
   hugo server
   ```

   > [!NOTE]
   > 首次构建会通过 Go 模块代理拉取主题,需要本机安装 Go。

1. 替换三处内容,它就是你的站点。

   | 位置 | 替换为 |
   | --- | --- |
   | `hugo.yml` 的 `title` | 你的站名 |
   | `hugo.yml` 的 `baseURL` | 你的域名 |
   | `content/` | 你的内容 |
{.steps}
  1. 克隆文档站,它本身就是主题的完整示例。

    git clone https://github.com/pgsty/oink.pgsty.com my-docs
    cd my-docs
  2. 启动本地服务器。

    hugo server
    说明

    首次构建会通过 Go 模块代理拉取主题,需要本机安装 Go。

  3. 替换三处内容,它就是你的站点。

    位置替换为
    hugo.ymltitle你的站名
    hugo.ymlbaseURL你的域名
    content/你的内容

{{< … >}} 形式的 shortcode(标签页、卡片、徽章等)也可以写在列表项里;{{% … %}} 形式不行,见下面的限制

一步里按平台分开

某一步在不同平台上命令不同时,把带 {tab=} 的围栏并排写进那个列表项,它们照样会合成标签页。

源码
1. 安装 Hugo Extended。

1. 安装依赖:

   ```bash {tab="EL / RHEL" group="stepdemo" value="rpm"}
   sudo dnf install golang git
   ```
   ```bash {tab="Debian / Ubuntu" value="deb"}
   sudo apt install golang-go git
   ```

1. 运行 `hugo server` 预览。
{.steps}
  1. 安装 Hugo Extended。

  2. 安装依赖:

    EL / RHEL
    sudo dnf install golang git
    Debian / Ubuntu
    sudo apt install golang-go git
  3. 运行 hugo server 预览。

接着上一组往下编号

正文隔断了一组步骤时,把新一组的第一项写成它实际的序号,Markdown 会输出 start,编号从那里继续(支持到 40)。

源码
4. 配置 `baseURL` 与部署工作流。
1. 推送到 `main`,等待 GitHub Actions 构建完成。
{.steps}
  1. 配置 baseURL 与部署工作流。
  2. 推送到 main,等待 GitHub Actions 构建完成。

带标题的步骤

步骤本身很长、每一步该有个能被链接和被目录收录的标题时,用 {{% steps %}}:它的正文是页面级 Markdown,里面的每一个直接子标题就是一步,正文不用缩进。下面三步的标题就在这一页的右侧目录里。

源码
{{% steps %}}

### 安装工具链 {#install-toolchain}

需要 Hugo Extended ≥ 0.160.1 与 Go。

### 启动服务器 {#run-server}

{{< tabs group="oink-os" default="macos" >}}
{{< tab label="macOS" value="macos" >}}
`brew install hugo go`
{{< /tab >}}
{{< tab label="Debian" value="debian" >}}
`sudo apt install hugo golang-go`
{{< /tab >}}
{{< /tabs >}}

### 发布 {#publish}

推送到 `main`,仓库自带的工作流会构建并发布。

{{% /steps %}}

安装工具链

需要 Hugo Extended ≥ 0.160.1 与 Go。

启动服务器

brew install hugo go

sudo apt install hugo golang-go

发布

推送到 main,仓库自带的工作流会构建并发布。

它是主题里唯一的 {{% … %}} shortcode。百分号形式的正文交给 Goldmark 当页面级 Markdown 处理:只有这样,里面的标题才能进目录,里面才能放 tabscardsfields 这些容器 shortcode。代价是它自己不能嵌进列表项,也不能嵌进另一个百分号容器。

同一组步骤的标题保持同一层级,不要把一个 steps 套进另一个里。

两种形态的选择

情况用法
步骤是一两句话加一段命令有序列表 + {.steps}
每一步需要标题、需要被链接、需要进目录{{% steps %}}
步骤里要放 tabscardsfields 容器{{% steps %}}
步骤本身要嵌在另一个列表项里有序列表 + {.steps}

输出形态

输出呈现
HTML原生形态是 <ol class="steps">,编号与竖线由 CSS 画;shortcode 形态是 <div class="td-steps"> 加各级标题
打印编号与内容照旧,竖线保留
Markdown原样输出源码:有序列表加 {.steps},或标题加正文
RSS静态列表 / 标题分节

不加载脚本;关闭 JavaScript 后呈现不变。

参数参考

两种形态都没有参数,只有写法约定:

{.steps} , 位置有序列表下一行
必需;写在无序列表上不生效
1. , 位置每一项
让 Markdown 自己数;内容缩进恒为三个空格
4.(首项) , 位置第一项
输出 <ol start="4">,编号从 4 接着走,支持 2–40
{{% steps %}} , 位置包住若干标题
直接子标题(########)就是步骤;正文不缩进

限制与常见问题

  • 列表项里不能写 {{% … %}}:百分号 shortcode 的多行输出会把列表截断。要在步骤里放容器就整组改用 shortcode 形态。
  • {{% steps %}} 不能放进列表项,也不能套在另一个百分号容器里。
  • 标记要紧贴列表:{.steps} 与列表之间不能有空行;经过 Prettier 之类的格式化工具时,把它包进 <!-- prettier-ignore-start --> / <!-- prettier-ignore-end -->
  • {.steps} 只对有序列表有效:写在 - 开头的无序列表上不会有编号。
  • 步骤不折叠、不记进度:没有「已完成」状态,也没有展开收起。

8 - 卡片

用带 {.cards} 的链接列表排出导航卡片网格;需要图标、徽章、图片时改用 shortcode。

卡片(Cards)是一组并列的链接:每张卡片一个链接标题加一句描述,网格随容器宽度自适应。适合栏目首页、「接下来读什么」与几条并列路径的入口。不适合排版正文段落(用普通段落)或做图片墙(用画廊)。

最简例子

{.cards} 的链接列表就是卡片。链接是标题, 之后是描述。

源码
- [快速上手](/zh/docs/start/) — 克隆这个文档站,删掉不需要的页面,替换为你的站点信息。
- [创作内容](/zh/docs/write/) — 页面怎么组织、front matter 有哪些键。
- [定制站点](/zh/docs/customize/) — 导航、搜索、品牌、多语言。
{.cards}
  • 快速上手 — 克隆这个文档站,删掉不需要的页面,替换为你的站点信息。
  • 创作内容 — 页面怎么组织、front matter 有哪些键。
  • 定制站点 — 导航、搜索、品牌、多语言。

整张卡片是点击热区,不只是标题文字。没有 columns 参数:列数由容器宽度决定,窄屏收成一列。

只有标题的卡片

描述可以省略。一行一个链接,{.cards} 收尾。

源码
- [提示块](/zh/docs/components/callout/)
- [标签页](/zh/docs/components/tabs/)
- [步骤](/zh/docs/components/steps/)
- [参数表](/zh/docs/components/fields/)
{.cards}

松散列表与多段描述

一句话装不下时改用松散列表:链接单独一段,描述另起一段,列表项之间空一行。标题独占一行,描述在标题下方。{.cards} 仍然紧贴最后一段,中间 不能有空行

源码
- [页面参数](/zh/docs/write/frontmatter/)

  每个页面参数在这里有唯一定义:类型、默认值、取值范围,以及讲它的那一页。

- [配置总览](/zh/docs/customize/config/)

  站点参数按功能分组,同样每行反向链接到讲它的指南页。
{.cards}
  • 页面参数

    每个页面参数在这里有唯一定义:类型、默认值、取值范围,以及讲它的那一页。

  • 配置总览

    站点参数按功能分组,同样每行反向链接到讲它的指南页。

图标与徽章

链接列表不支持图标、徽章、图片与多段描述,这些用 cards / card shortcode。icon 是恰好一对 Font Awesome class,badge 是一段纯文本。

源码
{{< cards >}}
{{< card title="快速上手" link="/zh/docs/start/" icon="fa-solid fa-rocket" badge="从这里开始" >}}
Fork 文档站本身,十分钟内完成本地预览。
{{< /card >}}
{{< card title="发布与下载页" link="/zh/docs/write/releases/" icon="fa-solid fa-box-open" badge="v0.5" >}}
`release` 事实记录 + 资产表 + 校验和,全部本地生成。
{{< /card >}}
{{< card title="键盘导航" link="/zh/docs/customize/keyboard/" icon="fa-solid fa-keyboard" >}}
全站快捷键与焦点顺序。
{{< /card >}}
{{< /cards >}}
快速上手从这里开始

Fork 文档站本身,十分钟内完成本地预览。

release 事实记录 + 资产表 + 校验和,全部本地生成。

图标格式不符(不是 fa-solid fa-xxx 这样的一对 class)时构建失败,不会静默丢弃。

Markdown 正文

card 的正文按页面级 Markdown 渲染:行内代码、强调、链接、列表都可以。titlebadge 这些参数是纯文本,不解析 Markdown。

源码
{{< cards >}}
{{< card title="Hugo Module" icon="fa-brands fa-golang" >}}
`hugo mod get github.com/pgsty/oink`。推荐方式,升级只需改一行版本号。
{{< /card >}}
{{< card title="Git Submodule" icon="fa-solid fa-code-branch" >}}
无需安装 Go:

- `git submodule add`
- 主题落在 `themes/oink`
{{< /card >}}
{{< /cards >}}
Hugo Module

hugo mod get github.com/pgsty/oink。推荐方式,升级只需改一行版本号。

Git Submodule

无需安装 Go:

  • git submodule add
  • 主题落在 themes/oink

不写 link 的卡片渲染成加粗标题,不生成链接。

带图片的卡片

image![alt](src) 的解析顺序一致:页面资源 → 全局资源 assets/ → 静态路径 /images/… → 远程 URL。本地资源带上固有尺寸,避免加载跳版。

image 必须配一个替代文字来源:image_alt="…"(有信息的图)或 decorative=true(纯装饰)。两个都写、两个都不写都会构建失败。

源码
{{< cards >}}
{{< card title="OINK 文档外壳" link="/zh/docs/about/features/" image="images/content-primitives/oink.webp" image_alt="OINK 文档页面:侧栏、正文与目录三栏" >}}
侧栏、正文、目录,三栏可以单独关闭。
{{< /card >}}
{{< card title="发布说明" link="/zh/docs/write/releases/" image="/images/releasenote.webp" decorative=true >}}
装饰性封面:`decorative=true` 输出空 alt,读屏器会跳过它。
{{< /card >}}
{{< /cards >}}

装饰性封面:decorative=true 输出空 alt,读屏器会跳过它。

卡片图片不参与图片缩放,整张卡片本身已经是链接。

栏目首页的自动卡片

栏目首页(_index.md)不需要手写卡片列表:主题读子页的 titledescriptionicon 自动生成一组卡片。本站在 hugo.yml 中全局启用:

hugo.yml
params:
  ui:
    section_index: cards # list | cards

单个栏目可以在自己的 front matter 里覆盖,也可以用 cascade 把选择推给整棵子树:

content/docs/customize/_index.zh.md
section_index: list

自动卡片与手写卡片使用同一套 td-content-card 样式,区别只在数据来源。栏目首页不要手写子页清单:手写清单会与侧栏不同步。要排的内容不是本栏目的子页时(例如混合站外链接、跨栏目推荐),才在正文里手写卡片。相关键的完整定义见配置总览

两种形态的选择

你要的用哪种
一句话描述的链接网格{.cards} 链接列表
图标、徽章、图片cards / card shortcode
描述里要列表、代码、多段cards / card shortcode
没有链接的卡片cards / card shortcode
本栏目的子页什么都不写,靠 section_index: cards

链接列表在 GitHub 上仍是一个链接列表,shortcode 不是。能用原生形态时用原生形态。

输出形态

输出呈现
HTML原生形态是 <ul class="cards">;shortcode 形态是 <div class="td-content-cards"> + 每张 <article class="td-content-card">。两者都是纯 CSS 网格,不加载脚本
打印原生形态竖排,shortcode 形态收成两列;两者的单张卡片都避免跨页断开
Markdown原生形态原样输出链接列表;shortcode 形态输出 - [标题](链接) (徽章) — 描述
RSS与 HTML 同样的标记(没有站点 CSS 时是一份可读的链接清单)

参数参考

原生形态:

{.cards} , 列表属性行 , default
写在无序列表 之后 的一行;只对无序列表生效
列表项首个链接 , Markdown 链接 , default
卡片标题,同时是整张卡片的点击目标
其余内容 , Markdown , default
描述。紧凑列表里跟在 后面,松散列表里另起一段

card 的参数(cards 自身不接受任何参数):

title , 纯文本 , default
必填,非空。卡片标题
link , URL , default
站内路径、相对路径、http(s):mailto:;外链自动加 rel="noopener"
icon , Font Awesome class 对 , default
例如 fa-solid fa-rocket;格式不符构建失败
badge , 纯文本 , default
标题右侧的小标签
image , 图片来源 , default
页面资源 / 全局资源 / 静态路径 / 远程 URL
image_alt , 纯文本 , default
image 时与 decorative 二选一
decorative , 布尔 , defaultfalse
true 表示装饰图,输出空 alt
正文 , Markdown , default
卡片描述

没有 colscolumnsaccentdesccolor 参数;未知参数一律构建失败。

限制与常见问题

  • {.cards} 只认无序列表:有序列表加了这个标记不会变成卡片。
  • {.cards} 必须紧贴列表:中间空一行、或缩进进列表项,标记被静默丢弃,构建不报错,列表仍是列表。渲染结果不是卡片时先检查这一行。
  • card 只能待在 cards 里:单独使用、或放进别的 shortcode,构建失败并指出位置。
  • 列数不可配:网格按容器宽度自适应,只有栏目首页的自动卡片能用 params.ui.section_index_columns 指定列数。
  • 卡片不放长文:描述超过两行时改用正文段落或提示块

9 - 文件树

filetree 围栏画带注释的目录结构:对齐的注释列、逐条目图标、可折叠目录、可拖动的分栏。

文件树(FileTree)是一个 filetree 围栏,围栏正文就是目录清单:缩进表示层级,结尾的 / 表示目录,# 之后是注释。适合解释一份目录结构里与读者有关的那部分,并逐条加上说明。需要读者逐字复制的清单用普通代码块。

最简例子

源码
```filetree
- content/
  - _index.zh.md
  - docs/
  - blog/
- hugo.yml
- go.mod
```
  • content/
    • _index.zh.md
    • docs/
    • blog/
  • hugo.yml
  • go.mod

项目符号(-*+)可以省略,效果相同。有子项的条目是目录;没有子项时,结尾的 / 告诉主题它是目录。

加注释

每行第一个前面带空白的 # 之后是注释,渲染成对齐的右列。注释是纯文本,里面的 Markdown 按字面显示;要一个字面井号就写 \#

源码
```filetree
- content/          # 全部页面,中英双语同目录
  - docs/          # 你正在读的这棵文档树
  - blog/           # 发布说明与文章
- assets/scss/      # 站点自己的 SCSS,覆盖主题变量
- layouts/          # 站点级模板覆盖,越少越好
- static/images/    # 不需要构建期处理的图
- hugo.yml          # 站点配置:语言、菜单、params.ui
```
  • content/全部页面,中英双语同目录
    • docs/你正在读的这棵文档树
    • blog/发布说明与文章
  • assets/scss/站点自己的 SCSS,覆盖主题变量
  • layouts/站点级模板覆盖,越少越好
  • static/images/不需要构建期处理的图
  • hugo.yml站点配置:语言、菜单、params.ui

注释列的起点在构建期算出,由最宽的一行决定,因此每行的 # 从同一列开始,与源码里是否对齐无关。注释列最多占面板的右半边,最少占三成。中间的虚线是分隔条,可以拖动,也可以用 Tab 聚焦后按方向键调整(Home / End 到两端)。

过长的名称与注释各自在本列内用省略号截断,鼠标悬停时由 title 提示完整文本。分隔条是文件树唯一的 JavaScript,只有 带注释 的树才加载它。

源码
```filetree {title="两列都发生截断"}
- runbooks/
  - a-deliberately-long-runbook-filename-for-a-failover-drill.md  # 同样超长的注释,写在一行里,因此必须在注释列内截断
  - restart.md                                                    # 短名字
```

两列都发生截断

  • runbooks/
    • a-deliberately-long-runbook-filename-for-a-failover-drill.md同样超长的注释,写在一行里,因此必须在注释列内截断
    • restart.md短名字

标题栏

围栏属性 {title="…"} 在树上方渲染一条标题栏;不写时没有标题栏。

源码
```filetree {title="oink.pgsty.com 仓库根目录"}
- content/          # 页面
- assets/           # 参与构建的资源
- data/             # 首页、Landing、下载页的数据
- layouts/          # 模板覆盖
- static/           # 原样拷贝的文件
- tests/            # Playwright 与 node --test
- hugo.yml
- go.mod            # 用 Hugo Module 引入主题
- Makefile          # make d / make b / make c
```

oink.pgsty.com 仓库根目录

  • content/页面
  • assets/参与构建的资源
  • data/首页、Landing、下载页的数据
  • layouts/模板覆盖
  • static/原样拷贝的文件
  • tests/Playwright 与 node --test
  • hugo.yml
  • go.mod用 Hugo Module 引入主题
  • Makefilemake d / make b / make c

缩进与层级

层级由缩进决定。两个空格、四个空格、制表符(按四列计算)都可以,同一棵树内不要求统一,条件是每次退回的层级此前已经打开过。tree 命令的输出可以整段粘贴,包括开头的根目录行与结尾的统计行,统计行会被丢弃。

源码
```filetree
content/docs
├── about
│   ├── _index.zh.md
│   └── features.zh.md
├── components
│   ├── filetree.zh.md
│   └── image
│       └── index.zh.md
└── _index.zh.md

3 directories, 5 files
```
  • content/docs
    • about
      • _index.zh.md
      • features.zh.md
    • components
      • filetree.zh.md
      • image
        • index.zh.md
    • _index.zh.md

退回到未打开过的缩进层级时构建失败,报错里带围栏内的行号。

折叠与显式类型

有子项的目录默认展开,{open=false} 使其初始收起。目录用原生 <details> 渲染,键盘可操作,不需要 JavaScript。open 只能写在目录上。没有子项、名字也不以 / 结尾的条目按文件处理,{type=dir} 覆盖这个判断,{type=file} 同理。

源码
```filetree {title="内容目录"}
- content/
  - docs/                # 新文档树
    - components/         # 22 个组件页        {open=false}
      - callout.zh.md
      - filetree.zh.md
      - image/            # 页面包:正文 + 图  {type=dir}
    - customize/          # 站点级配置          {open=false}
      - config.zh.md
  - blog/
    - release.zh.md
```

内容目录

  • content/
    • docs/新文档树
      • components/22 个组件页
        • callout.zh.md
        • filetree.zh.md
        • image/页面包:正文 + 图
      • customize/站点级配置
        • config.zh.md
    • blog/
      • release.zh.md

图标与配色

图标默认按名字推断:目录用文件夹图标,随开合切换;文件先按完整文件名匹配(LICENSEMakefilego.modpackage.json.gitignore 等),再按扩展名匹配(md yml toml json sh py go js sql css png svg pdf zip 等),都不匹配时用普通文件图标。

{icon=…} 覆盖它,取值是恰好一对 Font Awesome class。{tone=…} 给图标上色,取值与徽章相同:neutral info success warning danger

源码
```filetree {title="部署目录:权限与要点"}
- /etc/pigsty/                 # 0755 root:root · 配置根目录        {icon="fa-solid fa-server" tone=info}
  - pigsty.yml                 # 0644 root:root · 集群清单
  - ca/                        # 0700 root:root · 自签 CA,不要提交进 Git  {icon="fa-solid fa-lock" tone=danger open=false}
    - ca.key                   # 0600 root:root
- /var/lib/pgsql/18/data/      # 0700 postgres:postgres · 数据目录   {tone=warning}
  - postgresql.conf            # 0600 postgres:postgres
- /usr/bin/pig                 # 0755 root:root · 命令行工具         {icon="fa-solid fa-terminal" tone=success}
```

部署目录:权限与要点

  • /etc/pigsty/0755 root:root · 配置根目录
    • pigsty.yml0644 root:root · 集群清单
    • ca/0700 root:root · 自签 CA,不要提交进 Git
      • ca.key0600 root:root
  • /var/lib/pgsql/18/data/0700 postgres:postgres · 数据目录
    • postgresql.conf0600 postgres:postgres
  • /usr/bin/pig0755 root:root · 命令行工具

tone 只给图标上色,不改文字。颜色是补充,含义写在名字或注释里。

条目名写成 [名字](链接) 即为链接。站内路径、相对路径、http(s): 都可以,URL 校验与其它组件是同一套。

源码
```filetree {title="本站的组件页"}
- content/docs/components/
  - [callout.zh.md](/zh/docs/components/callout/)     # 提示块
  - [filetree.zh.md](/zh/docs/components/filetree/)   # 当前页面
  - [gallery.zh.md](/zh/docs/components/gallery/)     # 画廊
  - image/                                             # 页面包
    - [index.zh.md](/zh/docs/components/image/)       # 图片
- [hugo.yml](https://github.com/pgsty/oink.pgsty.com/blob/main/hugo.yml)   # 站点配置(GitHub)
```

本站的组件页

按平台分成标签页

围栏带 tab=(以及 group= value=)时成为一组标签页中的一页,可以与代码围栏混排。

源码
```filetree {tab="Linux" group="platform" value="linux"}
- /etc/pigsty/          # 配置
- /var/lib/pgsql/       # 数据
- /usr/bin/pig          # 可执行文件
```
```filetree {tab="macOS" value="macos"}
- ~/Library/Application Support/pigsty/   # 配置
- /opt/homebrew/bin/pig                   # 可执行文件
```
Linux
  • /etc/pigsty/配置
  • /var/lib/pgsql/数据
  • /usr/bin/pig可执行文件
macOS
  • ~/Library/Application Support/pigsty/配置
  • /opt/homebrew/bin/pig可执行文件

输出形态

输出呈现
HTML<div class="td-filetree">,可选标题栏,目录是原生 <details>;带注释时多一条可拖动分隔条(唯一的运行时)
打印同一棵树,全部展开,没有分隔条,注释换行不截断
Markdown原样输出 filetree 围栏
RSS围栏源码放进 <pre>

窄屏(小于 sm 断点)时布局收成单列:注释移到名称下方,不再截断,分隔条隐藏。不带注释的树是单列,也不加载任何脚本。

参数参考

围栏属性(写在 ```filetree 后面):

title , 纯文本 , default
树上方的标题栏;不写就不画;不能为空
tab , 纯文本 , default
让这棵树成为一个标签页
group / value , 字符串 , default
标签页分组与同步值;必须与 tab 同时出现
class , class 列表 , default
透传给站点 CSS

条目属性(写在每行末尾的 {…} 里):

icon , Font Awesome class 对 , default按名字 / 扩展名匹配
例如 fa-solid fa-lock;格式不符构建失败
tone , 枚举 , defaultneutral
neutral info success warning danger,只给图标上色
open , 布尔 , defaulttrue
仅目录;false 表示初始收起
type , 枚举 , default自动判断
dirfile,覆盖自动判断

行语法本身:

缩进
两个空格 / 四个空格 / 制表符 / tree│ ├── └── 连线都行
- name
项目符号可省略;- * + 等价
name/
结尾斜杠表示目录;名字原样渲染,斜杠保留
[name](url)
带链接的条目
# 注释
第一个前面带空白的 # 之后的内容;\# 是字面井号
N directories, M files
tree 的统计行,自动丢弃

未知属性、未知取值、写在文件上的 open、格式错误的 {…}、退回到未打开过的缩进层级,都会构建失败,并给出围栏内的行号。

限制与常见问题

  • 只有 filetree 围栏这一种形态:没有 {.filetree} 列表标记,也没有 shortcode。
  • 注释与名字都是纯文本:写 **粗体** 会原样显示,围栏源码在任何环境里都读得通。
  • 不读取磁盘:树是手写或粘贴的静态内容,不随仓库变化。
  • 不提供搜索、多选、复制整棵树:需要逐字复制时用普通代码块。
  • 分栏宽度不持久化:拖动过的位置刷新后回到构建期算出的默认值。

10 - 公式

用 KaTeX 写行内与块级数学公式,构建期渲染完毕,读者不下载任何脚本。

公式由 KaTeX 在构建期渲染成 HTML + MathML,页面只额外加载一份本地 KaTeX 样式表,没有 JavaScript,也不请求远程数学服务。行内公式写 \(…\),块级公式写 $$…$$\[…\],另有 mathchem 两种围栏。需要 TikZ 绘图或 KaTeX 不支持的宏包时,改用预渲染的图片

最简例子

行内公式写在句子中,前后的空格与标点留在分隔符外面。

源码
共享缓冲区命中率是 \(\mathrm{hit} = \frac{H}{H + R}\),其中 \(H\) 是 `blks_hit`,\(R\) 是 `blks_read`

共享缓冲区命中率是 hit=HH+R\mathrm{hit} = \frac{H}{H + R},其中 HHblks_hitRRblks_read

块级公式

独占一段的公式用 $$ 包起来,居中显示,字号更大。\[…\] 是等价写法。

源码
一棵扇出为 \(f\)、共 \(N\) 个键的 B 树,其高度为:

$$
h = \left\lceil \log_{f} N \right\rceil
$$

一棵扇出为 ff、共 NN 个键的 B 树,其高度为:

h=logfN h = \left\lceil \log_{f} N \right\rceil

一行装不下的长公式在正文列内横向滚动,不会把版面撑宽;打印时保持静态。

math 围栏

math 围栏是块级公式的另一种写法,不依赖站点的 passthrough 配置。源码在 GitHub 上是一个普通代码块。

源码
```math
N_{\text{conn}} = \lambda \cdot \bar{t}_{\text{resp}}
```
Nconn=λtˉrespN_{\text{conn}} = \lambda \cdot \bar{t}_{\text{resp}}

上式是 Little 定律在连接池上的形式:稳态下需要的并发连接数等于到达速率乘以平均响应时间。连接池大小通常远小于客户端数量。

化学式与单位

chem 围栏使用 KaTeX 的 mhchem 扩展,正文写 \ce{…}。同一个扩展也能排物理单位。

源码
```chem
\ce{CO2 + H2O <=> H2CO3 <=> H+ + HCO3^-}
```
COX2+HX2OHX2COX3HX++HCOX3X\ce{CO2 + H2O <=> H2CO3 <=> H+ + HCO3^-}

语法见 mhchem 手册

编号公式

块级公式下面跟一行属性即成为编号公式。num 是作者书写的字符串(3-15.3),主题不自动计数;#id 不写时默认为 eq-<num>。编号显示在公式右侧,前缀「公式」按站点语言本地化。

源码
$$
\text{WAL}_{\text{day}} \approx \text{TPS} \times \bar{s}_{\text{record}} \times 86400
$$
{#eq-wal num="3-1" caption="每日 WAL 产量的估算"}

见[公式 3-1](#eq-wal):乘上保留天数就是归档盘容量的下限。
WALdayTPS×sˉrecord×86400 \text{WAL}_{\text{day}} \approx \text{TPS} \times \bar{s}_{\text{record}} \times 86400
公式 3-1 每日 WAL 产量的估算

公式 3-1:乘上保留天数就是归档盘容量的下限。

caption(纯文本)可以省略。#idcaption 必须与 num 同时出现,不存在「半编号」的公式。同一页里重复的 ID、或同一编号指向两个 ID,都会构建失败。

交叉引用

正文可以用普通链接引用编号公式,上一节即是这种写法。跨页引用、或需要自动带上「公式 N」标签时用 xref

源码
容量规划从 {{< xref eq="3-1" anchor="eq-wal" />}} 开始。

容量规划从 公式 3-1 开始。

xref 可以写在目标之前,前向引用合法。整本书的公式目录、book-equations 索引见书籍出版

eq shortcode

eq 供无法开启 passthrough 的站点使用,正文交给同一个 KaTeX 渲染器。不带参数时是一个不注册编号的块级公式;带 num 时与上一节的属性行形态等价。

源码
{{< eq >}}\sigma_{\text{idx}} = \frac{\text{rows}_{\text{matched}}}{\text{rows}_{\text{total}}}{{< /eq >}}

{{< eq num="3-2" caption="顺序扫描与索引扫描的代价平衡点" >}}
c_{\text{seq}} \cdot P = c_{\text{rand}} \cdot \sigma \cdot T
{{< /eq >}}
σidx=rowsmatchedrowstotal\sigma_{\text{idx}} = \frac{\text{rows}_{\text{matched}}}{\text{rows}_{\text{total}}}
cseqP=crandσTc_{\text{seq}} \cdot P = c_{\text{rand}} \cdot \sigma \cdot T
公式 3-2 顺序扫描与索引扫描的代价平衡点

本站已开启 passthrough,日常写作用 $$eq 用于迁移来的书稿与不能修改 hugo.yml 的场合。

站点前置配置

mathchem 围栏无需配置。$$\[…\]\(…\) 这些分隔符依赖 Goldmark 的 passthrough 扩展。Hugo 不合并主题的 markup 配置,这段必须写在站点自己的配置文件里。本站使用下面这份:

hugo.yml
markup:
  goldmark:
    parser:
      attribute:
        block: true # 编号公式的属性行需要它
    extensions:
      passthrough:
        enable: true
        delimiters:
          block: [['\[', '\]'], ['$$', '$$']]
          inline: [['\(', '\)']]

各键的完整定义见配置总览。分隔符不能与站点正文冲突:单个 $ 没有配进去,避免「$5」这样的价格被当成公式。

输出形态

输出呈现
HTML构建期渲染好的 KaTeX HTML + MathML;本页额外加载一份本地 katex.min.css,没有公式的页面不加载
打印同 HTML,静态,长公式不滚动
Markdown原样输出源码:$$ 块(连同下面的属性行)、math / chem 围栏、\(…\)eq shortcode 输出 **公式 3-2.** 说明 + 一个 $$
RSS与 Markdown 相同的静态文本

任何形态都不加载 JavaScript。

参数参考

四种写法:

\(…\) , 位置行内
由站点 passthrough 配置决定;不能带属性
$$…$$ / \[…\] , 位置块级
同上;可以跟一行属性变成编号公式
```math , 位置块级围栏
不依赖 passthrough 配置;不接受属性
```chem , 位置块级围栏
同上,正文写 \ce{…}

块级公式的属性行 {…}

num , 字符串 , default
[0-9A-Za-z.-]+;注册为编号公式,右侧显示「公式 N」
#id , 标识符 , defaulteq-<num>
[A-Za-z][A-Za-z0-9_.:-]*;锚点与交叉引用目标
caption , 纯文本 , default
编号后面的说明;需要 num

eq shortcode 的参数:

num , 字符串 , default
同上;不写就是一个不编号的普通块级公式
id , 标识符 , defaulteq-<num>
需要 num
caption , 纯文本 , default
需要 num
class , class 列表 , default
需要 num;透传给站点 CSS
正文 , TeX , default
必填,非空

TeX 写错(未知命令、括号不配对)会构建失败,报错里带 KaTeX 的信息与源码位置。

限制与常见问题

  • 分隔符由站点配置决定:$$\[…\]\(…\) 是否渲染只取决于站点 markup.goldmark 的 passthrough 扩展。front matter 里写 math: true 主题不读,缺少配置时 $$ 仍然原样显示;改用 math 围栏或 eq 可以绕开。
  • 只有 $$ 块和 eq 能编号:math 围栏不接受属性行,需要编号就换写法。
  • 编号是手写的:主题不自动计数,也不重排;调整章节顺序要自己改 num
  • 行内公式不能带属性:属性行只对块级公式有效。
  • caption 是纯文本:里面的 Markdown 不解析。

11 - Mermaid

mermaid 围栏把文本写成流程图、时序图、甘特图、类图与状态图,本地渲染、跟随深浅色、diff 友好。

mermaid 围栏把一段文本渲染成流程图、时序图、甘特图、类图、ER 图与状态图。图以源码形式存在,可以进 Git、可以 review diff、可以被搜索命中;渲染由主题自带的 Mermaid 在读者浏览器里完成,不请求外部服务。需要像素级控制的示意图画成 SVG,按图片使用。

最简例子

源码
```mermaid
flowchart LR
  内容["content/"] --> Hugo
  配置["hugo.yml"] --> Hugo
  主题["OINK 主题"] --> Hugo
  Hugo --> 站点["public/"]
```
flowchart LR
  内容["content/"] --> Hugo
  配置["hugo.yml"] --> Hugo
  主题["OINK 主题"] --> Hugo
  Hugo --> 站点["public/"]

围栏语言写 mermaid 即可,没有其它开关。主题检测到这个围栏后才把 Mermaid 运行时加入这一页,同一页里画十张图也只加载一次。

时序图

sequenceDiagram 描述参与者之间按时间发生的消息,适合说明请求链路与加载顺序。

源码
```mermaid
sequenceDiagram
  autonumber
  participant 读者 as 读者浏览器
  participant CDN as 静态托管
  participant JS as 页面脚本包
  读者->>CDN: GET /zh/docs/components/mermaid/
  CDN-->>读者: HTML(含 <pre class="mermaid">)
  读者->>CDN: GET 本页的脚本包
  CDN-->>读者: mermaid.min.js
  JS->>JS: 把围栏源码渲染成 SVG
  Note over JS: 未使用的运行时不下载
```
sequenceDiagram
  autonumber
  participant 读者 as 读者浏览器
  participant CDN as 静态托管
  participant JS as 页面脚本包
  读者->>CDN: GET /zh/docs/components/mermaid/
  CDN-->>读者: HTML(含 <pre class="mermaid">)
  读者->>CDN: GET 本页的脚本包
  CDN-->>读者: mermaid.min.js
  JS->>JS: 把围栏源码渲染成 SVG
  Note over JS: 未使用的运行时不下载

甘特图

gantt 画时间区间。下面是 PostgreSQL 各大版本从发布日算起的五年社区支持期,1825d 即五年。

源码
```mermaid
gantt
  title PostgreSQL 大版本的五年社区支持期
  dateFormat YYYY-MM-DD
  axisFormat %Y
  section PG 15
  发布于 2022-10-13 :2022-10-13, 1825d
  section PG 16
  发布于 2023-09-14 :2023-09-14, 1825d
  section PG 17
  发布于 2024-09-26 :2024-09-26, 1825d
  section PG 18
  发布于 2025-09-25 :active, 2025-09-25, 1825d
```
gantt
  title PostgreSQL 大版本的五年社区支持期
  dateFormat YYYY-MM-DD
  axisFormat %Y
  section PG 15
  发布于 2022-10-13 :2022-10-13, 1825d
  section PG 16
  发布于 2023-09-14 :2023-09-14, 1825d
  section PG 17
  发布于 2024-09-26 :2024-09-26, 1825d
  section PG 18
  发布于 2025-09-25 :active, 2025-09-25, 1825d

类图与 ER 图

classDiagram 画类型与关系,erDiagram 画实体与基数。两者都常用来解释数据模型。

源码
```mermaid
classDiagram
  class Page {
    +string Title
    +string Description
    +int Weight
    +Content()
    +OutputFormats()
  }
  class Resource {
    +string Name
    +string RelPermalink
    +Resize(spec)
  }
  class OutputFormat {
    +string Name
    +string MediaType
  }
  Page "1" --> "0..*" Resource : 页面包资源
  Page "1" --> "1..*" OutputFormat : html / print / markdown / rss
```
classDiagram
  class Page {
    +string Title
    +string Description
    +int Weight
    +Content()
    +OutputFormats()
  }
  class Resource {
    +string Name
    +string RelPermalink
    +Resize(spec)
  }
  class OutputFormat {
    +string Name
    +string MediaType
  }
  Page "1" --> "0..*" Resource : 页面包资源
  Page "1" --> "1..*" OutputFormat : html / print / markdown / rss
源码
```mermaid
erDiagram
  pg_database ||--o{ pg_namespace : "包含模式"
  pg_namespace ||--o{ pg_class : "包含关系"
  pg_class ||--o{ pg_attribute : "包含列"
  pg_class ||--o{ pg_index : "被索引"
  pg_class {
    oid oid PK
    name relname
    char relkind
  }
  pg_attribute {
    oid attrelid FK
    name attname
    smallint attnum
  }
```
erDiagram
  pg_database ||--o{ pg_namespace : "包含模式"
  pg_namespace ||--o{ pg_class : "包含关系"
  pg_class ||--o{ pg_attribute : "包含列"
  pg_class ||--o{ pg_index : "被索引"
  pg_class {
    oid oid PK
    name relname
    char relkind
  }
  pg_attribute {
    oid attrelid FK
    name attname
    smallint attnum
  }

状态图

stateDiagram-v2 画状态与迁移条件。下面是 OINK 主题一次发布依次经过的五个状态。这五个状态互不等价,本地构建通过不属于其中任何一个。

源码
```mermaid
stateDiagram-v2
  [*] --> 源码完成
  源码完成 --> 已验证 : 主题检查脚本 + 站点测试套件全绿
  已验证 --> 已发布 : 推送不可变的签名 vX.Y.Z 标签
  已发布 --> 已文档化 : 站点 go.mod 钉住该标签
  已文档化 --> 已部署 : 生产构建上线
  已部署 --> [*]
  已发布 --> 源码完成 : 发现问题只能出新补丁版本,标签不移动
```
stateDiagram-v2
  [*] --> 源码完成
  源码完成 --> 已验证 : 主题检查脚本 + 站点测试套件全绿
  已验证 --> 已发布 : 推送不可变的签名 vX.Y.Z 标签
  已发布 --> 已文档化 : 站点 go.mod 钉住该标签
  已文档化 --> 已部署 : 生产构建上线
  已部署 --> [*]
  已发布 --> 源码完成 : 发现问题只能出新补丁版本,标签不移动

单张图的标题与配置

围栏正文最前面可以写 Mermaid 自己的 YAML 头,它不是 Hugo front matter。title 给图加标题,config 覆盖这一张图的 Mermaid 配置。写死 config.theme 的图不再跟随站点深浅色。

源码
```mermaid
---
title: 只有用到的运行时才会进包
config:
  flowchart:
    curve: linear
---
flowchart TD
  页面 --> 判断{用了什么组件?}
  判断 -->|Mermaid 围栏| M[mermaid.min.js]
  判断 -->|ECharts 围栏| E[echarts.min.js]
  判断 -->|都没用| B[只有基础包]
```
---
title: 只有用到的运行时才会进包
config:
  flowchart:
    curve: linear
---
flowchart TD
  页面 --> 判断{用了什么组件?}
  判断 -->|Mermaid 围栏| M[mermaid.min.js]
  判断 -->|ECharts 围栏| E[echarts.min.js]
  判断 -->|都没用| B[只有基础包]

深浅色

页面初始化时主题读取当前配色模式:深色模式下用 Mermaid 的 dark 主题,浅色模式下用站点配置的主题。Mermaid 不支持重新初始化,读者切换配色时会 重载整个页面,图的配色随之更新。

因此不要把 Mermaid 图放进需要保留输入状态的页面,例如带表单的页面。

站点级默认写在 hugo.yml 里,键名小写,主题按 Mermaid 的默认配置匹配回正确的大小写:

hugo.yml
params:
  mermaid:
    theme: neutral
    flowchart:
      diagrampadding: 6

完整键表见配置总览,可用值以 Mermaid 配置文档为准。

放进标签页与步骤

mermaid 围栏没有 tab 属性,相邻围栏标签页只对普通代码围栏生效。并排比较两张图用 tabs shortcode。

源码
{{< tabs >}}
{{< tab label="按数据流看" >}}
```mermaid
flowchart LR
  Markdown --> Goldmark --> 渲染钩子 --> HTML
```
{{< /tab >}}
{{< tab label="按输出形态看" >}}
```mermaid
flowchart LR
  页面 --> HTML
  页面 --> 打印
  页面 --> Markdown
  页面 --> RSS
```
{{< /tab >}}
{{< /tabs >}}
flowchart LR
  Markdown --> Goldmark --> 渲染钩子 --> HTML
flowchart LR
  页面 --> HTML
  页面 --> 打印
  页面 --> Markdown
  页面 --> RSS

{{% steps %}} 里的每一步是页面级 Markdown,其中可以写 mermaid 围栏,用法见步骤

输出形态

输出呈现
HTML<pre class="mermaid"> + 本地 Mermaid 运行时,浏览器画成 SVG
打印与 HTML 相同:打印视图同样加载运行时,图会画出来
Markdown原样保留 mermaid 围栏与它的源码
RSS输出 <pre class="mermaid"> 包着的图表源码,订阅端看到的是文本

参数参考

围栏属性:没有。mermaid 围栏不读属性行,写 {height=…}{class=…} 之类既不生效也不报错;尺寸由图自身与容器宽度决定。

站点参数(hugo.yml):

params.mermaid , map , default未设置
整个映射按 Mermaid 的 initialize() 配置传入;键名写小写,主题按 Mermaid 默认配置匹配回正确大小写
params.mermaid.theme , string , defaultMermaid 默认
浅色模式下的主题;深色模式下被强制为 dark

单张图的配置写在围栏正文最前面的 YAML 头里(titleconfig),属于 Mermaid 语法,不是主题参数。

限制与常见问题

  • 切换深浅色会重载页面:Mermaid 不支持重新初始化,主题在渲染正确与不刷新之间选择了前者。
  • 图不能编号、不能缩放:Mermaid 输出的是内联 SVG,不是 <img>{#id num=} 编号与图片缩放都不适用;需要编号时导出成图片,按图片的编号写法使用。
  • 围栏属性无效:宽度在图里控制(flowchart 的方向、classDiagram 的布局),或者用 CSS。
  • 语法错误只在浏览器里可见:Hugo 不解析 Mermaid 语法,写错的图在页面上显示 Mermaid 的报错框,构建照样通过,发布前要在浏览器里确认。
  • RSS 订阅者只能看到源码:结论要写在正文里,不要只画在图上。
  • PlantUML — UML 更全,但需要一个渲染服务
  • 思维导图 — 大纲式的层级图
  • ECharts — 有数值的统计图
  • 图片 — 手绘 SVG、需要编号与缩放的图

12 - PlantUML

plantuml 围栏写时序图、类图、组件图、活动图与用例图;渲染必须由你自己配置一个 PlantUML 服务。

plantuml 围栏里写 PlantUML 源码,浏览器把源码压缩编码后拼在一个 PlantUML 服务的 URL 后面,换回一张 SVG。适合需要完整 UML 表达力的时序图、类图、组件图、活动图与用例图。渲染依赖一个渲染服务:主题不提供默认端点,enable: true 却没给 svg_image_url 会让构建失败;没有可用服务时改用 Mermaid

本页只给源码,不放渲染结果

PlantUML 要连你自己的服务,本站不假设读者有哪个端点可用。当前主题版本的 plantuml 围栏还会把 <>&" 二次转义,带箭头或引号的源码送到端点后返回 Syntax Error? 图(见限制与常见问题)。下面每段源码本身都是正确的 PlantUML。

图会离开读者的浏览器

编码后的图表源码作为 URL 发给你配置的端点。不要在 PlantUML 图里写口令、内网主机名或客户名称。内网站点自建端点,或改用预渲染的图片

最简例子

时序图是 PlantUML 最常用的一类:participant 声明参与者,-> 是同步消息,--> 是返回。

源码
```plantuml
@startuml
actor 读者
participant 浏览器
participant 渲染端点 as Server
读者 -> 浏览器 : 打开页面
浏览器 -> Server : GET /plantuml/svg/{压缩编码后的源码}
Server --> 浏览器 : SVG
浏览器 -> 浏览器 : 用一个 img 元素替换掉围栏
@enduml
```

画出来是四条泳道、四条消息的一张时序图:读者打开页面 → 浏览器带着编码后的源码请求端点 → 端点返回 SVG → 运行时把围栏替换成图片。

类图

class 写成员,"1" -- "0..*" 写关系基数,用来解释数据模型。

源码
```plantuml
@startuml
class Publication {
  + pubname : name
  + puballtables : bool
  + pubinsert / pubupdate / pubdelete : bool
}
class Subscription {
  + subname : name
  + subconninfo : text
  + subslotname : name
}
class ReplicationSlot {
  + slot_name : name
  + plugin : name
  + confirmed_flush_lsn : pg_lsn
}
Publication "1" -- "0..*" Subscription : 被订阅
Subscription "1" -- "1" ReplicationSlot : 绑定
@enduml
```

三个方框各带一列字段,两条带基数标注的连线:一个发布可以被多个订阅使用,每个订阅绑定一个复制槽。

组件图

package 圈出部署单元,[组件] 是方块,--> 是依赖方向。

源码
```plantuml
@startuml
package "监控节点" {
  [Grafana] as grafana
  [Prometheus] as prom
  [Alertmanager] as alert
}
package "数据库节点" {
  [node_exporter] as node
  [pg_exporter] as pgexp
  [PostgreSQL] as pg
}
pg --> pgexp : 查询统计视图
node --> prom : /metrics
pgexp --> prom : /metrics
prom --> alert : 规则命中
grafana --> prom : PromQL
@enduml
```

两个虚线框,框里各三个组件方块,五条带标注的箭头串起采集链路。

活动图

start / stopif … then … else … endif 画带分支的流程。这类图不含箭头字符,是当前版本里能正常渲染的一类。

源码
```plantuml
@startuml
start
:写 content/docs/**/*.zh.md;
:补英文对等页,抄渲染出来的标题 ID;
if (hugo --panicOnWarning 通过?) then (是)
  :npm test;
else (否)
  :按报错的文件名与行号修正;
  stop
endif
if (测试全绿?) then (是)
  :提交 PR;
  stop
else (否)
  :返回修改;
  stop
endif
@enduml
```

一条竖向流程线,两个菱形判断各分出「是 / 否」两支,四个终点。

用例图

actor 是小人,(用例) 是椭圆,rectangle 圈出系统边界,适合放在文档的「读者是谁」一节。

源码
```plantuml
@startuml
left to right direction
actor 读者 as reader
actor 作者 as author
actor 维护者 as maintainer
rectangle 文档站 {
  reader --> (全文搜索)
  reader --> (切换中英文)
  reader --> (导出打印版)
  author --> (新增页面)
  author --> (本地预览)
  maintainer --> (升级主题版本)
  maintainer --> (发布上线)
}
@enduml
```

左边三个小人,右边一个方框里七个椭圆,连线表示谁能做什么。

深色模式下的配色

服务端不知道站点的配色模式,渲染出来的 SVG 底色是固定的白色。skinparam backgroundColor transparent 去掉底色,图落在页面背景上。线条与文字设成中性色后,两种模式下都可读。

源码
```plantuml
@startuml
skinparam backgroundColor transparent
skinparam defaultFontName sans-serif
skinparam ArrowColor #7C7C7C
skinparam ActivityBorderColor #7C7C7C
skinparam ActivityBackgroundColor #B0BEC522
start
:hugo mod get -u github.com/pgsty/oink;
:hugo --gc --minify;
:上传 public/;
stop
@enduml
```

PlantUML 的 !theme 指令(例如 !theme plain)也可用,主题包由服务端提供,自建端点需要确认已安装。

渲染服务

围栏本身没有开关,能否渲染取决于站点配置:

hugo.yml
params:
  plantuml:
    enable: true
    svg_image_url: https://plantuml.internal.example/plantuml/svg/
    svg: false
  • enable: true 却没写 svg_image_url → 构建报错 params.plantuml.enable requires an explicit params.plantuml.svg_image_url。主题不代替站点选择公共服务。
  • 自建可以用官方镜像 plantuml/plantuml-serversvg_image_url 指向它的 /svg/ 路径,结尾的斜杠不能省略,编码后的源码拼在它后面。
  • 端点的跨域策略、站点 CSP 的 img-srcsvg: true 时还有 connect-src)都要放行;子路径部署时写绝对 URL。

这几个键的完整定义在配置总览

输出形态

输出呈现
HTML先输出 <pre><code class="language-plantuml"> 源码,启用后由运行时替换成 <img>svg: true 时是 <svg data-src>
打印与 HTML 相同:打印视图同样加载运行时并请求端点
Markdown原样保留 plantuml 围栏与它的源码
RSS只有围栏源码,订阅端看到的是文本

未启用、或运行时没有加载时,页面上留下的是一段可读的源码块,不会出现坏图标。

参数参考

围栏属性:没有。plantuml 围栏不读属性行;它也不走 OINK 的代码块外壳,titlecopy、行号这些代码块参数在这里都无效。

站点参数(hugo.yml):

params.plantuml.enable , bool , defaultfalse
关闭时围栏保持为代码块,不加载运行时
params.plantuml.svg_image_url , string , default
渲染端点,编码后的源码直接拼在它后面;enable: true 时必填,否则构建失败
params.plantuml.svg , bool , defaultfalse
false<img src>true<svg data-src> 并额外加载外部 SVG 加载器,SVG 内容进 DOM、可被 CSS 影响

主题只读这三个键,其它键写了没有效果。

限制与常见问题

  • <>&" 会被二次转义:当前主题版本的 plantuml 围栏对内容多做了一次转义,页面上留下 --&gt;&#34; 这样的字面文本,端点收到后返回一张 Syntax Error? 图。带箭头的图(时序、组件、用例、状态)目前渲染不出来,只有活动图这类不含这些字符的能正常渲染。修复前请改用 Mermaid 或预渲染的图片
  • 必须有服务:主题不提供、也不默认任何公共端点。
  • 图表源码会离开浏览器:涉密内容不要写进 PlantUML 围栏。
  • 不跟随深浅色:服务端不知道读者的配色模式,只能靠 skinparam 自己调。
  • 不能编号、不能缩放:运行时插入的 <img> 不经过图片渲染钩子,{#id num=} 与图片缩放都用不上。
  • Mermaid — 不需要服务、跟随深浅色,日常首选
  • Draw.io — 同样需要自建服务的另一个图表集成
  • 图片 — 预渲染 SVG,可编号、可缩放、无外部依赖
  • 配置总览params.plantuml.* 的完整定义

13 - 思维导图

markmap 围栏把一段 Markdown 大纲变成可展开、可缩放的思维导图,源码本身就是能读的提纲。

markmap 围栏的正文是一段普通的 Markdown 大纲:标题与列表决定层级,浏览器把它画成一棵可展开、可折叠的树。适合把「这一节讲了什么」的层级一次呈现。节点之间有方向、有条件的流程用 Mermaid

最简例子

源码
```markmap
# OINK
## 本地优先
- 运行时全部随主题分发
- 不依赖任何 CDN
## Markdown 原生
- 组件是围栏和属性行
- 不写 shortcode 也能用
## 四态输出
- HTML
- 打印
- Markdown
- RSS
```
# OINK
## 本地优先
- 运行时全部随主题分发
- 不依赖任何 CDN
## Markdown 原生
- 组件是围栏和属性行
- 不写 shortcode 也能用
## 四态输出
- HTML
- 打印
- Markdown
- RSS

一级标题是根节点,其余标题与列表项按缩进挂在它下面。点击节点上的圆点折叠或展开这一支,鼠标滚轮缩放,拖动平移。右下角一排工具按钮提供缩放、适应窗口与下载 SVG。

多层级

层级越深字号越小,画布自动排布。下面是本站文档的六个栏目与它们的页数。

源码
```markmap
# OINK 文档
## 简介(4 页)
### 它是什么
### 功能一览
### 案例
### 许可
## 快速上手(3 页)
### Fork 本站
### 目录结构
### 从零开始
## 创作内容(8 页)
### 组织内容
### 编写页面
### 页面参数
### 博客
### 书籍
### 发布与下载
### OpenAPI
## 组件(22 页)
### 提示块 / 标签页 / 步骤 / 卡片
### 图片 / 画廊 / 表格 / 参数表
### 图表:Mermaid / PlantUML / 思维导图 / ECharts
## 定制站点(15 页)
### 品牌 / 导航 / 搜索 / 多语言
### 首页 / 版本 / 分类 / 打印
## 维护管理(7 页)
### 预览 / 部署 / 升级
### 评论 / 统计 / 排错
```
# OINK 文档
## 简介(4 页)
### 它是什么
### 功能一览
### 案例
### 许可
## 快速上手(3 页)
### Fork 本站
### 目录结构
### 从零开始
## 创作内容(8 页)
### 组织内容
### 编写页面
### 页面参数
### 博客
### 书籍
### 发布与下载
### OpenAPI
## 组件(22 页)
### 提示块 / 标签页 / 步骤 / 卡片
### 图片 / 画廊 / 表格 / 参数表
### 图表:Mermaid / PlantUML / 思维导图 / ECharts
## 定制站点(15 页)
### 品牌 / 导航 / 搜索 / 多语言
### 首页 / 版本 / 分类 / 打印
## 维护管理(7 页)
### 预览 / 部署 / 升级
### 评论 / 统计 / 排错

链接、代码与强调

节点里可以写行内 Markdown:链接可点击,行内代码用等宽字体,粗体与斜体照常生效。

源码
```markmap
# 日常命令
## 预览
- `hugo server` — 打开 [localhost:1313](http://localhost:1313/)
- `hugo server -D` — **连草稿一起**预览
## 构建
- `hugo --printPathWarnings --panicOnWarning`
- `hugo --gc --minify` — 发布用
## 主题
- `hugo mod get -u github.com/pgsty/oink`
- [主题仓库](https://github.com/pgsty/oink)
- [本站源码](https://github.com/pgsty/oink.pgsty.com)
```
# 日常命令
## 预览
- `hugo server` — 打开 [localhost:1313](http://localhost:1313/)
- `hugo server -D` — **连草稿一起**预览
## 构建
- `hugo --printPathWarnings --panicOnWarning`
- `hugo --gc --minify` — 发布用
## 主题
- `hugo mod get -u github.com/pgsty/oink`
- [主题仓库](https://github.com/pgsty/oink)
- [本站源码](https://github.com/pgsty/oink.pgsty.com)

节点里的公式

Markmap 运行时带了一份本地 KaTeX,节点里的 $…$ 会被渲染成公式。

源码
```markmap
# 常看的几个 PostgreSQL 指标
## 缓存命中率
- $\frac{blks\_hit}{blks\_hit + blks\_read}$
- 低于 0.99 时检查 shared_buffers
## 复制延迟
- $lsn_{primary} - lsn_{replica}$
## 事务吞吐
- $TPS = \frac{\Delta xact\_commit}{\Delta t}$
```
# 常看的几个 PostgreSQL 指标
## 缓存命中率
- $\frac{blks\_hit}{blks\_hit + blks\_read}$
- 低于 0.99 时检查 shared_buffers
## 复制延迟
- $lsn_{primary} - lsn_{replica}$
## 事务吞吐
- $TPS = \frac{\Delta xact\_commit}{\Delta t}$

控制初始展开层数

围栏正文最前面可以写一段 Markmap 自己的 YAML 头,它不是 Hugo front matter。initialExpandLevel 只展开前几层,其余分支由读者点开。colorFreezeLevel 指定从第几层起同一分支使用同一种颜色。

源码
```markmap
---
markmap:
  initialExpandLevel: 2
  colorFreezeLevel: 2
---

# 主题仓库的检查脚本
## 源码级契约
### check-i18n.py
### check-taxonomy.py
### check-font-tokens.py
## 输出级检查
### check-output.py
### check-goldens.py
### check-code-blocks.py
### check-content-primitives.py
### check-media-primitives.py
## 浏览器运行时
### node --test tests/js/**/*.test.js
```
---
markmap:
  initialExpandLevel: 2
  colorFreezeLevel: 2
---

# 主题仓库的检查脚本
## 源码级契约
### check-i18n.py
### check-taxonomy.py
### check-font-tokens.py
## 输出级检查
### check-output.py
### check-goldens.py
### check-code-blocks.py
### check-content-primitives.py
### check-media-primitives.py
## 浏览器运行时
### node --test tests/js/**/*.test.js

折进折叠块

每张导图固定 300 像素高,正文里连着放三张会占掉大量版面。把全景图折进 > [!DETAILS],由读者自己展开。折叠块里的每一行都要以 > 开头,围栏也不例外。

源码
> [!DETAILS] 主题仓库长什么样
> ```markmap
> # pgsty/oink
> ## layouts/
> - baseof.html 与各类型的壳
> - _partials/shell/
> - _markup/ 渲染钩子
> - _shortcodes/
> ## assets/
> - scss/ 令牌与组件样式
> - js/ 浏览器运行时
> - third_party/ 随主题分发的库
> ## i18n/
> - 32 个语言文件,键完全对齐
> ## docs/
> - 冻结契约文档
> ```
主题仓库长什么样
# pgsty/oink
## layouts/
- baseof.html 与各类型的壳
- _partials/shell/
- _markup/ 渲染钩子
- _shortcodes/
## assets/
- scss/ 令牌与组件样式
- js/ 浏览器运行时
- third_party/ 随主题分发的库
## i18n/
- 32 个语言文件,键完全对齐
## docs/
- 冻结契约文档

输出形态

输出呈现
HTML先输出 <pre><code class="language-markmap">,运行时把它换成 <div class="markmap"> 并画出 SVG
打印与 HTML 相同:打印视图同样加载运行时
Markdown原样保留 markmap 围栏与它的大纲源码
RSS只有大纲源码,订阅端看到的是一段可读的提纲

大纲本身就是内容:拿不到 JavaScript 的地方读到的仍是完整层级。

参数参考

围栏属性:没有。markmap 围栏不读属性行,高度由主题固定为 300px(.markmap > svg),宽度撑满正文栏。

站点参数(hugo.yml):

params.markmap , bool , defaultfalse
关闭时围栏保持为代码块,不加载任何运行时

键的完整定义见配置总览。每张图的行为写在围栏正文最前面的 markmap: YAML 头里(initialExpandLevelcolorFreezeLevelmaxWidth 等),属于 Markmap 语法,可用键以 Markmap 文档为准。

限制与常见问题

  • 输出是固定 300px 高的内联 SVG:高度由一条 .markmap > svg 规则统一,围栏改不了,层级太多时用 initialExpandLevel 收起或拆成两张图;内联 SVG 也不适用 {#id num=} 编号与图片缩放。
  • 不跟随深浅色:连线颜色由 Markmap 自己的调色板决定,两种模式下都需要检查对比度。
  • 没开 params.markmap 就只是代码块:不用这个组件的站点不加载任何运行时。
  • 右下角工具栏里的「下载 SVG」是浏览器行为,导出的是当前展开状态的快照。
  • 大纲里避开 <>&":当前主题版本的 markmap 围栏会把这几个字符二次转义,节点上会出现 &gt;&#34; 这样的字面文本;写链接用 [文字](URL),不要用尖括号自动链接。

14 - Draw.io

把带着可编辑副本的 .drawio.svg 当普通图片放进页面,读者鼠标移上去就能点开 Draw.io 编辑器改图。

Draw.io 集成没有围栏也没有 shortcode,用的是普通 Markdown 图片。Draw.io 导出时勾上「Include a copy of my diagram」,SVG 或 PNG 里会带一份 mxfile 源码;主题的运行时识别这份副本后,给图片加一个编辑按钮。适合需要读者取走修改的图;只用于展示的图按普通图片处理。

最简例子

写法与普通图片相同,文件名不受限制,.drawio.svg 只是惯例。

源码
![Hugo 构建流水线:content 目录经 Hugo 产出 public 目录](pipeline.drawio.svg)
{width="620" height="140"}
Hugo 构建流水线:content 目录经 Hugo 产出 public 目录

这张图嵌着一份 mxfile 副本,因此被包进了 .drawio 容器。鼠标移到图上时,右下角出现一个铅笔按钮;点击后在当前页面盖一层全屏 iframe,加载站点配置的编辑器。

副本检测

运行时的判断依据只有一条:文件内容里有没有 mxfile 字样,与文件名无关。下面这张同样是 SVG、同样是块级图片,但它是手写的,没有副本,也就没有按钮。

源码
![文档外壳的三栏:侧栏、正文、目录](plain-shell.svg)
{width="620" height="140"}
文档外壳的三栏:侧栏、正文、目录

带图注

Draw.io 图片走的是普通图片渲染钩子,图片的属性照常可用。加 caption 得到带图注的 figure,编辑按钮仍然出现在图上。

源码
![Hugo 构建流水线](pipeline.drawio.svg)
{caption="内容、配置与主题模板汇进 Hugo,产出 public/ 目录" width="620" height="140"}
Hugo 构建流水线
内容、配置与主题模板汇进 Hugo,产出 public/ 目录

编号成书里的图

{#id num=…} 得到一张可交叉引用的编号图,与别的图片一样能被 xref 引用、进入图目录。

源码
![Hugo 构建流水线](pipeline.drawio.svg)
{#fig_pipeline num="1-1" caption="从内容到静态站点" width="620" height="140"}
Hugo 构建流水线
图 1-1 从内容到静态站点

编号与交叉引用的完整规则见书籍出版

SVG 还是 PNG

两种都识别。Draw.io 导出 PNG 时同样能带上副本,存在 PNG 的文本块里,运行时的判断逻辑相同。

源码
![Hugo 构建流水线(PNG 导出)](pipeline.drawio.png)
{width="620" height="140"}
Hugo 构建流水线(PNG 导出)

文档里优先用 SVG:缩放不失真,文字是真实文本(可被搜索、可被读屏器读取),改动的 diff 也读得懂。图特别复杂、或目标平台不支持 SVG 时用 PNG。只有 PNG 能走 Hugo 的图片处理;SVG 上的处理操作会告警并保留原图,严格构建会拒绝该告警。

编辑流程

按钮依次做三件事。

盖一层遮罩

页面上插入一个全屏的 div.drawioframe,里面是一个 iframe,地址是配置的 drawio_server 加上一串固定参数(embed=1&ui=atlas&proto=json&saveAndEdit=1&noSaveBtn=1)。

把图送进编辑器

编辑器就绪后,运行时把这张图片的内容(含 mxfile 副本)作为 data URL 发进 iframe。这一步不经过你的服务器。

保存与回写

在编辑器里点保存,运行时让编辑器按原格式(SVG 或 PNG)导出,由浏览器下载成同名文件。运行时不写回仓库:把下载到的文件覆盖 content/ 里那一份,再自行提交。

编辑按钮供读者取走图去改,不是站点的在线编辑功能。

编辑器地址

hugo.yml
params:
  drawio:
    enable: true
    drawio_server: https://drawio.internal.example/
  • enable: true 却没写 drawio_server 时会告警并关闭编辑;严格构建会因该告警失败。主题不代替站点选择公共服务。
  • 编辑过程必须留在组织内部时,部署一份自托管编辑器,把地址指向它。
  • 公共端点 https://embed.diagrams.net/ 可用,读者的图会进入第三方页面。

这两个键的完整定义在配置总览

输出形态

输出呈现
HTML普通 <img>(或 <figure>);启用后运行时把带副本的图包进 <div class="drawio"> 并加按钮
打印图片照常打印;按钮默认隐藏(只在悬停时出现),打印上不会有它
Markdown普通 Markdown 图片语法
RSS普通 <img>,绝对 URL,没有按钮

图片本身在四态里都在,编辑按钮是增量能力。

参数参考

没有专属的围栏或 shortcode 参数。图片属性行沿用图片那一套(caption width height link #id num command options)。

站点参数(hugo.yml):

params.drawio.enable , bool , defaultfalse
关闭时不加载任何脚本,图片就是图片
params.drawio.drawio_server , string , default
编辑器地址;enable: true 时必填

限制与常见问题

  • 运行时只在渲染内容含 .svg.png 候选图的页面加载;同一 URL 的图片合并检查,只读取一次以查找 mxfile
  • 导出时忘了勾「Include a copy of my diagram」,图就只是一张图,没有按钮。
  • 编辑依赖编辑器,且不写回仓库:离线环境里图片正常显示,按钮点了没有反应;编辑器保存等于浏览器下载,替换文件与提交都要手动做。
  • 按钮只在悬停时出现:触屏设备上没有 hover,读者不容易发现它,不要把可编辑当成关键功能来讲。
  • 配色不跟随深浅色:导出的 SVG 颜色是固定的;把填充设成 none、线条与文字用中性灰,两种模式下都能看(本页这两张图就是这么做的)。
  • 图片 — 图注、编号、尺寸、缩放的完整规则
  • PlantUML — 另一个需要自建服务的图表集成
  • Mermaid — 不需要任何服务的文本画图
  • 配置总览params.drawio.* 的完整定义

15 - ECharts

echarts 围栏里用 YAML 或 JSON 写图表选项,Hugo 构建期校验,浏览器用本地 ECharts 画出跟随深浅色的统计图。

echarts 围栏的正文是一段 YAML 或 JSON 的 ECharts 选项对象,不是代码。适用于需要坐标轴、序列与图例的定量图表;只表达关系与流程时用 Mermaid,只表达顺序与层级时用 Infographic。Hugo 在构建期解析选项,解析失败则构建失败;浏览器用随主题分发的 ECharts 绘图,只有用到它的页面加载运行时。

最简例子

一个柱状图只需要三段:xAxisyAxisseries。下面是本站文档六个栏目各有多少页。

源码
```echarts {height="320px"}
tooltip:
  trigger: axis
xAxis:
  type: category
  data: [简介, 快速上手, 创作内容, 组件, 定制站点, 维护管理]
yAxis:
  type: value
  name: 页数
series:
  - name: 页数
    type: bar
    data: [4, 3, 8, 22, 15, 7]
```
tooltip:
  trigger: axis
xAxis:
  type: category
  data: [简介, 快速上手, 创作内容, 组件, 定制站点, 维护管理]
yAxis:
  type: value
  name: 页数
series:
  - name: 页数
    type: bar
    data: [4, 3, 8, 22, 15, 7]

两种格式都接受,YAML 不需要引号与逗号,写起来更短。缩进写错、正文解析成数组而不是映射,构建在这一行失败,不会输出一张空白图。

多序列折线

series 是数组,多一项就是多一条线;legend 让读者单独隐藏其中一条。下面是 PostgreSQL 各大版本的发布年份,以及按社区五年支持策略推算的终止年份。

源码
```echarts {height="360px"}
tooltip:
  trigger: axis
legend:
  data: [发布年份, 支持终止]
grid:
  left: 56
  right: 24
  top: 48
  bottom: 40
xAxis:
  type: category
  name: 大版本
  data: ["9.6", "10", "11", "12", "13", "14", "15", "16", "17", "18"]
yAxis:
  type: value
  min: 2015
  max: 2031
  name: 年份
series:
  - name: 发布年份
    type: line
    smooth: false
    data: [2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025]
  - name: 支持终止
    type: line
    lineStyle:
      type: dashed
    data: [2021, 2022, 2023, 2024, 2025, 2026, 2027, 2028, 2029, 2030]
```
tooltip:
  trigger: axis
legend:
  data: [发布年份, 支持终止]
grid:
  left: 56
  right: 24
  top: 48
  bottom: 40
xAxis:
  type: category
  name: 大版本
  data: ["9.6", "10", "11", "12", "13", "14", "15", "16", "17", "18"]
yAxis:
  type: value
  min: 2015
  max: 2031
  name: 年份
series:
  - name: 发布年份
    type: line
    smooth: false
    data: [2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025]
  - name: 支持终止
    type: line
    lineStyle:
      type: dashed
    data: [2021, 2022, 2023, 2024, 2025, 2026, 2027, 2028, 2029, 2030]

版本号要加引号:YAML 里不带引号的 10 是数字,9.6 也是;作为分类轴的标签它们必须是字符串。

饼图与环形图

radius 给两个值就是环形图。下面是 OINK 的 29 个 shortcode 按用途的构成。

源码
```echarts {height="340px"}
tooltip:
  trigger: item
  formatter: "{b}:{c} 个({d}%)"
legend:
  bottom: 0
series:
  - type: pie
    radius: [42%, 70%]
    itemStyle:
      borderRadius: 6
      borderWidth: 2
    label:
      formatter: "{b} {c}"
    data:
      - { value: 14, name: 核心组件 }
      - { value: 10, name: Book 编号与索引 }
      - { value: 3, name: 发布与下载 }
      - { value: 2, name: OpenAPI }
```
tooltip:
  trigger: item
  formatter: "{b}:{c} 个({d}%)"
legend:
  bottom: 0
series:
  - type: pie
    radius: [42%, 70%]
    itemStyle:
      borderRadius: 6
      borderWidth: 2
    label:
      formatter: "{b} {c}"
    data:
      - { value: 14, name: 核心组件 }
      - { value: 10, name: Book 编号与索引 }
      - { value: 3, name: 发布与下载 }
      - { value: 2, name: OpenAPI }

{b} {c} {d} 是 ECharts 的模板占位符(名称 / 数值 / 百分比),写在字符串里即可,不需要函数。

高度与通栏

height 默认 400px,接受 px rem em vh vw %full=true 去掉正文的宽度限制,让图铺满内容区。适用于数据点多、标签长的图。

源码
```echarts {height="260px" full=true}
tooltip:
  trigger: axis
grid:
  left: 40
  right: 16
  top: 24
  bottom: 32
xAxis:
  type: category
  data: [i18n, 分类法, 字体令牌, 内容契约, 导航, 运行时, 侧栏图标, 搜索, 动作, 命令面板, 双语文档, 阅读, 发布物, 下载, Landing, Book, 迁移, 键盘, 页尾, 输出, 金样本]
yAxis:
  type: value
  name: 脚本数
series:
  - type: bar
    data: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
```
tooltip:
  trigger: axis
grid:
  left: 40
  right: 16
  top: 24
  bottom: 32
xAxis:
  type: category
  data: [i18n, 分类法, 字体令牌, 内容契约, 导航, 运行时, 侧栏图标, 搜索, 动作, 命令面板, 双语文档, 阅读, 发布物, 下载, Landing, Book, 迁移, 键盘, 页尾, 输出, 金样本]
yAxis:
  type: value
  name: 脚本数
series:
  - type: bar
    data: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]

无效的高度(36036pt)会让构建失败,不会退回默认值。

深浅色

不写 theme 时,图按读者当前的配色模式初始化;切换配色时图原地重绘,不刷新页面。容器尺寸变化时自动 resize。把本页切到深色,上面每张图的底色与文字随之改变。

写定 theme 则固定配色,两种模式下都是同一套:

源码
```echarts {height="240px" theme="dark"}
xAxis:
  type: category
  data: [HTML, 打印, Markdown, RSS]
yAxis:
  type: value
series:
  - type: bar
    data: [1, 1, 1, 1]
```
xAxis:
  type: category
  data: [HTML, 打印, Markdown, RSS]
yAxis:
  type: value
series:
  - type: bar
    data: [1, 1, 1, 1]

运行时内置的只有 dark;其它 ECharts 主题要先用 echarts.registerTheme() 注册才能在这里引用。没有品牌要求时不写 theme,让图跟随站点配色。

回调:$fn:

围栏是数据,不能带 JavaScript。某个选项需要函数时(提示框格式化、数据驱动的颜色),在选项里写字符串 "$fn:名字",再把这个名字注册到 window.OinkEchartsFunctions

源码
<script>
  window.OinkEchartsFunctions = window.OinkEchartsFunctions || {};
  window.OinkEchartsFunctions.pageShare = function (params) {
    var p = params[0];
    return p.name + ':' + p.value + ' 页,占全站 ' + Math.round((p.value / 59) * 100) + '%';
  };
</script>

```echarts {height="300px"}
tooltip:
  trigger: axis
  formatter: "$fn:pageShare"
xAxis:
  type: category
  data: [简介, 快速上手, 创作内容, 组件, 定制站点, 维护管理]
yAxis:
  type: value
series:
  - type: bar
    data: [4, 3, 8, 22, 15, 7]
```
tooltip:
  trigger: axis
  formatter: "$fn:pageShare"
xAxis:
  type: category
  data: [简介, 快速上手, 创作内容, 组件, 定制站点, 维护管理]
yAxis:
  type: value
series:
  - type: bar
    data: [4, 3, 8, 22, 15, 7]

鼠标悬停在任意一根柱子上,提示框里是该函数拼出的句子。名字未注册时该选项解析为 undefined,图按未设置该项绘制,构建与运行都不报错。脚本与围栏放在同一页的相邻位置,便于一起改动。

这段脚本属于站点代码,按代码审查对待。字符串模板({b} {c} {d})能表达的格式不写成函数。

数据位置

围栏正文是字面量。Hugo 不在其中展开 shortcode、front matter 变量或 data/ 目录里的文件,数字写在围栏里。代价是数据不能共享,收益是图表源码与数据一起进入 Git,diff 能看出改动了哪个数值。

数据经常变动(版本矩阵、发布物清单)时不做成图:改用表格,或发布与下载页中由 data/ 驱动的组件。

输出形态

输出呈现
HTML<div class="td-echarts"> 里一个画布容器加一段 application/json 选项,本地 ECharts 画图
打印不画图,输出 <pre class="td-echarts-source"> 包着的围栏源码
Markdown原样保留 echarts 围栏与选项源码
RSS与打印相同,只有源码

图上的结论要在正文里写一遍:打印与 RSS 输出里没有图。

参数参考

围栏属性行(```echarts {…}):

height , CSS 长度 , default400px
只接受非负数字加 px rem em vh vw %;其它写法构建失败
theme , string , default未设置
固定使用某个 ECharts 主题,从此不再跟随站点配色;内置只有 dark
full , bool , defaultfalse
true 去掉正文宽度限制,图铺满内容区
class , 空格分隔的 class , default
透传给容器,交给站点 CSS

styleon* 与其它未知属性都会让构建失败。围栏正文必须能解析成一个 YAML/JSON 映射,解析失败或解析成数组同样失败。选项键本身是 ECharts 的,以官方选项手册为准。

没有站点级参数:ECharts 不需要在 hugo.yml 里开关,用到时才加载。

限制与常见问题

  • 围栏里不能写 JavaScript:需要函数时通过 $fn: 桥接,未注册的名字解析为 undefined,没有报错。
  • 围栏不读外部数据:data/ 目录、front matter 与 shortcode 都引用不到,数字写在围栏里。
  • 打印与 RSS 里只有源码,结论要写进正文。
  • YAML 的类型转换:分类轴上的 109.6onyes 会被解析成数字或布尔值,需要引号。
  • 颜色不是唯一的区分手段:多序列图同时区分线型或标记形状,两种配色模式下都要检查图例对比度。
  • Infographic — 表达结构与顺序的信息图,不是统计图
  • 表格 — 数据少、需要精确读数时用表格
  • Mermaid — 关系图与流程图
  • 代码块 — 围栏属性行的通用规则

16 - Infographic

infographic 围栏挑一个 AntV 模板,把标题与条目渲染成流程、时间线、漏斗、网格或层级信息图。

infographic 围栏挑一个 AntV 模板,把「标题 + 一串条目」渲染成信息图。适用于表达顺序、层级与对比这类结构。需要坐标轴与数值精度时用 ECharts,需要条件分支的流程时用 Mermaid。围栏正文是数据,在 GitHub 上仍是一段可读的文本。

最简例子

第一行是 infographic 模板名,其后是一个 data 块:title 是标题,items 下面每个条目至少要有 label

源码
```infographic
infographic list-row-simple-horizontal-arrow
data
  title 一次文档改动的三步
  items
    - label 写
      desc 先写中文 .zh.md
    - label 校
      desc 构建零告警,例子真渲染
    - label 发
      desc 补英文对等页,提交 PR
```
infographic list-row-simple-horizontal-arrow
data
  title 一次文档改动的三步
  items
    - label 写
      desc 先写中文 .zh.md
    - label 校
      desc 构建零告警,例子真渲染
    - label 发
      desc 补英文对等页,提交 PR

缩进决定结构,两个空格一级。标签要短,说明放 desc

时间线

sequence-timeline-* 系列把条目排成一条时间轴,label 是时间点,desc 是事件。

源码
```infographic {height="420px"}
infographic sequence-timeline-simple
data
  title PostgreSQL 近五个大版本
  items
    - label 2021
      desc 14:并行查询与逻辑复制的一轮改进
    - label 2022
      desc 15:MERGE 语句
    - label 2023
      desc 16:逻辑复制可以从备库进行
    - label 2024
      desc 17:增量备份与 JSON_TABLE
    - label 2025
      desc 18:异步 IO 子系统
```
infographic sequence-timeline-simple
data
  title PostgreSQL 近五个大版本
  items
    - label 2021
      desc 14:并行查询与逻辑复制的一轮改进
    - label 2022
      desc 15:MERGE 语句
    - label 2023
      desc 16:逻辑复制可以从备库进行
    - label 2024
      desc 17:增量备份与 JSON_TABLE
    - label 2025
      desc 18:异步 IO 子系统

漏斗

sequence-funnel-simple 画逐步收窄的阶段。下面是主题的五个发布状态:互不等价,走完最后一个才是上线。

源码
```infographic {height="420px"}
infographic sequence-funnel-simple
data
  title 一次主题发布要经过的五个状态
  items
    - label 源码完成
      desc 代码写完,仅此而已
    - label 已验证
      desc 主题检查脚本与站点测试套件全绿
    - label 已发布
      desc 不可变的签名标签,能从 Go 代理拉到
    - label 已文档化
      desc 文档站钉住了这个标签
    - label 已部署
      desc 生产环境运行的就是这个版本
```
infographic sequence-funnel-simple
data
  title 一次主题发布要经过的五个状态
  items
    - label 源码完成
      desc 代码写完,仅此而已
    - label 已验证
      desc 主题检查脚本与站点测试套件全绿
    - label 已发布
      desc 不可变的签名标签,能从 Go 代理拉到
    - label 已文档化
      desc 文档站钉住了这个标签
    - label 已部署
      desc 生产环境运行的就是这个版本

网格卡片

条目之间没有先后关系时用 list-grid-*,它把条目排成网格而不是队列。

源码
```infographic {height="380px"}
infographic list-grid-compact-card
data
  title 同一页内容的四种输出
  desc 每个内容组件都要在这四态里给出可用的结果
  items
    - label HTML
      desc 交互式,按需加载运行时
    - label 打印
      desc 折叠展开,去掉缩放与复制按钮
    - label Markdown
      desc 纯文本,按字节比对金样本
    - label RSS
      desc 静态,与打印同源
```
infographic list-grid-compact-card
data
  title 同一页内容的四种输出
  desc 每个内容组件都要在这四态里给出可用的结果
  items
    - label HTML
      desc 交互式,按需加载运行时
    - label 打印
      desc 折叠展开,去掉缩放与复制按钮
    - label Markdown
      desc 纯文本,按字节比对金样本
    - label RSS
      desc 静态,与打印同源

带数值的条目

条目上加 value,能表达比例的模板(饼、环、进度)会用到它。

源码
```infographic {height="400px"}
infographic chart-pie-donut-plain-text
data
  title 29 个 shortcode 的构成
  items
    - label 核心组件
      value 14
    - label Book 编号与索引
      value 10
    - label 发布与下载
      value 3
    - label OpenAPI
      value 2
```
infographic chart-pie-donut-plain-text
data
  title 29 个 shortcode 的构成
  items
    - label 核心组件
      value 14
    - label Book 编号与索引
      value 10
    - label 发布与下载
      value 3
    - label OpenAPI
      value 2

层级与手绘风格

条目下面可以再嵌 childrenhierarchy-mindmap-* 把它画成两层的结构图。顶层的 theme 块换整张图的风格,typelightdarkhand-drawn

源码
```infographic {height="320px"}
infographic hierarchy-mindmap-level-gradient-compact-card
theme
  type hand-drawn
data
  root
    label 主题仓库
    children
      - label layouts
        desc 模板
        children
          - label _markup
            desc 渲染钩子
          - label _partials
            desc 外壳与工具
      - label assets
        desc 资源
        children
          - label scss
            desc 令牌与组件样式
          - label js
            desc 浏览器运行时
          - label third_party
            desc 随主题分发的库
```
infographic hierarchy-mindmap-level-gradient-compact-card
theme
  type hand-drawn
data
  root
    label 主题仓库
    children
      - label layouts
        desc 模板
        children
          - label _markup
            desc 渲染钩子
          - label _partials
            desc 外壳与工具
      - label assets
        desc 资源
        children
          - label scss
            desc 令牌与组件样式
          - label js
            desc 浏览器运行时
          - label third_party
            desc 随主题分发的库

theme 属于 DSL,不是围栏属性。它不跟随站点的深浅色:写 type dark 的图在浅色页面上也是深底。两种配色模式下都要检查对比度。

挑模板

模板名是 结构-变体 的组合,同一个结构有多个视觉变体。常用的几类:

结构前缀表达什么例子
list-row-* list-column-*一排 / 一列并列的条目list-row-simple-horizontal-arrow
list-grid-*网格,条目之间无先后list-grid-compact-card list-grid-badge-card
list-pyramid-* sequence-funnel-*逐层收窄sequence-funnel-simple
sequence-timeline-* sequence-roadmap-vertical-*时间线与路线图sequence-timeline-simple
sequence-steps-* sequence-snake-steps-*有序步骤sequence-steps-simple
compare-binary-horizontal-* compare-quadrant-*二元对比与四象限compare-binary-horizontal-simple-vs
hierarchy-mindmap-* hierarchy-structure-*层级(配合 childrenhierarchy-mindmap-level-gradient-compact-card
chart-pie-* chart-bar-* chart-column-*value 的示意图chart-pie-donut-plain-text
relation-network-* relation-dagre-flow网络与流向(配合 relationsrelation-dagre-flow

选能表达清楚关系的最小形式。完整图库见 AntV Infographic 图库,模板名与随主题分发的版本一一对应。

输出形态

输出呈现
HTML<div class="td-infographic"> 里一个画布容器加一段 DSL,本地 AntV 运行时画成 SVG
打印不画图,输出 <pre class="td-infographic-source"> 包着的 DSL 源码
Markdown原样保留 infographic 围栏与 DSL
RSS与打印相同,只有源码

图上的信息要在正文里写一遍:打印与 RSS 输出里只有那段 DSL。

参数参考

围栏属性行(```infographic {…}):

height , auto 或 CSS 长度 , defaultauto
非负数字加 px rem em vh vw %;其它写法构建失败
full , bool , defaultfalse
true 去掉正文宽度限制
class , 空格分隔的 class , default
透传给容器

styleon* 与未知属性让构建失败;空的 DSL 正文也让构建失败。

DSL 的顶层键(属于 AntV,不是主题):

infographic / template
模板名,第一行
data
titledescitems(也可以是 sequences compares nodes values relations root,取决于模板结构)、order
theme
typelight / dark / hand-drawn)、palettecolorPrimarystylize
width / height
DSL 层的画布尺寸,一般交给围栏属性 height
design
逐部件的细调,少用

items 里每个条目可用 labeldescvalueiconchildrengroupid。DSL 的完整定义以 AntV Infographic 文档为准;随主题分发的版本与校验值记在主题仓库的 VENDOR.json 里。

限制与常见问题

  • 模板名写错不会让构建失败:Hugo 只检查围栏属性,DSL 由浏览器运行时解析,模板不存在时容器里显示一行错误文字。改动模板名后在页面上确认。
  • 不跟随深浅色:theme 写在 DSL 里,两种配色模式下都要检查对比度。
  • 打印与 RSS 里只有 DSL,关键结论要写进正文。
  • SVG 不是语义结构:屏幕阅读器读到的顺序未必是排版顺序。标题、列表、表格能表达的内容优先用它们。
  • 标签要短:长文本在窄屏下会被截断或挤压,改动后在手机宽度下确认。
  • ECharts — 需要坐标轴与精确数值时用它
  • 步骤 — 需要读者照做的流程用步骤
  • 卡片 — 可点击的入口网格
  • Mermaid — 带分支与条件的流程

17 - 画廊

gallery 围栏把一组相关截图排成响应式网格,每张可带说明或链接,并复用页面的图片缩放对话框。

画廊(Gallery)把一组相关图片排成响应式网格,围栏里每行一张图。适用于同一件事的几个视图:几张截图、几种状态、几套配色。单张图用图片;相互之间没有顺序与对比关系的图片不适合放进同一个画廊。

最简例子

围栏里一行一张图,语法是 Markdown 的 ![替代文字](来源)

源码
```gallery
![OINK 文档站的浅色首页](/images/hero-light.webp)
![OINK 文档站的深色首页](/images/hero-dark.webp)
```

替代文字必须写:它是这一项的标题、读屏器唯一能读到的文字,也决定这张图是否参与缩放。列数没有参数,网格随容器宽度自适应,窄屏减列。

加说明

图片后面用 # 起头写说明,显示在图下方。说明是纯文本,里面的 Markdown 按字面显示;要一个字面井号写 \#

源码
```gallery
![OINK 文档页面的三栏布局](/images/oink.webp) # 默认外壳:侧栏、正文、目录
![Docsy 的经典文档布局](/images/docsy.webp) # OINK 的上游 Docsy,内容模型一脉相承
![发布说明页面](/images/releasenote.webp) # 发布页由 data/download 里的事实生成,不联网
```

说明长短可以不一致:网格按最高的一项对齐,说明换行不影响相邻的图。图片先被解析,替代文字与路径里的 # 不需要转义。

行尾的 {link=…} 让这一项成为链接,站内路径、相对路径、http(s): 都可以。

源码
```gallery
![OINK 的默认文档外壳](/images/oink.webp) # 点击进入「图片」组件页 {link=/zh/docs/components/image/}
![发布说明页面](/images/releasenote.webp) # 点击进入「发布与下载页」 {link=/zh/docs/write/releases/}
```

带链接的项不参与缩放,点击已有别的含义。同一个画廊里两种项可以混排:有链接的打开页面,没有链接的打开大图。

图片来源

来源解析顺序与普通图片一致:页面资源(页面包里的同目录文件)→ 全局资源 assets/ → 静态路径 /images/… → 远程 URL。本地资源带上固有尺寸,加载时不跳版;远程图构建期不下载,也取不到尺寸。

源码
```gallery
![OINK 文档总览(全局资源)](images/content-primitives/oink.webp) # assets/images/… 下的图,可以做构建期处理
![浅色首页(静态路径)](/images/hero-light.webp) # static/images/… 下的图,原样发布
```

页面资源与全局资源找不到时构建失败;静态路径与远程 URL 不检查存在性。

装饰图与缩放

替代文字留空表示这是装饰性图片:没有标题,读屏器跳过,也不参与缩放。

图片缩放是站点级开关,默认关闭。本页在 front matter 中开启了它,上面每张有替代文字、没有链接的图都可以点开看大图(Esc 关闭,焦点回到原处)。

这一页的 front matter
image_zoom: true
源码:一张装饰图配一张正常图
```gallery
![](/images/docsy.webp) # 装饰性配图,不参与缩放
![Pigsty 发布说明页面](/images/releasenote.webp) # 有替代文字,可以点开
```

画廊没有自己的缩放运行时,复用整页共用的那个对话框。页面上没有可缩放的图时,运行时不加载。细节见图片 · 缩放

加 class 与分标签页

class 可以加在整个围栏上(写在语言后面)或某一项上(行尾),主题不解释它,原样透传给站点 CSS。围栏带 tab=(以及 group= value=)时成为一组标签页里的一页。

源码
```gallery {tab="浅色" group="theme" value="light"}
![浅色模式的首页](/images/hero-light.webp) # 默认配色
```
```gallery {tab="深色" value="dark"}
![深色模式的首页](/images/hero-dark.webp) # 跟随系统或手动切换
```
浅色
深色

输出形态

输出呈现
HTML<ul class="td-gallery">,每项一个 <li>;符合条件的图带 data-td-image-zoom 标记;全部懒加载
打印同一组图堆叠排列,没有缩放标记
Markdown原样输出 gallery 围栏
RSS与打印相同的静态堆叠

画廊不加载 JavaScript。

参数参考

行语法 ![alt](src) [# 说明] [{key=value …}]

![alt](src) , 必填
必须顶在行首。alt 是这一项的标题;留空表示装饰图
src , 必填
页面资源 / 全局资源 / 静态路径 / 远程 URL
# 说明 , 必填
纯文本,显示在图下方;\# 是字面井号;不能为空
{link=…} , 必填
让这一项成为链接,因而不可缩放
{class=…} , 必填
给这一项加站点 CSS class

围栏属性:

tab , 纯文本 , default
让这个画廊成为一个标签页
group / value , 字符串 , default
标签页分组与同步值;必须与 tab 同时出现
class , class 列表 , default
透传给站点 CSS

没有 columnscaptiontitle 属性。行首不是图片、# 之外的尾随文字、空说明、未知属性、格式错误的 {…} 都会让构建失败,报错给出围栏内的行号。

限制与常见问题

  • 只有围栏一种形态:没有 {.gallery} 列表标记,也没有 shortcode。代价是源码在 GitHub 上不渲染成图片,收益是四态输出与缩放资格由主题保证。
  • 不能指定列数,也不裁成统一宽高比:网格按视口自适应,图片按原始比例排列。
  • 没有幻灯片、轮播与上一张 / 下一张:缩放对话框一次显示一张。
  • 不下载远程图:构建期没有网络请求,远程图在浏览器加载前尺寸未知,可能跳版。
  • 说明不解析 Markdown:需要富文本时写在画廊下方的段落里。
  • 图片 — 单张图、图注、编号、缩放开关
  • 卡片 — 带图片的链接网格
  • 标签页 — 按平台 / 主题并列多组图
  • 文件树 — 行语法与画廊同源

18 - 徽章

在功能名、版本号或表格单元格旁边放一枚语义状态标签,五种 tone,不需要自定义颜色。

徽章(Badge)是紧跟在名字旁边的行内状态标签:Beta、已弃用、v0.5、需自建服务。适用于一两个词能说完的状态;作者只选语义 tone,颜色由主题决定,浅色与深色模式下的对比度都有保证。状态需要解释、操作步骤或截止日期时,改用正文或提示块

最简例子

源码
{{< badge text="Beta" tone="warning" >}}
Beta

text 是唯一必填参数,必须是非空字符串。

五种 tone

只有这五个取值,没有自定义颜色。

源码
{{< badge text="默认" >}}
{{< badge text="信息" tone="info" >}}
{{< badge text="已支持" tone="success" >}}
{{< badge text="实验性" tone="warning" >}}
{{< badge text="已弃用" tone="danger" >}}

默认 信息 已支持 实验性 已弃用

不写 tone 时使用 neutral。其它取值让构建失败,报错给出源文件位置。

夹在句子里

徽章是行内元素,跟在名字后面,不占单独一行。

源码
`params.ui.image_zoom` {{< badge text="默认关闭" tone="neutral" >}} 打开后,
有替代文字的块级图片可以点开看大图。PlantUML {{< badge text="需自建服务" tone="warning" >}}
与 Draw.io {{< badge text="需自建服务" tone="warning" >}} 没有配置服务端点时会让构建失败,
而不是连接公共服务。

params.ui.image_zoom 默认关闭 打开后, 有替代文字的块级图片可以点开看大图。PlantUML 需自建服务 与 Draw.io 需自建服务 没有配置服务端点时会让构建失败, 而不是连接公共服务。

标题旁边

标题里不要写 shortcode。 Hugo 先生成目录、后替换 shortcode,所以徽章在标题上渲染正常,目录里却会留下一段 Hugo 的内部占位符文本。把状态写进标题下面的第一段:

源码
### OpenAPI 页面 {#openapi-example}

{{< badge text="0.5 新增" tone="success" >}} 这一节介绍……

OpenAPI 页面

0.5 新增 徽章紧跟在标题下方,目录保持干净, 锚点链接分享出去也不会带上徽章文字。

表格单元格里

对照表里用徽章标状态,比整列写「是」「否」更容易扫读。

源码
| 组件 | 形态 | 状态 |
| --- | --- | --- |
| 提示块 | `> [!NOTE]` | {{< badge text="稳定" tone="success" >}} |
| 画廊 | ` ```gallery ` 围栏 | {{< badge text="稳定" tone="success" >}} |
| PlantUML | ` ```plantuml ` 围栏 | {{< badge text="需自建服务" tone="warning" >}} |
| `image` shortcode | — | {{< badge text="已移除" tone="danger" >}} |
组件形态状态
提示块> [!NOTE]稳定
画廊```gallery 围栏稳定
PlantUML```plantuml 围栏需自建服务
image shortcode已移除

列表与步骤里

源码
1. 安装 Hugo Extended {{< badge text="≥ 0.160.1" tone="info" >}}
1. 克隆文档站,修改 `hugo.yml` 里的 `baseURL`
1. `hugo server` 预览 {{< badge text="1313 端口" tone="neutral" >}}
{.steps}
  1. 安装 Hugo Extended ≥ 0.160.1
  2. 克隆文档站,修改 hugo.yml 里的 baseURL
  3. hugo server 预览 1313 端口

卡片里

卡片有自己的 badge 参数(纯文本,固定在标题右侧);卡片正文里可以放徽章 shortcode。

源码
{{< cards >}}
{{< card title="Hugo Module" icon="fa-brands fa-golang" badge="推荐" >}}
一行 `hugo mod get` 完成安装 {{< badge text="需要 Go" tone="info" >}}
{{< /card >}}
{{< card title="离线归档" icon="fa-solid fa-box-archive" >}}
不联网的机器也能构建 {{< badge text="手动升级" tone="warning" >}}
{{< /card >}}
{{< /cards >}}
Hugo Module推荐

一行 hugo mod get 完成安装 需要 Go

离线归档

不联网的机器也能构建 手动升级

link 后徽章变成链接(<a>),站内路径、相对路径、http(s):mailto: 都可以。

源码
当前版本 {{< badge text="v0.5" tone="info" link="/zh/blog/" >}},
升级步骤见 {{< badge text="版本升级" tone="neutral" link="/zh/docs/admin/upgrade/" >}}。

当前版本 v0.5, 升级步骤见 版本升级

链接非法(协议不在白名单里)会让构建失败。

输出形态

输出呈现
HTML无链接时 <span class="td-badge td-badge--<tone>">,有链接时 <a class="td-badge …">
打印同 HTML,静态行内元素
Markdown**Beta**,有链接时 [**Beta**](/…)
RSS同打印

不加载 JavaScript。徽章不是实时状态区域,新增徽章不会触发读屏器播报。

参数参考

text , 纯文本 , default
必填,非空。读者看到的文字
tone , 枚举 , defaultneutral
neutral info success warning danger
link , URL , default
设置后徽章变成链接

只接受命名参数。没有 iconclasscoloroutlinesize 参数;写了未知参数、空 text、非法 tone 或非法链接都会让构建失败。

限制与常见问题

  • 颜色不是唯一的含义载体:tone 是补充,文字要自己说清楚。{{< badge text="🔴" >}} 对读屏器没有信息。
  • 没有图标参数:需要图标时改用卡片提示块
  • 文字要短:徽章不换行地跟在名字后面,超过五六个字的内容写进正文。
  • 同一处不超过三枚:连排的徽章会盖过它修饰的名字。
  • 徽章只有 shortcode 一种形态,没有原生 Markdown 写法;纯 Markdown 阅读器里它退化成加粗文字。
  • 卡片card 自己的 badge 参数
  • 文件树tone 用的是同一套词汇
  • 按键 — 另一枚行内 shortcode
  • 提示块 — 状态需要解释时用它

19 - 按键

kbd 写快捷键:一个 shortcode 接一串按键名,输出语义化的按键序列,打印与 Markdown 输出里同样可读。

按键(Kbd)把读者要按下的键与正文区分开。适用于快捷键与组合键:一个按键一个位置参数,主题负责画框、补分隔符,并给读屏器一个可读的序列。命令名、选项名与要输入的文本用行内代码,它们不是物理按键。

最简例子

源码
按 {{< kbd "Ctrl" "K" >}} 打开命令面板。

CtrlK 打开命令面板。

参数必须加引号,一个按键一个参数。少于一个按键、空字符串、命名参数都会让构建失败。

单个按键

一个参数对应一个键,符号键按原样写。

源码
{{< kbd "Escape" >}} 关闭对话框;
{{< kbd "/" >}} 进入搜索;
{{< kbd "t" >}} 切换亮色 / 暗色;
{{< kbd "l" >}} 循环切换语言。

Escape 关闭对话框; / 进入搜索; t 切换亮色 / 暗色; l 循环切换语言。

组合键

多个参数按顺序渲染,中间补 +。这个加号对辅助技术隐藏,读屏器读到的是本地化的连接词。

源码
{{< kbd "⌘" "Shift" "P" >}} 与 {{< kbd "Ctrl" "Shift" "P" >}} 是同一个动作。
需要按字面的加号时,把它当成独立的一个按键:{{< kbd "Ctrl" "+" >}} 放大页面。

ShiftPCtrlShiftP 是同一个动作。 需要按字面的加号时,把它当成独立的一个按键:Ctrl+ 放大页面。

平台差异

按键名写读者键盘上印的标签:macOS 写 ,Windows / Linux 写 Ctrl。不要把两个平台合进同一个序列,Ctrl/⌘ 这类写法读屏器无法正确朗读。在句子里说明平台,或分成标签页

源码
macOS 按 {{< kbd "⌘" "K" >}},Windows 与 Linux 按 {{< kbd "Ctrl" "K" >}}。

macOS 按 K,Windows 与 Linux 按 CtrlK

快捷键表

速查表是按键最常见的位置。下面是本站生效的一部分全局键:

源码
| 按键 | 作用 |
| --- | --- |
| {{< kbd "Ctrl" "K" >}} | 打开命令面板(macOS 是 {{< kbd "⌘" "K" >}}) |
| {{< kbd "/" >}} | 面板的完整搜索态 |
| {{< kbd "t" >}} | 切换亮色 / 暗色 |
| {{< kbd "q" >}} / {{< kbd "e" >}} | 上一篇 / 下一篇 |
| {{< kbd "w" >}} {{< kbd "s" >}} {{< kbd "a" >}} {{< kbd "d" >}} | 在侧栏树里上下移动、折叠、展开 |
| {{< kbd "Escape" >}} | 从侧栏树回到正文 |
按键作用
CtrlK打开命令面板(macOS 是 K
/面板的完整搜索态
t切换亮色 / 暗色
q / e上一篇 / 下一篇
w s a d在侧栏树里上下移动、折叠、展开
Escape从侧栏树回到正文

全站快捷键的完整清单见键盘导航

步骤里

源码
1. 按 {{< kbd "Ctrl" "K" >}} 打开命令面板
1. 输入 `>` 进入纯命令态,或输入关键词搜索
1. 用 {{< kbd "↑" >}} {{< kbd "↓" >}} 选中一项,{{< kbd "Enter" >}} 前往
1. {{< kbd "Escape" >}} 关闭,焦点回到按下之前的位置
{.steps}
  1. CtrlK 打开命令面板
  2. 输入 > 进入纯命令态,或输入关键词搜索
  3. 选中一项,Enter 前往
  4. Escape 关闭,焦点回到按下之前的位置

原始 <kbd> 标签

Markdown 里写原始的 <kbd> 标签得到同样的样式,GitHub 也这么渲染。区别是分隔符与无障碍序列要自己维护:单个键两种写法都可以,组合键用 shortcode。

源码
按 <kbd>F5</kbd> 刷新;在编辑器里按 <kbd>Ctrl</kbd>+<kbd>S</kbd> 保存。

F5 刷新;在编辑器里按 Ctrl+S 保存。

输出形态

输出呈现
HTML<span class="td-kbd-sequence"> 包着每个键一个 <kbd>;可见的 + 对读屏器隐藏,另有一个本地化连接词
打印同 HTML,静态
Markdown纯文本 Ctrl + K⌘ + Shift + P
RSS同打印

没有 CSS 与 JavaScript 时,操作说明仍然可读。

参数参考

位置参数 1..n , 字符串 , default
至少一个,每个都必须非空且加引号;顺序就是显示顺序

只接受位置参数。没有 separatorlabelplatformclasssize 这些命名参数:Hugo 的 shortcode 不允许在一次调用里混用位置参数与命名参数。

限制与常见问题

  • 一个序列表示同时按下的一组键:先按 A 再按 B 这类连续操作写成两个 kbd 加一句说明(先按 Escape,再按 Enter)。
  • 不做平台检测:页面不会按访客的操作系统把 Ctrl 换成
  • 不做按键映射与录制:菜单路径、手势、游戏杆不在范围内。
  • 漏写引号会让构建失败:{{< kbd Ctrl K >}} 里的 Ctrl 不是字符串参数。
  • 不用它标命令:hugo server 写成行内代码,Ctrl 是按键。

20 - 引用

用 include 插入外部文件,用 param 插入站点参数,用 comment 写不会出现在任何输出里的注释。

三个 shortcode 各做一件事:include 把另一个文件的内容放进当前页面,param 打印一个页面或站点参数,comment 丢弃一段内容。适用于跨页复用的片段与散落在多页的常量:同一段安装步骤出现在三页时用 include,版本号出现在几十页时用 param,改一处即可。只在一页出现的内容写在那一页。

最简例子

include 只有一个必填参数 file

源码
{{< include file="parts/install-oink.zh.md" >}}

被引的文件是一段普通 Markdown,放在 assets/ 下:

assets/parts/install-oink.zh.md
把 OINK 安装到一个已有的 Hugo 站点,三条命令:

```sh
hugo mod init github.com/you/your-site
hugo mod get github.com/pgsty/oink
hugo server
```

> [!NOTE]
> `hugo mod get` 需要本机安装 Go;用离线归档或 submodule 时不需要。

当前发布版本是 {{< param version >}}。

渲染结果与写在本页里相同:代码块有复制按钮,提示块是提示块。

把 OINK 安装到一个已有的 Hugo 站点,三条命令:

hugo mod init github.com/you/your-site
hugo mod get github.com/pgsty/oink
hugo server
说明

hugo mod get 需要本机安装 Go;用离线归档或 submodule 时不需要。

当前发布版本是 v0.6.0。

被引的文件不是一篇独立页面:它不出现在侧栏、不参与翻译配对、没有自己的 URL。

文件位置

file 按下面的顺序解析,第一个命中的胜出:

顺序找哪里写法
1当前页面的页面资源(页面包里的文件)file="config.yaml"
2全局资源 assets/ 下的文件file="snippets/dsn.txt"
3content/ 下的文件:/ 开头是内容根目录,否则相对当前页面所在目录file="notes/caveat.md"file="/shared/notice.md"

三处都找不到时构建失败,不输出占位内容。路径里含 .. 也让构建失败:引用只能在 content/assets/ 中取文件。

引 Markdown 片段时写文件在磁盘上的真名。有一个陷阱只属于第 1 步:Hugo 把带语言后缀的页面资源(如 notice.zh.md)按去掉后缀的名字挂在页面上,向页面包索取 notice.md 拿到的是已渲染的 HTML 而不是源码,Markdown 输出里会出现 <div class="td-code">assets/content/ 下写什么名字就取什么文件,没有这层转换。非 Markdown 文件(.yaml.sh.txt)也没有这个区别。

本页两种语言各引一份自己的片段:中文引 assets/parts/install-oink.zh.md,英文引 assets/parts/install-oink.md。片段放在 assets/ 下而不是页面包里,两种语言就都按写下的名字取到源码。

引入代码文件

code=true 让文件按代码块渲染,lang= 指定高亮语言。引用仓库里的真实配置文件,文档与实际文件不会不一致。

源码
{{< include file="parts/module.zh.yml" code=true lang="yaml" >}}
module:
  imports:
    - path: github.com/pgsty/oink
  hugoVersion:
    extended: true
    min: 0.160.1

代码块与围栏走同一条渲染管线:高亮、行号、复制按钮都有。围栏属性(title=collapsehl_lines=)传不进来,需要它们时把文件内容写成普通代码块

片段内容

片段是页面级 Markdown,在当前页面的上下文里渲染:提示块、表格、列表、图片、步骤与 shortcode 都可以用。上面那段片段结尾的「当前发布版本是 v0.6.0」,是片段里的 {{< param version >}} 在本页展开的结果。

一个片段被两页引用时,两页各自渲染一遍,各自生成标题锚点与代码块 ID,互不冲突。

适合做成片段的内容

安装命令、连接串、支持矩阵、法务声明:会变动、且变动时必须处处同步的内容。只在一页出现的内容写在那一页。

插入站点参数

param 打印一个参数:先查本页 front matter,查不到再查站点配置(Hugo 的 .Param 规则)。

源码
本站发布版本 {{< param version >}},版权起始年 {{< param copyright.from_year >}},
本页 front matter 里写了 `pigsty_pg_major: 18`,这里取到 {{< param pigsty_pg_major >}}。

本站发布版本 v0.6.0,版权起始年 2026, 本页 front matter 里写了 pigsty_pg_major: 18,这里取到 18。

嵌套键用 . 连接,copyright.from_year 取的是 params.copyright.from_year。参数不存在、或者值是 map 与列表而不是标量时构建失败,不会留下空白。

在命令、表格与链接里插参数

param 的输出是转义后的纯文本,可以放进代码围栏、表格单元格与链接地址。安装命令里的版本号适合这么写:

源码
```sh
hugo mod get github.com/pgsty/oink@{{< param tdVersion.latest >}}
```

| 项目 | 值 |
| --- | --- |
| 当前版本 | {{< param version >}} |
| Hugo 下限 | {{< param hugoMinVersion >}} |

[发布说明](https://github.com/pgsty/oink/releases/tag/{{< param tdVersion.latest >}})
hugo mod get github.com/pgsty/oink@v0.6.0
项目
当前版本v0.6.0
Hugo 下限0.160.1

发布说明

站点参数在哪里定义、有哪些可用,见配置总览;页面参数见页面参数

构建期删除的注释

comment 的内容在 HTML、打印、Markdown、RSS 四种输出里都不出现。HTML 注释不同:它留在页面源码里,也会进入 llms.txt

源码
PostgreSQL 18 起 `pg_stat_io` 拆分了 WAL 统计。

{{< comment >}}
待办:v0.5 发布后把上面的版本号换成 19,并补一张 pg_stat_io 的截图。
这段文字不会出现在任何输出里,包括 llms.txt。
{{< /comment >}}

升级前先在测试库上验证监控面板。

PostgreSQL 18 起 pg_stat_io 拆分了 WAL 统计。

升级前先在测试库上验证监控面板。

上面两段之间有一段注释,查看页面源码也找不到它。

输出形态

输出include(Markdown)include code=trueparamcomment
HTML片段渲染成正常内容高亮代码块 + 复制按钮转义后的纯文本
打印同 HTML同 HTML,无复制按钮同 HTML
Markdown片段的源码原样输出源码围栏值本身
RSS同 HTML同 HTML同 HTML

Markdown 输出里片段是源码而不是 HTML,片段里的 shortcode 保持 {{< param version >}} 的原样。这与「Markdown 输出保留源码」一致,不是漏渲染。三个 shortcode 都不加载脚本。

参数参考

include(只接受具名参数):

file , 路径(必填) , default
解析顺序见文件放在哪;含 ..、文件缺失、空值都构建失败
code , 布尔 , defaultfalse
true 时按代码块渲染;必须写成 code=true,带引号的 code="true" 是字符串,构建失败
lang , 字符串 , default
代码语言;只能与 code=true 同用,单独出现构建失败

其它任何参数名都会构建失败,报错里带文件名与行号。

param(一个位置参数):

参数名 , 字符串(必填) , default
嵌套键用 . 连接;先页面 front matter 后站点 params;缺失或非标量(map / 列表)构建失败

comment 没有参数,成对使用,{{< comment >}}{{< /comment >}} 之间的内容整段丢弃。

限制与常见问题

  • include 不是模板:不能向片段传变量、不能条件引入、不能给引入的代码块加围栏属性(title=collapse)。按平台分版本时写两个片段配标签页
  • 片段的语言要自己维护:include 不做语言回退。中文页引中文片段,英文页引英文片段,两份文件并列存放(install-oink.zh.mdinstall-oink.md)。
  • param 只打印标量:结构化数据(版本矩阵、下载列表)用 data/ 目录里的数据配对应组件渲染。
  • comment 不是「暂时不发布」:内容每次构建都被丢弃,临时下线整页用 draft: true
  • 不把 include 当目录页:一页引入十个片段时,读者需要的是十条链接。
  • 代码块 — 围栏的全部属性,include code=true 用的是同一套渲染
  • 标签页 — 按平台 / 语言分版本的片段
  • 配置总览param 能取到的站点参数
  • 页面参数 — 页面级参数,优先于站点配置

21 - Asciinema

把 .cast 终端录像放进页面:文字仍然是可选中的文字,播放器随主题分发,不连 CDN。

asciinema 把一段 .cast 录像渲染成页面里的终端播放器。适用于命令行流程的演示:终端里的文字仍然是文字,可以选中复制,一段六分多钟的安装过程约 190 KB。图形界面的操作用截图或视频,本组件只播放终端录像。播放器与样式随主题分发,构建期不下载、运行期不连 CDN,只有用到它的页面加载这套运行时。

最简例子

只有 file 是必填的:

源码
{{< asciinema file="images/install.cast" >}}
images/install.cast

这段录像是 Pigsty 在一台 Debian 机器上的单机安装,120×36 的终端,约 6 分 40 秒。文件在本站的 static/images/install.cast,路径写站点根路径。放在 assets/ 下也写相对路径:主题先在资源里查找,找不到再当成站点根路径。不写 title 时,窗口标题显示 file 的值。

窗口标题与主题

title 设置窗口标题,theme 设置配色:

源码
{{< asciinema file="images/install.cast" title="Pigsty 单机安装" theme="dracula" >}}
Pigsty 单机安装

theme 默认 auto:跟随站点的深浅色,浅色用 td-light,深色用 td-dark,读者切换配色时播放器就地重挂一次。要固定成某套终端配色时,可选值是播放器自带的 asciinemadraculagruvbox-darkmonokainordsetisolarized-darksolarized-lighttango,以及主题提供的 td-light / td-dark。固定的主题不跟随深浅色,深色站点配 solarized-light 的对比度不合适。终端字体不用单独设置:播放器使用站点的代码字体,与页面上的代码块一致。

速度、起点与封面

长录像用三个参数控制起点:speed 设倍速,startAt 跳过开头,poster 决定未播放时定格的画面。

源码
{{< asciinema file="images/install.cast" title="从第 60 秒开始,两倍速"
  speed="2" startAt="60" poster="npt:1:30" >}}
从第 60 秒开始,两倍速

speedstartAt 是数字(秒),poster 用播放器的 npt: 记法定位时间点,npt:1:30 是第 1 分 30 秒。上面这个播放器停在第 90 秒的画面,点播放从第 60 秒开始。

idleTimeLimit 把静默段压缩到最多 N 秒。这段录像在录制时已经压缩过(.cast 头里是 idle_time_limit: 0.5),此处不必再设。只有录制时没有限制静默时长的文件才需要它。

尺寸与适配

播放器默认按容器宽度缩放(fit="width"),终端的行列数来自 .cast 文件头。cols / rows 可以覆盖它:

源码
{{< asciinema file="images/install.cast" title="只留 16 行高" rows="16" >}}
只留 16 行高

比录像本身小的行列数会裁掉内容,上面这个只显示 36 行里的 16 行。cols / rows 用于修正录像头里的错误尺寸,不是排版工具。要让播放器变矮,重录一次小终端。

fit 的四个值:width(默认,按宽度缩放)、height(按高度)、both(两个方向都装下)、none(不缩放,按字号原样显示,宽终端会溢出)。

循环与预加载

loop 播完自动重播,preload 在页面加载时取回 .cast,点播放不必等待:

源码
{{< asciinema file="images/install.cast" title="循环播放:登录后的第一分钟"
  startAt="0" speed="3" loop="true" preload="true" >}}
循环播放:登录后的第一分钟

autoplay="true" 让页面打开即播。不建议使用:系统的「减少动态效果」偏好只关闭播放器控件的过渡动画,不阻止自动播放。确实需要自动播放时,配上 loop、很短的内容,并且一页只放一个。

放进步骤里

录像放在某一步旁边:文字说明要做什么,录像展示实际输出。

源码
1. 安装依赖,获取安装脚本:

   ```sh
   curl -fsSL https://repo.pigsty.io/get | bash
   ```

2. 执行安装,全程约六分钟:

   {{< asciinema file="images/install.cast" title="pig install" speed="4" >}}

3. 打开 `http://<节点地址>:3000`,用 `admin / pigsty` 登录 Grafana。
{.steps}
  1. 安装依赖,获取安装脚本:

    curl -fsSL https://repo.pigsty.io/get | bash
  2. 执行安装,全程约六分钟:

    pig install
  3. 打开 http://<节点地址>:3000,用 admin / pigsty 登录 Grafana。

一页可以放多个播放器,脚本与样式只加载一次。

录制 cast 文件

主题只负责播放。用 asciinemaasciinema rec --idle-time-limit=2 --cols=100 --rows=28 install.cast 录制,asciinema play install.cast 本地回放确认。

  • 终端宽度控制在 100 列以内,窄屏上仍可读;录制前先 clear
  • 录制前清理密钥:.cast 是纯文本,录像里的每个字符都能 grep 到,提交前检查一遍。
  • 文件放进 static/images/ 或页面包并提交进仓库,不引用外站的 .cast URL。

输出形态

输出呈现
HTML<div class="td-asciinema"> 窗口外框 + 播放器;播放器 CSS/JS 与初始化脚本按需加载,一页一次
打印同 HTML(打印输出也加载播放器);印到纸上只会留下当时的一帧
Markdown同一段容器 HTML 加一个 JSON 配置块;纯文字里能读到的只有窗口标题
RSS同一段静态标记;阅读器不执行脚本,只剩一个空窗口外框

录像不能是唯一的信息来源。关键命令与关键输出要在录像旁边用文字或代码块写一遍:离线读者、llms.txt 的抓取方与打印读者只能看到这些文字。

参数参考

file , 路径(必填) , default
具名或第一个位置参数;先按全局资源找,找不到当站点根路径;带协议的完整 URL 原样透传
title , 纯文本 , defaultfile 的值
窗口标题
theme , 枚举 , defaultauto
auto 跟随站点深浅色;或 td-light td-dark asciinema dracula gruvbox-dark monokai nord seti solarized-dark solarized-light tango
fit , 枚举 , defaultwidth
width height both none;其它值构建失败
cols / rows , 整数 , default来自 .cast 文件头
覆盖终端行列数;比录像小会裁掉内容
speed , 数字 , default1
播放倍速
startAt , 数字(秒) , default0
起播位置
idleTimeLimit , 数字(秒) , default来自 .cast 文件头
静默段最多播这么久
poster , 字符串 , default
未播放时定格的画面,npt:分:秒
autoplay , "true" / 不写 , default
页面加载即播;不建议
loop , "true" / 不写 , default
循环播放
preload , "true" / 不写 , default
页面加载时就取回 .cast
pauseOnMarkers , "true" / 不写 , default
播到章节标记处暂停
markers , 时间:标签,时间:标签 , default
章节标记;见下面的限制,标签目前到不了播放器

布尔类参数比较的是文本 trueloop="true"loop=true 都表示开启,其它值表示关闭。fit 的取值由主题校验,非法值报错并给出参数名。数字类参数(speed cols rows startAt idleTimeLimit)写成非数字会在转换时让构建失败。

限制与常见问题

  • markers 的标签会丢失:主题把 时间:标签 的列表拼成一维数组交给播放器,播放器只接受成对写法,时间轴上会多出没有标签的标记点。需要章节时用录像旁边的文字列表。
  • 播放器需要 JavaScript:禁用脚本时,以及 Markdown 与 RSS 输出里只有一个空窗口,见输出形态
  • 录像不进搜索:站内搜索索引页面文字,录像里出现过的命令搜不到。
  • 不引用远程 .castfile 收到带协议的 URL 会原样透传给播放器,页面因此依赖一个外站。
  • 控制单段长度:超过五六分钟的录像少有人看完,长流程拆成几段短录像,各配一段文字。
  • 代码块 — 关键命令与输出写成可复制的代码块
  • 步骤 — 把录像放在某一步旁边
  • 图片 — 静态截图;终端内容优先用录像,图形界面用截图
  • 引用 — 同一段命令要在几页复用时