这是本节的多页打印视图。 点击此处打印.

返回本页常规视图.

欢迎使用 OINK

产品指南、配置、内容组件与部署参考

v0.16.0

欢迎阅读 OINK v0.16.0 用户指南。本指南涵盖仅依赖 Hugo 的构建方式、本地优先运行时、多语言框架、内容组件、自定义方法与部署方案。

OINK 是什么?

OINK 是一款面向中大型技术文档集的独立 Hugo 主题。它从 Docsy 直接演化而来:既保留 Docsy 成熟的内容模型和文档能力,也提供全新的标准外壳、本地依赖,以及从 PGSTY 生产站点提炼出的可复用组件。

消费站点只需 Hugo Extended 即可完成构建,无需 Node.js、npm、PostCSS、Autoprefixer 或 CDN。Bootstrap、Font Awesome、字体、本地搜索、图表、API 文档运行时和内容组件都随主题提供,并且只会在页面确实需要时加载。

OINK 提供:

  • 响应式文档与博客外壳,包括导航、目录(TOC)、搜索、打印输出、深色模式和无障碍交互;
  • 通用多语言框架,包括译文路由、缺失译文回退、语言权重、RTL 支持和 SEO 备用语言元数据;
  • 本地 Mermaid、KaTeX、Markmap、Swagger UI、Redoc、Asciinema、ECharts 和 Infographic 运行时;
  • 可复用的折叠块、标签页、卡片、导航卡片和文档轮播;
  • 双语 starter、Cloudflare Pages 指南、网络隔离发行包和可审计的 vendor 清单。

OINK 本身 不提供 源码托管,也不会替你部署生成后的站点。你可以把项目放在 GitHub、GitLab、私有 Git 服务或本地仓库中,再通过任意合适的平台发布 Hugo 生成的静态文件。

OINK 适合我吗?

如果文档项目页面众多、内容类型复杂、需要支持多种语言,或对可复现构建和网络隔离有严格要求,OINK 会尤其合适。当多个站点需要共享同一套持续维护的外壳,而不希望复制布局、脚本和短代码时,它也能显著降低维护成本。

如果项目只有一两页内容,也不需要结构化导航,那么 README 或更轻量的 Hugo 主题可能更简单。对于高度应用化的门户,可以使用 OINK 承载文档界面,同时把带有业务语义的组件留在站点层,不必强行纳入主题。

准备开始了吗?

先阅读 OINK 概览了解产品边界,再构建双语 starter。其余用户指南介绍 OINK 沿用的 Docsy 内容模型与兼容 API。

1 - 开始使用

使用 Hugo Extended 构建中英双语 Oink 文档站。

Oink 是一款将完整浏览器运行时随主题提供的 Hugo 主题。消费站点只需 Hugo Extended 即可构建;默认流程不安装 Node.js 软件包、不运行 PostCSS、不依赖 CDN,也不会在构建期间远程下载主题资源。

选择起点

  • Hugo Module(推荐):在已有或新建 Hugo 站点中导入 github.com/pgsty/oink。参阅 Oink 快速开始
  • 项目站点:把独立的 pgsty/oink.pgsty.com 仓库作为完整双语配置与回归参考。
  • 现有 Docsy 站点:按照迁移指南删除公共覆盖和消费端 npm 资源管线,无需重写正文。

安装前提条件

安装 Git、Go 与 Hugo Extended 0.160.1 或更高版本。平台说明和验证命令请参阅开始之前

添加 Oink

在站点根目录运行:

hugo mod init github.com/example/product-docs
hugo mod get github.com/pgsty/oink@THEME_REF

然后在 hugo.yaml 中导入主题:

module:
  imports:
    - path: github.com/pgsty/oink

THEME_REF 固定为发布标签或不可变 commit,并提交 go.modgo.sum

构建契约

所有受支持的模块消费站点都使用相同的预览与构建命令:

hugo server --disableFastRender
hugo --gc --minify

后续步骤

  1. 完成基础配置
  2. 设置代码仓库、版权信息、Logo 和菜单。
  3. page.mdpage.zh.md 的形式并置译文。
  4. 添加并自定义内容
  5. 选择部署目标

1.1 - 使用 Oink 主题

导入 Oink Hugo Module,或查看独立项目站点。

Oink 将消费站点与持续维护的主题分开。站点负责自己的内容、品牌素材、配置和业务组件;主题负责公共外壳、样式、浏览器运行时与可复用短代码。

github.com/pgsty/oink 作为固定版本的 Hugo Module 导入。独立的 pgsty/oink.pgsty.com 仓库通过中英文内容、本地搜索、深色模式、图表、API 文档与组件示例,演示完整生产契约。

熟悉 Hugo 的用户可以从零开始。现有 Docsy 站点应使用迁移指南,不要手工重新创建外壳。

主题源码选项

推荐使用已发布的 github.com/pgsty/oink 模块标签。完整发布归档、固定版本的 Git submodule 或固定 commit 的 clone 也可以使用。选择之前请阅读其他安装方式;生产环境绝不能跟随未固定版本的分支。

构建契约

无论选择哪一种源码方式,都必须能够通过以下命令构建站点:

hugo --gc --minify

项目站点仓库中的 Node 命令只供维护者运行回归测试,不是消费站点的前提条件。

1.1.1 - 开始之前

构建 OINK 站点的前提条件。

消费端唯一必需的工具是 Hugo Extended。是否需要 Git 和 Go,取决于主题源码的获取方式。

安装 Hugo Extended

安装 0.160.1 或更高版本。当前验证基线为 0.164.0。如果发布版本调整了这些数值,应以对应版本的支持矩阵为准。

核对实际选中的二进制文件:

hugo version

输出必须包含 extended;标准版 Hugo 无法编译主题的 SCSS。请根据平台使用 Hugo 官方安装指南,并在本地开发与 CI 中固定同一版本。

按需安装 Git

克隆站点、使用 submodule、保留 .GitInfo 或获取主题 checkout 时需要 Git。运行以下命令验证:

git --version

从已经解压的离线归档构建站点时,Hugo 可以在没有网络的情况下运行;不过仍建议使用版本控制管理源码。

只有 Hugo 模块需要 Go

Hugo 模块命令会使用 Go。如果站点以 Hugo 模块形式导入主题,请安装 Go 并运行:

go version
hugo mod graph

使用固定版本归档、相邻主题目录或 Git submodule 时,站点构建不需要 Go。

不要安装前端工具链

OINK 将 Bootstrap、Font Awesome、LTR 与 RTL CSS、字体、搜索和浏览器运行时作为有版本的本地资源提供。消费站点不需要为主题安装 Node.js、npm、PostCSS、Autoprefixer 或 RTLCSS。

项目站点仓库中的 Node 命令只供维护者使用。消费端的生产命令是:

hugo --gc --minify

检查完整发行物

用于离线或网络隔离环境时,请确认主题归档包含 go.modhugo.yamlassets/layouts/static/i18n/LICENSENOTICEVENDOR.json。进入隔离环境前安装 Hugo Extended,然后在禁用网络的情况下运行同一个构建命令。

后续步骤

1.1.2 - 查看双语项目站点

把独立 Oink 项目站点作为完整参考。

独立的 pgsty/oink.pgsty.com 仓库是完整的双语示例与回归站点。它有意比 starter 更全面:应把它作为参考,然后只保留产品真正需要的内容与配置。

克隆项目站点

Oink 主题公开发布后,可以直接克隆并构建站点:

git clone https://github.com/pgsty/oink.pgsty.com.git product-docs
cd product-docs
hugo --gc --minify

已提交的 go.mod 会固定 github.com/pgsty/oink。本地开发主题时,请把主题克隆为同级目录,并使用 Oink 快速开始记录的 workspace 命令。

运行站点检查

Hugo 本身即可构建站点。Node.js 只用于项目站点的格式、链接、翻译与回归检查:

npm install
npm test

打开生成的站点,分别检查中英文页面。请从具有译文的详情页使用语言切换器,不要只在首页测试。

替换示例身份

编辑 hugo.yamlconfig/ 下的文件,并替换:

  • 站点标题,以及各语言的标题与描述;
  • baseURL
  • 代码仓库与分支 URL;
  • 版权所有者与起始年份;
  • Logo 与品牌素材;
  • 中英文菜单标签。

不要创建 oink.* 参数命名空间。请使用 Hugo 的语言、菜单、模块、输出和 markup 设置,以及主题已经记录的参数。

替换示例内容

把每组译文放在一起:

content/docs/getting-started.md
content/docs/getting-started.zh.md

删除产品不需要的历史与回归内容。只有在页面不再引用后,才删除对应示例资源。

中文标题应显式使用英文页面实际渲染出的 ID:

## Configure search
## 配置搜索 {#configure-search}

将新站点纳入版本控制

发布派生站点前,请修改模块路径、仓库元数据与 remote。继续在 go.mod 中固定 Oink 版本。除非托管工作流有明确要求,否则不要提交生成的 public/ 产物。

后续步骤

1.1.3 - 新建站点:从零开始

在没有前端工具链的情况下创建最小双语 OINK 站点。

独立的双语项目站点是完整参考。需要更小、拥有自身内容结构的站点时,可以采用本流程。

创建站点骨架

运行:

hugo new site --format yaml my-new-site
cd my-new-site

初始化站点模块并固定 Oink:

hugo mod init github.com/example/my-new-site
hugo mod get github.com/pgsty/oink@THEME_REF

添加最低配置

将以下内容保存为 hugo.yaml

title: Product Docs
baseURL: https://docs.example.com/
defaultContentLanguage: en

languages:
  en:
    label: English
    locale: en-US
    weight: 1
    menus:
      main:
        - { name: Docs, pageRef: /docs, weight: 10 }
        - { name: Blog, pageRef: /blog, weight: 20 }
  zh:
    label: 简体中文
    locale: zh-CN
    weight: 2
    menus:
      main:
        - { name: 文档, pageRef: /docs, weight: 10 }
        - { name: 博客, pageRef: /blog, weight: 20 }

markup:
  goldmark:
    renderer:
      unsafe: true
  highlight:
    noClasses: false

params:
  offlineSearch: true
  ui:
    showLightDarkModeMenu: true
    sidebar_menu_foldable: true

module:
  imports:
    - path: github.com/pgsty/oink
  hugoVersion:
    extended: true
    min: 0.160.1

提交 go.modgo.sum。不要添加 npm 挂载项或 PostCSS 管线。

添加双语内容

创建以下文件:

content/
├── _index.md
├── _index.zh.md
├── docs/
│   ├── _index.md
│   ├── _index.zh.md
│   ├── getting-started.md
│   └── getting-started.zh.md
└── blog/
    ├── _index.md
    └── _index.zh.md

每个页面都需要 front matter。例如,content/docs/getting-started.md 可以写成:

---
title: Getting started
weight: 10
---

## Install {#install}

Install the product.

它的 getting-started.zh.md 译文保留显式标题 ID:

---
title: 开始使用
weight: 10
---

## 安装 {#install}

安装产品。

在两个示例中使用相同的显式 ID 不会产生问题,还能直观展示跨语言合同。翻译现有页面时,应从英文渲染 HTML 中复制 ID。

预览与构建

启动开发服务器:

hugo server --disableFastRender

随后单独验证生产构建:

hugo --gc --minify

添加自定义布局前,请检查 /docs//zh/docs/、语言选择器、本地搜索索引和浏览器控制台。

逐步添加功能

先复制 Logo 和品牌素材,再添加代码仓库链接与菜单。只在确实需要的页面中加入图表、API 文档和内容组件;OINK 会按需发布对应的本地运行时。

如果站点需要带业务语义的短代码,请将其保留在站点自己的 layouts/_shortcodes/ 下。只有接口已经摆脱站点假设,并且能被多个站点复用后,才应移入主题。

后续步骤

1.2 - 其他安装方式

使用 OINK 归档、Git checkout 或 Hugo Module。

推荐安装方式是使用 github.com/pgsty/oink Hugo Module。以下选项只改变 Hugo 获取同一份主题源码的方式,不会改变内容,也不会改变 Hugo-only 构建命令。

前提条件

所有方式都需要 Hugo Extended 0.160.1 或更高版本。Git 方式需要 Git,Hugo 模块需要 Go。消费站点采用任何一种方式都不需要 Node.js、npm、PostCSS 或 Autoprefixer。

选项 1:完整发布归档

完整离线归档包含主题、本地浏览器运行时、字体、许可证、NOTICE、vendor 清单和 checksum。它是网络隔离构建的首选输入,也是保留准确发行物最简单的方式。

把主题解压到站点的 themes/ 目录:

site/
├── hugo.yaml
└── themes/
    └── oink/

配置如下:

theme: oink

解压前先校验归档 checksum。只能使用明确发布版本附带的归档,不要把本地组装文件表述为已经发布的发行物。

选项 2:Git submodule

submodule 会在站点仓库中记录准确的 OINK 仓库 commit:

git submodule add https://github.com/pgsty/oink.git themes/oink
git -C themes/oink fetch --tags
git -C themes/oink checkout THEME_REF
git add .gitmodules themes/oink
git commit -m "Add OINK theme at THEME_REF"

配置嵌套主题路径:

theme: oink

CI 必须在运行 Hugo 前初始化 submodule。请将 THEME_REF 固定为发布标签或不可变的 commit,不要让生产环境跟随 main

选项 3:固定版本的 Git 克隆

如果托管平台要求构建输入包含完整主题树,或者站点需要随仓库提供已经评审的副本,可以使用克隆:

git clone https://github.com/pgsty/oink.git themes/oink
git -C themes/oink checkout THEME_REF

同样设置 theme: oink。请记录最终解析出的 commit,以及恢复克隆的流程。如果把这些文件提交到站点仓库,必须保留 OINK 的 LICENSENOTICEVENDOR.json

OINK 不以 npm 包形式发行。现有 Docsy npm 用户应遵循 npm 迁移指南

选项 4:Hugo Module

把公开模块固定到发布标签或不可变 commit:

hugo mod get github.com/pgsty/oink@THEME_REF
hugo mod tidy

hugo.yaml 中导入:

module:
  imports:
    - path: github.com/pgsty/oink

本地开发主题时,请使用被忽略的 Go workspace,把站点模块与同级 OINK checkout 一起加入。

预览与验证

所有源码方式都使用相同命令:

hugo server --disableFastRender
hugo --gc --minify

请验证:全新生产构建能够在没有 node_modules 目录的情况下完成;本地资源能在配置的 baseURL 下正确解析;中英文页面与搜索索引都已经生成。

版本变更和覆盖审查请参阅更新 OINK

1.3 - 在容器中运行 OINK

使用 Hugo Extended 容器构建和预览 OINK 站点。

容器并非必需:OINK 本身只需要 Hugo Extended。如果团队希望固定工具镜像,或不想在开发者工作站上安装 Hugo,可以选择容器方式。

创建 Hugo 镜像

下面的 Dockerfile 从发布包安装当前验证过的 Hugo Extended 版本。请让该版本始终与主题支持矩阵保持一致。

FROM debian:bookworm-slim

ARG HUGO_VERSION=0.164.0
ARG TARGETARCH

