diff --git a/Makefile b/Makefile index d10a03c..1b8aa00 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,7 @@ -.PHONY: build check dev serve +.PHONY: browser build check dev serve + +browser: + HUGO_MODULE_REPLACEMENTS='github.com/pgsty/oink -> $(abspath ../oink)' npm run test:browser build: hugo --cleanDestinationDir --minify diff --git a/README.md b/README.md index df61744..d51ff31 100644 --- a/README.md +++ b/README.md @@ -28,19 +28,20 @@ For theme development, clone both repositories as siblings: └── oink.pgsty.com/ ``` -The four Make targets separate published-theme checks from local-theme work: +The five Make targets separate published-theme checks from local-theme work: ```sh -make build # Build production output with the version pinned in go.mod -make check # Test the sibling theme with the non-browser regression suite -make dev # Start the fastest server with the sibling theme -make serve # Preview the pinned theme in the production environment +make build # Build production output with the version pinned in go.mod +make check # Test the sibling theme with the non-browser regression suite +make browser # Run the browser regression suite with the sibling theme +make dev # Start the fastest server with the sibling theme +make serve # Preview the pinned theme in the production environment ``` `build` and `serve` invoke Hugo directly and resolve the published version of -`github.com/pgsty/oink` pinned in `go.mod`. `dev` and `check` set a one-command -module replacement to `../oink`; they do not create a `go.work` file or modify -`go.mod`. `dev` keeps Hugo's fast-render defaults and renders to memory; +`github.com/pgsty/oink` pinned in `go.mod`. `dev`, `check`, and `browser` set a +one-command module replacement to `../oink`; they do not create a `go.work` +file or modify `go.mod`. `dev` keeps Hugo's fast-render defaults and renders to memory; `serve` uses the production environment, minifies the output, performs full renders after changes, and does not inject live reload. Node and npm are needed for the regression tests, not to build the OINK theme or site. diff --git a/TRANSLATION.md b/TRANSLATION.md index 1d4b2fa..479158c 100644 --- a/TRANSLATION.md +++ b/TRANSLATION.md @@ -20,8 +20,8 @@ | 合计 | 127 | 覆盖检查逐一核对首页以及 `docs/`、`blog/`、`book/`、`case/`、 -`authors/` 与 `series/`。`content/search.md` 是由主题 i18n 驱动的特殊搜索页面, -不要求独立的 `.zh.md` 同伴。以下命令同时检查文件覆盖率、显式锚点、中英文渲染 ID 和站内链接: +`authors/` 与 `series/`。以下命令同时检查文件覆盖率、显式锚点、中英文渲染 ID +和站内链接: ```bash make build diff --git a/content/blog/_index.md b/content/blog/_index.md index 84d7c01..dfb28cb 100644 --- a/content/blog/_index.md +++ b/content/blog/_index.md @@ -9,6 +9,9 @@ sidebar_root_link_self: true footer_style: slim comments: true blog_index: cards +# The section root is a feed index, not a destination: backlinks belong on +# the posts it lists, so it opts out of the site-wide default. +backlinks: false # The Book/Blog reading shells keep the title bar pinned: long-form reading # should not make the navbar appear and disappear under the pointer. navbar_autohide: false diff --git a/content/blog/_index.zh.md b/content/blog/_index.zh.md index 72a4cbe..fd34dda 100644 --- a/content/blog/_index.zh.md +++ b/content/blog/_index.zh.md @@ -9,6 +9,9 @@ sidebar_root_link_self: true footer_style: slim comments: true blog_index: cards +# 栏目根页是一份文章索引而不是落点:反向链接属于它列出的那些文章,所以这一页 +# 退出站点级默认。 +backlinks: false # The Book/Blog reading shells keep the title bar pinned: long-form reading # should not make the navbar appear and disappear under the pointer. navbar_autohide: false diff --git a/content/blog/release/1.1.0.md b/content/blog/release/1.1.0.md new file mode 100644 index 0000000..6e318de --- /dev/null +++ b/content/blog/release/1.1.0.md @@ -0,0 +1,90 @@ +--- +title: OINK 1.1.0 — Native locales and taxonomy directories +linkTitle: OINK v1.1.0 +date: 2026-09-03T18:00:00+08:00 +lastmod: 2026-09-03T18:00:00+08:00 +draft: true +description: >- + OINK 1.1.0 completes the native interface for every Docsy locale, turns + taxonomy roots into navigable directories, and carries the post-1.0 + correctness and bounded-checker fixes without requiring a site migration. +authors: [oink] +categories: [release] +tags: [Oink, Release] +series: [oink-releases] +series_weight: 31 +release_url: https://github.com/pgsty/oink/releases/tag/v1.1.0 +--- + +> [!IMPORTANT] Release candidate +> The source changes are merged on `main`, but the `v1.1.0` tag, Go Proxy +> module, documentation-site pin, deployment, and live verification are still +> pending. This note remains a draft until those states are complete. + +OINK 1.1.0 is the first minor release after the stable 1.0 contract. It adds a +complete native interface for every locale OINK inherits from Docsy and gives +taxonomy roots a real directory surface. It also includes the correctness fixes +prepared after the 1.0 review. No component, configuration, or content migration +is required. + +## At a glance {#at-a-glance} + +- All 31 Docsy locale catalogs plus generic `zh` now carry the same 194 OINK + messages without generated English fallback. Page counts use Hugo's CLDR + plural selection, including the Arabic, Polish, Romanian, Russian, Serbian, + and Ukrainian forms that cannot be expressed as a simple singular/plural + pair. +- `/tags/`, `/categories/`, `/authors/`, and `/series/` are now directories of + compact term cards instead of a filter-chip row. Taxonomy and term pages share + a localized head, and their right rail starts with a switcher between the + taxonomies declared by the site. Term pages remain row-list archives. +- Cached sidebars preserve page and cascade settings, remain navigable without + JavaScript, and hydrate their active path without a transient contrast drop. +- Dedicated Print output contains no shell navbar, and one per-page coordinator + keeps plain Print and overlapping Book aggregates from racing on Page Store. +- Invalid `book-toc drafts` and Landing `preview.source` inputs now follow the + existing warn-and-fallback or warn-and-omit contract in ordinary previews. +- Redoc static paths behave the same with or without a leading slash under root + and subpath deployments. +- The obsolete ScrollSpy patch no longer ships. Its 1.x configuration key stays + accepted as a quiet compatibility no-op because the normal outline runtime + already tracks the active heading. +- Output checkers build fresh input by default, every checker-owned Hugo process + has a 120-second ceiling, and redundant warning-fatal canaries were removed + without dropping per-case diagnostic and safe-output checks. + +## Compatibility {#compatibility} + +OINK 1.1.0 keeps the released 1.0 authoring and configuration surface. Existing +sites need no source migration; taxonomy roots change presentation only. The +only retired runtime was unreachable or duplicated existing behavior; +compatibility partials, all three supported search providers, explicit locale +fallbacks, and the migration tools remain in place. Hugo Extended 0.160.1 stays +the floor; on 0.160.x, a non-default generic `zh` alongside `zh-cn` and `zh-tw` +uses `locale: zh-CN`. + +## Verification {#verification} + +The candidate passed the complete ordered theme suite, the strict synthetic +fixture, 85 migration tests, 41 browser-runtime tests, Book publication checks, +and the real bilingual documentation site. The browser gate covered +accessibility, responsive navigation, keyboard control, content components, +code blocks, Landing, and section theme colors. + +The checker trace fell from 735 Hugo starts with 305 warning-fatal builds to 614 +and 184 respectively. All 613 checker-owned starts are now bounded; the one +remaining unwrapped build is the CI-owned top-level fixture command. + +## Upgrade after publication {#upgrade} + +After the public tag and module proxy are verified: + +```bash +hugo mod get github.com/pgsty/oink@v1.1.0 +hugo mod tidy +hugo --cleanDestinationDir --gc --minify --environment production \ + --printPathWarnings --panicOnWarning +``` + +Pinning the module, passing a local build, deployment, and live rendering remain +separate completion states. diff --git a/content/blog/release/1.1.0.zh.md b/content/blog/release/1.1.0.zh.md new file mode 100644 index 0000000..78e59b7 --- /dev/null +++ b/content/blog/release/1.1.0.zh.md @@ -0,0 +1,74 @@ +--- +title: OINK 1.1.0:原生多语言界面与分类法目录 +linkTitle: OINK v1.1.0 +date: 2026-09-03T18:00:00+08:00 +lastmod: 2026-09-03T18:00:00+08:00 +draft: true +description: >- + OINK 1.1.0 补齐全部 Docsy locale 的原生界面,将分类法根页改造成可导航的目录, + 并纳入 1.0 之后的正确性修复与有界 checker,现有站点无需迁移。 +authors: [oink] +categories: [release] +tags: [Oink, Release] +series: [oink-releases] +series_weight: 31 +release_url: https://github.com/pgsty/oink/releases/tag/v1.1.0 +--- + +> [!IMPORTANT] 发布候选 +> 源码修改已经合入 `main`,但 `v1.1.0` 标签、Go Proxy 模块、文档站版本固定、 +> 部署与线上核验仍未完成。在这些状态闭环前,本说明保持草稿。 + +OINK 1.1.0 是稳定 1.0 契约之后的第一个次版本:它为 OINK 从 Docsy 继承的全部 +locale 补齐原生界面,也让分类法根页拥有真正的目录表面,同时纳入 1.0 评审后准备的 +正确性修复。组件、配置与内容均无需迁移。 + +## 概览 {#at-a-glance} + +- Docsy 的 31 份 locale catalog 加上通用 `zh`,现在都拥有相同的 194 条 OINK + 消息,不再生成英文回退。页面计数交给 Hugo 按 CLDR 复数类别选择,正确覆盖无法用 + 简单单复数二分表达的阿拉伯语、波兰语、罗马尼亚语、俄语、塞尔维亚语与乌克兰语。 +- `/tags/`、`/categories/`、`/authors/` 与 `/series/` 从筛选芯片行变成紧凑的 + 术语卡片目录。分类法页与术语页共用本地化页头,右栏先提供站点已声明分类法之间的 + 切换器;具体术语页继续使用行列表归档。 +- 缓存侧栏保留页面与 cascade 设置;没有 JavaScript 时仍可导航,恢复 active 路径时 + 也不会短暂跌破对比度门槛。 +- 专用 Print 输出不再包含外壳 navbar;每页一个 coordinator,避免普通 Print 与 + 重叠 Book 聚合在 Page Store 上竞态。 +- 非法 `book-toc drafts` 与 Landing `preview.source` 输入在普通预览中遵循既有的 + 「告警并回退」或「告警并省略」契约。 +- Redoc 的 static 路径在根路径与子路径部署下,无论开头是否带 `/` 都得到相同结果。 +- 不再下发无效 ScrollSpy 补丁。其 1.x 配置键继续作为静默兼容 no-op 接受,因为 + 普通大纲运行时已经跟踪当前标题。 +- 输出 checker 默认构建新鲜输入;checker 启动的每个 Hugo 进程都有 120 秒上限; + 重复的 warning-fatal canary 被收缩,但逐例诊断与安全输出检查全部保留。 + +## 兼容性 {#compatibility} + +OINK 1.1.0 保持已经发布的 1.0 创作与配置表面,现有站点不需要迁移源码;分类法根页 +只改变呈现。唯一退役的运行时本来不可达或重复既有行为;兼容 partial、三种受支持 +搜索后端、显式 locale fallback 与迁移工具均继续保留。Hugo Extended 0.160.1 仍是 +下限;在 0.160.x 上同时配置非默认通用 `zh`、`zh-cn` 与 `zh-tw` 时,通用语言需使用 +`locale: zh-CN`。 + +## 验证 {#verification} + +候选版本通过完整有序主题套件、严格合成 fixture、85 个迁移测试、41 个浏览器运行时 +测试、Book 出版检查,以及真实双语文档站。浏览器门禁覆盖无障碍、响应式导航、键盘 +控制、内容组件、代码块、Landing 与分区主题色。 + +checker trace 从 735 次 Hugo 启动、其中 305 次 warning-fatal,降到 614 与 184。 +613 次 checker 自有启动现在全部有界;唯一未包装的是 CI 自己直接执行的顶层 fixture。 + +## 发布后升级 {#upgrade} + +公开标签与模块代理核验通过后执行: + +```bash +hugo mod get github.com/pgsty/oink@v1.1.0 +hugo mod tidy +hugo --cleanDestinationDir --gc --minify --environment production \ + --printPathWarnings --panicOnWarning +``` + +模块固定、本地构建、部署与线上渲染仍是彼此独立的完成状态。 diff --git a/content/docs/_index.md b/content/docs/_index.md index 70e8583..6bb7c5c 100644 --- a/content/docs/_index.md +++ b/content/docs/_index.md @@ -19,6 +19,9 @@ icon: fa-solid fa-book sidebar_expanded: true sidebar_root_for: self sidebar_root_link_self: true +# The section root is a table of contents, not a destination: backlinks +# belong on the pages it leads to, so it opts out of the site-wide default. +backlinks: false # Docs pins the title bar: a reference tree is read by jumping between pages, # so the global menu has to stay where the pointer left it. navbar_autohide: false diff --git a/content/docs/_index.zh.md b/content/docs/_index.zh.md index 8996136..2a0068e 100644 --- a/content/docs/_index.zh.md +++ b/content/docs/_index.zh.md @@ -11,6 +11,9 @@ icon: fa-solid fa-book sidebar_expanded: true sidebar_root_for: self sidebar_root_link_self: true +# 栏目根页是一份目录而不是落点:反向链接属于它指向的那些页面,所以这一页 +# 退出站点级默认。 +backlinks: false # 文档区固定顶栏:参考树是靠页面间跳转来读的,全局菜单必须停在指针离开时的位置。 navbar_autohide: false # 分区身份:文档保持品牌蓝,但显式写出而不是隐式继承 —— 这样侧栏根切换器里 diff --git a/content/docs/customize/config.md b/content/docs/customize/config.md index 8663a05..5f37bec 100644 --- a/content/docs/customize/config.md +++ b/content/docs/customize/config.md @@ -71,7 +71,7 @@ precedence first: 3. Site `params`. **Drop the `ui.` prefix when writing it in front matter.** The site's -`params.ui.scroll_spy` is simply `scroll_spy` on a page. A `ui:` block in front +`params.ui.reading_time` is simply `reading_time` on a page. A `ui:` block in front matter is read by nobody and reported by nobody, so a setting that seems to have no effect is worth checking against [Page parameters](/docs/write/frontmatter/) first. @@ -82,7 +82,7 @@ title: Wide reference page_width: wide navbar_enabled: false footer_style: slim -scroll_spy: true +reading_time: false --- ``` @@ -238,7 +238,7 @@ than from a parameter — see | `params.ui.sidebar_menu_compact` | boolean | true | Expands only the current branch and its neighbours | | `params.ui.sidebar_menu_foldable` | boolean | true | Lets the reader expand and collapse sections | | `params.ui.sidebar_menu_truncate` | integer | 2000 | Maximum entries rendered in one section; the rest are truncated | -| `params.ui.sidebar_cache_limit` | integer | 500 | Above this page count the site reuses shared navigation markup, and the browser restores the active state | +| `params.ui.sidebar_cache_limit` | integer | 500 | At this page count, reuse visible neutral navigation markup for matching language/root/effective settings; the browser adds active state | | `params.ui.sidebar_width_min` | integer | 220 | Lower bound in pixels for drag-resizing on the desktop | | `params.ui.sidebar_width_max` | integer | 480 | Upper bound in pixels for drag-resizing | | `params.ui.sidebar_item_overflow` | enum | ellipsis | `ellipsis` truncates a long title, `wrap` wraps it | @@ -263,7 +263,7 @@ the tracking behaviour: | --- | --- | --- | --- | | `markup.tableOfContents.startLevel` | integer | 2 | Hugo's own: the highest heading level collected | | `markup.tableOfContents.endLevel` | integer | 3 | Hugo's own: the lowest heading level collected | -| `params.ui.scroll_spy` | boolean | false | Scroll position tracking; `true` highlights the active entry | +| `params.ui.scroll_spy` | boolean | false | Quiet 1.x compatibility no-op; the normal shell runtime always tracks the active outline heading and this key emits no asset | {.fields meta="type default"} Hide the outline on one page with the front matter `notoc: true` — see diff --git a/content/docs/customize/config.zh.md b/content/docs/customize/config.zh.md index 3820baf..ec44916 100644 --- a/content/docs/customize/config.zh.md +++ b/content/docs/customize/config.zh.md @@ -63,7 +63,7 @@ Hugo 的 `.Param` 查找让大部分参数可以逐页覆盖,优先级从高 3. 站点 `params`。 **写进 front matter 时要去掉 `ui.` 前缀。** -站点上的 `params.ui.scroll_spy` 在页面里就写成 `scroll_spy`。front matter 里出现 `ui:` +站点上的 `params.ui.reading_time` 在页面里就写成 `reading_time`。front matter 里出现 `ui:` 块的话,里面的键没有人读,也没有人报错——某个设置看着没生效时,先对照[页面参数](/zh/docs/write/frontmatter/)核一遍键名。 ```yaml {title="content/docs/wide-reference.md"} @@ -72,7 +72,7 @@ title: 宽版参考 page_width: wide navbar_enabled: false footer_style: slim -scroll_spy: true +reading_time: false --- ``` @@ -211,7 +211,7 @@ favicon 没有参数:主题按约定名扫描 `static/`(`favicon.ico` `favic | `params.ui.sidebar_menu_compact` | boolean | true | 只展开当前分支与邻近条目 | | `params.ui.sidebar_menu_foldable` | boolean | true | 允许读者展开/折叠分区 | | `params.ui.sidebar_menu_truncate` | integer | 2000 | 一个分区最多渲染的条目数,超出截断 | -| `params.ui.sidebar_cache_limit` | integer | 500 | 站点页数超过它就复用共享导航标记,active 状态改由浏览器还原 | +| `params.ui.sidebar_cache_limit` | integer | 500 | 页数达到此值后,按语言、导航根与有效设置复用可见的中性导航标记;浏览器补 active 状态 | | `params.ui.sidebar_width_min` | integer | 220 | 桌面端拖拽调宽的下限,像素 | | `params.ui.sidebar_width_max` | integer | 480 | 拖拽调宽的上限,像素 | | `params.ui.sidebar_item_overflow` | enum | ellipsis | `ellipsis` 长标题省略,`wrap` 换行 | @@ -232,7 +232,7 @@ favicon 没有参数:主题按约定名扫描 `static/`(`favicon.ico` `favic | --- | --- | --- | --- | | `markup.tableOfContents.startLevel` | integer | 2 | Hugo 原生:收录的最高标题级别 | | `markup.tableOfContents.endLevel` | integer | 3 | Hugo 原生:收录的最低标题级别 | -| `params.ui.scroll_spy` | boolean | false | 滚动位置跟踪;设为 `true` 打开活动项高亮 | +| `params.ui.scroll_spy` | boolean | false | 1.x 静默兼容 no-op;普通外壳运行时始终跟踪当前大纲标题,此键不加载资源 | {.fields meta="type default"} 单页隐藏大纲用 front matter `notoc: true`,见[页面参数](/zh/docs/write/frontmatter/)。 diff --git a/content/docs/customize/layout.md b/content/docs/customize/layout.md index 5c0dd51..1bc1009 100644 --- a/content/docs/customize/layout.md +++ b/content/docs/customize/layout.md @@ -166,6 +166,12 @@ params: - `sidebar_width_min` / `sidebar_width_max` bound drag-resizing on the desktop, in pixels. The reader's adjusted width is kept locally, and double-clicking the divider restores the default. - `sidebar_item_overflow` defaults to `ellipsis` (long titles truncate); a site with many long titles can use `wrap`. +At `sidebar_cache_limit`, pages with the same effective settings may share a +neutral rendered tree. It remains visible and navigable without JavaScript; +the shell runtime only adds the current path and opens its ancestors. Page or +cascade overrides select the matching cached variant. A Book page with +`sidebar_headings` enabled keeps its page-specific tree instead. + Fold state, width and scroll position are stored locally per language. Below `md` the sidebar becomes a drawer with a backdrop. @@ -270,19 +276,16 @@ markup: ordered: false ``` -The theme governs only the tracking behaviour: - -```yaml {title="hugo.yml"} -params: - ui: - scroll_spy: false -``` +The normal shell runtime always tracks the active heading. It draws a continuous +rail, highlights the current section, and marks the position without a separate +switch. The reader can collapse the right column entirely, and that state is +kept locally. Below `xl` the right column is hidden and its content moves into +the sidebar drawer. -Scroll tracking is **off** by default. Set to `true`, the outline draws a -continuous rail, highlights the current section and marks the position. The -reader can collapse the right column entirely, and that state is kept locally. -Below `xl` the right column is hidden and its content moves into the sidebar -drawer. +The old `params.ui.scroll_spy` site key and `scroll_spy` page key remain accepted +as quiet compatibility no-ops throughout 1.x. Either boolean value produces the +same outline and loads no extra runtime; removing the keys is reserved for a +future breaking release. To hide the outline on one page, use the front matter `notoc: true`. diff --git a/content/docs/customize/layout.zh.md b/content/docs/customize/layout.zh.md index 645ef3d..a60ace1 100644 --- a/content/docs/customize/layout.zh.md +++ b/content/docs/customize/layout.zh.md @@ -142,6 +142,11 @@ params: - `sidebar_width_min` / `sidebar_width_max` 是桌面端拖拽调宽的上下限(像素)。读者调整后的宽度存在浏览器本地,双击分隔条恢复默认。 - `sidebar_item_overflow` 默认 `ellipsis`(长标题省略),中文长标题多的站点可以改 `wrap` 换行。 +达到 `sidebar_cache_limit` 后,有相同有效设置的页面可以共享一份中性渲染树。没有 +JavaScript 时它仍然可见且可导航;外壳运行时只补当前路径并展开其祖先。页面或 +cascade 覆盖会选择对应的缓存变体;启用 `sidebar_headings` 的 Book 页面仍使用自己 +的页面专属树。 + 折叠状态、宽度与滚动位置保存在读者本地,按语言隔离。小于 `md` 时侧栏变成带遮罩的抽屉。 单页去掉侧栏用 front matter: @@ -231,15 +236,13 @@ markup: ordered: false ``` -主题只管跟踪行为: - -```yaml {title="hugo.yml"} -params: - ui: - scroll_spy: false -``` +普通外壳运行时始终跟踪当前标题,无需额外开关。大纲绘制连续轨道、高亮当前区段 +并标出位置。读者可以整体折叠右栏,状态存在本地。小于 `xl` 时右栏隐藏,大纲内容 +移进侧栏抽屉。 -默认 **关闭** 滚动跟踪。设为 `true` 开启后,大纲绘制连续轨道、高亮当前区段并标出位置。读者可以整体折叠右栏,状态存在本地。小于 `xl` 时右栏隐藏,大纲内容移进侧栏抽屉。 +旧的站点键 `params.ui.scroll_spy` 与页面键 `scroll_spy` 在整个 1.x 期间仍作为静默 +兼容 no-op 接受。两个布尔值生成相同的大纲,也不加载额外运行时;只有未来的破坏性 +版本才会删除这两个键。 单页隐藏大纲用 front matter `notoc: true`。 diff --git a/content/docs/customize/taxonomy.md b/content/docs/customize/taxonomy.md index 1815616..7d3d6ba 100644 --- a/content/docs/customize/taxonomy.md +++ b/content/docs/customize/taxonomy.md @@ -1,7 +1,7 @@ --- title: Taxonomies linkTitle: Taxonomies -description: Give pages a second index that cuts across the directory tree with tags and categories — term pages, filter chips, the rail cloud and the navbar panel are all automatic. +description: Give pages a second index that cuts across the directory tree with tags and categories — term pages, term cards, the rail cloud and the navbar panel are all automatic. weight: 110 search_keywords: [taxonomy, taxonomies, tags, categories, term pages, tag cloud, term] @@ -12,7 +12,7 @@ aliases: A directory tree gives a page one path; a taxonomy gives it a second. The same PostgreSQL backup document sits under an "Operations" directory and is also reachable from a "backup" tag page. Enabling it needs only Hugo's `taxonomies:` -configuration: the term pages, filter chips, rail cloud and navbar panel are all +configuration: the term pages, term cards, rail cloud and navbar panel are all generated by the theme, with no template to write. This page carries a category. The "Categories: Customization" line under the @@ -143,8 +143,8 @@ Each taxonomy generates two levels of page: | Page | URL | Contents | | --- | --- | --- | -| Taxonomy list | `/categories/` | Titled with the taxonomy's localized name ("Categories"), followed by filter chips for every term, each with a count, the first being "All" | -| Term page | `/categories/customization/` | Titled "Categories: Customization", listing every page with that term newest first, styled like the blog list | +| Taxonomy list | `/categories/` | Headed by the taxonomy's glyph, its localized name ("Categories") and a term count, then one card per term, most-used first: the term glyph (an author's portrait), the term, and its page count | +| Term page | `/categories/customization/` | Headed by the term's title and its page count (a "Categories" kicker links back to the list where the breadcrumb is off), then every page with that term newest first, styled like the blog list | A Chinese term's URL uses Chinese characters (the address bar shows 定制站点 and the HTML is percent-encoded); Hugo does not transliterate. Where ASCII URLs are @@ -157,11 +157,9 @@ A term page has no fixed place in the content tree, so it borrows one: when every member of a term sits under one top-level section, the term page renders that section's sidebar tree and root link, and a reader clicking a tag from the documentation stays inside the documentation navigation. Where members span -sections, it falls back to the site-level tree. The "All" filter chip follows -the same rule: with one section it points at that section's index, and across -sections at the taxonomy list page. +sections, it falls back to the site-level tree. -Filter chips appear on the taxonomy list page only; term pages carry the rail +Term cards appear on the taxonomy list page only; term pages carry the rail cloud instead. ## The rail cloud {#rail-cloud} @@ -171,6 +169,12 @@ column (under the outline), with counted, collapsible chips. The group is automatic and has no switch: it appears wherever a taxonomy is defined and the current scope has terms. +Taxonomy list pages and term pages lead the column with a taxonomy switcher: +one row per declared taxonomy with its glyph, name and term count, linking to +its list page, the current one highlighted. A list page counts its clouds +site-wide and leaves out its own taxonomy, whose terms are the cards beside +it. A site with a single taxonomy shows no switcher. + The count is **not site-wide** but per top-level section: it first looks for a section named after the page's `type` (a `type: docs` page uses the `/docs/` tree), and otherwise uses the top-level section the page is in. "Tags: release @@ -257,7 +261,7 @@ Three things to look at on the page: - A "Categories: Customization" line under this page's title; - Chips grouped by taxonomy under the outline in the right column, each with a count; -- [/categories/](/categories/) showing filter chips for every term, each leading to its term page. +- [/categories/](/categories/) showing a card for every term, each leading to its term page. On the command line, check the output: diff --git a/content/docs/customize/taxonomy.zh.md b/content/docs/customize/taxonomy.zh.md index bdc40db..494e949 100644 --- a/content/docs/customize/taxonomy.zh.md +++ b/content/docs/customize/taxonomy.zh.md @@ -1,7 +1,7 @@ --- title: 分类体系 linkTitle: 分类体系 -description: 用 tags / categories 给页面加一条横跨目录的索引:术语页、筛选芯片、右栏分类云与顶栏分类面板都是自动的。 +description: 用 tags / categories 给页面加一条横跨目录的索引:术语页、术语卡片、右栏分类云与顶栏分类面板都是自动的。 weight: 110 search_keywords: [分类体系, 分类法, 标签, 分类, 术语页, 标签云, taxonomy, taxonomies, tags, categories, term] @@ -9,7 +9,7 @@ aliases: - /docs/content/taxonomy/ --- -目录树只有一条路径,分类体系(taxonomy)给页面加第二条:同一篇 PostgreSQL 备份文档既在「运维」目录下,又能从「备份」标签页找到。启用它只需要 Hugo 的 `taxonomies:` 配置,术语页、筛选芯片、右栏分类云与顶栏分类面板都由主题自动生成,无需编写模板。 +目录树只有一条路径,分类体系(taxonomy)给页面加第二条:同一篇 PostgreSQL 备份文档既在「运维」目录下,又能从「备份」标签页找到。启用它只需要 Hugo 的 `taxonomies:` 配置,术语页、术语卡片、右栏分类云与顶栏分类面板都由主题自动生成,无需编写模板。 本页带着一个分类:标题下面的「分类: 定制站点」一行,以及右栏目录下面那组带计数的芯片,都不需要在页面上写配置。 @@ -117,19 +117,21 @@ taxonomies: | 页面 | URL | 内容 | | --- | --- | --- | -| 分类法列表页 | `/zh/categories/` | 标题是分类法的本地化名(「分类」),下面是全部术语的筛选芯片,每枚带计数,第一枚是「全部」 | -| 术语页 | `/zh/categories/定制站点/` | 标题是「分类: 定制站点」,下面按日期倒序列出该术语的全部页面,样式与博客列表一致 | +| 分类法列表页 | `/zh/categories/` | 页头是分类法图标、本地化名(「分类」)与术语数,下面每个术语一张卡片,使用次数多者在前:术语图标(作者则是头像)、术语名与页面数 | +| 术语页 | `/zh/categories/定制站点/` | 页头是术语标题与页面数(关闭面包屑时另有一行「分类」kicker 链回列表页),下面按日期倒序列出该术语的全部页面,样式与博客列表一致 | 中文术语的 URL 使用中文字符(浏览器地址栏显示 `定制站点`,HTML 里是百分号编码),Hugo 不做拼音转写。需要 ASCII URL 时改用英文术语,再在 `content/categories/<术语>/_index.zh.md` 里用 `title` 给它一个中文显示名,这是 Hugo 的[术语页内容文件](https://gohugo.io/content-management/taxonomies/#add-custom-metadata-to-a-taxonomy-or-term)机制。 -术语页在内容树里没有固定位置,它借用一个:某术语的成员全部位于同一个顶层栏目下时,术语页用那个栏目渲染侧栏树与根链接,读者从文档里点进标签仍留在文档导航中;成员跨栏目时回退到站点级的树。筛选芯片里的「全部」按同一规则处理:只有一个栏目时指向该栏目首页,跨栏目时指向分类法列表页。 +术语页在内容树里没有固定位置,它借用一个:某术语的成员全部位于同一个顶层栏目下时,术语页用那个栏目渲染侧栏树与根链接,读者从文档里点进标签仍留在文档导航中;成员跨栏目时回退到站点级的树。 -筛选芯片只出现在分类法列表页;术语页上换成右栏的分类云。 +术语卡片只出现在分类法列表页;术语页上换成右栏的分类云。 ## 右栏的分类云 {#rail-cloud} 文档页、博客页与术语页的右栏(目录下面)每种分类法一组,芯片带计数,可折叠。这一组是自动的,没有开关:定义了分类法且当前范围内有术语时就会出现。 +分类法列表页与术语页的右栏最上面是分类法切换器:声明的每种分类法一行,带图标、名称与术语数,链向各自的列表页,当前那一行高亮。列表页的分类云按全站统计,并略去自己这一种——它的术语就是旁边的卡片。只有一种分类法的站点不显示切换器。 + 计数 **不是全站计数**,而是按顶层栏目统计:先看页面的 `type` 有没有同名栏目(`type: docs` 的页面用 `/docs/` 这棵树),没有就用页面所在的顶层栏目。博客页上的「标签: release 4」说的是博客里有 4 篇,不是全站有 4 篇。 图标按复数名配置: @@ -200,7 +202,7 @@ tags: [PostgreSQL, Patroni, 故障切换] - 本页标题下面有一行「分类: 定制站点」; - 右栏目录下面有按分类法分组的芯片,每枚带计数; -- 打开 [/zh/categories/](/zh/categories/) 能看到全部术语的筛选芯片,点任一枚进入术语页。 +- 打开 [/zh/categories/](/zh/categories/) 能看到每个术语一张卡片,点任一张进入术语页。 命令行上查产物: diff --git a/content/docs/design/architecture.md b/content/docs/design/architecture.md index 9bad9b3..b08b100 100644 --- a/content/docs/design/architecture.md +++ b/content/docs/design/architecture.md @@ -157,6 +157,13 @@ Every base template sets `Page.Store.tdOutputFormat`: | NAVJSON | Opt-in per site: one `navigation.json` per language, serializing the navigation authority the sidebar and pager already read | | BookManifest | Opt-in ordered JSON handoff for a publication packager; never presented as an EPUB or PDF | +Output formats run in their defined order; the mutable-format concern is not a +cross-format race. Within Print, however, Hugo may render a Book page and +overlapping aggregates in parallel. One per-page cached coordinator therefore +produces the plain and Book variants in a fixed order, and each caller selects +the form it needs. Plain Print keeps page-local heading and routed xref URLs; +Book aggregates keep namespaced headings and in-document xrefs. + Consumers opt into custom outputs; OINK does not force expensive Book aggregates. HTML gets the shared action and core layers plus stable first-party capability chunks selected by the page flags. Templated capabilities publish at @@ -216,8 +223,7 @@ Performance rules: - validate reachable author input, not hypothetical internal states. `bin/measure-baseline.py` measures build time, output weight, bundle count, and -shortcode density. `bin/sites/build-all.py` builds maintained consumers in -isolated snapshots. +shortcode density. ## Trust, CSS, and accessibility {#trust-css-and-accessibility} diff --git a/content/docs/design/architecture.zh.md b/content/docs/design/architecture.zh.md index bee4c3a..a4cd12d 100644 --- a/content/docs/design/architecture.zh.md +++ b/content/docs/design/architecture.zh.md @@ -137,6 +137,11 @@ front matter 覆盖。`banner` 在单页标题上方渲染图片,`wash` 用图 | NAVJSON | 按站点选择启用:每种语言一份 `navigation.json`,序列化侧栏与 pager 已经在读的导航权威 | | BookManifest | 选择启用、供出版打包器消费的有序 JSON 交接;绝不冒充 EPUB 或 PDF | +各输出格式按既定顺序执行;可变格式状态并不存在跨格式竞态。但在 Print 内,Hugo +可能并行渲染同一 Book 页面与相互重叠的聚合。因此每页由一个缓存 coordinator 按 +固定顺序生成普通与整书两种变体,各调用方只选择自己需要的形态。普通 Print 保留 +页面局部标题与带路由的 xref URL;Book 聚合保留带命名空间的标题与文档内 xref。 + 站点自行选择是否启用自定义输出;OINK 不会强制生成昂贵的整书聚合。HTML 加载 共享操作层、核心层,以及由页面 flag 选择的稳定第一方能力分片。需要模板化的能力 每种语言至多发布一份;flag 只决定引用哪些 script tag,绝不再生成新的组合 bundle。 @@ -182,7 +187,7 @@ Chrome/Chromium 二进制,输出带 CSS 页码的 A4 页面。两种工具都 - 校验确实可达的作者输入,不校验假想的内部状态。 `bin/measure-baseline.py` 测量构建时间、输出体积、bundle 数量与 shortcode -密度。`bin/sites/build-all.py` 在隔离快照中构建维护范围内的消费站点。 +密度。 ## 信任边界、CSS 与无障碍 {#trust-css-and-accessibility} diff --git a/content/docs/design/components.md b/content/docs/design/components.md index a906451..370d92b 100644 --- a/content/docs/design/components.md +++ b/content/docs/design/components.md @@ -138,6 +138,11 @@ All code highlighting uses Chroma. Common fence attributes include `title`, JSON/YAML; callbacks use `$fn:` from `window.OinkEchartsFunctions`, never embedded script execution. +Swagger and Redoc accept an HTTP(S) specification URL or a path rooted under +`static/`; neither resolves page resources. Redoc treats leading and +non-leading slashes equivalently and joins local paths to `baseURL`. Only HTML +is interactive; Print, Markdown, and RSS render a static specification link. + ### Book {#book} The `book` type extends the docs shell and follows the content tree or diff --git a/content/docs/design/components.zh.md b/content/docs/design/components.zh.md index 1883220..8573ecc 100644 --- a/content/docs/design/components.zh.md +++ b/content/docs/design/components.zh.md @@ -123,6 +123,10 @@ Markdown 图片钩子是普通图片 API。行内图片保持行内;块图片 操作返回作者源文。ECharts 输入是声明式 JSON/YAML;回调使用 `window.OinkEchartsFunctions` 中的 `$fn:`,绝不执行嵌入脚本。 +Swagger 与 Redoc 接受 HTTP(S) 规范 URL 或以 `static/` 为根的路径,都不解析页面 +资源。Redoc 将开头有无斜杠视为等价,并把本地路径与 `baseURL` 拼接。只有 HTML +输出可交互;Print、Markdown 与 RSS 输出静态规范链接。 + ### Book {#book} `book` 类型扩展 docs 外壳,并遵循内容树或 `data/docs_nav.json`。`book_number`、 diff --git a/content/docs/design/migration.md b/content/docs/design/migration.md index d566147..d490ccf 100644 --- a/content/docs/design/migration.md +++ b/content/docs/design/migration.md @@ -70,7 +70,7 @@ matching front-matter keys but never edits site configuration. | `github_url` | `github_repo` | | `ui.no_left_sidebar` | `ui.sidebar_enabled` (inverted) | | breadcrumb aliases | `ui.breadcrumb` | -| `ui.scrollSpy` | `ui.scroll_spy` (inverted) | +| `ui.scrollSpy` | No behavioral replacement; `ui.scroll_spy` remains a quiet 1.x compatibility no-op | | `ui.showLightDarkModeMenu` | `ui.dark_mode.show_menu` | | `ui.readingtime` | `ui.reading_time` | | `ui.ul_show` | `ui.sidebar_expand_levels` | diff --git a/content/docs/design/migration.zh.md b/content/docs/design/migration.zh.md index 3223d22..27620be 100644 --- a/content/docs/design/migration.zh.md +++ b/content/docs/design/migration.zh.md @@ -67,7 +67,7 @@ pg-internal profile;它不是通用解析器。 | `github_url` | `github_repo` | | `ui.no_left_sidebar` | `ui.sidebar_enabled`,取反 | | breadcrumb 别名 | `ui.breadcrumb` | -| `ui.scrollSpy` | `ui.scroll_spy`,取反 | +| `ui.scrollSpy` | 无行为替代;`ui.scroll_spy` 仅作为 1.x 静默兼容 no-op 保留 | | `ui.showLightDarkModeMenu` | `ui.dark_mode.show_menu` | | `ui.readingtime` | `ui.reading_time` | | `ui.ul_show` | `ui.sidebar_expand_levels` | diff --git a/content/docs/design/proposals/_index.md b/content/docs/design/proposals/_index.md index 16516f7..1fcb10a 100644 --- a/content/docs/design/proposals/_index.md +++ b/content/docs/design/proposals/_index.md @@ -26,8 +26,17 @@ or the documentation repository. | ------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------- | | [Backlinks and knowledge graph](/docs/design/proposals/knowledge-graph/) | G1 (static backlinks) is accepted, implemented on the theme's main branch, and ships with OINK 0.8.0; the local and global graphs (G2/G3) remain draft | | [Media convergence](/docs/design/proposals/media-convergence/) | Partially implemented; the media-result contract and Landing resource metadata shipped, M3 resolved for native-image processing, retirement (M4) open | -| [Bulk agent indexes](/docs/design/proposals/agent-indexes/) | Accepted (2026-08-27); both outputs are implemented on the theme's main branch and ship with OINK 0.8.0, after which this proposal retires | -| [Book publication pipeline](/docs/design/proposals/book-publication/) | Manifest and EPUB/PDF tooling are released — see [Architecture](/docs/design/architecture/); only consumer migration is still open here | + +The bulk agent-index proposal retired after the outputs shipped. Its stable +behaviour now belongs to [Architecture](/docs/design/architecture/#outputs-and-runtime), +and user steps belong to [Agent-ready output](/docs/customize/agents/). +The Book publication proposal likewise retired after `BookManifest` and the +EPUB/PDF tooling shipped. The stable behaviour belongs to +[Architecture](/docs/design/architecture/#outputs-and-runtime) and +[Writing a book](/docs/write/book/#print); dated downstream adoption evidence +belongs to [Consumer evidence](/docs/design/research/consumer-evidence/#publication-adoption). +Remaining consumer adoption does not keep an upstream design proposal active. +Both proposal drafts remain available in Git history. The generated-configuration-schema proposal has been retired through the lifecycle: the behaviour is documented normatively in diff --git a/content/docs/design/proposals/_index.zh.md b/content/docs/design/proposals/_index.zh.md index dcb235d..1278cb2 100644 --- a/content/docs/design/proposals/_index.zh.md +++ b/content/docs/design/proposals/_index.zh.md @@ -23,8 +23,14 @@ design_status: active | ---------------------------------------------------------------- | ---------------------------------------------------------------------- | | [反向链接与知识图谱](/zh/docs/design/proposals/knowledge-graph/) | G1(静态反向链接)已接受,已在主题 main 分支实现,随 OINK 0.8.0 发布;局部与全站图谱(G2/G3)保持草案 | | [媒体收敛](/zh/docs/design/proposals/media-convergence/) | 部分已实现;media-result 契约与 Landing 资源元数据已交付,M3 决议为原生图片处理,退役(M4)保持开放 | -| [Agent 批量索引](/zh/docs/design/proposals/agent-indexes/) | 已接受(2026-08-27);两类输出都已在主题 main 分支实现,随 OINK 0.8.0 发布,之后本提案退役 | -| [Book 出版链路](/zh/docs/design/proposals/book-publication/) | manifest 与 EPUB/PDF 工具已随版本发布,见[架构](/zh/docs/design/architecture/);本页只剩消费站迁移未决 | + +Agent 批量索引提案已在输出交付后退役。稳定行为现在归属 +[架构](/zh/docs/design/architecture/#outputs-and-runtime),用户步骤归属 +[Agent 就绪输出](/zh/docs/customize/agents/)。Book 出版提案也在 `BookManifest` 与 EPUB/PDF +工具交付后退役。稳定行为归属[架构](/zh/docs/design/architecture/#outputs-and-runtime)与 +[创作书籍](/zh/docs/write/book/#print),带日期的下游采纳证据归属 +[消费站证据](/zh/docs/design/research/consumer-evidence/#publication-adoption)。剩余的消费站采纳工作 +不会让上游设计提案继续保持活动状态。两份提案草案均由 Git 历史保存。 生成式配置 Schema 提案已按生命周期退役:行为的规范位置是[配置总览](/zh/docs/customize/config/#editor-schema), 长期理由进入[生成式配置 Schema 决策](/zh/docs/design/decisions/config-schema/),草案原文由 Git 历史保存。 diff --git a/content/docs/design/proposals/agent-indexes.md b/content/docs/design/proposals/agent-indexes.md deleted file mode 100644 index 8387a19..0000000 --- a/content/docs/design/proposals/agent-indexes.md +++ /dev/null @@ -1,139 +0,0 @@ ---- -title: Bulk agent indexes -linkTitle: Agent indexes -description: Optional per-section full-text bundles and a stable navigation JSON, built on OINK's existing Markdown outputs and navigation authority. -weight: 30 -icon: fa-solid fa-robot -search_keywords: [llms-full.txt, navigation JSON, agent outputs, machine-readable, LLMS] -design_kind: proposal -design_status: accepted -proposal_date: 2026-08-20 ---- - -> [!IMPORTANT] Implemented; ships with OINK 0.8.0 -> Accepted on 2026-08-27 after resolving every open decision. Both outputs -- -> the per-top-level-section `LLMSFULL` bundles and the `NAVJSON` navigation -> tree at the language root -- are implemented on the theme's `main` branch and -> ship with OINK 0.8.0, after which this proposal retires; released behaviour is -> owned by the -> [Architecture contract](/docs/design/architecture/#outputs-and-runtime). -> OINK already shipped per-page Markdown, the in-language `llms.txt`, the HTML -> discovery link, and Copy Markdown; this page covers only the two new outputs. - -## Current baseline {#current-baseline} - -A site can enable Hugo's Markdown output for pages and sections, and the `LLMS` home output that -generates `llms.txt`. OINK renders shortcodes as semantic Markdown, keeps source URLs and the -in-language LLMS index discoverable, and Copy Markdown reads the same alternative output URL. The -theme declares output formats but never forces a site's `outputs` choice. - -Navigation already has an authority chain: an explicit `data/docs_nav.json` tree when present, -otherwise the content tree and weights. The sidebar, the pager, and declared section indexes share -that authority. Machine navigation output must derive from the same tree rather than invent ordering. - -## Goals and non-goals {#goals-and-non-goals} - -Goals: - -- Optionally assemble an in-language full-text bundle for each explicitly enabled top-level section; -- Optionally publish a versioned navigation JSON for agents and external tools; -- Reuse the human site's page renderer, page-inclusion rules, and navigation authority; -- Keep every output opt-in through Hugo output configuration; -- Validate links, language isolation, media types, and deterministic order. - -Non-goals: - -- Replacing per-page Markdown or `llms.txt`; -- A new `params.oink.*` configuration tree; -- Scraping generated `public/` files during the Hugo build; -- Embedding private source paths, draft pages, or cross-language fallback; -- Promising that one giant full-text file fits every model context. - -## Full-text bundle {#full-text-bundle} - -The `llms-full.txt` output concatenates the same semantic Markdown used for per-page output, with a -stable, visible separator and source URL between pages. Version 1 implements per-top-level-section -bundles only: each top-level section that explicitly enables the format in its `_index` front matter -`outputs` gets one file per language. The whole-site single-file shape is deferred until real-site -evidence shows per-section bundling is not enough — a giant single file both outgrows model contexts -and couples every section's updates to one artifact. - -Hugo output configuration decides which sections receive the format, never a theme parameter. The -theme ships a checker that reports mismatches between intent and actual output, but must not modify -the site's output set. - -The bundle is assembled inside Hugo through the shared page-render partial; it never reads sibling -artifacts from `public/` and never depends on output build order. File size is reported as evidence; -no arbitrary threshold may let `--panicOnWarning` reject an otherwise legal publish. - -## Navigation JSON {#navigation-json} - -The navigation JSON is a home output beside `llms.txt`: one file per language at the language root. -It carries a schema version, the language, the root node, and recursive ordered nodes. Page nodes -carry a stable ID (the in-language permalink path), title, HTML URL, Markdown URL when enabled, -kind, and children — plus the description when one exists. Explicit external navigation nodes carry -only a label, a URL, and the external kind. - -Nodes **do not serialize `weight`**: array order is the contract, weight is the private mechanism -that derives it, and publishing it would invite consumers to re-sort. The output follows the same -visibility and ordering rules as the rendered sidebar, excluding drafts, headless resources, hidden -navigation items, and pages unavailable in the current language, and never serializes local -filenames. - -The format owns its JSON Schema (`schema/nav.v1.schema.json`, a hand-authored, versioned contract -artifact outside the generated configuration schema's drift gate) and golden fixtures, and is marked -`notAlternative` so Hugo never advertises it as a page-level alternate. - -## Discovery and output boundaries {#discovery-and-output-boundaries} - -`llms.txt` lists the enabled bundles and the navigation JSON by default — discovery belongs in the -index file; that is what it is for. The HTML head keeps discovering per-page Markdown and the -in-language LLMS index without stuffing every bulk artifact into every page. - -Shortcodes, Landing sections, Book targets, and interactive components keep their current Markdown -degradation. The new outputs may not add component HTML, scripts, comments, feedback controls, or -navigation chrome. - -## Compatibility and migration {#compatibility} - -Both outputs default off; a site that does not enable them is byte-identical. Enabling is a -site-side Hugo `outputs` choice — no new `params` keys, no renames, no migration steps. Disabling an -output is a complete exit with no residue. - -## Implementation and owning-checker plan {#implementation-plan} - -1. Output formats: `LLMSFULL` (`text/plain`, `baseName: llms-full`, `notAlternative`, section - level) and `NAVJSON` (`application/json`, `notAlternative`, home level), declared beside the - existing `MARKDOWN`/`LLMS` definitions. -2. Templates: the section `llms-full` layout concatenates the shared per-page Markdown render - partial in navigation order; the home navigation JSON layout walks the existing navigation - authority partial rather than introducing a second tree traversal. -3. Owning checker: a new `bin/check-agent-indexes.py` validates, on the `tests/site` fixture, - language isolation, link resolvability, sidebar-consistent order, schema compliance, and - byte-stable rebuilds, and reports each bundle's bytes and page count (report only, no ceiling - gate). -4. Goldens: the `check-goldens.py` matrix gains llms-full and navigation JSON fixtures. -5. Documentation: the site gains a bilingual agent-index guide; `llms.txt` discovery behaviour - folds into the existing LLMS documentation; this proposal retires through the lifecycle. - -## Acceptance criteria {#acceptance-criteria} - -- EN and ZH outputs contain only their own language's pages and URLs. -- Every listed Markdown URL exists; every navigation URL resolves or is explicitly external. -- Order under the same root matches the rendered sidebar and pager. -- The navigation JSON validates against `schema/nav.v1.schema.json`. -- With a pinned Hugo version and inputs, rebuilding the same sources is byte-stable. -- With the new formats off, HTML, Markdown, Print, RSS, and LLMS goldens show no regression. -- A large-site fixture proves per-top-level-section bundling rather than a file for every nested - section. - -## Decision log {#decision-log} - -- 2026-08-20: Drafted; the bundle listed whole-site and per-section shapes, and the navigation - JSON's location was undecided. -- 2026-08-27: Resolved the five open decisions and accepted the proposal. Version 1 builds - per-top-level-section bundles only, deferring the whole-site file until real evidence; the - navigation JSON is a home output; schema v1 node metadata is the minimal set (stable ID, title, - HTML URL, Markdown URL, kind, children, optional description) with `weight` never serialized; - `llms.txt` lists both enabled artifacts by default; the checker reports size evidence without - enforcing any model-context ceiling. diff --git a/content/docs/design/proposals/agent-indexes.zh.md b/content/docs/design/proposals/agent-indexes.zh.md deleted file mode 100644 index 730d471..0000000 --- a/content/docs/design/proposals/agent-indexes.zh.md +++ /dev/null @@ -1,120 +0,0 @@ ---- -title: Agent 批量索引 -linkTitle: Agent 索引 -description: 基于 OINK 既有 Markdown 输出与导航权威,可选生成按 section 分包的全文包和稳定导航 JSON。 -weight: 30 -icon: fa-solid fa-robot -search_keywords: [llms-full.txt, 导航 JSON, Agent 输出, 机器可读, LLMS] -design_kind: proposal -design_status: accepted -proposal_date: 2026-08-20 ---- - -> [!IMPORTANT] 已实现,随 OINK 0.8.0 发布 -> 2026-08-27 决议全部待决问题后接受本提案。两类输出——按顶层 section 分包的 -> `LLMSFULL` 与语言根下的 `NAVJSON` 导航树——都已在主题 `main` 分支实现,随 OINK -> 0.8.0 发布,之后本提案退役;发布后的行为由[架构契约](/zh/docs/design/architecture/#outputs-and-runtime) -> 承担。OINK 已经支持每页 Markdown、语言内 `llms.txt`、HTML discovery link 与 -> Copy Markdown;本页只覆盖新增的两类。 - -## 当前基线 {#current-baseline} - -站点可以为 page 与 section 启用 Hugo 的 Markdown 输出,并为 home 启用生成 `llms.txt` 的 `LLMS` -输出。OINK 把 shortcode 渲染成语义化 Markdown,保留源码 URL 和语言内 LLMS 索引发现信息,Copy -Markdown 也读取同一个 alternative output URL。主题声明输出格式,但不强迫站点选择哪些 `outputs`。 - -导航已经存在权威链:有显式 `data/docs_nav.json` 树时使用它,否则使用内容树与 weight。侧栏、 -pager 与已声明 section index 共用这一权威。机器导航输出必须从同一棵树派生,不能再造排序。 - -## 目标与非目标 {#goals-and-non-goals} - -目标: - -- 为显式启用的顶层 section 可选装配语言内全文包; -- 可选发布带版本的导航 JSON,供 Agent 与外部工具使用; -- 复用人工站点的同一 Markdown 页面渲染器、页面纳入规则与导航权威; -- 所有输出仍通过 Hugo output 配置 opt-in; -- 验证链接、语言隔离、media type 与确定性顺序。 - -非目标: - -- 替换每页 Markdown 或 `llms.txt`; -- 新建 `params.oink.*` 配置树; -- 在 Hugo 构建期间抓取生成好的 `public/` 文件; -- 嵌入私有源码路径、草稿页面或跨语言回退; -- 承诺一个巨型全文包适合所有模型上下文。 - -## 全文包 {#full-text-bundle} - -`llms-full.txt` 输出拼接每页输出所用的同一份语义化 Markdown。页面之间使用稳定、可见的 -分隔符与来源 URL。第 1 版只实现按顶层 section 分包:每个在自身 `_index` front matter 的 -`outputs` 中显式启用该格式的顶层 section,在语言内得到一个文件。整站单文件形态被推迟, -待真实站点证据表明按 section 分包不够用时再议——巨型单文件既容易超出模型上下文,又会 -把所有 section 的更新耦合到一个产物上。 - -由 Hugo output 配置决定哪些 section 获得该格式,而不是由主题参数决定。主题提供检查器, -报告意图与实际输出不一致,但不能修改站点输出集合。 - -全文包在 Hugo 内部通过共享页面渲染 partial 组装,不读取 `public/` 中的兄弟产物,也不依赖输出 -构建顺序。文件大小作为证据报告;任意阈值不能通过警告让 `--panicOnWarning` 拒绝原本合法的发布。 - -## 导航 JSON {#navigation-json} - -导航 JSON 是 home output,与 `llms.txt` 同级:每种语言在语言根下一个文件。内容包含 -schema 版本、语言、根节点与递归有序节点。页面节点包含稳定 ID(语言内 permalink 路径)、 -标题、HTML URL、启用时的 Markdown URL、kind 与 children;有 description 时一并携带。 -显式外部导航节点只包含标签、URL 与 external kind。 - -节点不序列化 `weight`:数组顺序就是契约,weight 是派生顺序的私有机制,公开它会诱导 -消费者重新排序。输出遵循渲染侧栏相同的可见性与排序规则,排除 draft、headless resource、 -隐藏导航项与当前语言不可用页面,永不序列化本机文件名。 - -该格式拥有自己的 JSON Schema(`schema/nav.v1.schema.json`,手工编写的版本化契约产物, -不属于生成式配置 Schema 的漂移门禁)与 golden 夹具,并标记为 `notAlternative`,避免 Hugo -把它广告为页面级 alternate。 - -## 发现信息与输出边界 {#discovery-and-output-boundaries} - -`llms.txt` 默认列出已经启用的全文包与导航 JSON——发现信息属于索引文件,这正是它存在的 -理由。HTML head 继续发现每页 Markdown 和语言内 LLMS 索引,不把每个批量产物塞进每一页。 - -shortcode、Landing section、Book 目标与交互组件继续使用当前 Markdown 降级。新输出无权增加组件 -HTML、脚本、评论、反馈控件或导航 chrome。 - -## 兼容与迁移影响 {#compatibility} - -两种输出都默认关闭,未启用的站点字节不变。启用是站点侧的 Hugo `outputs` 配置,没有新的 -`params` 键,没有重命名,没有迁移步骤。关闭输出即完全退出,不留残余。 - -## 实现与归属检查器计划 {#implementation-plan} - -1. 输出格式:`LLMSFULL`(`text/plain`、`baseName: llms-full`、`notAlternative`、 - section 级)与 `NAVJSON`(`application/json`、`notAlternative`、home 级), - 与既有 `MARKDOWN`/`LLMS` 定义并列声明。 -2. 模板:section 的 `llms-full` 布局复用每页 Markdown 输出的共享渲染 partial 按导航 - 顺序拼接;home 的导航 JSON 布局走既有导航权威 partial,不引入第二套树遍历。 -3. 归属检查器:新增 `bin/check-agent-indexes.py`,在 `tests/site` 夹具上验证语言隔离、 - 链接可解析、顺序与侧栏一致、schema 合规、字节稳定重建,并报告每个包的字节数与页数 - (只报告,不设上限门禁)。 -4. Golden:`check-goldens.py` 矩阵增加 llms-full 与导航 JSON 夹具。 -5. 文档:站点新增双语 Agent 索引指南;`llms.txt` 发现行为并入既有 LLMS 文档; - 本提案按生命周期退役。 - -## 验收标准 {#acceptance-criteria} - -- EN 与 ZH 输出只包含各自语言的页面和 URL。 -- 每个列出的 Markdown URL 都存在;每个导航 URL 都可解析,或明确标记为外部节点。 -- 同一根下的顺序与渲染侧栏、pager 一致。 -- 导航 JSON 通过 `schema/nav.v1.schema.json` 校验。 -- 固定 Hugo 版本与输入时,相同源码重建得到字节稳定输出。 -- 新格式关闭时,HTML、Markdown、Print、RSS 与 LLMS golden 均无回归。 -- 大站夹具能证明按顶层 section 分包,而不是为每个嵌套 section 都生成文件。 - -## 决策日志 {#decision-log} - -- 2026-08-20:起草;全文包给出全站与按 section 两种形态,导航 JSON 位置未定。 -- 2026-08-27:决议五个待决问题并接受提案。全文包第 1 版只做按顶层 section 分包, - 整站单文件推迟到有真实证据;导航 JSON 定为 home output;schema v1 节点元数据取 - 最小集(稳定 ID、标题、HTML URL、Markdown URL、kind、children、可选 description), - 不序列化 `weight`;`llms.txt` 默认列出已启用的两类产物;检查器只报告体积证据, - 不执行任何模型上下文上限。 diff --git a/content/docs/design/proposals/book-publication.md b/content/docs/design/proposals/book-publication.md deleted file mode 100644 index 58f0a3f..0000000 --- a/content/docs/design/proposals/book-publication.md +++ /dev/null @@ -1,244 +0,0 @@ ---- -title: Book publication pipeline -linkTitle: Book publication -description: Move Book semantic lowering and generic EPUB/PDF packaging into OINK without taking publication facts away from consumers. -weight: 40 -icon: fa-solid fa-book-open -search_keywords: [Book publication, EPUB, PDF, Pandoc, print, manifest] -design_kind: proposal -design_status: draft -proposal_owner: OINK maintainers -proposal_date: 2026-08-24 -affected_contracts: [architecture, authoring, Book, outputs] ---- - -> [!WARNING] Draft proposal with implemented publication tooling -> The opt-in `BookManifest`, the generic EPUB and PDF runners and their artifact -> validation are released, and the normative account of them is in -> [Architecture](/docs/design/architecture/). No build publishes either file on -> its own. What remains open here, and only here, is consumer migration. - -## Context and evidence {#context-and-evidence} - -OINK already owns Book navigation order, numbered figures, tables, equations, -examples, cross-references, whole-Book Print HTML, heading and footnote -namespacing, and per-page Markdown degradation. The missing boundary is a -machine-readable whole-Book handoff that a generic packager can consume. - -DDIA currently carries a substantial EPUB preprocessor that follows OINK's -numbered primitives, cross-page links, footnotes, image paths, and Book order. -TPME retains an older exporter whose historical root-file inputs no longer -match its current Hugo content tree. The first proves that publication is real; -the second proves that a consumer-local recipe can quietly become stale. - -An EPUB is not one rendered template. It is a ZIP container with publication -metadata, a resource manifest, a spine, navigation, content documents, styles, -and media. Hugo can render an intermediate output, but a packaging tool must -produce and validate the final file. - -## Goals {#goals} - -1. Give every Book primitive one theme-owned semantic result for publication. -2. Publish an opt-in whole-Book intermediate with deterministic page order, - stable targets, and cross-references; resolve local resources from the - semantic Print document only during explicit packaging. -3. Provide a generic, versioned EPUB packager and a version-pinned Print-to-PDF - runner that consumers invoke with their own publication facts. -4. Prove the boundary on two structurally different public Book consumers. - -## Non-goals {#non-goals} - -- Do not enable an expensive aggregate output for every site or section. -- Do not call a Markdown, JSON, or HTML intermediate an EPUB. -- Do not infer title, author, cover, ISBN, edition, rights, or release policy. -- Do not fetch remote images or services during an ordinary Hugo build. -- Do not add a second Book shell, duplicate navigation authority, or a generic - publication-configuration namespace. -- Do not promise pixel-identical PDF pagination across browser engines. - -## Ownership boundary {#ownership-boundary} - -| OINK owns | The consumer owns | -| --- | --- | -| Book order derived from the existing navigation authority | Which language, edition, and Book root to publish | -| Semantic lowering for `fig`, `tbl`, `eq`, `eg`, xrefs, headings, and footnotes | Title, authors, identifier, cover, rights, and imprint | -| Stable intermediate schema and generic packager behavior | Optional chapter exclusions and publication-specific front/back matter | -| EPUB structure/link validation and Print-to-PDF runner | Release automation, signing, distribution, and legal approval | -| Theme fixtures and compatibility checks | Content correctness and final artifact approval | - -The consumer passes facts; it does not patch OINK markup. OINK supplies -semantics; it does not decide whether a book may be distributed. - -## Proposed behavior {#proposed-behavior} - -The first implementation is an opt-in Book manifest, not a final ebook. It -references the already published per-page Markdown and records only facts the -theme can derive honestly: - -- schema version and language; -- Book root and flattened page order; -- page title, optional Book number, HTML URL, and Markdown URL; -- stable heading and numbered-object targets; -- cross-page references. - -The manifest is emitted only for a Book root that explicitly enables the output. -The normal HTML, Print, Markdown, RSS, search, and navigation builds remain -byte-for-byte independent of that opt-in. - -The OINK EPUB packager consumes the manifest and the existing whole-Book Print -HTML, whose semantic output already contains namespaced headings and footnotes, -numbered targets, raw authored anchors, MathML, and static interaction -fallbacks. It rewrites only publication URLs, invokes pinned Pandoc 3.10, -and validates the result with EPUBCheck plus OINK's internal-target checker. A -consumer supplies a small metadata file and cover. Per-page Markdown remains in -the manifest as an auditable source-shaped output, not as a second semantic -conversion path. - -Local resources must exist under the generated `public/` tree and are packaged -without network access. A consumer that knowingly retains a remote image must -opt in with `--allow-remote-resources`; this permits passive HTTP(S) media only, -never remote scripts or local-file schemes. The tool refuses to replace an -existing EPUB unless `--force` is explicit. - -PDF derives from the same whole-Book Print HTML. The runner exposes the build on -a temporary loopback server with `script-src 'none'`, blocks external resources -by default, invokes an explicit Chrome/Chromium binary, and refuses implicit -replacement. Print CSS -owns A4 size, paper-safe code wrapping, full-width numbered equations, and page -numbers. The checker uses Poppler to verify the PDF structure, A4 geometry, -extractable Book titles, and sampled page-number margins; final approval still -includes rendered-page review. - -The reference workflow remains four explicit steps, not a new Hugo mode: - -```sh -python3 /path/to/oink/bin/book-epub.py --manifest public/book/book.json \ - --public public --metadata metadata.yaml --output output/book.epub -python3 /path/to/oink/bin/check-book-epub.py output/book.epub \ - --manifest public/book/book.json -python3 /path/to/oink/bin/book-pdf.py --manifest public/book/book.json \ - --public public --chrome /path/to/chrome-headless-shell --output output/book.pdf -python3 /path/to/oink/bin/check-book-pdf.py output/book.pdf \ - --manifest public/book/book.json -``` - -## Output, accessibility, and security {#output-accessibility-security} - -- HTML and existing outputs do not load an exporter or gain browser runtime. -- The intermediate preserves document language, heading hierarchy, alternative - text, table headers, link text, and source order. -- Interactive controls degrade to their existing static Markdown/Print forms. -- Resource paths must resolve inside the build output or be explicit outbound - links; packaging never follows an arbitrary local path from authored content. -- No consumer value becomes raw HTML, CSS, command arguments, or a filesystem - path without the same validation and normalization used by current outputs. - -## Compatibility and migration {#compatibility-and-migration} - -This is additive and opt-in. Existing Book sites keep their current outputs and -scripts. The DDIA pilot removes consumer-side transformations only after the -theme intermediate accounts for every currently validated chapter, numbered -object, footnote, image, and internal link. TPME is the second-consumer gate; -no DDIA-specific route, label, or chapter list may enter the generic schema. - -## Prototype evidence {#prototype-evidence} - -An isolated opt-in build against the first manifest prototype produced these -results without modifying either consumer checkout: - -| Consumer | Ordered pages | Headings | Raw Markdown anchors | Numbered targets | Xrefs | Unresolved | -| --- | ---: | ---: | ---: | ---: | ---: | ---: | -| DDIA | 23 | 597 | 33 | 131 (106 figures, 3 tables, 22 examples) | 292 | 0 | -| TPME | 18 | 295 | 946 | 41 (31 figures, 10 tables) | 1,062 | 0 | - -Neither manifest contained a duplicate numbered target ID. Strict builds added -about 0.28 seconds for DDIA and 0.22 seconds for TPME in this local sample. The -large TPME raw-anchor count is decisive: the packager must consume a rendered -output where those explicit anchors survive. Whole-Book Print HTML already does -so while also carrying namespaced headings, footnotes, numbered targets, and -MathML; the manifest must not duplicate the whole document tree. - -The same isolated snapshots were then packaged through one generic command: - -| Consumer | EPUB chapters | Typed targets | Package size | OINK package/link check | EPUBCheck 5.3.0 | -| --- | ---: | ---: | ---: | --- | --- | -| DDIA | 23 | 131 | 22.9 MB | 0 errors | 0 errors, 0 warnings | -| TPME | 18 | 41 | 2.2 MB | 0 errors | 0 errors, 0 warnings | - -DDIA's one remote poster required the explicit network-resource opt-in; TPME -packaged entirely from local output. The generic checker verifies every page -anchor and each target's `kind` and `num` against `BookManifest`, rather than -depending on the old DDIA preprocessor's wrapper classes. The theme fixture -also passes after a minified Hugo build. Publication CI pins Pandoc 3.10 and -EPUBCheck 5.3.0 by version and archive digest, in a separate job from the Hugo -compatibility matrix. - -The Print-to-PDF pilot uses Chrome for Testing headless shell 151.0.7922.34, -pinned by archive digest in the same publication CI job: - -| Consumer | Book pages | PDF pages | Package size | Structural/text/page-number check | Rendered review | -| --- | ---: | ---: | ---: | --- | --- | -| Theme fixture | 5 | 23 | 1.1 MB | 0 errors | cover, tables, code, equations, footnotes | -| DDIA | 23 | 527 | 60.3 MB | 0 errors | CJK, tables, figures, code, references, end matter | -| TPME | 18 | 197 | 8.4 MB | 0 errors | CJK, wide tables, code, callouts, end matter | - -All three PDFs are tagged, unencrypted A4 documents. The real-consumer checker -found every manifest page title and the first, middle, and last CSS page number. -Visual review exposed and fixed three pre-existing Print defects: child math did -not propagate the KaTeX stylesheet to the Book aggregate; a broad Bootstrap -column reset matched KaTeX `col-align-*` internals; and `pre > code` defeated -paper-edge wrapping. These are narrow print fixes, not exporter-specific DOM -rewrites. - -## Implementation plan {#implementation-plan} - -1. **Done:** extract the Print page sequence into one shared Book partial without - changing rendered Print output. -2. **Done:** add the disabled-by-default manifest and fixture checker. -3. **Done:** package isolated DDIA and TPME snapshots with one generic EPUB path, - then validate typed targets, internal links, and EPUB 3.3 conformance. -4. **Done:** render the theme fixture plus isolated DDIA and TPME snapshots with - one pinned Chrome runner; validate and visually review representative pages. -5. **Next consumer migration:** replace DDIA's semantic preprocessor with metadata - plus one invocation only after its repository independently accepts the new - publication gate. - -## Acceptance criteria {#acceptance-criteria} - -- Default sites publish no new aggregate file and incur no material build cost. -- The pinned Hugo Extended 0.165.0 toolchain builds the opt-in fixture with - warnings fatal. -- Existing HTML, Print, Markdown, RSS, navigation, search, and browser tests pass. -- The DDIA pilot retains 23 chapters and all 106 figures, 3 tables, 22 examples, - and internal links as typed semantic targets with zero unresolved target. -- TPME produces an artifact through the same schema and packager. -- No consumer script contains OINK primitive-specific regular expressions. -- EPUBCheck and OINK's package/link checker pass; the PDF structure/text/page - checker and representative rendered-page review pass. - -## Open decisions {#open-decisions} - -1. Should consumer migration happen before or after the next OINK release tag? - -## Decision log {#decision-log} - -- 2026-08-24: Drafted the theme/consumer ownership boundary. Chose an opt-in - semantic intermediate before any final EPUB API or implementation. -- 2026-08-24: The DDIA/TPME isolated pilot resolved the first format decision: - keep one JSON manifest and consume existing whole-Book Print HTML; do not add - a generated whole-Book Markdown output or another semantic lowering path. -- 2026-08-24: The packager consumes whole-Book Print HTML, so the manifest uses - the existing `no_print` exclusion too. This keeps one publication sequence - and avoids a second output-specific exclusion key. -- 2026-08-24: Implemented the generic EPUB path and pinned Pandoc 3.10 plus - EPUBCheck 5.3.0 in publication CI. Isolated DDIA and TPME packages pass both - the typed-target/internal-link checker and official EPUB 3.3 validation. -- 2026-08-24: Remote publication resources remain rejected by default. DDIA's - historical remote poster exercises an explicit opt-in instead of weakening - the default or adding a DDIA-specific rewrite. -- 2026-08-24: Added the loopback Print-to-PDF runner and pinned Chrome for - Testing headless shell 151.0.7922.34 by archive digest. Theme, DDIA, and TPME - PDFs pass structural, text, A4, and page-number checks plus rendered review. -- 2026-08-24: PDF review fixed only the owning Print contracts: aggregate math - capability propagation, Bootstrap column-selector scope, code wrapping, - single-column numbered equations, and CSS page margins. diff --git a/content/docs/design/proposals/book-publication.zh.md b/content/docs/design/proposals/book-publication.zh.md deleted file mode 100644 index 3410f77..0000000 --- a/content/docs/design/proposals/book-publication.zh.md +++ /dev/null @@ -1,208 +0,0 @@ ---- -title: Book 出版链路 -linkTitle: Book 出版 -description: 把 Book 语义降级与通用 EPUB/PDF 打包收回 OINK,同时让出版事实继续属于消费站。 -weight: 40 -icon: fa-solid fa-book-open -search_keywords: [Book 出版, EPUB, PDF, Pandoc, 打印, manifest] -design_kind: proposal -design_status: draft -proposal_owner: OINK 维护者 -proposal_date: 2026-08-24 -affected_contracts: [architecture, authoring, Book, outputs] ---- - -> [!WARNING] 已实现出版工具的提案草案 -> 选择启用的 `BookManifest`、通用 EPUB/PDF runner 与产物校验都已随版本发布, -> 其规范性描述在[架构](/zh/docs/design/architecture/)。没有任何一次构建会自己产出这两种文件。 -> 本提案仍然未决、且只在本提案未决的,是消费站迁移。 - -## 背景与证据 {#context-and-evidence} - -OINK 已经负责 Book 导航顺序、编号图表公式示例、交叉引用、整书 Print HTML、标题与 -脚注命名空间,以及逐页 Markdown 降级。缺失的是一份机器可读的整书交接产物,供 -通用打包器消费。 - -DDIA 当前保留了一份规模不小的 EPUB 预处理器,持续追踪 OINK 的编号原语、跨页链接、 -脚注、图片路径与 Book 顺序。TPME 则留有一份更早的导出脚本,它依赖的历史根目录文件 -已经不再匹配当前 Hugo 内容树。前者证明出版需求真实存在,后者证明消费站自有配方会 -悄悄过期。 - -EPUB 不是一份模板渲染结果。它是一个 ZIP 容器,包含出版元数据、资源 manifest、 -spine、导航、内容文档、样式与媒体。Hugo 可以渲染中间输出,但最终文件必须由打包工具 -生成并校验。 - -## 目标 {#goals} - -1. 让每一种 Book 原语在出版场景中只有一份主题拥有的语义结果。 -2. 发布选择启用的整书中间产物,确定性记录页面顺序、稳定目标与交叉引用;只在显式打包时 - 从语义 Print 文档解析本地资源。 -3. 提供通用、带版本的 EPUB 打包器,以及版本固定的 Print-to-PDF runner;消费站只传入 - 自己的出版事实。 -4. 用两个结构不同的公开 Book 消费站证明边界成立。 - -## 非目标 {#non-goals} - -- 不为每个站点或分区默认启用昂贵的聚合输出。 -- 不把 Markdown、JSON 或 HTML 中间产物称为 EPUB。 -- 不猜测书名、作者、封面、ISBN、版本、权利或发布策略。 -- 普通 Hugo 构建不抓取远程图片或服务。 -- 不增加第二套 Book 外壳、第二个导航权威或通用出版配置命名空间。 -- 不承诺不同浏览器引擎产生逐像素相同的 PDF 分页。 - -## 所有权边界 {#ownership-boundary} - -| OINK 负责 | 消费站负责 | -| --- | --- | -| 从现有导航权威导出的 Book 顺序 | 要发布的语言、版本与 Book 根 | -| `fig`、`tbl`、`eq`、`eg`、xref、标题与脚注的语义降级 | 书名、作者、标识符、封面、权利与出版者信息 | -| 稳定中间 schema 与通用打包器行为 | 可选章节排除,以及出版专用的前后置内容 | -| EPUB 结构/链接校验与 Print-to-PDF runner | 发布自动化、签名、分发与法律批准 | -| 主题夹具与兼容检查 | 内容正确性与最终产物批准 | - -消费站传递事实,不补丁 OINK 标记;OINK 提供语义,不决定一本书是否可以分发。 - -## 提议行为 {#proposed-behavior} - -第一步实现是一份选择启用的 Book manifest,不是最终电子书。它引用已经发布的逐页 -Markdown,只记录主题能够诚实推导的事实: - -- schema 版本与语言; -- Book 根与拍平后的页面顺序; -- 页面标题、可选 Book 编号、HTML URL 与 Markdown URL; -- 稳定标题目标与编号对象目标; -- 跨页引用。 - -只有明确启用该输出的 Book 根才生成 manifest。普通 HTML、Print、Markdown、RSS、 -搜索与导航构建不受这项 opt-in 影响,保持彼此独立。 - -OINK EPUB 打包器消费 manifest 与既有整书 Print HTML;后者已经包含命名空间化标题与脚注、 -编号目标、作者原始锚点、MathML 与静态交互降级。打包器只改写出版 URL,调用固定版本的 -Pandoc 3.10 profile,再用 EPUBCheck 与 OINK 自有内部目标检查器验证。消费站提供一份小型 -metadata 文件与封面。逐页 Markdown 仍作为可审计的源码形态输出记录在 manifest 中, -但不成为第二条语义转换路径。 - -本地资源必须存在于生成的 `public/` 树下,默认打包完全不访问网络。如果消费站明确保留了 -远程图片,必须显式传入 `--allow-remote-resources`;它只允许被动 HTTP(S) 媒体,绝不放行 -远程脚本或本地文件协议。除非明确传入 `--force`,工具也不会覆盖已有 EPUB。 - -PDF 来自同一份整书 Print HTML。Runner 在带 `script-src 'none'` 的临时回环服务中提供构建产物, -默认阻止外部资源,调用显式指定的 Chrome/Chromium 二进制,并拒绝隐式覆盖。Print CSS 负责 A4 纸张、纸面代码 -换行、占满版心的编号公式与页码。Checker 通过 Poppler 校验 PDF 结构、A4 几何、可提取的 -Book 标题与抽样页码;最终批准仍包含渲染页面复核。 - -参考工作流保持四个显式步骤,不增加 Hugo 模式: - -```sh -python3 /path/to/oink/bin/book-epub.py --manifest public/book/book.json \ - --public public --metadata metadata.yaml --output output/book.epub -python3 /path/to/oink/bin/check-book-epub.py output/book.epub \ - --manifest public/book/book.json -python3 /path/to/oink/bin/book-pdf.py --manifest public/book/book.json \ - --public public --chrome /path/to/chrome-headless-shell --output output/book.pdf -python3 /path/to/oink/bin/check-book-pdf.py output/book.pdf \ - --manifest public/book/book.json -``` - -## 输出、无障碍与安全 {#output-accessibility-security} - -- HTML 与现有输出不加载 exporter,也不增加浏览器运行时。 -- 中间产物保留文档语言、标题层级、替代文本、表头、链接文本与源码顺序。 -- 交互控件沿用既有静态 Markdown/Print 降级。 -- 资源路径必须解析到构建产物内部,或明确作为外部链接;打包过程绝不跟随作者内容中的 - 任意本地路径。 -- 任何消费站值在未经现有输出同等级的校验与规范化之前,都不能成为原始 HTML、CSS、 - 命令参数或文件系统路径。 - -## 兼容与迁移 {#compatibility-and-migration} - -这是一项增量、选择启用的能力。现有 Book 站保留当前输出与脚本。只有当主题中间产物能 -解释 DDIA 当前校验的每一章、编号对象、脚注、图片与内部链接后,DDIA pilot 才删除消费站 -侧的转换。TPME 是第二消费站门禁;任何 DDIA 专用路由、标签或章节清单都不能进入通用 schema。 - -## 原型证据 {#prototype-evidence} - -第一版 manifest 在两个消费站隔离快照中选择启用,未修改任一消费站工作树,结果如下: - -| 消费站 | 有序页面 | 标题 | Markdown 原始锚点 | 编号目标 | Xref | 未解析 | -| --- | ---: | ---: | ---: | ---: | ---: | ---: | -| DDIA | 23 | 597 | 33 | 131(106 图、3 表、22 示例) | 292 | 0 | -| TPME | 18 | 295 | 946 | 41(31 图、10 表) | 1,062 | 0 | - -两份 manifest 都没有重复编号目标 ID。本机样本中,严格构建额外耗时约为 DDIA 0.28 秒、 -TPME 0.22 秒。TPME 大量保留原始锚点这一事实很关键:打包器必须消费仍保留这些显式锚点的 -渲染输出。整书 Print HTML 已经同时携带命名空间化标题、脚注、编号目标与 MathML,因此 -manifest 不应再复制整棵文档树。 - -随后,两份隔离快照通过同一个通用命令完成打包: - -| 消费站 | EPUB 章节 | 带类型目标 | 包大小 | OINK 包/链接检查 | EPUBCheck 5.3.0 | -| --- | ---: | ---: | ---: | --- | --- | -| DDIA | 23 | 131 | 22.9 MB | 0 错误 | 0 错误、0 警告 | -| TPME | 18 | 41 | 2.2 MB | 0 错误 | 0 错误、0 警告 | - -DDIA 唯一的远程海报图使用了显式网络资源开关;TPME 完全由本地产物打包。通用 checker -按 `BookManifest` 校验每个页面锚点,以及每个目标的 `kind` 与 `num`,不再依赖 DDIA -旧预处理器的包装类。主题夹具同样通过了 Hugo minify 后的打包检查。出版 CI 把 Pandoc -3.10 与 EPUBCheck 5.3.0 的版本及归档摘要固定下来,并与 Hugo 兼容矩阵分为独立 job。 - -Print-to-PDF 试点使用 Chrome for Testing headless shell 151.0.7922.34,并在同一出版 CI job -按归档摘要固定: - -| 消费站 | Book 页面 | PDF 页数 | 包大小 | 结构/文本/页码检查 | 渲染复核 | -| --- | ---: | ---: | ---: | --- | --- | -| 主题夹具 | 5 | 23 | 1.1 MB | 0 错误 | 封面、表格、代码、公式、脚注 | -| DDIA | 23 | 527 | 60.3 MB | 0 错误 | CJK、表格、图片、代码、参考文献、后置内容 | -| TPME | 18 | 197 | 8.4 MB | 0 错误 | CJK、宽表、代码、callout、后置内容 | - -三份 PDF 均为 Tagged、未加密的 A4 文档。真实消费站 checker 找到了全部 manifest 页面标题, -以及首页、中页和末页的 CSS 页码。视觉复核还暴露并修复了三项既有 Print 缺陷:子页面数学 -能力未把 KaTeX 样式传播给整书聚合;过宽的 Bootstrap 列重置误命中 KaTeX `col-align-*` -内部类;`pre > code` 覆盖了纸面换行。它们都是范围明确的 Print 修复,不是 exporter 专用 -DOM 改写。 - -## 实施计划 {#implementation-plan} - -1. **已完成**:把 Print 页面序列提取成一份共享 Book partial,不改变 Print 产物。 -2. **已完成**:增加默认关闭的 manifest 与夹具 checker。 -3. **已完成**:用同一条通用 EPUB 链路打包 DDIA 与 TPME 隔离快照,再校验带类型目标、 - 内部链接与 EPUB 3.3 合规性。 -4. **已完成**:用同一份固定 Chrome runner 渲染主题夹具与 DDIA/TPME 隔离快照,校验并 - 目视复核代表页面。 -5. **下一项消费站迁移**:只有在 DDIA 仓库独立接受新出版门禁后,才用 metadata 加一次 - 调用替代它的语义预处理器。 - -## 验收标准 {#acceptance-criteria} - -- 默认站点不发布新聚合文件,也没有显著构建成本。 -- 固定的 Hugo Extended 0.165.0 工具链能在 warning 即失败模式下构建 opt-in 夹具。 -- 现有 HTML、Print、Markdown、RSS、导航、搜索与浏览器测试全部通过。 -- DDIA pilot 保留 23 章以及全部 106 图、3 表、22 示例与内部链接;这些都作为带类型的 - 语义目标存在,未解析目标为零。 -- TPME 通过同一份 schema 与打包器生成产物。 -- 消费站脚本不再包含 OINK 原语专用正则表达式。 -- EPUBCheck 与 OINK 包/链接检查器通过;PDF 结构/文本/页码 checker 与代表页面渲染复核 - 全部通过。 - -## 待决问题 {#open-decisions} - -1. 消费站迁移应发生在下一个 OINK release tag 之前还是之后? - -## 决策记录 {#decision-log} - -- 2026-08-24:起草主题/消费站所有权边界;先选择 opt-in 语义中间产物,不提前承诺最终 - EPUB API 或实现。 -- 2026-08-24:DDIA/TPME 隔离试点解决了第一项格式决策:保留一份引用既有逐页 Markdown - 的 JSON manifest,并消费既有整书 Print HTML;不增加生成式整书 Markdown 或另一条 - 语义降级路径。 -- 2026-08-24:打包器消费整书 Print HTML,因此 manifest 同样使用既有 `no_print` 排除。 - 这样出版顺序只有一份,也不需要第二个输出专用排除键。 -- 2026-08-24:实现通用 EPUB 链路,并在出版 CI 固定 Pandoc 3.10 与 EPUBCheck 5.3.0。 - DDIA 与 TPME 隔离包同时通过带类型目标/内部链接检查与官方 EPUB 3.3 校验。 -- 2026-08-24:远程出版资源继续默认拒绝。DDIA 的历史远程海报图通过显式 opt-in 验证, - 没有因此放松默认值,也没有加入 DDIA 专用改写。 -- 2026-08-24:增加回环 Print-to-PDF runner,并按归档摘要固定 Chrome for Testing - headless shell 151.0.7922.34。主题、DDIA 与 TPME PDF 均通过结构、文本、A4、页码检查 - 与渲染复核。 -- 2026-08-24:PDF 复核只修复 owning Print 契约:聚合数学能力传播、Bootstrap 列选择器 - 范围、代码换行、编号公式单列布局与 CSS 页边距。 diff --git a/content/docs/design/research/consumer-evidence.md b/content/docs/design/research/consumer-evidence.md index b7c6719..38bfd96 100644 --- a/content/docs/design/research/consumer-evidence.md +++ b/content/docs/design/research/consumer-evidence.md @@ -7,7 +7,7 @@ icon: fa-solid fa-chart-column search_keywords: [consumer survey, corpus, migration evidence, Book, DDIA, TPME] design_kind: research design_status: verified-snapshot -last_verified: 2026-08-16 +last_verified: 2026-08-24 --- > [!NOTE] Dated corpus snapshot @@ -77,6 +77,18 @@ current profile boundaries are documented in [Writing a book](/docs/write/book/#migrate) and the [migration contract](/docs/design/migration/). +## Publication adoption snapshot {#publication-adoption} + +An isolated 2026-08-24 pass exercised the released generic Book publication +path against two consumers: + +| Consumer | Generic publication evidence | Downstream status | +| -------- | ------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------- | +| DDIA | 23 ordered pages, 131 typed targets, and 292 resolved cross-references; EPUBCheck, internal, and PDF checks passed | At this snapshot it still retained a semantic preprocessor pending independent acceptance of the new gate | +| TPME | 18 ordered pages, 41 typed targets, and 1,062 resolved cross-references; the same generic checks passed | A second consumer confirmed portability; it created no upstream migration gate | + +This is downstream adoption evidence, not an open upstream design boundary. + ## Limits {#limits} These counts should not be copied into product marketing or used as a current diff --git a/content/docs/design/research/consumer-evidence.zh.md b/content/docs/design/research/consumer-evidence.zh.md index 245f8a5..5f06eb1 100644 --- a/content/docs/design/research/consumer-evidence.zh.md +++ b/content/docs/design/research/consumer-evidence.zh.md @@ -7,7 +7,7 @@ icon: fa-solid fa-chart-column search_keywords: [消费站盘点, 语料, 迁移证据, Book, DDIA, TPME] design_kind: research design_status: verified-snapshot -last_verified: 2026-08-16 +last_verified: 2026-08-24 --- > [!NOTE] 带日期的语料快照 @@ -64,6 +64,17 @@ Fields 表头词汇,约 874 张属于参考型表格,约 1,300 张属于兼 [创作书籍](/zh/docs/write/book/#migrate)和 [迁移契约](/zh/docs/design/migration/)。 +## 出版采纳快照 {#publication-adoption} + +2026-08-24 的隔离验证让两个消费站运行了已发布的通用 Book 出版链路: + +| 消费站 | 通用出版证据 | 下游状态 | +| ------ | ------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- | +| DDIA | 23 个有序页面、131 个带类型目标与 292 条已解析交叉引用;EPUBCheck、内部检查与 PDF 检查均通过 | 该快照中仍保留语义预处理器,等待消费站独立接受新的门禁 | +| TPME | 18 个有序页面、41 个带类型目标与 1,062 条已解析交叉引用;同一套通用检查通过 | 第二个消费站证明了可移植性,没有形成上游迁移门禁 | + +这是下游采纳证据,不是尚未解决的上游设计边界。 + ## 边界 {#limits} 这些数字不能直接用于产品宣传,也不能当作当前站点清单。重做研究时,需要重新确定仓库清单并生成 diff --git a/content/docs/design/shell.md b/content/docs/design/shell.md index bfb5779..476b4cf 100644 --- a/content/docs/design/shell.md +++ b/content/docs/design/shell.md @@ -54,7 +54,11 @@ Sidebar and pager share root and order. `manual_link`, `build.render: link`, dividers, hidden nodes, and placeholders retain their documented semantics. `sidebar_icon_policy` is `all` (default), `groups`, or `none`; icons are one Font Awesome class pair. Invalid policies follow the shared warning/fallback -contract. +contract. At `sidebar_cache_limit`, the two walkers may reuse neutral markup +only for the same language, navigation root, and output-affecting effective +settings. That markup remains visible without JavaScript; the normal shell +runtime adds the active path. A Book page that emits `sidebar_headings` stays +page-specific and bypasses the shared tree cache. ## Immersive blog presentation {#immersive-blog-presentation} @@ -124,7 +128,10 @@ real focus without rewriting Tab order. The outline derives cursor and visible-heading range from one heading model and the scroller's computed `scroll-padding-top`; its SVG line and dot share the same animated values so they cannot drift. No speculative DOM repair pass is -allowed. +allowed. This tracking is always owned by the normal shell runtime. +`params.ui.scroll_spy` and the page key `scroll_spy` are quiet compatibility +no-ops throughout 1.x, emit no separate runtime, and may be removed only in a +future breaking release. ## Share {#share} @@ -189,8 +196,9 @@ glyph with a term glyph (`folder-open`/`folder`, `tags`/`tag`, `cubes`/`cube`, `params.ui.taxonomy_icons` overrides a pair with one string for both surfaces or a `taxonomy`/`term` map; unusable input warns and keeps the built-in. The right-rail cloud wears the whole-taxonomy glyph on its head alone: cloud chips -and the term-archive filter chips stay text plus count, because repeating the -glyph beside an announced taxonomy is noise. The byline carries the people +stay text plus count, because repeating the glyph beside an announced taxonomy +is noise. A standalone taxonomy directory card carries one term glyph; the +byline carries the people alone—portrait, name, and the profile's one-line bio—with no label and no date. List rows, cards, and term archives share one metadata line of the same shape: date, one localized author-and-section phrase, then word count and minutes @@ -241,7 +249,36 @@ are one flat run, newest first, sharing `blog_index_size` pagination—the metadata line's dates make year headings redundant; `table` shows the whole section as date/title/tag rows without pagination. Cards use the shared lead image, localized date/author/section metadata, tags, and a three-line summary. -Term and taxonomy pages stay row lists. + +A taxonomy page (`/tags/`, `/authors/`) and its term pages share one head, +`shell/taxonomy-head.html`. The taxonomy page opens with the whole-taxonomy +glyph in a tinted tile, the localized name, and a count of terms. A term page +opens with the term's title and its page count from `ui_taxonomy_pages`, using +the current locale's CLDR plural form; where no breadcrumb is rendered, a kicker above the +title names the taxonomy and links back to it, since an enabled trail already +does both one line higher: a crumb standing for a generated taxonomy page +borrows the same localized label the head renders, not Hugo's plural title. +Under the head the taxonomy page lays its terms out as a grid of one-line +cards, `shell/taxonomy-cards.html`, most-used first with alphabetical ties—the +order the rail cloud already uses—filling equal columns by `auto-fill` so a +short taxonomy never stretches two cards across the page. A card is the term +glyph, the term, and its page count, and the whole card is the link; authors +alone lead with the byline's small portrait through the same avatar partial. +No card carries a description or a newest page: a term has nothing to say +that its title and count do not, and the extra line only blurred the grid. +There is no filter chip row and no "All" chip: the section root already lives +in the sidebar and the navbar. Term pages stay row lists, and author profiles +keep their own head. + +The rail on a taxonomy or term page leads with `shell/taxonomy-switcher.html`: +one row per declared taxonomy—whole-taxonomy glyph, localized name, term +count—linking to its index page, the current taxonomy on the selected ground. +It is the way from one taxonomy's pages to another's, because cloud chips jump +to terms and cloud heads only collapse; a site with one taxonomy renders no +switcher. The group sits behind the same `toc_taxonomies` switch as the clouds. +A taxonomy page scopes its clouds to the whole site (`taxonomy-root.html` +returns no root for that kind) and omits its own cloud, whose terms are the +cards beside it; term pages keep the section scope and the full set. `params.ui.blog_index_toggle` renders all three forms for the current paginator slice and lets readers cycle them. The configured form controls first paint and diff --git a/content/docs/design/shell.zh.md b/content/docs/design/shell.zh.md index 5a4456a..1000bda 100644 --- a/content/docs/design/shell.zh.md +++ b/content/docs/design/shell.zh.md @@ -44,7 +44,10 @@ lg 以上是文字链接,之下收缩为图标链接。lg 与 md 之间,右 侧栏与翻页共享同一个根和顺序。`manual_link`、`build.render: link`、分隔行、 隐藏节点与占位节点保留各自已定义的语义。`sidebar_icon_policy` 可取默认的 `all`、 `groups` 或 `none`;图标是一对 Font Awesome class。无效策略遵循共享的警告与 -回退契约。 +回退契约。达到 `sidebar_cache_limit` 后,两种 walker 只有在语言、导航根与实际 +影响输出的有效设置均相同时才复用中性标记。没有 JavaScript 时这份标记仍然可见; +普通外壳运行时只补上 active 路径。会输出 `sidebar_headings` 的 Book 页面保持页面 +专属,并绕过共享树缓存。 ## 沉浸式博客展示 {#immersive-blog-presentation} @@ -105,7 +108,9 @@ URL,或调用内置 ID,绝不能注入 JavaScript。 页面大纲从同一套标题模型与滚动容器计算后的 `scroll-padding-top` 推导光标和可见 标题范围;SVG 线条与圆点共享同一组动画值,不会漂移。禁止增加臆测性的 DOM -修复遍历。 +修复遍历。这项跟踪始终由普通外壳运行时负责。`params.ui.scroll_spy` 与页面键 +`scroll_spy` 在整个 1.x 期间都是静默兼容 no-op,不加载独立运行时;只有未来的 +破坏性版本才会删除它们。 ## 分享 {#share} @@ -160,8 +165,9 @@ term 图标;可点击徽章在 hover 或 focus 时才取得当前分区的强 `tags`/`tag`、`cubes`/`cube`、`users`/`user-pen`、series 用 `book-bookmark`/`book`,其余用 `shapes`);`params.ui.taxonomy_icons` 可覆盖: 字符串同时作用于两个表面,`taxonomy`/`term` map 分别设置;无效输入警告并保留 -内置。右栏词云只在云头戴整体图标:云 chip 与术语归档筛选条保持"文本 + 计数"—— -分类法已经亮明身份,再在每个 chip 上重复图标只是噪声。作者署名只放人物——头像、姓名与个人资料的一行简介—— +内置。右栏词云只在云头戴整体图标:云 chip 保持"文本 + 计数"——分类法已经亮明 +身份,再在每个 chip 上重复图标只是噪声。独立的分类法目录卡片会带一枚术语图标; +作者署名只放人物——头像、姓名与个人资料的一行简介—— 不带标签或日期。列表行、卡片与术语归档共享同一形态的元数据行:日期、一条本地化 的作者与分区短语,以及由同一个 `reading_time` 开关控制的字数和分钟数。句子下方 是独立成行、自动换行的徽章行,按分类法字母序列出页面在全部分类法下的词条,每枚 @@ -197,8 +203,28 @@ term 图标;可点击徽章在 hover 或 focus 时才取得当前分区的强 博客分区索引使用 `params.ui.blog_index`:默认的 `list` 与 `cards` 都是按最新优先 排列的一段扁平结果,共享 `blog_index_size` 分页;元数据行已经显示日期,所以不再 需要年份标题。`table` 把整个分区显示为日期、标题、标签行,不分页。卡片使用共享 -首图、本地化日期/作者/分区元数据、标签与三行摘要。Term 与 taxonomy 页面保持 -行列表。 +首图、本地化日期/作者/分区元数据、标签与三行摘要。 + +分类法页(`/tags/`、`/authors/`)与其术语页共用一个页头 +`shell/taxonomy-head.html`。分类法页以整体分类法图标的着色方块、本地化名称与 +术语数开头;术语页以术语标题与取自 `ui_taxonomy_pages`、按当前 locale 的 CLDR +复数类别选择的页面数开头,没有渲染面包屑时标题上方再加一行 +kicker,写明分类法并链回列表页——面包屑开启时它在上一行已经做了这两件事:代表 +生成的分类法页的那一级面包屑借用页头同一个本地化标签,而不是 Hugo 的复数名标题。 +页头之下,分类法页把术语排成单行卡片网格 `shell/taxonomy-cards.html`:使用次数 +多者在前、同数按字母序(与右栏词云同序),以 `auto-fill` 填满等宽列,因此术语 +很少时两张卡片也不会被拉宽到整页。一张卡片就是术语图标、术语名与页面数,整张 +卡片即链接;只有作者以署名同款小头像开头,走同一个头像 partial。卡片不带描述、 +不带最新一页:术语没有标题与计数之外值得一说的内容,多出的那一行只会让网格发糊。 +不再有筛选芯片行与「全部」芯片:分区根已经在侧栏与顶栏里。术语页保持行列表, +作者资料页保留自己的页头。 + +分类法页与术语页的右栏以 `shell/taxonomy-switcher.html` 开头:声明的每种分类法 +一行——整体图标、本地化名称、术语数——链向其列表页,当前分类法置于选中底色上。 +这是从一种分类法的页面去另一种的路:词云芯片跳到术语页,词云头只负责折叠;只有 +一种分类法的站点不渲染切换器。这一组与词云共用 `toc_taxonomies` 开关。分类法页 +的词云按全站统计(`taxonomy-root.html` 对该 kind 不返回根),并省略自己那一组, +它的术语就是旁边的卡片;术语页保持分区作用域与完整的一组。 `params.ui.blog_index_toggle` 为当前分页切片渲染三种形态,并允许读者循环切换。 配置值控制首次绘制,隐藏形态不加载图片。读者存储的选择只作用于发布了全部三种 diff --git a/content/docs/write/frontmatter.md b/content/docs/write/frontmatter.md index 7295166..04b2c00 100644 --- a/content/docs/write/frontmatter.md +++ b/content/docs/write/frontmatter.md @@ -1,13 +1,13 @@ --- title: Page parameters linkTitle: Page parameters -description: The full front matter table — every page key the theme actually reads, grouped by sidebar, shell, search, output, page end, Book, landing and release pages. +description: The full front matter table — active page keys and retained 1.x compatibility no-ops, grouped by sidebar, shell, search, output, page end, Book, landing and release pages. weight: 30 search_keywords: [page parameters, front matter, cascade, page override, parameter table] --- -This page is the complete table of page-level parameters, listing only the keys -the OINK theme reads. Keys the theme reads solely to warn that they were +This page is the complete table of page-level parameters, listing the keys the +OINK theme reads plus explicit 1.x compatibility no-ops. Keys the theme reads solely to warn that they were renamed or removed are not listed here — they are in [Migration](/docs/design/migration/), and they are also kept out of the generated editor schema. Hugo's own front matter fields (`slug`, `url`, `build`, @@ -130,7 +130,7 @@ Site-level defaults and what they do are in | `body_class` | string | — | A class appended to `` for the site's own CSS | | `reading_time` | boolean | site value | Whether this page shows a reading time; `false` hides it | | `sidebar_enabled` | boolean | `true` | Whether this page shows the left sidebar; `false` hides it | -| `scroll_spy` | boolean | site value | Scroll tracking in the outline; `true` enables it | +| `scroll_spy` | boolean | site value | Quiet 1.x compatibility no-op; active-heading tracking is always provided by the normal shell runtime | | `keyboard_nav` | boolean | site value (`true`) | Single-key keyboard navigation — see [Keyboard navigation](/docs/customize/keyboard/). A non-boolean warns and falls back | | `lastmod_commit` | `subject` / `hash` / `none` | `subject` | How the commit is shown after "last modified". An invalid value warns and falls back | | `sidebar_expand_levels`, `sidebar_menu_compact`, `sidebar_menu_foldable`, `sidebar_item_overflow` | as the site parameter | site value | Sidebar behaviour can be overridden per page too; the values are in [Configuration](/docs/customize/config/) | diff --git a/content/docs/write/frontmatter.zh.md b/content/docs/write/frontmatter.zh.md index 5abcb12..9e1d6ac 100644 --- a/content/docs/write/frontmatter.zh.md +++ b/content/docs/write/frontmatter.zh.md @@ -1,12 +1,17 @@ --- title: 页面参数 linkTitle: 页面参数 -description: front matter 全表:主题真正读取的每一个页面键,按侧栏、外壳、搜索、输出、页尾、Book、Landing、发布页分组。 +description: front matter 全表:主题读取的页面键与 1.x 兼容 no-op,按侧栏、外壳、搜索、输出、页尾、Book、Landing、发布页分组。 weight: 30 search_keywords: [页面参数, front matter, 前置元数据, page parameters, cascade, 页面级覆盖, 参数表] --- -本页是页面级参数的全表,只列 OINK 主题会读取的键。主题仅为提示「已重命名或已移除」而读取的旧键不在此列——它们在[迁移](/zh/docs/design/migration/)里,也不会出现在生成的编辑器 Schema 中。Hugo 自身的 front matter 字段(`slug`、`url`、`build`、`sitemap`、`expiryDate` 等)照常可用,语义见 [Hugo 文档](https://gohugo.io/content-management/front-matter/)。站点级参数(`hugo.yml` 里的 `params.*`)见[配置总览](/zh/docs/customize/config/)。 +本页是页面级参数的全表,包含 OINK 主题会读取的键,以及 1.x 明确保留的兼容 +no-op。主题仅为提示「已重命名或已移除」而读取的旧键不在此列——它们在 +[迁移](/zh/docs/design/migration/)里,也不会出现在生成的编辑器 Schema 中。Hugo +自身的 front matter 字段(`slug`、`url`、`build`、`sitemap`、`expiryDate` 等) +照常可用,语义见 [Hugo 文档](https://gohugo.io/content-management/front-matter/)。 +站点级参数(`hugo.yml` 里的 `params.*`)见[配置总览](/zh/docs/customize/config/)。 ## 表格说明 {#how-to-read} 优先级从高到低: @@ -105,7 +110,7 @@ cascade: | `body_class` | 字符串 | — | 追加到 `` 上的 class,供站点自己的 CSS 使用 | | `reading_time` | 布尔 | 站点值 | 本页是否显示阅读时长;写 `false` 关掉 | | `sidebar_enabled` | 布尔 | `true` | 这一页是否显示左侧栏;写 `false` 关掉 | -| `scroll_spy` | 布尔 | 站点值 | 目录的滚动跟随;写 `true` 打开 | +| `scroll_spy` | 布尔 | 站点值 | 1.x 静默兼容 no-op;普通外壳运行时始终提供当前标题跟踪 | | `keyboard_nav` | 布尔 | 站点值(`true`) | 单键键盘导航,见[键盘导航](/zh/docs/customize/keyboard/)。非布尔告警并回退 | | `lastmod_commit` | `subject` / `hash` / `none` | `subject` | 「最后修改」后面怎么显示提交。非法值告警并回退 | | `sidebar_expand_levels`、`sidebar_menu_compact`、`sidebar_menu_foldable`、`sidebar_item_overflow` | 同站点参数 | 站点值 | 侧栏行为也可以逐页覆盖;取值见[配置总览](/zh/docs/customize/config/) | diff --git a/content/docs/write/openapi.md b/content/docs/write/openapi.md index 83e1098..68aabc7 100644 --- a/content/docs/write/openapi.md +++ b/content/docs/write/openapi.md @@ -31,11 +31,10 @@ and both shortcodes then receive a URL the browser can fetch: - openapi.md # this page ``` -Do not put the specification beside the page. `redoc` looks for a file of that -name in the content directory and builds a URL from it, but a `.yaml` in the -content directory is a page resource, and Hugo publishes one only when it is -referenced or processed. `redoc` builds a URL without referencing the resource, -so the browser gets a 404. +Do not put the specification beside the page. Both shortcodes treat a local +value as a path under `static/`; neither resolves page resources. A `.yaml` +beside a content page is a page resource, and Hugo does not publish it merely +because its name appears in one of these shortcodes, so the browser gets a 404. A remote specification (starting `https://…`) is accepted by both shortcodes, but that is a network dependency, and it exposes the reader's metadata to that @@ -82,12 +81,12 @@ second parameter warns and the shortcode renders nothing. {{< redoc "openapi/docs-demo.yaml" >}} -Path resolution has three branches, in order: anything starting with `http` is -a remote URL; a file of that name found in the content directory yields -`baseURL + page directory + filename`; otherwise it is `baseURL + the path as -written`. So a `redoc` path must not begin with a slash — `/openapi/…` would -produce a doubled slash such as `https://example.com//openapi/…`. Unlike -`swagger`, it generates an absolute URL based on `baseURL`. +An `http` or `https` URL remains remote. Any other accepted value is a path +under `static/`; leading and non-leading slash forms are equivalent. For +example, `openapi/docs-demo.yaml` and `/openapi/docs-demo.yaml` both become +`https://example.com/preview/openapi/docs-demo.yaml` when the site `baseURL` is +`https://example.com/preview/`. Unlike `swagger`, Redoc receives this absolute +URL based on `baseURL`. The theme pins five attributes — `hide-hostname`, `hide-logo`, `suppress-warnings`, `lazy-rendering`, `native-scrollbars` — and hides the @@ -139,7 +138,7 @@ four outputs. - The two can coexist on one page, but the page becomes long and its HTML output loads both runtimes. Pick one for a production site. - Neither interface is fully accessible, and both come from upstream distributions the theme does not rewrite. Swagger UI's markup has axe WCAG AA violations (`select-name`, `scrollable-region-focusable`); Redoc's operation descriptions fail AA colour contrast. This site excludes `.td-swagger-ui` and `.td-redoc` from its zero-violation gate for that reason — a site with such a gate has to do the same, and should say so rather than assume either widget passes. - `redoc` accepts no attribute parameter: a second positional argument warns and the shortcode renders nothing. -- A `redoc` path must not start with `/`, or the URL gains a doubled slash. +- A local `redoc` path is rooted under `static/`; a leading `/` is optional, and page resources are not resolved. - The specification must be fetchable by the browser: put it in `static/` and confirm the file exists under `public/` after a build. - There is no mock server: Swagger UI's "Try it out" makes a real request to whatever `servers` names, and the address in the sample specification is not reachable. diff --git a/content/docs/write/openapi.zh.md b/content/docs/write/openapi.zh.md index cc1a1fe..8d1a8ac 100644 --- a/content/docs/write/openapi.zh.md +++ b/content/docs/write/openapi.zh.md @@ -23,7 +23,9 @@ search_keywords: [API, OpenAPI, Swagger, Swagger UI, Redoc, 接口文档, swagge - openapi.zh.md # 这一页 ``` -不要把规范文件放在页面旁边。`redoc` 会在内容目录里查找同名文件并据此拼出 URL,但内容目录里的 `.yaml` 是页面资源,Hugo 只在它被引用或处理时才发布。`redoc` 只拼 URL、不引用资源,浏览器因此得到 404。 +不要把规范文件放在页面旁边。两个 shortcode 都把本地值视为 `static/` 下的路径, +都不解析页面资源。内容页面旁边的 `.yaml` 属于页面资源,仅在 shortcode 中写出 +它的名字并不会让 Hugo 发布它,浏览器因此会得到 404。 远程规范(`https://…` 开头)两个 shortcode 都接受,但那是一项网络依赖,还会把读者的元数据暴露给那台主机。内网部署与有 CSP 的站点应当使用同源规范。只接受 `http` 与 `https`:其它 scheme、协议相对的 `//host` 或空值都会告警,shortcode 不渲染。 @@ -51,7 +53,11 @@ search_keywords: [API, OpenAPI, Swagger, Swagger UI, Redoc, 接口文档, swagge {{< redoc "openapi/docs-demo.yaml" >}} -路径解析按顺序有三条分支:`http` 开头视为远程 URL;能在内容目录里找到同名文件时用 `baseURL + 页面目录 + 文件名`;否则用 `baseURL + 原样路径`。`redoc` 的路径因此不要以斜杠开头,`/openapi/…` 会拼出 `https://example.com//openapi/…` 这样的双斜杠。与 `swagger` 不同,它生成基于 `baseURL` 的绝对 URL。 +`http` 或 `https` URL 保持为远程地址。其它通过校验的值都是 `static/` 下的路径, +开头有无斜杠等价。例如站点 `baseURL` 为 `https://example.com/preview/` 时, +`openapi/docs-demo.yaml` 与 `/openapi/docs-demo.yaml` 都会变成 +`https://example.com/preview/openapi/docs-demo.yaml`。与 `swagger` 不同,Redoc +接收的是这个基于 `baseURL` 的绝对 URL。 主题固定了 `hide-hostname` `hide-logo` `suppress-warnings` `lazy-rendering` `native-scrollbars` 五个属性,并用 CSS 隐藏 Redocly 品牌图标。Redoc 的其余属性目前不开放给作者,需要它们时在站点里覆盖 `layouts/_shortcodes/redoc.html`。 @@ -90,7 +96,7 @@ cascade: - 两者可以同页共存,但页面会很长,HTML 输出也会同时加载两套运行时。正式站点选一个。 - 两个界面都不是完全无障碍的,且都来自主题不改写的上游产物。Swagger UI 的标记有 axe WCAG AA 违规(`select-name`、`scrollable-region-focusable`);Redoc 的接口描述文字不满足 AA 对比度。本站因此把 `.td-swagger-ui` 与 `.td-redoc` 排除在零违规门禁之外——有同类门禁的站点只能照做,并且应当明说,而不是默认其中某一个能过。 - `redoc` 不接受额外属性参数:写第二个位置参数会告警,shortcode 不渲染。 -- `redoc` 路径不要以 `/` 开头,否则拼出双斜杠。 +- 本地 `redoc` 路径以 `static/` 为根,开头的 `/` 可有可无;它不解析页面资源。 - 规范文件必须能被浏览器取到:放 `static/`,构建后确认 `public/` 下存在该文件。 - 没有服务端 mock:Swagger UI 的 "Try it out" 会向 `servers` 里写的地址发起真实请求,示例规范里的地址不可访问。 diff --git a/content/search.md b/content/search.md deleted file mode 100644 index 701743a..0000000 --- a/content/search.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Search Results -layout: search -outputs: [HTML] ---- diff --git a/data/landing/home-v2/en.yaml b/data/landing/home-v2/en.yaml deleted file mode 100644 index 5b93d5e..0000000 --- a/data/landing/home-v2/en.yaml +++ /dev/null @@ -1,213 +0,0 @@ -sections: - - type: hero - data: - eyebrow: OINK 0.5.0 · Hugo only · zero network calls at build time - title_lines: - - Everything a technical docs site needs, - - in one `hugo` command. - lead: No Node, no npm, no CDN. Full-text search, 32 locales, versioning, print, and agent-readable output are built in. - note: This site is built with OINK — the shell, the search, and the command palette all come from the theme. - note_icon: fa-solid fa-circle-check - image: - light: images/hero-light.webp - dark: images/hero-dark.webp - alt: OINK engineering documentation illustration - actions: - - label: Clone this site - url: docs/start/ - icon: fa-solid fa-arrow-right - style: primary - - label: Browse 21 components - url: docs/components/ - icon: fa-solid fa-cubes - style: ghost - - - type: metrics - key: v2-metrics - data: - eyebrow: Facts - title: Don't take the copy's word for it - animate: true - items: - - { value: 0, label: npm dependencies } - - { value: 0, label: build-time network calls } - - { value: 32, label: bundled locales } - - { value: 11, label: sites in production } - - - type: command-box - key: v2-install - data: - eyebrow: Install - title: One module line, and that is the whole setup - code: hugo mod get github.com/pgsty/oink - lang: bash - note: Requires Hugo Extended ≥ 0.160.1. No package.json, no postcss.config.js, no node_modules. - - - type: capabilities - key: v2-capabilities - data: - eyebrow: Why this one - title: Four things you will not get elsewhere - items: - - ref: 01 / Readable source - title: The components are Markdown itself - url: docs/components/ - motto: The source still reads on GitHub and in any editor - bullets: - - 'Callouts are `> [!NOTE]` blockquotes, steps are a list plus `{.steps}`, tabs are adjacent fences plus `{tab=}`' - - 'The 29 remaining shortcodes cover only what a native block cannot express' - - 'Swap the theme out and the content is still standard Markdown, not an MDX dialect' - value: Your content is not welded to the theme, so migration stays affordable. - visual: - type: code - aria_label: Native Markdown component example - title: guide.md - lines: - - { class: c, prefix: '', text: '> [!WARNING] Back up first' } - - { class: c, prefix: '', text: '> The upgrade rewrites config keys.' } - - { class: p, prefix: '', text: '' } - - { class: p, prefix: '', text: '1. Stop the service' } - - { class: p, prefix: '', text: '2. Run the migration' } - - { class: ok, prefix: '', text: '{.steps}' } - - ref: 02 / Builds offline - title: No network at build time — it fails rather than phone home - url: docs/about/features/ - motto: Air-gapped, internal, and audited environments build the full site - bullets: - - 'All 26 vendored runtimes — CSS, JavaScript, fonts, chart engines — ship with the theme' - - 'Features that need an external endpoint **fail the build** when none is configured; the theme never reaches for a public service on your behalf' - - 'Development, CI, and production follow one deterministic build path' - value: A compliance environment needs no network allowlist for its docs site. - visual: - type: code - aria_label: Hugo build example - title: build.sh - lines: - - { class: c, prefix: '# ', text: one command, one deterministic output } - - { class: p, prefix: '$ ', text: hugo --gc --minify } - - { class: ok, prefix: '✓ ', text: local assets bundled, no external calls } - - { class: ok, prefix: '✓ ', text: multilingual routes generated } - - { class: ok, prefix: '✓ ', text: public/ ready to deploy } - - ref: 03 / One source, four outputs - title: The same page, for readers and for models - url: docs/customize/agents/ - motto: HTML, print, Markdown, RSS — plus llms.txt - bullets: - - 'Every component has a defined shape in all four outputs, not a best-effort print fallback' - - '`llms.txt` and the `.md` output let agents consume the docs without scraping HTML' - - 'Print strips interaction, expands disclosures, and prints a whole book in one pass' - value: One body of content serves readers, search engines, and AI tooling alike. - visual: - type: components - aria_label: Four output formats - columns: 2 - items: - - { icon: fa-solid fa-globe, title: HTML, detail: interactive shell } - - { icon: fa-solid fa-print, title: Print, detail: whole-book export } - - { icon: fa-brands fa-markdown, title: Markdown, detail: source-level output } - - { icon: fa-solid fa-robot, title: llms.txt, detail: agent index } - - ref: 04 / Not a toy - title: Large bilingual sites already run on it - url: docs/about/showcase/ - motto: Search, versioning, taxonomy, and book numbering all included - bullets: - - 'Local Lunr search with a CJK substring fallback; ⌘K opens the command palette' - - 'Version menu and archive banner; each version is its own independent build' - - 'Book mode: numbered figures, tables, equations, examples, cross-references, and indexes' - value: The feature base comes from a decade of Docsy; the shell and the components are OINK's own. - visual: - type: shell - brand: OINK - search: Search or run a command - shortcut: ⌘ K - section: Docs - section_icon: fa-solid fa-book - items: - - { label: Get started, active: true } - - { label: Authoring } - - { label: Components } - - { label: Customization } - eyebrow: Engineering docs / long haul - title_lines: [Large sites, stay navigable.] - rules: ['88%', '72%', '55%'] - - - type: pricing-compare - key: v2-compare - data: - eyebrow: Evaluating - title: Three delivery models, three different bills - desc: Compared by delivery model rather than by product name; every row is a structural difference, not a feature count. - tiers: [Hugo single binary (OINK), Node toolchain, Hosted service] - groups: - - name: Build - rows: - - { name: No Node / npm required, cells: [Y, N, '—'] } - - { name: One executable does the build, cells: [Y, N, '—'] } - - { name: Builds with no network, cells: [Y, N, N] } - - name: Ownership - rows: - - { name: The output is your own static files, cells: [Y, Y, N] } - - { name: The content is standard Markdown, cells: [Y, N, N] } - - name: Long haul - rows: - - { name: Dependency tree needs regular upgrades, cells: [N, Y, '—'] } - - { name: Exposed to a vendor shutting down, cells: [N, N, Y] } - - - type: logo-wall - key: v2-sites - data: - eyebrow: Already in production - title: 11 sites; the largest carries over 1,400 Markdown files - layout: grid - items: - - { title: pigsty.io, icon: fa-solid fa-database, url: 'https://pigsty.io/' } - - { title: pigsty.cc, icon: fa-solid fa-database, url: 'https://pigsty.cc/' } - - { title: pig.pgsty.com, icon: fa-solid fa-box, url: 'https://pig.pgsty.com/' } - - { title: silo.pgsty.com, icon: fa-solid fa-hard-drive, url: 'https://silo.pgsty.com/' } - - { title: exp.pgsty.com, icon: fa-solid fa-chart-line, url: 'https://exp.pgsty.com/' } - - { title: pgsql.cc, icon: fa-solid fa-server, url: 'https://pgsql.cc/' } - - { title: pgsty.com, icon: fa-solid fa-building, url: 'https://pgsty.com/' } - - { title: oink.pgsty.com, icon: fa-solid fa-book, url: 'https://oink.pgsty.com/' } - - - type: steps - key: v2-steps - data: - eyebrow: Get started - title: A bilingual docs site in three steps - items: - - title: Clone - desc: This documentation site is the starter — a running bilingual site, not an empty template. - cmd: { code: 'git clone https://github.com/pgsty/oink.pgsty.com my-docs' } - - title: Change three things - desc: Site title, `baseURL`, and the navbar menu; then replace the sample content with yours. - cmd: { code: 'hugo server' } - - title: Publish - desc: The GitHub Pages workflow is already written — push and it deploys. - cmd: { code: 'hugo --gc --minify' } - - - type: faq - key: v2-faq - data: - eyebrow: Before you commit - title: What you are probably about to ask - style: accordion - items: - - question: I already run Docsy — how expensive is the move? - answer: OINK is a hard fork of Docsy and keeps the `td-` naming, the Sass variables, and most configuration keys, so existing site overrides usually work as they are. Content-syntax changes have a dry-runnable, idempotent migration script, and every renamed configuration key fails the build naming its replacement, so upgrading means following the errors one at a time. - - question: Can I restyle it without writing front-end code? - answer: Brand colours, fonts, and typography are Sass variables and CSS custom properties; a site overrides them in its own `_variables_project.scss`. Switching the whole type system is one parameter. None of this path needs Node or a build step. - - question: Do I have to write React or MDX components? - answer: No. A component's first form is Markdown syntax plus one attribute line; shortcodes — 29 of them — cover only what a native block cannot express. No JSX ever appears in your content. - - question: Do all these components make pages heavy? - answer: No. Scripts are bundled per page from the components that page actually uses; unused runtimes are never shipped. Print, Markdown, and RSS output load no JavaScript at all, and maths is rendered at build time, costing one stylesheet and its fonts. - - question: How actively is it maintained? - answer: 0.5.0 is the component API v5 and configuration convergence release; the public API freezes at 1.0. Eleven production sites track the main line, and the theme repository carries source-level contract checks, four-state output goldens, and browser runtime tests. - - - type: cta - key: v2-cta - data: - title: Clone this site, change three settings, and let Hugo do the rest. - text: Bilingual, searchable, printable, agent-readable — complete from the first build. - label: Get started - url: docs/start/ - style: primary diff --git a/data/landing/home-v2/zh.yaml b/data/landing/home-v2/zh.yaml deleted file mode 100644 index 9baec9e..0000000 --- a/data/landing/home-v2/zh.yaml +++ /dev/null @@ -1,213 +0,0 @@ -sections: - - type: hero - data: - eyebrow: OINK 0.5.0 · 仅依赖 Hugo · 构建期零外部请求 - title_lines: - - 工程文档站需要的一切, - - 一个 `hugo` 命令。 - lead: 不装 Node,不跑 npm,不连 CDN。全文检索、32 种语言、多版本、打印与 Agent 输出全部内置。 - note: 你正在看的这个站点就是用 OINK 构建的 —— 排版、搜索与命令面板都来自主题本身。 - note_icon: fa-solid fa-circle-check - image: - light: images/hero-light.webp - dark: images/hero-dark.webp - alt: OINK 工程文档插图 - actions: - - label: 克隆这个站点开始 - url: docs/start/ - icon: fa-solid fa-arrow-right - style: primary - - label: 浏览 21 个组件 - url: docs/components/ - icon: fa-solid fa-cubes - style: ghost - - - type: metrics - key: v2-metrics - data: - eyebrow: 事实 - title: 不用信文案,看数字 - animate: true - items: - - { value: 0, label: npm 依赖 } - - { value: 0, label: 构建期外部请求 } - - { value: 32, label: 内置界面语言 } - - { value: 11, label: 生产中的站点 } - - - type: command-box - key: v2-install - data: - eyebrow: 安装 - title: 加一行模块依赖,没有第二步 - code: hugo mod get github.com/pgsty/oink - lang: bash - note: 需要 Hugo Extended ≥ 0.160.1。没有 package.json,没有 postcss.config.js,没有 node_modules。 - - - type: capabilities - key: v2-capabilities - data: - eyebrow: 为什么是它 - title: 四件别处拿不到的事 - items: - - ref: 01 / 源码可读 - title: 组件就是 Markdown 本身 - url: docs/components/ - motto: 在 GitHub 上、在任何编辑器里,源码都还是能读的 - bullets: - - '提示块是 `> [!NOTE]` 块引用,步骤是列表加 `{.steps}`,标签页是相邻围栏加 `{tab=}`' - - '29 个 shortcode 只留给原生块表达不了的场景' - - '换掉主题,内容仍是标准 Markdown,不是 MDX 方言' - value: 内容资产不绑死在主题上,迁移成本始终可控。 - visual: - type: code - aria_label: Markdown 原生组件示例 - title: guide.md - lines: - - { class: c, prefix: '', text: '> [!WARNING] 先备份' } - - { class: c, prefix: '', text: '> 升级会重写配置键。' } - - { class: p, prefix: '', text: '' } - - { class: p, prefix: '', text: '1. 停止服务' } - - { class: p, prefix: '', text: '2. 执行迁移' } - - { class: ok, prefix: '', text: '{.steps}' } - - ref: 02 / 离线可构建 - title: 构建期不联网,宁可失败也不偷偷请求 - url: docs/about/features/ - motto: 断网、内网与审计环境里都能构建出完整站点 - bullets: - - 'CSS、JavaScript、字体与图表运行时共 26 项全部随主题分发' - - '需要外部端点的功能没配端点就**让构建失败**,主题不会替你连公共服务' - - '开发、CI 与生产走同一条确定性构建路径' - value: 合规环境不必为一个文档站单独开网络白名单。 - visual: - type: code - aria_label: Hugo 构建示例 - title: build.sh - lines: - - { class: c, prefix: '# ', text: 一条命令,一份确定性输出 } - - { class: p, prefix: '$ ', text: hugo --gc --minify } - - { class: ok, prefix: '✓ ', text: 本地资源已打包,无外部请求 } - - { class: ok, prefix: '✓ ', text: 多语言路由已生成 } - - { class: ok, prefix: '✓ ', text: public/ 可以部署 } - - ref: 03 / 一份内容四种输出 - title: 同一页,给人读也给模型读 - url: docs/customize/agents/ - motto: HTML、打印、Markdown、RSS,外加 llms.txt - bullets: - - '每个组件在四种输出下都有确定形态,不是「打印时凑合一下」' - - '`llms.txt` 与 `.md` 输出让 Agent 直接消费文档,不必爬 HTML' - - '打印视图去掉交互、展开折叠,整本书可以一次印出' - value: 一份内容同时服务读者、搜索引擎与 AI 工具。 - visual: - type: components - aria_label: 四种输出形态 - columns: 2 - items: - - { icon: fa-solid fa-globe, title: HTML, detail: 交互外壳 } - - { icon: fa-solid fa-print, title: 打印, detail: 整本可印 } - - { icon: fa-brands fa-markdown, title: Markdown, detail: 源码级输出 } - - { icon: fa-solid fa-robot, title: llms.txt, detail: Agent 索引 } - - ref: 04 / 不是玩具 - title: 大型双语站点已经在用 - url: docs/about/showcase/ - motto: 全文检索、多版本、分类法、书籍编号,一样不缺 - bullets: - - '本地 Lunr 检索带中文子串兜底,命令面板 ⌘K 直达' - - '版本菜单与归档横幅,每个版本是一次独立构建' - - 'Book 模式:编号图表式例、交叉引用、索引与整本打印' - value: 功能底座继承自 Docsy 十年的沉淀,外壳与组件是 OINK 自己的。 - visual: - type: shell - brand: OINK - search: 搜索内容或执行命令 - shortcut: ⌘ K - section: 文档 - section_icon: fa-solid fa-book - items: - - { label: 快速上手, active: true } - - { label: 创作内容 } - - { label: 组件参考 } - - { label: 定制站点 } - eyebrow: 工程文档 / 长期维护 - title_lines: [大站也能, 快速穿行。] - rules: ['88%', '72%', '55%'] - - - type: pricing-compare - key: v2-compare - data: - eyebrow: 选型 - title: 三条技术路线,代价各不相同 - desc: 按交付方式而不是按产品名比较;每一行都是结构性差异,不是功能多寡。 - tiers: [Hugo 单二进制(OINK), Node 工具链, 托管服务] - groups: - - name: 构建 - rows: - - { name: 无需 Node / npm, cells: [Y, N, '—'] } - - { name: 单个可执行文件完成构建, cells: [Y, N, '—'] } - - { name: 断网环境可构建, cells: [Y, N, N] } - - name: 归属 - rows: - - { name: 产物是你自己的静态文件, cells: [Y, Y, N] } - - { name: 内容是标准 Markdown, cells: [Y, N, N] } - - name: 长期维护 - rows: - - { name: 依赖树需要定期升级, cells: [N, Y, '—'] } - - { name: 供应商停服的风险, cells: [N, N, Y] } - - - type: logo-wall - key: v2-sites - data: - eyebrow: 已经在生产环境 - title: 11 个站点,最大的一个有一千四百多个 Markdown 文件 - layout: grid - items: - - { title: pigsty.io, icon: fa-solid fa-database, url: 'https://pigsty.io/' } - - { title: pigsty.cc, icon: fa-solid fa-database, url: 'https://pigsty.cc/' } - - { title: pig.pgsty.com, icon: fa-solid fa-box, url: 'https://pig.pgsty.com/' } - - { title: silo.pgsty.com, icon: fa-solid fa-hard-drive, url: 'https://silo.pgsty.com/' } - - { title: exp.pgsty.com, icon: fa-solid fa-chart-line, url: 'https://exp.pgsty.com/' } - - { title: pgsql.cc, icon: fa-solid fa-server, url: 'https://pgsql.cc/' } - - { title: pgsty.com, icon: fa-solid fa-building, url: 'https://pgsty.com/' } - - { title: oink.pgsty.com, icon: fa-solid fa-book, url: 'https://oink.pgsty.com/' } - - - type: steps - key: v2-steps - data: - eyebrow: 上手 - title: 三步得到一个双语文档站 - items: - - title: 克隆 - desc: 这个文档站本身就是可运行的双语 starter,不是空模板。 - cmd: { code: 'git clone https://github.com/pgsty/oink.pgsty.com my-docs' } - - title: 改三处 - desc: 站点名、`baseURL`、顶栏菜单;再把示例内容换成你自己的。 - cmd: { code: 'hugo server' } - - title: 发布 - desc: GitHub Pages 工作流已经写好,推上去即可。 - cmd: { code: 'hugo --gc --minify' } - - - type: faq - key: v2-faq - data: - eyebrow: 选型顾虑 - title: 决定之前你大概会问 - style: accordion - items: - - question: 我已经在用 Docsy,迁移成本多大? - answer: OINK 是 Docsy 的硬分叉,保留了 `td-` 命名、Sass 变量与绝大多数配置键,站点覆盖基本原样可用。内容语法的变更有一个可干跑、幂等的迁移脚本;改过名的配置键会让构建失败并直接告诉你新名字,升级就是照着报错逐条改。 - - question: 不写前端,还能定制外观吗? - answer: 品牌色、字体与排版是 SCSS 变量和 CSS 自定义属性,站点在自己的 `_variables_project.scss` 里覆盖即可。字体整体切换只要一个参数。这条路径不需要 Node,也不需要构建步骤。 - - question: 需要写 React 或 MDX 组件吗? - answer: 不需要。组件的第一形态是 Markdown 语法本身加一行属性;原生形态表达不了的场景才用 shortcode,一共 29 个。内容里不会出现 JSX。 - - question: 页面会不会因为组件多而变重? - answer: 不会。脚本按每一页实际用到的组件拼包,没用到的运行时不下发;打印、Markdown 与 RSS 输出不加载任何脚本。公式在构建期渲染完毕,页面上只多一份样式表与字体。 - - question: 维护状态如何? - answer: 0.5.0 是组件 API v5 与配置键收敛的版本,1.0 之前把公开 API 定死。11 个生产站点在跟随主线,主题仓库有源码级契约检查、四态输出金样本与浏览器运行时测试。 - - - type: cta - key: v2-cta - data: - title: 克隆这个站点,改三处配置,剩下的交给 Hugo。 - text: 双语、可检索、可打印、可被 Agent 读取 —— 从第一次构建就是完整的。 - label: 开始使用 - url: docs/start/ - style: primary diff --git a/data/landing/home-v3/en.yaml b/data/landing/home-v3/en.yaml deleted file mode 100644 index a31db5e..0000000 --- a/data/landing/home-v3/en.yaml +++ /dev/null @@ -1,155 +0,0 @@ -sections: - - type: hero - data: - align: center - eyebrow: OINK 0.5.0 · Documentation framework - title_lines: - - Markdown is enough. - title_size: 5.4rem - lead: One binary. One Markdown source. A complete documentation site. - actions: - - label: Get started - url: docs/start/ - icon: fa-solid fa-arrow-right - style: primary - - label: See the components - url: docs/components/ - style: ghost - note: The page you are reading was built with it. - - - type: code-plate - key: home-preview - data: - eyebrow: Components - title: What you write is what you get. - desc: Callouts, steps, tabs — plain Markdown plus one attribute line, still readable on GitHub. - aria_label: Markdown source example - lang: markdown - code: | - > [!TIP] Only Markdown - > Callouts, steps, and tabs are ordinary syntax. - - 1. Write Markdown - 2. Run `hugo` - 3. Deploy `public/` - {.steps} - - ```bash {tab="macOS"} - brew install hugo - ``` - - ```bash {tab="Linux"} - apt install hugo - ``` - - - type: metrics - key: home-metrics - data: - animate: true - items: - - { value: 0, label: npm dependencies } - - { value: 0, label: CDN requests } - - { value: 32, label: UI languages } - - { value: 11, label: sites in production } - - - type: capabilities - key: home-why - data: - eyebrow: Why - title: Fewer dependencies. More certainty. - items: - - ref: 01 - title: One binary - url: docs/about/features/ - motto: No Node, no npm, no CDN - value: The full site builds with no network. Development, CI, and production take one path. - visual: - type: code - aria_label: Build example - title: build.sh - lines: - - { class: c, prefix: '$ ', text: 'hugo --gc --minify' } - - { class: ok, prefix: '✓ ', text: assets bundled locally } - - { class: ok, prefix: '✓ ', text: multilingual routes generated } - - { class: ok, prefix: '✓ ', text: public/ ready to deploy } - - ref: 02 - title: One source, four outputs - url: docs/customize/agents/ - motto: HTML · Print · Markdown · llms.txt - value: The same page for readers, search engines, and AI. Every component has a defined shape in all four. - visual: - type: components - aria_label: Four output formats - columns: 2 - items: - - { icon: fa-solid fa-globe, title: HTML, detail: interactive shell } - - { icon: fa-solid fa-print, title: Print, detail: whole-book export } - - { icon: fa-brands fa-markdown, title: Markdown, detail: source-level output } - - { icon: fa-solid fa-robot, title: llms.txt, detail: agent index } - - ref: 03 - title: A complete shell - url: docs/customize/keyboard/ - motto: Search · ⌘K · languages · versions · keyboard - value: Large sites stay navigable. Reader choices are stored locally and sent nowhere. - visual: - type: shell - brand: OINK - search: Search or run a command - shortcut: ⌘ K - section: Docs - section_icon: fa-solid fa-book - items: - - { label: Get started, active: true } - - { label: Authoring } - - { label: Components } - - { label: Customization } - eyebrow: Engineering docs / long haul - title_lines: [Large sites, stay navigable.] - rules: ['88%', '72%', '55%'] - - - type: cards - key: home-complete - data: - eyebrow: Complete - title: Nothing missing. - columns: 3 - items: - - { title: Full-text search, icon: fa-solid fa-magnifying-glass, desc: "Local index, CJK aware, one keystroke away.", url: docs/customize/search/ } - - { title: Languages, icon: fa-solid fa-language, desc: "32 UI locales; content paired page by page.", url: docs/customize/i18n/ } - - { title: Versions, icon: fa-solid fa-code-branch, desc: "Version menu and archive banner.", url: docs/customize/versions/ } - - { title: Book mode, icon: fa-solid fa-book, desc: "Numbered figures and equations, cross-references, whole-book print.", url: docs/write/book/ } - - { title: Blog and RSS, icon: fa-solid fa-rss, desc: "Timeline, taxonomies, feeds.", url: docs/write/blog/ } - - { title: OpenAPI, icon: fa-solid fa-plug, desc: "Swagger UI and ReDoc, rendered locally.", url: docs/write/openapi/ } - - - type: logo-wall - key: home-sites - data: - eyebrow: Production - title: Already in use. - layout: grid - items: - - { title: Pigsty, icon: fa-solid fa-database, url: 'https://pigsty.io/' } - - { title: Pigsty (zh), icon: fa-solid fa-database, url: 'https://pigsty.cc/' } - - { title: PIG, icon: fa-solid fa-box, url: 'https://pig.pgsty.com/' } - - { title: SILO, icon: fa-solid fa-hard-drive, url: 'https://silo.pgsty.com/' } - - { title: PG Exporter, icon: fa-solid fa-chart-line, url: 'https://exp.pgsty.com/' } - - { title: pgsql.cc, icon: fa-solid fa-server, url: 'https://pgsql.cc/' } - - { title: PGSTY, icon: fa-solid fa-building, url: 'https://pgsty.com/' } - - { title: OINK, icon: fa-solid fa-book, url: 'https://oink.pgsty.com/' } - - - type: command-box - key: home-install - data: - eyebrow: Install - code: hugo mod get github.com/pgsty/oink - lang: bash - note: Hugo Extended ≥ 0.160.1. No package.json, no node_modules. - - - type: cta - key: home-cta - data: - title: Start here. - text: Clone this site, change three settings, and let hugo do the rest. - label: Get started - url: docs/start/ - style: primary diff --git a/data/landing/home-v3/zh.yaml b/data/landing/home-v3/zh.yaml deleted file mode 100644 index 6792da9..0000000 --- a/data/landing/home-v3/zh.yaml +++ /dev/null @@ -1,155 +0,0 @@ -sections: - - type: hero - data: - align: center - eyebrow: OINK 0.5.0 · 工程文档框架 - title_lines: - - Markdown 就够了。 - title_size: 5.4rem - lead: 一个二进制,一份 Markdown,一座完整的文档站。 - actions: - - label: 开始使用 - url: docs/start/ - icon: fa-solid fa-arrow-right - style: primary - - label: 看看组件 - url: docs/components/ - style: ghost - note: 你正在读的这一页,就是它构建的。 - - - type: code-plate - key: home-preview - data: - eyebrow: 组件 - title: 所写即所得。 - desc: 提示块、步骤、标签页:普通 Markdown 加一行属性,在 GitHub 上照样能读。 - aria_label: Markdown 源码示例 - lang: markdown - code: | - > [!TIP] 只用 Markdown - > 提示块、步骤、标签页,都是普通语法。 - - 1. 写 Markdown - 2. 运行 `hugo` - 3. 部署 `public/` - {.steps} - - ```bash {tab="macOS"} - brew install hugo - ``` - - ```bash {tab="Linux"} - apt install hugo - ``` - - - type: metrics - key: home-metrics - data: - animate: true - items: - - { value: 0, label: npm 依赖 } - - { value: 0, label: CDN 请求 } - - { value: 32, label: 种界面语言 } - - { value: 11, label: 个生产站点 } - - - type: capabilities - key: home-why - data: - eyebrow: 为什么 - title: 少一些依赖,多一份确定。 - items: - - ref: 01 - title: 一个二进制 - url: docs/about/features/ - motto: 不装 Node,不跑 npm,不连 CDN - value: 断网也能构建出完整站点。开发、CI 与生产走同一条路。 - visual: - type: code - aria_label: 构建示例 - title: build.sh - lines: - - { class: c, prefix: '$ ', text: 'hugo --gc --minify' } - - { class: ok, prefix: '✓ ', text: 资源已本地打包 } - - { class: ok, prefix: '✓ ', text: 多语言路由已生成 } - - { class: ok, prefix: '✓ ', text: public/ 可以部署 } - - ref: 02 - title: 一份内容,四种输出 - url: docs/customize/agents/ - motto: HTML · 打印 · Markdown · llms.txt - value: 同一页给人读,也给搜索引擎和 AI 读。每个组件在四种输出下都有确定形态。 - visual: - type: components - aria_label: 四种输出形态 - columns: 2 - items: - - { icon: fa-solid fa-globe, title: HTML, detail: 交互外壳 } - - { icon: fa-solid fa-print, title: 打印, detail: 整本可印 } - - { icon: fa-brands fa-markdown, title: Markdown, detail: 源码级输出 } - - { icon: fa-solid fa-robot, title: llms.txt, detail: Agent 索引 } - - ref: 03 - title: 完整的外壳 - url: docs/customize/keyboard/ - motto: 检索 · ⌘K · 多语言 · 多版本 · 键盘导航 - value: 大站也能快速穿行。读者的选择记在本地,不发往任何地方。 - visual: - type: shell - brand: OINK - search: 搜索内容或执行命令 - shortcut: ⌘ K - section: 文档 - section_icon: fa-solid fa-book - items: - - { label: 快速上手, active: true } - - { label: 创作内容 } - - { label: 组件 } - - { label: 定制站点 } - eyebrow: 工程文档 / 长期维护 - title_lines: [大站也能, 快速穿行。] - rules: ['88%', '72%', '55%'] - - - type: cards - key: home-complete - data: - eyebrow: 完整 - title: 该有的都有。 - columns: 3 - items: - - { title: 全文检索, icon: fa-solid fa-magnifying-glass, desc: 本地索引,中文可搜,⌘K 直达。, url: docs/customize/search/ } - - { title: 多语言, icon: fa-solid fa-language, desc: 32 种界面语言,内容逐页对等。, url: docs/customize/i18n/ } - - { title: 多版本, icon: fa-solid fa-code-branch, desc: 版本菜单与归档横幅。, url: docs/customize/versions/ } - - { title: 书籍模式, icon: fa-solid fa-book, desc: 编号图表公式,交叉引用,整本打印。, url: docs/write/book/ } - - { title: 博客与 RSS, icon: fa-solid fa-rss, desc: 时间线、分类法、订阅源。, url: docs/write/blog/ } - - { title: OpenAPI, icon: fa-solid fa-plug, desc: Swagger UI 与 ReDoc,本地渲染。, url: docs/write/openapi/ } - - - type: logo-wall - key: home-sites - data: - eyebrow: 生产环境 - title: 已经在用。 - layout: grid - items: - - { title: Pigsty, icon: fa-solid fa-database, url: 'https://pigsty.io/' } - - { title: Pigsty 中文站, icon: fa-solid fa-database, url: 'https://pigsty.cc/' } - - { title: PIG, icon: fa-solid fa-box, url: 'https://pig.pgsty.com/' } - - { title: SILO, icon: fa-solid fa-hard-drive, url: 'https://silo.pgsty.com/' } - - { title: PG Exporter, icon: fa-solid fa-chart-line, url: 'https://exp.pgsty.com/' } - - { title: pgsql.cc, icon: fa-solid fa-server, url: 'https://pgsql.cc/' } - - { title: PGSTY, icon: fa-solid fa-building, url: 'https://pgsty.com/' } - - { title: OINK, icon: fa-solid fa-book, url: 'https://oink.pgsty.com/' } - - - type: command-box - key: home-install - data: - eyebrow: 安装 - code: hugo mod get github.com/pgsty/oink - lang: bash - note: 基于 Hugo Extended ≥ 0.160.1。没有 package.json,没有 node_modules。 - - - type: cta - key: home-cta - data: - title: 从这里开始。 - text: 克隆这个站点,改三处配置,剩下的交给 hugo。 - label: 开始使用 - url: docs/start/ - style: primary diff --git a/data/landing/home-v4/en.yaml b/data/landing/home-v4/en.yaml deleted file mode 100644 index eebcbf4..0000000 --- a/data/landing/home-v4/en.yaml +++ /dev/null @@ -1,611 +0,0 @@ -# OINK homepage, iteration 4 (site data only — no theme changes). -# -# Every section below is a stock OINK 0.5.0 landing section (hero, metrics, -# capabilities, cards, logo-wall, steps, code-plate, pricing-compare, -# bar-chart, case-study, timeline, principles, faq, cta) driven by this -# file. Every number and claim is taken from the docs of this site -# (docs/about/, docs/about/features/, docs/about/showcase/) so the page -# stays honest and can be verified by the reader. -# -# Narrative: promise → proof → the four reasons → what is inside → -# how it compares → who runs it → how to start → principles → FAQ → CTA. - -sections: - - hero - - { type: metrics, key: facts } - - { type: capabilities, key: why } - - { type: cards, key: content_types } - - { type: logo-wall, key: components } - - { type: steps, key: start } - - { type: code-plate, key: config } - - { type: pricing-compare, key: compare } - - { type: bar-chart, key: scale } - - { type: logo-wall, key: production } - - { type: case-study, key: migration } - - { type: timeline, key: releases } - - { type: principles, key: principles } - - { type: faq, key: faq } - - { type: cta, key: cta } - -# ---------------------------------------------------------------- hero ------ -hero: - eyebrow: OINK 0.5.0 · Open · Indexed · Navigable · Knowledge - title_lines: - - Engineering documentation, - - built with nothing but Hugo. - title_size: 4.4rem - lead: >- - OINK turns a tree of Markdown into a complete documentation system — - docs, blog, books, release pages and API references — with local - full-text search, 32 UI languages, versioning, print and agent-readable - output built in. The only build dependency is Hugo Extended: no Node.js, - no npm, no CDN, and no network access at build time. - note: >- - This page is one YAML file rendered by the theme's stock landing - sections. No template was written for it. - note_icon: fa-solid fa-file-code - image: - light: images/hero-light.webp - dark: images/hero-dark.webp - alt: Markdown content, configuration and vendored assets flowing through one Hugo build into a static documentation site - actions: - - label: Get started - url: docs/start/ - icon: fa-solid fa-rocket - style: primary - - label: Browse the components - url: docs/components/ - icon: fa-solid fa-cubes - style: ghost - - label: GitHub - url: https://github.com/pgsty/oink - icon: fa-brands fa-github - style: ghost - external: true - detail: - label: What changed in 0.5.0 — component API v5 and the converged configuration - url: blog/release/0.5.0/ - icon: fa-solid fa-newspaper - -# ------------------------------------------------------------- metrics ------ -facts: - eyebrow: In numbers - title: Facts first, adjectives later - text: >- - Each figure links to the page that backs it. The whole site is the - theme's own regression suite, so what you read here is what it ships. - animate: true - columns: 6 - link_label: Details - items: - - value: 1 - label: build dependency - desc: Hugo Extended ≥ 0.160.1 - url: docs/start/ - - value: 0 - label: npm packages - desc: no PostCSS, no bundler, no CDN - url: docs/about/features/ - - value: 32 - label: UI languages - desc: en · zh-cn · zh-tw reviewed - url: docs/customize/i18n/ - - value: 4 - label: outputs per page - desc: HTML · print · Markdown · llms.txt - url: docs/customize/agents/ - - value: 11 - label: production sites - desc: two pages to fifteen hundred - url: docs/about/showcase/ - - value: 28 - label: vendored runtimes - desc: version, license, SHA-256 in VENDOR.json - url: docs/about/license/ - -# -------------------------------------------------------- capabilities ------ -why: - eyebrow: Why OINK - title: Four things a Hugo theme does not usually give you - text: >- - Most documentation themes are a skin. OINK is a documentation - system: authoring model, reading shell, output matrix and - delivery model are designed together, and each is verifiable in this site. - items: - - ref: 01 / MARKDOWN NATIVE - title: Components are Markdown, not a template language - url: docs/components/ - motto: Readable on GitHub. Complete on the site. - bullets: - - >- - [Callouts](docs/components/callout/) are `> [!NOTE]` blockquotes, - [steps](docs/components/steps/) and [cards](docs/components/cards/) - are lists with `{.steps}` / `{.cards}`, a [parameter table](docs/components/fields/) - is a table plus `{.fields}`, a [figure](docs/components/image/) is - an image plus `{caption="…"}`. - - >- - [Tabs](docs/components/tabs/) are adjacent fences with `{tab="…"}`; - [file trees](docs/components/filetree/), [galleries](docs/components/gallery/), - [Mermaid](docs/components/mermaid/) and [ECharts](docs/components/echarts/) - are data fences named by language. - - >- - 29 shortcodes cover what a block cannot express — and every one - validates its parameters at build time, so a typo is a build error, - not a silent gap in the page. - value: >- - Your content survives outside the theme: on GitHub or in any Markdown - reader it degrades to blockquotes, tables, lists and code blocks. - Nothing is lost, and nothing is locked in. - visual: - type: code - title: guide.md - aria_label: Markdown source with a callout, a step list and tabs - lines: - - { class: k, prefix: '> ', text: '[!TIP] One attribute line' } - - { class: c, prefix: '> ', text: 'Callouts, steps and tabs stay plain Markdown.' } - - { text: '' } - - { class: p, prefix: '1. ', text: 'Write Markdown' } - - { class: p, prefix: '2. ', text: 'Run hugo' } - - { class: p, prefix: '3. ', text: 'Deploy public/' } - - { class: k, text: '{.steps}' } - - { text: '' } - - { class: s, text: '```bash {tab="macOS"}' } - - { text: 'brew install hugo' } - - { class: s, text: '```' } - - { class: s, text: '```bash {tab="Linux"}' } - - { text: 'apt install hugo' } - - { class: s, text: '```' } - - ref: 02 / HUGO ONLY · LOCAL FIRST - title: One binary builds everything — offline - url: docs/about/features/ - motto: No Node. No npm. No CDN. It fails closed rather than phoning home. - bullets: - - >- - The consumer site's build dependency is [Hugo Extended 0.160.1 or - newer](docs/start/). SCSS compiles inside Hugo; there is no PostCSS, - webpack or build-time download. - - >- - Bootstrap, Font Awesome, four typefaces, Lunr, Mermaid, KaTeX, - Markmap, Swagger UI, Redoc, Asciinema, ECharts — [28 vendored - runtimes](docs/about/license/), each recorded in `VENDOR.json` with - version, source, license and SHA-256, and each loaded only on pages - that use it. - - >- - Anything that would need the network — a PlantUML server, a - draw.io server, Algolia keys — fails the build unless the site - configured it explicitly. Nothing connects out on its own. - value: >- - One deterministic path from a laptop to CI to production. An - air-gapped build machine is a supported case, not an edge case. - visual: - type: code - title: ci.log - aria_label: Build log of a consumer site - lines: - - { class: p, prefix: '$ ', text: 'hugo --gc --minify' } - - { class: ok, prefix: '✓ ', text: '0 npm packages, 0 build-time requests' } - - { class: ok, prefix: '✓ ', text: '28 vendored runtimes verified (VENDOR.json)' } - - { class: ok, prefix: '✓ ', text: '2 languages · search index per language' } - - { class: ok, prefix: '✓ ', text: 'HTML · print · Markdown · llms.txt' } - - { class: c, prefix: '# ', text: 'public/ is ready — host it anywhere' } - - ref: 03 / A SHELL FOR LARGE SITES - title: Navigation that holds up at fifteen hundred pages - url: docs/customize/navigation/ - motto: Sidebar tree, outline, ⌘K palette, keyboard, search — one shell for docs, blog and books - bullets: - - >- - [Local full-text search](docs/customize/search/): Lunr for Latin - scripts, a substring fallback for CJK; the query never leaves the - browser. Pages can add `search_boost` and `search_keywords`. - - >- - A [command palette](docs/customize/panel/) — `⌘K`, `/` for search, - `\` for commands — and [single-key navigation](docs/customize/keyboard/): - `w s a d` on the tree, `q e` for previous/next, `j k` along the - outline, `t` theme, `l` language. - - >- - [32 UI languages](docs/customize/i18n/) with translated-page fallback - and RTL, a [version menu](docs/customize/versions/) with archive - banners, dark mode, breadcrumbs, pager, print view — all part of the - shell, none of it a plugin. - value: >- - Reader preferences — theme, language, sidebar width, palette history — - are stored in the browser and sent nowhere. - visual: - type: shell - brand: OINK - search: Search or run a command - shortcut: ⌘ K - section: Docs - section_icon: fa-solid fa-book - items: - - { label: Introduction } - - { label: Get started, active: true } - - { label: Authoring } - - { label: Components } - - { label: Customization } - - { label: Administration } - eyebrow: ENGINEERING DOCS / LONG HAUL - title_lines: [Large sites, stay navigable.] - rules: ['88%', '72%', '55%'] - - ref: 04 / ONE SOURCE · FOUR OUTPUTS - title: The same page for readers, paper, search engines and AI - url: docs/customize/agents/ - motto: HTML · Print · Markdown · llms.txt — every component has a defined shape in each - bullets: - - >- - Interactive HTML for readers, and a [print view](docs/customize/print/) - per section or whole book with controls removed and folds expanded. - - >- - An `index.md` beside every page, announced with `rel="alternate"` in - ``, plus a site-level [`llms.txt`](docs/customize/agents/) - index — the same content, in the form assistants read best. - - >- - The site chooses in `outputs`; the theme never decides for it. The - "open in ChatGPT / Claude" actions stay off until a site opts in, - because they hand the current URL to a third party. - value: >- - Write once. Ship to a browser, a PDF, a Markdown reader and an agent — - from one build, with no second toolchain. - visual: - type: components - aria_label: The four output formats - columns: 2 - items: - - { icon: fa-solid fa-globe, title: HTML, detail: interactive shell } - - { icon: fa-solid fa-print, title: Print, detail: section or whole book, url: docs/customize/print/ } - - { icon: fa-brands fa-markdown, title: Markdown, detail: index.md beside every page, url: docs/customize/agents/ } - - { icon: fa-solid fa-robot, title: llms.txt, detail: site index for assistants, url: docs/customize/agents/ } - -# --------------------------------------------------------------- cards ------ -content_types: - eyebrow: Beyond docs - title: Six kinds of content, one theme - text: >- - Documentation sites rarely stay documentation-only. The shapes that - normally need a second tool are built in and share the shell, the search - index, the languages and the four outputs. - columns: 3 - link_label: Read the guide - items: - - title: Documentation - meta: The shell - icon: fa-solid fa-book - desc: >- - Sidebar tree from the content directory or an explicit - `data/docs_nav.json`, per-page outline, breadcrumbs, pager, edit and - history links, feedback and comments — the docs shell that everything - else reuses. - url: docs/write/pages/ - - title: Blog and RSS - meta: Updates - icon: fa-solid fa-rss - desc: >- - Dated posts in a timeline, taxonomies with count-sorted tag panels, - per-language feeds, and release notes that read like release notes. - url: docs/write/blog/ - - title: Books - meta: Long-form - icon: fa-solid fa-book-open - desc: >- - Chapter numbers, figures / tables / equations / examples numbered with - `{#id num=}`, `xref` cross-references, generated tables of contents - and figure lists, and a whole-book print output. - url: docs/write/book/ - - title: Releases and downloads - meta: Publishing - icon: fa-solid fa-download - desc: >- - `data/download/*.yaml` becomes release cards, asset tables and - checksums. Publication state is data — pending, published, archived — - not a copy-paste ritual. - url: docs/write/releases/ - - title: Landing pages - meta: Presentation - icon: fa-solid fa-layer-group - desc: >- - `data/home/.yaml` composes the homepage from twenty-one - server-rendered sections; any page with `layout: landing` does the - same. This page is one of them. - url: docs/customize/home/ - - title: API references - meta: OpenAPI - icon: fa-solid fa-plug - desc: >- - Swagger UI and Redoc as local runtimes. Keep the specification in the - repository, render it beside the prose, and it works offline like the - rest of the site. - url: docs/write/openapi/ - -# ----------------------------------------------------------- logo-wall ------ -components: - eyebrow: Components - title: Engineering-native content, loaded only where it is used - text: >- - Every component has a page of its own — source first, result second — - and a defined shape in HTML, print, Markdown and RSS. Diagram and chart - runtimes are vendored and attached per page, so a page without a chart - never downloads the chart library. - layout: grid - columns: 4 - items: - - { title: Callouts, icon: fa-solid fa-circle-info, desc: '`> [!NOTE]` · 10 types', url: docs/components/callout/ } - - { title: Tabs, icon: fa-solid fa-folder, desc: 'fences with `{tab=}`', url: docs/components/tabs/ } - - { title: Steps, icon: fa-solid fa-list-ol, desc: 'a list plus `{.steps}`', url: docs/components/steps/ } - - { title: Cards, icon: fa-solid fa-table-cells-large, desc: 'a list plus `{.cards}`', url: docs/components/cards/ } - - { title: Fields, icon: fa-solid fa-table-list, desc: 'a table plus `{.fields}`', url: docs/components/fields/ } - - { title: Code blocks, icon: fa-solid fa-code, desc: 'Chroma · lines · copy · fold', url: docs/components/code/ } - - { title: File trees, icon: fa-solid fa-folder-tree, desc: '`filetree` data fence', url: docs/components/filetree/ } - - { title: Galleries, icon: fa-solid fa-images, desc: '`gallery` data fence', url: docs/components/gallery/ } - - { title: Tables, icon: fa-solid fa-table, desc: 'captions · numbers · matrix', url: docs/components/table/ } - - { title: Mermaid, icon: fa-solid fa-diagram-project, desc: 'local diagram runtime', url: docs/components/mermaid/ } - - { title: Math, icon: fa-solid fa-square-root-variable, desc: 'KaTeX at build time', url: docs/components/math/ } - - { title: Markmap, icon: fa-solid fa-sitemap, desc: 'mind maps from headings', url: docs/components/markmap/ } - - { title: ECharts, icon: fa-solid fa-chart-line, desc: '`echarts` data fence', url: docs/components/echarts/ } - - { title: Infographic, icon: fa-solid fa-chart-pie, desc: 'declarative data fence', url: docs/components/infographic/ } - - { title: Asciinema, icon: fa-solid fa-terminal, desc: 'terminal casts, local player', url: docs/components/asciinema/ } - - { title: OpenAPI, icon: fa-solid fa-plug, desc: 'Swagger UI · Redoc', url: docs/write/openapi/ } - -# --------------------------------------------------------------- steps ------ -start: - eyebrow: Get started - title: A bilingual documentation site in three steps - text: >- - You do not start from an empty directory. You clone the site you are - reading — it is the theme's regression site, so every component and page - type is already in it — and delete what you do not need. - items: - - title: Clone this site and preview it - icon: fa-solid fa-code-branch - desc: >- - Requires Git, Go and Hugo Extended ≥ 0.160.1 — no Node.js. The - preview runs with live reload; the local search index is built on - demand. - cmd: { code: 'git clone https://github.com/pgsty/oink.pgsty.com my-docs && cd my-docs && hugo server' } - - title: Make it yours - icon: fa-solid fa-pen-nib - desc: >- - Change `baseURL`, `title` and `github_repo` in `hugo.yml`, replace the - pages under `content/`, keep the languages you need. Ten minutes, - according to the [walkthrough](docs/start/). - cmd: { code: 'hugo mod get github.com/pgsty/oink@v0.5.0' } - - title: Build and ship `public/` - icon: fa-solid fa-cloud-arrow-up - desc: >- - One command produces the whole site. Host the static output on GitHub - Pages, an object store or a plain web server — the [deployment - guide](docs/admin/deploy/) covers each. - cmd: { code: 'hugo --gc --minify' } - -# ---------------------------------------------------------- code-plate ------ -config: - eyebrow: Configuration - title: hugo.yaml — the whole build configuration - text: >- - Import the module, choose the outputs, switch on the features you want. - Interactive features are off until a site asks for them, and every - renamed or removed key fails the build with its replacement named — no - silent drift between versions. - aria_label: Minimal hugo.yaml for an OINK site - lang: yaml - code: | - module: - imports: - - path: github.com/pgsty/oink # pinned in go.mod, no node_modules - - outputs: # the site chooses; the theme never does - home: [HTML, RSS, markdown, LLMS] - page: [HTML, markdown] - section: [HTML, RSS, print, markdown] - - params: - offline_search: true # Lunr index per language, built by Hugo - ui: - dark_mode: true - image_zoom: true - keyboard_nav: true # w s a d · q e · j k · t · l - -# ----------------------------------------------------- pricing-compare ------ -compare: - eyebrow: In context - title: How it compares - text: >- - Structural differences only, taken from each project's own documentation - and repository. Versions move; check the current docs before choosing. - Details and caveats are on the [introduction page](docs/about/). - feature_label: Dimension - tiers: [OINK, Docsy, Hextra, Docusaurus] - groups: - - name: Build and delivery - rows: - - { name: Build toolchain, cells: ['Hugo Extended, one binary', 'Hugo Extended + Node / npm', 'Hugo', 'Node.js toolchain'] } - - { name: npm required by the site, cells: [N, Y, N, Y] } - - { name: Where frontend assets come from, cells: ['Vendored in the theme, audited in VENDOR.json', 'node_modules and CDN', 'Prebuilt output in the repo', 'npm dependencies'] } - - { name: Builds without network access, cells: [Y, N, Y, N] } - - name: Authoring - rows: - - { name: Component syntax, cells: ['Markdown-native forms, 29 shortcodes as full forms', '19 shortcodes', '29 shortcodes, native callouts', 'MDX (React components)'] } - - { name: Book numbering and cross-references, cells: [Y, N, N, 'Plugins or custom code'] } - - { name: Release and download pages, cells: [Y, N, N, 'Plugins or custom code'] } - - { name: Data-driven landing pages, cells: [Y, N, N, 'Custom React pages'] } - - name: Reach - rows: - - { name: Multilingual sites, cells: ['Hugo i18n + 32 UI locales', 'Hugo i18n', 'Hugo i18n + 21 UI locales', 'Built-in i18n framework'] } - - { name: Markdown output and llms.txt, cells: [Y, Y, Y, 'Plugins'] } - -# ----------------------------------------------------------- bar-chart ------ -scale: - eyebrow: Scale - title: Sizes it already carries - text: >- - Markdown files per production site, as documented on the - [showcase page](docs/about/showcase/). The largest and the smallest run - the same theme, pinned as a Hugo module. - unit: files - items: - - { label: 'pigsty.cc — Pigsty docs, Chinese', value: 1490, note: 'docs tree, 300+ blog posts, price landing page' } - - { label: 'pigsty.io — Pigsty docs, English', value: 1400, note: 'docs tree, 200+ blog posts, extension catalogue, version menu' } - - { label: 'pgsty.pro — Pigsty v5.0, bilingual', value: 930, note: '400+ pages per language, 130+ posts, 128 release cards' } - - { label: 'silo.pgsty.com — SILO, bilingual', value: 894, note: '447 pages per language, migrated from another docs system' } - - { label: 'pgsql.cc — PostgreSQL ecosystem docs', value: 303, note: 'six upstream doc trees, English first, Chinese in progress' } - - { label: 'caps.vonng.com — the smallest', value: 4, note: 'two pages per language: a landing page and a configurator' } - -# ----------------------------------------------------------- logo-wall ------ -production: - eyebrow: In production - title: Eleven sites, one theme - text: >- - Pure English, pure Chinese and bilingual sites; documentation trees, - company pages, price pages and two-page project sites. All of them pin - OINK as a Hugo module and all of them run local full-text search. - layout: grid - columns: 4 - items: - - { title: pigsty.io, icon: fa-solid fa-database, desc: 'Pigsty docs · English', url: 'https://pigsty.io/', external: true } - - { title: pigsty.cc, icon: fa-solid fa-database, desc: 'Pigsty docs · Chinese', url: 'https://pigsty.cc/', external: true } - - { title: pgsty.pro, icon: fa-solid fa-gem, desc: 'Pigsty v5.0 · bilingual', url: 'https://pgsty.pro/', external: true } - - { title: pig.pgsty.com, icon: fa-solid fa-box, desc: 'extension manager', url: 'https://pig.pgsty.com/', external: true } - - { title: sow.pgsty.com, icon: fa-solid fa-boxes-stacked, desc: 'APT / YUM repositories', url: 'https://sow.pgsty.com/', external: true } - - { title: silo.pgsty.com, icon: fa-solid fa-hard-drive, desc: 'S3 object storage', url: 'https://silo.pgsty.com/', external: true } - - { title: exp.pgsty.com, icon: fa-solid fa-chart-line, desc: 'PG Exporter metrics', url: 'https://exp.pgsty.com/', external: true } - - { title: pgsql.cc, icon: fa-solid fa-server, desc: 'PostgreSQL ecosystem', url: 'https://pgsql.cc/', external: true } - - { title: pgsty.com, icon: fa-solid fa-building, desc: 'company site', url: 'https://pgsty.com/', external: true } - - { title: caps.vonng.com, icon: fa-solid fa-keyboard, desc: 'two-page project site', url: 'https://caps.vonng.com/', external: true } - - { title: oink.pgsty.com, icon: fa-solid fa-book, desc: 'this site · regression suite', url: docs/ } - -# ---------------------------------------------------------- case-study ------ -migration: - eyebrow: Migration - title: Moving a large site in, without rewriting it - text: >- - SILO's documentation came from another documentation system. Its - sidebar is not hand-maintained: `data/docs_nav.json` was generated from - the migration inventory and drives the tree directly. - stats: - - { value: 387, label: upstream pages inventoried } - - { value: 894, label: pages live, two languages } - - { value: 1, label: JSON file drives the sidebar } - quote: >- - silo.pgsty.com is the largest migration case: `data/docs_nav.json` was - generated from the migration inventory of 387 upstream pages and serves - as the input of the sidebar tree. It also enables a `module` taxonomy and - ships a download page. - source: OINK showcase — silo.pgsty.com - url: /docs/about/showcase/#silo-pgsty-com - -# ------------------------------------------------------------ timeline ------ -releases: - eyebrow: Trajectory - title: From a Docsy fork to component API v5 - text: >- - OINK started as a fork of Docsy and evolved into an independent theme. - Each release is documented, and each migration is a script that reports - before it rewrites. - items: - - date: 2026-08-08 - title: 0.1.0 — A stable local-first foundation - desc: The Docsy content model kept; the shell, navigation, search and delivery model replaced. Every asset vendored. - url: /blog/release/0.1.0/ # timeline emits href verbatim, so this stays absolute - - date: 2026-08-10 - title: 0.2.0 — Richer content and sharper presentation - desc: Enhanced code blocks, diagrams and charts as local runtimes, and the first Markdown-native component forms. - url: /blog/release/0.2.0/ - - date: 2026-08-12 - title: 0.3.0 — Authoring, navigation, and a lighter page - desc: The command palette, keyboard navigation and page actions; a smaller, per-page JavaScript payload. - url: /blog/release/0.3.0/ - - date: 2026-08-14 - title: 0.4.0 — Scenario components for complete publishing workflows - desc: Books, releases and downloads, landing pages and reading flows in one release, with migration scripts. - url: /blog/release/0.4.0/ - - date: 2026-08-18 - title: 0.5.0 — Component API v5 and the converged configuration - desc: Native forms for every primitive, one attribute policy, one naming namespace, and legacy keys that fail the build with their replacement named. - url: /blog/release/0.5.0/ - -# ---------------------------------------------------------- principles ------ -principles: - eyebrow: Principles - title: What stays true from release to release - columns: 4 - items: - - icon: fa-solid fa-plug-circle-xmark - title: Local first, fail closed - desc: >- - Everything a browser needs ships with the theme. A feature that would - need the network fails the build unless the site configured it. - - icon: fa-brands fa-markdown - title: Markdown is the source of truth - desc: >- - Components have native Markdown forms first and shortcodes second, so - content reads correctly with or without the theme. - - icon: fa-solid fa-sliders - title: The site decides - desc: >- - Outputs, search, comments, zoom and assistant links are opt-in. The - site owns its content, brand and business components; the theme owns - the shell. - - icon: fa-solid fa-triangle-exclamation - title: Explicit over silent - desc: >- - Renamed keys and removed shortcodes fail the build and name their - replacement. Migrations are scripts that report before they rewrite. - -# ----------------------------------------------------------------- faq ------ -faq: - eyebrow: Before you commit - title: Questions worth asking first - items: - - question: Do I need Node.js, npm or a bundler anywhere? - answer: >- - No. The consumer site's build dependency is Hugo Extended (0.160.1 or - newer). Go is used once, to resolve the theme as a Hugo module; an - offline archive or a Git submodule works without it. Interactive - features still run JavaScript in the browser — search, the palette, - charts, tabs — but those scripts are shipped with the theme and - attached only to the pages that use them. - open: true - - question: I have a Docsy site. How hard is the move? - answer: >- - OINK keeps Docsy's content model and multilingual behaviour, so - content and front matter mostly carry over; the shell, navigation, - search and components are replaced. Renamed keys fail the build with - the new name, and `scripts/migrations/oink06.py` reports what it would - rewrite before it writes anything. Read [Upgrade and - migration](docs/admin/upgrade/). - - question: Is OINK only for large documentation trees? - answer: >- - No. The [showcase](docs/about/showcase/) runs from a two-page project - site to a fifteen-hundred-file distribution manual, and one site uses - OINK for landing pages alone. The shell scales down as well as up. - - question: How does it handle Chinese and other non-Latin content? - answer: >- - Local search uses Lunr for Latin scripts and a substring fallback for - CJK, so Chinese and Japanese pages are searchable without a hosted - service. English, Simplified Chinese (`zh-cn`, `zh`) and Traditional - Chinese (`zh-tw`) interface text is human-reviewed; 29 more locales - share the same key schema. RTL layouts are supported. - - question: What do AI assistants and crawlers get? - answer: >- - With `markdown` in `outputs`, every page has an `index.md` sibling - announced by `rel="alternate"`, and the `LLMS` format writes a - site-level `llms.txt` — this site's is at `/llms.txt`. The "open in - ChatGPT / Claude" page actions exist but are off by default, because - they hand the reader's URL to a third party. - - question: What is the license, and where does Docsy stand? - answer: >- - Apache License 2.0. OINK is a fork of Docsy that evolved - independently; Docsy's history, attribution and Apache-2.0 - obligations are preserved, and every vendored runtime is listed with - its license in `VENDOR.json`. See [License and - attribution](docs/about/license/). - -# ----------------------------------------------------------------- cta ------ -cta: - title: Start from the site you are reading. - desc: >- - Clone it, change three settings, and let Hugo do the rest — a bilingual, - searchable, printable, agent-readable documentation site in ten minutes. - actions: - - { label: Get started, url: docs/start/, icon: fa-solid fa-rocket, style: primary } - - { label: Star on GitHub, url: 'https://github.com/pgsty/oink', icon: fa-brands fa-github, style: ghost, external: true } - - { label: What's new in 0.5.0, url: blog/release/0.5.0/, icon: fa-solid fa-newspaper, style: ghost } diff --git a/data/landing/home-v4/zh.yaml b/data/landing/home-v4/zh.yaml deleted file mode 100644 index 3bfa015..0000000 --- a/data/landing/home-v4/zh.yaml +++ /dev/null @@ -1,563 +0,0 @@ -# OINK 首页第四版(只有站点数据,不改主题)。 -# -# 下面每个分区都是 OINK 0.5.0 自带的 landing 分区(hero、metrics、 -# capabilities、cards、logo-wall、steps、code-plate、pricing-compare、 -# bar-chart、case-study、timeline、principles、faq、cta),全部由本文件驱动。 -# 所有数字与说法都取自本站文档(docs/about/、docs/about/features/、 -# docs/about/showcase/),读者可以逐条核对。 -# -# 叙事顺序:承诺 → 证据 → 四个理由 → 里面有什么 → 横向对比 → -# 谁在用 → 怎么开始 → 原则 → 常见问题 → 行动号召。 - -sections: - - hero - - { type: metrics, key: facts } - - { type: capabilities, key: why } - - { type: cards, key: content_types } - - { type: logo-wall, key: components } - - { type: steps, key: start } - - { type: code-plate, key: config } - - { type: pricing-compare, key: compare } - - { type: bar-chart, key: scale } - - { type: logo-wall, key: production } - - { type: case-study, key: migration } - - { type: timeline, key: releases } - - { type: principles, key: principles } - - { type: faq, key: faq } - - { type: cta, key: cta } - -# ---------------------------------------------------------------- hero ------ -hero: - eyebrow: OINK 0.5.0 · Open · Indexed · Navigable · Knowledge - title_lines: - - 技术文档站, - - 只需要一个 Hugo。 - title_size: 4.4rem - lead: >- - OINK 把一棵 Markdown 目录树变成完整的文档系统:文档、博客、书籍、发布页与 - API 参考,内置本地全文检索、32 种界面语言、多版本、打印视图与面向 AI - 助手的输出。构建依赖只有 Hugo Extended——没有 Node.js、没有 npm、 - 没有 CDN,构建期不需要联网。 - note: >- - 这一页本身就是一份 YAML 文件,由主题自带的 landing 分区渲染, - 没有为它写过任何模板。 - note_icon: fa-solid fa-file-code - image: - light: images/hero-light.webp - dark: images/hero-dark.webp - alt: Markdown 内容、配置与随主题分发的资源经过一次 Hugo 构建汇成静态文档站 - actions: - - label: 十分钟上手 - url: docs/start/ - icon: fa-solid fa-rocket - style: primary - - label: 组件总览 - url: docs/components/ - icon: fa-solid fa-cubes - style: ghost - - label: GitHub - url: https://github.com/pgsty/oink - icon: fa-brands fa-github - style: ghost - external: true - detail: - label: 0.5.0 有什么变化 —— 组件 API v5 与收敛后的配置 - url: blog/release/0.5.0/ - icon: fa-solid fa-newspaper - -# ------------------------------------------------------------- metrics ------ -facts: - eyebrow: 用数字说话 - title: 先看事实,再谈形容词 - text: >- - 每个数字都链接到讲它的页面。本站同时是主题的回归测试站:你在这里读到的, - 就是主题实际交付的。 - animate: true - columns: 6 - link_label: 详情 - items: - - value: 1 - label: 个构建依赖 - desc: Hugo Extended ≥ 0.160.1 - url: docs/start/ - - value: 0 - label: 个 npm 包 - desc: 无 PostCSS、无打包器、无 CDN - url: docs/about/features/ - - value: 32 - label: 种界面语言 - desc: en · zh-cn · zh-tw 经人工审校 - url: docs/customize/i18n/ - - value: 4 - label: 种页面输出 - desc: HTML · 打印 · Markdown · llms.txt - url: docs/customize/agents/ - - value: 11 - label: 个生产站点 - desc: 从两页到一千五百页 - url: docs/about/showcase/ - - value: 28 - label: 个随主题分发的运行时 - desc: 版本、许可证与 SHA-256 记在 VENDOR.json - url: docs/about/license/ - -# -------------------------------------------------------- capabilities ------ -why: - eyebrow: 为什么是 OINK - title: 四件普通 Hugo 主题通常给不了你的事 - text: >- - 多数文档主题只是一层皮。OINK 是一套文档系统:写作模型、阅读外壳、 - 输出矩阵与交付方式一起设计,每一项都能在本站验证。 - items: - - ref: 01 / MARKDOWN 原生 - title: 组件写在 Markdown 里,而不是另一套模板语言 - url: docs/components/ - motto: 在 GitHub 上可读,在站点上完整。 - bullets: - - >- - [提示块](docs/components/callout/)是 `> [!NOTE]` 块引用, - [步骤](docs/components/steps/)与[卡片](docs/components/cards/)是列表加 - `{.steps}` / `{.cards}`,[参数表](docs/components/fields/)是表格加一行 - `{.fields}`,[图注](docs/components/image/)是图片下面一行 `{caption="…"}`。 - - >- - [标签页](docs/components/tabs/)是几个相邻围栏各带一个 `{tab="…"}`; - [文件树](docs/components/filetree/)、[画廊](docs/components/gallery/)、 - [Mermaid](docs/components/mermaid/) 与 [ECharts](docs/components/echarts/) - 是以语言命名的数据围栏。 - - >- - 29 个 shortcode 覆盖原生形态表达不了的场景,而且每一个都在构建期校验参数: - 写错一个键名是构建错误,不是页面上悄悄缺一块。 - value: >- - 内容离开主题也活得下去:在 GitHub 或任何 Markdown 阅读器里,它退化为块引用、 - 表格、列表与代码块。什么都不丢,也不被锁定。 - visual: - type: code - title: guide.md - aria_label: 含提示块、步骤列表与标签页的 Markdown 源码 - lines: - - { class: k, prefix: '> ', text: '[!TIP] 只需一行属性' } - - { class: c, prefix: '> ', text: '提示块、步骤与标签页仍然是普通 Markdown。' } - - { text: '' } - - { class: p, prefix: '1. ', text: '写 Markdown' } - - { class: p, prefix: '2. ', text: '运行 hugo' } - - { class: p, prefix: '3. ', text: '部署 public/' } - - { class: k, text: '{.steps}' } - - { text: '' } - - { class: s, text: '```bash {tab="macOS"}' } - - { text: 'brew install hugo' } - - { class: s, text: '```' } - - { class: s, text: '```bash {tab="Linux"}' } - - { text: 'apt install hugo' } - - { class: s, text: '```' } - - ref: 02 / 只要 HUGO · 本地优先 - title: 一个二进制构建一切,而且离线 - url: docs/about/features/ - motto: 没有 Node,没有 npm,没有 CDN;宁可构建失败,也不偷偷连出去。 - bullets: - - >- - 消费站点的构建依赖只有 [Hugo Extended 0.160.1 或更新版本](docs/start/)。 - SCSS 由 Hugo 内置编译,没有 PostCSS、没有 webpack、没有构建期下载。 - - >- - Bootstrap、Font Awesome、四款字体、Lunr、Mermaid、KaTeX、Markmap、 - Swagger UI、Redoc、Asciinema、ECharts——[28 个运行时](docs/about/license/) - 全部提交在主题仓库,`VENDOR.json` 逐项记录版本、来源、许可证与 SHA-256, - 并且只在用到它的页面下发。 - - >- - 凡是可能引起网络请求的功能——PlantUML 服务器、draw.io 服务器、 - Algolia 密钥——站点不显式配置就构建失败。没有任何东西会自作主张连出去。 - value: >- - 从笔记本到 CI 再到生产,只有一条确定性的路径。不能出网的构建机是被支持的场景, - 不是边缘情况。 - visual: - type: code - title: ci.log - aria_label: 消费站点的构建日志 - lines: - - { class: p, prefix: '$ ', text: 'hugo --gc --minify' } - - { class: ok, prefix: '✓ ', text: '0 个 npm 包,构建期 0 次网络请求' } - - { class: ok, prefix: '✓ ', text: '28 个随主题分发的运行时校验通过(VENDOR.json)' } - - { class: ok, prefix: '✓ ', text: '2 种语言 · 每种语言一份检索索引' } - - { class: ok, prefix: '✓ ', text: 'HTML · 打印 · Markdown · llms.txt' } - - { class: c, prefix: '# ', text: 'public/ 已就绪——放到任何地方托管' } - - ref: 03 / 面向大站的阅读外壳 - title: 一千五百页也撑得住的导航 - url: docs/customize/navigation/ - motto: 侧栏树、页内目录、⌘K 命令面板、键盘、检索——文档、博客、书籍共用一套外壳 - bullets: - - >- - [本地全文检索](docs/customize/search/):拉丁文字用 Lunr,中日韩文本用子串回退, - 查询内容不离开浏览器。页面可以用 `search_boost` 调权重、`search_keywords` - 补同义词。 - - >- - [命令面板](docs/customize/panel/)——`⌘K` 打开,`/` 进入搜索,`\` 进入命令—— - 加上[单键导航](docs/customize/keyboard/):`w s a d` 在树上移动,`q e` - 上下篇,`j k` 沿目录跳转,`t` 切主题,`l` 切语言。 - - >- - [32 种界面语言](docs/customize/i18n/)、缺译回退与 RTL, - 带归档横幅的[版本菜单](docs/customize/versions/),深色模式、面包屑、翻页、 - 打印视图——全部是外壳的一部分,没有一样是插件。 - value: >- - 读者的偏好——主题、语言、侧栏宽度、面板历史——存在浏览器里,不发往任何地方。 - visual: - type: shell - brand: OINK - search: 搜索或运行命令 - shortcut: ⌘ K - section: 文档 - section_icon: fa-solid fa-book - items: - - { label: 简介 } - - { label: 十分钟上手, active: true } - - { label: 写作 } - - { label: 组件 } - - { label: 定制 } - - { label: 运维 } - eyebrow: 技术文档 / 长期维护 - title_lines: [站点再大,, 也能走得动。] - rules: ['88%', '72%', '55%'] - - ref: 04 / 一份内容 · 四种输出 - title: 同一页面,同时面向读者、纸张、搜索引擎与 AI - url: docs/customize/agents/ - motto: HTML · 打印 · Markdown · llms.txt——每个组件在四种输出下都有确定的形态 - bullets: - - >- - 交互式 HTML 给读者;[打印视图](docs/customize/print/)按栏目或整本书生成, - 去掉控件、展开折叠块。 - - >- - 每个页面旁边多一份 `index.md`,`` 里用 `rel="alternate"` 指过去; - 站点根目录再生成一份 [`llms.txt`](docs/customize/agents/) 清单——同样的内容, - 换成 AI 助手最好读的形式。 - - >- - 用哪几种输出由站点在 `outputs` 里决定,主题从不替站点决定; - 「在 ChatGPT / Claude 中打开」默认关闭,因为它会把当前 URL 交给第三方。 - value: >- - 写一次,同时交付给浏览器、PDF、Markdown 阅读器与 AI 助手——一次构建, - 没有第二套工具链。 - visual: - type: components - aria_label: 四种输出格式 - columns: 2 - items: - - { icon: fa-solid fa-globe, title: HTML, detail: 交互式阅读外壳 } - - { icon: fa-solid fa-print, title: 打印, detail: 按栏目或整本书, url: docs/customize/print/ } - - { icon: fa-brands fa-markdown, title: Markdown, detail: 每页旁边一份 index.md, url: docs/customize/agents/ } - - { icon: fa-solid fa-robot, title: llms.txt, detail: 给 AI 助手的站点清单, url: docs/customize/agents/ } - -# --------------------------------------------------------------- cards ------ -content_types: - eyebrow: 不止文档 - title: 六类内容,一套主题 - text: >- - 文档站很少只有文档。那些通常需要第二个工具的内容形态在这里是内置的, - 并且共用同一个外壳、同一份检索索引、同一组语言与同样的四种输出。 - columns: 3 - link_label: 阅读指南 - items: - - title: 文档 - meta: 阅读外壳 - icon: fa-solid fa-book - desc: >- - 侧栏树来自内容目录或显式的 `data/docs_nav.json`,页内目录、面包屑、翻页、 - 编辑与历史链接、反馈与评论——其它一切内容类型复用的就是这套文档外壳。 - url: docs/write/pages/ - - title: 博客与 RSS - meta: 更新 - icon: fa-solid fa-rss - desc: >- - 按时间排列的文章、按数量排序的标签面板、按语言分别生成的订阅源, - 以及读起来像发布说明的发布说明。 - url: docs/write/blog/ - - title: 书籍 - meta: 长篇 - icon: fa-solid fa-book-open - desc: >- - 章节编号,图 / 表 / 式 / 例用 `{#id num=}` 编号、用 `xref` 交叉引用, - 自动生成目录与图表清单,整本书可打印。 - url: docs/write/book/ - - title: 发布与下载 - meta: 发布 - icon: fa-solid fa-download - desc: >- - `data/download/*.yaml` 生成发布卡片、资产表与校验和。发布状态——待发布、 - 已发布、已归档——是数据,不是一套复制粘贴的仪式。 - url: docs/write/releases/ - - title: Landing 页 - meta: 展示 - icon: fa-solid fa-layer-group - desc: >- - `data/home/.yaml` 用二十一种服务端渲染的分区拼装首页;任何页面加上 - `layout: landing` 也一样。你正在读的这一页就是其中之一。 - url: docs/customize/home/ - - title: API 参考 - meta: OpenAPI - icon: fa-solid fa-plug - desc: >- - Swagger UI 与 Redoc 都是本地运行时。规范文件放在仓库里,与正文并排渲染, - 和站点其它部分一样可以离线工作。 - url: docs/write/openapi/ - -# ----------------------------------------------------------- logo-wall ------ -components: - eyebrow: 组件 - title: 面向工程内容的组件,只在用到的页面加载 - text: >- - 每个组件都有自己的一页——先源码后效果——并且在 HTML、打印、Markdown 与 RSS - 下都有确定的形态。图表与图示运行时随主题分发、按页面挂载:没有图表的页面 - 永远不会下载图表库。 - layout: grid - columns: 4 - items: - - { title: 提示块, icon: fa-solid fa-circle-info, desc: '`> [!NOTE]` · 十种类型', url: docs/components/callout/ } - - { title: 标签页, icon: fa-solid fa-folder, desc: '围栏加 `{tab=}`', url: docs/components/tabs/ } - - { title: 步骤, icon: fa-solid fa-list-ol, desc: '列表加 `{.steps}`', url: docs/components/steps/ } - - { title: 卡片, icon: fa-solid fa-table-cells-large, desc: '列表加 `{.cards}`', url: docs/components/cards/ } - - { title: 参数表, icon: fa-solid fa-table-list, desc: '表格加 `{.fields}`', url: docs/components/fields/ } - - { title: 代码块, icon: fa-solid fa-code, desc: 'Chroma · 行号 · 复制 · 折叠', url: docs/components/code/ } - - { title: 文件树, icon: fa-solid fa-folder-tree, desc: '`filetree` 数据围栏', url: docs/components/filetree/ } - - { title: 画廊, icon: fa-solid fa-images, desc: '`gallery` 数据围栏', url: docs/components/gallery/ } - - { title: 表格, icon: fa-solid fa-table, desc: '图注 · 编号 · 矩阵', url: docs/components/table/ } - - { title: Mermaid, icon: fa-solid fa-diagram-project, desc: '本地图示运行时', url: docs/components/mermaid/ } - - { title: 数学公式, icon: fa-solid fa-square-root-variable, desc: 'KaTeX 构建期渲染', url: docs/components/math/ } - - { title: Markmap, icon: fa-solid fa-sitemap, desc: '标题生成思维导图', url: docs/components/markmap/ } - - { title: ECharts, icon: fa-solid fa-chart-line, desc: '`echarts` 数据围栏', url: docs/components/echarts/ } - - { title: 信息图, icon: fa-solid fa-chart-pie, desc: '声明式数据围栏', url: docs/components/infographic/ } - - { title: Asciinema, icon: fa-solid fa-terminal, desc: '终端录像,本地播放', url: docs/components/asciinema/ } - - { title: OpenAPI, icon: fa-solid fa-plug, desc: 'Swagger UI · Redoc', url: docs/write/openapi/ } - -# --------------------------------------------------------------- steps ------ -start: - eyebrow: 开始 - title: 三步得到一个双语文档站 - text: >- - 你不必从空目录开始。克隆你正在读的这个站点——它是主题的回归站, - 每个组件与每种页面类型都已经在里面——然后删掉不需要的部分。 - items: - - title: 克隆本站并预览 - icon: fa-solid fa-code-branch - desc: >- - 需要 Git、Go 与 Hugo Extended ≥ 0.160.1,不需要 Node.js。预览带实时重载, - 本地检索索引按需生成。 - cmd: { code: 'git clone https://github.com/pgsty/oink.pgsty.com my-docs && cd my-docs && hugo server' } - - title: 换成你自己的 - icon: fa-solid fa-pen-nib - desc: >- - 改掉 `hugo.yml` 里的 `baseURL`、`title` 与 `github_repo`,替换 `content/` - 下的页面,保留需要的语言。按[上手指南](docs/start/)的说法,十分钟。 - cmd: { code: 'hugo mod get github.com/pgsty/oink@v0.5.0' } - - title: 构建并发布 `public/` - icon: fa-solid fa-cloud-arrow-up - desc: >- - 一条命令生成整个站点。静态产物可以放到 GitHub Pages、对象存储或普通 - Web 服务器——[部署指南](docs/admin/deploy/)分别讲了怎么做。 - cmd: { code: 'hugo --gc --minify' } - -# ---------------------------------------------------------- code-plate ------ -config: - eyebrow: 配置 - title: hugo.yaml —— 全部构建配置 - text: >- - 引入模块,选择输出,打开你要的功能。交互功能默认关闭、站点显式开启; - 被重命名或移除的键会让构建失败并给出新名字——版本之间没有静默漂移。 - aria_label: 一个 OINK 站点最小的 hugo.yaml - lang: yaml - code: | - module: - imports: - - path: github.com/pgsty/oink # 在 go.mod 里固定版本,没有 node_modules - - outputs: # 由站点选择,主题从不替站点决定 - home: [HTML, RSS, markdown, LLMS] - page: [HTML, markdown] - section: [HTML, RSS, print, markdown] - - params: - offline_search: true # 每种语言一份 Lunr 索引,由 Hugo 生成 - ui: - dark_mode: true - image_zoom: true - keyboard_nav: true # w s a d · q e · j k · t · l - -# ----------------------------------------------------- pricing-compare ------ -compare: - eyebrow: 横向看 - title: 与其它文档方案的差别 - text: >- - 只列结构性差别,且只写能从各项目自身文档与仓库确认的部分。各项目的版本会变动, - 选型前以其当前文档为准;细节与补充说明见[简介页](docs/about/)。 - feature_label: 维度 - tiers: [OINK, Docsy, Hextra, Docusaurus] - groups: - - name: 构建与交付 - rows: - - { name: 构建工具, cells: ['Hugo Extended,单个二进制', 'Hugo Extended + Node / npm', 'Hugo', 'Node.js 工具链'] } - - { name: 消费站点要不要 npm, cells: [N, Y, N, Y] } - - { name: 前端资源从哪来, cells: ['全部提交在主题仓库,VENDOR.json 可审计', 'node_modules 与 CDN', '预编译产物提交在仓库', 'npm 依赖'] } - - { name: 不联网也能构建, cells: [Y, N, Y, N] } - - name: 写作 - rows: - - { name: 组件写法, cells: ['Markdown 原生形态为主,29 个 shortcode 兜底', 'shortcode(19 个)', 'shortcode(29 个),提示块有原生形态', 'MDX(React 组件)'] } - - { name: 书籍编号与交叉引用, cells: [Y, N, N, '需插件或自建'] } - - { name: 发布与下载页, cells: [Y, N, N, '需插件或自建'] } - - { name: 数据驱动的落地页, cells: [Y, N, N, '自写 React 页面'] } - - name: 覆盖面 - rows: - - { name: 多语言, cells: ['Hugo 多语言 + 32 个界面语言包', 'Hugo 多语言', 'Hugo 多语言 + 21 个界面语言包', '内置 i18n 框架'] } - - { name: Markdown 输出与 llms.txt, cells: [Y, Y, Y, '需插件'] } - -# ----------------------------------------------------------- bar-chart ------ -scale: - eyebrow: 规模 - title: 它已经承载的体量 - text: >- - 各生产站点的 Markdown 文件数,见[示例站点](docs/about/showcase/)页。 - 最大的与最小的用的是同一个主题,同样以 Hugo Module 固定版本。 - unit: 个文件 - items: - - { label: 'pigsty.cc —— Pigsty 中文文档', value: 1490, note: '文档树、三百多篇博客、价格 Landing 页' } - - { label: 'pigsty.io —— Pigsty 英文文档', value: 1400, note: '文档树、两百多篇博客、扩展目录、顶栏版本菜单' } - - { label: 'pgsty.pro —— Pigsty v5.0,双语', value: 930, note: '中英各四百多页、一百三十多篇博客、128 张发布卡片' } - - { label: 'silo.pgsty.com —— SILO,双语', value: 894, note: '中英各 447 页,从其它文档系统整站迁移' } - - { label: 'pgsql.cc —— PostgreSQL 周边组件文档', value: 303, note: '六棵上游文档子树,先发英文、中文逐步补齐' } - - { label: 'caps.vonng.com —— 最小的一个', value: 4, note: '中英各两页:一个首页加一个配置生成器' } - -# ----------------------------------------------------------- logo-wall ------ -production: - eyebrow: 生产环境 - title: 十一个站点,一套主题 - text: >- - 纯英文站、纯中文站与同站双语站;文档树、公司官网、价格页与两页的项目站。 - 它们全部通过 Hugo Module 固定主题版本,全部启用了本地全文检索。 - layout: grid - columns: 4 - items: - - { title: pigsty.io, icon: fa-solid fa-database, desc: 'Pigsty 文档 · 英文', url: 'https://pigsty.io/', external: true } - - { title: pigsty.cc, icon: fa-solid fa-database, desc: 'Pigsty 文档 · 中文', url: 'https://pigsty.cc/', external: true } - - { title: pgsty.pro, icon: fa-solid fa-gem, desc: 'Pigsty v5.0 · 双语', url: 'https://pgsty.pro/', external: true } - - { title: pig.pgsty.com, icon: fa-solid fa-box, desc: '扩展包管理器', url: 'https://pig.pgsty.com/', external: true } - - { title: sow.pgsty.com, icon: fa-solid fa-boxes-stacked, desc: 'APT / YUM 仓库', url: 'https://sow.pgsty.com/', external: true } - - { title: silo.pgsty.com, icon: fa-solid fa-hard-drive, desc: 'S3 对象存储', url: 'https://silo.pgsty.com/', external: true } - - { title: exp.pgsty.com, icon: fa-solid fa-chart-line, desc: 'PG Exporter 指标', url: 'https://exp.pgsty.com/', external: true } - - { title: pgsql.cc, icon: fa-solid fa-server, desc: 'PostgreSQL 周边生态', url: 'https://pgsql.cc/', external: true } - - { title: pgsty.com, icon: fa-solid fa-building, desc: '公司官网', url: 'https://pgsty.com/', external: true } - - { title: caps.vonng.com, icon: fa-solid fa-keyboard, desc: '两页的项目站', url: 'https://caps.vonng.com/', external: true } - - { title: oink.pgsty.com, icon: fa-solid fa-book, desc: '本站 · 回归测试站', url: docs/ } - -# ---------------------------------------------------------- case-study ------ -migration: - eyebrow: 迁移 - title: 把一个大站搬进来,而不是重写它 - text: >- - SILO 的文档来自另一套文档系统。它的侧栏不是手工维护的:`data/docs_nav.json` - 由迁移清单生成,直接驱动侧栏树。 - stats: - - { value: 387, label: 上游页面纳入清单 } - - { value: 894, label: 页上线,两种语言 } - - { value: 1, label: 份 JSON 驱动侧栏 } - quote: >- - silo.pgsty.com 是其中最大的迁移案例:`data/docs_nav.json` 由 387 页上游文档的 - 迁移清单生成,作为侧栏树的输入。它还启用了 `module` 分类法,并有下载页。 - source: OINK 示例站点 —— silo.pgsty.com - url: /zh/docs/about/showcase/#silo-pgsty-com - -# ------------------------------------------------------------ timeline ------ -releases: - eyebrow: 轨迹 - title: 从 Docsy 分叉到组件 API v5 - text: >- - OINK 从 Docsy 分叉而来,之后独立演化。每个版本都有发布说明, - 每次迁移都是一段先报告、再改写的脚本。 - items: - - date: 2026-08-08 - title: 0.1.0 —— 稳定的本地优先基础 - desc: 保留 Docsy 的内容模型;替换外壳、导航、检索与交付方式。所有资源随主题分发。 - url: /zh/blog/release/0.1.0/ # timeline 原样输出 href,因此写绝对路径 - - date: 2026-08-10 - title: 0.2.0 —— 更丰富的内容与更精致的呈现 - desc: 增强代码块,图示与图表成为本地运行时,第一批 Markdown 原生组件形态。 - url: /zh/blog/release/0.2.0/ - - date: 2026-08-12 - title: 0.3.0 —— 写作、导航与更轻的页面 - desc: 命令面板、键盘导航与页面动作;按页面下发、更小的 JavaScript。 - url: /zh/blog/release/0.3.0/ - - date: 2026-08-14 - title: 0.4.0 —— 面向完整发布流程的场景组件 - desc: 书籍、发布与下载、Landing 页与顺序阅读一并发布,附迁移脚本。 - url: /zh/blog/release/0.4.0/ - - date: 2026-08-18 - title: 0.5.0 —— 组件 API v5 与收敛后的配置 - desc: 每个内容原语都有原生形态,一套属性策略,一个命名空间,旧键让构建失败并给出新名字。 - url: /zh/blog/release/0.5.0/ - -# ---------------------------------------------------------- principles ------ -principles: - eyebrow: 原则 - title: 版本更迭中不变的事 - columns: 4 - items: - - icon: fa-solid fa-plug-circle-xmark - title: 本地优先,宁可失败 - desc: >- - 浏览器需要的一切随主题分发。可能需要联网的功能,站点不显式配置就构建失败。 - - icon: fa-brands fa-markdown - title: Markdown 是唯一事实来源 - desc: >- - 组件先有 Markdown 原生形态,再有 shortcode,所以有没有主题内容都能正确阅读。 - - icon: fa-solid fa-sliders - title: 由站点决定 - desc: >- - 输出、检索、评论、缩放与 AI 助手链接都要站点主动打开。站点拥有自己的内容、 - 品牌与业务组件,主题只负责外壳。 - - icon: fa-solid fa-triangle-exclamation - title: 显式胜过静默 - desc: >- - 改名的键与移除的 shortcode 会让构建失败并给出替代写法。迁移是先报告、 - 再改写的脚本。 - -# ----------------------------------------------------------------- faq ------ -faq: - eyebrow: 选型之前 - title: 值得先问的几个问题 - items: - - question: 哪里需要 Node.js、npm 或打包器吗? - answer: >- - 不需要。消费站点的构建依赖是 Hugo Extended(0.160.1 或更新版本)。 - Go 只用一次,用来把主题解析为 Hugo Module;离线归档或 Git submodule - 方式则连 Go 也不需要。界面交互仍在浏览器里执行 JavaScript——检索、面板、 - 图表、标签页——但这些脚本随主题分发,只挂在用到它们的页面上。 - open: true - - question: 我有一个 Docsy 站,迁移有多难? - answer: >- - OINK 保留了 Docsy 的内容模型与多语言行为,内容与 front matter 大多可以直接沿用; - 替换的是外壳、导航、检索与组件。改名的键会让构建失败并给出新名字, - `scripts/migrations/oink06.py` 在改写之前先报告它会改什么。见[升级与迁移](docs/admin/upgrade/)。 - - question: OINK 只适合大型文档树吗? - answer: >- - 不是。[示例站点](docs/about/showcase/)里既有两页的项目站,也有一千五百个文件的 - 发行版手册,还有一个只用 OINK 做落地页的公司站。外壳向下缩放和向上扩展一样自然。 - - question: 中文与其它非拉丁文字的内容怎么处理? - answer: >- - 本地检索对拉丁文字用 Lunr、对中日韩文本用子串回退,中文与日文页面无需托管服务 - 也能搜索。英语、简体中文(`zh-cn`、`zh`)与繁体中文(`zh-tw`)的界面文案经过人工审校, - 另外 29 种语言共用同一套 key。支持 RTL 布局。 - - question: AI 助手与爬虫拿到的是什么? - answer: >- - `outputs` 里加上 `markdown`,每个页面就有一份 `index.md`,由 `rel="alternate"` - 指过去;`LLMS` 输出格式在站点根目录写出 `llms.txt`——本站的在 `/zh/llms.txt`。 - 「在 ChatGPT / Claude 中打开」这类页面动作存在,但默认关闭,因为它会把读者的 - URL 交给第三方。 - - question: 许可证是什么?和 Docsy 是什么关系? - answer: >- - Apache License 2.0。OINK 是 Docsy 的分叉并独立演化;Docsy 的源码历史、署名与 - Apache-2.0 义务完整保留,每个随主题分发的运行时都在 `VENDOR.json` 里连同许可证列出。 - 见[开源许可与致谢](docs/about/license/)。 - -# ----------------------------------------------------------------- cta ------ -cta: - title: 从你正在读的这个站点开始。 - desc: >- - 克隆它,改三处设置,剩下的交给 Hugo——十分钟得到一个双语、可检索、可打印、 - 对 AI 助手友好的文档站。 - actions: - - { label: 十分钟上手, url: docs/start/, icon: fa-solid fa-rocket, style: primary } - - { label: 在 GitHub 上加星, url: 'https://github.com/pgsty/oink', icon: fa-brands fa-github, style: ghost, external: true } - - { label: 0.5.0 有什么新变化, url: blog/release/0.5.0/, icon: fa-solid fa-newspaper, style: ghost } diff --git a/hugo.yml b/hugo.yml index 91f34d6..a5bf8ee 100644 --- a/hugo.yml +++ b/hugo.yml @@ -351,7 +351,9 @@ params: ui: typography: technical # Site-wide backlinks: every page lists the pages that link to it, so the - # documentation demonstrates the 0.8.0 feature it describes. + # documentation demonstrates the 0.8.0 feature it describes. The Docs and + # Blog section roots opt out in their own front matter -- an index is read + # as a way in, not as a destination worth tracing links back to. backlinks: true # The blog index toolbar gets the form cycle (list, cards, table) beside # its feed button, on the root and on every child section index. diff --git a/layouts/_shortcodes/dev-version.html b/layouts/_shortcodes/dev-version.html deleted file mode 100644 index 021c179..0000000 --- a/layouts/_shortcodes/dev-version.html +++ /dev/null @@ -1,28 +0,0 @@ -{{/* - Returns the development version of Docsy. - - Usage: {{% dev-version [final] %}} - - The development version is read from .Params.version in the page's front - matter. If [as-final-version] is true, the final non-dev version is returned, - as derived from the dev version. For example, v0.15.0-dev → v0.15.0. - - */ -}} - -{{ $finalVersion := eq (.Get 0) "final" -}} -{{ with $.Site.Params.tdVersion.dev -}} - {{ $devVersion := . -}} - {{ if $finalVersion -}} - {{ replace $devVersion "-dev" "" -}} - {{ else -}} - {{ . -}} - {{ with $.Site.Params.tdVersion.buildId -}} - +{{ replace . "+" "-" -}} - {{ end -}} - {{ end -}} - -{{ else -}} - {{ with $.Site.Params.version -}} - {{ replace . " " "_" -}} - {{ end -}} -{{ end -}} diff --git a/layouts/_shortcodes/hugo-version.html b/layouts/_shortcodes/hugo-version.html deleted file mode 100644 index 9003e65..0000000 --- a/layouts/_shortcodes/hugo-version.html +++ /dev/null @@ -1 +0,0 @@ -{{ hugo.Version -}} diff --git a/layouts/_shortcodes/release-summary.md b/layouts/_shortcodes/release-summary.md deleted file mode 100644 index 4879a11..0000000 --- a/layouts/_shortcodes/release-summary.md +++ /dev/null @@ -1,67 +0,0 @@ -{{/* - Generates release summary links for the version specified in page params. - - Usage: {{% release-summary %}} - - The version is read from .Params.version in the page's front matter. - The changelog label and #v anchor follow the release-note version when a - matching report exists (e.g. 0.15.1-dev → blog/release/0.15.0 → changelog - #v0.15.0). -*/ -}} - -{{ $version := $.Page.Param "version" | string -}} -{{ $version = strings.TrimPrefix "v" $version -}} -{{ $isDevVersion := strings.Contains $version "-dev" -}} -{{ if not $version -}} - {{ errorf "%s: shortcode 'release-summary': version parameter not found in page or site params" .Position -}} -{{ end -}} - -{{/* Same major.minor with patch 0, for release lookup when there is no note for this patch (e.g. 0.14.1 → 0.14.0) */ -}} -{{ $parts := split $version "." -}} -{{ $versionForRelease := $version -}} -{{ if and (ge (len $parts) 3) (ne (index $parts 2) "0") -}} - {{ $versionForRelease = printf "%s.%s.0" (index $parts 0) (index $parts 1) -}} -{{ end -}} - -{{/* Get the release section and search for a note: exact version first, then same release with patch 0 */ -}} -{{ $releaseSection := $.Site.GetPage "/blog/release" -}} -{{ $releasePage := false -}} -{{/* Changelog anchor + label align with the resolved release-report version when a note exists */ -}} -{{ $changelogVersion := $version -}} -{{ range $ver := (slice $version $versionForRelease) -}} - {{ if $releasePage }}{{ break }}{{ end -}} - {{ $candidate := $releaseSection.GetPage $ver -}} - {{ if $candidate -}} - {{ $releasePage = $candidate -}} - {{ $changelogVersion = $ver -}} - {{ break -}} - {{ end -}} -{{ end -}} - -{{ $changelogUrlFragment := add "#v" $changelogVersion -}} -{{ $errorOnMissingReleaseNote := and (not $releasePage) (not $isDevVersion) -}} -{{ if $errorOnMissingReleaseNote -}} - {{ if false -}} - {{ errorf "%s: shortcode 'release-summary': release note not found for version %q (tried %q)" - .Position $version (delimit (slice $version $versionForRelease) ", ") -}} - {{ else -}} - {{ $changelogUrlFragment = "" -}} - {{ end -}} -{{ end -}} - -{{ $changelogURL := printf "/project/about/changelog/%s" $changelogUrlFragment -}} -{{ $productionURL := .Site.Params.productionURL -}} - -## Release summary - -- [{{ with $releasePage }}{{ .Title }}{{ else }}Release notes{{ end }}][release] -- [Changelog v{{ $changelogVersion }}][changelog] entry - -{{ if and (not $releasePage) (not $isDevVersion) -}} - -{{ end }} -[release]: <{{ $productionURL }}{{ with $releasePage }}{{ .RelPermalink }}{{ else }}/blog/release/{{ end }}> -[changelog]: <{{ $productionURL }}{{ $changelogURL }}> diff --git a/layouts/_shortcodes/siteGetPage.html b/layouts/_shortcodes/siteGetPage.html deleted file mode 100644 index 6a1f1b1..0000000 --- a/layouts/_shortcodes/siteGetPage.html +++ /dev/null @@ -1,23 +0,0 @@ -{{- /* - - Returns a property of the page at the given site-relative path. - Currently used in mock/test homepages. - - Usage: {{% siteGetPage "/path/to/page" "RelPermalink" %}} - -*/ -}} - -{{ $path := .Get 0 -}} -{{ $propertyName := .Get 1 -}} -{{ if or (not $path) (not $propertyName) -}} - {{ errorf "shortcode 'siteGetPage': path and property name are required" .Position -}} -{{ end -}} -{{ with site.GetPage $path -}} - {{ if eq $propertyName "RelPermalink" -}} - {{ .RelPermalink }} - {{ else if eq $propertyName "Permalink" -}} - {{ .Permalink }} - {{ else -}} - {{ errorf "shortcode 'siteGetPage': unsupported or unknown property name %q" $propertyName .Position -}} - {{ end -}} -{{ end -}} diff --git a/package-lock.json b/package-lock.json index c4a57b8..2c5f38d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,8 +12,7 @@ "afdocs": "^0.20.0", "cross-env": "^10.1.0", "jsdom": "^30.0.1", - "npm-check-updates": "^23.1.0", - "rtlcss": "^4.3.0" + "npm-check-updates": "^23.1.0" }, "engines": { "node": ">=24", @@ -509,16 +508,6 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/fsevents": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", @@ -644,25 +633,6 @@ "dev": true, "license": "CC0-1.0" }, - "node_modules/nanoid": { - "version": "3.3.18", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", - "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, "node_modules/node-html-parser": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-7.1.0.tgz", @@ -738,13 +708,6 @@ "node": ">=8" } }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, "node_modules/picomatch": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", @@ -790,35 +753,6 @@ "node": ">=20" } }, - "node_modules/postcss": { - "version": "8.5.26", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", - "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.17", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -839,25 +773,6 @@ "node": ">=0.10.0" } }, - "node_modules/rtlcss": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.3.0.tgz", - "integrity": "sha512-FI+pHEn7Wc4NqKXMXFM+VAYKEj/mRIcW4h24YVwVtyjI+EqGrLc2Hx/Ny0lrZ21cBWU2goLy36eqMcNj3AQJig==", - "dev": true, - "license": "MIT", - "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0", - "postcss": "^8.4.21", - "strip-json-comments": "^3.1.1" - }, - "bin": { - "rtlcss": "bin/rtlcss.js" - }, - "engines": { - "node": ">=12.0.0" - } - }, "node_modules/saxes": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", @@ -904,19 +819,6 @@ "node": ">=0.10.0" } }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", diff --git a/package.json b/package.json index c0cc046..b347a4c 100644 --- a/package.json +++ b/package.json @@ -23,18 +23,17 @@ "build:production": "npm run _hugo -- --minify", "build": "cross-env npm run _build -- --baseURL \"${BASE_URL:-http://localhost}\"", "clean": "rm -Rf public", - "ccr": "cross-env HUGO_PARAMS_TD_CHROME=shared npm run", "preupdate:goldens": "npm run build", "seq": "bash -c 'for cmd in \"$@\"; do npm run $cmd || exit 1; done' - ", "serve": "npm run _serve -- --minify", "test:a11y": "playwright test tests/browser/accessibility.spec.mjs", "test:alt-site": "node --test 'tests/alt-site/**/*.test.mjs'", - "test:base": "npm run seq -- build _test:base", + "test:base": "mkdir -p tmp && bash -o pipefail -c 'npm run build 2>&1 | tee tmp/hugo-build.log' && npm run _test:base", "test:browser": "npm run seq -- test:a11y test:responsive-shell test:keyboard-nav test:content-components test:code-blocks test:prd5 test:theme-color", "test:command-palette": "playwright test tests/browser/command-palette.spec.mjs", "test:code-blocks": "playwright test tests/browser/code-blocks.spec.mjs", "test:content-components": "playwright test tests/browser/content-components.spec.mjs", - "test:extra": "npm run seq -- test:hugo-build test:alt-site test:md-output test:favicons test:release-pin-contract", + "test:extra": "npm run seq -- test:hugo-build test:alt-site test:md-output test:favicons test:rendered-links test:release-pin-contract", "test:favicons": "node --test 'tests/favicons/**/*.test.mjs'", "test:hugo-build": "node --test 'tests/hugo-build/**/*.test.mjs'", "test:keyboard-nav": "playwright test tests/browser/keyboard-nav.spec.mjs", @@ -43,8 +42,9 @@ "test:theme-color": "playwright test tests/browser/theme-color.spec.mjs", "test:responsive-shell": "playwright test tests/browser/responsive-shell.spec.mjs tests/browser/blog.spec.mjs tests/browser/command-palette.spec.mjs", "test:release-pin-contract": "node --test 'tests/release-pin/**/*.test.mjs'", + "test:rendered-links": "node --test 'tests/rendered-links/**/*.test.mjs'", "test:release-pin": "npm run _check:release-pin", - "test": "npm run seq -- test:base test:extra", + "test": "npm run test:base && cross-env OINK_BUILD_LOG=tmp/hugo-build.log npm run test:extra", "update:favicon-goldens": "node tests/favicons/update-goldens.mjs", "update:goldens": "npm run _update:goldens", "update:md-goldens": "node tests/md-output/update-goldens.mjs", @@ -56,8 +56,7 @@ "afdocs": "^0.20.0", "cross-env": "^10.1.0", "jsdom": "^30.0.1", - "npm-check-updates": "^23.1.0", - "rtlcss": "^4.3.0" + "npm-check-updates": "^23.1.0" }, "engines": { "node": ">=24", diff --git a/scripts/check-rendered-links.mjs b/scripts/check-rendered-links.mjs index 493b62f..603d4fa 100644 --- a/scripts/check-rendered-links.mjs +++ b/scripts/check-rendered-links.mjs @@ -23,10 +23,11 @@ function relative(file) { function inScope(file) { const name = relative(file); - return ( - name === 'index.html' || - name === 'zh/index.html' || - /^(?:zh\/)?(?:docs|blog|about|examples|community)\/.*\.html$/.test(name) + if (name.split('/').includes('_print') || /(^|\/)404\.html$/.test(name)) { + return false; + } + return !Array.from(documentFor(file).querySelectorAll('meta[http-equiv]')).some( + (meta) => meta.getAttribute('http-equiv')?.toLowerCase() === 'refresh', ); } diff --git a/static/upgrades/0.15.0.yaml b/static/upgrades/0.15.0.yaml deleted file mode 100644 index abaa195..0000000 --- a/static/upgrades/0.15.0.yaml +++ /dev/null @@ -1,206 +0,0 @@ -# Docsy upgrade manifest — 0.15.0 -# -# Machine-readable companion to the 0.15.0 release note. Lists breaking-style -# review items (with detect rules), optional experimental features, and sanity -# checks so an agent (or human) can determine applicability and jump back to the -# release note, changelog, and user guide. -# -# Status: experimental. Schema is expected to evolve through 0.16.0+; -# see the tracking issue for the planned DRY/data-driven approach. -# -# Authoritative for: detect predicates, version pins, item ids. -# Authoritative narrative remains in the release note and user guide. - -schema: docsy-upgrade-manifest/v0 -from: 0.14.3 -to: 0.15.0 -references: - blog: /blog/docsy/0.15.0/ - changelog: /project/about/changelog/#v0.15.0 - release: https://github.com/google/docsy/releases/v0.15.0 - tag: https://github.com/google/docsy/tree/v0.15.0 -peer: - hugo: - from: '>=0.155.3' - to: '>=0.157.0' - notes: Matches oink.pgsty.com `params.hugoMinVersion` and `hugo-extended`. - node: - to: lts/24 - notes: Later Node versions may work but are not officially supported. - -# Breaking changes — items flagged as breaking in the release note (some also -# introduce new behavior; see blog). Low-risk items note that in title or summary. -# Each item is gated by a `detect` rule where applicable. -breaking: - - id: community-footer-multilingual-paths - title: community and footer link paths (multilingual) - summary: >- - For multilingual sites, community and footer link paths - (`params.links.user`, `params.links.developer`) are now interpreted as - site-relative rather than always resolving under the default language. To - force the default-language target, prefix the path with the default - language code (e.g. `/en/community/` instead of `/community/`). - applies-if: >- - Your site is multilingual and configures `params.links.user` or - `params.links.developer` with site-local paths. - detect: - all: - - kind: config-multilingual - description: Site has more than one language configured. - - kind: grep - pattern: '^\s*(user|developer)\s*[:=]' - paths: [config/, hugo.toml, hugo.yaml, hugo.json] - context: under params.links - actions: - - >- - Review each path and decide whether it should be site-relative or target - the default language. - - >- - For default-language targets, prefix with the default-language code - (e.g. `/en/community/`). - - Recheck generated community/footer links in each language. - references: - blog: /blog/docsy/0.15.0/#community-footer-links - user-guide: - - /docs/content/adding-content/#adding-a-community-page - prs: [2580] - - - id: version-menu-customization - title: version menu markup, classes, and mobile visibility - summary: >- - The version menu uses updated markup and CSS classes - (`.td-navbar__version-menu` wrapper, `.td-version-menu` dropdown, - kind-specific classes per entry) and is no longer hidden on smaller - viewports. - applies-if: >- - Your site configures `params.versions` AND customizes the version menu - partial, navbar partial, or related CSS. - detect: - all: - - kind: config-key-present - path: params.versions - - any: - - kind: file-exists - path: layouts/_partials/navbar-version-selector.html - - kind: file-exists - path: layouts/partials/navbar-version-selector.html - - kind: file-exists - path: layouts/_partials/navbar.html - - kind: file-exists - path: layouts/partials/navbar.html - - kind: grep - pattern: 'td-(navbar|version-menu)' - paths: [assets/] - actions: - - Review custom CSS that targets the version menu dropdown. - - >- - Diff local `navbar-version-selector.html` or `navbar.html` overrides - against the v0.15.0 partials. - - Recheck the navbar on both desktop and mobile viewports. - references: - blog: /blog/docsy/0.15.0/#version-menu - user-guide: - - /docs/advanced/versioning/#adding-a-version-drop-down-menu - - /docs/content/navigation/#version-menu - partials: - - https://github.com/google/docsy/tree/v0.15.0/layouts/_partials - - https://github.com/google/docsy/blob/v0.15.0/layouts/_partials/navbar-version-selector.html - - https://github.com/google/docsy/blob/v0.15.0/layouts/_partials/navbar.html - prs: [2557, 2586] - - - id: card-shortcode-rendering - title: Card shortcode rendering (low-risk) - summary: >- - Markdown in the `header`, `title`, `subtitle`, and `footer` arguments is - rendered with `.Page.RenderString` instead of `markdownify`, so it runs in - the including page's context. Relative Markdown links (including images) - and Markdown render hooks behave like body content—especially useful for - multilingual sites. Shortcodes inside those arguments may execute where - they previously did not under `markdownify`. Breakage should be rare; see - the blog NOTE. - applies-if: >- - Your project uses the `card` shortcode or maintains a custom - `layouts/_shortcodes/card.html` or `layouts/shortcodes/card.html` - override. - detect: - any: - - kind: grep - pattern: '\{\{[<%][[:space:]]*card\b' - paths: [content/, layouts/] - - kind: file-exists - path: layouts/_shortcodes/card.html - - kind: file-exists - path: layouts/shortcodes/card.html - actions: - - Ensure that cards still render as expected. - - >- - Diff any local `card` override against the v0.15.0 theme partial; update - it intentionally if you want page-context resolution, relative paths, or - render hooks in those arguments. - references: - blog: /blog/docsy/0.15.0/#card-shortcode - user-guide: - - /docs/content/shortcodes/#shortcode-card-textual-content - partials: - - https://github.com/google/docsy/blob/v0.15.0/layouts/_shortcodes/card.html - prs: [2565] - -# Fixes and other user-visible changes that do not require upgrade action. -fixes: - - id: community-footer-link-target - title: community and footer link target attribute - summary: >- - Community and footer links now open in a new browser target only for - external links, fixing site-local links that previously behaved like - external links. - references: - blog: /blog/docsy/0.15.0/#community-footer-links - user-guide: - - /docs/content/adding-content/#adding-a-community-page - issues: [2133] - prs: [2576] - -# Experimental features — opt-in; enablement is site-specific. -experimental: - - id: agent-support-phase-1 - title: Agent support (phase 1) - summary: >- - Opt-in Markdown alternate outputs, a _View Markdown_ page-meta link when a - Markdown alternate exists, and generated `llms.txt`. Phased evolution is - tracked in https://github.com/google/docsy/issues/2614. - references: - blog: /blog/docsy/0.15.0/#agent-support - user-guide: - - /docs/advanced/agent-support/ - prs: [2597, 2601, 2605, 2606] - -# Sanity checks to run after upgrading. These are project-agnostic. -sanity: - - run: hugo - description: >- - Build the site locally and surface errors (baseline: release-note sanity - checklist). - - run: hugo --printPathWarnings - description: >- - Same as the blog nested bullet under "Build your site locally": use when - your site is doc-rooted (see blog Doc-rooted sites section). - applies-if: doc-rooted-site - - check: - For multilingual sites, verify community and footer links (see blog - community/footer actions). - - check: - If `params.versions` is configured, check the version menu on desktop and - mobile viewports. - - check: Inspect pages that use the `card` shortcode. - - check: - If agent support is enabled, inspect generated `*.md` pages and - `/llms.txt`. - -# Rollback guidance: pin Docsy to the previous stable release if upgrading -# blocks shipping. See: https://github.com/google/docsy/releases/v0.14.3 -rollback: - docsy: 0.14.3 - hugo: '>=0.155.3' - notes: >- - Re-pin via npm and Hugo module dependencies. See `Upgrade to Docsy 0.12.0` - for the standard package-update procedure, applied in reverse. diff --git a/tests/alt-site/offline-search.test.mjs b/tests/alt-site/offline-search.test.mjs index ca0b292..7e038cd 100644 --- a/tests/alt-site/offline-search.test.mjs +++ b/tests/alt-site/offline-search.test.mjs @@ -7,10 +7,6 @@ import { fileURLToPath } from 'node:url'; import { gzipSync } from 'node:zlib'; const siteDir = fileURLToPath(new URL('../../', import.meta.url)); -const localWorkspace = join(siteDir, 'go.work'); -const moduleWorkspace = - process.env.HUGO_MODULE_WORKSPACE || - (existsSync(localWorkspace) ? localWorkspace : undefined); const rawBudget = 2 * 1024 * 1024; const gzipBudget = 512 * 1024; const requiredFields = [ @@ -57,12 +53,6 @@ for (const [deployment, baseURL, prefix] of [ cwd: siteDir, shell: true, encoding: 'utf8', - env: { - ...process.env, - ...(moduleWorkspace - ? { HUGO_MODULE_WORKSPACE: moduleWorkspace } - : {}), - }, }, ); const output = `${res.stdout ?? ''}${res.stderr ?? ''}`; diff --git a/tests/alt-site/prd4-navigation.test.mjs b/tests/alt-site/prd4-navigation.test.mjs index 4926064..1139757 100644 --- a/tests/alt-site/prd4-navigation.test.mjs +++ b/tests/alt-site/prd4-navigation.test.mjs @@ -8,10 +8,6 @@ import { JSDOM } from 'jsdom'; const siteDir = fileURLToPath(new URL('../../', import.meta.url)); const fixtureDir = join(siteDir, 'tests', 'fixtures', 'prd4-navigation'); -const localWorkspace = join(siteDir, 'go.work'); -const moduleWorkspace = - process.env.HUGO_MODULE_WORKSPACE || - (existsSync(localWorkspace) ? localWorkspace : undefined); function build(name, { baseURL, fixture } = {}) { const outDir = join(siteDir, 'tmp', `prd4-navigation-${name}`); @@ -36,10 +32,6 @@ function build(name, { baseURL, fixture } = {}) { const result = spawnSync('npm', args, { cwd: siteDir, encoding: 'utf8', - env: { - ...process.env, - ...(moduleWorkspace ? { HUGO_MODULE_WORKSPACE: moduleWorkspace } : {}), - }, }); const output = `${result.stdout ?? ''}${result.stderr ?? ''}`; assert.equal(result.status, 0, `Build failed:\n${output}`); diff --git a/tests/alt-site/prd4-runtime.test.mjs b/tests/alt-site/prd4-runtime.test.mjs index d18d68a..8e27a2a 100644 --- a/tests/alt-site/prd4-runtime.test.mjs +++ b/tests/alt-site/prd4-runtime.test.mjs @@ -6,10 +6,6 @@ import { join } from 'node:path'; import { fileURLToPath } from 'node:url'; const siteDir = fileURLToPath(new URL('../../', import.meta.url)); -const localWorkspace = join(siteDir, 'go.work'); -const moduleWorkspace = - process.env.HUGO_MODULE_WORKSPACE || - (existsSync(localWorkspace) ? localWorkspace : undefined); function build(name, extraArgs = []) { const outDir = join(siteDir, 'tmp', `prd4-runtime-${name}`); @@ -33,10 +29,6 @@ function build(name, extraArgs = []) { { cwd: siteDir, encoding: 'utf8', - env: { - ...process.env, - ...(moduleWorkspace ? { HUGO_MODULE_WORKSPACE: moduleWorkspace } : {}), - }, }, ); const output = `${result.stdout ?? ''}${result.stderr ?? ''}`; @@ -81,8 +73,8 @@ function manifest(pageHTML) { return JSON.parse(match[1]); } -test('subpath manifests localize safe commands and preserve action URLs', () => { - const outDir = build('manifest'); +test('default subpath output localizes manifests and keeps print static', () => { + const outDir = build('default'); for (const [relative, language, titles] of [ ['docs/customize/config/index.html', 'en', ['OINK issues']], ['zh/docs/customize/config/index.html', 'zh', ['OINK 问题反馈']], @@ -143,6 +135,20 @@ test('subpath manifests localize safe commands and preserve action URLs', () => ], ); } + + for (const relative of [ + '_print/docs/write/index.html', + 'zh/_print/docs/write/index.html', + ]) { + const page = html(outDir, relative); + assert.doesNotMatch(page, /id="td-shell-search"/); + assert.doesNotMatch(page, /data-td-shell-search-open/); + assert.doesNotMatch(page, /data-index-src=/); + assert.doesNotMatch(page, /lunr(?:\.min)?\.js/); + const bundle = mainBundle(outDir, page, false); + assert.doesNotMatch(bundle, /OinkCommandPalette|OinkPaletteModel/); + assert.doesNotMatch(bundle, /OinkSearchEngine|td-shell-search/); + } }); test('search-disabled pages omit the Palette runtime but retain page actions', () => { @@ -171,20 +177,3 @@ test('search-disabled pages omit the Palette runtime but retain page actions', ( assert.equal(existsSync(join(outDir, 'offline-search-index.en.json')), false); assert.equal(existsSync(join(outDir, 'offline-search-index.zh.json')), false); }); - -test('print output omits all Palette and local-index runtime', () => { - const outDir = build('print'); - for (const relative of [ - '_print/docs/write/index.html', - 'zh/_print/docs/write/index.html', - ]) { - const page = html(outDir, relative); - assert.doesNotMatch(page, /id="td-shell-search"/); - assert.doesNotMatch(page, /data-td-shell-search-open/); - assert.doesNotMatch(page, /data-index-src=/); - assert.doesNotMatch(page, /lunr(?:\.min)?\.js/); - const bundle = mainBundle(outDir, page, false); - assert.doesNotMatch(bundle, /OinkCommandPalette|OinkPaletteModel/); - assert.doesNotMatch(bundle, /OinkSearchEngine|td-shell-search/); - } -}); diff --git a/tests/browser/accessibility.spec.mjs b/tests/browser/accessibility.spec.mjs index 9575e9f..0b5ff41 100644 --- a/tests/browser/accessibility.spec.mjs +++ b/tests/browser/accessibility.spec.mjs @@ -178,6 +178,57 @@ test.describe('WCAG AA contract', () => { } } + test('cached sidebar hydrates its active path without a contrast transition', async ({ + page, + }) => { + await page.setViewportSize({ width: 1200, height: 900 }); + await page.addInitScript(() => { + localStorage.setItem('td-color-theme', 'light'); + }); + await page.goto('/zh/docs/customize/config/', { + waitUntil: 'domcontentloaded', + }); + + const state = await page.evaluate(() => { + const current = document.querySelector('#m-zhdocscustomizeconfig'); + const branch = document.querySelector('#m-zhdocscustomize-children'); + return { + marker: document + .querySelector('#td-sidebar-menu') + ?.hasAttribute('data-td-sidebar-hydrate-active'), + active: + current?.classList.contains('active') && + current.getAttribute('aria-current') === 'page', + activeRow: current + ?.closest('.td-shell-tree__row') + ?.classList.contains('td-shell-active'), + activePath: current + ?.closest('li') + ?.classList.contains('td-active-path'), + open: branch?.classList.contains('td-is-open'), + runningAnimations: + branch + ?.getAnimations({ subtree: true }) + .filter((animation) => animation.playState === 'running').length ?? + -1, + }; + }); + expect(state).toEqual({ + marker: false, + active: true, + activeRow: true, + activePath: true, + open: true, + runningAnimations: 0, + }); + + const { violations } = await scan(page, '#td-sidebar-menu'); + expect( + violations, + describeViolations('/zh/docs/customize/config/', violations), + ).toEqual([]); + }); + for (const { locale, path, theme, viewport } of [ { locale: 'en', diff --git a/tests/browser/blog.spec.mjs b/tests/browser/blog.spec.mjs index 2900fba..6ae8992 100644 --- a/tests/browser/blog.spec.mjs +++ b/tests/browser/blog.spec.mjs @@ -4,6 +4,7 @@ const blogPath = '/zh/blog/'; const blogSections = ['release', 'oink']; const blogArticles = { release: [ + '1.1.0', '1.0.0', '0.8.2', '0.8.1', diff --git a/tests/fixtures/browser.yml b/tests/fixtures/browser.yml index 45b0317..1b784c7 100644 --- a/tests/fixtures/browser.yml +++ b/tests/fixtures/browser.yml @@ -4,3 +4,7 @@ module: target: content - source: tests/fixtures/browser/content target: content/tests + +params: + ui: + sidebar_cache_limit: 1 diff --git a/tests/hugo-build/blog-rss.test.mjs b/tests/hugo-build/blog-rss.test.mjs index 1bbbae2..4344a38 100644 --- a/tests/hugo-build/blog-rss.test.mjs +++ b/tests/hugo-build/blog-rss.test.mjs @@ -1,6 +1,5 @@ import assert from 'node:assert/strict'; import { - existsSync, mkdtempSync, mkdirSync, readFileSync, @@ -15,7 +14,6 @@ import { fileURLToPath } from 'node:url'; const siteDir = fileURLToPath(new URL('../../', import.meta.url)); const sections = ['oink', 'release', 'rss-probe']; -const moduleWorkspace = path.join(siteDir, 'go.work'); function itemLinks(file) { const rss = readFileSync(file, 'utf8'); @@ -139,12 +137,6 @@ The complete article continues here. ], { encoding: 'utf8', - env: { - ...process.env, - ...(existsSync(moduleWorkspace) - ? { HUGO_MODULE_WORKSPACE: moduleWorkspace } - : {}), - }, }, ); assert.equal( diff --git a/tests/hugo-build/content-primitives.test.mjs b/tests/hugo-build/content-primitives.test.mjs index 8871fca..556eb24 100644 --- a/tests/hugo-build/content-primitives.test.mjs +++ b/tests/hugo-build/content-primitives.test.mjs @@ -1,6 +1,5 @@ import assert from 'node:assert/strict'; import { - existsSync, mkdtempSync, mkdirSync, readFileSync, @@ -15,7 +14,6 @@ import { fileURLToPath } from 'node:url'; const siteDir = fileURLToPath(new URL('../../', import.meta.url)); const publicDir = path.join(siteDir, 'public'); -const moduleWorkspace = path.join(siteDir, 'go.work'); function runHugo(contentDir, destination, { panicOnWarning = false } = {}) { const overlayConfig = path.join( @@ -52,12 +50,6 @@ function runHugo(contentDir, destination, { panicOnWarning = false } = {}) { { cwd: siteDir, encoding: 'utf8', - env: { - ...process.env, - ...(existsSync(moduleWorkspace) - ? { HUGO_MODULE_WORKSPACE: moduleWorkspace } - : {}), - }, }, ); } diff --git a/tests/hugo-build/no-deprecations.test.mjs b/tests/hugo-build/no-deprecations.test.mjs index 450163c..f32861f 100644 --- a/tests/hugo-build/no-deprecations.test.mjs +++ b/tests/hugo-build/no-deprecations.test.mjs @@ -1,24 +1,18 @@ import test from 'node:test'; import assert from 'node:assert/strict'; +import { mkdirSync, mkdtempSync, readFileSync, rmSync } from 'node:fs'; import { spawnSync } from 'node:child_process'; -import { - existsSync, - mkdirSync, - mkdtempSync, - readFileSync, - rmSync, -} from 'node:fs'; import { join } from 'node:path'; import { fileURLToPath } from 'node:url'; const siteDir = fileURLToPath(new URL('../../', import.meta.url)); const tmpDir = join(siteDir, 'tmp'); -const moduleWorkspace = join(siteDir, 'go.work'); -// Build the real site to a throwaway destination under the gitignored `tmp/` so -// this probe build never clobbers the published `public/` that `test:base` -// produces and other tests (e.g. published-head) read. function buildSite() { + if (process.env.OINK_BUILD_LOG) { + return readFileSync(join(siteDir, process.env.OINK_BUILD_LOG), 'utf8'); + } + mkdirSync(tmpDir, { recursive: true }); const destDir = mkdtempSync(join(tmpDir, 'no-deprecations-')); try { @@ -26,28 +20,24 @@ function buildSite() { cwd: siteDir, shell: true, encoding: 'utf8', - env: { - ...process.env, - ...(existsSync(moduleWorkspace) - ? { HUGO_MODULE_WORKSPACE: moduleWorkspace } - : {}), - }, }); const output = `${res.stdout ?? ''}${res.stderr ?? ''}`; - const deprecations = output - .split('\n') - .filter((line) => /deprecated/i.test(line)); - return { res, output, deprecations }; + assert.equal(res.status, 0, `Build failed:\n${output}`); + return output; } finally { rmSync(destDir, { recursive: true, force: true }); } } test('site build logs no Hugo deprecation notices', (t) => { - // The `_hugo` script builds with `--logLevel info`, the level at which - // Hugo first reports deprecated API usage. - const { res, output, deprecations } = buildSite(); - assert.equal(res.status, 0, `Build failed:\n${output}`); + // The complete suite explicitly passes the successful `test:base` log. + // Focused runs build a fresh copy instead of trusting an implicit tmp file. + const output = buildSite(); + assert.match(output, /Start building sites/); + assert.match(output, /Total in \d+ ms/); + const deprecations = output + .split('\n') + .filter((line) => /deprecated/i.test(line)); assert.deepEqual(deprecations, [], 'Hugo build logged deprecation notice(s)'); t.diagnostic(`Scanned ${output.split('\n').length} build-log lines`); }); diff --git a/tests/md-output/goldens/blog/index.md b/tests/md-output/goldens/blog/index.md index c66e4b7..664e35e 100644 --- a/tests/md-output/goldens/blog/index.md +++ b/tests/md-output/goldens/blog/index.md @@ -12,9 +12,3 @@ Section pages: - [Oink Release Notes](/blog/release/): Versioned OINK release notes, upgrade guidance, and compatibility notices - [Oink Blog](/blog/oink/): OINK announcements, engineering stories, and implementation notes - ---- - -Backlinks: - -- [Upgrade](/docs/admin/upgrade/) diff --git a/tests/md-output/goldens/blog/release/index.md b/tests/md-output/goldens/blog/release/index.md index 3ba19e4..2f00c8e 100644 --- a/tests/md-output/goldens/blog/release/index.md +++ b/tests/md-output/goldens/blog/release/index.md @@ -10,6 +10,7 @@ LLMS index: [llms.txt](/llms.txt) Section pages: +- [OINK 1.1.0 — Native locales and taxonomy directories](/blog/release/1.1.0/): OINK 1.1.0 completes the native interface for every Docsy locale, turns taxonomy roots into navigable directories, and carries the post-1.0 correctness and bounded-checker fixes without requiring a site migration. - [OINK 1.0.0 — Stable contracts, a supported starter, and a complete public release](/blog/release/1.0.0/): OINK 1.0.0 makes the current knowledge-publishing contracts stable and rolls up every theme change since 0.8.0: Print and Book correctness, pinned Go 1.27 and Hugo 0.165.0 release tooling, a supported Starter, and the public metadata and media needed to enter the wider Hugo ecosystem. - [OINK 0.8.2 — Go 1.26 module compatibility for the Hugo Themes builder](/blog/release/0.8.2/): OINK 0.8.2 lowers the Hugo Module's Go directive from 1.27 to 1.26 so GOTOOLCHAIN=local builders, including the official Hugo Themes pipeline, can import the theme without changing its Hugo floor or rendered behavior. - [OINK 0.8.1 — Stable Print anchors, resilient Book numbers, and release-ready packaging](/blog/release/0.8.1/): OINK 0.8.1 is a focused maintenance release: single-page Print keeps the same heading and footnote anchors as ordinary HTML, long Book titles no longer squeeze their sidebar numbers, and the project presentation now leads new sites through OINK Starter. diff --git a/tests/md-output/goldens/docs/write/index.md b/tests/md-output/goldens/docs/write/index.md index 932ba72..81caa68 100644 --- a/tests/md-output/goldens/docs/write/index.md +++ b/tests/md-output/goldens/docs/write/index.md @@ -59,7 +59,7 @@ Section pages: - [Writing pages](/docs/write/pages/): Creating a documentation page — where the file goes, what the front matter says, why heading anchors are written by hand, how links and images work, and what appears at the end of a page on its own. - [Organizing content](/docs/write/organize/): The directory structure is the sidebar tree — `_index.md` and weight, section index styles, icons and folding, hiding pages, and putting documentation at any path. -- [Page parameters](/docs/write/frontmatter/): The full front matter table — every page key the theme actually reads, grouped by sidebar, shell, search, output, page end, Book, landing and release pages. +- [Page parameters](/docs/write/frontmatter/): The full front matter table — active page keys and retained 1.x compatibility no-ops, grouped by sidebar, shell, search, output, page end, Book, landing and release pages. - [Blog posts](/docs/write/blog/): Setting up a blog section — directory conventions, a post's front matter, featured images, the year-grouped list page, and RSS. - [Books](/docs/write/book/): Turn a directory tree into a book with `type: book`: chapter numbering, numbered figures and tables, cross-references, generated indexes and whole-book print. - [Releases and downloads](/docs/write/releases/): Record versions, tags, archive links, checksums and install commands as local facts, then let release cards, asset tables, download blocks and index pages derive from that one record. diff --git a/tests/md-output/goldens/index.md b/tests/md-output/goldens/index.md index 4be433e..66511fa 100644 --- a/tests/md-output/goldens/index.md +++ b/tests/md-output/goldens/index.md @@ -14,7 +14,6 @@ Section pages: - [OINK Case](/case/): Fifteen real sites show how OINK scales from a two-page utility to a multilingual documentation estate and three books. - [Blog](/blog/): OINK engineering stories, immersive guides, and release notes - [OINK Documentation](/docs/): OINK is a local-first Hugo documentation framework — components stay readable in Markdown, assets ship with the theme, multilingual sites work out of the box, and one source serves readers and agents. -- [Search Results](/search/) --- diff --git a/tests/md-output/goldens/llms.txt b/tests/md-output/goldens/llms.txt index 75ff3b3..1280fda 100644 --- a/tests/md-output/goldens/llms.txt +++ b/tests/md-output/goldens/llms.txt @@ -24,7 +24,7 @@ - [Authoring](http://localhost/docs/write/index.md): Writing documentation pages, blog posts, books, release pages and API references — what a page looks like, and how content is organized. - [Writing pages](http://localhost/docs/write/pages/index.md): Creating a documentation page — where the file goes, what the front matter says, why heading anchors are written by hand, how links and images work, and what appears at the end of a page on its own. - [Organizing content](http://localhost/docs/write/organize/index.md): The directory structure is the sidebar tree — `_index.md` and weight, section index styles, icons and folding, hiding pages, and putting documentation at any path. - - [Page parameters](http://localhost/docs/write/frontmatter/index.md): The full front matter table — every page key the theme actually reads, grouped by sidebar, shell, search, output, page end, Book, landing and release pages. + - [Page parameters](http://localhost/docs/write/frontmatter/index.md): The full front matter table — active page keys and retained 1.x compatibility no-ops, grouped by sidebar, shell, search, output, page end, Book, landing and release pages. - [Blog posts](http://localhost/docs/write/blog/index.md): Setting up a blog section — directory conventions, a post's front matter, featured images, the year-grouped list page, and RSS. - [Books](http://localhost/docs/write/book/index.md): Turn a directory tree into a book with `type: book`: chapter numbering, numbered figures and tables, cross-references, generated indexes and whole-book print. - [Releases and downloads](http://localhost/docs/write/releases/index.md): Record versions, tags, archive links, checksums and install commands as local facts, then let release cards, asset tables, download blocks and index pages derive from that one record. @@ -62,7 +62,7 @@ - [Keyboard navigation](http://localhost/docs/customize/keyboard/index.md): Every single-key shortcut, when each stands down for typing, and how to turn them off per site or per page. - [Languages](http://localhost/docs/customize/i18n/index.md): Add a language, keep translations side by side, configure menus and interface strings per language, and align heading anchors across languages. - [Versions](http://localhost/docs/customize/versions/index.md): Configure the version switcher and the archive banner, and choose how several versions are laid out across domains. - - [Taxonomies](http://localhost/docs/customize/taxonomy/index.md): Give pages a second index that cuts across the directory tree with tags and categories — term pages, filter chips, the rail cloud and the navbar panel are all automatic. + - [Taxonomies](http://localhost/docs/customize/taxonomy/index.md): Give pages a second index that cuts across the directory tree with tags and categories — term pages, term cards, the rail cloud and the navbar panel are all automatic. - [Repository links](http://localhost/docs/customize/repository/index.md): Wire "edit this page", "open an issue" and "view history" to your repository, and show the last-modified line, contributors and the feedback widget at the page end. - [Print](http://localhost/docs/customize/print/index.md): A single page goes to the browser's Cmd/Ctrl+P; a whole section becomes one continuous document through the print output format. - [AI-agent support](http://localhost/docs/customize/agents/index.md): Give every page a `.md` twin, the site root an `llms.txt`, and the reader a way to hand the current page to ChatGPT or Claude. @@ -83,10 +83,8 @@ - [Consumer evidence](http://localhost/docs/design/research/consumer-evidence/index.md): A dated corpus snapshot that shaped OINK's shells, authoring primitives, and deterministic Book migration policy. - [Media convergence](http://localhost/docs/design/proposals/media-convergence/index.md): A draft for the remaining convergence between content images, numbered figures, Landing media, and featured-image selection. - [2026-08-26 review](http://localhost/docs/design/research/2026-08-26-comprehensive-review/index.md): An evidence-based review of OINK's post-v0.7.0 implementation, configuration, outputs, security, tests, performance, bilingual contracts, and real integration site. - - [Agent indexes](http://localhost/docs/design/proposals/agent-indexes/index.md): Optional per-section full-text bundles and a stable navigation JSON, built on OINK's existing Markdown outputs and navigation authority. - [Markdown-first authoring](http://localhost/docs/design/decisions/authoring/index.md): Native Markdown carries common semantics; shortcodes fill real capability gaps, and content models extend shared shells instead of forking them. - [Shell and navigation](http://localhost/docs/design/shell/index.md): Navigation authorities, immersive blog presentation, search, actions, taxonomies, indexes, and page-end composition. - - [Book publication](http://localhost/docs/design/proposals/book-publication/index.md): Move Book semantic lowering and generic EPUB/PDF packaging into OINK without taking publication facts away from consumers. - [Config schema](http://localhost/docs/design/decisions/config-schema/index.md): The editor schemas are projected from the existing configuration authorities; a CI drift gate keeps them from ever becoming a third one. - [Landing pages](http://localhost/docs/design/landing/index.md): The maintainer contract for landing data, the built-in section registry, language resolution, runtime, accessibility, and outputs. - [Migration boundary](http://localhost/docs/design/migration/index.md): The supported source, configuration, and validation boundaries for migration from OINK 0.4 through OINK 1.0.0. diff --git a/tests/md-output/goldens/zh/index.md b/tests/md-output/goldens/zh/index.md index 561c2da..e461e06 100644 --- a/tests/md-output/goldens/zh/index.md +++ b/tests/md-output/goldens/zh/index.md @@ -12,7 +12,6 @@ LLMS 索引: [llms.txt](/zh/llms.txt) - [使用 Oink 创作优美的内容](/zh/book/): 一本实战教程:用 OINK 创作清晰、优美且易于维护的技术内容。 - [OINK 案例](/zh/case/): 十五个真实站点展示 OINK 如何覆盖两页小工具、多语言文档群与三本书籍。 -- [Search Results](/zh/search/) - [博客](/zh/blog/): OINK 工程实践、沉浸式阅读指南与发布注记 - [OINK 文档](/zh/docs/): OINK 是一套本地优先的 Hugo 文档框架:组件在 Markdown 中仍然可读,资源随主题分发,多语言开箱可用,一份内容同时服务读者与 Agent。 diff --git a/tests/release-pin/release-pin.test.mjs b/tests/release-pin/release-pin.test.mjs index 068fb8c..414853e 100644 --- a/tests/release-pin/release-pin.test.mjs +++ b/tests/release-pin/release-pin.test.mjs @@ -16,6 +16,7 @@ const scriptSource = new URL( '../../scripts/check-release-pin.mjs', import.meta.url, ); +const siteDir = new URL('../../', import.meta.url); function runCheck(goMod, config = null) { const fixture = mkdtempSync(join(tmpdir(), 'oink-release-pin-')); @@ -141,3 +142,24 @@ test('CI resolves a public pin without a workspace and fails on warnings', () => 'CI must resolve the public module and verify its pin before building', ); }); + +test('non-browser tests never discover a stray go.work', () => { + for (const relative of [ + 'tests/alt-site/offline-search.test.mjs', + 'tests/alt-site/prd4-navigation.test.mjs', + 'tests/alt-site/prd4-runtime.test.mjs', + 'tests/hugo-build/blog-rss.test.mjs', + 'tests/hugo-build/content-primitives.test.mjs', + 'tests/hugo-build/no-deprecations.test.mjs', + ]) { + const source = readFileSync(new URL(relative, siteDir), 'utf8'); + assert.doesNotMatch(source, /go\.work|HUGO_MODULE_WORKSPACE/, relative); + } + + const makefile = readFileSync(new URL('Makefile', siteDir), 'utf8'); + assert.match( + makefile, + /^check:\n\tHUGO_MODULE_REPLACEMENTS=.* npm test$/m, + 'make check must select the sibling theme explicitly', + ); +}); diff --git a/tests/rendered-links/rendered-links.test.mjs b/tests/rendered-links/rendered-links.test.mjs new file mode 100644 index 0000000..c76477e --- /dev/null +++ b/tests/rendered-links/rendered-links.test.mjs @@ -0,0 +1,102 @@ +import assert from 'node:assert/strict'; +import { spawnSync } from 'node:child_process'; +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import test from 'node:test'; + +const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..'); +const checker = path.join(root, 'scripts/check-rendered-links.mjs'); + +function writeHtml(publicRoot, relative, body) { + const target = path.join(publicRoot, relative); + mkdirSync(path.dirname(target), { recursive: true }); + writeFileSync(target, `${body}`); +} + +function fixture(run) { + const temporary = mkdtempSync(path.join(tmpdir(), 'oink-rendered-links-')); + try { + run(temporary); + } finally { + rmSync(temporary, { recursive: true, force: true }); + } +} + +function check(publicRoot) { + return spawnSync(process.execPath, [checker, publicRoot], { + cwd: root, + encoding: 'utf8', + }); +} + +test('valid Book and Case links pass in both languages', () => { + fixture((publicRoot) => { + writeHtml(publicRoot, 'target/index.html', '

