本文既是 matery 主题布局与页面功能的完整使用教程,也是 自动化测试靶场。覆盖:文章布局特性、侧边栏组件、响应式断点、相册、搜索、评论、暗色模式、加密、RSS/站点地图、PWA。自动化测试(
tools/tests/release-test.sh)会断言下方标注的渲染标记。阅读方式:每个功能都先看 效果(真实渲染或行为描述),再看 对应配置(产出该效果的最小片段),需要查参数时直接跳到该节的「全部可调项」表。配置片段来自主题配置
userConfig/_config.tmp.yml(构建时注入themes/matery/_config.yml)。
1. 文章布局特性
1.1 上下篇导航(上一篇/下一篇)
效果:文章末尾左右两张卡片,各显示相邻文章的封面 + 标题 + 摘要,hover 有渐变背景;某一侧没有文章时渲染对方卡片占位(left-badge / right-badge 标记)。
对应配置:没有开关——post-detail.ejs:171 无条件渲染 _partial/post/prev-next.ejs。
# 配置文件里存在 post.prev_next.enable: true,但模板未读取该键,
# 改名/关闭都不会让导航消失。真正能影响它的是 front-matter:
# hide: true → prev_post()/next_post() 递归跳过该文,导航不指向它⚠️ 历史误记:旧版本本文写的
prenext.enable不是配置键,prenext-posts只是渲染出来的 DOM id。
测试断言:文章页含
#prenext-posts容器。
1.2 版权声明
效果:文章底部授权卡片——标题、作者、原文链接、发布日期、CC 图标 + 协议名(如 CC BY-NC-SA 4.0);开了 update_date 还会多一行"更新于"。
对应配置:
post:
copyright:
enable: true # 总开关
license: 'cc_by_nc_sa' # 默认协议
author:
enable: true # 显示作者
post_date:
enable: true # 显示发布日期
format: "LL"
update_date:
enable: false # 显示更新日期
format: "LL"常用选项:
① 换协议(8 种 + 两种特殊)
效果:卡片右下角的 CC 图标与名称随之变化;noreprint 显示"不允许转载",pay 显示"付费转载"。
对应配置(license 可选值,映射表见 copyright.ejs):
post:
copyright:
license: 'cc_by_nc_sa'
# cc_by | cc_by_nd | cc_by_sa | cc_by_nc | cc_by_nc_nd | cc_by_nc_sa
# cc0 | noreprint | pay② 单篇关闭 / 单篇换协议
效果:copyright: false 让这一篇不出授权卡片;reprintPolicy 只改这一篇的协议,其余文章仍用全局 license。
对应配置(写在文章 front-matter):
---
copyright: false # 本篇不显示版权卡片
# 或
reprintPolicy: cc_by # 本篇单独用 CC BY
---测试断言:文章页含版权声明容器(
#reprint-statement/.license-box)。
1.3 文章时效提示
效果:文章开头插入一条 note:发布超过 warning_day 天用 warning 样式,超过 error_day 天升级为 danger/错误样式。本站当前配置为关闭,所以本文看不到这条提示。
对应配置:
post:
outdate:
enable: false # 本站当前关闭;改 true 即对全站文章生效
warning_day: 365 # 超过 365 天 → warning 样式
error_day: 3650 # 超过 3650 天 → error 样式常用选项:
① 只留"过期"不升级
效果:所有过期文章都停在 warning 样式,不会突然变成刺眼的红色块。
对应配置:把 error_day 设得比站龄还大(如 36500)。
② 单篇豁免
效果:常青文章(如本系列教程)不显示过期提示。
对应配置:front-matter 写 outdate: false(该篇不插提示条)。
测试断言:启用后文章页含时效提示元素(
outdate相关 class);当前配置下不断言存在。
1.4 TOC 目录
效果:桌面端右侧悬浮目录树,随滚动高亮当前标题;移动端折叠进侧边栏。
对应配置(主题 _config.yml 顶层 toc,post / wiki / msg / bb 等所有含 TOC 的页面共用):
toc:
enable: true
headingSelector: "h2,h3,h4,h5,h6" # 参与目录的标题级别
collapseDepth: 0 # 折叠深度(0 = 全部折叠)常用选项:
① 只收 h2/h3
效果:目录只两层,侧栏更短,长文滚动时高亮跳变更少(h4 以下不再打断)。
对应配置:
toc:
headingSelector: "h2,h3"② 默认展开层级
效果:collapseDepth: 1 时一级标题默认展开其子级,读者不用逐层点开。
对应配置:
toc:
collapseDepth: 1 # 大于 0:存在下级标题则默认展开2026-09-25 更新:配置由
post.toc提升为顶层toc,wiki 页与 post 页共用同一份配置(此前 wiki 硬编码h1, h2, h3且完全忽略配置);同时移除了语义误导的showToggleBtn(它只决定按钮点击处理器是否注册,并非显隐开关)与无实现的placement(死配置)。
测试断言:文章页含
#toc-content目录容器。
1.5 广告位(TOC 下方)
效果:文章 TOC 下方出现一组彩色广告卡片(.tgCard),单条宽或两列(colspan 决定);没有任何一条 enable: true 时整块不渲染(不是空壳)。
对应配置:
post:
advertisements:
- id: "ad-1"
text: "广告文案"
link: "https://example.com"
enable: true字段全解见 §2.10(位置在此,全参数表在那里)。
测试断言:文章页含
.tgCard广告容器。
1.6 文章字数与阅读时长
效果:文章头部 article-info 显示"字数 · 阅读时长"(如 4.2k 字 · 14 分钟);页脚显示全站总字数。
对应配置(显示开关在 postInfo,计算系数在 post.min2read):
postInfo:
wordCount: true # 显示单篇字数
min2read: true # 显示阅读时长
post:
min2read:
awl: 1 # 每个字词的长度:中文 1 字 = 1 字符 → 1
wpm: 300 # 每分钟阅读字数:中文技术文章建议 250-350实现(themes/matery/scripts/helpers/wordcount.js):用三个内置 helper 取代原 hexo-wordcount 插件,函数名与签名完全一致,模板零改动:
| helper | 作用 |
|---|---|
wordcount(page) | 单篇字数(中文字符数 + 英文词数) |
min2read(page) | 阅读时长(分钟)= floor(字数 / (awl × wpm)) + 1 |
totalcount(site) | 全站总字数(文章 + wiki 页;多语言版本去重;加密文章按明文 post.origin 计数) |
常用选项:
① 只显示字数、不显示时长
效果:article-info 少一项,头部更紧凑。
对应配置:
postInfo:
wordCount: true
min2read: false② 按英文阅读速度算
效果:awl: 2 / wpm: 60(等效 120 字/分钟)下 520 字文章显示 5 分钟——这就是旧的错误默认值。
对应配置:
post:
min2read:
awl: 2
wpm: 60⚠️ 历史坑(2026-09-22 修正):旧默认值
awl: 2 / wpm: 60等效 120 字/分钟,为英文设计——520 字的中文文章显示 5 分钟;新默认值awl: 1 / wpm: 300下同一篇文章显示 2 分钟。
测试断言:文章页含
article-info字数/时长标记;三个 helper 的行为由tools/tests/unit/helpers.test.js覆盖。
1.7 更新时间(更新于)
效果:文章头部显示「更新于 2026年9月26日 上午」(各篇不同);从未改动过的文章不显示该行 —— 由 compare_date(page.date, page.updated) 守卫:两个时间相等时不出这一行。
数据来源(2026-09-26 修复):取文章源文件在 git 中的最后提交时间 —— 既不是文件 mtime,也不等于发布时间。
| 候选来源 | 为何不用 |
|---|---|
文件 mtime | 会被 git checkout/clone/批量脚本重置(实测 142 篇 _posts 的 mtime 全落在同一秒)→ 全站显示同一天 |
date(发布时间) | 无法反映修改,出现「更新于 == 发布于」 |
| git 提交时间 ✅ | 提交时固化,不受工作树任何操作影响 |
对应配置:
postInfo:
update: true # 显示"更新于"行的开关(模板读的是这个键)
post:
updated:
enable: false # 兼容位:post-detail 未读它,改它不生效
date_format: "LL a" # 日期格式
relative: false # true → 显示"3 天前"
note_class: info # note 样式:default|primary|info|success|warning|danger|light
updated_option: 'date' # 仅作兜底——'empty' 会让第三方 sitemap 模板的 post.updated.toDate() 崩溃常用选项:
① 相对时间
效果:「更新于 3 天前」而非「更新于 2026年9月26日 上午」。
对应配置:
post:
updated:
relative: true② 作者手动指定
效果:该篇显示 front-matter 写的日期,不再用 git 时间。
对应配置(front-matter):
---
updated: 2026-10-01 09:00:00
---实现:scripts/filters/post-updated.js 在 before_generate(早于全部 generator)用 scripts/utils/git-time.js 读到的 git 时间覆盖 post.updated;作者在 front-matter 写 updated: 时以作者值为准。
测试断言:
tools/tests/unit/unit-updated.test.js(4 用例:中文路径命中 / 不存在返回 null / 未显式指定被覆盖且为 moment / 作者显式值保留);文章页含datetime="…" update。
2. 侧边栏组件(widgets)
本节 10 个组件分两类:页面级自动渲染(无配置键,页面存在即有)与配置驱动(主题
_config.yml里的键)。每项都标出它由谁渲染、哪个键控制。
2.1 分类雷达图
效果:/categories/ 页面顶部一张 canvas 雷达图,每个分类一个维度,轴长 ∝ 该分类文章数;明暗切换时重绘。
对应配置:无配置键——categories.ejs:11 无条件渲染 _partial/widget/category-radar.ejs,数据来自文章分类。要它出现只需有分类页:
---
title: categories
type: "categories"
layout: "categories"
---菜单入口是另一回事(
menu.<任意名>的url+icon),与组件是否渲染无关。
测试断言:
/categories/含<canvas>元素。
2.2 标签云
效果:/tags/ 页面下方一排彩色圆角 chip,标签字号随机(视觉层次),点击跳到对应标签页。
对应配置:无配置键——tags.ejs:13 渲染 tag-cloud.ejs,数据来自全部文章标签。
测试断言:
/tags/含.tag-chips .chip。
2.3 词云
效果:/tags/ 页面 ECharts 词云(2026-09-05 自写螺旋 → echarts-wordcloud@2,修复 30s 超时);字号连续 sizeRange [14,56] ∝ 文章数;容器高度自动 max(minHeight, 词数 × heightPerWord) —— 词多不会裁剪,词少不留大片空白。
对应配置:
wordcloud:
minHeight: 500 # 词云最小高度(px),词少时兜底
heightPerWord: 2.2 # 每词高度增量(px/词),词多时扩展高度防裁剪常用选项:
① 词多时更高
效果:500 个标签时容器高度 = max(500, 500×2.2) = 1100px,不会把词挤成一团。
对应配置:
wordcloud:
heightPerWord: 3 # 每词 3px,词多时容器更高标题
标签词云走 i18n;明暗走Matery.echarts.init自动切换(无需额外配置)。
测试断言:
/tags/含#tag-wordcloud容器 + echarts 已加载且仅 1 份 + 明暗切换 dispose→reinit(tools/tests/wordcloud.test.jsL2g)。
2.4 归档时间线
效果:/archives/ 页面按年/月垂直时间线排列全部文章,圆点 + 年份折叠,滚动淡入。
对应配置:无配置键——archive.ejs 内置渲染(#cd-timeline),文章列表直接来自 site.posts。
测试断言:
/archives/含#cd-timeline。
2.5 关于页统计(postCharts)
效果:/about/ 页面三张 ECharts 图:文章发布趋势(#posts-chart)、分类分布(#categories-chart)、标签分布(#tags-chart),明暗自适应。
对应配置:无配置键——about.ejs:48 无条件渲染 post-charts.ejs;需要有 /about/ 页面(layout: about)。
测试断言:
/about/含#postCharts容器 + 多个<canvas>元素。
2.6 ArtiTalk 说说
效果:/about/ 页面底部嵌入 ArtiTalk 说说墙(LeanCloud 后端),可发短动态。默认禁用,所以本站关于页看不到它。
对应配置:
artitalk:
enable: false # 默认禁用;改 true 才渲染
appId: # LeanCloud App ID
appKey: # LeanCloud App Key常用选项:
① 启用说说墙
效果:#artitalk_main 容器出现并加载 jsDelivr 上的 artitalk 脚本,填入 appId/appKey 后即可读写真说。
对应配置:
artitalk:
enable: true
appId: "{{LEANCLOUD_APP_ID}}"
appKey: "{{LEANCLOUD_APP_KEY}}"模板无
enable时会整块跳过(about.ejs:62的if守卫),因此默认状态下不断言存在。
测试断言:默认状态下不渲染。
2.7 我的项目(myProjects)
效果:/about/ 页面三列项目卡片,每张含 Font Awesome 图标 + 渐变底 + 项目名 + 一句话简介,点击跳外链。
对应配置:
myProjects:
enable: true
data:
LinuxPTTools: # 项目名(卡片标题)
icon: fab fa-linux # Font Awesome 类名
iconBackground: 'linear-gradient(to bottom right, #66BB6A 0%, #81C784 100%)'
url: https://github.com/appotry/PTtool # 点击跳转
desc: 为PT用户设计,方便同时刮削和做种。 # 一句话简介常用选项:
① 关掉整个板块
效果:关于页不渲染 .my-projects 卡片区(其余板块不受影响)。
对应配置:
myProjects:
enable: false② 加一个项目
效果:多一张卡片,图标/渐变/链接/简介各按自己的值。
对应配置:在 data: 下追加一个同名层级(键名即标题,可含冒号,如 Docker:hexo)。
测试断言:
/about/含.my-projects容器。
2.8 我的技能(mySkills)
效果:/about/ 页面两列技能条,彩色渐变填充 + 百分比数字,按 percent 宽度渲染。
对应配置:
mySkills:
enable: true
data:
C语言:
background: 'linear-gradient(to right, #2196F3 0%, #42A5F5 100%)'
percent: 90%常用选项:
① 调熟练度
效果:进度条填充宽度随 percent 变化;90% 与 45% 并排时长度明显不同。
对应配置:
mySkills:
data:
C语言:
percent: 45%② 换配色分组
效果:同一色系的技能在视觉上归为一组(本站按蓝/粉/紫/青/绿五系分组)。
对应配置:同组技能用同一个 background 值即可。
测试断言:
/about/含.my-skills容器。
2.9 归档日历(postCalendar)
效果:/archives/ 页面顶部一块日历热力图(#post-calendar),格子颜色深浅表示当月发文数。
对应配置(注意:这是布尔根键,不是 map):
postCalendar: true # 在归档页显示文章日历控件常用选项:
① 关掉日历
效果:归档页直接从时间线开始,没有日历卡片。
对应配置:
postCalendar: false测试断言:
/archives/含#post-calendar容器。
2.10 侧边栏广告(advertisement)
效果:文章 TOC 下方一组广告卡片;有 image 时显示图片,没有则显示 text;colspan: 2 占整行,colspan: 1 占半行;暗色模式自动换 background_dark。
对应配置:
post:
advertisements:
- id: "ad-1" # 唯一标识(data-tg-id)
text: "广告文案" # 无 image 时显示的文字
link: "https://example.com" # 点击跳转
background: "#ff6666" # 亮色底
background_dark: "#8b3a3a" # 暗色底(缺省回退亮色)
colspan: 1 # 1=半行,2=整行
enable: true # 只有 true 的才渲染常用选项:
① 图片广告
效果:卡片里是一张图(图片替代文字),文字仅作 alt。
对应配置:
post:
advertisements:
- id: "ad-aliHongBao"
text: "支付宝扫码领红包"
link: "/msg/"
background: "var(--card-bg-color)"
colspan: 1
image: "/medias_webp/misc/hongbao.webp"
enable: true② 整行大卡 + 关掉某条
效果:colspan: 2 的广告独占一行;enable: false 的那条既不渲染也不占位。
对应配置:
post:
advertisements:
- id: "ad-3"
text: "广告招租"
link: "mailto:admin@example.com"
background: "#66ff66"
background_dark: "#3a8b3a"
colspan: 2
enable: false无启用广告时输出空(
advertisement.ejs先some(ad => ad.enable)判定)。
测试断言:文章页含
.tgCard广告容器。
2.11 关于页相册(myGallery)
效果:/about/ 页面下方一排缩略图卡片(/medias_webp/featureimages/ 系列),点击进灯箱。
对应配置:
myGallery:
enable: true
data:
- /medias_webp/featureimages/0.webp
- /medias_webp/featureimages/1.webp
- /medias_webp/featureimages/2.webp常用选项:
① 换图片集合
效果:卡片数量与图片一一对应,顺序即显示顺序。
对应配置:data: 下逐行追加/删除路径即可,无数量上限(多了会换行)。
与 §7 的
/galleries/独立相册页不是同一套:这里只是关于页的图片墙,没有封面/描述/加密。
测试断言:
/about/含.my-gallery容器。
3. 响应式断点
用途:用四个 Stylus 变量把视口宽度切成四段,决定导航折叠、侧栏显隐、TOC 位置、特效开关。
学习路径:改哪个变量 → 常见改法 → 全参数表 → 边界与冲突
3.1 如何启用
效果:默认四段——<768px 导航折叠为汉堡菜单、正文单列;768–992px 平板布局、侧边栏收起;>992px 桌面三栏(正文+侧边栏+TOC);>1400px 启用樱花/水波等特效。
对应配置(themes/matery/source/css/_variables/base.styl):
$breakpoint-sm: 368px // 超小屏(手机竖屏)
$breakpoint-md: 768px // 平板/手机横屏
$breakpoint-lg: 992px // 桌面
$breakpoint-fun: 1400px // 特效启用阈值(大屏)3.2 常用选项
① 提前进入桌面三栏
效果:横向 1280px 的笔记本上就显示侧栏 + TOC,不必等 1400px 宽屏。
对应配置:
$breakpoint-lg: 1200px② 推迟特效
效果:1400px 以下一律不加载樱花/水波动画,低配机器滚动更顺。
对应配置:
$breakpoint-fun: 1600px3.3 全部可调项
| 键 | 类型 | 默认 | 可选值/范围 | 作用 |
|---|---|---|---|---|
$breakpoint-sm | length | 368px | 任意 CSS 长度 | 超小屏(手机竖屏) |
$breakpoint-md | length | 768px | 任意 CSS 长度 | 平板/手机横屏 |
$breakpoint-lg | length | 992px | 任意 CSS 长度 | 桌面三栏 |
$breakpoint-fun | length | 1400px | 任意 CSS 长度 | 特效启用阈值(大屏) |
3.4 边界与冲突
- 定义在 Stylus 变量文件而非
_config.yml:改完必须重新编译(clean 构建),只看源码不生效。 - 四个值共同把宽度切成四段,改任一值会连带移动相邻两段的边界(下面的区间表是结果,不是独立配置)。
- 与 §4 的内容宽度阶梯正交:断点控制布局形态,
max-width控制列宽上限。
3.5 小结
| 断点 | 布局表现 |
|---|---|
<768px | 导航折叠为汉堡菜单,正文单列 |
768–992px | 平板布局,侧边栏收起 |
>992px | 桌面三栏(正文+侧边栏+TOC) |
>1400px | 特效启用(樱花/水波等) |
效果汇总:Bootstrap/Materialize 栅格自适应,侧边栏在窄屏自动隐藏,TOC 移动端折叠。
测试断言:
<768px导航折叠;>992px桌面三栏;>1400px特效启用。
4. 内容宽度统一(2026-09-07 新增)
用途:让正文列在不同屏幕上既铺满又不至于宽到读不下去。
学习路径:为什么要"比例+上限" → 单栏/多栏怎么选 → 宽度阶梯表 → 边界
4.1 如何启用
效果:4K 屏上正文列封顶 1525px(单栏)/ 2250px(多栏),不会拉成 3456px 宽的一行字;FHD 上则是 1125px / 1250px。
对应配置(_variables/base.styl,mixin content-container(wide)):
// 单栏:内容无侧栏
.content-container() // → 标准宽度阶梯
// 多栏:内容 + 侧栏/TOC
content-container(true) // → 宽宽度阶梯设计原则:“比例 + 上限”——内容区 width: 90%(相对屏幕)+ max-width 阶梯(可读性上限)。不要纯 90%——4K 下 90% = 3456px 不可读。
4.2 常用选项
① 单栏页面用标准宽度
效果:wiki 单栏页在 FHD 上 1125px、2K 上 1525px、4K 仍 1525px(封顶)。
对应配置:
.content-container() // 不传参 = 单栏② 多栏页面用宽宽度
效果:wiki(多栏)4K 内容列 899px → 1500px(+67%),与 post-container 同宽阶梯。
对应配置:
.content-container(true) // 传 true = 多栏选型规则:
- 单栏(内容无侧栏):
content-container()→ 标准宽度 - 多栏(内容 + 侧栏/TOC):
content-container(true)→ 宽宽度 - 禁止 layout 手写 max-width(统一走 mixin)
4.3 全部可调项
| 断点 | 标准(单栏) | 宽(多栏) |
|---|---|---|
| FHD 993-1920px | 1125px | 1250px |
| 2K 1921-2560px | 1525px | 1750px |
| 4K 2561px+ | 1525px(封顶) | 2250px |
4.4 边界与冲突
- 阶梯值写死在 mixin 里,改一处影响所有调用方;给单个页面破例会破坏"全站同宽"的视觉一致性。
- 与 §3 断点正交:断点决定"几栏",这里决定"每栏多宽"。
- 4K 单栏封顶 1525px 是有意的——再宽阅读时眼球回扫距离过长。
4.5 小结
- 默认单栏就够了:
content-container()一行 - 有侧栏/TOC:
content-container(true) - 别手写 max-width:布局页面之间会对不齐
- 验证:
tools/tests/content-width.test.js(L2m)覆盖 W1 4K wiki==post-container / W3 FHD / W4 2K / W5 单栏封顶 1525px。
4.6 FA 资源版本戳({urlVersion})
效果:发布新版本后浏览器/CDN 拉到新资源(URL 带 ?v=13.13),旧缓存自动失效;本地预览则没有版本后缀,功能不受影响。
对应配置(userConfig/_config.tmp.yml,static_prefix.libs 段):
static_prefix:
libs:
faSolid: /libs/awesome/webfonts/fa-solid-900.woff2{urlVersion}
matery:
mycss:
main: /css/main.css{urlVersion}
highlight:
highlight: /css/highlight.css{urlVersion}机制:资源 URL 带版本戳占位符 {urlVersion},由 tools/cicd.sh 的 sed 在构建时替换为实际版本号(从 tools/.VERSION 读取)。
涉及资源:
| 资源 | 配置键 | 示例 |
|---|---|---|
| FA WebFont | faBrands/faRegular/faSolid | /libs/awesome/webfonts/fa-solid-900.woff2{urlVersion} |
| 主题 CSS | matery/mycss/main | /css/main.css{urlVersion} |
| 高亮 CSS | highlight/highlightDark | /css/highlight.css{urlVersion} |
开发者无需手动操作:版本号由 CI 自动注入。
⚠️ 不要手动修改
themes/matery/_config.yml中的{urlVersion}占位符——该文件是构建产物,CI 时被userConfig/_config.tmp.yml覆盖。
5. 多语言与回落机制(2026-09-08 新增,2026-09-10 修订)
用途:一套内容按语言前缀输出多份页面,缺翻译时按固定链路回落,而不是 404。
学习路径:开哪几种语言 → 回落顺序 → 翻译文件怎么放 → 全参数 → 边界
5.1 如何启用
效果:只有 enabled: true 的语言才生成页面、才出现在语言下拉里;de/eo/es/ja/ru 等默认关闭,访问 /de/... 会回落到 en。
对应配置(主题 _config.yml 的 preference 段):
preference:
default_lang: zh-cn # 默认语言(无前缀路径)
fallback_lang: en # 回落中间层(须在 lang_meta enabled 中)
lang_meta:
zh-cn:
name: 简体中文
flag: 🇨🇳
enabled: true
en:
name: English
flag: 🇬🇧
enabled: true
de:
name: Deutsch
flag: 🇩🇪
enabled: false # 关闭 → 不生成页面、不出现在下拉回落链(三层):
请求语言存在? → 用它
→ 不存在 → fallback_lang 存在? → 回落 fallback_lang
→ 不存在 → default_lang 存在? → 回落 default_lang
→ 不存在 → 任意可用语言5.2 常用选项
① 只做中英双语
效果:构建产物只有 / 与 /en/ 两套,不再生成 de/eo/es/ja/ru 的空壳页面。
对应配置:保持 default_lang: zh-cn + fallback_lang: en + 两者 enabled: true,其余 false。
② 加一门语言
效果:面板下拉多出一个选项,/{lang}/ 与 /{lang}/page/N/ 出现,缺翻译时仍按回落链取内容。
对应配置:把该语言 enabled 改 true,并在 source/{lang}/ 下补翻译文件。
③ 真实翻译映射(post / wiki)
效果:有真实翻译时生成 /{lang}/posts/{abbrlink}/ 真实内容页;无翻译则回落链生成。
对应配置:翻译文件与默认语言同名,放在语言子目录:
| 类型 | 翻译源路径 | front-matter |
|---|---|---|
| 文章 | source/{lang}/_posts/{同名}.md | 保留相同 abbrlink + 新增 lang: en |
| wiki | source/{lang}/docs/{同名}.md | 同上(wiki 页无 abbrlink,按同名路径映射) |
中英关联双模式:有 abbrlink 插件 → 相同 abbrlink 关联;无插件 → 同名文件关联(带文件系统兜底检查)。
注:hexo 原生只认
source/_posts/(en/_posts/会被忽略),文章翻译由主题i18n-post-generator.js生成。
5.3 全部可调项
| 键 | 类型 | 默认 | 可选值/范围 | 作用 |
|---|---|---|---|---|
preference.default_lang | string | zh-cn | 语言 code | 默认语言,路径无前缀 |
preference.fallback_lang | string | en | 语言 code(须 enabled) | 回落中间层 |
preference.lang_meta.<code>.name | string | — | 任意字符串 | 下拉/面板显示名 |
preference.lang_meta.<code>.flag | string | — | emoji | 下拉国旗 |
preference.lang_meta.<code>.enabled | bool | false(示例中 en/zh-cn 为 true) | true / false | 是否生成该语言页 + 是否进下拉 |
5.4 边界与冲突
各页面类型行为(2026-09-10 修订):
| 页面类型 | 多语言版 | 提示条 |
|---|---|---|
| 文章(post) | ✅ 有翻译用翻译,无翻译回落链生成 | ✅ 显示"xx 不存在,显示 yy 语言版本" |
| wiki 页 | ✅ 同上 | ✅ 同上 |
| 聚合页(tag/category/archive) | ✅ 按 enabled 语言生成 | ❌ 只生成不提示 |
| 独立页(about/musics/movies/friends/msg/bb/galleries 等) | ✅ 生成 /{lang}/{page}/(界面文案走 languages/{lang}.yml i18n) | ❌ 无 |
| 首页 | ✅ /en/、/en/page/N/ | ❌ 无 |
独立页(含 gallery 子页与 pages 集合中
layout:'post'的历史页如 dash)由standalone-page-generator.js按排除法生成(layout ∉ {wiki, tags, categories, 404} 即生成,新增独立页自动覆盖,无需登记清单)。
客户端语言策略(方案 A 全站跟随,2026-09-10):
| 场景 | 行为 |
|---|---|
| 首访 | 无本地偏好且浏览器语言匹配已启用语言 → 弹窗询问切换;接受则记忆并跳转,拒绝则记住默认语言(不再重复弹) |
| 面板切换 | 语言下拉选择 → 写入本地偏好 + 跳转到对应语言版 |
| 页面加载 / 刷新 | 有本地偏好 → 直接按偏好加载对应语言版(head.ejs 内联重定向,早于渲染无闪现);爬虫跳过(SEO);无多语言版路径跳过(防 404) |
| 面板回显 | 语言下拉显示本地偏好(localStorage.wiki_lang) |
语言前缀规则(url_for_lang):资源(css/js/libs/medias/images/fonts 等)与静态文件(xml/txt/json 等)不加语言前缀;其余内容路径一律加前缀。豁免规则单一来源 scripts/utils/url-lang-blacklist.js。独立页导航因此带上 /en/ 前缀指向对应语言版。
标题 i18n(page_title_i18n,2026-09-10):<title> 与封面大标题(bg-cover-content.ejs)共用 scripts/helpers/page-title.js 的 page_title_i18n:tags/categories/about 按 title、其余按 layout 映射 i18n key;返回 null 时调用方回退原 title。
2026-09-27 补充(i18n 批):
| 项 | 变更 |
|---|---|
| 404 页 | 改客户端语言检测(单文件),按浏览器语言 / 本地偏好动态选文案;不再按路径静态生成多份(/en/404.html 等作废) |
| archives 聚合页 | 全语言统一分页——不再按语言各自分页导致页数不一致 |
| 加密页语言 | 加密提示语 / 按钮文案随页面语言切换(hbe-bundle 兜底文案走 MATERY_I18N,E7) |
| 运行时文案 | 搜索 / 弹窗 / codeLang 等运行时文案 i18n 化(P0/P1) |
| 内链语言 | 文章内链(url_for_lang)指向对应语言版(E2) |
5.5 小结
效果汇总:访问 /de/docs/setup/(de 未启用)→ 内容用 zh-cn 加提示条;浏览器语言为 en 的访客首访时被询问是否切换到英文版。
- 只开两门语言:
enabled是唯一开关,不用删文件 - 缺翻译不算错:回落链兜底,页面照样 200
- 资源不加前缀:
url-lang-blacklist.js是唯一豁免清单 - 测试:
tools/tests/lang-fallback.test.js(L13,回落链 4 级 + 配置兼容/过滤/校验 13 断言);tools/tests/release-test.shL2x(MATERY_LANG 注入 / 回落页 / 9 个独立页 200);tools/tests/unit-standalone.test.js(L13,22 断言);tools/tests/lang-client-strategy.test.js(L11,7 断言)。
6. Wiki 多语言(docs/api/tutorial 子站,2026-09-10 修订)
用途:Wiki 文档子站(docs/api/tutorial)三栏布局 + 中英双语 + 回落机制,与博客文章共用同一套多语言体系。
学习路径:最小 front-matter → 侧栏与加密 → 全参数 → 边界(样式/宽度陷阱)
6.1 如何启用
效果:写了这两个键的页面渲染成三栏布局(左侧栏 + 正文 + 右 TOC);缺 layout 就退化成普通页面,缺 wiki 就没有侧栏。
对应配置(front-matter 必填):
---
layout: wiki # 必填,否则不渲染三栏布局
wiki: tutorial # 必填,子站名(docs/api/tutorial)
lang: en # 英文页必填(坑 3)
---目录结构:
| 语言 | 路径 |
|---|---|
| 中文(默认) | source/{wiki}/(无前缀) |
| 英文 | source/en/{wiki}/(/en/ 前缀) |
回落机制:无英文版 → /en/... 回落显示中文 + 顶部提示条(“no en version”)+ noindex。
6.2 常用选项
① 配侧栏
效果:左侧出现分组导航(guide 下挂 writing 等子项),条目文案跟随语言切换。
对应配置(source/_data/{wiki}-sidebar.yml):
# source/_data/docs-sidebar.yml
structure:
docs:
- key: guide
children:
- key: writing
title: 写作指南- 条目 key 英文 snake_case,显示文本走
languages/*.yml翻译
② 整个 wiki 加密
效果:该 wiki 全部页面渲染前先要密码;输入一次后同 wiki 其他页面自动解密免重输(派生密钥存 localStorage 的 wiki.<wiki名>,TTL 1 天)。
对应配置:
# source/_data/docs-sidebar.yml 顶层
encrypt_password: "{{WIKI_PASSWORD}}"
encrypt_message: "请输入访问密码"
encrypt_placeholder: "密码"
encrypt_button_text: "进入"
encrypt_前缀元数据键只存在于数据文件顶层(structure分区之外),天然不参与侧栏导航渲染。
共 4 个键:
encrypt_password(必填)+encrypt_message/encrypt_placeholder/encrypt_button_text(三者可选,缺省用内置文案)。
③ 内部链接
效果:绝对路径 + .html 的链接在英文页自动变成 /en/tutorial/...,不会掉回中文页。
对应配置:
[topic](/tutorial/guide/writing.html)④ 段落编号
效果:手写编号的 wiki 页不再出现「1. 1. 概述」。
对应配置(与博客一致):
closeAutoTocNum: true⑤ 新增 wiki 页流程
效果:新页出现在侧栏,中英各一份,9 语言 key 齐全。
对应配置(5 步):
- 写中文
source/{wiki}/xxx.md(front-matterlayout: wiki+wiki: <子站>) - 补英文
source/en/{wiki}/xxx.md - 更新
source/_data/{wiki}-sidebar.yml - 9 语言翻译 key(
languages/*.yml) hexo clean重建
6.3 全部可调项
| 键 | 类型 | 默认 | 可选值/范围 | 作用 |
|---|---|---|---|---|
layout | string | — | wiki | 启用三栏 wiki 布局(必填) |
wiki | string | — | docs / api / tutorial | 子站名,决定侧栏数据文件 |
lang | string | default_lang | 语言 code | 侧栏链接语言前缀(英文页必填) |
closeAutoTocNum | bool | — | true | 关闭 CSS 自动编号(手写编号时用) |
encrypt_password | string | — | 任意字符串 | 侧栏顶层:整个 wiki 加密 |
encrypt_message | string | — | 任意字符串 | 加密提示正文 |
encrypt_placeholder | string | — | 任意字符串 | 密码框 placeholder |
encrypt_button_text | string | — | 任意字符串 | 解密按钮文案 |
6.4 边界与冲突
Wiki 页 tag 组件样式与布局宽度——两个真实踩过的坑:
- 样式生效条件:tag 组件的 CSS 全部 scope 在
.post-container后代选择器下。wiki<main>必须同时带post-container和wiki-main类,组件才能获得样式。如果新增 tag 组件 CSS 时未以.post-container为前缀,该组件在 wiki 页将不可见。 - 布局宽度:wiki
<main>需显式width: 100%(_tags/_base/media.styl中.wiki-main { width: 100% }),否则 flex item 的automargin 会触发 shrink-to-fit,导致宽度随内容多少而不一致(内容少的页变窄)。max-width由.post-container的全局规则控制,各 wiki 页宽度统一。
英文页漏 lang: en → 侧栏链接指向 /docs/ 而非 /en/docs/(详见「已知坑」坑 3)。
6.5 小结
效果汇总:/en/docs/setup/ 无英文版时显示中文内容 + 提示条 + noindex;有英文版则显示英文。
- 两个必填键:
layout: wiki+wiki: <子站> - 英文页三件套:
lang: en别漏 - 整站加密:
encrypt_password一行搞定,encrypt_*不会泄漏进侧栏 - 测试:
release-test.shL2b(en 页面 200 + 回落提示条 + 评论 path 归一化 + 默认语言无前缀);L2y(wiki 加密页解密 + 侧栏无structure/i18n/encrypt_泄漏);readmode-integration.test.js断言<main>含post-container且.wiki-layout宽度跨页一致。
7. 相册功能
用途:
/galleries/列表 + 单相册灯箱浏览,支持加密相册。学习路径:最小相册 → 三种图片写法 → 全参数 → 边界
7.1 如何启用
效果:/galleries/ 出现相册卡片(封面 + 名称 + 描述),点进去是灯箱页(缩略图网格,点击全屏,caption 显示标题/描述)。
对应配置(source/_data/galleries.yml 顶层 + 相册页路由):
galleries:
- name: 2020
cover: /medias_webp/images/01.webp
description: "我的图床"
photos:
- /medias_webp/images/02.webp配套页面(hexo new gallery 2020 生成;缺它则列表卡片点进去 404):
---
title: 2020
layout: gallery
---7.2 常用选项
① 单图(完整信息)
效果:该图在灯箱里显示自己的标题与描述,而不是回退相册描述。
对应配置:
photos:
- url: /medias_webp/images/01.webp # 图片路径(必填)
title: "2020跨年" # 图片标题(可选,空则回退相册 description)
desc: "拍摄于上海外滩" # 图片详细描述(可选,空则不显示)② 单图(简写)
效果:只写路径,灯箱标题回退为相册 description。
对应配置:
photos:
- /medias_webp/images/02.webp③ 整目录自动扫描
效果:目录下所有图片(webp/jpg/jpeg/png/gif/svg/avif)自动入册,标题为「图1、图2…」,文件名自然排序(01 < 02 < 10)。
对应配置:
photos:
- dir: /medias_webp/featureimages/ # 目录路径(基准为 source/,必填)
title_prefix: "图" # 标题前缀(可选)
desc: "featureimages 系列" # 目录统一描述(可选)
sort: asc # asc=文件名升序(默认) / desc=降序④ 加密相册
效果:进相册页先要密码,解密后灯箱才可浏览。
对应配置(相册页 front-matter):
---
title: private
layout: gallery
password: "{{GALLERY_PASSWORD}}"
---7.3 全部可调项
| 键 | 类型 | 默认 | 可选值/范围 | 作用 |
|---|---|---|---|---|
galleries[].name | string | — | 任意字符串(须与 index.md 的 title 一致) | 相册名 |
galleries[].cover | string | — | 图片路径 | 列表页封面 |
galleries[].description | string | — | 任意字符串 | 相册描述(灯箱无标题时回退) |
photos[].url | string | — | 图片路径 | 单图(完整形式) |
photos[].title | string | — | 任意字符串 | 单图标题 |
photos[].desc | string | — | 任意字符串 | 单图描述 |
photos[](字符串) | string | — | 图片路径 | 单图简写形式 |
photos[].dir | string | — | 目录路径(基准 source/) | 目录扫描形式 |
photos[].title_prefix | string | — | 任意字符串 | 目录形式标题前缀 |
photos[].sort | string | asc | asc / desc | 目录形式排序 |
password(页面 front-matter) | string | — | 任意字符串 | 加密相册 |
7.4 边界与冲突
- 三种
photos形式可混用,按数组顺序排列;多目录不跨目录混排(各目录内排序)。 name与source/galleries/<name>/index.md的title必须对应,否则列表卡片点进去 404。- 解密后灯箱走统一
initGallery()(hbe-bundleonSuccess),见 §11.4。
7.5 小结
效果汇总:列表卡片 + 内页 lightGallery 灯箱浏览,加密相册解密后显示。
- 最省事:
dir:一行扫整个目录 - 要 caption:用
url+title+desc三行 - 要私密:页面 front-matter 加
password - 测试:
/galleries/列表、/galleries/2020/灯箱 + caption、/galleries/private/加密解密 UI。
8. 搜索功能
用途:导航栏搜索框 + 弹窗实时结果,三引擎可切换。
学习路径:默认引擎 → 换引擎 / 缩索引 → 全参数 → 边界
8.1 如何启用
效果:导航栏出现搜索输入框(#searchInput),点击或 Ctrl+K(Mac ⌘+K)弹出搜索浮层,输入即出结果(auto 触发)。
对应配置(主题 _config.yml 的 search 块):
search:
default: "algolia" # 默认引擎:local | algolia | pagefind
algolia:
enable: true # 总开关
local:
enable: true
path: /search.json # 本地索引输出路径(主题生成器 local-search.js)
pagefind:
enable: true # 构建后 npx pagefind 扫描 public/ 生成静态索引三引擎:local(本地 search.json)+ algolia(Algolia API)+ pagefind(Pagefind 索引)。
8.2 常用选项
① 全本地、不依赖外部服务
效果:搜索只 fetch /search.json,无外部请求;代价是索引随文章增长(大站首次打开稍慢)。
对应配置:
search:
default: "local"
algolia:
enable: false
pagefind:
enable: false若
default指向的引擎enable: false,会自动降级为第一个已启用的引擎并打警告日志。
② 缩小索引范围
效果:只索引文章标题(content: false),search.json 体积大幅下降,换来只能按标题命中。
对应配置:
search:
local:
enable: true
field: [post] # post | page | wiki | all | 数组组合
content: false # false = 仅匹配标题③ 改成回车才搜
效果:输入过程中不发请求,按回车/点按钮才搜(长索引场景省流量)。
对应配置:
search:
local:
trigger: manual # auto(输入即搜)| manual(回车/按钮)④ Algolia 记录体积
效果:content_length: 2500 时中文每条约 7.5KB,1000 条 ≈ 7.5MB(Algolia 单批推荐 ≤10MB);设成 25000 会超 10KB/条上限,推送直接 400 失败。
对应配置:
search:
algolia:
content_length: 2500 # 正文纯文本截断(中文建议 2000-2500)
excerpt_length: 300 # 摘要截断
batch_size: 1000 # 每批推送条数8.3 全部可调项
| 键 | 类型 | 默认 | 可选值/范围 | 作用 |
|---|---|---|---|---|
search.default | string | algolia | local / algolia / pagefind | 默认引擎(未启用则自动降级) |
search.algolia.enable | bool | true | true / false | Algolia 引擎开关 |
search.algolia.indexName | string | blog | 任意索引名 | Algolia 索引 |
search.algolia.content_length | int | 2500 | 2000-2500(中文建议) | 正文截断字符数 |
search.algolia.excerpt_length | int | 300 | 150-300 | 摘要截断字符数 |
search.algolia.batch_size | int | 1000 | 整批 ≤10MB | 每批推送条数 |
search.local.enable | bool | true | true / false | 本地引擎开关 |
search.local.path | string | /search.json | 输出路径 | 索引文件位置 |
search.local.field | array | [post, wiki] | post / page / wiki / all / 数组组合 | 搜索范围 |
search.local.content | bool | true | true / false | 是否扫正文 |
search.local.format | string | striptags | striptags / raw | 索引正文格式 |
search.local.trigger | string | auto | auto / manual | 触发方式 |
search.local.top_n_per_article | int | 1 | -1 显示全部 | 每篇命中片段数 |
search.pagefind.enable | bool | true | true / false | Pagefind 引擎开关 |
search.pagefind.output_subdir | string | pagefind | 相对 public/ 的子目录(勿用 _ 开头) | 索引输出目录 |
search.local.unescape/preload两个键在配置里存在,但注释明确写着「当前实现未接入此选项,规划中」——改了不生效,其他值未验证。
8.4 边界与冲突
/search.json是主题 generator 的构建产物,手改会被下一次构建覆盖。- 三引擎继承同一基类、共用
_highlight/_escapeHtml;新增引擎不继承它,结果高亮与转义风格就会和另外两个不一致。 #searchInput由导航栏模板提供,与搜索配置无关——即使引擎全关,输入框仍在。- 加密文章通过
post-detail.ejs的data-pagefind-body条件渲染自动排除在 Pagefind 索引外。
交互:
- 弹窗:点击/聚焦导航栏搜索框打开;
Ctrl+K(Mac⌘+K)快捷键打开 - 引擎切换:弹窗内下拉在 local / Algolia / Pagefind 三引擎间切换
- 多语言过滤:按语言前缀过滤 + 回落去重(
applyLangFilter) - 高亮统一:三引擎继承同一基类,共用
_highlight/_escapeHtml(结果高亮一致)
8.5 小结
效果汇总:#searchInput 可见;弹窗可打开;/search.json 可访问。
- 最省心:
local.enable: true+field: [post],零外部依赖 - 要全站搜:
field: [post, wiki],索引变大 - 索引爆了:先
content: false,再考虑trigger: manual - 测试:页面含
#searchInput;/search.json可访问;弹窗可打开。
9. 评论系统
用途:文章/留言/友链/说说页底部评论框。
学习路径:选引擎 → 常用外观项 → 全参数 → 边界(Valine 已废弃)
9.1 如何启用
效果:页面底部出现 Waline 评论框(含 Markdown 编辑、Emoji、点赞),暗色模式自动跟随。
对应配置(主题 _config.yml 的 post.comments 段):
post:
comments:
enable: true
type: waline # utterances|disqus|gitalk|gitment|valine|waline|changyan|livere|remark42|twikoo|cusdis|giscus|discuss单页覆盖(front-matter;comments 是开关、comment 是选工具,写错位置不生效):
---
comments: true # 开关(false = 本篇关评论)
comment: waline # 选工具(字符串)
---9.2 常用选项
① 换头像与语言
效果:评论头像从 Gravatar 风格切换;界面文案跟随 lang。
对应配置:
waline:
serverURL: 'https://waline.17lai.site'
avatar: 'mp' # ''/mp/identicon/monsterid/wavatar/retro/hide
lang: zh-CN② 暗色适配
效果:站点切暗色时评论框同步换肤,不出现白底刺眼区域。
对应配置:
waline:
dark: 'html[data-user-color-scheme="dark"]'③ 限制长度与分页
效果:评论超过 wordLimit 无法提交;列表每页 pageSize 条。
对应配置:
waline:
wordLimit: 1000 # 0 = 不限制
pageSize: 10④ 单篇关评论
效果:该篇底部不渲染评论区(#comments 不出现)。
对应配置:
---
comments: false
---9.3 全部可调项
| 键 | 类型 | 默认 | 可选值/范围 | 作用 |
|---|---|---|---|---|
post.comments.enable | bool | true | true / false | 文章评论总开关 |
post.comments.type | string | waline | 见上方枚举 | 全局评论引擎 |
waline.enable | bool | true | true / false | Waline 客户端开关 |
waline.serverURL | string | — | Waline 服务端 URL | 评论后端地址 |
waline.avatar | string | mp | '' / mp / identicon / monsterid / wavatar / retro / hide | 头像风格 |
waline.lang | string | zh-CN | 语言 code | 界面语言 |
waline.dark | string | html[data-user-color-scheme="dark"] | CSS 选择器 | 暗色适配钩子 |
waline.login | string | enable | enable / force / disable | 登录模式 |
waline.wordLimit | int | 1000 | 0 = 不限 | 评论字数上限 |
waline.pageSize | int | 10 | 任意正整数 | 列表分页条数 |
waline.highlighter | string | hanabi | 代码高亮器名 | 评论内代码高亮 |
waline.highlight.enable | bool | false | true / false | 关闭原始 highlighter |
front-matter comments | bool | — | true / false | 单页开关 |
front-matter comment | string | — | waline 等 | 单页引擎覆盖 |
9.4 边界与冲突
valine.enable: false是停用态,valine.*段仅作历史残留;实际引擎是 Waline。- front-matter 未写
comment按全局post.comments.type;comments是开关、comment是选工具,两个键不要混用(post-filter.js按此约定处理默认值)。 - 评论区依赖
#comments容器,页面没注入容器时脚本不初始化——表现为空白而非报错。
已废弃:Valine(
valine.enable: false)已停用,实际评论引擎为 Waline。
9.5 小结
效果汇总:Waline 评论框(Markdown/Emoji/点赞/后台管理)。
- 最小可用:
post.comments.enable+waline.serverURL两项 - 单篇关掉:front-matter
comments: false - 换引擎:改
post.comments.type(其余引擎参数未在本文件展开,其他值未验证) - 测试:文章/msg/friends/bb 页含
#comments;waline 初始化脚本存在。
10. 暗色模式
用途:全站明暗切换,跟随系统或本地时间。
学习路径:开关 + 默认值 → auto 的判定顺序 → 全参数 → 边界
10.1 如何启用
效果:导航栏出现明暗切换按钮;auto 下系统是深色就用深色,系统无偏好时按本地时间 18:00–6:00 进暗色;切换按钮/悬浮面板手动选择后覆盖默认。
对应配置(主题 _config.yml):
dark_mode:
enable: true
default: auto # auto | light | dark10.2 常用选项
① 固定浅色(不跟随系统)
效果:无论系统/时间如何,首访都是浅色;读者仍可手动切暗。
对应配置:
dark_mode:
default: light② 固定深色
效果:首访即暗色(适合夜间向的内容站)。
对应配置:
dark_mode:
default: dark10.3 全部可调项
| 键 | 类型 | 默认 | 可选值/范围 | 作用 |
|---|---|---|---|---|
dark_mode.enable | bool | true | true / false | 暗色模式总开关 |
dark_mode.default | string | auto | auto / light / dark | 初始模式 |
10.4 边界与冲突
auto的判定顺序固定:先系统prefers-color-scheme,系统无明确偏好时才用本地时间 18:00–6:00。- 手动切换(按钮或悬浮面板)写入本地偏好并覆盖
default;清掉本地存储后回落default。 - 属性驱动:
[data-user-color-scheme="dark"]换一整套 CSS 变量,硬编码颜色的组件不会跟随(见docs/dark-mode-principles.md)。 - 关掉
enable时按钮消失,但历史本地偏好仍存在——读者本地存的暗色记录需要清 localStorage 才回浅色。
10.5 小结
效果汇总:[data-user-color-scheme="dark"] 属性驱动全套 CSS 变量切换,图片/图表自适应。
- 默认就好:
enable: true+default: auto - 改默认:改
default一个值 - 新组件不跟随:用
var(--*),别硬编码颜色(见 §15) - 测试:CSS 含
[data-user-color-scheme="dark"]规则;切换按钮可切换明暗。
11. 加密文章
用途:按标签批量加密文章,读者输密码解密;也可整篇/整 wiki 加密。
学习路径:标签加密 → 文案与密钥缓存 → 全参数 → 边界 + 解密后刷新
11.1 如何启用
效果:打了加密标签的文章,页面上只有解密 UI(#hbePass + 密码框),密码正确后正文才渲染。
对应配置(根 _config.yml 的 encrypt 段):
encrypt:
enable: true
abstract: 有东西被加密了, 请输入密码查看.
message: 您好, 这里需要密码.
tags:
- {name: 私人, password: "{{TAG_PASSWORD}}" }
wrong_pass_message: 抱歉, 这个密码看着不太对, 请再试试.常用选项:
① 按标签加密
效果:所有 tags: [私人] 的文章自动加密,无需逐篇配置;去掉标签后需重新构建才恢复明文。
对应配置:
encrypt:
tags:
- {name: 私人, password: "{{PWD_1}}"}
- {name: mywork, password: "{{PWD_2}}"}② 单篇密码
效果:该篇用自己写的密码,不受标签规则影响;password: "" 可让某篇在标签下也不加密。
对应配置(front-matter):
---
tags:
- 私人
password: "{{POST_PASSWORD}}"
default_abstract: 本文已加密 # 加密提示文案(可选)③ 解密后免重输
效果:刷新页面跳过密码框(AES 密钥缓存在 localStorage 的 hbe.v4.<post-permalink-hash>)。
对应配置:
encrypt:
autoSave: true # 全局开启;也可在单篇 front-matter 写 autoSave: true④ 稳定 salt(静态部署平台)
效果:每次干净构建 salt 不变,读者本地缓存不会因重新部署而失效;AES-GCM nonce 仍每次随机(避免 nonce reuse)。
对应配置:
encrypt:
stableSalt: true
kdf:
iterations: 600000 # 默认 250000;下限 100000,推荐 ≥600000(OWASP 2023)11.2 全部可调项
| 键 | 类型 | 默认 | 可选值/范围 | 作用 |
|---|---|---|---|---|
encrypt.enable | bool | true | true / false | 加密功能总开关 |
encrypt.abstract | string | 有东西被加密了, 请输入密码查看. | 任意字符串 | 摘要区提示 |
encrypt.message | string | 您好, 这里需要密码. | 任意字符串 | 解密框提示 |
encrypt.tags | array | — | {name, password} 对象数组 | 命中这些标签的文章自动加密 |
encrypt.wrong_pass_message | string | 抱歉, 这个密码看着不太对, 请再试试. | 任意字符串 | 密码错误提示 |
encrypt.autoSave | bool | true(本站配置值) | true / false | 派生密钥缓存到 localStorage |
encrypt.stableSalt | bool | false | true / false | 跨 clean build 稳定 PBKDF2 salt |
encrypt.kdf.iterations | int | 250000 | ≥100000,推荐 ≥600000 | PBKDF2 迭代次数 |
front-matter password | string | — | 任意字符串("" = 不加密) | 单篇密码 |
front-matter autoSave | bool | — | true / false | 单篇覆盖密钥缓存 |
11.3 边界与冲突
- 按标签匹配生效,不是逐篇开关:加标签即加密,去掉标签需重新构建才恢复明文。
- 加密态不显示 TOC 是设计行为(见「已知坑」坑 2),测试别写死「TOC 必须存在」。
- 解密后必须走
hbe-bundle onSuccess统一 refresh,否则正文里的代码块/灯箱/Mermaid 不会初始化。 kdf.iterations低于推荐值构建会打印 warning(不是错误,构建仍成功)。
11.4 解密后统一刷新(2026-09-09 新增)
效果:解密瞬间,正文里的代码块工具栏、图片灯箱、Mermaid 图、瀑布流布局一次性全部就位(解密前 DOM 里根本没有这些元素)。
对应机制(无配置项,hbe-bundle onSuccess 统一触发):
| 组件 | 解密后动作 |
|---|---|
| 代码块(code-area) | codeWidget() 扫描新 pre 生成 toolbar(语言标签/复制/收起/全屏) |
| 图片灯箱 | articleInit() 包装 img → .img-item + lightGallery 绑定(post/wiki 共用) |
| Mermaid 图表 | events.refresh() 触发 refresh 回调重渲染(data-processed 幂等) |
| 布局(AOS/Masonry) | boot.refresh() → refreshLayout 重排 |
统一触发点:hbe-bundle onSuccess(所有加密场景:文章/相册/wiki/片段共用)。各监听器只保留特有处理:
- post:tocbot reinit(文章目录)
- wiki:TOC 加密态移除 + tocbot reinit
- 相册:initGallery()
幂等保护:codeWidget data-code-processed、mermaid data-processed、articleInit destroy 旧实例重建——重复调用不产生重复元素。
11.5 小结
效果汇总:文章页显示解密 UI(#hbePass),输入正确密码后渲染正文。
- 最小可用:
enable: true+tags: [{name, password}] - 别用明文密码:配置里的密码应走占位符/私密配置,不进公开仓库
- 读者嫌烦:
autoSave: true - Vercel/CF Pages:
stableSalt: true,否则每次部署缓存全失效 - 测试:tag 加密文章含
#hbePass;解密后.code-toolbar数量 =.code-area数量;图片包装.img-item+ 容器lg-uid属性存在。
12. RSS / 站点地图
用途:输出订阅源(RSS/Atom/JSON Feed)与搜索引擎抓取用的 sitemap。
学习路径:最小配置 → 关掉某格式 → 全参数 → 边界
12.1 如何启用
效果:站点根目录出现 /rss.xml、/atom.xml、/feed.json(订阅源)与 /sitemap.xml、/baidusitemap.xml(抓取用)。
对应配置(根 _config.yml):
feed:
limit: 10
order_by: "-date"
tag_dir: false
category_dir: "categories"
rss:
enable: true
output: "rss.xml"
atom:
enable: true
output: "atom.xml"
jsonFeed:
enable: true
output: "feed.json"
sitemap:
path: sitemap.xml
baidusitemap:
path: baidusitemap.xml12.2 常用选项
① 只要 Atom
效果:/rss.xml 与 /feed.json 不再生成,只剩 /atom.xml(现代阅读器都吃 Atom)。
对应配置:
feed:
rss:
enable: false
atom:
enable: true
jsonFeed:
enable: false② 只导最近 20 篇
效果:feed 里最多 20 条,旧文章不进订阅源(新读者不会被历史洪水淹没)。
对应配置:
feed:
limit: 20
order_by: "-date"③ 换输出路径
效果:订阅源改到自定义文件名;改完必须 clean 构建,否则旧的 atom.xml / sitemap.xml 还留在 public/。
对应配置:
sitemap:
path: sitemap-custom.xml12.3 全部可调项
| 键 | 类型 | 默认 | 可选值/范围 | 作用 |
|---|---|---|---|---|
feed.limit | int | 10 | 任意正整数 | 每条 feed 收录文章数 |
feed.order_by | string | -date | 任意字段(前缀 - 倒序) | 排序 |
feed.tag_dir | bool | false | true / false | 是否输出标签 feed |
feed.category_dir | string | categories | 目录名 | 分类 feed 目录 |
feed.rss.enable | bool | true | true / false | RSS 输出开关 |
feed.rss.output | string | rss.xml | 输出文件名 | RSS 路径 |
feed.atom.enable | bool | true | true / false | Atom 输出开关 |
feed.atom.output | string | atom.xml | 输出文件名 | Atom 路径 |
feed.jsonFeed.enable | bool | true | true / false | JSON Feed 输出开关 |
feed.jsonFeed.output | string | feed.json | 输出文件名 | JSON Feed 路径 |
sitemap.path | string | sitemap.xml | 输出文件名 | 通用站点地图 |
baidusitemap.path | string | baidusitemap.xml | 输出文件名 | 百度站点地图 |
12.4 边界与冲突
feed.*.output只管各自那一份;三份互相独立,关一个不影响另两个。- 百度站点地图是另一个插件的产物(
baidusitemap),与sitemap.path没有关联。 - 改
path/output后要 clean 构建,否则旧文件仍留在public/。 - 主题侧 RSS 模板是
themes/matery/layout/_partial/rss.ejs(自定义模板),改样式在模板不在配置。
12.5 小结
效果汇总:/rss.xml、/atom.xml、/feed.json、/sitemap.xml 均可访问。
- 默认就好:三种订阅格式都开着,兼容性最好
- 减产物:
rss/jsonFeed想关就关 - 改完不生效:clean(坑 5)
- 测试:
/rss.xml、/atom.xml、/feed.json、/sitemap.xml均可访问。
13. PWA
用途:可安装 + 离线缓存 + 版本更新自动刷新。
学习路径:总开关 → 缓存清单 → 全参数 → 边界(localhost 豁免)
13.1 如何启用
效果:Chrome 地址栏出现安装按钮,安装后独立窗口运行;断网时已缓存页面仍可打开;站点发新版后首次访问自动刷新一次。
对应配置(主题 _config.yml):
pwa:
enable: true13.2 常用选项
① 改缓存清单
效果:列表里的资源在首次访问时预缓存(/css/main.css、js/boot.js 等),断网可用;不在列表里的按需缓存。
对应配置(userConfig/sw.tmp.js):
var cacheList = [
'/css/main.css{urlVersion}',
'/css/highlight.css{urlVersion}',
'{cdnUrl}/js/boot.js{urlVersion}',
'{cdnUrl}/js/events.js{urlVersion}'
];② 缓存版本号
效果:{uniqueIdentifier} 被 CI 替换为构建时间戳(17lai-cache-{时间戳}),键一变即视为新版本,触发一次自动刷新并清掉旧 17lai-cache-*。
对应配置:
const cacheStorageKeyPrefix = '17lai-cache-';
const cacheStorageKey = `17lai-cache-{uniqueIdentifier}`;13.3 全部可调项
| 键 | 类型 | 默认 | 可选值/范围 | 作用 |
|---|---|---|---|---|
pwa.enable | bool | true | true / false | PWA 总开关 |
userConfig/sw.tmp.js | 模板 | — | CI 注入 {uniqueIdentifier} 后生成 source/sw.js | Service Worker 模板 |
cacheList | array | 见模板 | 资源路径数组 | 预缓存清单 |
cacheStorageKey | string | 17lai-cache-{uniqueIdentifier} | 前缀 + 标识 | 缓存键(变更即新版本) |
pwa段内的 manifest(名称/图标/主题色)由构建注入,具体键名未在本文件展开,其他值未验证。
13.4 边界与冲突
source/sw.js是构建产物,手改会被 CI 覆盖;要改逻辑得改sw.tmp.js。- localhost 豁免 SW 注册(测试用),验证离线能力需用域名(如测试域)。
- 缓存键一变即视为新版本,触发一次自动刷新;
skipWaiting+clients.claim()让新 SW 立即接管,并清掉旧17lai-cache-*。 - 改了
cacheList但版本号没变(非 CI 构建),浏览器不会感知更新。
13.5 小结
效果汇总:可安装(manifest)、离线缓存静态资源、版本更新自动刷新。
SW 版本对比自动刷新:注册 SW 时监听 updatefound(新 SW installed)与 controllerchange,对比新旧 cacheStorageKey:变化则自动刷新一次(不弹窗,用户无需手动刷新)。
- 默认就好:
pwa.enable: true,其余交给 CI - 加预缓存:往
cacheList追加路径,记得带{urlVersion} - 本地测不到:localhost 豁免,用测试域
- 测试:
/sw.js可访问,SW 注册正常(localhost 豁免)。
14. SEO 结构化数据(JSON-LD + Open Graph)
用途:为搜索引擎与社交平台提供结构化元数据:JSON-LD 富结果 + Open Graph / Twitter Card 分享预览。
学习路径:它自动输出什么 → 实体与 @id → 全参数 → 边界(无开关)
14.1 如何启用
效果:每个页面 <head> 里有一份 <script type="application/ld+json">(@graph 单 script:WebSite + Person + Organization,文章页再追加 BlogPosting),另有 og:type/title/url/site_name/description/image 与 twitter:card=summary_large_image。分享到社交平台显示标题/描述/封面图卡片。
对应配置:无独立开关——随 head.ejs 无条件输出。
layout/_partial/common/structured-data.ejs # JSON-LD(@graph)
layout/_partial/common/head.ejs # OG / Twitter Card14.2 常用选项
① 站点搜索框富结果
效果:Google 结果里可能出现站内搜索框(Sitelinks Searchbox),提交后直接跳站内搜索。
对应配置:由 WebSite 实体里的 SearchAction 携带,指向站内搜索 URL(模板内已固定,无独立键)。
② 作者/发布者关联
效果:BlogPosting.author 指 #person、publisher 指 #organization,富结果校验不会报"实体缺失"。
对应配置:@id 互相引用(改 @id 必须同步改引用方)。
14.3 全部可调项
| 键 | 类型 | 默认 | 可选值/范围 | 作用 |
|---|---|---|---|---|
| 开关 | — | — | 无(随 head.ejs 无条件输出) | 无独立配置项 |
@graph 实体 | — | — | WebSite(含 SearchAction)、Person、Organization;文章页追加 BlogPosting | 结构化数据 |
| 描述截断 | — | 210 字符 | — | strip_html 后截断长度 |
14.4 边界与冲突
- 没有开关:要关只能改
structured-data.ejs/head.ejs模板。 @id互相引用(publisher→#organization、author→#person),改@id需同步改引用方,否则富结果校验失败。- JSON-LD 与 OG/Twitter 是两套独立输出,缺一套不影响另一套。
- 描述经
strip_html后按 210 字符截断——想改截断长度得改模板,不是配置。
14.5 小结
效果汇总:页面含 application/ld+json;文章页含 BlogPosting;含 og:type / twitter:card 标记。
- 零配置:默认就有,不用开
- 要改内容:改
structured-data.ejs,注意@id双向引用 - 分享没图:查文章
img/ 封面图,不是 JSON-LD 的问题 - 测试:页面含
application/ld+json;文章页含BlogPosting;含og:type/twitter:card标记。
15. 设计令牌与 CSS 变量架构
用途:统一设计令牌(圆角/阴影/过渡/间距/字号),经三层传递链落到 CSS 自定义属性供全站引用,避免硬编码。
学习路径:令牌在哪定义 → 改一处全站生效 → 全参数 → 边界(三层链)
15.1 如何启用
效果:卡片圆角统一 8px、按钮 3px、弹窗 12px;卡片默认 shadow_md、hover shadow_lg;间距按 4px 递增;正文字号随偏好面板的 --font-scale 等比缩放。
对应配置(主题 _config.yml 的 design 段):
design:
radius_sm: 3px # 按钮/小标签
radius_md: 8px # 卡片/便签
radius_lg: 12px # 弹窗
shadow_md: "0 2px 5px 0 rgba(0,0,0,0.16)"
shadow_lg: "0 5px 11px 0 rgba(0,0,0,0.18)"
transition_fast: 0.2s
space_1: 4px # 4px 基准递增
font_size_md: 1.125rem # rem 制,随 --font-scale 缩放三层传递链:
_config.yml design.* → theme-config() → Stylus 变量 → :root CSS 自定义属性 → 组件 var(--*)- 方案层:
source/css/_schemes/matery.styl(对齐 NexT_schemes/机制,未来可新增方案) - 字号:根字号
calc(var(--font-size-base) * var(--font-scale)),偏好面板调--font-scale全站 rem 等比缩放
15.2 常用选项
① 改圆角风格
效果:把三档圆角一起调小 → 全站卡片/按钮/弹窗同步变"方正";只改 radius_md 会导致按钮与卡片风格不一致。
对应配置:
design:
radius_sm: 2px
radius_md: 4px
radius_lg: 6px② 调暗色阴影
效果:暗色模式下阴影不显脏(浅色阴影在深底上会发灰),卡片浮起感保留。
对应配置:
design:
shadow_md: "0 2px 5px 0 rgba(0,0,0,0.16)"
shadow_md_dark: "0 2px 5px 0 rgba(0,0,0,0.45)"15.3 全部可调项
| 键 | 类型 | 默认 | 可选值/范围 | 作用 |
|---|---|---|---|---|
design.radius_sm | length | 3px | 任意 CSS 长度 | 按钮/小标签圆角 |
design.radius_md | length | 8px | 任意 CSS 长度 | 卡片/便签圆角 |
design.radius_lg | length | 12px | 任意 CSS 长度 | 弹窗圆角 |
design.radius_pill | length | 999px | 任意 CSS 长度 | 胶囊圆角 |
design.radius_full | length | 50% | 任意 CSS 长度 | 圆形 |
design.shadow_sm | string | "0 1px 3px 0 rgba(0,0,0,0.12)" | 合法 box-shadow | 悬浮微阴影 |
design.shadow_md | string | "0 2px 5px 0 rgba(0,0,0,0.16)" | 合法 box-shadow | 卡片默认 |
design.shadow_lg | string | "0 5px 11px 0 rgba(0,0,0,0.18)" | 合法 box-shadow | 卡片 hover |
design.shadow_xl | string | "0 10px 30px 0 rgba(0,0,0,0.2)" | 合法 box-shadow | 弹窗/浮层 |
design.shadow_*_dark | string | 0 1px 3px …0.4 等 | 合法 box-shadow | 暗色模式对应阴影 |
design.transition_fast | time | 0.2s | 任意 CSS 时间 | 悬停反馈 |
design.transition_normal | time | 0.3s | 任意 CSS 时间 | 面板/弹窗 |
design.gradient_primary | string | linear-gradient(to right, #4facfe, #00f2fe) | 合法 linear-gradient | 导航/按钮渐变 |
design.gradient_accent | string | linear-gradient(135deg, #667eea, #764ba2) | 合法 linear-gradient | 卡片装饰渐变 |
design.space_1 | length | 4px | 任意 CSS 长度 | 间距基准 |
design.space_2 | length | 8px | 任意 CSS 长度 | 间距刻度 |
design.space_3 | length | 12px | 任意 CSS 长度 | 间距刻度 |
design.space_4 | length | 16px | 任意 CSS 长度 | 间距刻度 |
design.space_5 | length | 24px | 任意 CSS 长度 | 间距刻度 |
design.font_size_xs | length | 0.75rem | rem 值 | 字号刻度 |
design.font_size_sm | length | 0.875rem | rem 值 | 字号刻度 |
design.font_size_md | length | 1.125rem | rem 值 | 正文字号(随 --font-scale) |
design.font_size_lg | length | 1.25rem | rem 值 | 字号刻度 |
design.font_size_xl | length | 1.5rem | rem 值 | 字号刻度 |
design.font_size_2xl | length | 2rem | rem 值 | 字号刻度 |
15.4 边界与冲突
- 令牌经三层链落到
:root自定义属性,组件必须用var(--*)才跟随;硬编码值不会变。 font_size_md是 rem:改它等于改全站字号基准,偏好面板的--font-scale会在其上再乘一次。- 映射在方案层
source/css/_schemes/matery.styl;改令牌名要同步改 scheme,否则变量悬空、组件拿不到值。 - 暗色阴影必须用
*_dark那一套;直接复用亮色阴影在深底上几乎看不见。
15.5 小结
效果汇总:组件统一引用令牌(圆角/阴影/间距一致),改 design 段一处即全站生效。
- 改圆角/阴影/间距:改
design段,别去组件里改 - 暗色要单独给值:
*_dark不能省 - 新组件:
var(--*),硬编码就不跟随主题 - 测试:CSS 含
--font-scale规则与根字号calc(... * var(--font-scale))。
⚠️ 已知坑
坑 1:标题编号双重叠加
现象:标题渲染为「1. 1. 概述」(两个编号)。
原因:主题 CSS 自动给 h2–h6 加编号(默认开启),手写编号(如 ## 1. 概述)会叠加。
正确做法:手写编号的页面在 front-matter 加 closeAutoTocNum: true;不手写编号的页面不要加该字段。
本文已正确设置
closeAutoTocNum: true(front-matter 第 19 行)。
坑 2:加密 ↔ TOC 冲突
现象:wiki 整体加密时 TOC 按设计不显示。
原因:加密模式下 TOC 组件被跳过(设计如此)。
正确做法:相关测试断言勿写死「TOC 必须存在」。加密 wiki 页面不显示 TOC 是正常行为,不是 bug。
坑 3:Wiki 英文页漏 lang: en
现象:侧栏链接指向 /docs/ 而非 /en/docs/。
原因:wiki_sidebar 使用 page.lang || default_lang,省略 lang: en 则回落到默认语言路径。
正确做法:所有 source/en/tutorial/ 下的页面必须写 lang: en。验证:for f in source/en/tutorial/**/*.md; do grep -L 'lang: en' "$f"; done。
坑 4:_config.tmp.yml 注释行保留
现象:尝试删除注释行瘦身,被用户否决并 100% 回滚。
原因:用户明确声明注释行是有用储备(未来可能恢复)。
正确做法:永远不要删除或修改 userConfig/_config.tmp.yml 中的注释行。
坑 5:改配置后必须 clean 构建
现象:改了配置但页面不变。
原因:Hexo 缓存机制不会自动检测配置变更。
正确做法:改配置或 source/_data/* 后必须 npm run clean && npm run build。构建日志中出现 INFO Deleted database. 和 INFO Deleted public folder. 表示 clean 成功执行。
坑 6:source/_data/* 增量缓存误判
现象:修改 source/_data/friends.yml 等数据文件后,页面空白或数据不更新。
原因:Hexo 增量缓存误判「已缓存未变化」(Cache 哈希更新但 Data model 未写入)。
正确做法:
- 本地:
rm -f db.json && npm run build - 容器:
tools/cicd.sh -d(内置 clean + generate + 配置注入) - 容器内
pm2 restart不够:pm2 内存缓存 db.json 旧 content
附:布局页面功能速查表
| 功能 | 页面/位置 | 关键配置 | 自动化断言标记 |
|---|---|---|---|
| 上下篇 | 文章底部 | 无键(post.prev_next.enable 未被读取) | #prenext-posts |
| 版权声明 | 文章底部 | post.copyright.enable / license | #reprint-statement |
| 时效提示 | 文章开头 | post.outdate | outdate |
| TOC | 右侧/侧栏 | toc(顶层) | #toc-content |
| 广告位 | TOC 下方 | post.advertisements | .tgCard |
| 分类雷达图 | /categories/ | 无键(categories.ejs) | canvas(Matery.echarts 明暗切换) |
| 标签云/词云 | /tags/ | wordcloud.minHeight / heightPerWord | .tag-chips / #tag-wordcloud |
| ArtiTalk 说说 | /about/ | artitalk.enable: false(默认禁用) | LeanCloud 后端 |
| 我的项目 | /about/ | myProjects.enable: true | .my-projects |
| 我的技能 | /about/ | mySkills.enable: true | .my-skills |
| 关于页相册 | /about/ | myGallery.enable: true | .my-gallery |
| 归档日历 | /archives/ | postCalendar: true | #post-calendar |
| 在线音乐 | /musics/ | musics + failover | .aplayer(tools/tests/musics-integration.test.js L2i) |
| 归档时间线 | /archives/ | 无键(archive.ejs) | #cd-timeline |
| 关于页图表 | /about/ | 无键(about.ejs) | canvas |
| 相册 | /galleries/ | galleries.yml | 灯箱 + 加密 UI |
| 搜索 | 导航栏 | search | #searchInput / search.json |
| 评论 | 文章/msg 等 | post.comments | #comments |
| 暗色模式 | 全局 | dark_mode | data-user-color-scheme |
| 加密 | 标签驱动 | encrypt.tags | #hbePass |
| RSS/Sitemap | 根路径 | feed / sitemap | /rss.xml 等 |
| PWA | 全局 | pwa + sw.tmp.js | /sw.js |
| SEO | <head> | structured-data.ejs + head.ejs | application/ld+json / og:type |
| 设计令牌 | 全局 | design 段 + _schemes/ | --font-scale |
参考&致谢
系列教程
Hexo 主题功能测试系列
- Hexo主题功能测试-布局与页面篇 —— 页面布局、导航、侧边栏、卡片等结构功能
- Hexo主题功能测试-交互视觉篇 —— 代码高亮、图片灯箱、动画、明暗切换等交互
- Hexo主题功能测试-内容tag篇 —— note / tabs / timeline / label / button 等 tag 插件
- Hexo主题功能测试-Markdown语法篇 —— Markdown 渲染与扩展语法
- Hexo主题功能测试-自动化测试篇 —— 主题功能的自动化测试方案

