跳转到主要内容

1 - Oink 发布注记

OINK 的版本发布注记、升级指南与兼容性说明

1.1 - OINK 1.0.0:稳定契约、Starter 与正式发布

OINK 1.0.0 将现有知识发布契约定为稳定表面,并汇总 0.8.0 之后的全部主题修改: Print 与 Book 正确性、固定的 Go 1.27 与 Hugo 0.165.0 发布工具链、正式支持的 Starter,以及进入更广泛 Hugo 生态所需的公开元数据与展示素材。

OINK 1.0.0 是稳定性里程碑,不是临发布前重置 API。它把整个 0.x 周期形成的组件、 配置、内容、输出与维护者契约提升为第一个主版本。本说明汇总 v0.8.0..v1.0.0 范围内的全部主题修改;0.8.0 已经交付的 Agent 输出与反向链接是比较基线,不会再 冒充 1.0 新功能重复计算。

已经使用 0.8.0、0.8.1 或 0.8.2 的站点不需要迁移内容或配置。把模块固定到新版本, 执行 warning 即失败的严格构建,再像任何主题升级一样检查真实渲染即可。

概览

  • 当前创作、外壳、Landing、Book、Release、Print、Markdown 与 Agent 输出契约, 现在共同构成 OINK 1.0 的稳定表面。
  • 单页 Print 保留普通页面的标题与脚注 ID;只有多页分区 Print 与整书 Print 才为 页面局部目标增加命名空间。
  • 长标题换行时,Book 侧栏编号仍保持为不可压缩、不可拆分的原子单元。
  • 主题 CI、文档站与 OINK Starter 使用 Go 1.27 和 Hugo Extended 0.165.0;公开 声明的 Hugo 兼容下限仍为 Extended 0.160.1。
  • OINK Starter 成为进入框架的正式起点,提供中性的 Docs、Blog、Book 内容,以及 严格的 GitHub Pages 与 Cloudflare Pages workflow。
  • README、主题元数据、案例链接、徽章与优化后的 3:2 Hugo Themes 展示图,现在 描述和呈现的是代码真正交付的同一个产品。

1.0 稳定了什么

主版本号约束的是契约,不是宣称界面从此停止演进。OINK 仍可在 1.x 增加组件与可选 输出;1.0 的含义是普通站点不必在每个次版本重新学习或改写当前基础。

表面 1.0 契约
内容 原生 Markdown 仍是源文件;组件对非交互输出保持明确的静态降级
配置 params.ui.* 管理主题策略,页面覆盖去掉该前缀,非法作者输入告警并使用安全回退
外壳 Docs、Blog、Book、Swagger/Redoc 与 Landing 各自保留清晰、成文的职责
输出 HTML、RSS、Print、Markdown、LLMS、LLMSFULL、NAVJSON 与 BookManifest 保持明确的选择启用与降级边界
运行时 第三方资源继续本地化,能力代码只在实际渲染内容需要时加载
维护 实现、归属检查器、双语契约、发布状态、消费站固定版本与部署继续作为独立证据

规范性的中英文记录位于设计与开发;其状态现在统一为 released-v1.0.0。带日期的研究与活跃提案仍是证据或未来工作,不会暗中算作 已经交付的 1.0 能力。

0.8.0 之后的全部修改

完整源码比较见 v0.8.0...v1.0.0。 其中是一组刻意收敛的稳定化改动:

范围 修改 用户可见结果
Book 侧栏 固定编号单元,并为编译后 CSS 增加回归断言 长标题换行时不会再压缩、裁切或拆开章节编号
Print 锚点 区分单页 Print 与分区 / 整书聚合,再刷新输出 golden 普通页面有效的 fragment 在该页 Print 中继续有效;聚合文档的 ID 仍不会冲突
主题 CI 用一个固定的 Extended 0.165.0 工具链替代历史 Hugo 矩阵,并为模块模式明确固定 Go 1.27 发布证据与当前上游工具链一致,0.160.1 Hugo 下限则继续单独成文
公开 README 围绕 OINK Starter 重写第一条上手路径,补齐能力、兼容性、生产案例、文档入口与 Docsy 边界 访客无需反向拆解回归站就能评估项目
Hugo Themes 素材 换成优化过的 3:2 Landing 截图 图库得到不带浏览器边框、大小分别为 166,526 与 68,488 字节的 PNG
主题元数据 扩展描述、标签与特性,统一 OINK 字标,记录 Docsy 原始主题身份 目录中的归属与可发现性符合仓库真实范围
模块 directive 0.8.2 临时适配旧版 Go 1.26 上游构建器;1.0 随更新后的上游流程回到 Go 1.27 只改变模块准入;OINK 仍无 Go 源码,directive 不改变渲染结果

这个范围没有组件改名、配置键移除、默认值翻转或内容语法迁移。

正确的 Print 身份

页面局部 ID 与聚合文档 ID 解决的是两类问题。普通页面与它自己的 Print 表示是同一 份文档的两种视图,因此作者明确编写或 Goldmark 生成的标题、脚注 ID 应保持一致。 分区 Print 与整书 Print 会组合多个源页面,两个章节可能都带 #overviewfn:1, 所以这些目标必须增加源页面命名空间。

输出 标题与脚注 ID
普通 HTML 页面 作者明确编写或 Goldmark 生成的页面局部 ID
单页 Print 与普通 HTML 相同的页面局部 ID
多页分区 Print 增加源页面命名空间
整书 Print 增加源页面命名空间

Book 图、表、公式、示例,以及改写后的跨页链接继续沿用现有显式目标规则。修复只是把 命名空间限制到真正聚合多份文档的两种输出。

正式支持的第一公里

OINK Starter 现在属于正式支持的发布表面,而不是非正式演示。它从小而中性的项目站 开始:三种语言 profile、Docs、Blog、Book、本地资源与两条 warning 即失败的部署 workflow。它刻意排除了 OINK 自身的分析账号、评论、品牌、文档全集、浏览器套件与 维护者 fixture。

Starter 教程按由浅入深的顺序推进:先建立未修改基线, 再设置身份、选择语言、替换首页数据、改写内容与导航、增加品牌、启用完整集成、执行 严格构建,最后才部署。已有 Hugo 站点仍可以采用更小的 从零接入模块路径

工具链与兼容性

Hugo 官方主题更新流程在本次发布当天升级到 Go 1.27 与 Hugo 0.165.0。OINK 1.0 跟随这条当前发布基线:

依赖 OINK 1.0 策略
Hugo Extended 0.160.1 或更新版本;发布、站点与浏览器验证固定 0.165.0
Go 解析 Hugo Module 时需要 1.27 或更新版本
Node.js 消费站构建与运行均不需要

短暂存在的 0.8.2 只降低了模块的 go directive,让当时固定 Go 1.26、使用本地 工具链选择的官方更新器可以准入主题。上游转到 1.27 后,继续保留这一例外已无法描述 真实发布环境。OINK 本身仍由模板、样式、资产与检查器组成,不包含 Go 源码。使用离线 归档或 Git submodule 时不需要 Go 解析模块。

升级

hugo mod get github.com/pgsty/oink@v1.0.0
hugo mod tidy
hugo --cleanDestinationDir --gc --minify --environment production \
  --printPathWarnings --panicOnWarning

提交 go.modgo.sum,再检查有代表性的 Docs、Blog、Book、Print、语言、深浅色 与窄屏路由。本地构建成功、公开标签、可解析的模块校验和、消费站固定版本、部署与线上 渲染仍是彼此独立的发布状态。

仓库级完整流水账继续记录在 CHANGELOG.md

1.2 - OINK 0.8.2:面向 Hugo Themes 构建器的 Go 1.26 模块兼容性

OINK 0.8.2 把 Hugo Module 的 Go directive 从 1.27 降到 1.26,使包括 Hugo 官方主题流水线在内、使用 GOTOOLCHAIN=local 的构建器无需改变 Hugo 兼容下限或渲染行为就能导入主题。

OINK 0.8.2 是一次模块元数据兼容性发布。它不修改模板、资产、组件 API、配置键、 内容语法或任何渲染输出。已经使用 0.8.1 的站点不需要迁移内容。

概览

  • 主题模块现在声明 Go 1.26,而不是 Go 1.27。
  • Hugo 官方主题构建器可以在固定的 Go 1.26 与 GOTOOLCHAIN=local 环境中导入 OINK。
  • Hugo Extended 0.160.1 仍是公开兼容下限;OINK Starter 继续使用 Hugo Extended 0.165.0 构建。
  • OINK 不包含 Go 源码,也没有使用 Go 1.27 的语言或模块特性,因此降低 directive 只改变模块准入条件。

为什么需要这个补丁

OINK 0.8.1 使用 Go 1.27 构建和测试,因此 go.mod 声明了 go 1.27.0。但这个仓库 是由模板和资产组成的 Hugo Module,并不是 Go 软件包。Hugo 官方主题流水线使用 Go 1.26,而且禁用自动工具链切换,因此会在读取主题元数据之前正确拒绝更高的 directive。

OINK 0.8.2 声明当前发布流程真正需要的最低 Go 工具链:Go 1.26。渲染表面保持 逐字节一致;变化的只有允许模块进入构建器的兼容性门禁。

兼容性

依赖 OINK 0.8.2 要求
Hugo Extended 0.160.1 或更新版本
用于解析 Hugo Module 的 Go 1.26 或更新版本
Node.js 不需要

离线归档或 Git submodule 安装不要求 Go,因为它们不需要 Hugo 解析模块。

升级

hugo mod get github.com/pgsty/oink@v0.8.2
hugo mod tidy

提交 go.modgo.sum,再执行 warning 即失败的生产构建。完整变更见 CHANGELOG.md

1.3 - OINK 0.8.1:稳定的 Print 锚点、不被标题挤压的 Book 编号与完整发布材料

OINK 0.8.1 是一次范围明确的维护版本:单页 Print 与普通 HTML 保持相同的标题与脚注锚点,过长的 Book 标题不再挤压侧栏编号,项目公开入口也改为从 OINK Starter 引导新站点。

OINK 0.8.1 收口了两个小而具体的渲染问题,并让项目的公开展示与 0.8.0 已经交付的框架能力保持一致。它没有修改组件 API、配置键、内容语法或兼容性下限。 现有 0.8.0 站点只需升级模块固定版本,不需要迁移内容。

概览

  • 一个页面单独渲染为 Print 时,现在与普通页面保持完全相同的标题和脚注 ID。 分区与整书 Print 仍会给这些页面局部 ID 增加命名空间,因为多个源页面共享 同一份聚合文档。
  • Book 侧栏编号现在是宽度固定的原子单元。长标题可以换行,但不能再压缩、裁切或 拆开旁边的编号。
  • 持续集成对主题、出版、站点与浏览器检查统一使用固定的 Hugo Extended 0.165.0。 Hugo Extended 0.160.1 仍是向消费站点声明的兼容性下限。
  • 项目 README 现在把 OINK Starter 放在第一条路径,记录能力与兼容边界、代表性生产站点, 也明确说明 OINK 为何是独立主题,而不是 Docsy 换皮。
  • 新的 3:2 Hugo Themes 展示图、更完整的主题元数据与明确的 Docsy 归属补齐了提交材料, 且不会给消费站点增加页面资源。

Print ID 应与当前输出表面一致

普通 HTML 中的标题与脚注 ID 是页面局部事实。在 0.8.1 之前,同一页单独渲染为 Print 时, 也被加上了只应用于聚合文档的前缀。于是在普通页面上有效的 URL fragment,在单页 Print 中不再指向对应元素。

现在的规则是:

输出 标题与脚注 ID
普通 HTML 页面 作者明确编写或 Goldmark 生成的页面局部 ID
单页 Print 与普通 HTML 完全相同的页面局部 ID
多页分区 Print 给页面局部 ID 增加源页面命名空间
整书 Print 给页面局部 ID 增加源页面命名空间

图、表、公式和示例等显式 Book 目标仍然稳定。聚合输出仍会把跨页链接改写到加了命名 空间的标题和脚注目标,避免两个章节同时带有 #overviewfn:1 时在一份文档中生成 重复 ID。

长标题旁的 Book 编号保持可读

Book 侧栏使用一个编号单元和一个标题单元。以前编号可能继承长标题的压缩与溢出行为, 在窄屏下被裁切或拆行。现在编号不会收缩,且保持为一个整体;只有标题换行。这是一项 纯 CSS 修正,不改变 Book 编号或导航顺序。

从更清晰的起点进入 OINK

现在推荐从小而中性的 pgsty/oink-starter 模板开始,而不是克隆文档回归站。 它内置中性的 Docs、Blog 与 Book 内容、三种语言 profile,以及 warning 即失败的 GitHub Pages 与 Cloudflare Pages workflow,不包含 OINK 自己的分析、评论、测试框架或品牌。

新的 Starter 教程 依次处理身份、语言、首页数据、内容与导航、品牌、 集成、严格构建和部署。现有 Hugo 站点仍可以使用更小的 从零开始模块路径

升级

hugo mod get github.com/pgsty/oink@v0.8.1
hugo mod tidy

提交 go.modgo.sum,再执行站点的 warning 即失败生产构建。不需要迁移内容或配置。 完整变更见 CHANGELOG.md

1.4 - Oink 0.8.0:整个栏目一次取走,侧栏直接当数据读,以及谁链到了这一页

Oink 0.8.0 为「以程序身份来读」的访客加了两种可选输出格式:把整个栏目装进一个文件的 全文包,以及以 JSON 发布的导航树;再加上静态反向链接,在右栏列出链接到本页的页面。 三者都默认关闭,不点名就不会出现。

Oink 0.8.0 不改动任何组件 API,也不需要修改内容。三项新增里有两项服务于以程序身份来读的访客。 每一页本来就会多产出一份 .md,它服务的是已经知道自己要哪一页的 agent; 而想读完整份手册的 agent,仍然只能一页一页地爬,边爬边发现链接。 这两种新输出格式回答的是另一半问题:把整个栏目给我,以及在我开始抓之前,先告诉我这个站点里有什么。 第三项是给作为人的读者的:页面右栏现在可以列出有哪些页面链接到它。

概览

  • LLMSFULL 为每个顶层栏目产出一份 llms-full.txt:栏目下的每一页,按侧栏阅读顺序,装在一个文件里。
  • NAVJSON 为每种语言产出一份 navigation.json:侧栏那棵树,以数据形式发布,并由 JSON Schema 定版。
  • params.ui.backlinks 在右栏列出链接到本页的页面,索引在构建时从你 Markdown 里本来就有的链接派生。
  • 三者都是可选的,主题绝不会替你打开。三个都不点名的站点,构建结果与此前逐字节一致。
  • llms.txt 会列出你开启的那些文件,发现入口仍留在 agent 本来就会抓的那个文件里。
  • data/docs_nav.json 里没有 children 键的节点不再中断构建。

全文包:整个栏目一次取走

LLMSFULL 把整个栏目收进一个文件:栏目根目录下的 llms-full.txt, 按侧栏与翻页器呈现的顺序把栏目下每一页依次拼接,每页之前有一行分隔符标出它的来源地址。 对 agent 来说,/docs/llms-full.txt 是一次抓取,而原来的做法是每页一次抓取外加一张要跟着走的链接图; 而且结果是有序的,于是这个栏目读起来像一份手册,而不是一堆页面。