Target

'); + for (const source of [ + 'book/page/index.html', + 'case/page/index.html', + 'zh/book/page/index.html', + 'zh/case/page/index.html', + ]) { + writeHtml(publicRoot, source, 'Valid'); + } + + const result = check(publicRoot); + assert.equal(result.status, 0, result.stderr); + assert.match(result.stdout, /5 pages; 4 internal links; 4 fragments/); + }); +}); + +test('broken Book and Case targets and fragments fail in both languages', () => { + fixture((publicRoot) => { + writeHtml(publicRoot, 'target/index.html', '

Target

'); + const sources = [ + ['book', 'book/broken/index.html'], + ['case', 'case/broken/index.html'], + ['zh-book', 'zh/book/broken/index.html'], + ['zh-case', 'zh/case/broken/index.html'], + ]; + for (const [name, source] of sources) { + writeHtml( + publicRoot, + source, + `Missing` + + `Missing fragment`, + ); + } + + const result = check(publicRoot); + assert.equal(result.status, 1, result.stdout); + for (const [name, source] of sources) { + const route = `/${source.replace(/index\.html$/, '')}`; + assert.match(result.stderr, new RegExp(`missing target: /missing-${name}/`)); + assert.match(result.stderr, new RegExp(`missing fragment: /target/#missing-${name}`)); + assert.match(result.stderr, new RegExp(`from ${route.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`)); + } + }); +}); + +test('intentional opt-outs and generated HTML sources remain excluded', () => { + fixture((publicRoot) => { + writeHtml( + publicRoot, + 'index.html', + '
Ignored subtree
' + + 'Ignored link', + ); + writeHtml(publicRoot, '_print/book/index.html', 'Print'); + writeHtml(publicRoot, 'zh/_print/case/index.html', 'Print'); + writeHtml(publicRoot, '404.html', '404'); + writeHtml( + publicRoot, + 'old/index.html', + 'Alias', + ); + + const result = check(publicRoot); + assert.equal(result.status, 0, result.stderr); + assert.match(result.stdout, /1 pages; 0 internal links; 0 fragments/); + }); +});