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

返回本页常规视图.

站点配置

导航菜单、多语言、版本管理与仓库链接。

站点跑起来之后,这一章处理站点级的结构性配置:读者怎么导航、内容有几种语言、文档有几个版本、页面操作指向哪个仓库。

具体页面的写法见创作内容,可用组件见组件参考

本章内容

1 - 配置

使用 Hugo 设置与职责明确的主题参数配置 Oink。

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

配置原则

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

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

完整基线配置

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

YAML
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
  copyright:
    authors: '[Example Authors](https://example.org/)'
    from_year: 2026
  footer_center_info: 'Powered by [Oink](https://oink.pgsty.com)'
  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

文件命名

本站使用并置模型:

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

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

选择器状态

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

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

品牌与代码仓库

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

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

YAML
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。

如果希望导航栏、侧边栏抽屉与页脚使用横向品牌图,请设置 params.wordmark。它接受与 params.logo 相同的 Hugo Asset 或 static/ 路径。紧凑状态的导航栏会自动回退到 params.logo,因为横向品牌图会占满整行;完全没有设置 wordmark 时,OINK 保留原有的“图标 + 标题”样式:

YAML
params:
  logo: images/product-mark.svg
  wordmark: images/product-wordmark.svg

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

YAML
params:
  page_width: normal
  ui:
    navbar_enabled: true
    footer_style: fat # fat | slim | none
    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
    taxonomy_icons:
      categories: fa-solid fa-folder
      tags: fa-solid fa-tags
    page_context_menu:
      enable: true
      assistant_links: false
      links: []
    readingtime:
      enable: true

navbar_enabledfooter_style 决定每个页面是否带站点导航栏、以及使用哪种页脚形态。两者默认开启(分别为 truefat),作用于所有布局,可以通过 cascade 按分区覆盖,也可以在页面 front matter 中覆盖;footer_style 取值无法识别时构建会失败。详见导航与菜单站点页脚

page_width 接受 normalwidefull,也可以在页面 front matter 中覆盖。侧栏最小与最大值以像素为单位,用来限制桌面端拖动调整的范围。sidebar_item_overflow: wrap 会让长标签换行;其他值保持紧凑的省略号行为。

quick_links 指定外壳中显示的顶层 page reference。请在各语言主菜单中定义相应的本地化名称。taxonomy_icons 按分类复数名设置右栏分组图标,默认 categories 用文件夹、tags 用标签,其余分类使用通用形状图标。

页面操作是面包屑行中的拆分按钮:左半边复制本页 Markdown,菜单则在所有视口宽度下保证「复制 Markdown 文本」、助手入口、「查阅 Markdown 源码」「查阅编辑历史」「编辑本页」「创建子页面」、文档与项目 issue,以及「打印整个分区」都可访问。内置 ChatGPT 和 Claude 助手入口默认关闭;设置 assistant_links: true 后才会在有源文件的页面上显示。读者激活入口时,完整的当前 URL(包括 query string 与 fragment)会随本地化提示词离开本站;OINK 不会上传页面正文。请勿在 URL 中放置秘密信息,并披露这一第三方边界。页面可用布尔型 assistant_links front matter 覆盖站点策略。

github_repo 能解析“编辑此页面”使用的同一仓库路径时,才会显示“查阅编辑历史”。links 默认为空,额外的自定义链接可使用经过 URL 编码的 {url}{title}{markdown_url} 占位符:

YAML
params:
  ui:
    page_context_menu:
      enable: true
      assistant_links: true
      links: []
      # - name: 询问外部助手
      #   icon: fa-solid fa-wand-magic-sparkles
      #   url: https://assistant.example/new?source={markdown_url}&title={title}

首页内容位于 data/home/<language>.yaml;缺少相应语言数据时回退到英文。每个语言文件包含具名数据块,以及一个可选的 sections 列表;该列表会按照准确顺序把数据块组合成首页。页脚已不再属于这份文件——它现在渲染在所有布局上,数据读取自 data/footer/<language>.yaml。详见站点页脚