开关在栏目自己的 front matter 上,主题不会把它加进站点的输出集合:

content/docs/_index.zh.md
---
title: 文档
outputs: [HTML, print, RSS, markdown, LLMSFULL]
---

front matter 里的 outputs 是对站点级列表的整体替换,因此要把这个栏目原本就有的格式一并写回。 它按语言生效,所以中文全文包需要在 _index.zh.md 里同样写一遍。

每一页贡献进来的,就是它自己 .md 里那份语义 Markdown,而不是第二次渲染的结果。 每页 Markdown 正文已经挪进两种输出共用的同一个 partial,因此全文包中的一段与该页的 .md 逐字节相同, 两者不可能各走各的。顺序同样来自侧栏读的那份权威:docsbook 栏目声明了 data/docs_nav.json 显式树时以显式树为准,其余按内容树的 weight。不在侧栏里的页面,也不会进全文包。

全文包属于顶层栏目,没有整站版本:想要全部内容的 agent,一个栏目读一份。 写在更深一层的栏目上会告警并且什么都不产出,于是 hugo server 照常能用, 而加了 --panicOnWarning 的发布构建会停在这里。

本站的文档栏目已经开启,https://oink.pgsty.com/zh/docs/llms-full.txt 一次取走全部中文文档。文件的确切形状等细节见全文包

导航 JSON

侧栏是站点的目录,读得懂它的 agent 可以在正文上花掉第一次抓取之前,先规划好路线。 NAVJSON 把它作为数据发布出来:每种语言一份 navigation.json,放在语言根目录下。 由站点在首页打开:

hugo.yml
outputs:
  home: [HTML, LLMS, NAVJSON]

这棵树不是对站点结构的第二次描述。它序列化的是侧栏与翻页器本来就在读的那份权威, 走的也是同一个 partial:声明了 data/docs_nav.json 显式树的地方以显式树为准,其余按内容树的 weight。 有一项检查断言 docs 子树展平后恰好等于全文包产出的页面序列——两条模板路径,一份权威。

每个节点带 id(去掉语言前缀的路径,因此同一页在每种语言里 id 相同)、绝对地址 url、 页面确实产出 .md 时的 markdown 地址、titledescriptionkind,以及有序的 children。 其中两条值得当作承诺而不是实现细节来读:

  • 数组顺序就是契约。顺序已经算好了,weight 不会被序列化——消费方再排一次,只会与它来源的侧栏对不上。
  • 格式带版本。schemaVersion1,契约随主题仓库发布,见 schema/nav.v1.schema.json。要消费这个文件,就拿它做校验。

本站的 https://oink.pgsty.com/zh/navigation.json 就是实例。占位条目与完整键表等细节见导航 JSON

从搜索落到一个页面的读者,只能看到这一页指向哪里,看不到它自己处在什么位置。 反向链接补上的就是这另一半:右栏目录下方多出一个「反链」组,列出有哪些页面链接到它, 默认展开,超过八条折进「再显示 N 条」。一个键就能打开:

hugo.yml
params:
  ui:
    backlinks: true

单页用 front matter 键 backlinks 覆盖,分区用 cascade 覆盖它下面的所有页面。

索引在构建时从你本来就写好的东西里派生:页面源码里的普通 Markdown 链接,以及 ref / relref。 没有 [[wikilink]] 这类新语法要采纳,没有内容要迁移,也不需要 JavaScript—— 链接就在 HTML 里,也在这一页的 Markdown 输出里,关掉脚本的读者一样看得到。 扫描前先剥掉代码围栏与行内代码;指向同一目标的多个链接合并成一条; 自链接、外链与同页锚点都不计入;每种语言各有一张图。 顺序是稳定页面路径,因此同样的内容永远构建出同样的列表;没有页面链进来时,整个区块不出现。

有一处需要说清楚的遗漏:读源码看不见藏在自定义 shortcode 参数里或原始 <a href> 里的 URL, 解析不出来的目标也会被静默丢弃。这是导航,不是链接检查——查断链仍然要用链接检查器。

本站全站开启:看任何一篇文档的右栏就能看到,被引用最多的配置总览列出了四十多个入链。细节见反向链接

发现入口仍在 llms.txt

两个文件都不是某个页面的替代表示,因此都不会出现在 <head> 里,也不会有对应的页面动作。 取而代之的是 llms.txt——agent 本来就会先抓的那个文件——多出一段 ## Full-text bundles 列出本语言的全部全文包,并在站点索引里列出本语言的 navigation.json。 两处条目都只在站点确实发布了对应文件时才出现:主题绝不指向自己没有产出的东西。

没有 children 的导航节点不再中断构建

data/docs_nav.json 里没有 children 键的节点,会让构建以侧栏遍历器内部抛出的一个反射错误告终。 遍历器假定每个节点都带这个键——这对生成的 JSON 成立,对人手写的 JSON 不成立, 因为手写时叶子节点很自然地就写成一个没有 children 的节点。 现在作者写的数据会降级而不是报错:没有子节点的节点,就按它本来的样子渲染成叶子。

升级

hugo mod get github.com/pgsty/oink@v0.8.0
hugo mod tidy

不点名就什么都不会变。组件 API 没有改动,也不需要修改内容——两种输出格式在 outputs 里声明, 反向链接是 params.ui 下的一个布尔;三个都不点名的站点,发布出来的东西和 0.7.1 一样。 两种输出格式以及它们产出的东西长什么样,都在 Agent 支持; 反向链接的开关见导航与菜单

完整清单见 CHANGELOG.md

1.5 - Oink 0.7.1:页面不再外泄,坏输入不再中断构建

Oink 0.7.1 是一次安全与校验修补。Swagger UI 不再把你的 spec 地址发给第三方, 配错的参数会告警并回退而不是中断普通构建,OpenAPI 与终端录像组件也终于像其他组件一样, 在打印、Markdown 和 RSS 中表现正常。

Oink 0.7.1 不改动任何组件 API,也不需要修改内容。它修复了对 0.7.0 主线外部审查发现的 代码问题:一个真实的隐私外泄、一类会直接中断构建的配置值,以及三个从未被告知 “非 HTML 输出"存在的组件。

概览

  • Swagger UI 不再联系在线 validator。已发布的 API 页面每次被浏览都会发出一个第三方请求,现在不会了。
  • 写在站点配置里的 URL,现在和作者写的 URL 走同一道安全检查。
  • params 中数值或布尔值写错,会告警并回退,而不是终止普通的 hugo server
  • swaggerredocasciinema 在打印、Markdown 和 RSS 中输出纯链接,只在交互 HTML 中装载运行时。

Swagger 不再向外汇报

Swagger UI 默认开启在线 validator,地址指向 validator.swagger.io。它对 localhost 跳过这个请求——这正是本地预览和浏览器测试从来看不到它的原因,也意味着每一个已经部署上线的 API 页面,都在悄悄把你的 spec 地址交给第三方。在内网站点上,那个地址就是一个内部主机名。

现在初始化写死 validatorUrl: null,并从内联 <script> 移入可缓存的 js/chunks/swagger-init.js。普通构建依然不下载任何东西,而现在普通的浏览也不再上传任何东西。

配置里的 URL 与作者写的走同一道门

有两处设置未经检查就进入了 hrefparams.ui.page_context_menu.links 里的自定义链接, 以及归档站点横幅的 params.url_latest_version。在其中任何一处写 javascript: URL, 都会渲染成一个可点击、可执行的脚本链接。

现在两者都走主题的统一 URL 策略:不支持的 scheme 会告警并丢弃该链接,而不是尝试修补。 归档版本横幅在写入页面时还会额外做 HTML 转义——因为"scheme 合法"和"放进 HTML 属性里安全” 不是一回事。

自定义链接还会跳过缺少名称或名称不是文本的条目,并且只有当确实有链接留下来时, 才渲染它们上方的分隔线。

配置写错会告警,而不再让预览挂掉

主题一直有一条规则:非法的作者或配置输入应当告警、回退到有文档记载的默认值, 并保持 hugo server 可用;而 --panicOnWarning 会在发布时把这个告警变成失败。 只是有一批数值和布尔配置从来没有接入这条规则。

在 0.7.1 之前,blog_index_size: nope 会以一个 Go 模板错误终止构建。另一些则因为安静而更糟: sidebar_width_min: -50 一声不响地输出了负的像素宽度,blog_index_columns: 2.5 把一个小数送进了 CSS 网格。

现在每一个数值与布尔配置都经过统一校验器:

输入 之前 现在
blog_index_size: nope 构建失败 告警,使用 12
blog_index_size: 0 静默变成 12 告警,使用 12
sidebar_width_min: -50 输出 -50px 告警,使用 220
sidebar_width_min: 300max: 200 布局反转 告警,使用 220/480
blog_index_columns: 2.5 小数进入 CSS 告警,使用 3
sidebar_item_overflow: clip 静默当作 ellipsis 告警,使用 ellipsis
print.toc: nope 静默当作 true 告警,使用 true

同样的处理覆盖了 Landing 各区块:hero 的 media.ratiomedia.max_width、 capabilities 的 columnsrules、以及跑马灯的 rows。其中 hero 的两个样式输入尤其值得一提—— 它们此前被原样拼进 style 属性,因此页面自己的 front matter 就能往页面上注入任意 CSS。 现在 ratio 只接受两个轨道尺寸('1fr 240px'),max_width 只接受一个纯 CSS 长度。

如果你的站点此前一直用着某个被主题静默纠正过的值,升级后会看到新的告警。这正是目的所在—— 升级后用 --panicOnWarning 构建一次,把它们找出来。

OpenAPI 与终端录像尊重其他输出

Oink 的每个组件都只渲染一次,然后适配它所在的输出:交互 HTML、静态打印、 给智能体读的纯 Markdown,以及 RSS。已有十六个组件这样做,而 swaggerredocasciinema 没有——它们把交互标记原样渲染进了全部四种输出。

结果是:Markdown 输出里带着 <div class="td-asciinema"> 和一整块 JSON 配置, 打印页面上是一个本该有播放器的空壳,而单页打印甚至真的下载了播放器运行时, 只为显示一帧静止画面。

现在三者都读取输出格式:

输出 你会得到
HTML 完整的交互组件
打印 一行带标题的静态链接,地址可见
Markdown / LLMS 一个纯 Markdown 链接,仅此而已
RSS 同样的纯链接

只有交互 HTML 会登记运行时,因此打印与机器输出不再装载播放器、Swagger 包或 ReDoc 包。 录像与 spec 地址现在同样走统一 URL 策略,而写错的 speedcolsrows 或标记时间 会告警并被忽略,不再终止构建。

其他修复

  • capabilities 的横条现在按作者写的宽度渲染。模板一直在输出这些宽度,只是样式表从未读取。
  • 生成的配置 Schema 与 Hugo 实际解析的结果一致。hugo.yaml 的行尾注释此前污染了十一个默认值—— print.toc 是以字符串 "true # section print views…" 发布的——另有四段注释挂在了错误的键上。 仅用于提示重命名的旧键不再出现在编辑器补全里。
  • heromedia 不是一个映射时会告警并丢弃该媒体,而不是终止构建。

升级

hugo mod get github.com/pgsty/oink@v0.7.1
hugo mod tidy

不需要修改内容、配置或模板。升级后建议做一件事:用 --panicOnWarning 构建一次。 那些过去被静默纠正的配置现在会开口,而这次构建就是你听到它们的地方。

完整清单见 CHANGELOG.md

1.6 - Oink 0.7.0:主题色、统一的字体口径,以及终于能读的图

Oink 0.7.0 让每个板块通过外壳的底色拥有自己的强调色,把七个字体角色交给站点配置, 并把 mermaid 围栏变成一张真正的图:居中、切换深浅色就地重绘、可以按原始尺寸打开。

Oink 0.7.0 没有改动任何组件 API。它只做两件读者真正长时间面对的事——页面周围的外壳 与页面上的字——并补完了一个从来没有被设计过、只是继承下来的围栏。

概览

  • params.ui.theme_color 让板块拥有自己的强调色,作用于外壳的底色,而非正文。
  • params.ui.fonts 覆盖全部七个字体角色;Book 不再自带字体。
  • mermaid 围栏是一张图:居中、无边框、切换配色就地重绘、可按原始尺寸打开缩放拖动。
  • 行内代码是绯色墨迹配极淡底纹,不再是灰色药丸。
  • 主题自带的浏览器行为以稳定能力分块发布在 js/chunks/ 下,页面按需选择脚本而不再自制打包。
  • 配置 schema 由解析器生成,不再手工维护。

主题色

params.ui.theme_color 接受 #rgb#rrggbb,为外壳的强调底色着色:选中的侧栏行 以及相邻行在指针下的底色、悬停底纹、大纲的胶囊及其滑动轨道与圆点、标签与 chip 的 悬停、卡片的悬停边缘、分享按钮的悬停填充、文本选区,以及焦点环。

hugo.yaml
params:
  ui:
    theme_color: "#2f6f4f"

板块可以设置自己的颜色,页面用 theme_color: false 退出继承来的颜色。它刻意不碰阅读 表面——正文链接、外部链接与行内代码在任何板块都保持品牌色——所以着色的板块是一个安静 的位置信号,而不是把整页重新上色。

统一的字体口径

params.ui.fonts 从配置触达主题的七个字体角色,站点不用再自带样式表就能改变自己的 字体口径。

Book 不再自带字体。它的编号与图表标题原先用一套只含拉丁子集的等宽字体渲染,导致一句 中文标题在句中被拆成两种字面——数字用一套,汉字落到读者恰好装有的任意回退字体。现在 它们继承周围的字体,由 tabular-nums 维持侧栏那一列的对齐。

终于能读的图

mermaid 围栏原本是五行透传:把代码块的 <pre> 交给 Mermaid,剩下的交给 startOnLoad。三个缺陷都源自这一个决定,而它们的修法是同一个——让源码在 Mermaid 跑过之后依然可读。

围栏现在输出一个 figure,里面是空舞台加上以 JSON 保存的源码,也就是 echartsinfographic 已经在用的形状,由运行时决定每张图何时绘制。

居中,且无边框。 Mermaid 输出 width="100%" 加上等于图自身尺寸的 max-width, 所以比栏窄的图会贴在起始边,旁边留下最多 300px 空白——而且那块空白是被框起来的, 框来自代码块。这里刻意不提供对齐属性:图是 figure,没有读者想要一张贴右的图。

可以按原始尺寸打开。 Mermaid 在窄栏里不会溢出,它会缩小以适应,所以 overflow-x 从来给不出退路:在 390px 手机上,本站 Mermaid 文档页里的时序图渲染为 自身宽度的 35%,14px 的标签变成 5px。把指针移到图上(或用键盘走到它),图的角上出现 一个按钮,点开后图会按原始尺寸重新渲染一遍进入对话框。拖动平移,滚轮、双指捏合或 + - 缩放,0 复位,Esc 关闭。如果一张图要缩到一半以下才放得下,它会按 1:1 停在起始角打开,而不是变成缩略图;而无论多大,往回缩总能看到整张图。

