跳转到主要内容

1 - 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

2 - 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

3 - 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

4 - 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_indexlistlistcardstable
ui.blog_index_columns3卡片列数
ui.blog_index_size12列表/卡片每页文章数
ui.blog_index_togglefalse读者侧三形态循环切换

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

分享

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_imagenonenone / banner / wash / hero
ui.toc_stylefixedfixed / flow
ui.toc_taxonomiestrue是否在右轨显示分类词云
ui.blog_indexlistlist / cards / table
ui.blog_index_columns3卡片列数
ui.blog_index_size12列表/卡片分页大小
ui.blog_index_togglefalse读者侧三形态切换
ui.share[]有序分享目标
ui.translation_noticefalse可选的权威语言
time_format_blog2006-01-02默认值有变更
time_format_default2006-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

5 - 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.40.5.0说明
offlineSearchofflineSearchIndexofflineSearchMaxResultsofflineSearchOnServeofflineSearchSummaryLengthoffline_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.disableui.scroll_spy取反;默认 false
ui.no_left_sidebarui.sidebar_enabled取反
ui.breadcrumb_disableui.breadcrumb取反;默认 true
print.disable_tocprint.toc取反;默认 true
disable_click2copy_chromaui.code_copy取反;只设默认值
ui.readingtime.enableui.reading_time裸布尔
ui.ul_showui.sidebar_expand_levels默认 2
Taxonomy.taxonomyCloud.taxonomyCloudTitle.taxonomyPageHeadertaxonomy.cloud.cloud_title.page_header一个小写 map
ui.annotation.enableui.image_zoom.enableui.keyboard_nav.enableui.annotationui.image_zoomui.keyboard_nav裸布尔
ui.typography.presetui.typographytechnical | system;环境变量覆盖 HUGO_PARAMS_UI_TYPOGRAPHY=system
ui.pager.typesui.pager_types[docs, book, blog]
markmap.enablemarkmap裸布尔
content_widthslim | norm | widereading_widthslim | normal | wideBook 正文测量;body class td-book-content--normal,令牌 --td-book-content-normal
ui.docs_rootui.docs_sidebar_rootsection | home
github_urlgithub_repo编辑、历史、Issue 链接由仓库推导
algolia_docsearchsearch.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 matter0.5.0
params: { ui: { <key>: … } }(任何键)顶层(或 params: 下)的 <key>: …
params.ui.image_zoom.enableimage_zoom: true | false
params.ui.keyboard_nav.enableparams.ui.annotation.enablekeyboard_navannotation(裸布尔)
annotation: { enable: … }annotation: true | false
context_menupage_context_menutrue | false,或 { enable, assistant_links }
assistant_links(顶层)page_context_menu: { assistant_links: false }——页面只能收窄站点策略
hide_readingtime: truereading_time: false
hide_feedback: truefeedback: false
exclude_searchexcludeSearchsearch_exclude
content_width: normreading_width: normal
manualLinkmanualLinkTitlemanualLinkTargetmanualLinkRelrefmanual_linkmanual_link_titlemanual_link_targetmanual_link_relref
body_class: td-no-left-sidebarsidebar_enabled: false
contributingUrl随 community 页面删除
Iconicon(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.40.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.htmltaxonomy-terms-article.htmltaxonomy-terms-article-wrapper.htmltaxonomy-terms-cloud.html
_partials/taxonomy_terms_clouds.htmlcode/markdown-escape.html0.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.40.5.0
classoink-*(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* / echartsFunctionswindow.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

6 - 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

7 - 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
HugoExtended 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

8 - 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
HugoExtended 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 的完整源码差异

9 - 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
HugoExtended 0.160.1 或更高版本
模块路径github.com/pgsty/oink
消费端前端工具链
默认浏览器依赖本地优先
主要内容模型与 Docsy 兼容的 Markdown 与 front matter

验证范围

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

完整变更

请参阅 v0.1.0 源码快照