组合首页分区

字符串条目会同时把该值用作分区类型与数据键。映射条目可以选择内置 type,读取另一个具名 key,设置稳定 id,或临时设置 enabled: false

YAML
sections:
  - hero
  - metrics
  - capabilities
  - type: logo-wall
    key: ecosystem
  - gallery
  - faq
  - cta

ecosystem:
  title: 使用熟悉的工具构建
  columns: 4
  items:
    - {
        name: Hugo,
        icon: fa-solid fa-bolt,
        url: https://gohugo.io/,
        external: true,
      }

映射条目也可以通过 data 直接携带内容,适合短小且只使用一次的区块。两个分区需要相同呈现时,可以用不同的键重用内置类型。站点自有布局还可以指定 partial,但这属于自定义模板契约,而不是可移植的首页数据。

如果没有 sections,OINK 会保留 0.1.x 的顺序,从 herometricscapabilitiesprinciplescta 中按存在情况进行渲染。添加 sections 表示显式组合;此后即使文件中仍有某个数据块,只要列表没有引用,它就不会出现在首页。

内置分区

OINK 0.4.0 提供 21 种分区类型:

类型 适用内容
hero 核心信息、操作按钮与跟随主题的图片
metrics 紧凑的事实、数字、链接与辅助文字
capabilities 交替的功能叙事与专用视觉面板
principles 带编号的产品原则或工作原则
cards 通用功能、价值、服务或路径集合
logo-wall 工具、集成、合作伙伴或项目渊源
gallery 带徽标与操作的截图或图标示例
testimonials 带可选署名与来源链接的引语
contributors 人员、角色、头像与个人页链接
faq 使用原生展开控件与 Markdown 答案的问答
markdown 没有合适集合布局时使用的自由文字
cta 最后一个操作,或一组紧凑操作
pricing 产品层级、价格、功能与操作按钮
pricing-compare 不同价格层级之间的功能对比矩阵
command-box 带复制操作与可选说明的聚焦命令
steps 带可选命令示例的有序步骤
timeline 带日期的里程碑、路线图与发布历史
code-plate 展示面板中的静态代码或逐行内容
case-study 带指标、引语和来源的证据型案例
download 经过验证的滚动与固定版本下载渠道
bar-chart 无需图表 JS 的非负数值比较

首页与普通 layout: landing 页面使用同一套注册表。数据解析、9 种场景型分区契约、无 JavaScript 行为与输出规则见 Landing 页面

通用集合区块接受 eyebrowtitledesctextcolumnsitems。条目字段随呈现方式而异,但统一使用 titlenamedesctexticonimageurlexternal。普通文字字段会渲染 Markdown。站内 URL 应相对于当前语言根路径;应作为外部导航打开的链接设置 external: true

每个区块都可以省略,因此无需复制布局也能得到更精简的首页。例如:

YAML
hero:
  eyebrow: 本地优先的产品文档
  title_lines:
    - words:
        - { mark: P, text: roduct, color: red }
        - { mark: D, text: ocs, color: blue }
  lead: 只用 Hugo 构建和交付的技术文档。
  image:
    light: images/hero-light.webp
    dark: images/hero-dark.webp
    alt: 产品文档工作流插图
  actions:
    - { label: 阅读文档, url: docs/, icon: fa-solid fa-book, style: primary }

可选的 hero.image 会在 Hero 右侧添加一幅跟随颜色主题的图片。将 lightdark 指向站点 static/ 目录下的文件,图片会随主题选择器切换。只配置 srclightdark 中的一项时,OINK 会在两种主题下复用该图;也可以直接用字符串配置通用图片。省略 image 则保持纯文字 Hero。