切换配色不再重载页面。 旧运行时在任何含图页面上、每次切换主题都会重载整个页面, 理由是 Mermaid 8.x 时代的一条限制。Mermaid 11 支持干净地重新初始化,所以图会就地 重绘,且每张图在重绘期间保持原有高度,读者眼前不会有东西移动。

处于非激活标签页里的图现在也能以正确尺寸渲染。在 display: none 之内,一切文字测量 都返回零,Mermaid 把由此得到的 max-width: 16px 永久写进了 SVG,切回那个标签页也救 不回来。

Markdown、RSS 与打印输出携带围栏源码。打印此前携带的是一个没有任何运行时能触达的 <pre class="mermaid">,且 font-size: 0——所以打印出来的图一直是一段空白。

阅读表面

行内代码是绯色墨迹配极淡底纹,不再是灰色药丸。旧的色块让每个 token 都变成一颗药丸; 现在淡得多的底纹只负责标出 token 的边界,识别工作交给等宽字面、字重与色相,这让 token 密集的段落保持可读,而不是变成一片灰色控件。

系列条现在是与栏同宽的一块面板,而不是一摞链接;分类 chip 静止时安静、在指针下亮起; 导航栏的下拉面板是呼吸式展开而不是弹出;链接悬停从沉闷的藏青转为明亮的天蓝。

构建与基础设施

  • 主题自带的浏览器行为以稳定能力分块发布在 js/chunks/ 下。页面按能力选择 script 标签,而不再自制一份打包,因此这些分块可以跨页命中缓存。
  • bin/generate-config-schema.py 从解析器生成 schema,新的 params 键若没有对应 schema,CI 会失败。
  • 新增可选的 BookManifest 输出,用稳定 id 记录 Book 的顺序。
  • 每一张解析出的图片背后是同一套 media-result 契约。
  • Google Analytics 仅限交互式 HTML 输出,打印与机器输出不再携带。
  • Book 发布任务终于能渲染出 PDF。它从来没有成功过:chrome-headless-shell 需要 非特权用户命名空间,而 Ubuntu 24.04 通过 AppArmor 限制了它,该任务此前每一次运行 都是失败的。

升级

hugo mod get github.com/pgsty/oink@v0.7.0
hugo mod tidy

组件 API 没有变化,因此不需要改动内容。两点值得知道:

  • mermaid 围栏不再渲染 <pre class="mermaid">。站点里针对该选择器的 CSS 现在匹配 不到任何东西;图现在是 figure.td-diagram,内含 .td-diagram__stage
  • 如果站点自己的检查脚本里硬编码了主题版本号,那条断言需要跟着 pin 一起更新。

完整清单见 CHANGELOG.md

1.7 - Oink 0.6.0:沉浸式博客、更稳健的构建、更精简的内部实现

Oink 0.6.0 为现有博客外壳增加沉浸式呈现,用题图、作者、系列、三种索引形态和分享条 补全博客发布能力,并以安全告警取代会中止整个构建的模板错误。

Oink 0.6.0 保留 0.5 确立的组件 API,集中改进它周围的系统:长文阅读、博客发现、 来源标注、版本发布、构建韧性,以及主题自身的可维护性。

本版本没有新增 article 类型,也没有第二套页面外壳。沉浸式阅读只是现有博客外壳的 一种配置,因此文章仍处于原有列表、订阅源、分类、系列与翻页序列中。

概览

  • 博客外壳新增全幅 hero 题图和随正文起步的流式大纲轨道。
  • 博客发布新增作者主页与署名、系列顺序、列表/卡片/表格三种索引,以及本地优先分享条。
  • 引入页面与译文可选用经过校验的来源标注。
  • 主题不再调用 errorf:普通预览告警并安全降级,发布构建继续由 --panicOnWarning 严格把关。
  • 发布信息收敛为一个 release_url,不再重复维护一张事实表。
  • 重复模板计算、页面 bundle 和测试构建显著减少;Font Awesome 等公共创作资产不做裁剪。

沉浸式博客呈现

沉浸式页面由四个相互独立的 front matter 键组成:

featured_image: hero
toc_style: flow
toc_taxonomies: false
sidebar_enabled: false

把同样的键放入栏目 cascade,即可作用于其中的文章。Hugo 会把 cascade 的值同时解析到 声明它的栏目索引及其所有后代上,所以栏目本身要采用这种呈现时,这些键只需写一次。

hero 把解析后的题图铺在开篇背后,并在正文开始前渐隐。常规导航栏仍然可用, 叠在图片上时带有渐隐蒙版。toc_style: flow 让大纲从正文起点开始,滚动后再吸附; toc_taxonomies: false 则移除轨道中的分类词云。 博客外壳默认不显示面包屑;需要时可在页面或 cascade 中设置 breadcrumb: true

这些开关可以独立使用。没有图片时回到普通开篇;没有大纲且关闭词云时不渲染空轨道。 页面的博客归属与输出格式均不改变。

博客补完

params.ui.featured_image 与页面键 featured_image 支持:

模式 呈现
none 不渲染文章题图;默认值
banner 标题上方的 16:9 带框题图
wash 低不透明度铺在文章头部背后
hero 博客页面的全幅背景

所有模式都复用列表缩略图与社交元数据使用的代表图片解析器。没有图片是合法状态, 非 HTML 输出继续保留静态、接近源码的形态。

作者

声明 taxonomies: {author: authors} 即可启用。作者 term 页就是作者主页: 标题是姓名,摘要与正文是简介,代表图片是头像。文章通过 authors: [vonng, oink] 指定作者,书写顺序会被保留。未使用该 taxonomy 时, 旧的 author: 字符串仍作为兼容回退。

系列

声明 taxonomies: {series: series} 即可启用。文章通过 series 加入一个或多个系列, 并可设置 series_weight。带权重的成员按权重升序排列,未加权成员随后按日期升序排列。 文章条带与系列 term 页共用同一个解析器,因此篇次与归档顺序不会漂移。

三种索引形态

默认值 含义
ui.blog_index list listcardstable
ui.blog_index_columns 3 卡片列数
ui.blog_index_size 12 列表/卡片每页文章数
ui.blog_index_toggle false 读者侧三形态循环切换

列表与卡片共用年份分组与分页。单独发布的表格是完整、不分页的归档;开启读者切换后, 三种形态共用当前分页切片,不会在每个分页页重复整张归档。配置决定首屏形态, 本地偏好可以覆盖它。

分享

params.ui.share 是一个有序列表,可选 xblueskymastodonfacebooklinkedinreddithackernewstelegramwhatsapplinepinterestweibochatgptclaudeemailcopy。默认空列表;share: false 可关闭某一页。

分享条只使用平台意图链接与本地复制动作,不加载平台 SDK、iframe、计数器或第三方样式。

页面标注

upstream_link 是每页的来源 URL。配套事实包括 upstream_nameupstream_copyrightupstream_licenseupstream_noticeupstream_refupstream_modified,可来自站点参数、data/upstreams 条目或 front matter。

事实不完整、许可证未知、URL 不安全或类型错误时,主题会告警并省略整行标注; 严格构建会拒绝该告警。upstream_link: "" 可让某页明确退出继承的来源标注。

params.ui.translation_notice 可选地指定权威语言。主题不会把它强加到页面 front matter 中;原生撰写的页面可用 translation_notice: false 退出。

告警替代预览宕机

主题中已经没有 errorf 调用。简单标量由 validate.html 统一校验, 组件自身的记录与标记仍由最了解它们的代码校验。

非法输入遵循同一条规则:

  1. 告警并说明坏值以及安全回退或省略方式;
  2. 不输出不安全、错误或容易误导的结果;
  3. 普通 hugo server 继续工作;
  4. --panicOnWarning 在 CI 与发布阶段阻止构建。

这样既保留严格门禁,也不会让单页笔误拖垮所有预览 URL。

大纲轨道

大纲在同一条 SVG 路径上显示可见范围与当前光标。光标携带 aria-current="location";在减少动画或不支持注册属性的浏览器中, 它会安全回退,不会与高亮线脱节。

修复与精简

  • 挂载内容不再把构建机路径写入编辑、历史或新建子页链接。
  • data-*aria-* 取值统一由一个 HTML 转义器输出。
  • Algolia 凭据不完整时,不再渲染容器、CSS 或 JavaScript。
  • Draw.io 只在页面存在 PNG/SVG 候选图时加载,同一 URL 只检查一次。
  • 页面动作、翻页状态、语言目标与栏目子页按页面或站点复用结果,不再反复扫描全站。
  • 不依赖语言的 feature bundle 可在不同语言页面之间共享。
  • Fields 锚点由字段名派生,并在页内保持唯一。
  • 打印聚合为标题与脚注增加命名空间,而常规页面 ID 不变。
  • 非法输入 checker 把等价案例合并后,内容组件阶段启动 Hugo 的次数从 160 次降至 6 次, 同时保留每一条告警与回退断言。
  • 已删除过期 CSS、i18n 键、废弃的独立 Article 外壳产物、重复 checker 代码和叙事式代码注释; 完整的 Font Awesome 支持范围保持不变。

配置

默认值 说明
ui.featured_image none none / banner / wash / hero
ui.toc_style fixed fixed / flow
ui.toc_taxonomies true 是否在右轨显示分类词云
ui.blog_index list list / cards / table
ui.blog_index_columns 3 卡片列数
ui.blog_index_size 12 列表/卡片分页大小
ui.blog_index_toggle false 读者侧三形态切换
ui.share [] 有序分享目标
ui.translation_notice false 可选的权威语言
time_format_blog 2006-01-02 默认值有变更
time_format_default 2006-01-02 默认值有变更

默认 shell 与 pager 类型列表在适用处仍由 docsbookblogswagger 组成,没有新增 article 类型。

迁移

从 0.5 升级时:

  1. 若不希望使用 ISO 日期,请保留显式的本地化日期格式。
  2. 确认发布命令带有 --panicOnWarning
  3. 把旧 release map 改为 release_url: https://github.com/<owner>/<repo>/releases/tag/<tag>
  4. upstream_attribution 改为 upstream_link, 把 downstream_modified 改为 upstream_modified
  5. 不要把内容迁移为 type: article,请使用上文的博客呈现键。

迁移工具只自动处理 content Markdown 与受支持的 YAML front matter; 站点配置映射仍由维护者明确完成。从 0.4 升级时,继续执行既有顺序: reportmigrate --writecheck

验证

0.6.0 正式版经过以下验证:

  • Hugo Extended 0.160.1 与 0.164.0;
  • 40 个 HTML/打印/Markdown/RSS/LLMS Golden;
  • 85 个迁移测试与 38 个浏览器运行时测试;
  • 严格示例站、Hugo Module、system 字体、旧字体覆盖与非法配置构建;
  • 双语项目站构建及其非浏览器回归;
  • 代表性大站性能测量与真实 EN/ZH 浏览器检查。

本地验证、提交、打标签、推送、消费站锁定与部署仍然是不同的发布状态。

完整变更集

v0.5.0 到 v0.6.0

1.8 - Oink 0.5.0 — 组件 API v5 与收敛后的配置

Oink 0.5.0 用原生 Markdown 形态取代大多数 shortcode,把全部配置键与 front matter 键收敛到三条规则上,删除 0.x 兼容层,并附带把 0.4 站点改写到位的迁移工具。 每一个旧键、旧形态、旧 shortcode 都会让构建失败并指出替代写法,而不是被静默忽略。

Oink 0.5.0 是 API 冻结版本。它包含 1.0 线将要冻结的全部变更:组件 API v5 (原生 Markdown 形态优先,29 个 shortcode 作为完整形态)、按三条规则收敛的配置键与 front matter 键、主题产出物统一的命名空间、0.x 兼容层与无人使用的 Docsy 遗留物的 删除,以及能把 0.4 站点改写到位的迁移工具。任何被退役的键、形态或 shortcode 都会让构建失败,并在错误信息里给出替代写法。

对每一个 0.4 站点来说这都是破坏性升级。请先看概览, 再看迁移指南;中间的参考章节逐项列出每一处变化的新旧形态。