RUN apt-get update \
    && apt-get install -y --no-install-recommends ca-certificates curl git \
    && curl -L -o /tmp/hugo.deb \
      "https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-${TARGETARCH}.deb" \
    && apt-get install -y /tmp/hugo.deb \
    && rm -rf /var/lib/apt/lists/* /tmp/hugo.deb

WORKDIR /src
EXPOSE 1313
ENTRYPOINT ["hugo"]
CMD ["server", "--bind", "0.0.0.0", "--disableFastRender"]

在站点根目录构建镜像:

docker build -t oink-hugo .

镜像构建过程会下载 Hugo。在网络隔离环境中,请预先镜像基础镜像和 Hugo 软件包,或者将 OINK 完整离线发行包与获准使用的内部镜像组合使用。

预览站点

挂载完整站点源码,包括相邻存放或随站点提供的主题:

docker run --rm -it \
  -p 1313:1313 \
  -v "$PWD:/src" \
  oink-hugo

打开 http://localhost:1313/。宿主机上的变更会被容器中的 Hugo 实时重载进程检测到。

执行生产构建

覆盖默认的 server 命令:

docker run --rm \
  -v "$PWD:/src" \
  oink-hugo --gc --minify

生成的站点会写入挂载源码目录中的 public/。请确保容器用户对该目录具有写权限;在共享环境中,应按本地策略映射用户 ID 或修正文件所有权。

这个镜像不需要 Node.js、npm、PostCSS,也不应包含远程浏览器资源步骤。

1.4 - 站点基础配置

配置 OINK 站点、语言、导航与本地功能。

Hugo 从 hugo.yamlhugo.tomlhugo.json 读取站点级设置。Oink 项目站点使用 YAML,因为多语言菜单和主题选项更便于浏览与评审。

最低配置

下面的节选展示了 Hugo Module 的关键结构。

title: Product Documentation
baseURL: https://docs.example.com/
defaultContentLanguage: en

languages:
  en:
    label: English
    locale: en-US
    weight: 1
    title: Product Documentation
    menus:
      main:
        - name: Docs
          pageRef: /docs
          weight: 10
        - name: Blog
          pageRef: /blog
          weight: 20
  zh:
    label: 简体中文
    locale: zh-CN
    weight: 2
    title: 产品文档
    menus:
      main:
        - name: 文档
          pageRef: /docs
          weight: 10
        - name: 博客
          pageRef: /blog
          weight: 20

markup:
  goldmark:
    renderer:
      unsafe: true
  highlight:
    noClasses: false

params:
  offlineSearch: true
  github_repo: https://github.com/example/product-docs
  github_branch: main
  copyright:
    authors: Example Authors
    from_year: 2026
  ui:
    showLightDarkModeMenu: true
    sidebar_menu_foldable: true
    breadcrumb_disable: false

module:
  imports:
    - path: github.com/pgsty/oink
  hugoVersion:
    extended: true
    min: 0.160.1

英文权重为 1,也是默认语言;简体中文权重为 2;其他语言依次排列。直接点击语言按钮时会按此顺序切换,完整的悬停菜单也采用相同顺序。

内容译文

将译文并置存放:

content/
├── _index.md
├── _index.zh.md
├── docs/
│   ├── _index.md
│   ├── _index.zh.md
│   ├── install.md
│   └── install.zh.md
└── blog/
    ├── release.md
    └── release.zh.md

会影响路由的元数据应保持一致。标题、描述、菜单标签、摘要、标签、图片替代文字和短代码中的可见字符串都需要翻译。每个中文标题都应显式使用英文页面实际渲染出的标题 ID,确保不同语言中的 URL 片段保持稳定。

本地搜索与浏览器资源

offlineSearch: true 会启用主题的同源 Lunr 索引和 CJK 回退。索引按语言分别生成。除非站点明确接受相应的网络依赖,否则不要配置公共搜索服务。

Mermaid、KaTeX、Markmap、Swagger UI、Redoc、Asciinema、ECharts 和 Infographic 都由主题本地提供,并按页面加载。PlantUML 和 Draw.io 属于依赖服务的例外:请显式配置获准使用的端点,否则保持禁用。

在站点层设置 title、各语言标题、params.logo、代码仓库 URL、版权和菜单。OINK 不新增 oink.* 配置树,而是沿用 Hugo 与兼容 Docsy 参数的位置。

代码仓库元数据用于在内容页提供编辑、查看、提交问题和内容年龄信息。请确保 github_repogithub_project_repogithub_branchgithub_subdir 同源码布局一致。

生产默认值

  • 使用真实的生产 baseURL,包括可能存在的子路径。
  • 除非属于明确的产品决策,否则关闭在线分析、评论、Google CSE、Algolia 和远程嵌入。
  • 在 CI 中固定 Hugo Extended 和主题版本。
  • 使用 hugo --gc --minify 作为生产构建命令。
  • 重新分发归档时保留 LICENSENOTICE 和 vendor 清单。

可构建的完整参考配置请查看项目站点的 hugo.yaml

1.5 - 故障排查与已知问题

诊断 OINK 安装、构建、语言、搜索与平台问题。

请从一次干净的生产构建开始诊断:

hugo --gc --minify --logLevel info

消费端命令不应调用 npm、PostCSS、Autoprefixer,也不应下载主题浏览器资源。

构建问题

Hugo 不是 Extended 版本或版本过旧

运行 hugo version。输出必须包含 extended,版本也不得低于 0.160.1。如果 shell、编辑器、CI runner 或容器仍然选中了旧二进制文件,请检查它的 PATH 和固定工具配置,不要盲目再安装一份。

找不到主题

module "github.com/pgsty/oink" not found 一类错误表示 Hugo 无法解析配置中的主题。请按所选安装方式检查:

  • 对于 Git checkout,主题名称必须与目录路径一致;
  • 对于 Hugo 模块,运行 hugo mod graph,并检查 go.modgo.sum,以及所有 Hugo workspace 或 replacement;
  • 对于 CI checkout,请在运行 Hugo 前初始化固定版本的 submodule,或恢复完整发布归档。

缺少本地浏览器资源

如果缺少 Bootstrap、Font Awesome、Lunr、Mermaid 或其他 OINK 资源,不要通过添加 CDN URL 来掩盖问题。请确认发行物完整,并包含 assets/third_party/assets/js/third_party/static/webfonts/VENDOR.json。如果确有文件缺失,请重新解压或获取同一个固定版本。

译文页面没有出现

逐项检查以下四个条件:

  1. hugo.yaml 中存在 languages.zh,并且设置了权重。
  2. 文件名是 page.zh.md,其中 zh 必须小写。
  3. 译文 front matter 没有设置 draft: true,日期也不在未来。
  4. 除非有意采用不同路由,否则会影响路由的元数据应与源文件一致。

当 Hugo 能找到页面译文时,语言选择器会直接链接过去;否则会按设计回退到目标语言首页。

翻译后的标题文字通常会生成不同的自动 ID。请在译文标题中显式加入英文渲染 ID:

## 安装 {#installation}

不要推测包含短代码或内联 HTML 的标题 ID。请检查英文渲染结果,再比较中英文标题 ID 列表。

搜索问题

启用 offlineSearch: true 后,每种语言都会生成自己的搜索索引。请确认输出中存在 offline-search-index.en.jsonoffline-search-index.zh.json,并检查浏览器是否从站点 base URL 请求这些文件。子路径部署中,错误的 baseURL 是索引缺失的常见原因。

中文分词使用主题的 CJK 回退。如果搜索结果为空,应先确认中文页面内容确实进入中文索引,而不是立即修改分词器。

平台问题

macOS 报告打开文件过多

大型实时预览内容树可能超过 shell 的打开文件数限制。通过 ulimit -n 查看当前限制;如果本地策略允许,可以为当前 shell 临时提高限制。在修改整台机器的限制之前,应优先从监视树中排除生成目录和无关目录。

Windows Subsystem for Linux 速度慢或遗漏变更

请让 Hugo 处理 Linux 文件系统中的路径,而不是 Windows 挂载路径。跨文件系统的通知与权限行为可能让实时重载变慢或不可靠。

诊断清单

  • 使用固定的准确 Hugo Extended 版本复现问题。
  • 通过项目规定的清理命令删除陈旧的 public/resources/ 产物,再重新构建。
  • 比较开发环境与生产环境的配置层。
  • 关注第一条构建错误,而不只是最后出现的级联报错。
  • 使用最小页面区分主题行为与站点覆盖。
  • 分小组逐步重新启用站点覆盖和内容组件。
  • 检查故障页面的浏览器控制台和网络日志。

2 - 内容与自定义

如何为 Docsy 站点添加内容并进行自定义。

2.1 - Logo 与图片

在项目中添加和自定义 Logo、图标与图片。

默认情况下,OINK 会在顶部导航栏起始位置(即最左侧)显示站点 Logo。把项目的 SVG Logo 放在 assets/icons/logo.svg,即可覆盖主题中的默认 Logo。

如果不希望顶部导航栏显示 Logo,请在项目配置中把站点参数 navbar_logo 设为 false

[params.ui]
navbar_logo = false
params:
  ui:
    navbar_logo: false
{
  "params": {
    "ui": {
      "navbar_logo": false
    }
  }
}

Logo 样式的更多信息请参阅设置项目 Logo 与名称的样式

使用图标

OINK 默认包含免费版 Font Awesome 图标,其中也包括 GitHub、Stack Overflow 等站点的 Logo。可以在 Font Awesome 文档中查看全部可用图标、每个图标加入的 Font Awesome 版本,以及它是否对免费版用户开放。OINK 随发行物内置已经固定版本的字体与图标;确切版本记录在 theme/VENDOR.json 和发布说明中。

你可以把 Font Awesome 图标添加到顶部导航栏侧栏导航或正文中的任意位置。

添加 favicon

主题本身不提供 favicon 文件,但会 发现并链接 采用约定名称的图标。请生成 favicon 文件,然后放入项目的 static 目录,使其发布到站点根目录——浏览器会在那里探测这些文件。OINK 会按以下顺序,为找到的文件在每个页面的 <head> 中添加 <link> 元素:

文件 链接
favicon.ico rel="icon"1
favicon.svg rel="icon",并带有 type="image/svg+xml"
favicon-NxN.png rel="icon",并带有 type="image/png" sizes="NxN"
apple-touch-icon.png rel="apple-touch-icon"(隐含尺寸为 180×180)
apple-touch-icon-NxN.png rel="apple-touch-icon",并带有 sizes="NxN"

如果提供了上述任意方形尺寸变体,OINK 会按尺寸升序添加。

一个现代 favicon.ico 加上 SVG 和 apple-touch-icon.png,足以覆盖常见浏览器与平台的 favicon 需求。如需更多能力:

生成 favicon

还没有 favicon?可以通过 favicon.ioRealFaviconGenerator 等在线工具,从单张图片生成 favicon。

如果已经有源 SVG 并安装了 ImageMagick,OINK 也保留 gen-favicons 辅助工具。把源 SVG 保存为 static/favicon.svg——主题会直接链接它——再在同一位置生成栅格图标。从站点项目根目录运行命令。

对于上游 Docsy npm 包安装:

npx --no-install gen-favicons static/favicon.svg static/

其他安装方式运行:

node OINK_THEME_DIR/scripts/gen-favicons/cli.mjs static/favicon.svg static/

OINK_THEME_DIR 替换为实际主题目录。使用 Git submodule 时通常是 themes/oink/theme;本仓库中则是 theme/。运行带 --help 的命令可以查看尺寸与其他选项。

该辅助工具只用于一次性生成素材,并不是站点构建依赖。消费端生产构建仍然只运行 Hugo;也可以使用其他获准的图片工具生成同名文件。

添加图片

落地页

OINK 的 blocks/cover 短代码可以方便地为落地页添加封面图(也称为 Hero 图片)。短代码会在落地页的页面包中查找文件名包含 background 的图片。

例如,示例站点的落地页 content/en/_index.md 使用同一目录下的图片 content/en/featured-background.jpg;可在 GitHub 上查看 content/en 文件夹。

通过区块的 height 参数设置封面容器及其图片的首选显示高度。要铺满视口高度,请使用 full,并配合 td-below-navbar 辅助类把封面放在顶部导航栏下方:

{{% blocks/cover
  title="Welcome to OINK!"
  image_anchor="top"
  height="full td-below-navbar"
%}}
...
{{% /blocks/cover %}}

要使用较矮的图片,可以选择 minmedmax,或表示图片自然高度的 auto

{{% blocks/cover
  title="About the OINK Example"
  image_anchor="bottom"
  height="min td-below-navbar"
%}}
...
{{% /blocks/cover %}}

其他页面

要在其他页面中添加行内图片,可以使用 imgproc 短代码。也可以直接使用普通 Markdown 或 HTML 图片,并将图片文件放入项目的 static 目录。该目录的更多信息请参阅添加静态内容


  1. .ico 链接不声明 sizes:文件本身会描述所含帧尺寸(浏览器会读取),在链接中声明尺寸只会带来与真实文件不一致的风险。同时提供 favicon.svg 时,支持 SVG favicon 的浏览器(绝大多数现代浏览器)会优先使用它,.ico 则作为回退。 ↩︎

2.2 - 代码仓库链接与页面信息

帮助读者查看、编辑页面源码,并针对源码报告问题。

OINK 的文档与博客布局可以显示指向当前页面源码仓库的链接:

  • 查看页面源码:打开源文件。
  • 编辑本页:打开可编辑的源码视图。
  • 创建子页面:在当前页面下新建文件,并可使用站点的 assets/stubs/new-page-template.md 模板。
  • 创建文档 issue:携带页面上下文,在文档仓库中创建 issue。
  • 创建项目 issue:可选地把 issue 提交到另一个产品仓库。

内置 URL 模式面向 GitHub 风格的代码仓库。如果使用其他兼容托管服务,请逐项验证;如果 URL 结构不同,应覆盖相应 partial。

典型站点配置如下:

params:
  github_repo: https://github.com/OWNER/DOCS
  github_project_repo: https://github.com/OWNER/PRODUCT
  github_branch: main
  github_subdir: site

当内容来自多个代码仓库时,可以在全局、单种语言、分区 cascade 或页面 front matter 中设置这些值。

github_repo

文档源码仓库 URL。它用于生成查看、编辑、创建子页面和创建文档 issue 链接:

params:
  github_repo: https://github.com/pgsty/oink

省略后将隐藏从仓库派生的页面操作。如果页面源码实际位于消费站点,不要把它错误地指向主题仓库。

github_subdir(可选)

设置从仓库根目录到 Hugo 站点源码的路径。本项目把站点存放在 oink.pgsty.com 中:

params:
  github_subdir: oink.pgsty.com

该值是仓库内路径,不是本地绝对路径;除非内容目录就是实际站点根目录,否则也不能直接填写内容目录。

github_project_repo(可选)

设置另一个产品仓库,以显示 创建项目 issue

params:
  github_project_repo: https://github.com/OWNER/PRODUCT

内容缺陷应提交到文档仓库,页面讨论的产品行为应提交到产品仓库。如果读者无法清楚理解两者区别,应省略第二条链接。

github_branch(可选)

设置源码与编辑 URL 使用的分支:

params:
  github_branch: main

通常应填写站点源码分支。它不一定是部署分支、自动生成的 Pages 分支或主题修订版本。

path_base_for_github_subdir(可选)

如果某棵内容子树从另一个仓库挂载,请使用分区 cascade。系统会先移除 path base,再把剩余内容路径附加到 github_subdir

---
title: Imported reference
cascade:
  github_repo: https://github.com/OWNER/UPSTREAM
  github_project_repo: https://github.com/OWNER/UPSTREAM
  github_subdir: docs
  path_base_for_github_subdir: content/reference
---

对于源页面 content/reference/api/client.md,以上配置会把仓库路径映射为 docs/api/client.md

path_base_for_github_subdir 可以是正则表达式。按语言目录组织内容的站点可以写成:

path_base_for_github_subdir: content/\w+/reference

OINK 将 .md.zh.md 并置保存,通常两种语言使用相同静态 base,因此表达式中不需要语言目录。

如果源文件使用不同名称,请使用 fromto 映射。下面把分区 _index.md 映射到上游 README.md

path_base_for_github_subdir:
  from: content/reference/(.*?)/_index.md
  to: $1/README.md

请分别从叶子页、分区页和两种语言页面测试查看与编辑链接。正则表达式移除路径过多时,可能生成看似合理却指向错误位置的仓库 URL。

github_url(可选)

旧页面可以在 front matter 中设置完整的自定义编辑 URL:

---
title: Imported page
github_url: https://github.com/OWNER/UPSTREAM/edit/main/README.md
---

使用该值的页面只显示 编辑本页。当目标与 GitHub 不兼容时,更适合使用站点专属模板覆盖。

每种操作都有稳定的 CSS 类:

链接 CSS 类
查看页面源码 .td-page-meta__view
编辑本页 .td-page-meta__edit
创建子页面 .td-page-meta__child
创建文档 issue .td-page-meta__issue
创建项目 issue .td-page-meta__project-issue

当目标不支持某项操作时,可以在 assets/scss/_styles_project.scss 中将其隐藏:

.td-page-meta__child {
  display: none;
}

对于全局不可用的目标,应优先从配置中省略。CSS 隐藏适合选择性策略,但不能让错误链接变正确。

页面最后修改信息

启用 Hugo Git 信息并配置源码仓库:

enableGitInfo: true
params:
  github_repo: https://github.com/OWNER/DOCS

OINK 随后可以在文档与博客页显示最后一次提交的日期、主题、hash 和源码链接。CI 必须为当前文件获取足够的 Git 历史;浅克隆可能导致元数据缺失或产生误导。

如果要在特定站点或分区隐藏提示,可以覆盖样式或负责页面元信息的 partial。当 Git 历史不可用时,不要把构建时间冒充为“最后修改”时间。

2.3 - 打印支持

让整节文档更便于打印。

大多数浏览器都能很好地打印单篇文档,因为页面样式会从打印输出中移除导航外壳。

有些站点适合启用“打印整节”功能(本用户指南就是如此)。选择后,系统会把当前顶层分区(本页所在的“内容与自定义”等)连同全部子页面和子分区渲染为适合打印的格式,并附上该分区的完整目录。

要启用此功能,请在站点的 hugo.tomlhugo.yamlhugo.json 中,为 section 类型添加 print 输出格式:

[outputs]
section = [ "HTML", "RSS", "print" ]
outputs:
  section:
    - HTML
    - RSS
    - print
{
  "outputs": {
    "section": [
      "HTML",
      "RSS",
      "print"
    ]
  }
}

随后,站点右侧导航中会显示“打印整节”链接。

进一步自定义

禁用目录

如果不希望可打印视图显示目录,可以在页面 front matter,或者 hugo.tomlhugo.yamlhugo.json 中将 disable_toc 参数设为 true

+++

disable_toc = true

+++
---

disable_toc: true

---
{
  …,
  "disable_toc": true,
  
}
[params.print]
disable_toc = true
params:
  print:
    disable_toc: true
{
  "params": {
    "print": {
      "disable_toc": true
    }
  }
}

布局钩子

主题定义了多种布局 partial 和钩子,可用来定制打印格式。这些文件位于 layouts/_partials/print

钩子可以按内容类型定义。例如,如果希望 blog 页与 docs 页使用不同的标题布局,可以创建 layouts/_partials/print/page-heading-<type>.html,例如 page-heading-blog.html。默认实现使用页面标题和描述作为页首标题。

同理,可以通过创建 layouts/_partials/print/content-<type>.html 来定制每个页面的正文格式。

2.4 - 导航与菜单

配置 OINK 导航、语言切换、侧边栏和页面大纲。

OINK 把 Hugo 的内容树和菜单模型组织成一套文档工作台:全局导航栏、可折叠且可调整宽度的分区侧边栏,以及可折叠的页面大纲。同一套结构适用于英文、中文和从右向左书写的语言。

全局导航栏由 Hugo 的 main 菜单与 OINK 自动生成的控件组成。根据配置和页面类型,其中可以显示版本、语言、颜色模式与搜索控件。

添加 main 菜单项

可以在页面 Front Matter 中定义菜单项:

---
title: 文档
linkTitle: 文档
menu:
  main:
    weight: 20
    pre: <i class="fa-solid fa-book" aria-hidden="true"></i>
---

权重越小,位置越靠前。站点级外部链接写法类似:

menus:
  main:
    - name: GitHub
      identifier: github
      weight: 50
      url: https://github.com/pgsty/oink
      pre: <i class="fa-brands fa-github" aria-hidden="true"></i>

需要在配置中引用菜单项时,应为其设置 identifiernamelinkTitle 可以按语言翻译,但标识符必须稳定。

版本菜单

配置 params.versions 后会显示版本选择器。条目可以表示标题、分隔线、正式版本、开发版本或站点变体:

params:
  version: v1.0.0
  version_menu: v1.0.0
  version_menu_pagelinks: true
  versions:
    - version: v1.1.0-dev
      kind: next
      url: https://next.example.org/
    - version: v1.0.0
      kind: latest
      url: https://docs.example.org/

version 标识已发布的站点变体,不一定是 Git 引用。安装命令等必须使用可解析标签的内容,应改用项目显式定义的发布引用参数。启用页面链接后,OINK 会先尝试目标版本中的同一路径,找不到时再使用条目配置的 URL。

语言菜单

OINK 根据 Hugo 的 AllTranslations 构造语言目标。当前页面缺少某种语言译文时,会链接到该语言首页,而不是生成损坏的 URL。只配置一种语言时不显示控件;配置两种或更多语言时,直接点击会按 weight 顺序切换到下一种语言,悬停半秒或聚焦控件则打开完整菜单。当前站点按英文、简体中文的顺序循环。目标链接包含 langhreflang、locale 与文字方向属性。

浅色/深色主题菜单

启用颜色模式后,导航栏与文档工作台会显示主题控件。详见浅色/深色模式菜单

启用离线搜索后,文档工作台会使用本地搜索对话框。侧边栏按钮会显示当前平台快捷键(Command/Ctrl+K)。在线搜索集成仍可通过显式配置启用。详见搜索

为导航栏添加图标

在菜单项中使用 prepost。OINK 已在本地提供免费版 Font Awesome 资源:

menus:
  main:
    - name: 源码
      identifier: source
      url: https://github.com/pgsty/oink
      weight: 50
      pre: <i class="fa-brands fa-github" aria-hidden="true"></i>
      post: <span class="visually-hidden">(外部链接)</span>

装饰性图标需要设置 aria-hidden="true";链接本身必须保留有意义的文字或无障碍标签。在新标签页打开的外部链接必须使用 rel="noopener"

侧边导航

文档页与博客页的左侧面板由内容层级自动生成。OINK 按 weight 排序,并在存在 linkTitle 时用它作为标签。分区来自 _index.md;翻译后的分区需要配套 _index.zh.md,才能正确本地化导航元数据。

从侧边栏隐藏页面:

toc_hide: true

从分区落地页摘要中隐藏页面则使用 hide_summary: true。只有页面确实不应出现在这两个发现入口中时,才同时设置二者。

侧边导航选项

常用控制项如下:

params:
  ui:
    sidebar_menu_compact: true
    sidebar_menu_foldable: true
    sidebar_menu_truncate: 128
    sidebar_cache_limit: 2000
    sidebar_search_disable: false
    sidebar_width_min: 220
    sidebar_width_max: 480
    sidebar_item_overflow: ellipsis
  • sidebar_menu_compact 只显示当前分支和附近条目;
  • sidebar_menu_foldable 允许读者展开或折叠分区;
  • sidebar_menu_truncate 限制条目数,数值过小时会发出构建警告;
  • sidebar_cache_limit 在站点规模超过阈值后启用共享导航标记;
  • sidebar_width_minsidebar_width_max 限制桌面端拖拽调整的宽度;
  • sidebar_item_overflow 默认为 ellipsis,长标签需要换行时改用 wrap

折叠状态、宽度和滚动位置保存在读者本地。移动端会转换为带遮罩层和安全焦点控件的可关闭抽屉。

为侧边导航添加图标

在页面 Front Matter 中设置 icon

---
title: 运维
icon: fa-solid fa-screwdriver-wrench
---

同级条目的图标用法应保持一致。图标只是辅助线索,不能取代文字标签。

在所需位置创建占位页面:

---
title: API 状态
weight: 90
manualLink: https://status.example.org/
manualLinkTitle: 实时服务状态
manualLinkTarget: _blank
---

内部内容引用应使用 manualLinkRelref 而不是 manualLink;Hugo 无法解析目标时会令构建失败。OINK 会为新标签页链接补充 noopener。由于 Hugo 仍会为占位文件生成页面,正文应简短说明实际去向。

启用根侧边栏:

params:
  ui:
    sidebar_root_enabled: true
    sidebar_root_menu: true

然后在分区的 _index.md 中设置:

---
title: API Reference v2
sidebar_root_for: self
sidebar_root_link_self: true
---

self 会把该根节点应用于分区索引及其后代;children 会把索引留在父级树中,只限制其后代。可选的根菜单用于在不同根节点之间切换。根分区可以嵌套,但冗余或无效取值会触发构建警告。

页面目录

Hugo 根据 Markdown 标题生成右侧页面大纲。OINK 将其渲染为固定文档面板,并放置快捷链接、语言与主题控件、仓库元数据和分类标签。读者可以折叠该面板,状态保存在本地。

由 Markdown 短代码({{% ... %}})输出的标题会进入 Hugo 目录;仅由标准短代码({{< ... >}})输出的标题通常不会进入。因此,只要条件允许,内容结构都应保留在 Markdown 中。

目录定制

在单个页面隐藏大纲:

notoc: true

配置 Hugo 收录的标题层级:

markup:
  tableOfContents:
    startLevel: 2
    endLevel: 4
    ordered: false

toc_on_this_page 等标签在站点 i18n 资源包中翻译。自定义 CSS 调整大纲轨道或固定面板尺寸后,需要测试活动项跟踪、缩放、键盘焦点,以及完全没有标题的页面。

使用 ScrollSpy 跟踪目录活动项

OINK 使用本地 Bootstrap ScrollSpy 补丁与 IntersectionObserver 跟踪活动标题。工作台会绘制连续轨道、活动区段和位置标记。为某个页面关闭跟踪:

params:
  ui:
    scrollSpy:
      disable: true

旧版 ScrollSpy 配置也接受全局 rootMargin。它会改变条目进入活动状态的时机,应在短分区、长分区和直接片段导航中分别测试。

ScrollSpy 高级定制

优先使用配置与项目 CSS。覆盖 ScrollSpy 属性 Partial 或 docs-shell.js 会形成实现级分支;必须增加浏览器 Fixture,覆盖哈希更新、前进/后退导航、尺寸变化、减少动态效果模式,以及存在重复或缺失 ID 的页面。

普通内容页上方和分类结果中会显示面包屑。全局关闭方式如下:

params:
  ui:
    breadcrumb_disable: true
    taxonomy_breadcrumb_disable: true

页面或分区 cascade 也可以设置 ui.breadcrumb_disable。面包屑标签来自本地化页面标题,而且必须与侧边栏遵循同一逻辑层级。

使用方站点可以启用 OINK 标题渲染钩子:

{{ partial "td/render-heading.html" . }}

生成的 .td-heading-self-link 控件默认使用 #。它在触控设备上始终可见,在指针设备上则于悬停或聚焦时出现。链接必须支持键盘访问,并保留足以避开固定导航的滚动偏移。

标题别名与页内目标

修改标题可能破坏外部片段链接,因此标题 ID 应按公开路由对待。需要重命名 ID 时,应保留旧 ID 的空锚点,并显式写入新 ID:

## Quickstart <a id="get-started"></a> {#quickstart}

别名和其他页内目标应使用空的 <a id="..."></a>。不要仅为片段目标使用 span。ID 必须唯一、稳定,在可行时使用 ASCII,并在各语言版本中保持一致。

快速开始

这个真实标题演示了 #get-started#quickstart 都能到达同一位置。译文标题应显式写入英文页面渲染后的 ID,不要依赖不同语言各自生成的自动 slug。

实现说明

  • 文档为固定界面设置全局滚动偏移;
  • 内置块目标使用 td-anchor-no-extra-offset,避免重复应用额外偏移;
  • 翻译审计会比较英文与中文页面渲染后的标题 ID;
  • 删除旧别名属于破坏性文档变更,需要重定向或明确记录兼容性决策。

2.5 - 短代码

安全、无障碍地使用 OINK 的本地优先内容组件。

短代码用于表达普通 Markdown 无法承载的行为。OINK 保留 Docsy 核心组件,并新增本地提供的图表、终端录像、信息图、轮播、卡片和折叠组件。浏览器运行时只在实际使用它们的页面加载。

标题、正文、列表、链接、表格和图片应优先使用 Markdown。短代码一旦投入使用,就成为内容 API 的一部分:修改名称或参数可能破坏所有调用它的页面。

短代码分隔符

Hugo 支持两种形式:

  • {{< name >}} 使用标准分隔符,原样传递内部内容;
  • {{% name %}} 使用 Markdown 分隔符,在周围内容的上下文中渲染内部 Markdown。

请采用各组件文档指定的形式。嵌套、缩进和空行都会影响结果,在列表和块引用中尤其如此。示例里的 /* ... */ 转义用于防止 Hugo 执行正在展示的短代码。

