布局与页面
本页梳理 matery 主题的布局与页面相关功能:文章布局特性、侧边栏组件、响应式断点、相册、搜索、评论、暗色模式、加密、RSS/站点地图、PWA。每个功能给出配置位置与使用方法。
配置分两处:主题配置
themes/matery/_config.yml(布局/外观/交互)与站点配置_config.yml(插件/生成器)。下面逐一标注。注意:本项目 CI 构建时主题配置文件会被userConfig/_config.tmp.yml模板覆盖,修改请同步模板(见「安装与主题配置」篇)。怎么读这页:每个功能都按「效果 → 对应配置」成对展示——先看页面真实表现,再抄下面的 YAML/Stylus 配置。默认值取自
userConfig/_config.tmp.yml(与themes/matery/_config.yml同源),未在其中出现的取值集合标注「其他值未验证」。
布局核心与组件
1. 文章布局特性
用途:文章页外围的可选模块——上下篇、版权、时效提示、目录、广告位,决定「读完一篇之后往哪走」以及「这篇还能不能放心引用」。
学习路径:如何启用 → 常用选项(单篇覆盖)→ 全部可调项 → 边界与冲突
1.1 如何启用
上下篇导航
效果:文章末尾左右两张卡片,显示相邻文章标题与缩略图,hover 有渐变背景。
对应配置(主题配置,位于 post 块内):
post:
prev_next:
enable: true版权声明
效果:文章底部灰色版权卡片,含作者、链接、许可协议三行。
对应配置(主题配置,位于 post 块内):
post:
copyright:
enable: true # 显示版权声明
license: 'cc_by_nc_sa' # 默认转载规则(CC BY-NC-SA 4.0)文章时效提示
效果:文章开头显示过期提示 note,天数越长样式越醒目(先警告色,再错误色)。
对应配置(主题配置,位于 post 块内;默认 enable: false):
post:
outdate:
enable: true # 设为 true 开启
warning_day: 365 # 超过 365 天显示警告样式 note
error_day: 3650 # 超过 3650 天显示错误样式 noteTOC 目录
效果:桌面端侧边栏目录树随滚动高亮;移动端折叠收起。
对应配置(主题配置,顶层 toc 段):
toc:
enable: true # 全局开关
headingSelector: "h2,h3,h4,h5,h6" # 参与目录的标题级别
collapseDepth: 0 # 折叠深度,0 全部折叠广告位
效果:侧边栏 TOC 下方展示彩色广告卡片,支持明暗两套背景色。
对应配置(主题配置,位于 post 块内,数组可增删):
post:
advertisements:
- id: "ad-1"
text: "自用超便宜的 VPS 服务商推荐"
link: "https://example.com/your-aff-link"
image: "/medias_webp/cover/hexo.webp" # 可选:卡片图片
background: "#6670ff"
background_dark: "#3a4399"
colspan: 1 # 卡片占列宽
enable: true # false 则隐藏1.2 常用选项
效果:单篇文章不显示目录,其它文章不受影响。
对应配置(front-matter):
toc: false效果:单篇文章改用自己的转载许可,站点默认值对它失效。
对应配置(front-matter):
reprintPolicy: cc_by_nc # 本篇改为"署名-非商业性使用"可用规则(共 9 种):cc_by、cc_by_nd、cc_by_sa、cc_by_nc、cc_by_nc_nd、cc_by_nc_sa、cc0、noreprint、pay。
效果:临时撤下某张或全部广告卡片,无需删配置。
对应配置:单条撤下把该条 enable 改 false;全站撤下把 advertisements 整段注释。
1.3 全部可调项
| 键 | 类型 | 默认 | 可选值/范围 | 作用 |
|---|---|---|---|---|
post.prev_next.enable | bool | true | true / false | 文末上下篇卡片 |
post.copyright.enable | bool | true | true / false | 文末版权卡片 |
post.copyright.license | string | cc_by_nc_sa | 上述 9 种之一 | 默认转载规则 |
post.outdate.enable | bool | false | true / false | 时效提示总开关 |
post.outdate.warning_day | int | 365 | 天数 | 超过即用警告样式 |
post.outdate.error_day | int | 3650 | 天数 | 超过即用错误样式 |
toc.enable | bool | true | true / false | 全局目录开关 |
toc.headingSelector | string | "h2,h3,h4,h5,h6" | CSS 选择器 | 参与目录的标题级别 |
toc.collapseDepth | int | 0 | 0 = 全折叠;>0 = 有下级时展开 | 目录折叠深度 |
post.advertisements[].id / text / link | string | — | — | 卡片标识、文案、跳转 |
post.advertisements[].image | string | — | 站内路径 | 卡片配图(可选) |
post.advertisements[].background / background_dark | string | — | 颜色值 | 明/暗两套底色 |
post.advertisements[].colspan | int | 1 | 列宽数 | 卡片横向占比 |
post.advertisements[].enable | bool | true | true / false | 单条启停 |
page.toc | bool | 继承全局 | true / false | 单篇目录覆盖 |
page.reprintPolicy | string | 继承全局 | 上述 9 种之一 | 单篇许可覆盖 |
1.4 边界与冲突
- 时效提示默认关闭:
outdate.enable: false,不显式打开就不会出现提示,容易被误判为功能失效。 toc: false是字符串比较:模板按theme.toc.enable && page.toc !== 'false'判断,写布尔以外的形式(如"False")不生效。- 广告位与 TOC 共用同一块侧边栏区域:广告条数多、目录层级深时侧边栏明显变长;移动端侧边栏整体隐藏,两者都看不到。
- 版权卡与「复制追加版权」不是同一功能:
post.copyright管文末卡片,post.copy.copyright管复制正文时追加的文字,改错块不生效。
1.5 小结
- 五个模块都是主题配置里的一行开关,除时效提示外默认全开。
- 单篇粒度只有两个覆盖点:
toc、reprintPolicy。 - 改主题配置后必须
hexo clean && hexo generate;CI 场景还要同步userConfig/_config.tmp.yml。
2. 页面级组件(widgets)
用途:分类/标签/归档/关于四个聚合页的可视化组件,全部由主题生成器自动渲染,随内容更新。
学习路径:如何启用(零配置)→ 全部可调项(无)→ 边界与冲突
2.1 如何启用
效果:下面这些页面在对应内容存在时自动出现图表 / 词云 / 时间线:
| 页面 | 组件 | 说明 |
|---|---|---|
/categories/ | 分类雷达图 | canvas 雷达图展示各分类文章数量分布 |
/tags/ | 标签云 + 词云 | chip 样式标签列表;canvas 词云字号 ∝ 文章数 |
/archives/ | 归档时间线 | 按年/月垂直时间线,圆点 + 年份折叠 |
/about/ | 站点统计图表 | 多组 canvas 图表(文章数/分类/标签/时间线等) |
/about/ | 站点统计数字 | 文章数/分类数/标签数(_partial/post/post-statis,自动渲染) |
对应配置:无(页面本身需在 source/ 下建页,layout 取 categories / tags / archives / about)。
2.2 常用选项
效果:图表配色随暗色模式切换,不需要单独设置。
对应配置:由 CSS 变量驱动(见 §7 暗色模式),未提供逐图表的配置项。
2.3 全部可调项
本组功能没有用户可调配置键——数据全部来自 Hexo 的 categories / tags / posts 模型,外观由 themes/matery/source/css/ 控制。
2.4 边界与冲突
- 页面不存在时不会自动创建:
/categories/等需要source/categories/index.md等文件存在。 - 图表依赖 canvas 脚本;脚本被 CDN 拦截或懒加载失败时退化为空白区域,不报错。
- 词云/雷达图的信息量来自文章数,文章很少(<5 篇)时图表几乎看不出差异。
2.5 小结
- 零配置、零维护,代价是外观不可调。
- 要改外观只能改 CSS/模板,不能靠配置。
3. 响应式断点
用途:定义手机 / 平板 / 桌面 / 大屏四档布局切换点,以及各档内容列的宽度上限。
学习路径:如何启用(变量)→ 常用选项(内容宽度)→ 全部可调项 → 边界与冲突
3.1 如何启用
效果:窗口缩放时,导航折叠、侧边栏收放、多栏切单栏按下列阈值发生。
对应配置(themes/matery/source/css/_variables/base.styl,Stylus 变量):
$breakpoint-sm = 368px // 超小屏(手机竖屏)
$breakpoint-md = 768px // 平板/手机横屏
$breakpoint-lg = 992px // 桌面
$breakpoint-xl = 1200px // 大屏
$breakpoint-fun = 1400px // 特效启用阈值(大屏)各断点布局表现:
| 断点 | 布局表现 |
|---|---|
< 768px | 导航折叠为汉堡菜单,正文单列,侧边栏隐藏 |
768–992px | 平板布局,侧边栏收起 |
> 992px | 桌面多栏(正文 + 侧边栏 + TOC) |
> 1400px | 特效启用(樱花/水波等) |
布局基于 Materialize 栅格自适应,无需手动维护。
3.2 常用选项
内容宽度统一(2026-09-07)
效果:内容区在各档屏幕上都保持「正文可读、大屏不无限拉伸」——width: 90% 配上按屏幕分档的 max-width 上限。
对应配置(_variables/base.styl 的 $content-max-* / $content-wide-max-*):
| 断点 | 标准(单栏) | 宽(多栏) |
|---|---|---|
| FHD 993-1920px | 1125px | 1250px |
| 2K 1921-2560px | 1525px | 1750px |
| 4K 2561px+ | 1525px(封顶) | 2250px |
选型规则(mixin content-container(wide)):
- 单栏(无侧栏):
content-container()→ 标准 - 多栏(有侧栏/TOC):
content-container(true)→ 宽 - 禁止 layout 手写 max-width(统一走 mixin)
wiki(多栏)4K 内容列 899 → 1500px(+67%)。测试:
tools/tests/content-width.test.js(L2m)。
3.3 全部可调项
| 变量 | 类型 | 默认 | 可选值/范围 | 作用 |
|---|---|---|---|---|
$breakpoint-sm | px | 368px | 长度 | 超小屏阈值 |
$breakpoint-md | px | 768px | 长度 | 手机横屏/平板阈值,导航折叠分界 |
$breakpoint-lg | px | 992px | 长度 | 桌面多栏分界 |
$breakpoint-xl | px | 1200px | 长度 | 大屏阈值 |
$breakpoint-fun | px | 1400px | 长度 | 特效启用阈值 |
$content-max-* | px | 见上表 | 长度 | 单栏宽度上限 |
$content-wide-max-* | px | 见上表 | 长度 | 多栏宽度上限 |
3.4 边界与冲突
- 断点变量被大量 styl 文件引用,改动会同时影响导航、侧边栏、栅格、特效,「只改一处」的预期不成立。
- 内容宽度必须走
content-container()mixin;layout 里手写max-width会与阶梯上限打架(大屏被截断或过宽)。 - 这些是 Stylus 变量,不是
_config.yml配置:改完需重新编译样式(hexo clean后构建)。 $breakpoint-fun只控制特效,不影响布局列数。
3.5 小结
- 五个断点 + 两套宽度阶梯构成全部响应式行为。
- 宽度优先级:单栏用标准、多栏用宽版,统一由 mixin 决定。
4. 相册功能
用途:用一份 YAML 维护全部相册(封面 + 描述 + 图片列表),自动生成列表页与灯箱内页。
学习路径:如何启用 → 常用选项(三种图片写法 / 加密封锁)→ 全部可调项 → 边界与冲突
4.1 如何启用
效果:/galleries/ 出现相册列表卡片(含封面与描述);点进内页是 lightGallery 灯箱,图片标题/描述显示为 caption。
对应配置(数据源 source/_data/galleries.yml,单点维护):
2020:
cover: /medias_webp/images/01.webp
description: 2020 年跨年
photos:
- url: /medias_webp/images/01.webp # 形式一:完整对象(url/title/desc)
title: "2020跨年"
- /medias_webp/images/02.webp # 形式二:仅路径
- dir: /medias_webp/featureimages/ # 形式三:目录,自动扫描全部图片
title_prefix: "图"对应配置(新建相册页):
hexo new gallery <名称>生成 source/galleries/<名称>/index.md(front-matter 只需 title + layout: gallery)。
4.2 常用选项
效果:进入相册先要求输密码,输对后才渲染图片。
对应配置(相册页 front-matter 加 password):
---
title: 2020 跨年
layout: gallery
password: "********"
---效果:一个目录下的图片全部收录,不必逐张写路径。
对应配置:photos 里用 dir: 形式(见 §4.1 第三种写法),可配 title_prefix 自动生成标题前缀。
4.3 全部可调项
| 键 | 类型 | 默认 | 可选值/范围 | 作用 |
|---|---|---|---|---|
<相册名> | map | — | 任意键名 | 相册标识(也是页内标题来源) |
cover | string | — | 站内图片路径 | 列表页封面 |
description | string | — | 文本 | 列表页描述 |
photos | array | — | 对象 / 字符串 / dir 对象 | 图片列表,三种形式可混用 |
photos[].url / title / desc | string | — | — | 单图地址与标题、描述 |
photos[].dir | string | — | 目录路径 | 扫描该目录全部图片 |
photos[].title_prefix | string | — | 文本 | 自动编号标题前缀 |
page.password | string | — | 任意字符串 | 相册加密 |
4.4 边界与冲突
- 改
galleries.yml必须清缓存:数据变更会撞上 Hexo 增量缓存误判(site.data变 undefined、页面空白)。容器内用tools/cicd.sh -d,本地用rm -f db.json && hexo generate。 - 相册名同时是 URL 段:改名等于换地址,旧链接 404。
dir形式会扫描目录下全部图片,目录里混入封面/临时图会一起进相册。- 加密只保护内页;列表页的封面与描述仍公开。
4.5 小结
- 一份
galleries.yml管所有相册,图片支持单图/路径/目录三种写法。 - 加密码是相册级(整本),不是单张图级。
5. 搜索功能
用途:导航栏搜索弹窗,三套引擎(local / Algolia / Pagefind)可独立启停,前端按需切换。
学习路径:如何启用 → 常用选项(引擎对比 / 快捷键 / 多语言过滤 / Pagefind 构建)→ 全部可调项 → 边界与冲突
5.1 如何启用
效果:导航栏出现搜索框;点击弹出搜索面板,输入关键词实时出结果,/search.json 作为本地索数据源,加密文章自动从索引排除。
对应配置(主题配置的 search 块,三引擎):
search:
algolia:
enable: true # Algolia 引擎(applicationID/apiKey/indexName 见主题配置)
indexName: "blog"
local:
enable: true # 本地搜索:hexo generate 时由生成器输出 /search.json
path: /search.json
field: [post, wiki] # 搜索范围:post | page | wiki | all,可用数组组合
content: true # 是否索引正文
pagefind:
enable: true # Pagefind 静态索引(构建后浏览器端搜索)
output_subdir: pagefind5.2 常用选项
三引擎配置键与默认引擎
效果:不同引擎各有取舍——local 无外部服务、Algolia 体验最好但依赖外部索引、Pagefind 纯静态且索引在构建期生成。
对应配置:原键名以 themes/matery/_config.yml 的 search 块为准(模板 userConfig/_config.tmp.yml 同源):
| 引擎 | 配置块 | 关键键 | 说明 |
|---|---|---|---|
| local 本地 | search.local.* | enable / path / field / content / format: striptags / preload / trigger / top_n_per_article | 构建时由主题生成器输出 search.json;preload/trigger 当前未接入实现,实际始终在首次打开搜索弹窗时下载索引 |
| Algolia | search.algolia.* | enable / applicationID / apiKey / indexName / content_length / excerpt_length / batch_size / hits.per_page / labels | apiKey 是前端检索 Key(可暴露);写库 Key 单独放在 deploy.algolia_write_key(仅构建时读取) |
| Pagefind | search.pagefind.* | enable / output_subdir: pagefind | 构建时扫描 public/ 生成静态索引,浏览器端搜索,无外部服务 |
效果:站点默认用哪个引擎由一行决定;配错了不会白屏,而是降级。
对应配置(顶层 search.default):
search:
default: "algolia" # algolia | local | pagefindsearch.default(值algolia/local/pagefind)指定默认引擎;若配置的默认引擎未启用,前端自动降级为第一个已启用引擎并console.warn。- Algolia 客户端(
algoliasearch)按需加载:打开搜索弹窗时才注入,不再全站预加载。
页内切换引擎与快捷键
(源:themes/matery/layout/_partial/common/search.ejs、_partial/script/search.ejs)
效果:弹窗内可即时换引擎、换完自动重搜,下次打开还记得上次选择;Ctrl+K 随时唤起。
对应配置:无(内置行为),要点如下:
- 弹窗左侧引擎图标
#searchEngineBtn展开下拉菜单#searchEngineMenu,在 local / algolia / pagefind 间切换;可用引擎 ≤ 1 时下拉按钮自动隐藏。 - 结果按当前引擎实时渲染,切换引擎时会用输入框当前值立即重搜;当前引擎记忆在
localStorage(键search_engine)。 - 快捷键 Ctrl+K / Cmd+K 打开搜索弹窗,Esc 关闭菜单;弹窗用原生
popover,点击外部自动 light dismiss。 - 中文输入法兼容:输入事件跳过 IME composition 中间态,
compositionend兜底用完整值搜索(三引擎共用)。
多语言过滤与统一高亮
(源:themes/matery/source/js/search.js)
效果:同一篇文档的多语言版本不会重复刷屏,只留用户偏好语言那一条;关键词在结果与跳转页都被安全高亮。
对应配置:无(内置行为),要点如下:
- 多语言过滤:三引擎共用
applyLangFilter()——按路径语言前缀分组,优先返回用户偏好语言(localStorage键wiki_lang,语言列表来自window.MATERY_LANG.langs)命中的那条,缺失则回落默认语言(default_lang),同组去重。 - 统一高亮:三引擎共用基类
_highlight()——关键词先转义再生成成对<mark class="search-keyword">(防 XSS / 标签不闭合);Algolia 返回的高亮 HTML 经_safeHighlight()剥离后重新高亮。 - 跳转页高亮:结果链接附
?highlight=<关键词>,文章页由search.ejs尾部脚本解析并高亮正文(跳过代码块与链接)。
Pagefind 构建步骤
效果:public/pagefind/ 生成静态索引,浏览器端无外部服务即可搜索。
对应配置(在 hexo generate 之后、部署之前扫描 public/,运行于 CI 阶段,见 tools/CI.sh):
npx --no-install pagefind --site public --output-subdir pagefind \
|| npx --yes pagefind@1.5.2 --site public --output-subdir pagefind优先用本地已装版本,未装则用固定版本 pagefind@1.5.2 一次性下载(避免 npx 拉最新版漂移)。
5.3 全部可调项
| 键 | 类型 | 默认 | 可选值/范围 | 作用 |
|---|---|---|---|---|
search.default | string | algolia | algolia / local / pagefind | 默认引擎,未启用则降级 |
search.algolia.enable | bool | true | true / false | Algolia 引擎开关 |
search.algolia.applicationID | string | — | Algolia 应用 ID | 索引归属 |
search.algolia.apiKey | string | — | 前端检索 Key | 只读检索(可暴露) |
search.algolia.indexName | string | blog | 索引名 | 搜哪个索引 |
search.algolia.content_length | int | — | 字符数 | 推送到 Algolia 的正文截断长度 |
search.algolia.excerpt_length | int | — | 字符数 | 结果摘要长度 |
search.algolia.batch_size | int | — | 条数 | 单次推送批量 |
search.algolia.hits.per_page | int | — | 条数 | 每页结果数 |
search.algolia.labels | map | — | 文本 | 结果区文案 |
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 | 是否索引正文(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.local.preload | bool | — | true / false | 预加载(当前未接入实现) |
search.pagefind.enable | bool | true | true / false | Pagefind 引擎开关 |
search.pagefind.output_subdir | string | pagefind | 目录名(勿用下划线开头) | 索引输出子目录 |
5.4 边界与冲突
preload/trigger是配置了但没实现的键:设了也不改变行为,索引始终在首次打开弹窗时下载。- Algolia 单条记录硬上限 10KB:正文太长会被服务端拒绝(HTTP 400),需靠
content_length截断;中文按 3 字节/字符算,上限比英文低得多。 output_subdir不能用下划线开头:GitHub Pages(Jekyll)会忽略_开头的目录,索引直接不发布。- Pagefind 必须在
hexo generate之后跑:顺序颠倒时扫到的是旧产物或空目录。 - 三引擎同时开启会让配置互相掩盖问题:排查搜索异常时先只留一个引擎验证。
5.5 小结
- 三引擎互相独立,
search.default决定首选,未启用自动降级。 - local 与 Pagefind 都是纯静态方案,Algolia 需要额外推送索引。
field/content/content_length是控制索引体积的三个主要旋钮。
6. 评论系统
用途:文章、留言页、友链页共用的 Waline 评论,支持 Markdown/Emoji/点赞/后台管理。
学习路径:如何启用 → 常用选项(单页开关)→ 全部可调项 → 边界与冲突
6.1 如何启用
效果:文章底部出现评论框,自动跟随明暗模式;未登录也可发表(按服务端配置)。
对应配置(主题配置):
waline:
enable: true
serverURL: 'https://waline.17lai.site' # 你的 Waline 服务端
lang: zh-CN
dark: 'html[data-user-color-scheme="dark"]' # 跟随暗色模式
login: enable
wordLimit: 1000
pageSize: 106.2 常用选项
效果:某篇文章(或某个页面)不显示评论区,其它页面照常。
对应配置(front-matter):
comments: false # true 显示 / false 隐藏6.3 全部可调项
| 键 | 类型 | 默认 | 可选值/范围 | 作用 |
|---|---|---|---|---|
waline.enable | bool | true | true / false | 评论区总开关 |
waline.serverURL | string | — | 服务端 URL | 评论数据归属 |
waline.lang | string | zh-CN | 语言码 | 界面语言 |
waline.dark | string | html[data-user-color-scheme="dark"] | CSS 选择器 | 暗色模式下套用的样式 |
waline.login | string | enable | enable / 其他值未验证 | 是否要求登录 |
waline.wordLimit | int | 1000 | 字符数 | 单条评论字数上限 |
waline.pageSize | int | 10 | 条数 | 每页评论数 |
waline.avatar | string | mp | ''/mp/identicon/monsterid/wavatar/retro/hide | 头像来源 |
page.comments | bool | 继承全局 | true / false | 单页开关 |
6.4 边界与冲突
- 客户端与服务端版本必须一致:Waline 版本错配会导致评论框加载出来但提交失败。
serverURL写错不会报错:只是评论框空白或一直 loading,需看浏览器网络面板确认。- 暗色模式靠
dark选择器:若主题的data-user-color-scheme属性被改,评论框会一直亮色。 waline与valine两套配置同时存在,启用哪个看enable;同时开会出现两个评论框。
6.5 小结
- 启用只需
enable+serverURL两行,其余是体验微调。 - 单页粒度用 front-matter
comments。
7. 暗色模式
用途:由
[data-user-color-scheme]属性驱动的整套 CSS 变量切换,覆盖正文、图片、图表、评论。学习路径:如何启用 → 常用选项(三种默认模式)→ 全部可调项 → 边界与冲突
7.1 如何启用
效果:页面出现明暗切换按钮;默认按系统偏好或本地时间自动切,用户手动切换后记住选择。
对应配置(主题配置):
dark_mode:
enable: true
default: auto # auto | light | dark7.2 常用选项
效果:三种默认策略的表现差异如下。
对应配置:上面的 dark_mode.default。
- auto:优先遵循
prefers-color-scheme,其次按本地时间 18:00–6:00 自动进入暗色 - light / dark:固定初始模式,用户手动切换后覆盖
- 手动切换按钮会覆盖默认模式(记忆用户选择)
7.3 全部可调项
| 键 | 类型 | 默认 | 可选值/范围 | 作用 |
|---|---|---|---|---|
dark_mode.enable | bool | true | true / false | 是否提供切换与暗色样式 |
dark_mode.default | string | auto | auto / light / dark | 初始模式 |
image.light_filter / light_filter_dark | string | brightness(1) / brightness(0.92) | CSS filter | 亮/暗下图片滤镜 |
image.card_opacity / card_opacity_dark | string | 0.85 / 0.7 | 0–1 | 亮/暗下卡片透明度 |
7.4 边界与冲突
- 暗色值不走
_config.yml单一来源:颜色在color-schema.styl的[data-user-color-scheme="dark"]块里成对定义,只改一处会亮暗不匹配。 auto的时段判断用访客本地时间,不是站点时区;同一时刻不同地区用户看到的模式可能不同。- 第三方组件(评论、ECharts)各自有暗色适配逻辑,新增组件时要单独接
data-user-color-scheme。 - 手动切换的选择存在浏览器本地,清理站点数据会回到默认模式。
7.5 小结
- 一个属性(
data-user-color-scheme)+ 一套 CSS 变量 = 全站暗色。 - 新增任何自绘组件都要同时写亮/暗两份变量,否则暗色下会出现「白斑」。
8. 加密
用途:文章 / 相册 / 内容片段 / 整个 wiki 四种粒度的密码保护,前端 WebCrypto 解密。
学习路径:如何启用 → 常用选项(四种方式 / wiki 整体加密 / 页内 front-matter)→ 全部可调项 → 边界与冲突
8.1 如何启用
效果:被加密的文章页显示解密 UI(#hbePass),输入正确密码后渲染正文;密码错误显示提示文案。
对应配置(自研 HBE v4 体系,2026-08-09 起去插件化,替代 hexo-blog-encrypt;AES-256-GCM + PBKDF2 250k 轮,文章/相册/片段/wiki 统一。前端 WebCrypto 解密,hexo-blog-decrypt 事件触发主题刷新。主题 encrypt 段):
encrypt:
enable: true
abstract: 有东西被加密了, 请输入密码查看.
message: 您好, 这里需要密码.
tags: # 按标签加密:命中标签的文章自动加密
- {name: 私人, password: "********"}
wrong_pass_message: 抱歉, 这个密码看着不太对, 请再试试.
autoSave: true # 派生 key 缓存到 localStorage(1 天 TTL),刷新免输密码8.2 常用选项
四种加密方式
效果:从「整篇按标签批量加密」到「只藏一段」,共四种粒度。
对应配置:
| 方式 | 用法 | 说明 |
|---|---|---|
| 标签加密 | 文章打 encrypt.tags 中标签 | 命中标签自动整篇加密 |
| 单篇加密 | front-matter password: "********" | 整篇加密 |
| 内容片段加密 | encrypt tag(密码 + 标题 + 简介) | 段落级加密,支持内部 tag 嵌套(2026-09-07 方案 Y) |
| Wiki 整体加密 | {wiki}-sidebar.yml 加 encrypt_password | 整个 wiki 一密码,跨页共享 dk(2026-09-07) |
片段加密的完整写法(参数:密码、标题、简介):
{% encrypt 密码 "标题" "简介" %}markdown 内容{% endencrypt %}Wiki 整体加密
效果:加密中间内容 + 右侧 TOC,左侧导航保留(相当于可见标题);解密任一页,该 wiki 所有语言页共享解锁状态(1 天 TTL)。
对应配置(任一 {wiki}-sidebar.yml,如 source/_data/dev-sidebar.yml;统一 encrypt_ 前缀):
encrypt_password: "整个wiki的密码" # 必填
encrypt_message: "该文档受密码保护" # 可选:提示语
encrypt_wpm: "密码错误" # 可选:错误提示
encrypt_placeholder: "请输入密码" # 可选:输入占位
encrypt_button_text: "解锁" # 可选:按钮文字Wiki 页 front-matter 清单(2026-09-03)
效果:wiki 页面能按需打开 Mermaid、MathJax、锚点、Prism 插件、ECharts,或关掉目录/评论/加密本页。
对应配置:wiki 页面(layout: wiki)支持的 front-matter 与文章一致,常用项:
| front-matter | 效果 |
|---|---|
mermaid: true | 启用 Mermaid 流程图/时序图 |
mathjax: true | 启用 MathJax 公式 |
toc: false | 关闭本页目录 |
comment: false | 关闭本页评论 |
anchorjs: false | 关闭标题锚点 |
treeview: true | 启用 Prism treeview 代码树插件 |
autolinker: true | 启用 Prism autolinker 链接插件 |
diffhighlight: true | 启用 Prism diff 高亮插件 |
echarts: true | 启用 ECharts 图表(需正文含图表容器) |
password: "********" | 加密本页(解锁体验与文章一致) |
ECharts 图表(2026-09-03 统一)
效果:正文里出现可交互图表,明暗模式自动适配,3D/globe 等 GL 图表也能用。
对应配置(front-matter 加 echarts: true,加载 libs echarts(latest,当前默认 v6.1.0)+ echarts-gl 2.1.0):
{% echarts 宽 高 %}
{ "xAxis": { "type": "category", "data": ["A","B"] }, "yAxis": { "type": "value" }, "series": [{ "type": "bar", "data": [1,2] }] }
{% endecharts %}- 文章内图表:
echarts标签(参数依次为宽、高,宽默认 100%、高默认 400px)+ option JSON - 明暗:自动适配(init 传当前主题,切换时重建)
- GL 图表:option 含 grid3D/globe 等 GL 组件时自动可用(echarts-gl 已加载)
- 注意:echarts 6 要求 xAxis 显式声明
type(category/value)
8.3 全部可调项
| 键 | 类型 | 默认 | 可选值/范围 | 作用 |
|---|---|---|---|---|
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 | 派生 key 缓存(1 天 TTL) |
page.password | string | — | 任意字符串 | 单篇/单页加密 |
<wiki>-sidebar.yml: encrypt_password | string | — | 任意字符串 | wiki 整体密码(必填即启用) |
encrypt_message / encrypt_wpm / encrypt_placeholder / encrypt_button_text | string | — | 文本 | wiki 解锁框文案 |
page.echarts | bool | false | true / false | 加载 ECharts 运行时 |
8.4 边界与冲突
enable: false会让已加密内容变不可读:加密产物仍在,但前端不再加载解密逻辑,表现为空白或原样输出密文——重新开启才恢复。autoSave是便利也是风险:解密 key 缓存在localStorage(1 天),共用设备上等于一天内免密。- wiki 密码与文章密码互相独立:改了 wiki 的
encrypt_password,已经解锁过的浏览器仍可凭旧 key 读到旧页(TTL 内)。 hexo-blog-encrypt插件已弃用:只认本主题的encrypt段,按旧插件文档配置无效。- 片段加密里的
raw标签少写一个就会在构建期报unknown block tag(本页示例已加)。 - 资源压缩(2026-09-25):hbe 加密资源已统一进
source/libs/hbe/并走静态管线,hexo-minify对其生效(此前绕过压缩管线)。
8.5 小结
- 四种粒度:标签批量、单篇、片段、整个 wiki。
- 全是前端解密,防的是「随手翻到」,不是服务端权限控制——密文与密钥派生参数都在页面里。
- wiki 解锁状态跨页共享,1 天 TTL。
发布与独立页面
9. RSS / 站点地图
用途:输出三类订阅源与两套站点地图,供阅读器与搜索引擎抓取。
学习路径:如何启用 → 常用选项(条数 / 输出名)→ 全部可调项 → 边界与冲突
9.1 如何启用
效果:站点根目录生成 /rss.xml、/atom.xml、/feed.json 三个订阅源,以及 /sitemap.xml、/baidusitemap.xml 两份站点地图。
对应配置(根 _config.yml,hexo-feed + hexo-generator-sitemap):
feed: # 三类订阅源独立开关
rss:
enable: true
output: "rss.xml"
atom:
enable: true
output: "atom.xml"
jsonFeed:
enable: true
output: "feed.json"
limit: 10
sitemap: # 通用站点地图
path: sitemap.xml
baidusitemap: # 百度站点地图
path: baidusitemap.xml9.2 常用选项
效果:订阅源只保留最近 20 篇,减小单文件体积。
对应配置:
feed:
limit: 20效果:换掉订阅源输出文件名(如统一到 /feed/rss.xml)。
对应配置:改 feed.rss.output / feed.atom.output / feed.jsonFeed.output(RSS 模板为 themes/matery/layout/_partial/rss.ejs)。
9.3 全部可调项
| 键 | 类型 | 默认 | 可选值/范围 | 作用 |
|---|---|---|---|---|
feed.rss.enable | bool | true | true / false | 输出 RSS 2.0 |
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 输出路径 |
feed.limit | int | 10 | 篇数 | 订阅源条目上限 |
sitemap.path | string | sitemap.xml | 路径 | 通用站点地图 |
baidusitemap.path | string | baidusitemap.xml | 路径 | 百度站点地图 |
9.4 边界与冲突
- 这三项在根
_config.yml,不是主题配置;写进主题配置不生效。 - 生成器在构建期输出,改配置后不
hexo clean时旧文件仍留在public/。 - 加密文章仍会出现在订阅源里(标题/摘要),只是正文加密;要彻底排除需在生成器层过滤。
- 换了
output路径,旧的订阅地址立刻 404,老读者会断更。
9.5 小结
- 三类订阅源各自独立开关,
limit控制条数。 - 两份站点地图分别给通用搜索引擎与百度。
10. PWA
用途:把站点变成可安装、可离线访问的 PWA;每次构建自动换缓存版本并让客户端静默刷新一次。
学习路径:如何启用 → 常用选项(SW 版本管理)→ 全部可调项 → 边界与冲突
10.1 如何启用
效果:浏览器地址栏出现安装入口(manifest + 主题色生效);断网时已缓存页面仍可打开;站点更新后自动刷新一次到新版。
对应配置(主题配置):
pwa:
enable: trueuserConfig/sw.tmp.js 是 Service Worker 模板:CI 时注入离线缓存版本号(17lai-cache-{时间戳})生成 source/sw.js。
10.2 常用选项
SW 版本管理细节
(源:userConfig/sw.tmp.js、themes/matery/layout/_partial/common/head.ejs、tools/cicd.sh)
效果:部署新版后用户无需手动刷新、也不会被弹窗打断;旧版本缓存自动清理。
对应配置:无(由 CI 与模板协作),要点如下:
- 模板 → 产物:CI 构建时
tools/cicd.sh的inject_pwa_sw()先把userConfig/sw.tmp.js复制为source/sw.js,再用sed注入占位符;产物发布为站点根路径的/sw.js。 - 缓存键 / 版本注入:模板用字面量
17lai-cache-{uniqueIdentifier}作为缓存键,缓存名前缀为17lai-cache-;CI 用构建时间戳(date '+%Y%m%d%H%M%S',即offline_version)替换占位符,得到形如17lai-cache-20260913181700的唯一键——每次构建即新版本。同一步还会注入{cdnUrl}/{cdnPathVersion}/{cdnPathLatest}/{urlVersion}等占位符。 - 客户端自动刷新一次(
head.ejs,theme.pwa.enable时以async脚本注册,scope: '/';localhost下跳过注册,避免本地开发命中旧缓存):- 注册后经
MessageChannel向 SW 发checkIdentifier消息,取回当前缓存版本号并记录基准。 updatefound→ 新 SWinstalled(预缓存addAll完成),以及controllerchange(SW 接管)两个时机触发checkVersionAndRefresh()。- 仅当缓存版本号变化才自动
window.location.reload()(延迟 500ms 等 activate 完成);refreshed守卫保证每页只刷一次,避免 Vercel 重复部署触发循环刷新——无弹窗、无手动刷新。
- 注册后经
- 激活与旧缓存清理(SW 模板):
install事件预缓存cacheList后self.skipWaiting();activate事件遍历caches.keys(),删除前缀为17lai-cache-且不等于当前缓存键的旧缓存,重新addAll后self.clients.claim()接管所有页面。 - 压缩版备份:release 模式把未压缩
sw.js备份到userConfig/sw.js,构建完成再备份压缩版到userConfig/sw.min.js;continue 模式复用sw.min.js以保证压缩产物一致(缺失则回退模板版)。
10.3 全部可调项
| 键 / 文件 | 类型 | 默认 | 可选值/范围 | 作用 |
|---|---|---|---|---|
pwa.enable | bool | true | true / false | 注册 SW 与 manifest |
userConfig/sw.tmp.js | 模板 | — | — | SW 源码(CI 注入版本号后输出 source/sw.js) |
{uniqueIdentifier} | 占位符 | 构建时间戳 | YYYYMMDDHHMMSS | 缓存键,每次构建变化 |
{cdnUrl} / {cdnPathVersion} / {cdnPathLatest} / {urlVersion} | 占位符 | CI 注入 | — | SW 内资源地址 |
userConfig/sw.min.js | 文件 | — | — | continue 模式复用的压缩产物 |
10.4 边界与冲突
- SW 缓存最容易造成「改了不生效」:本地
localhost已跳过注册,但用域名访问测试域时会命中旧缓存——排查前先注销 SW / 清站点数据。 - 版本号不变不会触发更新:只改内容不重新构建(或
-c复用旧版本)时缓存键不变,客户端可能继续用旧缓存。 - 模板占位符没被注入(本地直接
hexo generate)时,{cdnUrl}等会以字面量留在sw.js里,导致缓存请求 404。 localhost跳过注册是刻意行为,别在本地调试「为什么没有 SW」。
10.5 小结
- PWA 开关只有一个,真正复杂的是
sw.tmp.js的版本注入链路。 - 缓存键 = 构建时间戳,因此「每次部署都是新版本」。
11. 独立页面(Standalone Pages)
用途:主题提供的专用 layout 页面(影视/短片/留言/友链/联系/404),页面正文写在 md 里,结构由模板渲染。
学习路径:如何启用(逐个 layout)→ 常用选项(数据源 / front-matter)→ 全部可调项 → 边界与冲突
11.1 如何启用
以下页面由主题提供专用 layout 模板,需在 source/ 下创建对应页面(front-matter 指定 layout),页面正文写在 md 文件中,模板负责渲染。
av 影视页(layout: av)
⚠️ 已废弃(2026-09-12):
avlayout 无实际用途,已从主题删除(themes/matery/layout/av.ejs移除、source/av/不再创建)。以下内容仅作历史参考——请勿再按此创建该页。
效果(历史设计):影视/资源分享页,结构类似关于页——音乐播放器 + 视频 + 我的相册 + 资源分享正文。
对应配置(历史数据源,已无模板消费):
theme.music(音乐播放器,见「交互视觉」篇 §9)theme.video(视频播放器,见「交互视觉」篇 §10)theme.myGallery(我的相册,见「侧边栏组件」篇 §8)page.content(页面正文,资源分享列表)
当时的建页方式(当前项目未建此页):
hexo new page avsource/av/index.md front-matter:
---
title: 影视资源
layout: av
---movies 电影页(layout: movies)
效果:励志短片页,页面中是一个 Bilibili iframe 播放器(地址硬编码在模板里)。
对应配置(项目已有 source/movies/index.md):
---
title: 视频
layout: movies
---数据源:无数据文件——短片地址硬编码在 themes/matery/layout/movies.ejs 的 iframe src 中,更换短片需编辑模板。
msg 留言页(layout: msg)
效果:留言板——正文(博客时间线等)+ 留言信封组件 + 评论区;信封有展开动画与留言表单提示(样式 letter.styl,文案 i18n letter.*)。
对应配置(项目已有 source/msg/index.md):
---
title: 留言板
layout: msg
toc: false # 关闭 TOC(模板按 theme.toc.enable && page.toc !== 'false' 判断)
comment: waline
---评论开关:模板经 inject_point('pageComments') 注入评论,front-matter comment: waline(或 comments: true)开启。
friends 友链页(layout: friends)
效果:友链卡片墙(Masonry 瀑布流),卡片背景色按顺序循环 frind-card1~frind-card10;页面正文显示在友链列表下方,评论区经 linksComments 注入点渲染。
对应配置(项目已有 source/friends/index.md):
---
title: 'For My Friends!'
layout: friends
comment: waline
---数据源(source/_data/friends.yml,纯数组,模板经 site.data.friends 读取):
- name: 友链名称 # 必填,卡片标题
url: https://example.com # 必填,跳转地址
avatar: /favicon.png # 必填,头像
introduction: 一句话介绍 # 必填
title: 前去学习 # 可选,按钮文字contact 联系页(layout: contact —— ⚠️ 活动主题未实现,勿用)
注意:活动主题
themes/matery/layout/中不存在contact.ejs(仅未启用的上游参考主题有),layout: contact不会渲染。以下内容仅供了解历史设计。
效果(设计意图):联系页——页面正文 + 弹幕互动(文字/链接/速度)+ 评论区。
对应配置(历史设计):基于 LeanCloud(AV)存储的 barrager 弹幕,valine/waline 任一启用(theme.valine.enable 或 theme.waline.enable)即显示弹幕表单;客服组件(daovoice / tidio / tuxiaochao)见「交互视觉」篇 §14。
当时的建页方式(当前项目未建此页):
hexo new page contactsource/contact/index.md front-matter:
---
title: 联系我
layout: contact
---404 页(layout: 404)
效果:未找到页面提示 + 搜索入口 + 倒计时 8 秒跳转首页;banner 图按日期每日切换(/medias_webp/banner/{0-6}.webp)。
对应配置(项目已有 source/404.md):
---
title: 404
layout: 404
excerpt: "Oops~,我崩溃了!找不到你想要的页面 :(" # 显示在页面描述处
---11.2 常用选项
效果:控制某个独立页面是否出目录、是否出评论。
对应配置(页面 front-matter):
toc: false # 关闭本页目录
comment: waline # 挂载 Waline 评论(或用 comments: true)效果:友链卡片按列表顺序换配色,无需写样式。
对应配置:friends.yml 的条目顺序即配色顺序(循环 frind-card1~frind-card10)。
11.3 全部可调项
| layout 值 | 页面 | 模板 | 状态 |
|---|---|---|---|
av | /av/ | 已删除(av.ejs 移除) | 已废弃(2026-09-12) |
movies | /movies/ | themes/matery/layout/movies.ejs | 可用(视频地址硬编码) |
msg | /msg/ | themes/matery/layout/msg.ejs | 可用 |
friends | /friends/ | themes/matery/layout/friends.ejs | 可用(数据源 friends.yml) |
contact | /contact/ | 不存在 | 未实现,勿用 |
404 | /404.html | themes/matery/layout/404.ejs | 可用 |
page.toc | 各独立页 | — | true / false,关目录 |
page.comment / page.comments | 各独立页 | — | 开启评论 |
page.excerpt | 404 等 | — | 页面描述文案 |
11.4 边界与冲突
layout: av和layout: contact都是死配置:写了不报错,只是回落到默认 page 模板(或空白),排查时很容易误判成样式问题。movies内容不可配:视频地址在.ejs里,改短片必须改主题代码(属代码改动,发布要走-r)。- 友链的
frind-card*拼写是错字但已固化:按正确拼写friend-card改样式不会命中。 - 独立页面要同时在
source/建页:只写配置不建 md 文件,路由不存在。 - 页面级
toc/comment只对支持注入点的 layout 有效。
11.5 小结
- 当前可用四个半:
movies、msg、friends、404(av已删、contact未实现)。 - 数据源固定的只有
friends(source/_data/friends.yml),其余靠模板硬编码或注入点。 - 建页三件套:
hexo new page <名>+ front-matterlayout+ (必要时)数据文件。
附:布局功能速查表
| 功能 | 位置 | 配置位置 | 说明 |
|---|---|---|---|
| 上下篇导航 | 文章底部 | 主题 post.prev_next | 按时间取相邻文章 |
| 版权声明 | 文章底部 | 主题 post.copyright(含 license 子键) | front-matter reprintPolicy 单篇覆盖 |
| 时效提示 | 文章开头 | 主题 post.outdate | 默认关闭,按天数分级 |
| TOC 目录 | 文章侧边栏 | 主题 toc | front-matter toc: false 单篇关闭 |
| 广告位 | 侧边栏 | 主题 post.advertisements | 数组多卡片,可单独启停 |
| 分类雷达图 | /categories/ | 自动渲染 | 无需配置 |
| 标签云/词云 | /tags/ | 自动渲染 | 无需配置 |
| 归档时间线 | /archives/ | 自动渲染 | 无需配置 |
| 关于页图表 | /about/ | 自动渲染 | 无需配置 |
| 响应式断点 | 全局 | base.styl 的 $breakpoint-* 变量 | 368/768/992/1200/1400 |
| 相册 | /galleries/ | source/_data/galleries.yml | hexo new gallery 建页 |
| 搜索 | 导航栏 | 主题 search(algolia/local/pagefind) | 三引擎可独立启停 |
| 评论 | 文章/留言等页 | 主题 waline | front-matter comments 控制开关 |
| 暗色模式 | 全局 | 主题 dark_mode | auto 跟随系统 + 时段 |
| 加密 | 文章页 | 根 encrypt | 标签加密或 front-matter password |
| RSS/Sitemap | 根路径 | 根 feed / sitemap / baidusitemap | 三类订阅源 + 双站点地图 |
| PWA | 全局 | 主题 pwa + userConfig/sw.tmp.js | 离线缓存 + 可安装 |
| 独立页面 | /movies/ /msg/ /friends/ /404 | source/ 下建页 + front-matter layout | 四种专用模板,见 §11(/av/ 已删除、/contact/ 模板未实现) |