首页各分区之下,每个页面都以同一个站点页脚收尾:footer_stylefat 时先渲染多列网格,然后是 bottom bar。左侧保留 Docsy 的 params.copyright API:可以使用 Markdown 字符串,也可以使用包含 authorsfrom_yearto_year 的 Map;未设置时原样渲染 Hugo 顶层的 copyright。OINK 的 params.footer_center_info 接受行内 Markdown,默认显示 Powered by Oink,显式设为空字符串可隐藏中间区域。右侧保留语言控件。

仍在 data/home/<language>.yaml 中保留 footer 块的站点照常读取该数据。这份数据现在供给所有页面的页脚,而不只是首页。方便时请迁移到 data/footer/<language>.yaml

可导航的功能面板

价值主张区块可以把组件面板变成紧凑导航。为每个可导航项目添加 url,用 aria_label 命名导航区域,并配置一至四列。没有 URL 的项目仍是装饰卡片,因此既有面板会保持原有行为:

YAML
capabilities:
  items:
    - title: 按需加载内容能力
      visual:
        type: components
        aria_label: 浏览内容组件
        columns: 3
        compact: true
        items:
          - {
              title: Asciinema,
              icon: fa-solid fa-terminal,
              url: docs/components/layout/#asciinema,
            }
          - {
              title: Mermaid,
              icon: fa-solid fa-share-nodes,
              url: docs/components/diagrams/#diagrams-with-mermaid,
            }

项目站默认启用本地搜索:

YAML
params:
  offlineSearch: true
  offlineSearchIndex: summary
  offlineSearchSummaryLength: 70
  offlineSearchMaxResults: 10

offlineSearchIndex 控制每种语言索引中可下载的文本范围,四档范围逐级累加:title 索引标题与分类元数据;heading 增加页面标题;summary 增加描述或摘要;content 再加入完整正文。content 是兼容旧行为的默认值,而多数文档站可从体积更小的 summary 开始。offlineSearchMaxResults 同时约束 Lunr 与 CJK 子串兜底结果数。

每种语言都会得到独立索引。通过 Docsy 既有配置仍可使用托管搜索,但启用它们会显式增加外部服务边界。除非已经决定界面应显示哪一种,否则不要同时配置多个相互竞争的搜索提供方。

内容运行时

纯浏览器运行时

Mermaid 与 KaTeX 会根据内容自动检测;Markmap 需要在站点级启用:

YAML
params:
  markmap:
    enable: true
  mermaid:
    theme: default

Swagger UI、Redoc、Asciinema、ECharts、Infographic 与轮播资源会在相应短代码出现时加载。它们的本地运行时路径属于内部实现,不应配置。

服务端点

PlantUML 与 Diagrams.net 需要显式端点:

YAML
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 可以在隔离网络内部访问。

页面级覆盖

Hugo 的 .Param 查找机制允许在 front matter 中覆盖许多站点参数:

YAML
---
title: Wide reference
page_width: wide
navbar_enabled: false
footer_style: slim
hide_feedback: true
hide_readingtime: true
ui:
  no_left_sidebar: false
  scrollSpy:
    disable: false
---

navbar_enabledfooter_style 直接从 front matter 顶层读取,不在 ui 块内,因此分区可以在自己的 cascade 中一次性设定。

只应为真实的内容差异使用覆盖,不要靠逐页设置重建另一套视觉系统。

避免虚假配置

不要暴露:

  • 在“Docsy”与“OINK”外壳之间切换的开关;
  • vendor JavaScript、CSS、字体或内部 partial 的路径;
  • 品牌命名空间下重复的语言或仓库值;
  • 只用于二选一复制实现的开关。

如果站点需要定制产品矩阵或门户,请把该组件留在站点,并使用范围明确的 hook 或短代码。清晰的本地业务功能,优于误导性的全局主题选项。

验证配置变更