概览

  • 内容:大多数组件直接用 Markdown 写——> [!TYPE] 提示块、{.steps} / {.cards} 列表、{.fields} / {.matrix} / {caption=} / {#id num=} / {tab=} 表格、```filetree / ```gallery / ```echarts / ```infographic / ```checksums 数据围栏、相邻代码围栏成标签页、 带属性行的 Markdown 图片。0.4.2 的 53 个 shortcode 中 32 个删除或改名、8 个新增, 剩下 29 个作为完整形态。scripts/migrations/oink06.py 负责改写内容。
  • 配置:三条规则——开关就是裸的特性名、单键 map 压平、front matter 键 = 站点键去掉 ui.。约四十个键改名或改形,每个旧键都会让构建失败并指出替代。 主题的全部默认值都声明在主题的 hugo.yaml 里。
  • Front matter:不再有 ui: 块;页面覆盖用裸键(section_index: cards), page_context_menu 与站点 map 同形,manualLink* 改为 manual_link*hide_* / exclude_search 删除。
  • 命名空间:主题 class 一律 td-*、data 属性 data-td-*、自定义属性 --td-*、 JS 全局 Oink*oink-* 一族和 Docsy 遗留(leafhas-childnav-* …) 消失。提示块文案改为 callout_* i18n 键。
  • 删除home/** 适配 partial、outputformat.htmltd/render-heading.html、 Docsy community 页面与 params.linkstd/code-dark / td/color-adjustments-dark / td/gcs-search-dark / td/extra 这些 Sass 文件、.td-box*-bg-* 调色 class、 Prism、Open Sans、click-to-copy.jsswaggerui(改名 swagger)。
  • 行为:标题带自链接;print 内容每次构建只渲染一次(修了一个真实的竞态); 三个可缓存的 JS bundle;print 页面加载 8 KB JS 而不是 100 KB;shell 动效在结构上 遵守 reduced-motion;giscus 调色板随主题发布并且只在渲染评论的页面加载。
  • 迁移:内容与 front matter 走 oink06.py report → migrate --write → check, 然后构建——报错就是配置清单。
  • 发布加固:API 冻结前进行了两轮对抗性评审,修复了客户端命名空间迁移、 动作注册表加载顺序、迁移输入 fail-closed、多实例 OpenAPI 嵌入、通用属性与图片 URL 策略,以及消费站配置预检。

组件 API v5

原生形态优先

v5 的原则:Markdown 块能表达的组件就用 Markdown 写;shortcode 只为块表达不了的 情形存在。渲染钩子识别原生形态,所有钩子共用一套属性策略。

组件:原生形态与完整形态

Callout 提示块 , native

> [!NOTE] Title 引用块;[!TYPE]- 折叠 / [!TYPE]+ 展开;可选 {icon="fa-solid fa-x"};类型 note tip important warning caution success danger question example quote details。没有 shortcode。

Tabs 标签页 , native + shortcode

原生:相邻围栏(或表格)加 {tab= group= value=}

Shortcode:tabs group= default= label= tab label= value=/tab /tabs

Steps 步骤 , native + shortcode

原生:1. 列表 + {.steps}

Shortcode:steps 加标题——唯一用 % 分隔符书写的 shortcode(正文是页面级 Markdown);步骤里的标题会进入页内目录。

Cards 卡片 , native + shortcode

原生:链接列表 + {.cards}

Shortcode:cards card title= link= icon= badge= image= image_alt=|decorative= 正文 /card /cards

Fields 字段 , native + shortcode

原生:表格 + {.fields [caption=] [id=] [meta="type required default -"]}——第一列是名称,最后一列是说明,中间列是元数据 chip。

Shortcode:fields label= id= class= field name= type= required= default= 正文 /field /fields——用于块级说明(本列表就是)。两种形态渲染同样的 chip;每个条目有 #field-<name> 锚点。

FileTree 文件树 , native

```filetree {title=} 围栏,每个条目一行 - name[/] # comment {icon= tone= open= type=};2/4 空格、tab 或 tree 缩进。CSS + 原生 <details>;注释列在构建期对齐。没有 shortcode。

Gallery 画廊 , native

```gallery 围栏,每张图一行 ![alt](src) # description {link= class=};alt 必填,条目可缩放。没有 shortcode。

Image 图片 , native

![alt](src "title") 加属性行 {#id num= caption= width= height= link= command= options=},承担图注、编号、链接与 Hugo 图片处理。imgproc 退役;没有图片 shortcode。

表格族 , native

{.full-width} {.fields} {.matrix} {caption=} {#id} {#id num= caption=} {tab= group= value=};站点 class 透传。互斥:fields ⟂ matrix / full-width / num;num ⟂ tab。

Fig / Tbl / Eq / Eg , native + shortcode

原生:图片 / 表格 / $$ 块 / 围栏 + {#id num= caption=}(默认 id fig-tbl-eq-eg-<num>)。

Shortcode:fig tbl eq egeg 必须有图注)。

Xref 交叉引用 , native + shortcode

原生:普通 Markdown 链接(不带 kind)。

Shortcode:xref fig|tbl|eq|eg="…" [page=] [anchor=]

Book 索引 , shortcode

book-toc book-figures book-tables book-equations book-examples——没有 kind= 参数。

代码围栏 , native

围栏属性 {title copy wrap collapse label id tab group value num caption lineNos hl_lines lineNoStart anchorLineNos tabWidth};只有 Chroma。

数据围栏 , native

mermaid plantuml markmap math chem echarts infographic checksums filetree galleryecharts 只做声明式配置,$fn:<name> 回调来自 window.OinkEchartsFunctions

叶子组件 , shortcode

kbd badge param include comment contributors asciinema(原生 <kbd> 同样可用);badge 没有 outlineparam 只接受标量。

Release / OpenAPI , shortcode

release-card release-assets download / swagger redocchecksums 围栏是发布信息的原生形态。

29 个 shortcode:核心 14(tabs tab steps cards card fields field include kbd badge param comment contributors asciinema)、Book 10(fig tbl eq eg xref book-toc book-figures book-tables book-equations book-examples)、Release 3、 OpenAPI 2。嵌套名(tabcardfield)只在父级里合法;每个 shortcode 都校验 参数,未知参数导致构建失败(0.4 里 asciinemaredocswaggerparamcommentsteps 会静默接受任何参数)。

删除的 shortcode 与替代

每个条目上的 chip 是迁移工具键(scripts/migrations/oink06.py migrate --only <key>); manual 表示报告会列出、需要人工修改。

删除的 shortcode 与替代

alert · details · td-page-notice , callout

0.4:alert color=… title=…detailstd-page-notice(都是 % shortcode)、原生 <details><summary>

0.5.0:> [!TYPE] title 提示块,折叠块用 > [!DETAILS]-

tabpane · tab · code-group · code-tab , tabs

0.4:tabpanetab header=…(都是 % shortcode)、code-groupcode-tab

0.5.0:相邻围栏加 {tab= group= value=}(纯代码面板),或 tabstab(混合内容)。

filetree · filetree/folder · filetree/file , filetree

0.4:filetreefiletree/folderfiletree/file;过渡期的 {.filetree} 列表标记。

0.5.0:```filetree 围栏——label 改为 titleopeniconcolorcommentlink 保留。

gallery · gallery/image , gallery

0.4:gallerygallery/image;图片列表 + {.gallery}

0.5.0:```gallery 围栏。

echarts · infographic , datafence

0.4:echartsinfographic shortcode。

0.5.0:同名数据围栏;$fn: 回调不变,js 子围栏改到 window.OinkEchartsFunctions

doc-cards · doc-card · nav-cards · nav-card · card · cardpane · doc-carousel , cards

0.4:Docsy 的卡片族与 OINK 的 doc-cards / nav-cards 包装。

0.5.0:cardscard,或链接列表 + {.cards}card 作为 cards 的子元素保留名字,契约不同。

imgproc , image

0.4:imgproc …(以及发布前短暂存在的 image …)。

0.5.0:![alt](src) + 属性行 {command= options= caption=}

readfile , include

0.4:readfile file=…

0.5.0:include file=… [code=true lang=…]——依次查页面资源、assets、content 相对路径。

围栏 filename= , fencetitle

0.4:围栏上的 {filename="x"}

0.5.0:{title="x"}

badge outline= , badge

0.4:badge … outline=…

0.5.0:去掉 outline——只有一种徽章外观。

example · book-figures kind= , eg

0.4:自闭合的 example … + 围栏;book-figures kind="tbl"

0.5.0:eg/egbook-tablesbook-equationsbook-examples

fields · field(百分号形态) , fieldsdelim

0.4:用 % 分隔符书写的 fields / field(从未发布)。

0.5.0:fields / field

_param · iframe · conditional-text · netlify · 不带 kind 的 xref , reportonly

file:line 报告,人工处理;_param 占位符由 param_placeholders 变换处理。

blocks/cover · blocks/feature · blocks/lead · blocks/link-down · blocks/section , reportonly

0.5.0:layout: landing + sections(数据文件或内联 front matter)。只报告,不改写。

swaggerui , manual

改名为 swagger;改调用名即可。

pageinfo , manual

改写为 > [!NOTE] 提示块。

td/site-build-info/netlify.md , manual

删除,无替代。

相对 0.4.2 新增:tabscardsincludeegbook-tablesbook-equationsbook-examples,以及改名而来的 swaggercardtab 名字未变,但现在是 cards / tabs 的子元素,契约不同。

没有图片 shortcode:渲染钩子统一为 Markdown 图片、fig 与配置里的图片来源解析页面 资源、栏目资源、全局 assets 以及 static / 远程路径,并把图注、编号、链接与 Hugo 图片处理(commandoptions)都放在属性行上——imgproc 能做的它都能做。

块属性策略

所有渲染钩子(表格、图片、代码块、passthrough、引用块、标题)共用一套策略: 白名单键由钩子消费,class 经 token 校验后透传,data-*aria-* 透传, styleon* 与任何未知键让构建失败。内容上的站点 CSS class 仍然合法; 内联样式和事件处理器永远到不了输出。

代码围栏

  • {filename="x"} 改为 {title="x"};同一围栏上 titlefilename 互斥。
  • Prism 路径删除。params.prism_syntax_highlightingstatic/js/prism.jsstatic/css/prism.css 不复存在;Chroma 加 params.highlight_classes(默认 true)是唯一的高亮器。Prism 无法与 tabgroupvaluenumcaption 共存,任何用了标签页或编号示例的 0.4 站点一开启它就已经构建失败。
  • 复制控件依次遵循围栏上的 copy=all|command|true|false、会话类 lexer 默认值 (consoleshell-sessioncommand)、再是 allparams.ui.code_copy: false 只改站点默认值;显式写了 copy 的围栏仍按自己的写法。旧键 disable_click2copy_chroma 会静默压过作者的显式值。
  • Docsy 的 click-to-copy.js(0.3 起从未加载)及其 .td-click-to-copy 样式删除。

配置

三条规则

  1. 布尔开关就是裸的特性名:ui.annotation: true,不是 ui.annotation.enable, 也不是 ui.annotation_enabled。仅存的 _enabled 后缀是 ui.navbar_enabledui.sidebar_enabledui.sidebar_root_enabled——它们的裸名会与同族兄弟键冲突。
  2. 单键 map 压平成标量。只有拥有多个子设置的特性才保留 map——commentsui.feedbackui.page_context_menuui.dark_modeui.command_paletteui.alt_sitetaxonomyprintsearchplantumldrawiomermaidcopyrightui.taxonomy_icons——其中开关型的同时接受裸布尔 (comments: falseplantuml: falsedark_mode: truefeedback: truepage_context_menu: false)。
  3. front matter 键 = 站点键去掉 ui. 前缀,没有例外(见 Front matter)。

键名一律 snake_case、正向、按用途命名。camelCase 只在原样透传给外部运行时的地方 保留(comments.giscus.* 是 giscus 自己的属性名,mermaid.* 交给 mermaid.initialize())。

任何旧键或旧形态都会让构建失败并指名替代——站点配置由 layouts/_partials/config-legacy.html 负责,页面由 layouts/_partials/front-matter-legacy.html 负责——升级就是按报错逐条替换。 没有任何东西被静默忽略。

改名与改形的站点键

0.4 0.5.0 说明
offlineSearchofflineSearchIndexofflineSearchMaxResultsofflineSearchOnServeofflineSearchSummaryLength offline_searchoffline_search_indexoffline_search_max_resultsoffline_search_on_serveoffline_search_summary_length 环境变量覆盖写 HUGOxPARAMSxOFFLINE_SEARCH_ON_SERVE=true(Hugo 的备用分隔符 x_ 无法定位含下划线的键)
ui.showLightDarkModeMenutrue / false / "enable-only (experimental)" ui.dark_mode——true,或 { enable, show_menu } show_menu: true 隐含 enable
ui.scrollSpy.disable ui.scroll_spy 取反;默认 false
ui.no_left_sidebar ui.sidebar_enabled 取反
ui.breadcrumb_disable ui.breadcrumb 取反;默认 true
print.disable_toc print.toc 取反;默认 true
disable_click2copy_chroma ui.code_copy 取反;只设默认值
ui.readingtime.enable ui.reading_time 裸布尔
ui.ul_show ui.sidebar_expand_levels 默认 2
Taxonomy.taxonomyCloud.taxonomyCloudTitle.taxonomyPageHeader taxonomy.cloud.cloud_title.page_header 一个小写 map
ui.annotation.enableui.image_zoom.enableui.keyboard_nav.enable ui.annotationui.image_zoomui.keyboard_nav 裸布尔
ui.typography.preset ui.typography technical | system;环境变量覆盖 HUGO_PARAMS_UI_TYPOGRAPHY=system
ui.pager.types ui.pager_types [docs, book, blog]
markmap.enable markmap 裸布尔
content_widthslim | norm | wide reading_widthslim | normal | wide Book 正文测量;body class td-book-content--normal,令牌 --td-book-content-normal
ui.docs_root ui.docs_sidebar_root section | home
github_url github_repo 编辑、历史、Issue 链接由仓库推导
algolia_docsearch search.algoliaappIdapiKeyindexName 构建失败
rss_sections 删除 从未被读取
params.links.user[] / .developer[] 删除 Docsy community 页面已删
plantuml.enabledrawio.enable 不变,且 map 接受 plantuml: false / drawio: false
comments.enable 不变,且接受 comments: false
comments.giscus.lightTheme / darkTheme 默认不设 默认使用主题自带调色板(见样式与资源

主题的全部默认值现在都在主题 hugo.yaml 里声明并附取值域注释。以前只是模板回退、 现在正式声明的有:offline_search: falseoffline_search_summary_length: 70ui.breadcrumb: trueui.reading_time: falseui.dark_mode: falseui.docs_sidebar_root: sectionui.sidebar_icon_policy: allui.section_index_columns: 2ui.code_copy: trueprint.toc: trueprint.section_break_wordcount: 50markmap: falseplantuml.enable: falsedrawio.enable: falsegithub_branch: main。两个默认值保持派生并如此记录: ui.quick_links(来自 docs_sectionblog_section)和 ui.taxonomy_icons (内置 categories/tags 图标)。ui.sidebar_expand_levels(2)与 ui.sidebar_menu_truncate(2000)的模板回退与声明值一致。

保持不变、继续可用的 Docsy 键:github_repogithub_project_repogithub_branchgithub_subdirpath_base_for_github_subdirtime_format_blogtime_format_defaultversionversionsversion_menuversion_menu_pagelinksarchived_versionurl_latest_versioncopyrightdescriptionauthorgcs_engine_idsearch.algolia.*mermaidplantuml.*drawio.*ui.sidebar_menu_compactui.sidebar_menu_foldableui.sidebar_menu_truncateui.sidebar_cache_limitui.sidebar_root_enabledui.feedback.{enable,reasons}

大声失败,而非静默

同时配置多个搜索后端(offline_searchgcs_engine_idsearch.algolia)现在会让 构建失败(以前只警告)。PlantUML 不设 plantuml.svg_image_url、Diagrams.net 不设 drawio.drawio_server、Algolia 缺任一凭据,与 0.4 一样构建失败。构建错误遵循同一 形状——<component>: <subject> <expectation>; got <value> at <position>——全小写、 位置只用一个介词、配置错误给出完整的 params. 路径;不再指向文档 URL。

Front matter

页面键 = 站点键去掉 ui. 前缀,front matter 里不再有 ui: 块。栏目 cascade 同理 (cascade: { params: { section_index: cards } } 或直接裸键)。一个解析器 (ui-param.html)为每个可按页覆盖的 params.ui.* 设置先读页面值、再读站点值: sidebar_menu_compactsidebar_menu_foldablesidebar_expand_levelssidebar_width_minsidebar_width_maxsidebar_item_overflowsidebar_headingssidebar_enabledsection_indexsection_index_columnslastmod_commitbreadcrumbscroll_spycode_copykeyboard_navbook_draft_banner;再加上显式页面键 navbar_enablednavbar_autohidefooter_styleannotationfeedbackimage_zoomreading_timepage_context_menucommentspage_widthreading_width

0.4 front matter 0.5.0
params: { ui: { <key>: … } }(任何键) 顶层(或 params: 下)的 <key>: …
params.ui.image_zoom.enable image_zoom: true | false
params.ui.keyboard_nav.enableparams.ui.annotation.enable keyboard_navannotation(裸布尔)
annotation: { enable: … } annotation: true | false
context_menu page_context_menutrue | false,或 { enable, assistant_links }
assistant_links(顶层) page_context_menu: { assistant_links: false }——页面只能收窄站点策略
hide_readingtime: true reading_time: false
hide_feedback: true feedback: false
exclude_searchexcludeSearch search_exclude
content_width: norm reading_width: normal
manualLinkmanualLinkTitlemanualLinkTargetmanualLinkRelref manual_linkmanual_link_titlemanual_link_targetmanual_link_relref
body_class: td-no-left-sidebar sidebar_enabled: false
contributingUrl 随 community 页面删除
Icon icon(Hugo 不区分大小写;主题按小写读取)

未变的页面键:toc_hidetoc_rootnotocno_printno_listsimple_listhide_summarysidebar_root_forsidebar_dividersidebar_expandedsidebar_root_menusidebar_root_link_selfsearch_keywordssearch_boostpagerlandingsectionsbook_numberbook_statusreleaserelease_productsrelease_group_by_productupstream_attributiondownstream_modifiedbylineauthorbody_class

scripts/migrations/oink06.py migrate --only frontmatter 会改写以上全部页面键, 包括 cascade: map 与列表里的。

模板、partial 与布局

删除项,以及复制或调用过它们的站点应改用什么:

0.4 0.5.0
_partials/home/**(18 个适配器)、_partials/home-data.html _partials/landing/**landing/home-data.html
_partials/outputformat.html .Store.Get "tdOutputFormat"html | print | markdown | rss,每个 base 模板都会设置)
_partials/td/render-heading.html 以及调用它的站点侧 _markup/render-heading.html 主题自己的 _markup/render-heading.html——删掉站点覆盖
layouts/community/list.htmllayouts/docs/community.html_partials/community_links.html 无——Docsy community 页面已删
_partials/taxonomy_terms_article.htmltaxonomy_terms_article_wrapper.htmltaxonomy_terms_cloud.html taxonomy-terms-article.htmltaxonomy-terms-article-wrapper.htmltaxonomy-terms-cloud.html
_partials/taxonomy_terms_clouds.htmlcode/markdown-escape.html 0.4 里就已是死文件;活的是 shell/taxonomy-terms-clouds.htmlcontent/markdown-escape.html
_shortcodes/swaggerui.html _shortcodes/swagger.html
从 0.4 复制的 layouts/_default/_markup/render-* 保留任何覆盖前先与 0.5.0 对比——每个钩子都变了

有覆盖的站点还应知道的模板层变化:

  • 侧栏两个来源——内容树与显式的 data/docs_nav.json——每一行都经 shell/sidebar-node.html 渲染。shell/config.html 仍是品牌、logo、栏目配置的 唯一解析器。
  • 每个渲染内容的布局都调用 content/render.html 而不是 .Content (图片缩放候选扫描在那里进行)。
  • Print:print/page-content.html 通过 partialCached 让每页的 print 内容在一次 构建里只渲染一次;print/render.htmlprint/content.htmlbook/print.html 与各 single.print.html 布局都读它。复制过 0.4 print 模板的站点应删掉副本—— 0.4 的流水线在"本身是 section 的章节被父级再次聚合"时会在页面 store 上竞态。
  • 标题渲染钩子归主题所有。每个标题带 id 与悬停显示的自链接 (.td-heading-self-link,文案 ui_heading_self_link);print 与 RSS 会剥掉链接。
  • DocSearch 容器只有一个 #td-docsearch;写死的 #docsearch-0/1 两个 id 没有了。

样式与资源

一个命名空间

主题产出的一切都有前缀,scripts/check-namespace.py 守着这条线。站点里挂在旧名字 上的 CSS 或 JS 必须迁移:

类别 0.4 0.5.0
class oink-*(landing 子系统)、leafhas-childactive-pathis-openis-activeis-hiddenis-disabledlanding-headerlanding-navlanding-containerarticle-metapageinfonav-*taxonomy-*ul-N 全部 td-*;站点页头与导航是 td-site-headertd-site-navtd-site-container
data 属性 data-oink-* data-td-*
自定义属性 --oink-*--term-* --td-*
JS 全局 oink* / echartsFunctions window.OinkActionsOinkEchartsFunctionsOinkLandingOinkSearchEngineOinkSurfaceCoordinator
作者标记(无前缀,不变) {.steps} {.cards} {.fields} {.matrix} {.full-width}

Sass 与令牌

删除的 Sass 文件(站点 _styles_project.scss 若仍 import 会编译失败): td/code-darktd/color-adjustments-darktd/gcs-search-darktd/extratd/extra/bs-defaultstd/extra/buttonstd/extra/main-containertd/extra/navbartd/boxes.td-box.td-box--<color>.td-box--height-*)、 td/colors.-bg-<name>.-text-<name>)。删除的变量:$td-box-colors$td-print-font-name$td-enable-webfonts

改名或新增的令牌:--td-book-content-norm--td-book-content-normal.td-book-content--norm--normal);--td-print-font-family 角色保留但在 两种预设下都跟随 --td-body-font-family;新增 --td-motion-duration-fast (100 ms)、--td-motion-duration(150 ms)、--td-motion-duration-slow(250 ms), shell 的每个过渡都引用它们,prefers-reduced-motion: reduce 把它们置零。

排版:UI 与正文使用 Inter(可变字重,Latin/Latin-ext/西里尔/希腊/越南语子集按 unicode-range 提供;CJK 与 emoji 回落到平台字体栈)、无边框行内代码、安静的代码 卡片加悬停显示的复制控件、Mintlify 风格字段行、页末两个文本链接的翻页器、卡片式 栏目索引上方的分隔线。Open Sans(18 个 woff2 子集、652 KB,为一个仅打印用的字体 发布到每个站点)已删除;想在纸面上换字体的站点自己在样式表里设 --td-print-font-familysystem 预设依旧不请求任何品牌字体。

shell 图标改为由 shell/icon.html 分发的 Font Awesome class 对 (<i class="td-shell-icon td-shell-icon--<name> fa-solid fa-…">)而不是内联 SVG;--td-shell-icon-size 设置尺寸盒。

发布的资源

  • 三个 JavaScript bundle 取代按特性组合生成的单个 bundle:js/actions.jsjs/core.js 在每一页字节相同、可以缓存;只有一个小的 js/page-<hash>.js 随页面变化。ECharts 单独一个 <script>。print 输出加载 7.9 KB 而不是 100 KB。
  • static/css/giscus-oink-{light,dark}.css 没有了。调色板以 assets/css/giscus-{light,dark}.css 随主题提供,只在渲染评论的页面发布, 并且是 comments.giscus.lightTheme / darkTheme 的默认值;指向旧路径的站点 删掉那两行即可(或写 giscus 内置主题名 / 自己的样式表 URL)。
  • 删除:static/js/prism.jsstatic/css/prism.cssstatic/webfonts/open-sans/assets/js/click-to-copy.jsVENDOR.json 与 vendor 目录哈希已重新生成。

i18n

  • 提示块文案改为带命名空间的键:callout_notecallout_tipcallout_importantcallout_warningcallout_cautioncallout_successcallout_dangercallout_questioncallout_examplecallout_quotecallout_details。 主题不再占用 noteexamplequote 这类裸顶层键;在自己 i18n/ 里覆盖过 这些键的站点需要改名。
  • 删除:community_joincommunity_introducecommunity_learncommunity_usingcommunity_developcommunity_contributecommunity_how_tocommunity_guideline
  • 新增:ui_heading_self_linkui_field_self_link(各语言英文兜底;中文变体已审校)。
  • 32 个语言文件保持键完全一致(174 个键)。

数据文件

  • data/home/<lang>.yaml(或 data/home.yaml)必须列出 sections;隐式的 hero → metrics → capabilities → principles → cta 顺序没有了,缺失会构建失败。
  • 胖页脚只读 data/footer/<lang>.yaml(或 data/footer.yaml)。data/home 里的 footer 键会构建失败并指出新位置。
  • data/landing/<key>/<lang>.yamldata/docs_nav.jsondata/download/<key>.yamldata/brand.yaml 不变。

行为与输出变化

  • 标题带悬停显示的自链接;print 与 RSS 输出剥掉锚点,Markdown 输出 (RenderShortcodes)不受影响。
  • print 聚合每次构建对每页内容只渲染一次。0.4 里"本身是 section 的章节"会被自己的 print 输出和父级的 print 输出并发渲染两次,两次渲染在页面 store 上竞态——可见 症状是 _print/ 里偶发重复的 td-code-… id。
  • <main> 不再带 role="main";侧栏 <aside> 不再重复内层 <nav> 的 “Section navigation” 标签。
  • ui.dark_mode: true 同时开启暗色调色板与 System / Light / Dark 菜单; 单独的 show_menu: true 隐含 enable
  • ui.code_copy: false 只设默认值(见代码围栏)。
  • 首页渲染导航栏;提示块标题满足对比度;Gallery 条目与其它图片同等享有缩放; 标签页运行时保住运行边界、唯一同伴 id 与 print 标题;FileTree 与整个 shell 遵守 prefers-reduced-motion
  • 表格渲染钩子也在 print 与 RSS 输出里运行,表格在交互式 HTML 之外也保留图注、 编号与滚动容器;两种形态的 fields 产出同一渲染,每个条目都有 #field-<name> 锚点。
  • llms.txt 读取 params.ui.docs_section,并带描述列出文档页面。
  • 图片解析器的错误按调用方标注(Markdown 图片是 image:fig 是 shortcode 名), 配置里的图片来源与内容遵循同一 URL 策略。

发布候选加固

最终评审发现了一个系统性迁移缺口:模板已经输出新的 data-td-* 契约,若干运行时与 测试 mock 却仍读取旧 dataset 名称;动作清单还位于同步动作注册 bundle 之后,注册表 可能以空状态初始化。这两项现已修复,并新增结构检查阻止回归。页面动作、命令面板搜索、 代码复制与折叠、反馈页面标识、折叠控件文案、Giscus 主题、图片缩放标签与 Asciinema 计时标签,现在会在测试与真实渲染 DOM 中使用同一组属性。

同一轮加固还完成了以下修正:

  • reportmigratecheck 遇到不存在、空、不可读或非 UTF-8 的目标时直接失败, 不再给出误导性的“无残留”结果;JSON front matter 改用 JSON 解码器解析;
  • Markdown、RSS 与聚合打印输出都会执行旧 front matter 检查,页面评论等覆盖项严格 校验布尔值与 map 形态;
  • 数据围栏与提示块会保留允许的 data-* / aria-* 属性,同时图表布尔参数保持严格;
  • 每个 Swagger 与 ReDoc 嵌入都有唯一实例,不再覆盖 window.onload 或发布 window.ui
  • shell Logo、Wordmark 与配置型 Featured Image 统一使用共享 URL 策略;
  • 新增 scripts/check-site-markup.py,从消费站解析后的配置中检查原生形态必需的三项 Goldmark 设置。

迁移指南

顺序很重要:先内容(工具默认 dry-run 且幂等),再让构建错误驱动配置与布局的修改。

改写内容前,先确认消费站能够渲染原生形态:

python3 path/to/oink/scripts/check-site-markup.py --site ~/pgsty/example.com

1. 盘点

python3 scripts/migrations/oink06.py report --sites ~/pgsty/example.com --md report.md --json report.json

报告逐站列出工具会改写的每一个 0.4 结构、不会碰的(带 file:line 与原因), 以及改写之后仍会被标记的残留。

2. 内容与 front matter

python3 scripts/migrations/oink06.py migrate --site ~/pgsty/example.com          # dry run:diff + 计数
python3 scripts/migrations/oink06.py migrate --site ~/pgsty/example.com --write  # 原子改写
python3 scripts/migrations/oink06.py migrate --site ~/pgsty/example.com --write  # 第二次:changed 0
python3 scripts/migrations/oink06.py check   --site ~/pgsty/example.com          # 残留旧语法 → exit 1

变换按应用顺序:frontmatter(页面键,含 cascade:)、calloutparam_placeholderstabsfiletreegallerydatafencecardsfieldsdelimimageincludefencetitlebadgeegreportonly--only <key> 选子集。围栏内的文本永远不改写;TOML/JSON front matter 只报告不改写。 在 11 个自有站点上,front matter 变换触及 628 个文件、零 finding。

报告会列出需要人工处理的项:swaggeruiswaggerpageinfo → 提示块、 _param 占位符、iframe/conditional-text/blocks/*、不带 kind 的 xref, 以及必须改成 window.OinkEchartsFunctions 条目的 echarts js 子围栏。

3. 配置

构建站点。每个旧键都会带着替代写法失败:

ERROR params.offlineSearch was renamed: use params.offline_search
ERROR params.ui.typography.preset was flattened: use params.ui.typography: technical | system
ERROR params.ui.showLightDarkModeMenu was renamed: use params.ui.dark_mode.show_menu
ERROR params.print.disable_toc was renamed: use params.print.toc (inverted)
ERROR params.rss_sections was removed: the key was never read; delete it

一份典型的 0.4 hugo.yaml 变成:

params:
  offline_search: true
  offline_search_on_serve: true
  offline_search_index: summary
  offline_search_summary_length: 70
  offline_search_max_results: 10
  reading_width: normal            # 原 content_width: norm
  markmap: true                    # 原 markmap: { enable: true }
  print:
    toc: true                      # 原 disable_toc: false
  comments:
    enable: true
    type: giscus
    giscus:
      repo:  # lightTheme / darkTheme 两行删除
  ui:
    typography: technical          # 原 typography: { preset: technical }
    dark_mode: true                # 原 showLightDarkModeMenu: true
    sidebar_expand_levels: 2       # 原 ul_show: 2
    scroll_spy: false              # 原 scrollSpy: { disable: true }
    reading_time: false            # 原 readingtime: { enable: false }
    image_zoom: true               # 原 image_zoom: { enable: true }
    keyboard_nav: true             # 原 keyboard_nav: { enable: true }
    annotation: true               # 原 annotation: { enable: true }
    pager_types: [docs, book, blog] # 原 pager: { types: [...] }
    docs_sidebar_root: section     # 原 docs_root
    breadcrumb: true               # 原 breadcrumb_disable: false
    sidebar_enabled: true          # 原 no_left_sidebar: false
    code_copy: true                # 原 disable_click2copy_chroma: false(顶层)

删除 params.linksprism_syntax_highlightingrss_sectionsgithub_url (改用 github_repo)、algolia_docsearch,以及 giscus 的 lightTheme / darkTheme URL。

4. cascade 与栏目索引

设置过 params.ui.*cascade 改为裸键——_index.md 里的由变换处理, hugo.yaml 里手写的 cascade 要自己检查:

cascade:
  type: blog
  params:
    sidebar_menu_compact: false    # 原 params.ui.sidebar_menu_compact
    sidebar_expand_levels: 3       # 原 params.ui.ul_show

5. Sass、布局与站点脚本

  • assets/scss/_styles_project.scss:删掉 @import 'td/color-adjustments-dark''td/code-dark''td/extra''td/extra/bs-defaults''td/gcs-search-dark'; 去掉针对 .td-navbar-cover.td-navbar-transparent.td-box*-bg-*oink-*--oink-* 的规则。
  • assets/scss/_variables_project.scss:去掉 $td-print-font-name$td-enable-webfonts$td-box-colors
  • layouts/partial "home-data.html" / "home/section.html" 换成 landing/…partial "outputformat.html" 换成 .Store.Get "tdOutputFormat";删掉调用 td/render-heading.html_markup/render-heading.htmltaxonomy_terms_* 调用改名;其它复制过的 partial 或钩子在保留前先与 0.5.0 对比。
  • 站点 JS 与测试:oink-* id 与 data-oink-* 属性改为 td-* / data-td-*; 动作清单是 #td-action-manifest;每页 bundle 是 js/page-<hash>.js,旁边是 js/actions.jsjs/core.js
  • 站点 i18n/ 覆盖:notetip… 改名为 callout_notecallout_tip…。

6. 数据

footer: map 从 data/home/<lang>.yaml 移到 data/footer/<lang>.yaml; 确认 data/home/<lang>.yaml 列出了 sections

7. 校验

hugo --printPathWarnings --panicOnWarning
python3 scripts/check-output-security.py --public public --base-url https://example.com/

然后检查变化最大的几个面:一个有代码标签页与提示块的文档页、一个有图片的页面 (缩放开与关)、一个 Book 章节及其 _print/ 聚合、index.md Markdown 输出、 一个 RSS feed、首页 landing、暗色调色板。v0.5.0 标签推送后再固定它:

hugo mod get github.com/pgsty/oink@v0.5.0
hugo mod tidy

兼容性

  • Hugo Extended 0.160.1 仍是最低版本;CI 跑 0.160.1 与 0.164.0,并新增以 Hugo Module 模式构建消费站点。
  • 模块路径仍是 github.com/pgsty/oink;消费站点仍不需要 Node.js、CDN 或构建期下载。
  • 对 0.4 没有兼容层:改名的键、形态、shortcode、partial、class 要么构建失败要么 直接消失,这是有意的。旧键报错就是迁移指南;其中源自 Docsy 的条目同样服务 从 Docsy 迁来的站点。
  • 合理的 Docsy 键保持不变(见配置下的清单);sidebar_* 一族名字未动。
  • 交互特性仍然默认关闭:offline_searchui.image_zoomcommentsui.feedbackui.dark_modepage_context_menu.assistant_links 需要站点主动开启。

验证

主题 CI:34 个检查脚本(i18n 键一致、分类法、字体令牌、导航 / 组件 / 内容原语 / Book 契约、运行时隔离、侧栏图标、搜索、动作、命令面板、阅读、发布资产、下载、 landing、Book 迁移、共享场景、键盘、shell、命名空间、参数、vendor 清单、输出结构 与安全、30 个面的四态 goldens、代码块、内容与媒体原语、图片缩放、Gallery、组件)、 浏览器运行时单测、迁移工具测试(85)、在 Hugo 0.160.1 与 0.164.0 上无警告构建的 fixture 站点、system 排版预设、遗留 Sass 覆盖、非法预设构建失败,以及新增的 Module 模式消费站点构建。scripts/check-params.py 为每个退役键各构建一个站点 (32 个站点键、14 个页面键),断言每个都失败并指名替代。

本站按上述迁移后在 0.5.0 上无警告构建。最终门禁在 Hugo 0.160.1 与 0.164.0 上各跑 一遍完整矩阵;媒体断言允许各支持版本使用不同的不透明派生缓存哈希,同时仍严格检查 渲染 URL 结构、尺寸、alt 语义与 Zoom 排除。源码校验、本地附注标签、远端标签发布、 消费站固定版本与部署仍是可独立审计的门禁。

完整变更集

完整源码差异见 v0.4.2 到 v0.5.0 与主题的 CHANGELOG.md

1.9 - Oink 0.4.0 — 面向完整发布流程的场景组件体系

Oink 0.4.0 在一个合并发布中交付连续阅读与发布界面、可复用 Landing 页面、 带稳定引用的 Book 出版能力,以及键盘优先的站点外壳。

Oink 0.4.0 完整交付场景组件体系。原始设计将 Reading & Release、Landing 与 Book 分别放在 0.4、0.5、0.6 三个里程碑中;公开版本将三条轨道合并到一个已签名的 v0.4.0 标签,让消费站接入一套连贯契约,而不是一串彼此依赖的预览版本。

本版本继续保持本地优先:消费站仍然只需 Hugo Extended 与 Go,不需要 Node.js、浏览器端 API 或 CDN。所有交互都采用渐进增强;HTML、打印、Markdown 与 RSS 输出会保留理解对应界面所需的完整内容。

版本亮点

阅读与发布

文档、Book 与博客页面现在拥有连续阅读 Pager,其顺序来自读者在侧栏看到的同一棵扁平导航树。上一页和下一页也会作为同源 rel 元数据写入文档头。显式导航数据、纯链接条目、侧栏分组和博客时间顺序仍保留各自语义,不会意外变成阅读目的地。

数学公式可以通过 Goldmark passthrough 使用主题内置的本地 KaTeX 渲染器。暂时无法启用 passthrough 的站点,可以使用严格、无参数的 eq 逃生舱渲染块公式;只有显式提供 num 时,同一短代码才进入 Book 的编号公式模式。

发布页面可以从本地 front matter 渲染发布事实、发布卡片、校验和与资产清单,无需浏览器查询 GitHub。经过验证的 data/download/<key>.yaml 模型同时供 download 短代码与 Landing 下载分区使用,区分滚动渠道、固定版本渠道与明确的待发布状态。

完整契约见顺序阅读与数学公式版本发布与下载

Landing 页面

数据驱动的首页渲染器现在也是普通页面可用的 layout: landing 外壳。页面可以使用内联数据,或从 data/landing/<key>/ 读取带语言回退的记录,再组合 21 种内置分区,包括价格、对比表、命令框、步骤、时间线、代码面板、案例、下载与条形图。

所有事实都在构建时留在本地。揭示、数字递增、复制、主题图片和紧凑菜单等可选行为只在 Landing 页面需要时加载。关闭 JavaScript 后内容仍然完整;跑马灯可以因焦点或用户选择暂停,遵守 reduced motion,并向辅助技术隐藏重复轨道。

数据解析、全部 21 种分区、本地事实规则与输出矩阵见 Landing 页面

Book 出版

长篇手册可以在既有文档外壳上声明 Book 元数据。章节获得草稿标签、当前页侧栏标题,以及语义化的 figtbl、编号 eq 与按当前语言解析的 xref 目标。整书图表目录与目录树复用同一套注册表。

可选的整书打印文档会把跨章节组件链接改写为文档内引用,并为重复标题 ID 加命名空间。配套迁移工具默认 dry-run、可重复执行,提供可复现的 TPME、DDIA 与 pg-internal 配方、机器可读报告、歧义跳过项,以及第二次运行零变更检查。

创作与迁移契约见 Book 出版

键盘与站点外壳

站点外壳现在支持单键阅读导航:ws 在侧栏移动,ad 折叠或展开分组, jk 在页面大纲间移动,qe 沿连续 Pager 翻页。h 切换会话级阅读模式; ltfc 分别切换语言、主题、搜索与命令界面。所有按键都会让位于编辑控件、输入法组合、按住的修饰键与已打开的对话框。

导航栏现在覆盖文档、博客、taxonomy 与 Swagger 布局,并以一个紧凑状态取代第二套移动菜单。页面操作移到面包屑行,成为以「复制 Markdown」为主操作的分裂按钮。页脚支持经过验证的 fatslimnone 三种样式,读者还可以折叠胖页脚的链接网格并保留该偏好。

参见键盘导航导航与菜单

兼容性与行为变化

  • 最低支持版本仍为 Hugo Extended 0.160.1。
  • 模块路径仍为 github.com/pgsty/oink;消费站仍不需要前端工具链。
  • Pager 默认作用于 docsbookblog 内容类型;需要退出的站点可配置明确的类型列表,或在页面设置 pager: false
  • / 现在打开完整搜索,\ 打开纯命令模式;命令面板内部的 > 前缀保持不变。
  • params.footer_icpparams.footer_icp_url 被一个行内 Markdown 值 params.footer_center_info 取代;显式空字符串会隐藏中间区域。
  • params.ui.navbar_enabled 默认为 true,可以在全站、section cascade 或单页覆盖。
  • 旧首页数据与 Docsy block 短代码继续兼容;新的 Landing 页面应使用标准分区注册表。

升级到 0.4.0

  1. 固定已签名标签并整理模块图。
  2. 使用过 ICP 页脚参数的站点改用 footer_center_info
  3. 检查 Pager 默认值、/\ 快捷键,以及站点自己的导航栏与页脚覆盖。
  4. 只有对比清楚本地差异后,才删除复制出来的主题 partial。
  5. 构建有代表性的文档、博客、Landing、Book、打印、Markdown、移动端与明暗模式界面。
hugo mod get github.com/pgsty/oink@v0.4.0
hugo mod tidy
hugo --gc --minify

消费站检查清单见本站的 0.4.0 升级指南;主题仓库保留冻结的 PRD 5 迁移参考

验证

已签名标签与发布主题源码指向同一提交。主题 CI 覆盖 Hugo Extended 0.160.1 与 0.164.0、32 个语言包、vendor 资产、运行时单元测试、全部 PRD 4/5/6 契约,以及零警告示例站构建。项目站固定公开标签,并覆盖双语源码、渲染 Markdown、站内链接、替代配置构建、浏览器行为与完整多语言 WCAG AA 矩阵。

有代表性的文档站、门户、Book 与归档站也在关闭 workspace 的情况下,从公开 v0.4.0 模块完成构建。

源码验收、公开标签、消费站固定版本与线上部署是不同的证据门禁。发布这篇注记不能代替站点流水线完成后的线上 URL 冒烟检查。

完整变更集

完整源码差异见 v0.3.0 到 v0.4.0

1.10 - Oink 0.3.0 — 写作、导航与更轻的页面

Oink 0.3.0 带来增强代码块与代码分组、日常内容组件、嵌套导航与命令面板、 语义化字体预设,并从每个页面移除了 jQuery。

发布门禁:上面的标签必须能够公开解析,项目站必须固定到该精确标签,并且线上检查必须通过。在此之前,请把当前源码页面视为发布候选材料。

Oink 0.3.0 是围绕写作与导航的一个版本。写页面时,代码块有了现代化的呈现,并补齐了一组每天都会用到的小组件;读页面时,多了嵌套导航与命令面板;而所有页面都实实在在变轻了——jQuery 已被移除。

模块路径、最低 Hugo 版本以及 Hugo-only 的消费者构建方式均未改变。有三项改动可能影响既有站点,详见破坏性变更

版本亮点

代码块与代码分组

普通围栏代码块现在会渲染出完整的代码外壳:可选的文件名、语言标签、由服务端输出而非脚本注入的复制按钮、可选的自动换行,以及长代码的折叠。Hugo 原生的高亮选项——行号、行锚点、hl_lines、制表符宽度——行为完全不变。

复制行为是确定的,而不是靠猜。consoleshell-session 这类会话 lexer 默认只复制命令,不含提示符与输出;其余语言默认复制整块。对于无法区分二者的 lexer, copy=command 会直接报错——静默复制错误内容比构建失败更糟。

code-group 短代码把包管理器、语言、平台这类并列选项组织成同步切换的标签页,并带稳定的 URL hash,因此一个链接可以直接打开读者需要的那个变体。既有的 tabpane 内容继续工作,存储键也保持不变。

完整参数契约见代码块

日常内容组件

在既有的大型组件之外,0.3.0 补上了作者每天真正会用的小组件:badgekbdfieldsfiletreegallery,以及可选启用的 image_zoom。它们全部输出语义化 HTML,其中非交互组件不加载任何 JavaScript,并且每个组件在打印和 Markdown 输出下都有明确定义的呈现方式。

独立的公共 icon 短代码仍然有意推迟:在这套 API 被认真设计出来之前,组件只使用私有的、带白名单的图标注册表来做自身装饰。

各组件契约见组件

导航与命令面板

顶层菜单在桌面端支持一级下拉,在移动端有对应的折叠面板;父级链接与展开控件分别独立操作,因此父级本身始终可以点击跳转。平铺菜单不受影响。

本地搜索升级为命令面板,具备三种模式:空查询提供快捷入口与页面操作,文本查询返回分组的页面结果,> 前缀则只搜索命令。页面可以提供 search_keywords、正值的 search_boost 以及规范化的排除标记;Lunr 路径与 CJK 子串路径应用同样的加权。

页面操作与面板命令现在走同一套注册表,因此复制文本、在 ChatGPT / Claude 中打开、查阅源码、查阅编辑历史、打印、切换主题、语言或版本,无论从哪里触发行为都一致。助手提示词在激活时解析浏览器 URL,保留实际部署域名、查询参数与片段;历史链接则使用“编辑此页面”的同一仓库路径。助手入口默认关闭,站点必须显式设置 params.ui.page_context_menu.assistant_links: true 才会启用。激活后完整 URL 会离开本站,因此不要在 query 或 fragment 中放置秘密信息。

在可编辑控件之外按 /,可以直接以命令模式打开面板。Cmd/Ctrl-K 仍然是通用入口;这个单字符快捷键不会抢占 input、textarea、select 或 contenteditable 区域中的输入。

侧栏新增图标密度策略 allgroupsnone。兼容默认值仍是 all,起步示例站选用 groups

完整配置面见迁移参考

字体预设

字体选择被收敛到七个语义化的 --td-*-font-family 角色之后,覆盖界面、正文、标题、代码、展示文字、元信息与打印输出。本次提供两个经过校验的预设:technical 保持当前 Oink 外观,system 使用平台字体栈且完全不请求 Oink 品牌字体。既有的 Docsy 与 Bootstrap Sass 字体变量会作为这些角色的初值,因此原有覆盖继续有效。

这只是更大范围设计令牌工作中的字体一层。颜色、表面、圆角、密度与外观预设不在本次发布范围内。

参见字体令牌

更轻的页面

jQuery 已被移除。此前它以阻塞渲染的方式出现在每个页面的 <head> 里——在任何内容之前先加载 87.5 KB——而主题自身的架构原则是只在用到的页面加载对应运行时。文档壳层没有任何地方需要它,而由它驱动的 offline-search.js 早已被命令面板取代。

另外两项开销是被消除而不是被接受的。当前输出格式改为从 page store 读取,不再在每次构建中重复推导数千次;文档壳层配置按语言缓存。在 576 页的构建上,这让模板耗时从 357 毫秒降到 72 毫秒,且生成结果逐字节一致。CJK 搜索改为在建立索引时一次性折叠字段,不再在每次击键时把整个语料重新小写化——800 篇文档的查询从每次击键 3.44 毫秒降到 0.34 毫秒。

在所测项目站快照上,移除 jQuery 与被替代的搜索运行时后,一个典型文档页的 CSS 与 JavaScript 合计减少约 88 KB。后续候选资源变化会使精确总量有所浮动。

正确性与本地化

本版本还修复了几项不太显眼但会影响正确性的缺口。Markdown 页面只在当前语言确实发布 llms.txt 时才链接它,索引也不再把站外菜单外壳当作内容。内部自定义命令在子路径部署下保持正确前缀,共用内容类型则会解析到正确的产品 root。归档版本横幅与 Giscus 回退文本已经本地化;打印与 Markdown 输出无论属性使用何种引号,都能清理只用于 Image Zoom 交互的属性。旧搜索链接也会对查询文本做百分号编码,不再遇到 & 就截断查询。

主题 CI 现在会真正运行浏览器 runtime 测试,不再把 Hugo 能打包脚本当作唯一信号。终端录屏也会等待配置字体加载后再适配播放器,避免使用 fallback 字体计算错误尺寸。

破坏性变更

不再加载 jQuery。 第三方清单此前把它列为界面基础的一部分,因此消费站自己的脚本可能依赖全局 $。主题的任何功能都不需要它。仍然需要的站点,请通过项目 JavaScript 自行打包:

<!-- layouts/_partials/hooks/head-end.html -->
<script src="{{ (resources.Get "js/jquery.min.js").RelPermalink }}"></script>

移除 static/js/tabpane-persist.js assets/js/code-tabs.js 已接管旧的持久化契约,保留了 td-tp-persist 存储键与 data 属性,因此已写好的标签页内容不受影响。只有直接引用该发布路径的站点需要去掉这个引用。

正文与标题字体角色直接作用于内容。 此前只修改原始 body 或标题选择器的站点,应改为使用对应的 --td-*-font-family 角色或既有的 Sass 变量:

// 之前
body {
  font-family: 'My Sans', sans-serif;
}

// Oink 0.3.0
:root {
  --td-body-font-family: 'My Sans', sans-serif;
}

升级到 0.3.0

  1. 检查项目 JavaScript 是否依赖全局 $,如果依赖,请自行打包 jQuery。
  2. 删除对 static/js/tabpane-persist.js 的直接引用;已写好的 tabpane 内容本身不需要改。
  3. 把原始 body 或标题字体覆盖迁移到字体角色。
  4. 决定是否显式启用助手入口;如启用,请检查 URL 是否含敏感 query 或 fragment 数据,并披露第三方边界。
  5. 更新 Hugo 模块并整理模块图。
  6. 构建站点,检查有代表性的文档页、博客页、移动端、打印视图与明暗模式。
hugo mod get github.com/pgsty/oink@v0.3.0
hugo mod tidy
hugo --gc --minify

不需要重写任何 Markdown 内容。既有的围栏代码块、tabpane 内容、平铺菜单、短代码,以及普通的 Docsy 兼容页面都继续照常工作。

兼容性

契约 Oink 0.3.0
Hugo Extended 0.160.1 或更新;未变
模块路径 github.com/pgsty/oink;未变
消费端前端工具链 无;未变
需要的内容迁移
需要的配置迁移 无;助手入口需显式启用
需要的项目 JS 迁移 仅当依赖全局 jQuery

验证

0.3.0 候选版本通过并列的 Oink 项目站进行验证,因此站点构建针对的是候选主题本身,而不只是它最后固定的发布版本。公开发布前,主题侧必须通过完整契约测试套件、在最低与当前 Hugo 版本上零警告构建示例站、两种字体预设,以及浏览器运行时单元测试。站点侧必须通过格式化、中英文页面配对与稳定标题 ID、渲染后的 Markdown 与站内链接、Hugo 模块 fixture、备用配置构建、Markdown 与 favicon golden、响应式与组件浏览器行为,以及 axe 无障碍检查。标签、公共模块解析、站点版本钉住与线上冒烟仍是批准后的独立门禁。

完整变更集

完整源码差异见 v0.2.1 到 v0.3.0

1.11 - Oink 0.2.0:更丰富的内容与更精致的呈现

Oink 0.2.0 新增可组合首页分区、跟随颜色模式的图片、字标、可导航组件面板与 steps 短代码,并改进终端录像与版本发布内容的呈现体验。

Oink 0.2.0 聚焦读者与作者最常接触的表面:首页、品牌呈现、博客发现、分区索引与操作指南。Oink 项目站点也同步成为更清晰的双语参考,用于说明主题的当前契约。

模块路径、最低 Hugo 版本以及消费端仅依赖 Hugo 的构建方式均保持不变。唯一可能影响现有站点的配置改名,详见破坏性变更

发布亮点

首页与品牌

首页现在可以通过有序 sections 列表组合 12 种内置分区。字符串会选择同名数据;映射则可以通过不同的 key 重用呈现方式、在不删除数据的前提下禁用区块,或直接携带短小的一次性内容。没有 sections 的站点保留 0.1.x 首页顺序,因此显式组合是新增能力,而不是必需迁移。

数据驱动的首页现在可以在 Hero 文案旁放置响应式图片。作者可以配置一张通用图片,也可以分别提供浅色与深色图片;如果图片本身包含信息,还可以提供有意义的替代文字。布局会从桌面端的双栏 Hero 自动调整为紧凑的移动端呈现,无需站点覆盖模板。

Oink 还新增 params.wordmark。配置字标后,首页导航、文档页头、抽屉和页脚会统一使用它;只配置 params.logo 的站点继续使用原来的“图标 + 标题”样式。

首页组件面板现在可以成为真正的导航区域。条目支持链接、可选的外部链接行为、紧凑样式,以及一至四列布局。纯装饰面板仍保持不可交互,兼容 0.1.0 的既有契约。

完整数据结构参见首页与页脚

博客与版本发布

博客列表现在把图片与摘要作为一个整体进行响应式布局。特色图片不再把文字挤出平板宽度的容器,摘要可以安全断开机器生成的长标识符;没有图片的文章则会完整使用文本宽度。署名行中的分区名称现在可以点击,RSS 入口也会进入与其他页面操作一致的侧栏区域。

分类与标签使用和 TOC、页面操作相同的折叠区规则。在宽侧栏与移动抽屉中,条目都显示为易于扫描的行,并附带数量徽章。分区索引更加简洁,描述拥有更多空间;最后修改信息移动到子页面索引之后,不再打断页面导语。

Oink 项目站点现在把上游 Docsy 历史、Oink 工程文章与版本化 Oink 发布注记拆分为三个独立的双语分区。读者可以直接找到版本报告,同时不会把继承的 Docsy 文章误认为 Oink 发布。

内容组件

0.2.0 新增 Markdown 优先的 steps 短代码。直接子标题会成为自动编号的步骤,并由引导线连接;整体移动、新增或删除步骤时,无需手工维护数字。如果某个辅助标题不应占用编号,可以添加 class="no-step-marker"

Asciinema 录像新增精致的终端边框、标题栏、紧凑控制栏与跟随颜色模式的样式,并把字体契约直接传入播放器。这样既避免播放器回退到不同的终端字体,也能让录像在两种主题下保持响应式与清晰可读。

ECharts 回调代码块继续采用既有的可信作者模型:回调属于可执行内容,必须像内联 HTML 或其他自定义集成一样接受评审。渲染器不再为每个已评审的回调块重复输出警告。

steps 契约参见短代码,完整组件模型参见 Oink 组件

文档与测试

独立项目站点同步完成一轮文档更新:

  • 扩充中英文首页与组件示例。
  • 记录全部 12 种可组合首页分区,并在项目首页中使用适合的分区。
  • 添加真实的 Asciinema 安装录像与独立的 giscus 指南。
  • 把示例移入文档树,并删除过时的社区入口与仅供维护者使用的页面。
  • 将 Hugo 配置合并到根目录 hugo.yml,移除旧的 Netlify 专用工具。
  • 让浏览器测试与 live reload 隔离,并继续把响应式、无障碍、翻译、渲染后 Markdown 与链接检查纳入发布关卡。

这些都是项目站点改动,不会给主题消费端增加新的运行时依赖。

破坏性变更

0.2.0 将继承的特色图片设置从 default_featured_image 改名为 default_featured。请更新页面、分区 cascade 与站点级配置中的旧键:

# Oink 0.1.x
default_featured_image: /images/blog-card.webp

# Oink 0.2.0
default_featured: /images/blog-card.webp

主题内置的隐式占位图也被移除。如果文章没有图片、匹配的页面资源或显式 default_featured,Oink 现在会渲染干净的纯文本列表项。如果整个分区需要统一的视觉标识,请把 default_featured 指向站点自有图片;如果希望明确关闭默认图片,可以将其设为 false

旧配置键没有兼容别名。这是 0.2.0 唯一必需的配置迁移。

升级到 0.2.0

  1. 将所有 default_featured_image 设置替换为 default_featured
  2. 更新 Hugo 模块并整理模块依赖图。
  3. 构建站点,并检查具有代表性的首页、博客、文档、移动端与颜色模式页面。
hugo mod get github.com/pgsty/oink@v0.2.0
hugo mod tidy
hugo --gc --minify

本版本不要求重写 Markdown 内容。现有首页分区、仅配置图标的品牌样式、短代码以及与 Docsy 兼容的普通页面均可继续使用。

兼容性

契约 Oink 0.2.0
Hugo Extended 0.160.1 或更高版本;未改变
模块路径 github.com/pgsty/oink;未改变
消费端前端工具链 无;未改变
必需内容迁移
必需配置迁移 default_featured_image 改名

验证范围

0.2.0 候选版本通过同级目录中的 Oink 项目站点接受验证,因此站点构建使用的是候选主题,而不只是上一个固定版本。发布关卡覆盖格式、中英文页面配对与稳定标题 ID、渲染后 Markdown 与内部链接、Hugo 模块 fixture、响应式浏览器行为,以及 axe 无障碍检查。

完整变更

查看从 v0.1.0 到 v0.2.0 的完整源码差异

1.12 - Oink 0.1.0:稳定的本地优先基础

Oink 的首个稳定版本将实现预览完善为可直接使用的 Hugo 模块,提供响应式页面外壳、多语言基础设施、本地优先组件与更扎实的无障碍基线。

Oink 0.1.0 是 Oink 主题的首个稳定版本。它包含 0.0.1 实现预览以及之后的稳定化工作:统一的文档页面外壳、消费端仅依赖 Hugo 的构建、本地优先的浏览器资源、基于 Hugo 原生对象的多语言行为,以及可复用的内容组件。

本版本继续使用模块路径 github.com/pgsty/oink,要求 Hugo Extended 0.160.1 或更高版本。消费站点在构建和提供主题自带功能时,不需要 Node.js、npm、PostCSS、Autoprefixer 或 CDN。

发布亮点

本地优先的主题基础

Oink 随主题提供自有的样式、字体、图标、本地搜索、图表、API 文档运行时与内容组件运行时。可选资源只在页面实际使用时加载;可分发仓库本身是一个根 Hugo 模块,不再内嵌项目站点或前端工作区。

本版本还确立了核心产品契约:

  • Hugo 的语言与翻译对象统一驱动语言路由、切换、hreflang、书写方向与 locale 元数据。
  • 主题支持单语言、多语言与 RTL 站点,不依赖 PGSTY 专属域名假设。
  • Asciinema、ECharts、Infographic、图表、API 参考、标签页、卡片等可复用组件,共享本地且按页面加载的运行时。
  • 通过可选的 giscus 集成支持 GitHub Discussions 评论;站点未启用时不会加载任何外部评论脚本。
  • 继续支持与 Docsy 兼容的内容组织、菜单、分类法、打印输出与扩展钩子。

响应式页面外壳

文档、博客与 API 参考布局现在共用一套响应式外壳。桌面导航、可调整宽度的侧栏、目录(TOC)、页面操作、分类法、版本选择器和页脚采用一致的视觉与交互规则。

在平板与手机上,Oink 会把 TOC、页面操作、分类与标签移动到导航抽屉中,而不是渲染第二份副本。这样可以保持 ID唯一,并确保滚动跟踪、折叠区与复制操作在视口动态变化时仍能正常工作。语言与颜色模式控件在所有宽度下均可访问;颜色选择器明确提供“自动”“浅色”和“深色”三种偏好。

导航条目使用一致的图标,移动菜单会限制键盘焦点,页脚各列完整利用可用宽度,紧凑的页面操作菜单也不再与右侧栏重复。复制 Markdown、查看 Markdown、编辑、问题反馈和打印操作现在共用一套实现。

发布与内容呈现

语法高亮现在使用基于 class 的 Chroma 输出,并协调浅色与深色调色板。即使 JavaScript 尚未初始化颜色模式,代码仍然清晰可读;站点也可以通过 params.highlight_classes: false 选择退出。

博客列表新增确定性的特色图片解析顺序。在 0.1.0 中,它依次检查 front matter 中的 images、匹配的页面资源、继承的 default_featured_image、站点参数,最后使用主题占位图。现代博客列表与兼容的旧 partial 共用这一解析器。

新的墨迹标志与占位图能够正确处理“系统主题 × 站点所选主题”的四种组合。Oink会同时明确声明浅色与深色页面实际使用的 color-scheme,因此用户在站点中的显式选择会覆盖操作系统偏好。

无障碍与正确性

0.1.0 修复了桌面端、移动端、打印视图与辅助技术评审中发现的问题:

  • 修正标题顺序、landmark 名称、任务列表标签与打印列表语义。
  • 确保博客列表在平板宽度下不会溢出视口,并允许长 URL 或标识符安全换行。
  • 对 GitHub issue 链接中的标题与 URL 进行正确编码。
  • 本地化 404 页面,并移除无障碍名称中硬编码的标点。
  • 为 iframe 嵌入补充标题与延迟加载,只注册一次尺寸调整逻辑,并安全处理跨域 frame。
  • 每页只输出一个 contentinfo landmark,同时保留消费站点可直接使用的主题扩展 partial 与可选 SCSS 入口。

兼容性审计删除了确实不可达的旧页面外壳代码,也恢复了下游站点可以直接导入的文件。可达性判断会检查消费站点的布局与 _styles_project.scss,而不只检查主题自身的入口。

升级到 0.1.0

更新 Hugo 模块并重新构建站点:

hugo mod get github.com/pgsty/oink@v0.1.0
hugo mod tidy
hugo --gc --minify

本版本不要求迁移内容。如果站点直接导入 Oink partial 或 SCSS,请在升级过程中构建该站点,让其自定义表面与主题一起接受检查。

兼容性

契约 Oink 0.1.0
Hugo Extended 0.160.1 或更高版本
模块路径 github.com/pgsty/oink
消费端前端工具链
默认浏览器依赖 本地优先
主要内容模型 与 Docsy 兼容的 Markdown 与 front matter

验证范围

0.1.0 最终候选版本在主题 fixture 与 Oink 项目站点上接受了七种视口宽度的完整检查。记录结果中没有控制台错误、请求失败、水平溢出或 axe 违规。独立 fixture 还覆盖最低与当前 Hugo 版本、LTR 与 RTL 语言、子路径、打印输出、重复组件实例,以及网络隔离环境中的消费端构建。

完整变更

请参阅 v0.1.0 源码快照

2 - Oink 博客

OINK 公告、工程实践与实现笔记

2.1 - 在 Blog 外壳上沉浸式阅读

四个 front matter 键就能把普通 Blog 页面变成阅读优先的版式, 以全幅 Hero 开场,再用随文的大纲栏承接正文。

本页仍然由普通 Blog 外壳渲染,背后没有特殊的内容类型。只需四个 front matter 键 就能改变呈现;一个分区也可以在 cascade 里一次写下同样的配方:

featured_image: hero      # 题图成为全幅开场
toc_style: flow           # 更宽的大纲从正文起点开始
toc_taxonomies: false     # 右栏只保留大纲
sidebar_enabled: false

Hero 开场

带有题图的页面可以用它开场。hero 会把图片变成横跨视口顶部的背景, 将标题向下移动以留出空间,并在正文开始前让画面逐渐隐去。由于图片由外壳绘制, 分区索引页也能使用同一种呈现。

页面卡片、社交预览与 Hero 共用同一套代表图片解析规则。featured_image: banner 保留带边框的选项;没有合适图片的页面则自然回退到普通开头。

随文大纲栏

toc_style: flow 用一条更宽的随文大纲取代钉在视口上的面板。它在 Hero 下方 与正文同时开始,滚动后才固定位置。这个开关与图片相互独立,因此即使有些页面没有题图, 整个分区仍能保持一致的大纲样式。

toc_taxonomies: false 会移除分类法云。如果页面既没有大纲也没有分类法内容, 空的右栏会被完全省略。

仍然保留的能力

开场之后仍然是一篇完整的 Blog 文章:日期与阅读时间、标签徽章、作者与档案、系列导航、 描述导语、分享、页面注记、顺序翻页与评论。Blog 外壳默认不显示面包屑; 需要强调页面在内容树中的位置时,可以用 breadcrumb: true 恢复它。

2.2 - OINK 实施日记:从复制外壳到统一主题

记录 OINK 实现预览背后的技术决策、迁移方法、安全边界、测试与文档工作。

OINK 始于一个令人不安的事实:多个生产文档站之所以看起来相互关联,是因为它们确实源于同一套实现;但公共实现却以复制文件的形式散落在各处。呈现效果足够一致,维护模型却并非如此。

这篇日记记录项目如何从重复站点覆盖项走向一款直接演化的统一主题。它关注决策与证据,而不是逐条复述提交历史。

锁定产品契约

第一项真正有价值的工作是做减法。选择实现方式之前,我们先写清产品必须是什么:

  • 从 Docsy 直接演化而来的独立主题;
  • 唯一标准外壳,而不是可切换皮肤;
  • Hugo Extended 是消费端唯一构建依赖;
  • 所有主题自带浏览器资源默认本地优先;
  • 多语言行为从 Hugo 推导,而不是从 PGSTY 域名推导;
  • 可复用组件进入主题,业务语义留在站点;
  • 保留 Docsy 历史、许可证与可追踪的上游关系。

这排除了一个看似诱人、实际代价高昂的捷径:增加 params.oink.enabled 并保留旧外壳。模式开关会让每次布局调整、无障碍修复与测试都支持两套产品。直接演化则让目标设计成为唯一设计。

替换页面外壳

文档、博客与 API 参考布局围绕一组小型共享 partial 重新构建。新的外壳包括:

  • 全局导航与响应式次级导航;
  • 可调整宽度、可折叠的侧栏;
  • 本地搜索与快捷链接;
  • 语言与颜色模式控件;
  • 面包屑、目录(TOC)、页面元数据与反馈;
  • 一致的页脚与打印布局。

真正困难的不是画出导航栏,而是在删除复制的 baseof.html 时保留 Docsy 既有扩展点。范围明确的 hook 仍然存在;复制整个站点外壳不再是正常的定制路径。

移除消费端工具链

原有依赖链假设 Bootstrap 与 Font Awesome 来自 npm,部分路径还会调用 PostCSS。OINK 把必需源码与编译产物移入主题,并让 SCSS 留在 Hugo 自身的 Asset Pipeline 中。

测试不只检查 hugo 是否成功。fixture 中的陷阱会在消费端构建尝试运行 Node.js、npm、PostCSS 或 Autoprefixer,或模板调用 resources.GetRemote 时立即失败。LTR 与 RTL 页面遵守同一约束。

这里的区分非常重要:仓库仍使用 Node 运行维护者测试工具。“仅依赖 Hugo”描述的是消费站点取得完整主题后所需的构建环境,并不是禁止主题仓库使用开发工具。

纳管浏览器运行时

下一层工作覆盖浏览器原本可能从远端获取的全部依赖:Bootstrap、Font Awesome、字体、jQuery、Lunr、Mermaid、KaTeX、Markmap、Swagger UI、Redoc、Asciinema、ECharts、Infographic 及其辅助库。

theme/VENDOR.json 为每项选定产物记录来源、固定版本、许可证路径、校验值与更新流程。许可证与 vendor 内容相邻存放。清单会与真实文件一起验证,而不是停留在愿望列表。

PlantUML 与 Diagrams.net 迫使我们做出一项重要区分:它们依赖服务,而不只是 JavaScript 库。OINK 拒绝虚构公共端点;启用功能却没有配置服务时,构建会失败。

构建多语言内核

旧的语言行为散落在导航逻辑与站点专用假设中。新的内核从 Hugo 已配置语言、.Translations.AllTranslations 出发。

呈现方式刻意保持稳定:只有一种语言时隐藏选择器;两种或更多语言统一使用同一个图标按钮。点击会按配置权重切换,短暂悬停或键盘聚焦则打开完整语言菜单。

缺少译文时回退到目标语言首页。语言名称使用该语言的自称。同一组对象还会驱动 lang、书写方向、canonical、 hreflang 与 Open Graph locale 元数据,因此可见选择器不会与 SEO 输出漂移。

测试会让 RTL 语言作为当前语言覆盖每种状态,而不只验证 starter 的两种 LTR 语言。原生链接与 disclosure 控件让键盘行为保持可预期。

提炼通用组件

Asciinema、ECharts、Infographic、文档轮播、折叠块、标签页、卡片与参数渲染,已经在 PGSTY 站点证明了价值。接下来的工作,是把复制品转化为产品 API:

  • 统一参数名称与默认值;
  • 根据页面身份与短代码序号生成唯一 ID;
  • 每页只加载一次运行时,未使用页面完全省略;
  • 保持子路径 URL 正确;
  • 支持多个内容完全相同的实例;
  • 提供打印、深色模式、移动端、键盘与减少动态效果行为;
  • 为导入内容保留兼容别名。

产品矩阵等业务控件没有迁入主题。是否复用不能只看有多少仓库包含同一个副本;通用组件必须拥有稳定、与业务无关的契约。

支持 ECharts 回调

ECharts 回调是 JSON 与 YAML 无法表达的合法图表选项。现有页面用它们格式化提示、标签以及按数据选择颜色。把这些回调单独视为迁移例外,只会增加配置,并不会形成沙箱。

因此短代码采用一套直接契约:

  1. 内容提供 JSON 或 YAML,由 Hugo 解析并安全序列化;
  2. 可选的 JavaScript 围栏代码块声明回调;
  3. $fn:name 在选项解析后重新连接这些回调;
  4. 作者按照行内 HTML 与其他自定义集成相同的信任模型审查可执行代码。

测试覆盖内容相同的重复图表、非法 CSS 长度、结构化选项与回调注册。

创建 starter 与归档

如果最小示例能完整演示契约,契约就更容易获得信任。starter 包含双语首页、文档、博客与组件页面,以及本地搜索、深色模式、图表、API 文档和新增组件;它没有 package.json,也没有站点工作流。

离线打包器会组合 theme/starter/、许可证、上游记录与迁移指南,并排除生成结果与依赖缓存。它会写出配套 SHA-256 文件,并拒绝覆盖已有产物。

验收测试把 starter 与主题复制到临时目录,清空缓存,阻断 HTTP/HTTPS 与 Go 代理,使用 Hugo 构建,再检查 HTML 与 CSS 中是否出现第三方子资源。

演练四站迁移

SILO、PGSTY、SOW 与 Pigsty 提供了现实检验。演练工具会复制各工作区而不是修改源目录,只删除已经分类的公共覆盖项,应用本地主题 replacement,禁止网络与前端工具,再运行生产构建。

最近一次演练分别从 SILO、PGSTY、SOW 删除 20 个公共覆盖项,从 Pigsty 删除 24 个。Pigsty 保留三个业务矩阵短代码与现有 ECharts 回调。所有临时副本均成功构建,分别生成 1,095、16、128 与 2,473 个 HTML 文件。

这些数字证明的是记录提交上的迁移演练,不代表任何生产仓库已经改变,也不代表任何托管站点已经部署。

把样例站变成 OINK 文档

继承而来的 docsy.dev 站点是很有价值的回归语料库,但它只描述 Docsy。文档阶段完成了四项工作:

  1. 把英文设为首要语言、简体中文设为第二语言;后续外壳评审又从演示站点移除了法文;
  2. 为每份核心文档与博客源文件创建并置的 .zh.md 译文;
  3. 在每个中文标题中显式保留英文标题 ID;
  4. 增加 OINK 产品指南、项目公告与本实施日记。

翻译之前,我们先建立术语与排版指南。随后使用检查器验证源文件与译文配对、标题数量、中文显式 ID,以及渲染后的中英文标题 ID 是否完全相同。

Docsy 历史发布文章保持忠实翻译,其中的 npm 时代说明属于历史语境;OINK 架构与迁移指南则明确说明当前仅依赖 Hugo 的产品契约。

测试如何改变设计

多项测试不仅验证实现,也反过来改变了设计:

  • 子路径 fixture 迫使每个本地组件 URL 都经过 Hugo URL 处理;
  • 重复实例测试用“页面加序号”ID 替代内容哈希;
  • 离线浏览器检查暴露了隐含运行时请求;
  • RTL 语言矩阵避免选择器只适用于 starter 的两种 LTR 语言;
  • ECharts 回调 fixture 保证回调注册与结构化选项可以协同工作;
  • 迁移演练保留了直接清空 layouts/ 时会被误删的站点专用 partial。

最有力的测试套件约束的是产品边界,而不只是当前 HTML 快照。

后续工作

目前仍有两个发布关卡有意保持开放。公开品牌、仓库、模块与软件包身份,以及首个版本需要批准。随后还要让真实 Cloudflare Pages 项目从源分支构建,并通过托管验证。

生产迁移应逐站进行,使用专用分支、预览部署、视觉回归与回滚产物。四站临时演练是这项工作的基础,不能替代正式迁移。

经验总结

  • 移动文件前先写清产品边界。
  • 本地优先承诺必须同时具有构建阶段与浏览器阶段证据。
  • 配置应表达用户选择,而不是内部实现分支。
  • 翻译质量不仅是正文,还包括稳定链接、代码保真、排版与渲染结构。
  • 复用应消除维护副本,而不能吞并业务语义。
  • “构建”“打包”“公开发布”“部署”与“迁移”是不同声明,需要不同证据。

最终成果没有重写那样戏剧化,却更加实用:一款能够作为完整产品被理解、构建、测试、翻译与迁移的统一主题。

2.3 - OINK 实现预览正式亮相

OINK 将直接定制的 Docsy 代码库演化为本地优先、仅依赖 Hugo 的文档主题,并提供多语言基础设施与可复用内容组件。

今天,我们发布 OINK 实现预览:它从 Docsy 直接演化而来,提供唯一标准产品外壳、仅依赖 Hugo 的消费端构建、本地优先浏览器依赖、通用多语言框架,以及一组从 PGSTY 文档站提炼出的可复用内容组件。

这是实现与文档里程碑,不是已经公开的版本化发行。最终公开品牌、模块与软件包身份、首个版本,以及生产 Cloudflare Pages 部署,仍是必须显式关闭的发布关卡。

为什么需要 OINK?

多个成熟文档站分别复制了相同的 Docsy 布局、导航、搜索代码、SCSS、JavaScript 与短代码。一个公共修复必须在多个仓库重复实施;与此同时,每个站点都携带前端工具链与隐含网络依赖,让网络隔离构建变得异常复杂。

OINK 将真正可复用的部分合并到主题中。产品矩阵、门户、价格页和其他业务专用行为仍留在各自站点;共享主题负责文档外壳、浏览器运行时、多语言路由、无障碍行为与内容组件契约。

有哪些变化?

产品本身,而不是一种模式

OINK 不是可选皮肤。项目没有 oink.enabled 开关、params.oink.* 命名空间,也没有“上游版与品牌版”并行的模板树。theme/ 中的实现就是产品。

这项决策避免维护两套视觉系统与两套测试矩阵。Hugo 原生设置与兼容的 Docsy 参数继续保持原有含义。

消费端仅依赖 Hugo 构建

完整消费站点只需运行:

hugo --gc --minify

Bootstrap、Font Awesome、字体、搜索、图表、API 文档运行时与 OINK 组件都已随主题提交。Node.js、npm、PostCSS、Autoprefixer 与 CDN 下载不属于消费端要求。

仓库维护者仍会使用 Node 工具运行测试和更新 vendor 资源;这套维护工具链有意置于公开站点构建契约之外。

本地优先的浏览器行为

默认 starter 会从生成后的站点提供页面外壳、字体、图标、搜索、Mermaid、KaTeX、Markmap、Swagger UI、Redoc、Asciinema、ECharts 与 Infographic 依赖。可选运行时按页面选取,并且每页最多加载一次。

PlantUML 与 Diagrams.net 不再拥有公共服务默认值。站点必须配置受控端点、使用预渲染结果,或明确选择远程服务。

多语言基础设施

语言路由来自 Hugo 的语言与翻译对象。配置一种语言时隐藏选择器;配置两种或更多语言时,直接点击按配置顺序切换,短暂悬停或键盘聚焦则打开完整菜单。当前页面缺少译文时,选择器会进入目标语言首页,而不是失效路径。

starter 与本站均以英文为首要语言、简体中文为第二语言。核心文档与博客范围内的每个页面都有并置的 .zh.md 译文,且标题采用稳定的显式 ID。

可复用组件

OINK 新增主题级 Asciinema、ECharts、Infographic、文档轮播、折叠块、标签页、卡片、导航卡片、文档卡片与参数组件。它们会生成唯一实例 ID,并且只在实际使用时加载本地资源。

ECharts 接受结构化 JSON 或 YAML,也支持通过 $fn:name 引用可选的 JavaScript 回调。回调代码只在声明它的页面运行。

哪些能力保持不变?

OINK 沿用 Docsy 的内容组织、front matter、文档与博客 section、菜单、taxonomy、打印输出、仓库链接、常用短代码、图表、API 参考能力与扩展 hook。现有站点可以删除重复公共实现,而不必重写普通 Markdown。

项目也保留 Docsy 的 Apache-2.0 历史与归属信息。vendor 清单记录固定的第三方来源、许可证、产物与校验值。

体验 starter

安装 Hugo Extended 0.160.1 或更高版本,然后在当前检出目录运行:

hugo --source starter --gc --minify

当前验证基线为 Hugo Extended 0.164.0。打开生成的英文与中文页面,切换语言、使用本地搜索、改变颜色模式,并访问组件示例。

需要传入网络隔离环境时,维护者可以创建完整归档:

scripts/package-offline.sh /absolute/path/oink-preview.tar.gz preview

归档包含主题、starter、许可证、上游记录、迁移指南、vendor 清单和配套校验值。

当前验证范围

当前实现的自动化覆盖包括:

  • 最低版本与当前版本 Hugo Extended 构建;
  • 禁止消费端 Node/npm/PostCSS/Autoprefixer 路径;
  • LTR、RTL、子路径、打印、颜色模式与生产资源;
  • 完整的一种/两种/三种/四种及以上语言选择器矩阵;
  • 本地按页运行时与重复组件实例;
  • ECharts 结构化选项与回调集成;
  • 离线双语 starter 与离线发行归档;
  • vendor 许可证与校验值;
  • SILO、PGSTY、SOW 与 Pigsty 的非破坏性迁移演练。

最近一次四站演练成功构建了临时副本,但没有修改或部署这些生产仓库。

正式发布前还需要什么?

公开身份与首个版本必须获批,并一致应用到模块、软件包、源码标签、嵌套主题标签、归档与文档。随后还要把目标 Cloudflare Pages 项目连接到源分支,使用固定 Hugo 版本构建、公开发布,并在托管 URL 上完成验证。

这些关卡关闭之前,请把该预览用于评估与迁移演练,不要把未固定版本的代码当作生产依赖。