blocks/* 短代码

块短代码用于组合全宽落地页。color 参数使用 OINK/Bootstrap 语义颜色或项目自定义块样式,height 参数接受各组件说明的取值。

blocks/cover

使用页面包中匹配 *background* 的图片以及可选的 *logo* 创建首屏:

{{< blocks/cover title="OINK" subtitle="本地优先文档"
    color="dark" height="max" >}} [开始使用](/zh/docs/get-started/){ .btn
.btn-lg .btn-primary } {{< /blocks/cover >}}

image_anchorlogo_anchor 控制图片裁切位置,byline 用于标注图片来源。高度可取 autominmedmaxfull。即使背景无法显示,首屏关键信息也必须保持可读。

blocks/lead

创建醒目的介绍区块:

{{% blocks/lead color="primary" height="min" %}} OINK 只用 Hugo
Extended 即可构建完整文档体验。 {{% /blocks/lead %}}

高度支持 autominmedmaxfull

blocks/section

创建通用落地页区块:

{{% blocks/section color="light" type="row" height="auto" %}}

### 一个分区

区块内部使用普通 Markdown。 {{% /blocks/section %}}

type 选择容器形式,height 使用块高度取值。标题级别必须与页面大纲保持一致。

blocks/feature

创建单个功能单元,通常放在 Section 中:

{{% blocks/feature icon="fa-solid fa-box-archive"
    title="离线可用" url="/zh/docs/oink/local-first/"
    url_text="阅读设计说明" %}} 所需浏览器资源均已锁定版本并从本地提供。
{{% /blocks/feature %}}

图标只是装饰,含义必须由 title 和链接文本表达。

从当前块添加指向下一块的链接。它必须嵌套在块内。生成目标必须长期稳定时,应显式设置 id

导航栏下方布局校正

直接位于固定导航下方的块使用 td-below-navbar/td-anchor-no-extra-offset 校正导航栏高度。不要自行添加任意上边距;修改导航栏尺寸后,应验证直接访问片段链接的效果。

辅助短代码

alert

旧版告警短代码仍可使用:

{{% alert title="兼容性说明" color="warning" %}}
新内容优先使用 Markdown 块引用告警。 {{% /alert %}}

color 映射到 Bootstrap 告警后缀。新内容通常应采用添加内容介绍的 Markdown 告警语法。

告警、缩进与示例

开始和结束短代码应与外层列表或块引用对齐,块级 Markdown 前后应保留空行。需要原样展示短代码时,应转义分隔符,不要把活动调用包在另一个组件中。

pageinfo

在 Markdown 外渲染信息面板:

{{% pageinfo color="info" %}} 本页介绍预览接口。 {{% /pageinfo %}}

警告信息应使用语义告警;pageinfo 适合提供页面上下文。

imgproc

处理当前页面包中的图片:

{{% imgproc "architecture" Fit "960x540" %}} OINK 运行时架构。
{{% /imgproc %}}

命令可取 FitResizeFillCrop,第三个参数遵循 Hugo 图片处理语法。内部文字会成为图注;资源存在 params.byline 时会附加署名。始终提供有意义的替代文字或相邻说明。

swaggerui

嵌入本地纳管的 Swagger UI 运行时:

{{< swaggerui src="/openapi.yaml" >}}

离线或严格 CSP 部署应使用同源规范。远程 src 是显式网络依赖,也可能向该主机暴露读者元数据。当前兼容短代码在一页中只应放置一个 Swagger UI 实例。

redoc

嵌入本地纳管的 Redoc 运行时:

{{< redoc "openapi.yaml" >}}

第一个参数可以是页面相对、站点相对或显式 HTTP 规范;可选第二个参数包含 Redoc 元素选项。规范内容必须经过审查,大型 Schema 还应测试移动端表现。

iframe

嵌入另一个页面:

{{< iframe src="/demo/" name="demo" id="demo-frame"
    sandbox="allow-scripts allow-same-origin" >}}

请设置有描述力的 name、唯一的 id、后备 sub 提示,以及满足需求的最严格 sandbox。默认值支持宽度和自动高度,但跨域文档并不总能测量。iframe 是安全与隐私边界,不是通用布局工具。

OINK 内容组件

以下组件由 OINK 新增。各运行时都在 theme/VENDOR.json 中锁定版本,并按需从同源加载。

details

创建无障碍折叠内容:

{{% details title="显示迁移说明" closed="false" %}} 正文支持 Markdown。
{{% /details %}}

closed 默认为 true。摘要应简洁,而且不得把强制操作隐藏在默认关闭的折叠区中。

asciinema

播放 asciinema .cast 录像:

{{< asciinema file="casts/install.cast" speed="1.25"
    markers="0:开始,18:验证" fit="width" >}}

主要参数包括 themeautoplaylooppreloadspeedstartAtpostercolsrowsidleTimeLimitpauseOnMarkersmarkersfitwidthheightbothnone)。本地录像可以来自 Hugo assets 或站点相对 URL。不要自动播放,必须清除终端历史中的机密,并为关键步骤提供相邻文字说明。

echarts

根据 JSON 或 YAML 选项对象渲染 Apache ECharts:

{{< echarts height="320px" >}} xAxis: type: category data:
[构建, 测试, 发布] yAxis: type: value series:

- type: bar data: [42, 38, 12] {{< /echarts >}}

height 必须是安全的 CSS 长度;theme 选择 ECharts 主题,full=true 会取消常规正文宽度限制。

短代码内部的 JavaScript 块默认会被拒绝。只有单次调用设置 unsafe=true,或全局设置 params.content.echarts_unsafe=true 时才能执行。该选项允许可执行内容,绝不能为不可信作者启用。应优先使用声明式 JSON/YAML,添加相邻文字摘要,并验证深色模式。

infographic

渲染本地纳管的信息图 DSL:

{{< infographic height="360px" >}} infographic
list-row-simple-horizontal-arrow data items - label 构建 - label 测试 -
label 发布 {{< /infographic >}}

height 可以是 auto 或安全 CSS 长度;full=true 会取消宽度限制。DSL 属于数据,并非任意 HTML。可视化不可用时,相邻正文也必须能表达相同结论。

doc-cardsnav-cards

两个容器都接受 1 至 4 的 cols。子卡片接受 titlelinkimagealticondescaccentbadge

{{< nav-cards cols="2" >}}
{{< nav-card title="开始使用" link="/zh/docs/get-started/"
      icon="fa-solid fa-rocket" desc="使用 Hugo {version} 构建。" >}} {{< nav-card title="架构" link="/zh/docs/oink/architecture/"
      badge="设计" >}}
{{< /nav-cards >}}

doc-card/doc-cards 与其共享渲染契约,适合编辑型内容;nav-card/nav-cards 则明确表示导航。{version} 等描述占位符会从站点参数解析。卡片图片采用延迟加载;除非图片纯属装饰,否则必须提供有意义的 alt

doc-card 放入支持键盘滚动的轮播:

{{< doc-carousel label="发布亮点" >}}
{{< doc-card title="本地资源" >}}无需 CDN。{{< /doc-card >}}
{{< doc-card title="中英双语" >}}稳定的中英文路由。{{< /doc-card >}}
{{< /doc-carousel >}}

label 为辅助技术命名该区域。上一项/下一项按钮会本地化。信息不能只存在于屏幕外卡片中;禁用脚本后,轨道仍应可用。

param

输出页面参数;根据 Hugo 的 Page.Param 规则,在页面缺省时回退到站点配置:

OINK 版本 {{< param version >}}。

找不到参数会令构建失败。param 适合显示标量值,不应用于注入未经审查的 HTML。内部兼容短代码 _param 还会为旧内容执行带编号的占位符替换。

标签页

标签页用于组织 YAML/TOML/JSON 配置等同一信息的等价表示,不应隐藏连续步骤或互不相关的选择。

{{< tabpane text=true persist=lang >}}
{{< tab header="YAML" lang="yaml" >}} params: offlineSearch: true
{{< /tab >}} {{< tab header="TOML" lang="toml" >}} [params]
offlineSearch = true {{< /tab >}} {{< /tabpane >}}

选择状态保存在浏览器本地。persist 接受 headerlangdisabled。已弃用的 persistLang 不应出现在新内容中。

短代码细节

text=true 将内部内容渲染为正文而不是高亮代码;right=true 把标签对齐到末端;langEqualsHeader=true 根据标题推导语言标识。父级默认值可以由单个标签覆盖。

tabpane

父组件会校验布尔值和持久化参数、生成唯一 ID,并确保存在选中项。只有禁用的标题标签确实能提供有用分组信息时才使用它。

tab

tab 必须放在 tabpane 内部。它接受 headerselectedlanghighlighttextrightdisabled。只能选中一个标签。面向读者的标题需要翻译,语言标识则必须稳定。

卡片面板

旧版 cardpane/card 组合用于布局 Bootstrap 风格卡片。新的导航表面应优先使用 OINK 内容卡片,既有 Docsy 内容可以继续使用兼容组件。

card 短代码:文本内容

{{% cardpane %}}
{{% card header="说明" title="本地构建" footer="已验证" %}} Markdown
**正文**。 {{% /card %}} {{% /cardpane %}}

headertitlesubtitlefooter 接受渲染文本。并列卡片应保持简洁,不能用卡片取代标题结构。

card 短代码:程序代码

设置 code=true,并按需设置 lang/highlight

{{< cardpane >}} {{< card code=true header="Go" lang="go" >}}
fmt.Println("OINK") {{< /card >}} {{< /cardpane >}}

卡片组

cardpane 中相邻的卡片会形成响应式分组。应测试文字长度不一、移动端堆叠、代码溢出以及两种语言版本。

引入外部文件

readfile 短代码在构建期读取仓库文件,并将其渲染为 Markdown 或高亮代码。除非路径以 / 开头,否则路径相对于当前内容文件。

复用文档

{{% readfile "includes/installation.md" %}}

被引入的 Markdown 不是独立发布页面,因此不参加页面配对审计。如果共享正文面向读者,应有意识地创建并选择语言专属的 include 文件;Hugo 不会自动翻译 include。

安装

可复用片段应放在调用方附近的 includes/ 目录中。需要明确其所有权,并避免多层嵌套:读者和审阅者应能迅速找到源文件。

引入代码文件

{{< readfile file="includes/config.yaml" code="true" lang="yaml" >}}

code=true 会用 lang 高亮文件。绝不能引入机密、生成的凭据或不可信路径。

错误报告

找不到文件时构建会失败。draft=true 会把失败改为可见的草稿警告,只适合创作阶段,绝不能进入正式发布构建。

条件文本

conditional-text 根据 params.buildCondition 选择内容:

{{% conditional-text include-if="enterprise,preview" %}}
这段文字只出现在匹配的构建中。 {{% /conditional-text %}}

include-ifexclude-if 接受条件列表,同一条件不能同时出现在二者中。该功能适用于确实不同的发布变体,不应用来选择语言;多语言内容必须写入翻译后的页面文件。

2.6 - 分类法支持

使用标签、类别、标记等分类法组织内容。

OINK 在文档与博客分区中支持 Hugo 分类法。本页既展示默认布局,也可以用来测试生成链接的行为。

术语

使用分类法前,需要理解以下术语:

  • 分类法(Taxonomy):用于对内容进行分类的体系,例如标签、类别、项目、人物。

  • 术语(Term):分类法中的一个键。例如,在“项目”分类法中可以有“项目 A”和“项目 B”。

  • 值(Value):分配给某个术语的一项内容,例如属于特定项目的站点页面。

Hugo 文档提供了一个电影网站分类法示例

参数

项目配置文件中有多项参数可以控制分类法功能。Hugo 默认启用 tagscategories 分类法。要 禁用 分类法,请在项目配置中添加:

disableKinds = ["taxonomy"]
disableKinds: [taxonomy]
{
  "disableKinds": [ "taxonomy" ]
}

保持默认设置时,Hugo 会生成 tagscategories 的分类法页面。如果要使用其他分类法,需要在配置文件中定义。如果希望自定义分类法与默认的 tagscategories 并存,也必须把默认分类法一并写入配置。每种分类法都需要提供单数与复数标签。

下面的示例在默认 tagscategories 之外,又定义了 projects 分类法:

[taxonomies]
tag = "tags"
category = "categories"
project = "projects"
taxonomies:
  tag: tags
  category: categories
  project: projects
{
  "taxonomies": {
    "tag": "tags",
    "category": "categories",
    "project": "projects"
  }
}

项目配置中的以下参数可控制两类输出:文档和博客文章页显示的分类法术语,以及 OINK 右侧栏显示的“标签云”:

[params.taxonomy]
taxonomyCloud = ["projects", "tags"] # set taxonomyCloud = [] to hide taxonomy clouds
taxonomyCloudTitle = ["Our Projects", "Tag Cloud"] # if used, must have same length as taxonomyCloud
taxonomyPageHeader = ["tags", "categories"] # set taxonomyPageHeader = [] to hide taxonomies on the page headers
params:
  taxonomy:
    taxonomyCloud:
      - projects    # remove all entries
      - tags        # to hide taxonomy clouds
    taxonomyCloudTitle:   # if used, must have the same
      - Our Projects      # number of entries as taxonomyCloud
      - Tag Cloud
    taxonomyPageHeader:
      - tags        # remove all entries
      - categories  # to hide taxonomy clouds
{
  "params": {
    "taxonomy": {
      "taxonomyCloud": [
        "projects",
        "tags"
      ],
      "taxonomyCloudTitle": [
        "Our Projects",
        "Tag Cloud"
      ],
      "taxonomyPageHeader": [
        "tags",
        "categories"
      ]
    }
  }
}

以上设置只会在 OINK 右侧栏中显示 projectstags 的分类云(标题分别为“ Our Projects”和“Tag Cloud”),并在每个页面显示 tagscategories 分类法中已经分配的术语。

要禁用所有分类云,请设置 taxonomyCloud = [];如果不想显示已分配术语,请设置 taxonomyPageHeader = []

默认情况下,分类法的复数标签会用作分类云标题。可以通过 taxonomyCloudTitle 覆盖默认标题,但这样做时,必须为每个启用的分类云手工定义一个标题;taxonomyCloudtaxonomyCloudTitle 的长度必须相同。

如果没有设置 taxonomyCloudtaxonomyPageHeader,系统会为所有已定义分类法生成相应的分类云或已分配术语。

Partial

显示分类法时默认使用的 partial 经过专门设计,可以方便地在自定义布局中复用。

taxonomy_terms_article

taxonomy_terms_article partial 会显示一篇文章或页面(partial 参数 context,通常是当前页面或上下文 .)在指定分类法(partial 参数 taxo)中分配到的全部术语。

下面是在 layouts/docs/list.html 中为文档分区每个页面的 header 使用它的示例:

{{ $context := . }}
{{ range $taxo, $taxo_map := .Site.Taxonomies }}
  {{ partial "taxonomy_terms_article.html" (dict "context" $context "taxo" $taxo ) }}
{{ end }}

它会针对当前页面(或上下文)中的每个已定义分类法,输出一份包含全部已分配术语的列表:

<div class="taxonomy taxonomy-terms-article taxo-categories">
  <h5 class="taxonomy-title">Categories:</h5>
  <ul class="taxonomy-terms">
    <li>
      <a
        class="taxonomy-term"
        href="//localhost:1313/categories/taxonomies/"
        data-taxonomy-term="taxonomies"
        ><span class="taxonomy-label">Taxonomies</span></a
      >
    </li>
  </ul>
</div>
<div class="taxonomy taxonomy-terms-article taxo-tags">
  <h5 class="taxonomy-title">Tags:</h5>
  <ul class="taxonomy-terms">
    <li>
      <a
        class="taxonomy-term"
        href="//localhost:1313/tags/tagging/"
        data-taxonomy-term="tagging"
        ><span class="taxonomy-label">Tagging</span></a
      >
    </li>
    <li>
      <a
        class="taxonomy-term"
        href="//localhost:1313/tags/structuring-content/"
        data-taxonomy-term="structuring-content"
        ><span class="taxonomy-label">Structuring Content</span></a
      >
    </li>
    <li>
      <a
        class="taxonomy-term"
        href="//localhost:1313/tags/labelling/"
        data-taxonomy-term="labelling"
        ><span class="taxonomy-label">Labelling</span></a
      >
    </li>
  </ul>
</div>

taxonomy_terms_article_wrapper

taxonomy_terms_article_wrappertaxonomy_terms_article 的包装 partial,只有一个 context 参数(通常是当前页面或上下文 .)。它会检查项目 hugo.tomlhugo.yamlhugo.json 中的分类法参数,遍历 taxonomyPageHeader 中列出的全部分类法;如果没有设置 taxonomyPageHeader,则遍历页面定义的全部分类法。

taxonomy_terms_cloud

taxonomy_terms_cloud partial 会显示站点(partial 参数 context,通常是当前页面或上下文 .)在指定分类法(partial 参数 taxo)中使用的全部术语,并使用 title 参数作为标题。

下面是在 taxonomy_terms_clouds partial 中显示所有已定义分类法及其术语的示例:

{{ $context := . }}
{{ range $taxo, $taxo_map := .Site.Taxonomies }}
  {{ partial "taxonomy_terms_cloud.html" (dict "context" $context "taxo" $taxo "title" ( humanize $taxo ) ) }}
{{ end }}

对于 categories 分类法,它会生成以下 HTML 标记:

<div class="taxonomy taxonomy-terms-cloud taxo-categories">
  <h5 class="taxonomy-title">Cloud of Categories</h5>
  <ul class="taxonomy-terms">
    <li>
      <a
        class="taxonomy-term"
        href="//localhost:1313/categories/category-1/"
        data-taxonomy-term="category-1"
        ><span class="taxonomy-label">category 1</span
        ><span class="taxonomy-count">3</span></a
      >
    </li>
    <li>
      <a
        class="taxonomy-term"
        href="//localhost:1313/categories/category-2/"
        data-taxonomy-term="category-2"
        ><span class="taxonomy-label">category 2</span
        ><span class="taxonomy-count">1</span></a
      >
    </li>
    <li>
      <a
        class="taxonomy-term"
        href="//localhost:1313/categories/category-3/"
        data-taxonomy-term="category-3"
        ><span class="taxonomy-label">category 3</span
        ><span class="taxonomy-count">2</span></a
      >
    </li>
    <li>
      <a
        class="taxonomy-term"
        href="//localhost:1313/categories/category-4/"
        data-taxonomy-term="category-4"
        ><span class="taxonomy-label">category 4</span
        ><span class="taxonomy-count">6</span></a
      >
    </li>
  </ul>
</div>

taxonomy_terms_clouds

taxonomy_terms_cloudstaxonomy_terms_cloud 的包装 partial,只有一个 context 参数(通常是当前页面或上下文 .)。它会检查项目配置中的分类法参数,遍历 taxonomyCloud 列出的全部分类法;如果没有设置 taxonomyCloud,则遍历页面定义的全部分类法。

分类法的多语言支持

对于多语言站点,分类法术语只会在各自语言站点内计数和链接。分类法配置参数也可以按语言分别调整。

2.7 - 分析、用户反馈与 SEO

配置可选分析和反馈,同时提供有用的 SEO 元数据。

OINK 默认不会连接分析、表单、评论或广告服务。这些集成属于站点决策:必须显式启用、记录数据边界,并根据用户与站点所在司法辖区提供必要的同意机制或政策说明。

添加分析

Hugo 为分析服务提供嵌入模板。站点配置 Google Analytics 后,页面浏览量与自定义事件等浏览器使用信息会发送给 Google。这与完全网络隔离的运行环境不兼容,也可能不符合严格的同源内容安全策略(CSP)。

配置

取得站点的 Google Analytics measurement ID,然后使用 Hugo 当前的服务配置:

services:
  googleAnalytics:
    id: G-YOUR-ID

不要同时设置已经弃用的顶层 googleAnalytics 键。通常只有 Hugo production 环境才会输出分析代码。发布前,请构建生产预览,并检查 HTML 与浏览器网络日志。

禁用分析后,OINK 不会发起 Google Analytics 请求。应彻底删除相关配置,而不是填写虚假 ID。

用户反馈

OINK 可以在文档页底部显示“本页是否有帮助?”小组件。它提供 两个操作,随后显示配置好的响应;响应通常包含创建文档 issue 的链接。

页面询问内容是否有帮助,并提供“是”和“否”两个按钮。
图 1:页面反馈组件

即使不启用分析,响应仍然可以发挥作用:它可以把读者引导到 issue 模板、讨论区、电子邮箱或站点自有的其他反馈渠道。只有站点配置了适当目标后,才会发生数据收集和事件上报。

反馈数据有什么用?

应结合上下文理解反馈,不能把单一分数当作结论。访问量高且反复收到负面反馈的页面是值得优先复查的候选;高评分页面则可能揭示值得在其他页面验证的模式。

应尽可能采用聚焦的编辑变更。例如,只更新一篇过时教程,或者把一小组页面的代码示例提前,然后在合适的时间范围内比较反馈。同时记录发布事件、流量变化、支持事件和其他可能解释变化的因素。

反馈只能提供方向性证据,不能取代用户研究、无障碍评审、支持数据或技术验证。

配置

OINK 默认关闭该小组件。请设置全局默认值,并配置本地化响应。英文配置如下:

params:
  ui:
    feedback:
      enable: false
languages:
  en:
    params:
      ui:
        feedback:
          yes: >-
            Glad to hear it! Please <a
            href="https://github.com/OWNER/REPOSITORY/issues/new">tell us how we
            can improve</a>.
          no: >-
            Sorry to hear that. Please <a
            href="https://github.com/OWNER/REPOSITORY/issues/new">tell us how we
            can improve</a>.

简体中文字符串放在 languages.zh.params 下:

languages:
  zh:
    params:
      ui:
        feedback:
          yes: >-
            很高兴本页对你有帮助!欢迎<a
            href="https://github.com/OWNER/REPOSITORY/issues/new">告诉我们如何继续改进</a>。
          no: >-
            很抱歉本页没有解决问题。请<a
            href="https://github.com/OWNER/REPOSITORY/issues/new">告诉我们缺少什么</a>。

可见响应 HTML 属于可信站点配置。内容应保持精简,链接需要经过评审,并且不能插入不可信值。

配置 Google Analytics 后,小组件可以发送自定义 page_helpful 事件。正面操作使用 params.ui.feedback.max_value(默认为 100),负面操作使用 0。

访问反馈数据

使用 Google Analytics 时,可以在服务商的事件报告中查看 page_helpful,并按需创建页面级报告。没有事件并不一定表示没有用户反馈;也可能是分析被阻止或禁用、用户没有同意,或者所选时间范围不正确。

不要仅仅为了显示小组件就启用分析。站点可以保留响应和链接体验,同时关闭事件收集。

在单个页面覆盖反馈设置

在页面 Front Matter 中设置 feedback。页面设置可从任一方向覆盖全局默认值:

---
title: 反馈示例
feedback: true
---

全局默认开启时,可用 feedback: false 隐藏单个页面的小组件。为保持兼容,未设置 feedback 时,hide_feedback: true 仍会隐藏小组件。

设置所有页面的默认值

设置以下站点参数。OINK 默认值为 false;只有大多数文档页都应显示小组件时,才将其设为 true

params:
  ui:
    feedback:
      enable: false

使用 Fabform 添加联系表单

Fabform 和类似托管表单端点都是可选在线服务。创建账户并评审其数据处理方式后,站点可以把表单提交到分配的端点:

<form action="https://fabform.io/f/{form-id}" method="post">
  <label for="email">电子邮箱</label>
  <input id="email" name="email" type="email" autocomplete="email" />
  <button type="submit">提交</button>
</form>

请替换 {form-id}、翻译可见标签、加入隐私说明,并提供错误与成功状态。该表单无法离线使用。如果站点必须让提交内容留在自身边界内,应优先使用本地或第一方端点。

搜索引擎优化元数据

OINK 会按以下优先级为每个页面选择 HTML meta description:

  1. 页面 front matter 中的 description
  2. 对于非索引页,使用 Hugo 计算出的页面摘要;
  3. params 中的站点描述。

请为每种语言编写精炼且针对当前页面的描述。不要把英文描述复制到中文页面。搜索元数据无法弥补内容单薄、重复或不准确的问题。

主题还会根据 Hugo 页面译文输出 canonical 与备用语言链接。请使用正确的生产 baseURL、稳定的译文路由和显式译文标题 ID。只有主题尚未提供某类 meta 标签时,才应通过站点的 layouts/_partials/hooks/head-end.html 覆盖添加。

底层服务与内容概念请参阅 Hugo 的 Google Analytics 配置页面摘要和 Google 的 SEO 入门指南

2.8 - 搜索

配置本地多语言搜索,或显式启用在线服务商。

OINK 默认并推荐使用本地搜索。Hugo 会为每种语言生成独立索引;主题从同源资源提供 Lunr 及其 CJK 回退。站点无需公共爬虫、外部账户、CDN 或网络连接,即可完成构建和搜索。

Google Custom Search 与 Algolia DocSearch 仍作为兼容的在线集成保留。它们默认关闭;只有站点明确接受相应的外部请求、索引方式、可用性与隐私边界时,才应启用。

同一时间只能启用一种搜索实现。

使用 Lunr 的本地搜索

hugo.yaml 中启用本地搜索:

params:
  offlineSearch: true

不要同时配置 gcs_engine_idparams.search.algolia。生产构建完成后,输出中会为每种语言生成一个索引,例如:

offline-search-index.en.json
offline-search-index.zh.json

浏览器加载当前语言的索引,并在不离开页面的情况下显示结果。中文内容使用 OINK 的 CJK 回退,不依赖以空格分词。

测试前构建索引

启动预览前先执行常规构建:

hugo --gc
hugo server --disableFastRender

如果索引变化时 server 已经在运行,请将其重启。对于子路径部署,请确认浏览器从配置的 baseURL 下请求索引,而不是从域名根目录请求。

配置结果摘要与数量限制

设置摘要长度和最大结果数:

params:
  offlineSearch: true
  offlineSearchSummaryLength: 120
  offlineSearchMaxResults: 12

所选限制应确保搜索对话框在移动设备上保持流畅。摘要用于帮助发现内容,不能替代认真编写的页面描述。

排除页面

在页面 front matter 中设置 exclude_search: true

---
title: Internal index
exclude_search: true
---

该设置适用于工具页、重复页、生成页或测试页。不要仅仅因为当前译文不完整就排除页面;应修复译文。

设置结果面板样式

结果面板会随内容扩展。站点可以在 assets/scss/_styles_project.scss 中限制宽度:

.td-offline-search-results {
  max-width: 46rem;
}

覆盖搜索样式时,必须保留键盘焦点、可见选中状态、移动端宽度和深色模式对比度。

搜索入口

OINK 会在品牌外壳中提供搜索入口,也可以在侧栏显示输入框。如果要隐藏侧栏输入框,同时保留主搜索入口,请配置:

params:
  ui:
    sidebar_search_disable: true

外壳的打开与关闭控件会向辅助技术暴露对话框关系和状态。自定义实现必须保留这些语义。

搜索始终停留在当前语言。请验证:

  • 每种已发布语言都有自己的索引;
  • 译文标题、描述和正文出现在对应索引中;
  • 结果 URL 包含正确的语言前缀;
  • 英文结果不会通过内容回退取代中文结果;
  • 结果页上的语言选择器能前往对应译文,或按文档规则回退到语言首页。

中文搜索出现故障时,应先检查生成的中文 JSON,再考虑修改分词。索引缺失或只包含英文,通常属于内容或构建配置问题。

Google Custom Search Engine(GCSE)通过 Google 索引搜索公开站点。它需要已经部署且允许爬取的生产站点,并会把查询发送给第三方服务。

Google Programmable Search 中创建搜索引擎后,添加搜索结果页:

---
title: 搜索结果
layout: search
---

随后配置搜索引擎 ID:

params:
  gcs_engine_id: YOUR_ENGINE_ID
  offlineSearch: false

为每种支持语言创建译文结果页;必要时使用适合该语言的搜索引擎配置。删除 gcs_engine_id 即可禁用 GCSE。

消费站点应在隐私政策中说明外部请求和隐私影响。GCSE 无法在网络隔离部署中使用。

Algolia DocSearch(可选)

Algolia DocSearch 为符合条件的公开文档站点提供托管爬虫和交互式结果面板。取得项目的 application ID、搜索 API key 和索引名称后,配置:

params:
  offlineSearch: false
  search:
    algolia:
      appId: YOUR_APP_ID
      apiKey: YOUR_SEARCH_API_KEY
      indexName: YOUR_INDEX_NAME

只能使用公开的只读搜索 key,绝不能使用管理 key。爬虫规则、语言 facet、索引更新与外部服务声明应与站点配置一同维护。该集成有意与本地优先默认值分离。

可以覆盖主题 partial layouts/_partials/algolia/head.htmllayouts/_partials/algolia/scripts.html,实现站点专属集成。空的覆盖文件会禁用对应主题 partial。

如果现有选项都不合适,站点可以替换搜索输入、结果行为与样式。应尽量复用外壳的对话框与无障碍合同。除非自定义代码与服务商无关,并且能被多个产品复用,否则应保留在站点层。

自定义在线服务商必须显式启用,并说明网络、隐私、索引、故障与离线行为。自定义本地服务商必须从站点或主题发布全部运行时资源,并遵守语言和 baseURL 边界。

2.9 - 添加内容

在 OINK 中组织和编写中英双语文档与博客内容。

OINK 沿用 Hugo 的内容模型:Markdown 承载信息,Front Matter 保存页面元数据,布局则把二者渲染成静态站点。本指南说明随项目提供的中英双语样例站采用的内容约定。

内容根目录

站点内容位于 content/ 目录下。多语言站点既可以分别使用 content/en/content/zh/ 等内容根目录,也可以在同一棵挂载目录中使用语言后缀。本仓库采用后一种形式:

content/docs/content/
├── adding-content.md
└── adding-content.zh.md

英文文件是源页面,.zh.md 文件是对应的简体中文译文。Hugo 解析语言后缀后,二者具有相同的逻辑路径。

生成文件以及必须逐字节复制的文件不应放入内容树,而应放入 static/;详见添加静态内容

内容分区与模板

内容根目录下的每个一级目录都是 Hugo 分区。OINK 提供以下布局:

  • docs:带分区树、目录、面包屑、上一篇/下一篇导航和仓库链接的文档页;
  • blog:带日期、分类元数据、Feed 和时间倒序列表的文章页;
  • community:展示项目与贡献者链接的社区页;
  • 默认页面:不显示文档侧边栏的落地页。

Hugo 根据内容所属分区选择布局,因此 content/docs/ 下的页面会使用 docs 布局。只有确实要复用其他分区布局时,才在 Front Matter 中设置 type

自定义分区

在内容根目录下新建目录;默认布局无法满足需求时,再为页面指定类型:

---
title: 架构决策
description: 项目已经采纳的设计决策。
type: docs
weight: 30
---

如果某项行为适用于整个分区,应在 _index.mdcascade 中设置共享值,避免每页重复。只有现有 OINK 布局与 Partial 均不适用时,才在项目的 layouts/ 下新增布局。

以文档为根的站点

EXPERIMENTAL

以文档为主的站点可以把 docs 分区发布到 URL 根路径,同时仍将源码保存在 content/.../docs/ 下:

permalinks:
  page:
    docs: /:sections[1:]/:slug/
  section:
    docs: /:sections[1:]

此时,文档分区落地页会成为站点首页。请为每种语言的物理站点根索引添加以下 Front Matter,使其仍可作为链接使用,同时不会争抢相同的输出路径:

build: { render: link }

检查路径冲突

文档会与博客、社区及其他分区共享 URL 根路径。构建时启用 --printPathWarnings,并在发布前解决所有重复目标:

hugo --printPathWarnings

旧版纯文档配置

旧版 Docsy 示例曾通过 Front Matter 的 cascade 强制设置页面类型。迁移到基于永久链接的文档根配置时,应删除这项变通设置,否则首页与分区布局可能出现不一致的解析结果。

页面 Front Matter

Front Matter 是用 YAML、TOML 或 JSON 编写的页面元数据。OINK 样例站使用 YAML:

---
title: Local-first architecture
linkTitle: Local-first
description: How OINK removes browser and build-time CDN dependencies.
weight: 20
date: 2026-08-08
tags: [architecture, offline]
---

title 是实际所需的最小字段。对于持续维护的文档,还应提供简洁的 description 供搜索和页面元数据使用;顺序有意义时应设置 weight。只有导航标签需要更短文本时才使用 linkTitle

译文应翻译面向读者的元数据,同时保留结构性取值:

---
title: 本地优先架构
linkTitle: 本地优先
description: OINK 如何消除浏览器端与构建期的 CDN 依赖。
weight: 20
date: 2026-08-08
tags: [架构, 离线]
---

不要翻译字段名、短代码名称、配置项、文件路径或稳定标识符。

文档页与博客页会在站点页脚上方显示紧凑的元数据区域。最后修改日期取自 Hugo 的 .Lastmod 值;以下两个可选 Front Matter 字段用于补充来源说明:

lastmod: 2026-08-09
upstream_attribution: https://upstream.example/docs/page/
downstream_modified: true

upstream_attribution 链接到上游原文及其署名信息;downstream_modified: true 表示下游项目修改过本页。某项说明不适用时,请省略对应字段。

页面正文

除非布局确实要求 HTML,否则页面应使用 Markdown。Hugo 通过 Goldmark 渲染 Markdown,并支持属性、脚注、表格、任务列表、渲染钩子和围栏代码块。

Markdown

即使脱离渲染后的站点,源码也应保持可读:

  • 使用 ATX 标题(## 标题);
  • 列表、块和围栏代码前后保留空行;
  • 代码语言已知时必须标注;
  • 使用能说明去向的链接文本和图片替代文本;
  • 普通正文按便于审阅的宽度换行,但不要重排代码或 URL。

OINK 为块引用告警以及 Mermaid、数学公式、化学公式、Markmap 和 PlantUML 代码块提供渲染钩子。详见图表与公式

标记、短代码与内容功能

普通正文优先使用标准 Markdown。需要标签页、卡片、终端录像、API 查看器或安全图表等有实际行为的组件时,再使用短代码。短代码属于内容契约的一部分:应在两种语言中核对其参数,不要把渲染后的 HTML 复制到译文。

告警

OINK 支持 GitHub 风格的块引用告警,也支持可选的 Obsidian 风格标题:

> [!TIP]
>
> 每次发布前都要运行翻译审计。

> [!WARNING] 必须使用稳定锚点
>
> 译文标题必须保留英文页面渲染后的 ID。

语义类型包括 NOTETIPIMPORTANTWARNINGCAUTION,以及与 Bootstrap 兼容的类型和 NB。告警应节制使用:关键信息在屏幕阅读器和打印版中也必须成立。外观设置参见告警

稳定公开路由使用根路径相对链接,相邻页面或页面包资源使用普通相对链接。Hugo 的 refrelref 短代码可以校验内容引用,并处理语言和永久链接规则:

[配置]({{< ref "/docs/oink/configuration" >}})

编写双语页面时:

  • 链接到逻辑页面,不要直接链接 .zh.md 文件名;
  • 片段 ID 应保持语言中立;
  • 验证两种语言能否解析到相同片段;
  • 目标必须相对于当前主机时使用 relref

调整路由或标题后,应运行站内链接检查。

内容风格

任务型文档应使用直接、明确的语言:先介绍概念,再给出配置;明确说明默认值;区分本地构建验证、部署与正式发布。中文版遵循 oink.pgsty.com/TRANSLATION.md 中的术语与排版规则。

页面包

独立页面只有一个 Markdown 文件;叶子页面包则由 index.md 和页面资源组成:

content/docs/tutorial/
├── index.md
├── index.zh.md
├── architecture.svg
└── example.yaml

两种语言的页面可以共用同一图片和下载文件。在单主机多语言站点中,Hugo 通常会在语言版本之间共享页面资源,因此不要复制完全相同的二进制资源。只有图片包含需要翻译的文字时才制作本地化版本,并为资源添加清晰的语言后缀。

包含子页面的分区使用分支页面包(_index.md),带资源的末端页面使用叶子页面包(index.md)。

添加文档与博客文章

每个持续维护的英文页面都应在同一目录下配有中文页面:

guide.md
guide.zh.md

页面包则将 index.mdindex.zh.md 配对。除非语言差异确有必要,否则二者的路由元数据、日期、权重、别名和资源声明应保持一致。

组织文档

目录应反映读者看到的信息架构,而不是实现代码的包结构。每个文档子分区都需要 _index.md_index.zh.md。子页面会按 weight 排列在侧边栏中,权重相同时再使用配置的后备顺序。

层级应尽量浅。页面面向独立任务或受众时才拆分,不要仅仅因为文件较长而拆分。详见组织内容

文档分区落地页

文档分区的 _index.md 默认会渲染子页面摘要。使用:

simple_list: true

可以改为紧凑列表;使用:

no_list: true

可以关闭自动列表。每种语言都应提供本地化标题和描述,并保持结构选项一致。

组织博客文章

博客文章既可以直接放在 blog/ 下,也可以按年份或分类建目录。OINK 使用日期目录,并为每篇文章配对:

blog/2026/
├── oink-release.md
└── oink-release.zh.md

文章通常包含:

---
title: OINK 1.0
description: A local-first Docsy distribution.
date: 2026-08-08
author: OINK maintainers
tags: [release]
---

不同语言版本的发布日期与作者身份应保持一致。标题、描述、分类标签、图注和正文需要翻译;提交 ID、发布标签、命令和 URL 不应翻译。

使用一级落地页

默认布局适用于首页、产品概览和其他不需要文档侧边栏的入口页。

自定义样例站页面

随项目提供的首页是 content/_index.md,其中文译文是 content/_index.zh.md。它与 OINK 其余页面使用同一套本地资源和主题流水线。品牌调整应修改站点内容与项目资源,不要为了品牌外观去编辑已经纳管的运行时文件。

构建自己的落地页

使用标准 Markdown 和blocks/* 短代码组合落地页。关键信息必须保留为文本,行动链接应说明实际去向,并在两种语言中分别测试移动端和桌面端布局。

添加社区页面

创建 community/_index.mdcommunity/_index.zh.md。社区布局会读取 params.links.userparams.links.developer

params:
  links:
    user:
      - name: 用户论坛
        url: https://community.example.org/
        icon: fa-solid fa-comments
        desc: 提问并分享解决方案
    developer:
      - name: GitHub
        url: https://github.com/pgsty/oink
        icon: fa-brands fa-github
        desc: 源码、议题与拉取请求

条目可以设置 rel;对于外部 HTTP 链接,OINK 也会按需补充 noopener。贡献指南不在约定的文档路径时,请在社区页 Front Matter 中设置 params.contributingUrl

添加静态内容

static/ 下的文件不会经过 Markdown 渲染或指纹处理,而是原样复制到发布根目录:

static/reference/api/index.html

会发布为 /reference/api/index.html。该目录适合外部生成的参考站点、验证文件以及要求稳定文件名的下载内容。需要缩放、指纹或页面包相对寻址的资源,应优先使用页面资源或 Hugo Pipes。

OINK 的浏览器运行时有意从主题或站点自身提供。新增依赖库时,必须本地纳管并锁定版本,在 theme/VENDOR.json 中登记,而且不得引入隐式 CDN 后备地址。

RSS Feed

Hugo 会为首页和列表分区生成 Feed。只有站点确实没有 Feed 消费者时才全局关闭:

disableKinds: [RSS]

分区声明自定义输出格式时,应显式保留 RSS:

outputs:
  section: [HTML, RSS, print]

检查每种语言生成的 Feed URL,并核对标题、摘要、日期、规范 URL 与 hreflang 关系。

站点地图

Hugo 默认生成 sitemap.xml。站点级设置如下:

sitemap:
  changefreq: monthly
  filename: sitemap.xml
  priority: 0.5

页面可以覆盖这些值:

---
title: 发布说明
sitemap:
  priority: 0.8
---

应把 changefreqpriority 视为提示而非承诺。部署前应排除草稿、私有内容和非规范副本,并检查每种发布语言生成的站点地图。

2.10 - 图表与公式

在页面中添加本地图表、思维导图与科学公式。

OINK 支持 KaTeX、Mermaid、Markmap、PlantUML 和 Diagrams.net。KaTeX、Mermaid 与 Markmap 使用构建期能力或主题随附的同源资源。PlantUML 和 Diagrams.net 编辑器需要显式配置服务端点;主题不会静默使用公共服务。

使用 KaTeX 支持 LaTeX

KaTeX 可以在 Web 上渲染 TeX 数学公式。Hugo 内置的 KaTeX 支持可以在构建期间渲染公式,因此读者不需要连接远程数学服务。

行内公式

行内公式使用 Goldmark 中配置的 passthrough 分隔符。条件允许时,应把公式前后的空格与标点留在公式之外。

独立显示公式

使用 math 代码块独立显示公式:

```math
E = mc^2
```
E=mc2E = mc^2

启用 KaTeX 支持

mathchem 代码块会自动使用主题渲染钩子。对于行内公式和使用分隔符的公式,请启用 Goldmark 的 passthrough 扩展,并设置适合站点的分隔符。随仓库提供的 oink.pgsty.com 配置展示了方括号、双美元符号和圆括号分隔符。

启用 passthrough 扩展

相关 YAML 结构如下:

markup:
  goldmark:
    extensions:
      passthrough:
        enable: true
        delimiters:
          block: []
          inline: []

请根据 Hugo 文档填写分隔符数组。所选分隔符不能与站点正文或代码冲突,并且必须在所有构建环境中保持一致。

添加 passthrough 渲染钩子

对于使用分隔符的数学公式,请在站点中创建 layouts/_markup/render-passthrough.html

{{ partial "scripts/math.html" . }}

也可以把钩子放在对应布局目录下,将其限制到某种内容类型或某个分区。限制作用域可以避免把无关内容当作数学 passthrough 处理。

化学方程式与物理单位

Hugo 内置 KaTeX 支持 mhchem 扩展。化学方程式可以使用 chem 代码块;同一扩展也支持物理单位。方程式与单位语法请参阅 mhchem 手册

使用 Mermaid 绘图

Mermaid 可以在浏览器中把文本定义转换为图表。使用 mermaid 代码块:

```mermaid
flowchart LR
  源码 --> Hugo --> 静态文件
```
flowchart LR
  源码 --> Hugo --> 静态文件

主题会检测代码块、发布固定版本的本地 Mermaid 运行时,并且在该页只加载一次。不使用 Mermaid 的页面不会加载运行时。

站点级 Mermaid 设置位于 params.mermaid

params:
  mermaid:
    theme: neutral
    flowchart:
      diagramPadding: 6

每幅图也可以通过 Mermaid 支持的 front matter 覆盖设置。图表源码应保持可读,并同时测试深浅色模式。对于图表无法渲染时仍必须传达的信息,请提供相邻正文。

使用 PlantUML 绘制 UML 图

PlantUML 支持时序图、用例图、类图、状态图和其他面向 UML 的图表。plantuml 代码块包含图表源码:

```plantuml
actor Reader
participant Browser
participant "PlantUML endpoint" as Server
Reader -> Browser: Open page
Browser -> Server: Request encoded diagram
Server --> Browser: SVG
```

PlantUML 需要渲染端点。只有在配置了获准使用的本地或显式远程服务后才应启用:

params:
  plantuml:
    enable: true
    theme: default
    svg_image_url: https://plantuml.internal.example/plantuml/svg/
    svg: false

浏览器会把编码后的图表源码发送给端点。请评审其保密性、可用性、CSP 与离线影响。网络隔离站点应使用内部端点或提交预渲染图片,默认配置不能指向公共演示服务器。

使用 Markmap 支持思维导图

Markmap 可以把 Markdown 大纲转换为交互式思维导图:

```markmap
# 本地优先
## 构建
- Hugo Extended
## 浏览器
- 本地脚本
- 本地字体
```
# 本地优先
## 构建
- Hugo Extended
## 浏览器
- 本地脚本
- 本地字体

需要时可以全局启用:

params:
  markmap:
    enable: true

运行时采用固定版本并从本地提供。底层大纲本身也应有用,同时不要依赖只能通过指针完成的交互。

使用 Diagrams.net 绘图

Diagrams.netdraw.io)可以导出包含可编辑图表副本的 SVG 与 PNG。显式配置编辑器端点后,OINK 可以检测这些图片并显示 编辑 操作。

params:
  drawio:
    enable: true
    drawio_server: https://drawio.internal.example/

导出时请启用 Include a copy of my diagram。页面可以离线显示导出图片,但打开编辑器需要连接配置的服务。编辑器保存时会把更新后的文件下载到浏览器,不会直接写入文档仓库。

公共 Diagrams.net 端点属于在线集成。如果编辑过程必须留在组织内部,请部署获准使用的自托管编辑器,并让 drawio_server 指向它。

资源与创作检查清单

  • 当可评审 diff 很重要时,优先使用文本图表。
  • 为关键信息提供替代文字或相邻正文。
  • 测试深浅色、移动端、打印和减少动态效果模式。
  • theme/VENDOR.json 中固定本地运行时,并且只在使用时加载。
  • 绝不能把机密写入会发送给服务端点的图表源码。
  • 无法接受在线渲染器时,使用预渲染输出。
  • 在子路径 baseURL 下验证所有资源与端点 URL。

2.11 - 外观与风格

定制 OINK 的本地优先视觉系统、主题、字体、代码样式与布局。

OINK 在 Bootstrap 与 Docsy 基础上提供完整的视觉系统,并将字体、图标、样式和浏览器端代码全部本地化。使用方无需重建 Node 依赖树,就能通过设计变量和项目样式完成定制。

项目样式

Hugo Extended 通过 Hugo Pipes 编译主题 SCSS。项目覆盖项会进入同一个资源包,因此生产构建可以对一份同源样式表完成压缩、指纹和完整性校验。

项目样式文件

在站点的 assets/scss/ 目录中覆盖以下文件:

文件 用途
_variables_project.scss 在 Bootstrap 与 OINK 默认值之前设置变量
_variables_project_after_bs.scss 设置依赖 Bootstrap 定义的变量或映射
_styles_project.scss 在主题组件样式之后加载项目选择器

先从最小覆盖项开始:

// assets/scss/_variables_project.scss
$primary: #315f8f;
$secondary: #b4762e;
// assets/scss/_styles_project.scss
.td-content {
  --td-content-max-width: 78ch;
}

普通品牌定制不要直接修改纳管的 Bootstrap、Font Awesome 或本地字体文件。主题更新会覆盖这些改动,也会模糊依赖边界。

高级样式定制

OINK 的 SCSS 导入顺序如下:

  1. Bootstrap 函数;
  2. 项目变量;
  3. OINK 默认值与 Bootstrap;
  4. Bootstrap 之后的项目变量;
  5. OINK 组件与本地品牌层;
  6. 项目样式。

稳定的设计决策应通过变量或 CSS 自定义属性表达。没有合适设计变量时才覆盖选择器,而且作用域应尽量缩小到具体组件。许多颜色会随主题变化,因此必须检查浅色与深色输出。

⚠️ 重置内部样式

OINK 的内部 Partial 并不是公开 Sass API。单独导入或屏蔽内部文件会让站点耦合到仓库布局和导入顺序。产品确实需要完全不同的页面框架时,应覆盖 Hugo 布局或有意识地维护主题分支,而不是重置整份样式表。

额外样式

隔离的第三方 CSS 可以通过钩子发布为本地资源:

{{ $extra := resources.Get "css/extra.css" | minify | fingerprint }}
<link rel="stylesheet" href="{{ $extra.RelPermalink }}"
  integrity="{{ $extra.Data.Integrity }}" crossorigin="anonymous">

将模板放在 layouts/partials/hooks/head-end.html。如果规则属于站点设计系统,应优先写入项目 SCSS 文件。绝不能把远程样式表当作隐式后备资源。

颜色与颜色主题

主题各处都可以使用 Bootstrap 语义颜色与 OINK 品牌设计变量。语义名称比具体色值更能说明用途。

站点颜色

在编译前设置 Bootstrap 变量:

$primary: #315f8f;
$secondary: #b4762e;
$success: #2c7a4b;
$warning: #9a6700;
$danger: #b42318;

OINK 的标准品牌层还公开 --td-brand-elev--td-brand-silk--td-brand-copper--td-brand-header-bg--td-brand-mark-gradient 等 CSS 属性。应同时在 :root[data-bs-theme='dark'] 中成对覆盖:

:root {
  --td-brand-copper: #a66722;
}

[data-bs-theme='dark'] {
  --td-brand-copper: #e0a35c;
}

浅色/深色主题与模式支持

颜色 主题 是组件采用的配色方案,颜色 模式 则是整个站点当前处于浅色还是深色状态。OINK 使用 Bootstrap 的 data-bs-theme="light|dark" 属性,并把读者明确选择的模式保存在浏览器本地存储中。没有明确选择时,站点跟随 prefers-color-scheme

每个自定义组件都必须为两种模式定义可读状态,包括悬停、焦点、禁用、选中和代码颜色。不能只用颜色传递含义。

浅色/深色模式

样例站默认启用颜色模式支持并显示选择器:

params:
  ui:
    showLightDarkModeMenu: true

选择器会在页面进入正常交互前更新文档,以减少错误主题闪烁。OINK 的脚本从本地加载,不会联系外部服务。

为站点选择主题或颜色模式

多数站点应使用默认自动行为。只有完整视觉系统已经在某种模式下通过测试,而且读者确实不需要另一种模式时,才应强制指定。截图不足以完成验证:还要检查真实正文、表格、告警、表单、图表、代码和焦点指示器。

禁用深色模式

如需禁用深色模式并隐藏菜单:

params:
  ui:
    showLightDarkModeMenu: false

实验值 enable-only (experimental) 会启用主题感知样式,但不显示选择器。该配置面仍可能变化,只能作为过渡选项使用。

选择具有良好对比度的颜色

所有组件状态都应满足 WCAG 对比度要求,并以浏览器实际计算后的颜色为准,包括叠加在图片上的半透明图层。作为工作基线,普通文字的对比度至少为 4.5:1,大号文字至少为 3:1;焦点和非文本界面指示器同样需要足够对比度。自动化工具可以发现常见问题,但仍需进行键盘和人工视觉审查。

字体

OINK 不会拉取 Google Fonts。主题使用的 Open Sans、Chakra Petch、IBM Plex Mono 与 Font Awesome 字体文件均保存在本地。由于历史原因,旧版 Sass 变量 $td-enable-google-fonts 实际控制的是随主题提供的 Open Sans 字体。

_variables_project.scss 中设置字体:

$td-enable-google-fonts: true;
$font-family-sans-serif: 'Noto Sans SC', 'Open Sans', system-ui, sans-serif;
$font-family-monospace: 'IBM Plex Mono', ui-monospace, monospace;

新增字体时,应制作所需子集并自行托管,包含必要字形,设置 font-display: swap,在 theme/VENDOR.json 中记录许可证,并测试 CJK 后备字体。页面渲染不能依赖字体 CDN。

CSS 工具类

在允许原始 HTML 的 Markdown 和布局中可以使用 Bootstrap 工具类。内容优先使用语义化 Markdown 与 OINK 短代码;工具类只适合在不同断点下仍易于理解的小范围表现调整。项目级模式应写入 _styles_project.scss

代码块

OINK 默认支持 Hugo Chroma,并提供本地纳管的 Prism 兼容选项。一个站点应统一选择一种高亮器;同时启用会产生重复标记或样式。

使用 Chroma 进行代码高亮

Chroma 在 Hugo 构建期间运行,不需要浏览器端高亮器。代码块应指定语言:

```go
fmt.Println("hello")
```

Chroma 基础样式配置

在 Hugo 中配置标记渲染:

markup:
  highlight:
    guessSyntax: false
    noClasses: false
    lineNos: false

OINK 使用基于 class 的输出,以便浅色和深色模式采用不同样式。重新生成配色时,应将 CSS 保存在本地,并结合品牌背景完成审查。

浅色/深色代码样式及其他配置

主题在 theme/assets/scss/td/chroma/ 中提供两套 Chroma 配色,并按模式应用。项目覆盖项应在相应主题属性下定位 .chroma,不要硬编码全局背景。

选择控制台代码块内容

终端记录使用 console。OINK 会调整提示符和输出的选中行为,使读者复制命令时不会带上装饰性提示符。命令与输出应各占一行,而且不能只靠颜色区分。

未指定语言的代码块

没有标签的围栏会渲染为纯代码。只有确实不存在相应语法时才这样做;命令会话应标为 consolebash,不要让 Chroma 猜测。

复制到剪贴板

除非 params.disable_click2copy_chroma 为 true,否则 Chroma 会显示复制按钮。已部署站点中的剪贴板访问需要安全上下文。该控件必须支持键盘操作,而且不应复制行号或提示符。

使用 Prism 进行代码高亮

设置:

params:
  prism_syntax_highlighting: true

即可使用 OINK 本地提供的 prism.jsprism.css。这是面向既有站点的兼容选项;若要尽量减少浏览器负担,优先使用 Chroma。

没有语言的代码块

Prism 同样会把没有标签的代码块当作纯文本。应补充正确的语言 class,而不是启用启发式检测。

扩展 Prism 语言或插件

构建并纳管准确的 Prism 资源包,通过受控主题变更替换本地文件,记录版本与许可证,并添加覆盖该语言或插件的 Fixture。运行时不得从 CDN 拉取 Prism 组件。

OINK 导航栏包含项目标识、主菜单、按需显示的版本与语言选择器、颜色模式控件以及搜索。小屏幕上,溢出的主菜单项仍可通过横向滚动访问。

默认外观

导航栏使用本地品牌配色和固定的最小高度。

移动端

品牌与操作控件保持可见,主菜单可以滚动。应测试较长的中文标签、200% 缩放、触控目标、焦点顺序以及两种页面方向。

桌面端

主菜单在一行内展开;版本、语言、模式和搜索控件保持分组。不要添加过多自定义入口,以免把控件挤出视口。

覆盖图上的默认半透明效果

blocks/cover 短代码会把导航栏标记为覆盖图感知状态。导航栏起初为半透明,页面滚动后恢复常规背景。

行为通过配置调整,表现通过项目 SCSS 调整。覆盖导航栏 Partial 时,必须保留导航地标、焦点顺序、无障碍标签和响应式溢出行为。

在主题样式编译前覆盖 $td-navbar-min-height。锚点偏移、侧边栏高度、移动端换行和覆盖图都依赖该值,因此必须重新测试。

在两种模式下分别设置 --td-navbar-bg-color--td-brand-header-bg。背景为半透明时,应在所有覆盖图上验证对比度,并为滚动状态提供不透明背景。

覆盖图需要浅色前景控件时,页面可以在 Front Matter 或 cascade 中设置 ui.navbar_theme: dark。这只会调整导航栏组件样式,不会强制改变整个站点的颜色模式。

自定义覆盖图上的半透明效果

可以在站点级禁用半透明:

params:
  ui:
    navbar_translucent_over_cover_disable: true

覆盖图不可预测,或无障碍审查无法保证对比度时,应优先关闭该效果。

设置项目徽标与名称样式

徽标 Partial 覆盖项放在 layouts/partials/,源资源放在 assets/static/。具有信息含义的标志应提供有意义的替代文本;纯装饰标志应使用空替代文本。SVG 必须包含 view box,并为两种模式继承或定义颜色。

OINK 样例使用带本地渐变效果的文字标识。站点标题在语言配置中修改,视觉变量在项目 SCSS 中修改;可选择的文字能够胜任时,不要用图片替代品牌名称。

浅色/深色模式菜单

params.ui.showLightDarkModeMenu 为 true 时显示选择器。应把它留在共享导航中,使颜色状态在所有语言和页面类型中保持一致。

告警

Markdown 告警类型会映射到语义化 OINK/Bootstrap 样式。.alert-* 与告警渲染钩子应成对调整,保留可见标签或图标,并测试每种背景中的链接和行内代码。语法参见添加内容

表格

Markdown 表格具有响应式和主题感知样式。单元格应保持简洁,表头应使用真正的标题单元格;需要上下文时可在自定义 HTML 中添加标题,并在移动端测试横向溢出。不能用表格布局互不相关的内容。

自定义模板

Hugo 会优先解析站点布局,再解析主题布局。只复制确实需要修改的最小 Partial,并在同步上游时进行对比;覆盖完整 baseof.html 可能会悄然遗漏后续的无障碍与资源流水线修复。

在 head 或 body 末尾添加代码

Head 附加内容使用 layouts/partials/hooks/head-end.html,脚本或结束集成使用 layouts/partials/hooks/body-end.html。资源应自行托管,只在需要的页面加载,并与生产 CSP 保持兼容。

在页面正文前添加横幅

根据页面参数设置条件,并覆盖相应钩子或内容 Partial。横幅不得遮挡页面标题、困住键盘焦点,也不能把锚点目标挤到固定导航下方。

为 body 元素添加自定义 class

在页面 Front Matter 或分区 cascade 中设置 body_class

---
body_class: product-reference
---

OINK 会把该值追加到自动生成的 body class。请使用项目专属且有语义的名称,绝不能向该字段写入不可信内容。

2.12 - 文档版本管理

为多个文档版本自定义导航与提示横幅。

根据项目的发布和版本管理方式,你可能需要让用户访问旧版文档。旧版本的具体部署方式由你决定。本页介绍 OINK 提供的功能:在各个文档版本之间导航,并在归档站点上显示信息横幅。

添加版本下拉菜单

如果在 hugo.tomlhugo.yamlhugo.json 中添加 [params.versions],OINK 会在顶部导航栏加入版本下拉选择器。请为每个需要加入菜单的版本指定 URL 和名称,例如:

# Add your release versions here
[[params.versions]]
  version = "master"
  url = "https://master.kubeflow.org"

[[params.versions]]
  version = "v0.2"
  url = "https://v0-2.kubeflow.org"

[[params.versions]]
  version = "v0.3"
  url = "https://v0-3.kubeflow.org"
params:
  versions:
    - version: master
      url: 'https://master.kubeflow.org'
    - version: v0.2
      url: 'https://v0-2.kubeflow.org'
    - version: v0.3
      url: 'https://v0-3.kubeflow.org'
{
  "params": {
    "versions": [
      {
        "version": "master",
        "url": "https://master.kubeflow.org"
      },
      {
        "version": "v0.2",
        "url": "https://v0-2.kubeflow.org"
      },
      {
        "version": "v0.3",
        "url": "https://v0-3.kubeflow.org"
      }
    ]
  }
}

别忘了加入当前版本,这样用户才能返回!

版本下拉菜单的默认标题是 Releases。要修改标题,请在 hugo.tomlhugo.yamlhugo.json 中调整站点参数 version_menu

[params]
version_menu = "Releases"
params:
  version_menu: Releases
{
  "params": {
    "version_menu": "Releases"
  }
}

如果把 version_menu_pagelinks 参数设为 true,版本下拉菜单会链接到其他版本中的当前页面,而不是它们的首页。如果文档在不同版本之间变化不大,这项功能会很有用。请注意:如果当前页面在另一版本中不存在,链接就会失效。

还可以分别配置每个菜单项:

  • 如果菜单标签不是版本号,使用 name 代替 version
  • name 设为 --- 可添加菜单分隔线。
  • 省略 url 可渲染禁用的文本项,例如分组标题。
  • 设置 kind 可添加与类型对应的 CSS 类。详情请参阅导航与菜单
  • 即使全局 version_menu_pagelinks 参数为 true,仍可在某个菜单项上设置 pagelinks: false,让它始终链接到该版本首页。

例如:

params:
  version_menu: v1.2
  version_menu_pagelinks: true
  versions:
    - name: '**Versions**'
    - version: v1.3-dev
      kind: next
      url: https://next.example.com
    - version: v1.2
      kind: latest
      url: https://docs.example.com
    - name: ---
    - name: Preview variant
      kind: home
      pagelinks: false
      url: https://preview.example.com

要进一步了解 OINK 菜单,请参阅导航与菜单

在归档文档站点显示横幅

如果为旧版文档创建归档快照,可以在归档文档的每个页面顶部添加提示,告诉读者他们正在查看不再维护的快照,并提供指向最新版本的链接。

例如,可以查看 Kubeflow v0.6 归档文档

一个文本框,说明当前页面是不再维护的文档快照。
图 1:Kubeflow v0.6 归档文档中的横幅

要在文档站点加入横幅,请在 hugo.tomlhugo.yamlhugo.json 中完成以下修改:

  1. 将站点参数 archived_version 设为 true

    [params]
    archived_version = true
    params:
      archived_version: true
    {
      "params": {
        "archived_version": true
      }
    }
  2. 将站点参数 version 设为归档文档集的版本。例如,如果归档文档对应 0.1 版:

    [params]
    version = "0.1"
    params:
      version: 0.1
    {
      "params": {
        "version": "0.1"
      }
    }
  3. 确认站点参数 url_latest_version 包含希望读者前往的网站 URL。大多数情况下,它应该是最新版文档的 URL:

    [params]
    url_latest_version = "https://your-latest-doc-site.com"
    params:
      url_latest_version: https://your-latest-doc-site.com
    {
      "params": {
        "url_latest_version": "https://your-latest-doc-site.com"
      }
    }

2.13 - AI 智能体支持

帮助 AI 智能体和自动化工具发现并使用站点内容的可选功能,包括 Markdown 输出、HTML 中的备用链接与 llms.txt。

功能

站点显式启用后,OINK 会提供以下面向用户和机器可读的行为:

  • 支持 Markdown 输出格式。项目的 outputs 配置决定哪些页面类型发布 Markdown。
  • 发现机制:页面 HTML 的 header 会包含指向该页 Markdown 版本的 rel="alternate" 链接。
  • 查看 Markdown:页面元信息区域会显示指向 Markdown 版本的“查看 Markdown”链接。
  • llms.txt:位于站点根目录的内容清单文件。

本页其余部分介绍如何启用各项功能,并结合示例讨论相应的验证与指标

启用 Markdown 输出

Hugo 提供多种内置输出格式,其中包括 markdown。要启用 Markdown 输出,请在 Hugo 的 outputs 配置中,把 markdown 加入需要支持的页面类型。例如:

outputs:
  home: [HTML, markdown]
  page: [HTML, markdown]
  section: [HTML, RSS, print, markdown]
[outputs]
home = [ "HTML", "markdown" ]
page = [ "HTML", "markdown" ]
section = [ "HTML", "RSS", "print", "markdown" ]
{
  "outputs": {
    "home": ["HTML", "markdown"],
    "page": ["HTML", "markdown"],
    "section": ["HTML", "RSS", "print", "markdown"]
  }
}

让页面退出 Markdown 输出

如果要让某些页面不输出 Markdown,请在页面 front matter 中把 outputs 设为仅 HTML,或者在排除 markdown 的同时列出该页原本的全部默认输出格式。例如:

---
title: HTML-only test page
outputs: [HTML]
---
...

启用 llms.txt

llms.txt 是一种简单的文本格式,用来列出指向站点机器可读内容的链接。智能体可以轻松发现和解析它,它也能补充信息更丰富但结构更复杂的 Markdown 输出。进一步了解请参阅 llmstxt.org

OINK 会在站点根目录生成 llms.txt,其中包含首页、主菜单页面,以及存在时的 Markdown 备用版本链接。要启用它,请在 Hugo 的 outputs 配置中为首页添加 LLMS。例如:

outputs:
  home: [HTML, markdown, LLMS]
  page: [HTML, markdown]
  section: [HTML, RSS, print, markdown]

本站生成的 llms.txt 示例请参阅 /llms.txt

自定义输出

OINK 通过 layouts/all.md 渲染 Markdown 输出,并通过 layouts/index.llms.txt 生成 llms.txt。你可以在多个层级覆盖默认行为:

  • 按类型:在项目的 layouts/ 下添加 home.md_default/single.md 等模板,为特定 Hugo 类型定制 Markdown 输出。
  • 按短代码:为项目本地短代码添加输出格式专属短代码模板,使其在适当场景输出便于 Markdown 使用的内容。
  • 按页面:为需要精心设计智能体视图的高价值页面提供专属内容或结构。

服务端支持

虽然不属于 OINK 的支持范围,站点仍可通过服务端内容协商,帮助智能体发现和访问 Markdown 内容。例如,在与 HTML 相同的 URL 上响应 Accept: text/markdown

验证与指标

我们使用 AFDocs 评估面向智能体内容的基础结构支持,并验证生成的输出是否满足配置的检查项。我们也鼓励站点针对智能体访问模式实现自己的监控和指标,例如记录对 Markdown URL 或 llms.txt 的请求,并统计其使用情况。详情请参阅智能体支持检查

oink.pgsty.com 项目包含 AFDocs 配置和 npm 脚本,维护者可据此对已部署 URL 评分。这些检查与 OINK 的智能体支持目标有重合,包括 Markdown URL、llms.txt 和相关类别。

评分表示例

评分表示例包括:

  • OpenTelemetry 智能体评分在线报告;

  • 本站的 AFDocs 评分表:

    oink.pgsty.com 评分表

    Running in oink.pgsty.com…

    Agent-Friendly Docs Scorecard

    http://localhost:1313 · 4/26/2026, 5:43:59 AM

    Overall Score: 100 / 100 (A+)

    Category Scores: Content Discoverability 100 / 100 (A+) Markdown Availability 100 / 100 (A+) Page Size and Truncation Risk 100 / 100 (A+) Content Structure 100 / 100 (A+) URL Stability and Redirects 100 / 100 (A+) Observability and Content Health 100 / 100 (A+) Authentication and Access 100 / 100 (A+)

    Check Results:

    Content Discoverability
        PASS  llms-txt-exists                llms.txt found at 1 location(s)
        PASS  llms-txt-valid                 llms.txt follows the proposed structure (H1, blockquote, heading-delimited link sections)
        PASS  llms-txt-size                  llms.txt is 1,131 characters (under 50,000 threshold)
        PASS  llms-txt-links-resolve         All 13 same-origin links resolve (13 total links)
        PASS  llms-txt-links-markdown        13/13 same-origin links point to markdown content (100%)
        PASS  llms-txt-directive             llms.txt directive found in all 13 pages, near the top of content
      
      Markdown Availability
        PASS  markdown-url-support           13/13 pages support .md URLs (100%)
        PASS  content-negotiation            13/13 pages support content negotiation (100%)
      
      Page Size and Truncation Risk
        PASS  rendering-strategy             All 13 pages contain server-rendered content
        PASS  page-size-markdown             All 13 pages under 50K chars (median 2K, max 9K)
        PASS  page-size-html                 All 13 pages convert under 50K chars (median 2K, 0% boilerplate)
      
      Content Structure
        PASS  tabbed-content-serialization   No tabbed content detected across 13 pages
        PASS  section-header-quality         No tabbed content found; header quality check not applicable
        PASS  markdown-code-fence-validity   All 1 code fences properly closed across 14 pages
      
      URL Stability and Redirects
        PASS  http-status-codes              All 13 pages return proper error codes for bad URLs
        PASS  redirect-behavior              No redirects detected across 13 pages
      
      Observability and Content Health
        PASS  cache-header-hygiene           All 14 endpoints have appropriate cache headers
      
      Authentication and Access
        PASS  auth-gate-detection            All 13 pages are publicly accessible
        SKIP  auth-alternative-access        All docs pages are publicly accessible; no alternative access paths needed
      

    Full spec: https://agentdocsspec.com/spec/

这些检查的配置详情请参阅智能体支持检查


  1. 这与 Hugo 文档描述的 front matter 配置行为不同,但截至 Hugo 0.158.0,我们的测试确认实际行为如此。 ↩︎

3 - OINK

产品边界、架构与运作方式

Oink 是一款从 Docsy 演化而来的独立、本地优先 Hugo 文档主题。它保留 Docsy 成熟的内容模型,同时把一套实现确立为标准产品:文档外壳、仅依赖 Hugo 的消费端构建、本地浏览器运行时、多语言基础设施,以及可复用的内容组件。

公开 Hugo Module 是 github.com/pgsty/oink;文档与回归内容独立存放在 github.com/pgsty/oink.pgsty.com

产品契约

唯一标准主题

Oink 不是叠加在另一套 Docsy 安装之上的皮肤。项目不存在 oink.enabled 开关、不建立 params.oink.* 命名空间,也没有需要同步维护的第二套视觉实现。主题仓库根目录中的布局与资源就是产品本身。

语言、模块、菜单、输出与标记设置使用 Hugo 原生配置;语义仍然适用时沿用 Docsy 现有参数;只有当主题确实需要用户做出选择时,才增加职责单一的参数。

消费端仅依赖 Hugo 构建

站点导入模块后,生产构建命令只有:

hugo --gc --minify

消费站点无需安装 Node.js、npm、PostCSS、Autoprefixer 或浏览器端软件包。项目站点仓库中的维护工具不属于消费端构建契约。

默认本地优先

Bootstrap、Font Awesome、Web 字体、本地搜索、图表与 API 文档运行时,以及 Oink 内容组件都随主题提供。资源从生成后的站点提供;在可行的情况下,只有实际使用相应能力的页面才会加载它们。

作者仍可链接互联网、嵌入远程媒体、启用托管服务,或配置 PlantUML 与 Diagrams.net 端点。但这些网络边界必须显式声明;对于主题自带能力,Oink 不会暗中选择公共端点。

把多语言作为基础设施

语言行为完全根据 Hugo 已配置的语言和页面译文推导。Oink 会输出语言、书写方向、canonical、hreflang 和 Open Graph locale 元数据,并支持 .md.zh.md 这样的并置译文。

交付内容

  • 响应式文档与博客外壳、导航、搜索、打印输出、深色模式和移动端行为;
  • 本地 Mermaid、KaTeX、Markmap、Swagger UI、Redoc、Asciinema、ECharts 与 Infographic 运行时;
  • 折叠块、标签页、卡片、导航卡片、文档卡片和轮播;
  • 翻译资源,以及记录来源、许可证和校验值的版本化 VENDOR.json
  • Hugo 模块声明、Apache-2.0 许可证与必需归属信息。

不在交付范围内的内容

主题仓库不包含项目网站、生成的 public/ 输出、npm workspace、产品专用控件或部署配置。这些职责留在消费站点或独立的项目站点仓库中。

生产站点应固定发布标签或不可变 commit,而不是跟随 main

代码仓库

仓库 用途
pgsty/oink 发布主题与 Hugo Module
pgsty/oink.pgsty.com 文档、示例、测试与部署

本地开发主题时,把两个仓库克隆为同级目录,并用被忽略的 Go workspace 连接。

项目状态

当前验证基线为 Hugo Extended 0.164.0,主题声明的最低版本为 0.160.1。本地构建成功,本身并不能证明公开标签、托管站点或下游部署已经存在。

项目保留 Docsy 的 Apache-2.0 历史与归属信息。源码与离线发行包必须保留 LICENSENOTICE 以及适用的第三方声明。

后续步骤

  1. 安装 Hugo Module
  2. 阅读架构本地优先模型
  3. 查看内容组件配置
  4. 选择部署方案,并遵循发布检查表
  5. 从现有 Docsy 站点迁移时,请从迁移指南开始。

3.1 - 快速开始

以 Hugo Module 方式为站点添加 Oink

Oink 以 Hugo Module github.com/pgsty/oink 发布。消费站点只需 Hugo Extended 即可构建;Node.js、npm、PostCSS 和 CDN 托管的浏览器软件包不属于构建契约。

前置条件

安装 Git、Go 与 Hugo Extended 0.160.1 或更高版本。项目站点目前使用 0.164.0 验证:

git --version
go version
hugo version

Hugo 版本输出必须包含 extended

添加模块

在 Hugo 站点根目录中,如果站点还没有模块,先初始化模块,再固定一个 Oink 版本:

hugo mod init github.com/example/product-docs
hugo mod get github.com/pgsty/oink@THEME_REF

请把 THEME_REF 替换为 v0.16.0 之类的已发布标签,或不可变的 commit。然后在 hugo.yaml 中添加导入:

module:
  imports:
    - path: github.com/pgsty/oink

提交生成的 go.modgo.sum。生产构建不要跟随未固定版本的分支。

预览站点

启动编辑服务器:

hugo server --disableFastRender

生成生产构建产物:

hugo --gc --minify

Oink 已随主题提供 Bootstrap、Font Awesome、字体、搜索、图表、API 文档运行时和内容组件。消费站点不需要 node_modules 目录。

使用本地 checkout 开发

把主题与站点克隆为同级目录,再使用本地 Go workspace:

~/pgsty/
├── oink/
└── product-docs/
cd ~/pgsty/product-docs
go work init .
go work edit -replace=github.com/pgsty/oink=../oink
export HUGO_MODULE_WORKSPACE=go.work
hugo server

不要把 go.work 提交到版本库。已提交的 go.mod 仍固定公开模块;workspace 只在本机把它替换为同级 checkout。

添加双语内容

先创建英文页面:

content/docs/operations.md

然后在旁边添加译文:

content/docs/operations.zh.md

front matter 标识符、代码、命令、参数名与链接目标应保持语义一致;面向读者的正文则需要翻译。为了让不同语言下的深层链接保持稳定,请在中文标题中显式保留英文标题 ID:

## 故障恢复 {#failure-recovery}

配置最小站点

最基本的配置很精简:

title: Product Docs
baseURL: https://docs.example.com/
defaultContentLanguage: en

languages:
  en:
    label: English
    locale: en-US
    weight: 1
  zh:
    label: 简体中文
    locale: zh-CN
    weight: 2

params:
  logo: icons/logo.svg
  offlineSearch: true

module:
  imports:
    - path: github.com/pgsty/oink
  hugoVersion:
    extended: true
    min: 0.160.1

站点扩展后,再逐步加入菜单、输出格式、Markdown 扩展、仓库链接和可选功能。支持的配置模型详见配置

发布前验证

至少完成以下检查:

  1. 使用已经提交模块文件的全新 checkout 构建;
  2. 使用固定版本的 Hugo Extended 运行 hugo --gc --minify
  3. 浏览具有代表性的英文与中文页面;
  4. 验证语言切换、搜索、移动导航、深色模式与打印输出;
  5. 如果站点承诺离线运行,检查浏览器网络请求。

这些检查只能证明构建产物成立。发布该产物并验证托管地址,是两个独立的部署步骤。

3.2 - 架构

Oink 如何把内容与本地资源构建成文档站点

Oink 是一款直接运行于 Hugo 的主题,而不是应用服务器,也不是套在 Docsy 外面的运行时包装层。Hugo 在构建阶段解析内容、配置、布局与资源,再生成可由普通文件托管服务发布的静态站点。

系统边界

flowchart LR
  C[站点内容] --> H[Hugo Extended]
  G[Hugo 配置] --> H
  T[Oink Hugo Module] --> H
  V[已提交的第三方资源] --> T
  H --> P[静态 public 目录]
  P --> B[浏览器]

消费端边界始于站点与已经解析的主题模块,止于 Hugo 生成的静态文件。在这条路径中,不需要 JavaScript 包管理器、CSS 后处理器可执行文件或远程资源下载。

交互功能仍会在浏览器中运行 JavaScript。“仅依赖 Hugo”描述的是构建依赖,并不意味着用户界面完全没有 JavaScript。

仓库边界

主题仓库

github.com/pgsty/oink 是公开 Hugo Module。仓库根目录包含标准布局、partial、短代码、SCSS、JavaScript、字体、图标、浏览器运行时、翻译资源、go.modhugo.yamlVENDOR.json 记录随附的第三方资源。

该仓库不包含项目网站或 npm workspace。README.mdLICENSENOTICEtheme.toml 与 vendor 清单等根元数据,是发布和标注主题来源所必需的内容。

项目站点仓库

github.com/pgsty/oink.pgsty.com 包含文档、双语示例、回归页面、站点专用布局与资源、基于 npm 的站点测试,以及部署配置。它在 hugo.yaml 中导入公开主题模块,并在 go.mod 中固定版本。

跨仓库本地开发时,被忽略的 go.work 会替换为同级主题 checkout;站点模块不会提交相对文件系统 replacement。

构建流水线

Hugo 会合并四类输入:

  1. 消费站点的页面 bundle 与 Markdown 内容;
  2. Hugo 原生配置和受支持的主题参数;
  3. 主题模板、翻译、SCSS 与 JavaScript;
  4. 已提交的 static 或 Hugo Asset 资源。

Hugo 使用内置流水线编译 SCSS、打包页面 JavaScript、压缩生产资源、为适用产物生成指纹,并按照配置的 baseURL 重写相对 URL。Oink 不调用 Hugo 的 postCSS pipe。

最终 public/ 目录包含 HTML、CSS、JavaScript、字体、搜索索引、feed、sitemap 与复制的静态文件;部署时不需要源码树。

页面外壳

标准页面外壳由小型 partial 组装:

  • 全局 navbar 与响应式次级导航;
  • 语言和颜色模式控件;
  • 可调整宽度、可折叠的文档侧栏;
  • 面包屑、目录、阅读元数据、反馈与仓库链接;
  • 公共页脚与打印布局。

Hugo 的正常模板查找机制仍可用于站点扩展。应覆盖最小范围的 partial,而不是复制 baseof.html 或整个外壳。

条件运行时加载

内容短代码会在 page store 中记录功能使用情况。资源 partial 检查这些标记,并且最多加入一次对应本地运行时:

flowchart TD
  S[短代码渲染] --> M[设置页面功能标记]
  M --> A[组装资源]
  A --> Q{是否使用功能?}
  Q -- 是 --> L[加入一次本地运行时]
  Q -- 否 --> O[省略运行时]

因此普通文章不会加载 ECharts、Asciinema 或 Infographic,同时功能页仍可包含多个组件实例。

多语言路由

Oink 把语言身份交给 Hugo 管理。选择器使用每页的 .Translations 与按权重排序的站点语言。缺少译文时回退到目标语言首页;同一组数据也用于 canonical 与 alternate 元数据。

安全边界

Oink 区分作者数据与作者提供的可执行代码:

  • 结构化 ECharts 选项按 JSON 或 YAML 解析并安全序列化;
  • ECharts 中的 JavaScript 默认拒绝,除非显式启用 unsafe 迁移开关;
  • 组件 ID 与配置由模板生成,不通过未转义 HTML 字符串拼装;
  • 托管搜索、分析、评论、远程媒体与服务端点始终由站点显式决定。

Goldmark 的 unsafe 设置允许受信任的项目作者使用行内 HTML;它不是针对不受信任输入的净化器。

上游维护

Oink 保留 Docsy 的源码历史与 Apache-2.0 义务。上游变更会被分类为适用、已被 Oink 有意差异取代,或无关。适用变更会移植到标准实现中,而不会重新制造上游与品牌两套运行模式。

扩展边界

一项实现如果广泛可复用、具有稳定内容 API,并能自行管理资源与无障碍行为,就应放入主题;如果它嵌入产品数据、价格、目录假设或一次性落地页结构,则应保留在站点中。

3.3 - 本地优先运行

在没有隐藏网络依赖的情况下构建并浏览 OINK 核心功能

OINK 的本地优先原则很简单:由主题提供的功能,不得暗中依赖公共 CDN、构建期下载或未经配置的公共服务。完整发行包能够在网络隔离环境中构建,其核心页面也能在该环境中浏览。

本地优先覆盖的范围

主题会从生成后的站点提供以下依赖:

能力 本地交付方式
页面外壳与响应式界面 Bootstrap 与 OINK CSS/JavaScript
图标与字体 Font Awesome、Open Sans、Chakra Petch、IBM Plex Mono
搜索 Lunr、CJK 子串回退与按语言生成的索引
图表与公式 Mermaid、KaTeX 与 Markmap
API 文档 Swagger UI 与 Redoc
富内容 Asciinema、ECharts、Infographic 与轮播运行时

这些资源提交在 assets/static/ 中。Hugo 会按照站点 baseURL 发布它们,部署在子路径时也不例外。

本地优先不覆盖的范围

OINK 无法让作者任意添加的内容自动离线。以下项目仍然是显式网络选择:

  • 外部链接、远程图片、视频、iframe 与 API 规范;
  • Algolia、Google CSE 等托管搜索;
  • 分析、评论、身份提供商与其他 SaaS 集成;
  • 作者主动选择远程渲染器时的 PlantUML 或 Diagrams.net。

使用这些功能的页面仍然可以是有效页面,但站点不应再宣称该页面能够完全离线使用。

依赖服务的图表

PlantUML 与 Diagrams.net 不同于纯浏览器库:其常规工作流依赖渲染或编辑服务。因此 OINK 不提供隐含的公共端点。

启用 PlantUML 却未设置 params.plantuml.svg_image_url,或启用 Diagrams.net 却未设置 params.drawio.drawio_server 时,构建会失败并给出可操作的错误信息。你可以配置受控的本地端点、发布预渲染图片,或者明确选择远程服务:

params:
  plantuml:
    enable: true
    svg: true
    svg_image_url: https://diagrams.internal.example/plantuml/svg/
  drawio:
    enable: true
    drawio_server: https://diagrams.internal.example/

为了继续渲染继承而来的图表示例,OINK 文档回归站显式配置了公共演示服务。这是样例站自己的选择,不是主题默认值,也不应复制到网络隔离站点。

设置:

params:
  offlineSearch: true

Hugo 会为每种语言生成搜索索引。浏览器对拉丁文字查询使用本地 Lunr 搜索,对 CJK 文本使用本地子串回退。查询内容不会离开站点。

为了提高搜索质量,请编写清晰的标题与摘要、设置正确的页面语言,并排除不应进入公开客户端索引的生成页面或敏感页面。本地索引可被每位访客下载,不能充当访问控制手段。

页面级资源加载

OINK 不会给每个页面都加载所有运行时。Mermaid、KaTeX、Markmap、Swagger UI、Redoc、Asciinema、ECharts、Infographic 与轮播,会根据页面功能标记按需选取。不使用某个组件的页面不会收到对应运行时。

同一页包含多个相同组件实例时,运行时仍只会加入一次。在 Hugo 流水线允许的情况下,生产资源会生成指纹,便于提供完整性元数据并使用长期缓存。

第三方来源追踪

VENDOR.json 是随附依赖的机器可读清单。每项依赖都会记录:

  • 名称与固定版本;
  • 原始来源;
  • 适用的许可证文件;
  • 已选取产物的路径与 SHA-256 值;
  • 维护者更新流程。

主题会在 vendor 资源旁保留相应许可证。更新运行时意味着同时更新产物、许可证与 NOTICE 材料、校验值和测试,使整个变更可以一次性审查。

获取离线归档

使用 Oink release 附带的版本化主题归档与 checksum。把两个文件传入隔离环境后运行:

shasum -a 256 -c oink-vX.Y.Z.tar.gz.sha256
tar -xzf oink-vX.Y.Z.tar.gz
mkdir -p product-docs/themes
mv oink-vX.Y.Z product-docs/themes/oink

让隔离站点使用解压后的传统主题:

theme: oink

归档必须包含 go.modhugo.yaml、布局、资源、静态文件、翻译、LICENSENOTICEVENDOR.json。在断网构建中使用前,应先检查归档内容。

验证隔离站点

有意义的网络隔离验收必须同时覆盖构建阶段与浏览器阶段:

  1. 从已经验证的主题归档和空 Hugo 缓存开始;
  2. 阻断出站 HTTP、HTTPS 与 Go Module 代理;
  3. 运行 Hugo 生产构建命令;
  4. 浏览生成结果中的英文与中文页面;
  5. 操作搜索、深色模式、图表、API 文档与内容组件;
  6. 检查全部 HTML 和 CSS 子资源 URL,确认没有意外远程来源。

项目站点回归套件会针对本地主题候选版本执行这些检查。一次成功只能证明被测提交与环境;每个候选版本以及每次随附依赖更新后都应重新验证。

内容安全策略

本地资源让严格的内容安全策略(CSP)更容易实现,但 OINK 不会为所有站点虚构一份万能策略。作者行内 HTML、ECharts unsafe 模式、分析服务、远程规范与自定义集成都可能改变所需指令。

请从能够支持已审查功能的最小策略开始。让 ECharts 保持结构化数据模式,避免任意行内脚本,只为站点主动启用的集成增加远程来源。

3.4 - 内容组件

OINK 新增的本地、可复用内容组件

OINK 把已经在多个 PGSTY 站点证明具有复用价值的内容组件纳入主题。每个组件都有稳定的作者接口、唯一实例 ID、本地资源和明确的安全边界。站点专用的数据控件仍然留在主题之外。

加载模型

交互式短代码会标记页面实际使用的功能。OINK 随后为每个所需样式或运行时只加入一次,即使页面中存在多个组件实例也不例外。普通页面不会下载从未使用的组件代码。

相对资源与链接参数会经过 Hugo URL 处理,因此部署到 baseURL 子路径时仍然正确。在适用情况下,组件标记还覆盖打印、深色模式、移动端、键盘操作与减少动态效果偏好。

Asciinema

使用 asciinema 播放保存在本地的 .cast 终端录像:

{{< asciinema
  file="oink/demo.cast"
  speed="1.5"
  markers="0:开始,1:完成"
>}}

file 是必填参数,也可以作为第一个位置参数传入。支持的选项包括 themefitwidthheightbothnone)、autoplaylooppreloadspeedstartAtpostercolsrowsidleTimeLimitpauseOnMarkers,以及逗号分隔的 markers

为了离线使用,请把 cast 文件保存在本地。只有作者显式提供远程 URL 时,组件才会访问远端。

ECharts

默认安全模式接受 JSON 或 YAML,并把解析后的值序列化到 application/json 元素中:

{{< echarts height="280px" >}}
xAxis: { type: category, data: [源码, 构建, 发布] }
yAxis: { type: value }
series: [{ type: bar, data: [1, 2, 3] }]
{{< /echarts >}}

height 默认为 400px,并且必须使用安全的 CSS 长度单位。theme 用来选择 ECharts 主题,full=true 则移除通常的正文宽度限制。

旧页面可能包含 JavaScript 围栏代码块与 $fn:name 引用。除非短代码设置 unsafe=true,或站点临时启用以下开关,否则 OINK 会拒绝这种可执行形式:

params:
  content:
    echarts_unsafe: true

该开关只能用于经过审查的迁移过程。新图表应始终采用结构化 JSON/YAML 模式。

Infographic

infographic 使用本地运行时渲染 AntV Infographic DSL:

{{< infographic >}}
infographic list-row-simple-horizontal-arrow
data
  items
    - label 源码
      desc Markdown 与配置
    - label 构建
      desc Hugo Extended
    - label 发布
      desc 静态文件
{{< /infographic >}}

height 接受 auto 或安全 CSS 长度;full=true 会移除通常的正文宽度限制。DSL 会作为数据序列化,而不是作为可执行脚本插入页面。

doc-cardnav-card 共用同一套卡片实现;doc-cardsnav-cards 可以创建一至四列的响应式卡片组。这些别名让现有站点内容继续使用语义最贴切的名称,同时避免复制标记与样式。

{{< nav-cards cols="3" >}}
  {{< nav-card
    title="架构"
    link="/zh/docs/oink/architecture/"
    icon="fa-solid fa-diagram-project"
    desc="了解构建与运行时边界。"
  >}}
  {{< nav-card
    title="部署"
    link="/zh/docs/oink/deployment/"
    badge="仅依赖 Hugo"
  >}}发布静态输出。{{< /nav-card >}}
{{< /nav-cards >}}

了解构建与运行时边界。

部署仅依赖 Hugo

卡片接受 titlelinkimagealticondescaccentbadge。卡片正文可以包含 Markdown 链接。desc 中的 {version} 之类 token,在站点参数存在同名值时会被替换。

把文档卡片放进 doc-carousel,即可生成无障碍横向轮播:

{{< doc-carousel label="OINK 工作流" >}}
  {{< doc-card title="编写" >}}创建成对内容。{{< /doc-card >}}
  {{< doc-card title="构建" >}}运行 Hugo Extended。{{< /doc-card >}}
  {{< doc-card title="验证" >}}检查静态站点。{{< /doc-card >}}
{{< /doc-carousel >}}

label 提供轮播的无障碍名称。方向键与可见的上一个/下一个按钮都能移动轨道;启用减少动态效果偏好时,不必要的动画会被禁用。

折叠块

details 输出原生 detailssummary 元素:

{{% details title="为什么只依赖 Hugo?" closed="false" %}}
已经提交的浏览器资源让消费端构建保持可复现。
{{% /details %}}
为什么只依赖 Hugo?
已经提交的浏览器资源让消费端构建保持可复现。

title 设置摘要。折叠块默认关闭;设置 closed=false 可让它初始展开。

标签页

OINK 沿用 Docsy 的 tabpanetab 创作模型,同时保留导入站点依赖的 selected=true 与空白处理行为:

{{< tabpane text=true >}}
  {{< tab header="本地" selected=true >}}
  使用完整本地主题构建。
  {{< /tab >}}
  {{< tab header="Cloudflare" >}}
  从源分支运行同一条 Hugo 命令。
  {{< /tab >}}
{{< /tabpane >}}
使用完整本地主题构建。
从源分支运行同一条 Hugo 命令。

Markdown 内容应设置 text=true;否则标签页会按代码进行语法高亮。标签页还支持按语言保存选择、禁用标签,以及右对齐条目。生成的标签与面板 ID 会形成正确的 ARIA 对应关系。

参数

param 输出页面参数;页面没有该参数时,会回退到同名站点参数:

当前版本:{{< param version >}}

当前版本:v0.16.0

指定参数不存在时,短代码会让构建失败。这是有意设计:缺少发布版本或仓库信息时,不应悄悄生成误导性文档。

现有富内容能力

OINK 也为继承而来的内容功能提供本地运行时:

  • mermaidmathmarkmap 围栏代码块;
  • swaggeruiredoc API 文档短代码;
  • Docsy blocks、alert、image、include、readfile、cards 等既有短代码。

完整创作参考详见短代码图表和公式

创作规则

  • 优先使用结构化数据,而不是可执行内容。
  • 为图片编写有意义的 alt 文本,并为轮播设置清晰的 label
  • 除非内容确实需要,否则不要启用自动播放。
  • 创建新包装组件时,要在同一页测试多个完全相同的实例。
  • 检查键盘导航、焦点可见性、深浅色主题、移动布局、打印输出与减少动态效果行为。
  • 把带有业务语义的数据组件留在消费站点。

3.5 - 配置

使用 Hugo 原生设置与职责明确的主题参数配置 OINK

OINK 遵循“原生优先”的配置模型。站点身份、语言、菜单、输出、taxonomy、标记与模块继续放在 Hugo 规定的位置;语义仍然适用的 Docsy 参数也保持原位。只有无法可靠推导的行为选择,OINK 才会增加职责明确的配置。

配置原则

  1. 优先使用 Hugo 配置,不创建主题专用的重复项。
  2. 优先使用成熟的 Docsy 参数,不另造 OINK 同义词。
  3. 品牌、内容、仓库与 UI 选项应放在各自语义位置。
  4. 内部 vendor 路径与模板组装方式不属于公开 API。
  5. 遇到非法值或缺少必需端点时,应尽早失败。

OINK 不提供 oink.enabled 开关,也不建立 params.oink.* 配置树。增加这些配置会制造第二套主题模式,让每项修复、测试和文档都产生歧义。

完整基线配置

以下示例把英文设为首要语言、简体中文设为第二语言:

title: Product Documentation
baseURL: https://docs.example.com/
defaultContentLanguage: en
enableRobotsTXT: true

languages:
  en:
    label: English
    locale: en-US
    weight: 1
    title: Product Documentation
    menus:
      main:
        - { name: Docs, pageRef: /docs, weight: 10 }
        - { name: Blog, pageRef: /blog, weight: 20 }
  zh:
    label: 简体中文
    locale: zh-CN
    weight: 2
    title: 产品文档
    menus:
      main:
        - { name: 文档, pageRef: /docs, weight: 10 }
        - { name: 博客, pageRef: /blog, weight: 20 }

outputs:
  home: [HTML]
  section: [HTML, RSS, print]

markup:
  goldmark:
    renderer:
      unsafe: true
    extensions:
      passthrough:
        enable: true
        delimiters:
          block: [['\[', '\]'], ['$$', '$$']]
          inline: [['\(', '\)']]
  highlight:
    noClasses: false

params:
  logo: icons/logo.svg
  offlineSearch: true
  offlineSearchIndex: summary
  offlineSearchMaxResults: 10
  github_repo: https://github.com/example/product-docs
  github_branch: main
  footer_icp: ''
  footer_icp_url: https://beian.miit.gov.cn/
  copyright:
    authors: Example Authors
    from_year: 2026
  ui:
    showLightDarkModeMenu: true
    quick_links: [docs, blog]
    sidebar_menu_foldable: true
    sidebar_item_overflow: wrap
    breadcrumb_disable: false

module:
  imports:
    - path: github.com/pgsty/oink
  hugoVersion:
    extended: true
    min: 0.160.1

模块版本固定在站点的 go.mod 中。使用传统主题 checkout 时,可以把仓库放在 themes/oink/,并改用 theme: oink

语言

defaultContentLanguage 决定不带路径前缀的首要站点;语言 weight 控制显示顺序;label 是该语言的自称;locale 提供完整的 HTML 与 SEO locale。对于 RTL 语言,还应设置 languageDirection: rtl

文件命名

本站使用并置模型:

content/docs/guide.md
content/docs/guide.zh.md

基本名称相同的文件互为译文,其逻辑页面身份应保持一致。OINK 读取 Hugo 建立的翻译关系,不会根据任意 URL 模式猜测。

选择器状态

语言选择器不需要模式参数。只配置一种语言时隐藏;配置两种或更多语言时,点击语言图标会按 weight 顺序切换到下一种语言,悬停半秒或聚焦图标则打开完整菜单。

当前页面缺少目标译文时,会进入目标语言首页。不要为了让选择器停留在同一路径而生成貌似存在、实际失效的页面 URL。

品牌与代码仓库

请设置站点与各语言的 title 和描述。params.logo 可以指向 Hugo Asset,也可以指向 static/ 下的路径。favicon 与社交分享图应放在文档指定的资源位置。

仓库元数据用于生成“编辑此页”、问题反馈和最后修改记录链接:

params:
  github_repo: https://github.com/example/product-docs
  github_project_repo: https://github.com/example/product
  github_branch: main
  github_subdir: site

在支持的位置,github_project_repo 默认回退到 github_repogithub_subdir 是内容站在 monorepo 中的路径。github_branch 必须能够解析;用于展示的版本号不一定是 Git ref。

OINK 沿用 Docsy 菜单与 UI 参数,并增加职责明确的外壳控制项:

params:
  page_width: normal
  ui:
    quick_links: [docs, blog]
    sidebar_width_min: 220
    sidebar_width_max: 480
    sidebar_item_overflow: wrap
    sidebar_menu_compact: true
    sidebar_menu_foldable: true
    sidebar_root_enabled: true
    sidebar_root_menu: true
    sidebar_search_disable: false
    breadcrumb_disable: false
    showLightDarkModeMenu: true
    page_context_menu:
      enable: true
      links: []
    readingtime:
      enable: true

page_width 接受 normalwidefull,也可以在页面 front matter 中覆盖。侧栏最小与最大值以像素为单位,用来限制桌面端拖动调整的范围。sidebar_item_overflow: wrap 会让长标签换行;其他值保持紧凑的省略号行为。

quick_links 指定外壳中显示的顶层 page reference。请在各语言主菜单中定义相应的本地化名称。

页面上下文菜单在所有视口宽度下都把“复制 Markdown”“查看 Markdown”、编辑、反馈与打印入口放在页面标题旁。links 默认为空,因此站点未主动启用时,不会向外部 AI 服务发送页面信息。自定义链接可使用经过 URL 编码的 {url}{title}{markdown_url} 占位符:

params:
  ui:
    page_context_menu:
      enable: true
      links: []
      # - name: 询问外部助手
      #   icon: fa-solid fa-wand-magic-sparkles
      #   url: https://assistant.example/new?source={markdown_url}&title={title}

首页内容位于 data/home/<language>.yaml;缺少相应语言数据时回退到英文。可配置的顶层区块包括 herometricscapabilitiesprinciplesctafooter。每个区块都可以省略,因此无需复制布局也能得到更精简的首页。例如:

hero:
  eyebrow: 本地优先的产品文档
  title_lines:
    - words:
        - { mark: P, text: roduct, color: red }
        - { mark: D, text: ocs, color: blue }
  lead: 只用 Hugo 构建和交付的技术文档。
  actions:
    - { label: 阅读文档, url: docs/, icon: fa-solid fa-book, style: primary }

footer:
  brand:
    name: Product Docs
    tagline: 支持 **Markdown** 的简短介绍。
    slogan: 让答案离产品更近。
  columns:
    - title: 产品
      links:
        - { label: 概览, url: docs/ }

首页会在通用小页脚上方渲染品牌与导航组成的大页脚。小页脚左侧来自 params.copyright,中间使用可选的 params.footer_icpparams.footer_icp_url,右侧列出所有已配置语言。版权作者与大页脚品牌文字中的 Markdown 会渲染为真实链接与行内标记。

starter 默认使用本地搜索:

params:
  offlineSearch: true
  offlineSearchIndex: summary
  offlineSearchSummaryLength: 70
  offlineSearchMaxResults: 10

offlineSearchIndex 控制每种语言索引中可下载的文本范围,四档范围逐级累加:title 索引标题与分类元数据;heading 增加页面标题;summary 增加描述或摘要;content 再加入完整正文。content 是兼容旧行为的默认值,而多数文档站可从体积更小的 summary 开始。offlineSearchMaxResults 同时约束 Lunr 与 CJK 子串兜底结果数。

每种语言都会得到独立索引。通过 Docsy 既有配置仍可使用托管搜索,但启用它们会显式增加外部服务边界。除非已经决定界面应显示哪一种,否则不要同时配置多个相互竞争的搜索提供方。

内容运行时

纯浏览器运行时

Mermaid 与 KaTeX 会根据内容自动检测;Markmap 需要在站点级启用:

params:
  markmap:
    enable: true
  mermaid:
    theme: default

Swagger UI、Redoc、Asciinema、ECharts、Infographic 与轮播资源会在相应短代码出现时加载。它们的本地运行时路径属于内部实现,不应配置。

服务端点

PlantUML 与 Diagrams.net 需要显式端点:

params:
  plantuml:
    enable: true
    svg: true
    svg_image_url: https://diagrams.internal.example/plantuml/svg/
  drawio:
    enable: true
    drawio_server: https://diagrams.internal.example/

网络隔离站点应保持这些功能关闭,除非上述 URL 可以在隔离网络内部访问。

ECharts 迁移开关

结构化 ECharts 输入默认安全:

params:
  content:
    echarts_unsafe: false

只有迁移包含 JavaScript 且已经审查的旧页面时,才把它设为 true。更好的做法是在最小范围的短代码实例上设置 unsafe=true,随后重写图表并删除例外。

页面级覆盖

Hugo 的 .Param 查找机制允许在 front matter 中覆盖许多站点参数:

---
title: Wide reference
page_width: wide
hide_feedback: true
hide_readingtime: true
ui:
  no_left_sidebar: false
  scrollSpy:
    disable: false
---

只应为真实的内容差异使用覆盖,不要靠逐页设置重建另一套视觉系统。

避免虚假配置

不要暴露:

  • 在“Docsy”与“OINK”外壳之间切换的开关;
  • vendor JavaScript、CSS、字体或内部 partial 的路径;
  • 品牌命名空间下重复的语言或仓库值;
  • 只用于二选一复制实现的开关。

如果站点需要定制产品矩阵或门户,请把该组件留在站点,并使用范围明确的 hook 或短代码。清晰的本地业务功能,优于误导性的全局主题选项。

验证配置变更

修改配置后:

  1. 分别使用最低支持版本与当前验证版本的 Hugo Extended 构建;
  2. 测试每种已配置语言,以及至少一个缺少译文的页面;
  3. 如果同时支持根路径与子路径部署,验证两种 baseURL 输出;
  4. 检查本地搜索与可选运行时请求;
  5. 检查桌面端和移动端外壳、深浅色主题与打印输出。

真正可接受的配置必须能够正确构建并按预期运行,而不只是可以被 YAML 解析。

3.6 - 部署

一次构建 OINK,再发布其静态输出

OINK 部署包含两个独立阶段:Hugo 先生成完整的 public/ 目录,静态托管服务再发布该目录。请把构建验证与线上验证分开,避免把一次成功的本地命令误认为已经完成生产发布。

生产构建

在站点根目录使用固定版本的 Hugo Extended 运行:

hugo --gc --minify --cleanDestinationDir

--gc 会清理不再使用的缓存资源,--minify 生成生产资源,--cleanDestinationDir 删除上次构建残留的文件。如果 publishDir 不是站点专用输出目录,使用最后一个参数前必须先检查命令目标。

构建应顺利完成,并且不能用忽略告警的方式掩盖缺失内容、端点或资源。上传前请先在本地检查 public/

本地预览

编辑期间运行:

hugo server --disableFastRender

Hugo 开发服务器只能证明源码可以渲染;它不是生产托管服务,实时重载行为也不属于生成后的站点。每次发布前都应执行一次干净的生产构建。

静态托管

任何能够提供目录和文件的主机都可以发布 OINK:

  • 对象存储与 CDN;
  • GitHub Pages、GitLab Pages 或类似的 Git 驱动静态托管;
  • Netlify、Cloudflare Pages 或其他构建并发布的平台;
  • Nginx、Caddy、Apache 或内部文件服务器。

请把 baseURL 设置为生产环境 canonical URL。如果站点发布在 https://example.com/manual/ 之类的子路径下,应包含该路径并进行测试;OINK 的本地资源与组件 URL 设计为支持子路径部署。

Cloudflare Pages

让 Pages 直接连接源分支。OINK 不需要由 GitHub Actions 预先构建并推送孤立 Pages 分支。

当前 starter 使用以下设置:

设置
生产分支 main,或经过审查的源分支
根目录 独立站点目录
构建命令 hugo --gc --minify
构建输出目录 public
HUGO_VERSION 0.164.0
SKIP_DEPENDENCY_INSTALL 1

截至 2026-08-08,Cloudflare Pages v3 构建镜像文档中的默认 Hugo 版本为 0.147.7,低于 OINK 最低要求 0.160.1。应当在 Production 与 Preview 中都显式设置 HUGO_VERSION,不要依赖持续变化的平台默认值。 SKIP_DEPENDENCY_INSTALL=1 可阻止平台的通用依赖安装器增加站点并不需要的前端安装步骤。

预览环境如需使用 Pages 生成的地址作为构建 canonical URL,可以运行:

hugo --gc --minify --baseURL "$CF_PAGES_URL"

Cloudflare 官方文档把 public 列为 Hugo 标准输出目录,并说明了 HUGO_VERSION 覆盖与 CF_PAGES_URL base URL 用法。调整构建镜像或固定 Hugo 版本时,请重新核对平台文档。

参阅 Cloudflare Hugo 指南Cloudflare 构建镜像

网络隔离部署

在断网环境中,应同时传入站点源码与已经验证的主题归档,而不是依赖首次构建时下载 Hugo Module:

  1. 验证主题归档附带的 SHA-256 文件;
  2. 在环境中安装受支持的 Hugo Extended 二进制文件;
  3. 把主题解压到站点的 themes/oink/ 目录;
  4. 设置 theme: oink,并在站点中运行 hugo --gc --minify
  5. public/ 发布到内部静态服务器。

除非已经配置隔离网络内可达的端点,否则请保持 PlantUML 与 Diagrams.net 关闭。外部链接与嵌入内容仍由内容作者负责。

响应头与缓存

带指纹的 CSS 与 JavaScript 可以使用长期 immutable 缓存。HTML、搜索索引、Feed 与站点地图应使用较短缓存或重新验证,以便新部署及时生效。

项目站点提供了适用于部分托管平台的 static/_headers 示例。它只是起点,并非可移植标准。应根据站点实际使用的行内内容与集成审查安全响应头。

预览与生产 URL

canonical、hreflang、Open Graph、Feed 与绝对链接都依赖 baseURL。生产构建应使用生产 URL;如果链接验证或社交元数据需要准确,预览构建可以使用临时地址。

不要把针对预览地址生成的产物直接发布到生产环境;反过来,也不要因为预览中出现有意传入的预览域名就判定失败。

部署验收

每一层都要独立验证:

源码与配置

  • 预期提交与固定主题版本确实存在;
  • baseURL、语言、菜单、仓库元数据与可选端点正确;
  • 未发布草稿或秘密信息没有进入公开内容树。

构建产物

  • 使用固定版本的 Hugo Extended 完成干净生产构建;
  • 英文、中文、Feed、站点地图、搜索索引与 404.html 均存在;
  • 本地资源在根路径与配置的子路径下都能解析;
  • 产物包含所需许可证与归属说明。

托管站点

  • 生产 URL 返回新产物;
  • canonical 与备用语言 URL 使用生产域名;
  • 导航、搜索、语言切换、深色模式、打印和代表性组件在真实浏览器中工作;
  • 重定向、自定义响应头、缓存策略与 404 行为符合配置;
  • “支持网络隔离”的结论有浏览器网络审计作为依据。

绿色构建日志只完成产物阶段;托管检查全部通过后,部署才算完成。

回滚

保留上一份已知可用的静态产物或托管平台部署标识。新版本未通过线上验证时,应先恢复该产物,再诊断源码或平台行为。使用新的、未固定工具链重新构建旧提交,并不等同于恢复原产物。

3.7 - 迁移现有站点

用 OINK 替换复制的 Docsy 外壳,同时保留站点专用行为

OINK 旨在替换各站点复制的公共外壳、运行时与短代码,而无需批量重写普通正文。安全迁移应按依赖关系删除覆盖项,把产品专用行为留在站点,并在修改生产环境前先验证临时副本。

迁移原则

  • 固定目标实现,不要把生产站点迁移到未固定版本的分支。
  • 删除覆盖项之前先完成清点。
  • 删除公共主题副本,不删除站点业务逻辑。
  • 在 OINK API 兼容的地方,保持内容 URL、front matter 与短代码行为不变。
  • unsafe 或在线例外必须显式声明,并且只在过渡期使用。
  • 分别测试构建产物、浏览器行为与托管站点行为。

固定目标版本

请在 go.mod 中固定已发布标签,或使用完整的版本化归档。预发布评估期间,Hugo Module 站点可以使用被忽略的 Go workspace,在不修改已提交模块版本的情况下解析本地 checkout:

go work init .
go work edit -replace=github.com/pgsty/oink=/absolute/path/to/oink
export HUGO_MODULE_WORKSPACE=go.work
hugo --gc --minify

站点的 hugo.yaml 导入 github.com/pgsty/oink;workspace 只替换本地 checkout。

清点现有覆盖项

把每个站点级文件归入以下四类之一:

类别 处理方式
公共外壳的完全或近似副本 OINK 验证通过后删除
OINK 已提供的可复用组件 删除或机械重命名
范围明确的品牌或产品定制 保留,再缩小到最小 hook
业务专用数据或交互 留在站点

layouts/assets/static/、配置与构建工作流必须一起检查。复制的短代码通常还伴随一份 JavaScript bundle、样式表、vendor 文件和 CI 安装步骤。

迁移配置

搜索与品牌

启用主题提供的本地搜索,并让外壳使用站点自己的 Logo:

params:
  logo: img/product.svg
  offlineSearch: true

继续在原有语义位置使用 titlelanguages.*github_repogithub_project_repogithub_branchpage_widthui.*,不要把它们迁入 oink.* 命名空间。

ECharts 旧内容

旧 Pigsty 页面可能在 ECharts 块中包含 JavaScript。仅在经过审查的过渡期启用:

params:
  content:
    echarts_unsafe: true

新建与已经转换的图表都应使用 JSON 或 YAML。迁移完成后删除站点级开关;确实暂时无法转换时,也应把 unsafe=true 限制在具体短代码上。

字体

旧 Sass 开关 $td-enable-google-fonts: true 现在会选择 OINK 随附的本地 Open Sans,而不会请求 Google Fonts。$td-web-font-path 不再参与当前构建。需要其他字体的站点必须提供获准使用的本地资源及其许可证。

删除公共覆盖项

临时构建证明等价后,可以删除站点中的以下副本:

  • layouts/baseof.html 与公共 docs/blog baseof*.html
  • 公共 navbar、footer、sidebar、目录(TOC)、search、head CSS partial 及相应 hook;
  • 旧的公共品牌文档外壳 partial;
  • asciinemaechartsinfographicdoc-carouseldetailstab/tabpane、card 与 param 短代码副本;
  • 只服务于上述已删除实现的 JavaScript、Lunr 副本、轮播代码与 SCSS;
  • 不再被任何站点资源需要的消费端 PostCSS 与 Autoprefixer 步骤。

应按引用关系删除,而不是直接清空 layouts/。首页、下载页与门户仍可能调用本地 icon、search dialog、blog row 或 tag filter 等 partial。

保留站点专用行为

保留语义属于具体产品的内容与代码:

  • 产品矩阵与兼容性数据;
  • 价格、下载、门户、解决方案与目录页面;
  • 站点专用首页结构;
  • 自定义重定向、响应头、分析或身份集成;
  • 承载业务数据、而非通用呈现逻辑的内容组件。

对于 Pigsty 家族,pgverspgext_matrixpgext_os_matrixhome-docs 以及当前 metric 实现继续留在站点层。

参考站点矩阵

当前迁移计划采用以下边界:

站点 删除或迁移 保留
SILO 公共 docs/blog 外壳、核心短代码与重复运行时;设置 logo: img/silo.svg 首页、下载页与产品数据
PGSTY 公共外壳与核心短代码;设置 logo: img/logo/logo.svg 门户、解决方案与企业页面
SOW 公共 docs/blog 外壳、核心短代码与重复运行时;设置 logo: img/sow.svg 首页与仓库专用内容
Pigsty 公共外壳、核心短代码与重复运行时;设置 logo: icons/logo.svg,并在过渡期审查旧 ECharts 扩展矩阵、首页与价格页、目录样式

该矩阵是清点工作的起点,并不意味着可以删除所有名称相似的文件。必须在目标检出目录中解析实际模板引用。

演练工作流

请在消费站点的临时副本中演练迁移。应用本地 Oink workspace,每次删除一组计划覆盖项,阻断非预期网络与前端工具访问,再执行生产构建:

HUGO_MODULE_WORKSPACE=go.work hugo --gc --minify

演练不得修改源工作区。保留失败副本用于诊断,并记录准确的主题 commit、Hugo 版本、已删除文件与输出数量。

当前证据

最近一次记录的演练发生在 2026-08-08,使用 Hugo Extended 0.164.0

站点 演练结果 HTML 文件数
SILO 删除 20 个公共覆盖项;完整构建中英文内容,OINK 外壳、同源搜索与站点 Logo 生效 1,095
PGSTY 删除 20 个公共覆盖项;构建双语门户,并用临时 docs 页面验证外壳 16
SOW 删除 20 个公共覆盖项;完整构建中英文内容,OINK 外壳、同源搜索与站点 Logo 生效 128
Pigsty 删除 24 个公共覆盖项;保留三个业务矩阵短代码,并启用经过审查的旧 ECharts unsafe 模式 2,473

这些是临时副本的构建结果,不代表四个生产站点已经完成迁移或部署。

生产迁移流程

对每个站点依次执行:

  1. 创建专用迁移分支;
  2. 固定 OINK 候选版本并记录其源码提交;
  3. 每次只删除一组内聚的覆盖项;
  4. 执行干净的 Hugo-only 构建与针对性自动化测试;
  5. 比较具有代表性的首页、文档页、博客页、特殊页面与 404 页面;
  6. 检查移动导航、两种颜色模式、语言切换、搜索、打印与站点保留的业务组件;
  7. 部署预览,并验证真实 URL 与网络请求;
  8. 评审通过后才合并并部署,随后执行生产冒烟测试。

对于 OINK 有意改变外壳的部分,应记录合理差异,而不是强求像素级相同。

回滚

保留迁移前的主题 pin、站点提交与已知可用部署产物。回滚时三者应一致恢复。针对新主题只重新引入一部分随机复制布局,会形成比任一完整版本都更难诊断的混合状态。

3.8 - 发布流程

发布 Oink 主题并更新其独立项目站点

Oink 把实现、验证、公开发布与部署视为不同状态。一次绿色本地构建是有价值的证据,但它不是公开标签、可下载模块,也不是已经部署的文档更新。

发布状态

状态 必需证据
源码完成 范围、文档、变更日志、归属信息与评审全部完成
验证完成 主题模块与项目站点检查通过
公开发布 pgsty/oink 中存在不可变根标签,并可通过 Go 解析
文档完成 pgsty/oink.pgsty.com 固定并记录该标签
部署完成 托管文档与目标消费站点通过验证

应报告准确状态与证据;不要把本地构建称为发布。

版本管理

主题版本在 github.com/pgsty/oink 使用 vX.Y.Z 这样的根标签。主题现在就是仓库根模块,因此不再使用嵌套的 theme/vX.Y.Z 标签。

项目站点的 version 参数标识发布的站点变体,不会自动成为 Git ref。安装说明与 go.mod 必须使用真实可解析的主题标签。

验证主题仓库

从干净的 pgsty/oink checkout 开始:

  1. 检查源码 diff 与归属信息变更;
  2. 验证 VENDOR.json 中每个文件与 SHA-256;
  3. 确认仓库没有生成的 public/、资源缓存、node_modules/ 或内嵌 example site;
  4. 使用最低版本与当前支持版本的 Hugo Extended,通过 Hugo Module 路径构建最小消费站点;
  5. 检查 module zip,确认布局、资源、翻译、静态文件、许可证与 NOTICE 都存在。

module zip 测试很重要,因为 Go 会从发布模块中排除 vendor 等特殊目录名。Oink 把随附依赖放在 assets/third_party/,确保它们能进入模块发行物。

验证项目站点

pgsty/oinkpgsty/oink.pgsty.com 克隆为同级目录,再用被忽略的 workspace 连接:

cd oink.pgsty.com
go work init .
go work edit -replace=github.com/pgsty/oink=../oink
export HUGO_MODULE_WORKSPACE=go.work
npm install
npm test

检查具有代表性的中英文页面、移动导航、两种颜色模式、本地搜索、打印输出、图表、API 文档与 404 页面。这只能验证候选版本与站点配合正常,不会发布任何仓库。

标记并发布主题

评审完成后,在主题仓库中创建一个不可变的签名根标签:

git tag -s vX.Y.Z -m "Oink vX.Y.Z"
git push origin main vX.Y.Z

推送以及创建 GitHub release 都需要明确授权。标签公开后,从干净环境验证:

hugo mod get github.com/pgsty/[email protected]
hugo mod graph

如果 release 附带离线归档,请发布并独立验证 SHA-256。归档中必须保留 LICENSENOTICEVENDOR.json

更新项目站点

主题标签能够公开解析后,更新独立站点仓库:

hugo mod get github.com/pgsty/[email protected]
hugo mod tidy
npm test

go.modgo.sum、版本参数、变更日志与升级指南一起提交。先验证部署预览,评审通过后再推进生产发布分支。

发布后验证

公开发布后:

  1. 从干净 clone 获取标签并检查签名;
  2. 通过公开 Go proxy 解析模块;
  3. 使用文档命令构建一个全新最小站点;
  4. 打开生产文档,验证模块说明、canonical 链接、语言、搜索与资源;
  5. 验证所有发布归档与 checksum;
  6. 记录最终标签、模块版本、托管 URL 与产物哈希。

热修复与回滚

热修复范围可以更小,但仍要经过同一证据链。绝不能移动或替换已经发布的标签。站点需要回滚时,应恢复到已知产物;主题需要修复时,则发布新的补丁版本。

完成定义

只有批准的标签已经存在、公开模块能够解析、必需检查通过、项目站点固定该标签,并且托管冒烟测试成功,版本才算发布完成。任何未完成项都应按实际状态报告。

4 - 部署与预览

部署 Docsy 站点。

Hugo 站点有多种部署方式,包括 Netlify、Firebase Hosting、Bitbucket 搭配 Aerobatic 等;完整列表请参阅 托管与部署。Hugo 也能轻松地在本地运行站点,以便快速预览内容。

构建环境与索引

默认情况下,使用 hugo 构建的站点(相对于在本地通过 hugo server 提供服务)会采用 Hugo 的 production 构建环境。以 production 环境构建并部署的 Docsy 站点可以被搜索引擎索引,包括 Google 自定义搜索引擎。生产构建还会针对线上部署优化 JavaScript 和 CSS,例如输出压缩后的 JS,而不是更易阅读的原始源码。

如果不希望已部署的站点被搜索引擎索引(例如线上站点仍在开发),或者需要构建开发版本用于离线分析,可以把 Hugo 构建环境设为其他值,例如 development(使用 hugo server 本地运行时的默认值)、test,或任意自定义的环境名称。

最简单的设置方式是在 hugo 命令中使用 -e 参数,例如:

hugo -e development

4.1 - 使用 Amazon S3 和 CloudFront 部署

使用 Amazon S3 和 Amazon CloudFront 部署 Docsy 站点。

通过 Amazon Web Services 发布网站有多种方案。本节介绍最基础的一种:把站点部署到 S3 存储桶,并启用 CloudFront CDN(内容分发网络)来加速已部署内容的传输。

  1. 完成 AWS 注册后,创建 S3 存储桶,将其关联到你的域名,再加入 CloudFront CDN。可以参考这篇博客文章,其中包含完整流程和易于操作的分步说明。

  2. 下载并安装最新版 AWS 命令行界面(CLI)v2。随后运行 aws configure 配置 CLI 实例(请提前准备 AWS Access Key ID 和 AWS Secret Access Key):

    $ aws configure
    AWS Access Key ID [None]: AKIAIOSFODNN7EXAMPLE
    AWS Secret Access Key [None]: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
    Default region name [None]: eu-central-1
    Default output format [None]:
    
  3. 运行 aws s3 ls 检查 AWS CLI 配置是否正确;命令应输出你的 S3 存储桶列表。

  1. hugo.tomlhugo.yamlhugo.json 中添加如下 [deployment] 分区:

    [deployment]
    [[deployment.targets]]
    name = "aws"
    URL = "s3://www.your-domain.tld"
    cloudFrontDistributionID = "E9RZ8T1EXAMPLEID"
    deployment:
      targets:
        - name: aws
          URL: 's3://www.your-domain.tld'
          cloudFrontDistributionID: E9RZ8T1EXAMPLEID
    {
      "deployment": {
        "targets": [
          {
            "name": "aws",
            "URL": "s3://www.your-domain.tld",
            "cloudFrontDistributionID": "E9RZ8T1EXAMPLEID"
          }
        ]
      }
    }
  1. 运行 hugo --gc --minify,将站点资源渲染到 Hugo 构建环境的 public/ 目录。

  2. 使用 Hugo 内置的 deploy 命令把站点部署到 S3:

    hugo deploy
    Deploying to target "aws" (www.your-domain.tld)
    Identified 77 file(s) to upload, totaling 5.3 MB, and 0 file(s) to delete.
    Success!
    Invalidating CloudFront CDN...
    Success!
    

    如输出所示,执行 hugo deploy 会自动使 CloudFront CDN 缓存失效

  3. 至此全部完成。今后只需使用 Hugo 内置的 deploy 命令,即可轻松部署到 S3 存储桶。

有关 Hugo deploy 命令及其命令行参数的更多信息,请参阅命令概览。其中,--maxDeletes int 和强制上传所有文件的 --force 参数可能会很有用。

如果 S3 无法满足需求,可以考虑 AWS Amplify Console。这是更高级的持续部署(CD)平台,内置对 Hugo 静态站点生成器的支持。Hugo 官方文档提供了相应的入门指南

4.2 - 部署到 GitHub Pages

仅使用 Hugo 将 OINK 站点部署到 GitHub Pages。

如果源码托管在 GitHub,只需一份 Actions 工作流,就能通过 GitHub Pages 构建并发布站点。消费站点需要 Hugo Extended,但不需要 Node.js、npm、PostCSS,也不需要生成专门的部署分支。

项目站点的 URL 形如 https://<OWNER>.github.io/<REPOSITORY>/;用户和组织站点使用 https://<OWNER>.github.io/。GitHub Pages 也支持自定义域名。

准备代码仓库

把完整的站点源码推送到 GitHub,并确认在仓库根目录执行以下命令能够成功:

hugo --gc --minify

将站点的 baseURL 设为生产 URL,或者在工作流中通过 Hugo 的 --baseURL 参数传入 Pages URL。项目站点必须包含仓库路径,否则 CSS、JavaScript 和其他资源会从错误的位置解析。

添加 Pages 工作流

创建 .github/workflows/pages.yml,内容如下。请让 HUGO_VERSION 始终与主题已经验证的版本保持一致。

name: Deploy Hugo site to Pages

on:
  push:
    branches: [main]
  workflow_dispatch:

permissions:
  contents: read
  pages: write
  id-token: write

concurrency:
  group: pages
  cancel-in-progress: false

env:
  GO_VERSION: 1.25.5
  HUGO_VERSION: 0.164.0

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
        with:
          fetch-depth: 0
          submodules: recursive
      - uses: actions/setup-go@v6
        with:
          go-version: ${{ env.GO_VERSION }}
      - name: Install Hugo Extended
        run: |
          curl -L -o hugo.deb \
            "https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb"
          sudo dpkg -i hugo.deb
      - uses: actions/configure-pages@v6
        id: pages
      - name: Build
        run: >-
          hugo --gc --minify --baseURL "${{ steps.pages.outputs.base_url }}/"
      - uses: actions/upload-pages-artifact@v5
        with:
          path: public

  deploy:
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    runs-on: ubuntu-latest
    needs: build
    steps:
      - name: Deploy
        id: deployment
        uses: actions/deploy-pages@v5

如果通过 Git submodule 安装主题,submodules: recursive 会在 Hugo 运行前检出主题。如果使用完整离线归档,则可以把相邻的 theme/ 目录提交到仓库,或在构建输入中恢复该目录。

启用 GitHub Pages

在仓库设置中打开 Pages。在 Build and deployment 下,将 Source 设为 GitHub Actions。把工作流推送到 main,然后在仓库的 Actions 标签页中查看第一次运行。

工作流只上传生成的 public/ 目录,并通过 Pages 部署 API 发布,不会维护 gh-pages 分支。

有关其他身份验证、域名和权限选项,请参阅 GitHub 的 Pages 文档和 Hugo 的 GitHub 托管指南

4.3 - 部署到 Netlify

仅使用 Hugo 将 OINK 站点部署到 Netlify。

Netlify 可以从 GitHub、GitLab 或 Bitbucket 构建站点,并为每个拉取请求发布预览。OINK 消费端会直接运行 Hugo Extended,不安装 Node.js 软件包,也不调用 PostCSS。

配置站点

把完整源码推送到 Git 服务商,在 Netlify 中导入仓库,然后使用以下构建设置:

设置
构建命令 hugo --gc --minify
发布目录 public
HUGO_VERSION 0.164.0 或主题验证过的其他版本

如果 Netlify 检测到仅供主题维护工具使用的软件包清单,请为站点关闭自动依赖安装。这些工具不属于消费端构建合同。

如果通过 Git submodule 安装主题,请启用递归 submodule 检出。如果使用 Hugo 模块,Netlify 还需要具备普通的 Git 和 Go 访问能力,以便在全新构建中下载已经固定版本的模块。完整离线发行包使用相邻的 theme/ 目录,可避免首次构建时下载依赖。

将配置保存在仓库中

也可以把同样的设置写入 netlify.toml 并提交:

[build]
command = "hugo --gc --minify"
publish = "public"

[build.environment]
HUGO_VERSION = "0.164.0"

除非预览环境专门用于测试升级,否则生产环境和部署预览应使用同一个 Hugo 版本。如果预览构建需要把自动生成的 URL 作为 base URL,请在对应环境的 Hugo 命令中加入 Netlify 部署 URL。

如果不希望非生产部署被索引,请按照构建环境与索引中的说明使用非生产 Hugo 环境。

保存设置后触发一次部署,并检查构建日志。正常的消费端构建应该只出现一条 Hugo 命令,不应运行 npm、PostCSS、Autoprefixer、CDN 下载或构建期远程资源步骤。

4.4 - 在本地运行站点

根据所选部署方式,你可能需要在开发期间于本地运行站点,以便预览内容变更。具体步骤如下:

  1. 确认已经从代码仓库克隆站点文件,并将本地副本更新至最新状态。

  2. 按照前提条件与安装中的说明,安装 Hugo Extended,以及所选主题安装方式获取源码时需要的工具。Node.js 和 PostCSS 不是站点构建的前提条件。

  3. 在站点根目录运行 hugo server。默认情况下,可以通过 http://localhost:1313 访问站点。

站点在本地运行后,Hugo 会监视内容变更并自动刷新页面。如果本地有多个 Git 分支,切换分支后,本地站点也会随之反映当前分支中的文件。

4.5 - 页面外壳

主题会在每个页面中渲染完整的品牌导航外壳。

主题会在每个适用页面中完整渲染顶部导航栏、侧栏、目录、搜索入口和页脚。无论是常规构建、预览、离线归档、搜索爬虫,还是不运行 JavaScript 的客户端,这都是唯一且规范的生产结构。

本主题不包含上游实验性的 td.chrome = shared 供体/恢复模式。params.td.chrome 设置不会产生任何效果,迁移站点时应将其删除。只保留一套服务端渲染结构,可以避免出现第二套视觉实现,并让导航、语言选择、无障碍语义和离线行为保持确定。

请使用 Hugo 压缩和托管层压缩来减少传输体积:

hugo --gc --minify

交互式外壳脚本只会增强已经渲染的标记,不负责重建缺失的导航区域。

5 - 多语言支持

配置语言、译文、稳定链接、搜索与 RTL。

OINK 使用 Hugo 的多语言页面模型,不依赖某个站点专属的域名或模板假设。随仓库提供的站点将英文设为首要语言,将简体中文(zh)设为第二语言。

配置语言

hugo.yaml 中定义默认语言与所有启用的语言:

defaultContentLanguage: en

languages:
  en:
    label: English
    locale: en-US
    weight: 1
    title: Product Documentation
    params:
      description: Product guides and reference
  zh:
    label: 简体中文
    locale: zh-CN
    weight: 2
    title: 产品文档
    params:
      description: 产品指南与参考资料
      time_format_default: 2006年1月2日
      time_format_blog: 2006年1月2日

weight 同时决定语言排序和选择器顺序。label 使用该语言自己的文字显示。locale 为 HTML、备用链接和 Open Graph 元数据提供符合标准的语言标签。

语言专属参数会覆盖全局值;未定义的参数继承全局值。菜单标签不同时,请在每种语言下分别定义菜单。

组织译文内容

OINK starter 将译文并置保存:

content/docs/
├── install.md
└── install.zh.md

共同的基础文件名会让 Hugo 把这些文件识别为同一页面的不同译文。除非确实需要语言专属差异,否则日期、权重、别名、资源和影响路由的元数据应保持一致。

所有可见文本都需要翻译,包括 front matter 标题与描述、摘要、菜单标签、标签、图片替代文字、提示块和短代码参数。命令、标识符、配置键、文件名、URL 与产品名称应保持原样。

语言树规模很大且由不同团队独立维护时,也可以使用 Hugo 的语言专属 contentDir 模型。不要随意混用两种布局;应选定一种、写入规范,并验证 Hugo 如何关联译文。

自动标题 ID 取决于标题文字,因此翻译后通常会破坏共用的片段链接。请在译文中显式使用英文页面实际渲染出的 ID:

## Configure local search
## 配置本地搜索 {#configure-local-search}

必须检查渲染后的 HTML,不能凭规则猜测。内联 HTML、标点、徽章和短代码都可能影响 Hugo 生成的 ID。对应页面应具有相同的标题顺序和渲染 ID 列表。

语言选择器行为

语言选择器根据 Hugo 配置的站点和页面译文自动生成。只配置一种语言时隐藏;配置两种或更多语言时,统一显示一个语言图标按钮。直接点击会按 weight 顺序切换到下一种语言;悬停半秒或聚焦按钮则展示完整语言菜单。

对于每种目标语言,如果当前页面存在译文,选择器就会链接到该译文;如果不存在,则链接到目标语言首页,避免生成断链或冒充译文的路由。当前语言具有可见状态和 aria-current 状态。

SEO 与文档元数据

每个页面都会输出:

  • 正确的 HTML langdir 值;
  • 当前页面的规范 URL;
  • 为所有配置语言生成带 hreflangrel="alternate" 链接;
  • Open Graph locale 与备用 locale 元数据。

备用目标采用与可见选择器相同的“当前页面译文或目标语言首页”回退规则。请使用正确的生产 baseURL;OINK 支持子路径部署,布局中不得用硬编码绝对路径替代它。

从右向左语言

为 RTL 语言设置 direction: rtl

languages:
  ar:
    label: العربية
    locale: ar
    direction: rtl
    weight: 4

主题会加载已经提交的本地 Bootstrap RTL 产物,自有外壳则使用逻辑 CSS 属性。LTR 与 RTL 站点使用同一个命令:

hugo --gc --minify

消费站点不安装 RTLCSS、PostCSS 或 npm。测试时应使用真实 RTL 内容,并检查导航、代码、表格、图表和双向混排字符串,不能认为选中样式表就已足够。

UI 翻译包

主题 UI 字符串位于 theme/i18n/。OINK 包含英文、简体中文、繁体中文,以及从上游继承的其他翻译包。站点可以创建自己的 i18n/<language>.yaml,只覆盖确实需要修改的字符串;其余值继续回退到主题翻译包。

翻译期间运行:

hugo server --printI18nWarnings

通用译文应贡献到主题中;产品专属语言应留在站点翻译包中。

分语言搜索

启用 offlineSearch: true 后,OINK 会为每种语言生成独立的同源索引。简体中文索引使用主题的 CJK 回退,搜索结果不会离开当前语言。

请验证 offline-search-index.en.jsonoffline-search-index.zh.json 均已生成,包含预期页面,并能在部署后的 baseURL 下正确解析。

翻译检查清单

  • 支持范围内的每个源页面都有对应 .zh.md 文件。
  • front matter 身份和路由元数据一致。
  • 可见正文、UI 字符串、替代文字和元数据均已翻译。
  • 每个中文 Markdown 标题都有显式稳定 ID。
  • 中英文渲染标题 ID 列表一致。
  • 站内链接与片段在两种语言中都能解析。
  • 导航、面包屑、上一页/下一页链接和搜索保持在当前语言。
  • 日期、标点、空格和技术术语符合目标语言的编辑规范。
  • 生产构建输出正确的 canonical 与备用语言元数据。

Hugo 底层模型请参阅多语言模式

6 - 更新 OINK

安全更新主题、Hugo Extended 与本地覆盖。

本节介绍 OINK 的更新合同。目标版本 指站点准备升级到的版本。开始之前,请先阅读对应的发布文章,其中会记录破坏性变更、必要操作和已经验证的 Hugo 版本范围。

OINK 消费端构建不安装 Node.js 软件包。npm 仍可供主题维护者使用,但不是站点更新步骤。

更新前的准备

  • 在 Git 分支或其他可恢复的站点副本上操作。
  • 记录当前固定的主题修订版本与 Hugo Extended 版本。
  • 先完整构建一次当前生产站点,以便区分新增故障与原有问题。
  • 阅读当前版本到目标版本之间的每一篇发布文章,不要跳过中间版本的迁移操作。

更新顺序

请按以下顺序更新:

  1. 如果目标版本改变了支持范围,先更新 Hugo
  2. 根据站点的安装方式更新主题
  3. 审查主题覆盖
  4. 分别通过开发构建与生产构建检查站点

更新 Hugo

安装目标版本支持的 Hugo Extended,并同步更新本地开发环境、CI、Cloudflare Pages、Netlify、容器镜像和相关缓存键。构建前先核对实际选中的二进制文件:

hugo version

当前验证基线是 Hugo Extended 0.164.0,主题当前声明的最低版本是 0.160.1。如果发布文章调整了其中任一数值,应以发布文章为准。

更新主题

根据站点的安装方式选择对应页面:

如果使用发布归档,请先保留站点自己的覆盖,再用目标版本归档替换现有主题目录。务必校验归档的 checksum,并让 LICENSENOTICEVENDOR.json 始终随发行物保留。

审查主题覆盖

如果站点覆盖了主题文件,请逐一与新版本主题中的对应文件比较,并移植仍然适用的变更。重点检查以下目录:

  • assets/
  • i18n/
  • layouts/
  • static/

当主题已经提供相同行为时,应删除对应覆盖。带业务语义的站点组件、产品页面和品牌素材则应继续留在站点层。

检查站点

既要运行开发预览,也要执行与生产环境完全相同的命令。Hugo-only 合同下的生产构建命令是:

hugo --gc --minify

至少验证以下项目:

  • 构建完成,且没有错误、警告或弃用提示。
  • 中英文首页、文档页与博客页均能正常渲染。
  • 导航、面包屑、目录、稳定标题链接和语言切换均指向正确位置。
  • 本地搜索能返回中英文结果。
  • 深浅色模式、移动端导航与打印输出仍然可用。
  • 页面只加载实际使用的本地运行时;默认页面不发起由主题产生的第三方子资源请求。
  • Mermaid、KaTeX、Markmap、Swagger UI、Redoc 以及实际使用的内容组件仍能渲染。
  • 站点自有短代码和业务页面保持完整。

最后,执行目标版本发布文章列出的所有版本专属检查。

6.1 - 更新 OINK Hugo 模块

更新以固定版本 Hugo 模块形式导入主题的站点。

固定版本

生产站点应导入发布标签或不可变的 commit,绝不能跟随未固定版本的分支。在站点根目录,把 Oink 更新到指定 ref:

hugo mod get github.com/pgsty/oink@THEME_REF
hugo mod tidy

THEME_REF 替换为该版本发布说明指定的根标签或 commit。

测试本地 checkout

如果要在不修改已提交模块版本的前提下测试本地 OINK checkout,请使用被忽略的 Go workspace:

go work init .
go work edit -replace=github.com/pgsty/oink=/absolute/path/to/oink
export HUGO_MODULE_WORKSPACE=go.work
hugo --gc --minify

不要把包含开发者机器专属绝对路径的 go.work 提交到仓库。

验证解析出的模块

检查 Hugo 的依赖图:

hugo mod graph

确认主题解析到预期的标签、commit 或本地 replacement。OINK 不需要运行 hugo mod npm packnpm install,因为浏览器依赖已经随主题提供。

随后继续审查主题覆盖

6.2 - 从 Docsy npm 包迁移

从 OINK 消费站点中移除上游 npm 主题包。

上游 @docsy/theme npm 包不是 OINK 的发行渠道。OINK 将 Bootstrap、Font Awesome、字体和浏览器运行时直接随主题提供,因此消费站点只需 Hugo Extended 即可构建。

移除 npm 主题集成

首先选择一种 OINK 发行方式:固定版本的归档、Git submodule 或克隆,或者兼容 Hugo 模块。让 Hugo 能够访问该主题,并确认执行 hugo --gc --minify 时可以正确解析。

随后,从站点的 package.json 中移除 @docsy/theme,以及仅用于构建 Docsy 资源的依赖。删除 Hugo 配置中 Bootstrap 和 Font Awesome 的 npm 挂载项,同时删除只为旧主题管线存在的 PostCSS 与 Autoprefixer 构建步骤。

不要仅仅因为某项应用依赖使用 npm 就将其删除。Hugo-only 合同针对文档主题;站点自有应用或业务组件仍可能采用另一套明确且必要的工具链。

验证迁移

从全新 checkout 开始,只安装 Hugo Extended,不创建 node_modules 目录,然后执行:

hugo --gc --minify

如果站点同时支持 LTR 和 RTL 页面,请分别检查。还要验证本地字体与图标、搜索、图表、API 文档和所有已经迁移的内容组件。构建完全正常后,只有在站点自有工具也不再使用 lockfile 时,才可以删除过时的 lockfile。

随后继续审查主题覆盖

6.3 - 更新 OINK Git submodule 或克隆

更新以 Git submodule 或克隆形式保存的 OINK 主题。

请根据安装方式选择相应步骤:submodule克隆。两种方式都必须固定到目标版本标签或不可变的 commit。

更新 submodule

在站点根目录进入主题仓库获取标签,并 checkout 目标 ref:

git -C themes/oink fetch --tags
git -C themes/oink checkout THEME_REF
git add themes/oink
git commit -m "Update OINK theme to THEME_REF"

如果站点使用其他目录名,请相应替换 themes/oink。父仓库会记录最终的 submodule commit。请推送这次父仓库提交,确保 CI 和其他贡献者解析到完全相同的源码。

无需安装任何 npm 软件包。如果某个发行版的完整主题包含仅供源码使用的嵌套 submodule,请按照该版本的说明初始化;OINK 发行物所需的浏览器运行时资源已经包含在内。

更新克隆

如果主题目录是由站点跟踪或恢复的克隆,请将其更新到目标 ref:

git -C themes/oink fetch --tags
git -C themes/oink checkout THEME_REF

沿用站点现有的可复现方式,提交、归档或记录更新后的主题。不要让生产构建持续跟随 main

如果克隆中包含本地修改,请在切换 ref 前把它们提交到分支。更新后再通过 rebase 或其他方式重新应用,并显式解决冲突。可复用的修改应尽量回馈 OINK;消费站点只保留真正属于站点的覆盖。

随后继续审查主题覆盖

6.4 - 将 Docsy 站点迁移到 OINK

用仅依赖 Hugo 的 OINK 主题替换 Docsy 消费端工具链。

这次迁移会删除复制到站点中的公共外壳覆盖,以及消费端 npm 资源管线,但不要求批量重写 Markdown 正文。

开始之前

新建工作分支,并确认现有站点能够构建。盘点 layouts/assets/static/i18n/ 下的自定义文件,将其分为三类:

  • 已经由 OINK 提供的 Docsy 公共外壳代码;
  • 已经由 OINK 提供的可复用组件;
  • 必须保留的站点品牌、产品页面或业务组件。

不要删除第三类文件。

选择主题发行方式

选择固定版本的 Git checkout、版本归档、完整离线发行包或公开的 Oink Hugo Module。如果要在本地临时演练,请导入 Oink,并使用 Go workspace 解析本地 checkout:

go work init .
go work edit -replace=github.com/pgsty/oink=/absolute/path/to/oink
export HUGO_MODULE_WORKSPACE=go.work
hugo --gc --minify

这样可以测试 OINK,而不会把开发者专属路径写入站点配置或 go.mod

移除消费端资源管线

删除只用于获取 Bootstrap、Font Awesome、字体或主题浏览器运行时的 npm 挂载项与构建步骤。删除仅为 Docsy 存在的 postCSS 调用和 Autoprefixer 步骤。如果站点自有软件仍然需要 package.json,请继续保留;但文档构建本身必须能够在不安装这些软件包的情况下完成。

移除公共覆盖

OINK 直接提供文档与博客外壳、顶部导航栏、页脚、侧栏、目录、搜索、语言选择器、head 资源和核心内容组件。请按依赖关系逐组删除站点中的对应覆盖。

自定义首页、门户、下载页、产品数据和业务短代码应继续保留,直到有明确的替代实现。详细的删除/保留矩阵请参阅迁移指南

验证结果

从全新 checkout 开始,在系统中仅提供 Hugo Extended,然后运行:

hugo --gc --minify

检查双语页面集、本地搜索、深色模式、移动端导航、打印输出、图表、API 文档、内容组件和站点专属页面。查看浏览器网络日志,确认主题默认资源均来自同源地址。

只有迁移后的构建与视觉检查全部通过,才可以删除已经过时的配置、lockfile 或工作流步骤。

7 - 贡献指南

如何为 OINK 贡献代码与双语文档。

OINK 是从 Docsy 派生的独立主题。贡献必须保留 Apache-2.0 历史和适用的第三方 NOTICE,同时改进唯一的标准实现。

提交变更之前

  • OINK 仓库中搜索已有 issue 和拉取请求。
  • 报告缺陷时,请记录 Hugo 版本、安装方式、语言、路由、生产命令和最小可复现输入。
  • 提议功能时,请说明它为什么属于可复用主题,而不是消费站点的业务层。
  • 不要引入 oink.enabled 开关、oink.* 配置树或平行视觉外壳。OINK 的标准布局就是产品本身。

小型修复可以直接实现。较大的行为变更应在编写代码前说明兼容性、离线、无障碍、安全与迁移影响。

开发环境

消费站点只需要 Hugo Extended、Go 与 Git。主题仓库是直接的 Hugo Module;项目站点仓库使用固定的 Node.js 与 npm 版本运行格式、链接、翻译与回归检查。

请在仓库根目录按照 lockfile 安装维护依赖。不要在无关变更中顺带更新依赖。

项目拆分为:

  • github.com/pgsty/oink:发布主题源码与 VENDOR.json
  • github.com/pgsty/oink.pgsty.com:文档、示例与测试。

构建消费端合同

必须从消费站点验证用户真正运行的路径:

hugo --gc --minify

该构建必须在消费站点不安装 npm 软件包的情况下成功,也不能为了主题自有浏览器资源发起网络请求。

测试本地主题候选版本时,把两个仓库克隆为同级目录,并启用被忽略的 Hugo workspace:

go work init .
go work edit -replace=github.com/pgsty/oink=../oink
HUGO_MODULE_WORKSPACE=go.work npm run build

运行聚焦测试

先选择最小的相关测试集:

npm run test:hugo-build
npm run test:alt-site
npm run test:md-output
npm run test:favicons

完整站点测试使用 npm test

多语言变更应覆盖 1、2、3、4 种以上语言状态,以及缺失页面回退、RTL、规范 URL、hreflang 和 Open Graph locale 元数据。

内容组件变更应覆盖单实例与多实例、未使用页面不加载资源、非法参数、子路径构建、打印、键盘操作、减少动态效果和离线行为。

编写双语文档

content/docs/content/blog/ 下新增的所有面向用户页面,都需要对应 .zh.md 文件。术语与中文排版遵循 TRANSLATION.md

中文 Markdown 标题必须显式使用英文渲染 HTML 中的 ID。先检查源码覆盖,再在构建后比较渲染标题 ID:

node scripts/check-doc-translations.mjs
node scripts/check-doc-translations.mjs --public public

代码、配置键、URL、发布事实、作者信息和链接定义必须保持准确;可见元数据、替代文字、提示块、UI 标签和短代码字符串必须翻译。不要为了通过文件名检查而提交占位内容或未翻译正文。

预览文档

使用固定的公开模块运行项目站点,或启用上文所述本地 workspace:

npm run serve

请分别在桌面和移动端宽度下检查变更页面的中英文版本,并覆盖深浅色模式、目录、语言切换、搜索、代码块、表格、提示块、打印输出和片段链接。

本地构建只能证明本地渲染。CI、发布打包、托管预览和生产发布是彼此独立的验证层。

保持兼容

  • 复用现有 partial、短代码、SCSS 辅助方法和资源加载器。
  • 浏览器运行时只在实际使用的页面中加载,并且每页最多一次。
  • 默认行为保持本地优先和同源。
  • 安全序列化结构化数据;任意 JavaScript 必须设置显式 unsafe 边界。
  • 使用逻辑 CSS 属性,并同时测试 LTR 与 RTL。
  • 保留站点自有业务组件和已经记录的兼容别名。
  • 重新分发资源时保留法律归属与 vendor 元数据。

创建拉取请求

提交与说明应保持精炼,并解释面向用户的行为和迁移影响。列出实际运行的聚焦命令与结果。

如果某项变更有意偏离 Docsy,请更新相应迁移或发布文档。不能删除上游版权、许可证或历史。

8 - 最佳实践

关于技术文档组织、编写与管理的可选指导和建议。

本节介绍使用 Docsy 创建技术文档时值得参考的一些最佳实践。

8.1 - Hugo 内容技巧

使用 Docsy 主题编写 Hugo 站点内容时的实用建议。

Docsy 是一款面向 Hugo 静态站点生成器的主题。如果你还不熟悉 Hugo,本页汇总了一些添加和编辑站点内容时的实用技巧及常见陷阱。也欢迎补充自己的经验!

链接

默认情况下,Hugo 会原样保留链接中的普通相对 URL(它们在站点生成的 HTML 中仍是相对链接)。因此,像 [相对交叉链接](../../peer-folder/sub-file.md) 这样硬编码的相对链接,其行为可能与你在本地文件系统中看到的不同。为了避免生成的站点出现断链,可以使用 Hugo 内置的链接短代码,例如 relref。例如,Hugo 中的 {{< ref "filename.md" >}} 会真正找到名为 filename.md 的文件,并自动生成指向它的链接。

但请注意,refrelref 链接不适用于 _indexindex 文件(例如本站的内容首页)。指向分区首页或其他索引页时,需要使用普通 Markdown 链接,并从站点根 URL 开始写路径,例如:/docs/content/

进一步了解链接的用法

8.2 - 组织内容

关于如何组织文档站点的可选指导和建议。

查看我们的示例站点,你会发现其中的“文档”分区被划分为多个子分区;每个子分区都附有建议,说明适合放入哪些内容。

必须采用这种结构吗?

当然不必!示例站点的结构面向功能众多、潜在任务复杂、参考资料丰富的大型产品和大型文档集。对于更简单的文档集(比如本站),围绕用户需要了解的具体功能来组织文档就很好。即使面对大型文档集,你也可能发现这套结构不能直接照搬,或者没有必要用到其中的全部分区类型。

不过,我们建议至少提供以下内容(本站也是如此):

  • 产品的 概览——可以放在文档首页,也可以单独成页,用来告诉用户为什么值得关注你的项目;
  • 开始使用 页面;
  • 一些 示例

你也可以围绕项目功能编写任务指南或操作方法。如果更喜欢本站这种精简结构,可以复制整个 Docsy 用户指南站点,也可以只复制其中的文档分区。

进一步了解 Hugo 和 Docsy 如何利用文件夹及其他文件组织站点

为什么采用这种结构?

示例站点的结构来自我们为不同类型项目创建和使用大型文档集的经验,也参考了针对一些大型站点开展的用户研究。研究表明,用户最关心并会立即寻找的是“开始”或“开始使用”分区——顾名思义,他们希望马上动手;其次是可供探索和复制的示例。因此,我们把这两类内容设计成站点中醒目的顶层文档分区。

用户还希望找到易于检索的“配方”,以便完成具体任务,并把这些配方组合成自己的应用或项目。因此,我们建议把这类内容组织为“任务”。概念说明、参考文档和端到端教程等其他内容类型并非对所有文档集都同样重要,对小型项目尤其如此。示例站点也明确说明这些分区均为可选项。

随着我们进一步了解用户如何使用技术文档,尤其是开源项目文档,我们还会继续完善示例站点的结构。

写作风格指南

本指南和示例站点只介绍如何把文档内容组织成页面和分区。至于每个页面应如何组织和撰写内容,我们推荐参考 Google 开发者文档风格指南,尤其是其中的风格指南要点

9 - 关于 OINK

OINK 是一款本地优先、仅依赖 Hugo 的多语言技术文档主题

OINK 把 Markdown、配置与本地资源转换为完整的技术文档站点。站点只要取得主题源码并安装 Hugo Extended,就能构建文档、博客、多语言导航、本地搜索、图表、API 参考与可复用内容组件,无需安装前端工具链。

你可以先阅读文档、查看可运行示例,或浏览 OINK 产品参考

OINK 是什么

OINK 是一款直接从 Docsy 演化而来的独立主题。它保留了 Docsy 成熟的 Hugo 内容模型,同时确立了一套标准产品形态:

  • 品牌化、响应式的文档外壳;
  • 消费站点仅依赖 Hugo 的构建方式;
  • 版本明确的本地浏览器运行时;
  • 多语言路由、元数据、搜索与导航;
  • 可复用并兼顾无障碍的内容组件;
  • 双语 starter,以及可验证的离线发行包。

OINK 是项目当前使用的名称。在公开版本正式确定最终品牌、模块路径与发行坐标之前,请使用明确的检出内容、归档包或不可变版本,不要根据继承而来的元数据自行推断。

怎样的技术文档才真正有用

技术文档应帮助读者理解产品,并以尽可能低的阻力完成任务。一套好用的文档应当具备以下特征:

  • 可靠:陈述、命令、版本与示例都与产品实际情况一致。
  • 全面:不同角色的读者都能找到所需的概念、操作步骤、参考资料与故障排查内容。
  • 组织清晰:相关信息采用一致方式编排,并可通过导航、搜索与稳定链接找到。
  • 无障碍:内容、组件、配色、焦点状态与键盘操作能够服务广泛的读者。
  • 便于维护:作者无需依赖脆弱的交付流水线,就能审阅、翻译、测试和发布变更。

面对国际读者,同一信息还应在不同语言之间保持等价。OINK 把语言标识、译文路由、稳定标题 ID、搜索索引与多语言替代元数据视为基础设施,而不是可有可无的装饰。

OINK 提供什么

能力 主题提供的内容
文档与博客布局 响应式导航、面包屑、目录(TOC)、页面元数据、意见反馈、打印输出与内容索引
自动导航 Hugo 内容结构会自动形成分区导航,无需另行维护菜单清单
多语言行为 使用语言本名的选择器、译文路由、首页回退、hreflang、locale 元数据与分语言本地搜索
本地优先的浏览器能力 Bootstrap、Font Awesome、字体、Mermaid、KaTeX、Markmap、Swagger UI、Redoc、Asciinema、ECharts 与 Infographic 资源随主题交付
内容组件 标签页、折叠块、卡片、导航卡片、轮播、图示、终端录屏、数据图表与参数替换
站点自主定制 Hugo 配置、菜单、内容、项目 SCSS、模板与业务组件仍由站点自己掌控
可复现交付 仅依赖 Hugo 的生产命令、双语 starter、第三方清单、离线归档、迁移指南与自动化 fixture

简单地创作与发布

使用 Markdown 或 HTML 编写内容,通过 Hugo 本地服务器预览,再把生成的 public/ 目录发布到任意静态托管平台。消费站点的生产构建命令是:

hugo --gc --minify

请参阅部署方案,了解本地环境、GitHub Pages、Cloudflare Pages、Netlify 与对象存储的部署方式。

在本地构建与搜索

OINK 的默认路径不会在构建时下载文件,也不会产生由主题发起的第三方浏览器请求。搜索使用同源、按语言划分的索引,并为简体中文提供 CJK 子字符串回退。作者仍可配置远程分析、媒体、图表服务或托管搜索,但这些边界必须由站点显式决定。

服务多语言读者

译文可以与源页面并置保存为 page.mdpage.zh.md。主题根据 Hugo 页面模型生成语言切换与 SEO 元数据。本站以英文为首要语言、简体中文为第二语言。

请阅读多语言支持,了解内容组织、显式稳定标题 ID、RTL 行为与翻译检查。

无需复制外壳即可定制

站点负责自己的 Logo、配色、字体、菜单、内容与业务组件;OINK 负责标准外壳和通用基础组件。这种职责划分既避免维护一份复制出来的布局树,也保留了在真实产品需求下使用 Hugo 常规覆盖机制的能力。

请参阅外观与样式内容组件

上游项目与致谢

OINK 建立在成熟的开源成果之上。Hugo、Docsy 与 Fumadocs 以不同方式影响了这个项目:Hugo 是构建平台,Docsy 是直接上游,而 Fumadocs 是设计参考。

项目 与 OINK 的关系 OINK 延续的部分
Hugo 构建平台 内容模型、模板、资源管线、多语言路由、分类法与静态站点生成
Docsy 直接上游 仓库历史、文档约定、布局、Bootstrap 基础与兼容 API
Fumadocs 设计参考 克制且以内容为中心的外壳、清晰的信息层级与细腻的导航交互

Hugo:基础平台

Hugo 是 OINK 的静态站点生成器。OINK 依靠 Hugo Extended 完成内容发现、模板渲染、多语言页面、分类法、资源处理与静态文件输出。OINK 是 Hugo 主题,并不是 Hugo 的分支。

Docsy:直接上游

Docsy 是 OINK 在代码与内容模型上的直接上游。OINK 保留 Docsy 的 Apache-2.0 历史与归属信息,也延续了对现有站点仍有价值的内容约定和兼容 API。

OINK 并不是叠加在另一套 Docsy 安装之上的可选皮肤,而是把继承的主题发展成一个独立产品:提供统一的标准外壳、消费端仅依赖 Hugo 的构建方式、本地浏览器运行时、通用多语言模型,以及更多内容组件。

Fumadocs:设计参考

Fumadocs 是一款 React.js 文档框架,其设计者是 Fuma Nama。在 OINK 的项目传承与依赖关系中,它是设计参考,而不是直接代码上游或构建平台。

OINK 当前的视觉语言与文档外壳参考了 Fumadocs:克制且以内容为中心的呈现方式、信息层级、导航几何、侧边栏交互,以及页内目录的处理。OINK 针对 Hugo 与源自 Docsy 的代码库重新实现这些设计思路,而不是逐像素复制。

感谢 Fuma Nama 与 Fumadocs 社区的贡献者。感谢他们开放分享这些成果,并提升了技术文档设计的标准。

归属与边界

对 Hugo、Docsy 与 Fumadocs 的引用分别用于说明项目传承、平台依赖或设计灵感,并不表示这些项目为 OINK 背书。相关名称与商标归各自所有者所有。源码与发行包会保留适用的许可证和声明文件。

项目状态与后续步骤

当前检出内容是一份实现与文档预览。本地构建成功,本身并不能证明公开版本、托管部署或稳定的远程模块路径已经存在。