修改配置后:

  1. 分别使用最低支持版本与当前验证版本的 Hugo Extended 构建;
  2. 测试每种已配置语言,以及至少一个缺少译文的页面;
  3. 如果同时支持根路径与子路径部署,验证两种 baseURL 输出;
  4. 检查本地搜索与可选运行时请求;
  5. 检查桌面端和移动端外壳、深浅色主题与打印输出。

真正可接受的配置必须能够正确构建并按预期运行,而不只是可以被 YAML 解析。

2 - 导航与菜单

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

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

导航栏由 Hugo 的 main 菜单与 OINK 自动生成的控件组成:版本选择器、语言选择器、颜色模式控件、搜索,以及项目仓库链接。它会在所有布局上渲染——落地页、文档、博客、Swagger 和分类页面都不例外,因此站点级导航在任何位置都只有一次点击的距离。

navbar_enabled 默认为 true。可以对整个站点关闭导航栏,也可以通过 front matter(前置元数据)cascade 对某个分区关闭,或只对单个页面关闭:

hugo.yaml
YAML
params:
  ui:
    navbar_enabled: false
YAML
---
title: 独立报告
navbar_enabled: false
---

Front matter 的优先级高于站点参数,而且显式写出的 false 在任何层级都会生效。关闭导航栏后,OINK 会恢复此前被导航栏取代的界面:移动端子导航、侧边栏的品牌与搜索行、目录轨道上的工具按钮,以及侧边栏底部的工具区。这个开关适用于必须独占整个视口的页面,不应当作常规排版偏好使用。

导航栏只有两种状态:

宽度 状态
lg 及以上 完整:品牌、菜单文字标签与各个工具控件
小于 lg 紧凑:先是 Logo,其余条目全部右对齐为图标

紧凑状态并不是精简过的菜单。菜单项保留各自的图标,搜索仍是放大镜,版本、语言与主题控件也停在原处——没有任何东西会折叠进汉堡按钮,因为根本不存在独立的移动菜单。唯一按宽度切换的控件出现在带侧边栏的页面上:小于 md 时会多出一个图标,用于打开侧边栏抽屉。

添加 main 菜单项

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

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

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

YAML
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 可以按语言翻译,但标识符必须稳定。

嵌套下拉菜单

顶层菜单支持一级下拉。用 Hugo 的 parent 建立父子关系:

hugo.yaml
YAML
menus:
  main:
    - identifier: docs
      name: 文档
      pageRef: /docs
      weight: 20
    - identifier: docs-tutorial
      parent: docs
      name: 快速上手
      pageRef: /docs/tutorial
      weight: 10
      params:
        icon: fa-solid fa-route
        description: 安装 OINK 并创建第一个文档站

子项的 params.description 会显示在下拉项的标题下方,帮助读者判断该去哪。

交互上有一个关键设计:父级本身就是一个普通链接。悬停或键盘聚焦时展开面板,点击或按 Enter 则直接跳转到父级页面。这里没有单独的展开箭头,父级页面也不会被自己的下拉「劫持」。Esc 关闭面板并把焦点留在链接上;触屏读者会直接进入父级页面,那里的正文同样列出了这些链接。

版本菜单

配置 params.versions 后会显示版本选择器。它是一个分支图标,悬停或键盘聚焦时展开列表,与语言、主题控件共用同一套浮层样式。条目可以表示标题、分隔线、正式版本、开发版本或站点变体:

YAML
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 与文字方向属性。

浅色/深色主题菜单

启用颜色模式后,导航栏会显示主题控件。点击它在浅色与深色之间切换,悬停或聚焦则展开「跟随系统 / 浅色 / 深色」三项选择器,其中「跟随系统」采用读者操作系统的设置。详见浅色/深色模式菜单

搜索是导航栏上的一个放大镜图标,点击后打开命令面板;Cmd/Ctrl-K 以及在可编辑控件之外按下的 / 同样可以打开它。启用离线搜索后该图标才会出现;关闭导航栏时,搜索行会回到侧边栏顶部。在线搜索集成仍可通过显式配置启用。详见搜索

为导航栏添加图标

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

YAML
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"

小于 lg 时,图标就是菜单项仅剩的表现形式,因此每个顶层菜单项都应配置 pre 图标。没有图标的菜单项在紧凑状态下无内容可显示。

侧边导航

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

从侧边栏隐藏页面:

YAML
toc_hide: true

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

侧边导航选项

常用控制项如下:

YAML
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 允许读者展开或折叠分区。博客栏目默认展开,在栏目 front matter 中设置 sidebar_expanded: false 可让它默认收起;
  • sidebar_menu_truncate 限制条目数,数值过小时会发出构建警告;
  • sidebar_cache_limit 在站点规模超过阈值后启用共享导航标记;
  • sidebar_width_minsidebar_width_max 限制桌面端拖拽调整的宽度;
  • sidebar_item_overflow 默认为 ellipsis,长标签需要换行时改用 wrap

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

为侧边导航添加图标

在页面 Front Matter 中设置 icon

YAML
---
title: 运维
---

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

叶子页面全都带图标会产生明显的视觉噪声。用 sidebar_icon_policy 控制密度:

hugo.yaml
YAML
params:
  ui:
    sidebar_icon_policy: groups # all | groups | none
取值 效果
all 每个有图标的侧栏条目都显示
groups 只有根节点和带子页的节点显示图标,普通叶子页不显示
none 侧栏完全不显示条目图标

未设置时的兼容默认值是 all新站点建议显式设为 groups——保留了分组的语义标识,同时去掉叶子层的噪声。本站就用这个设置。

无效取值会产生警告并回退到 all

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

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

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

侧边栏树以读者当前所在的顶层分区为根,树上方那一行标出的就是这个根节点。规模较大的子树——带版本的 API 参考、独立的手册——可以自己成为一个根节点,让读者不必离开当前分区就能切换过去:

YAML
params:
  ui:
    sidebar_root_enabled: true
    sidebar_root_menu: true

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

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

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

切换器的范围限定在当前顶层分区之内:条目是该分区本身(默认项),加上每个设置了 sidebar_root_for: self 的后代分区。同级的其他顶层分区不会列出——在文档与博客之间跳转是导航栏的职责。因此,没有可切换后代的分区根本不显示下拉框,那一行只是一个指向分区落地页的普通链接,没有边框,与树的顶层条目对齐。

分类术语页没有内容层级,因此术语会采用其成员共同所属的顶层分区。从文档页面点进某个标签后,侧边栏保留的仍是文档树和文档根链接,而不会回退到站点级的树;成员横跨多个分区的术语则不显示根节点行。

页面目录

Hugo 根据 Markdown 标题生成右侧页面大纲。OINK 把它渲染为固定右栏中的第一个分组,后面依次排列当前分区的分类标签云。读者可以折叠整个右栏,状态保存在本地。

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

目录定制

在单个页面隐藏大纲:

YAML
notoc: true

配置 Hugo 收录的标题层级:

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

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

右栏分组

右栏里的每个分组都使用同一套标题行:图标、标题和折叠箭头,整行作为一个条目高亮。大纲分组的标题是「目录」,它的图标是一个三横线字形,作用是折叠整个右栏,而不只是装饰。在侧边栏抽屉中,该分组保留静态的三横线图标,从而与旁边的分类标题保持一致。

分类分组的图标可以按分类复数名配置:

hugo.yaml
YAML
params:
  ui:
    taxonomy_icons:
      categories: fa-solid fa-folder
      tags: fa-solid fa-tags
      projects: fa-solid fa-diagram-project

categories 默认使用文件夹图标,tags 默认使用标签图标;其他分类在这里指定之前,一律使用通用的形状图标。标签云本身的范围规则参见分类法支持

使用 ScrollSpy 跟踪目录活动项

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

YAML
params:
  ui:
    scrollSpy:
      disable: true

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

ScrollSpy 高级定制

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

普通内容页上方和分类结果中会显示面包屑,这一行同时承载页面操作。顶层分区页面保留只有一级的面包屑,使这一行在任何层级都保持稳定。全局关闭面包屑的方式如下:

YAML
params:
  ui:
    breadcrumb_disable: true
    taxonomy_breadcrumb_disable: true

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

页面操作

页面操作是面包屑行末尾的一个纯图标拆分按钮。左半边复制本页 Markdown,成功后翻转为绿色对勾;右侧箭头展开一个包含十项操作的菜单,分为两组。上半组负责把页面内容带到别处:

  • 复制 Markdown 文本
  • 在 ChatGPT 中打开
  • 在 Claude 中打开
  • 查阅 Markdown 源码
  • 查阅编辑历史

其后是一条分隔线,下半组负责修改或产出内容:

  • 编辑本页
  • 创建子页面
  • 提交文档 issue
  • 提交项目 issue
  • 打印整个分区

配置的 page_context_menu.links 排在最后,前面还有一条分隔线。每个条目只有在能解析出目标时才出现:Markdown 相关操作需要 markdown 输出格式,仓库相关操作需要 github_repo,项目 issue 需要 github_project_repo,助手操作则需要 params.ui.page_context_menu.assistant_links

在博客根分区及其一级子分区上,左半边变成 RSS 链接,菜单中仍保留「复制 Markdown 文本」。博客叶子页面不显示订阅图标。没有 Markdown 输出的页面会去掉左半边,改为渲染带文字的「操作」按钮。

create_child_pagecreate_project_issueprint_section 现在都是注册表中的一等操作,因此也会出现在命令面板中。页面级的 print 操作已废弃,读者直接使用浏览器自带的 Cmd/Ctrl+P

页脚会在所有布局上渲染,由 footer_style 在三种形态之间选择:

取值 渲染内容
fat 版权行之上的多列网格(默认值)
slim 只有版权行
none 完全不渲染页脚
hugo.yaml
YAML
params:
  ui:
    footer_style: fat

Front matter(包含分区 cascade)的优先级高于站点取值:

YAML
---
title: 内嵌参考
footer_style: slim
---

无法识别的取值会令构建失败,而不是静默回退。

多列网格读取 data/footer/<语言>.yaml;单语言站点可以直接使用 data/footer.yaml

data/footer/zh.yaml
YAML
brand:
  name: 产品文档
  tagline: 一段简短的**支持 Markdown 的**说明。
  slogan: 贴近产品,给出明确答案。
columns:
  - title: 文档
    links:
      - { label: 文档, url: /docs/ }
      - { label: 博客, url: /blog/ }
  - title: 项目
    links:
      - { label: GitHub, url: https://github.com/pgsty/oink, external: true }

brand.namebrand.logo 未设置时回退到站点自身的品牌名、Logo 和 wordmark。taglineslogan 会渲染 Markdown。站内 url 以语言根为基准解析;external: true 会在新标签页打开链接并附带 rel="noopener noreferrer"。网格的列数由数据中的列数决定。

没有数据的 fat 页脚会降级为 slim,因此站点可以先保留默认值,再逐步补齐各列内容。

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

GO-HTML-TEMPLATE
{{ partial "td/render-heading.html" . }}

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

标题别名与页内目标

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

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

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

快速开始

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

实现说明

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

3 - 多语言

语言配置、译文组织、稳定锚点与 RTL 支持。

OINK 直接使用 Hugo 的多语言页面模型,不引入站点专属的域名约定或模板假设。本站以英文为首要语言、简体中文(zh)为第二语言。

配置语言

hugo.yaml
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日
label , string , required

语言选择器里显示的名字,用该语言自己的文字写——简体中文 而不是 Chinese

locale , string

标准语言标签,用于 <html lang>hreflang 备用链接和 Open Graph 元数据。

weight , integer

同时决定语言排序和选择器轮换顺序,数字小的在前。

title , string

该语言下的站点标题。

params.* , map

语言级参数覆盖全局同名值;没定义的继承全局。日期格式通常需要按语言设置。

菜单标签因语言而异时,在各语言下分别定义 menus

组织译文

译文与原文并排放在同一目录,用文件名后缀区分:

  • content/docs
    • install.md
    • install.zh.md

相同的基础文件名让 Hugo 把它们识别为同一页面的不同语言版本。

要保持一致的:日期、权重、别名、页面资源,以及所有影响路由的元数据。

要翻译的:front matter 的 titledescription、摘要、菜单标签、标签、图片 alt 文本、提示块、shortcode 的可见参数。

不要翻译的:命令、标识符、配置键、文件名、URL、产品名。

稳定的标题锚点

这是多语言文档最容易出问题的地方。Hugo 从标题文本生成 ID,所以中文标题会生成中文 ID,/docs/page/#install/zh/docs/page/#安装 变成两个互不相通的锚点。

在译文标题里显式写上原文 ID:

MARKDOWN
## 安装 {#install}

翻译已有页面时,ID 要从英文渲染出的 HTML 里取,不要凭标题文本猜——含 shortcode 或行内代码的标题,生成的 ID 往往和你想的不一样。

本站用一个脚本强制中英标题数量、顺序和 ID 完全一致:

BASH
node scripts/check-doc-translations.mjs --public public

语言选择器行为

选择器读取每个页面的 .Translations

  • 目标语言有对应译文 → 直接跳到那一页
  • 目标语言没有译文 → 回退到该语言的首页

回退是有意设计,不是缺陷。把读者送到一个不存在的 URL 更糟。

搜索与语言

offlineSearch: true 时,每种语言生成各自独立的索引:

TEXT
public/offline-search-index.en.json
public/offline-search-index.zh.json

读者在中文页面搜索,只会命中中文内容。

中文查询走主题的 CJK 子串回退——Lunr 无法可靠地对中文分词,所以命令面板会在检测到 CJK 字符时切换到子串匹配路径,两条路径应用相同的排序加权。

从右向左的语言

在语言下声明书写方向:

hugo.yaml
YAML
languages:
  ar:
    label: العربية
    locale: ar
    languageDirection: rtl
    weight: 3

OINK 会加载 Bootstrap 的 RTL 样式表,主题自身的 CSS 使用逻辑属性(margin-inline-start 而非 margin-left),因此镜像布局是自动的。

站点自己写的 CSS 也应使用逻辑属性,否则 RTL 下会错位。

界面文案翻译

主题内置 32 个 locale 的界面文案。英文、简体中文(zh-cn 与通用 zh)和繁体中文(zh-tw)经过完整审校;其余语言保留继承自 Docsy 的翻译,OINK 新增的标签暂时使用英文兜底。

站点要覆盖某条界面文案时,在自己的 i18n/ 下建同名文件:

i18n/zh.yaml
YAML
ui_search: 搜索文档

翻译检查清单

  • 每个 page.md 都有对应的 page.zh.md
  • 中文标题带显式 ID,且与英文渲染 ID 一致
  • 影响路由的 front matter 保持一致
  • 命令、配置键、URL 未被翻译
  • 语言选择器在有译文和无译文的页面上都验证过
  • 两种语言的搜索都能返回结果

下一步

4 - 版本管理

让读者在多个文档版本之间切换,并标记归档版本。

产品有多个受支持版本时,文档通常也要分版本。OINK 提供两样东西:版本切换菜单和归档版本横幅。

各版本具体怎么部署由你决定——常见做法是每个版本一个子域名或子路径,各自独立构建。

版本切换菜单

params.versions 中列出要出现在菜单里的版本:

hugo.yaml
YAML
params:
  version_menu: v2.1
  versions:
    - version: v2.1
      url: https://docs.example.com
    - version: v2.0
      url: https://v2-0.docs.example.com
    - version: v1.9
      url: https://v1-9.docs.example.com
version_menu , string

菜单按钮上显示的文字,通常是当前版本号。

versions[].version , string , required

版本标识,显示在菜单项上。

versions[].url , string , required

该版本文档站的地址。留空的条目会显示为不可用。

version_menu_pagelinks , boolean , default: false

是否把当前页面路径附加到目标版本的 URL 后面。

菜单里可以用 - name: '---' 插入分隔线,把「受支持版本」和「历史版本」分开:

hugo.yaml
YAML
params:
  versions:
    - name: '**当前版本**'
    - version: v2.1
      url: https://docs.example.com
    - name: '---'
    - name: '**历史版本**'
    - version: v1.9
      url: https://v1-9.docs.example.com

逐页跳转的取舍

version_menu_pagelinks: true 会把当前页面路径拼到目标版本的 URL 上,读者切换版本时停留在同一篇文档。

代价是:目标版本不一定有这个页面。文档结构在版本之间演进,旧版本可能没有新写的页面,读者会撞上 404。

hugo.yaml
YAML
params:
  version_menu_pagelinks: true
  versions:
    - version: v2.1
      url: https://docs.example.com
    - version: v1.9
      url: https://v1-9.docs.example.com
      pagelinks: false # 这个版本结构差异大,只跳首页

单个版本条目上的 pagelinks: false 会覆盖全局设置,让该版本只跳转到首页。

归档版本横幅

在不再维护的旧版本站点上,显式告诉读者:

hugo.yaml
YAML
params:
  archived_version: true
  version: v1.9
  url_latest_version: https://docs.example.com
archived_version , boolean , default: false

设为 true 时,在每个页面顶部显示归档提示横幅。

version , string

横幅中显示的当前版本号。

url_latest_version , string

指向最新版本的地址。横幅会给出一个链接。

横幅文案随站点语言本地化,不需要你自己写。

部署布局

两种常见做法:

布局 baseURL 特点
子域名 https://v1-9.docs.example.com/ 各版本完全独立,互不影响
子路径 https://docs.example.com/v1.9/ 单一域名,需要托管方支持路径路由

各版本是独立构建的:从对应的 Git 分支或标签检出内容,用该版本自己的 hugo.yaml 构建,产物发布到对应地址。OINK 不提供跨版本的单次构建。

下一步

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

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

OINK 的文档与博客布局可以显示指向当前页面源码仓库的链接。它们位于面包屑行末尾的页面操作菜单中:

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

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

典型站点配置如下:

YAML
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 链接:

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

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

github_subdir(可选)

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

YAML
params:
  github_subdir: oink.pgsty.com

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

github_project_repo(可选)

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

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

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

github_branch(可选)

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

YAML
params:
  github_branch: main

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

path_base_for_github_subdir(可选)

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

YAML
---
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 可以是正则表达式。按语言目录组织内容的站点可以写成:

YAML
path_base_for_github_subdir: content/\w+/reference

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

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

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

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

github_url(可选)

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

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

使用该值的页面会显示 编辑本页,但不显示 查阅编辑历史:这个不透明 URL 没有可供 OINK 推导历史链接的仓库路径。当目标与 GitHub 不兼容时,更适合使用站点专属模板覆盖。

菜单中的每个条目都在 data-oink-action 上携带稳定的操作 ID:

链接 操作 ID
查阅生成源码 view_markdown
查阅编辑历史 view_history
编辑本页 edit_page
创建子页面 create_child_page
创建文档 issue create_issue
创建项目 issue create_project_issue

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

SCSS
.td-page-actions__item[data-oink-action='create_child_page'] {
  display: none;
}

命令面板中的操作使用同一批 ID,因此只隐藏菜单条目并不会让对应命令从面板中消失。

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

页面最后修改信息

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

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

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

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