al-folio 模板定制修改总结
本文总结将 al-folio 模板 fork 到个人仓库后所做的全部定制修改,供后续维护参考。
1. 站点基本信息配置(_config.yml)
在 _config.yml 中修改了以下关键配置项:
# 个人信息
first_name: Roderick
middle_name:
last_name: Huang
lang: zh-CN
contact_note: >
欢迎通过 GitHub 或邮件与我联系。
description: >
Roderick Huang 的个人博客,记录工作与技术。
# 站点 URL
url: https://hxf0223.github.io
baseurl: # 个人站点留空
# 博客设置
blog_name: WorkLog
# 页面宽度
max_width: 1400px
# Giscus 评论系统
giscus:
repo: hxf0223/hxf0223.github.io
repo_id: R_kgDOL6EGLA
category: General
category_id: DIC_kwDOL6EGLM4Czxtq
mapping: pathname
strict: 0
lang: zh-CN
# Scholar
scholar:
last_name: [Huang]
first_name: [Roderick, R.]
# 注释掉外部文章源
# external_sources: ...
另外在 defaults 中为所有文章默认启用右侧目录:
defaults:
- scope:
path: ""
type: posts
values:
toc:
sidebar: right
2. 博客文章批量迁移
将旧博客的 155 篇 Markdown 文章迁移到 _posts/ 目录,使用 Python 脚本批量更新 frontmatter,确保格式符合 al-folio 要求:
- 添加
layout: post - 添加
toc: sidebar: right - 保留原始
title、date、categories、tags字段
分两轮处理:
- 第一轮处理 112 个非中文文件名的文件
- 第二轮使用
git -c core.quotepath=false处理 43 个中文文件名的文件
3. Tags/Categories 自动显示(_pages/blog.md)
问题
al-folio 默认使用 _config.yml 中的 display_tags 和 display_categories 手动列表控制博客页面的标签过滤栏。新添加文章的 tags 不会自动出现。
解决方案
修改 _pages/blog.md,将手动列表替换为 Jekyll 自动收集的 site.tags 和 site.categories:
{% assign all_tags = site.tags | sort %}
{% assign all_categories = site.categories | sort %}
{% if all_tags.size > 0 or all_categories.size > 0 %}
<div class="tag-category-list">
<ul class="p-0 m-0">
{% for tag in all_tags %}
<li>
<i class="fa-solid fa-hashtag fa-sm"></i>
<a href="{{ tag[0] | slugify | prepend: '/blog/tag/' | relative_url }}">{{ tag[0] }}</a>
</li>
{% unless forloop.last %}<p>•</p>{% endunless %}
{% endfor %}
...
</ul>
</div>
{% endif %}
注意:
site.tags是一个 hash([name, posts_array]),取标签名需使用tag[0],而site.display_tags是一个字符串数组。
4. PWA 支持(可安装为桌面应用)
4.1 新增 manifest.json
创建 manifest.json(使用 Liquid front matter 动态填充站点信息):
{
"name": "Roderick Huang",
"short_name": "Huang",
"id": "/",
"scope": "/",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#1a1a2e",
"icons": [
{ "src": "/assets/img/pwa-icon-192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "/assets/img/pwa-icon-512.png", "sizes": "512x512", "type": "image/png" },
{ "src": "/assets/img/pwa-icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" }
]
}
-
id和scope是新版 Chrome/Edge 用于标识 PWA 身份和作用域的关键字段 - 需要在
assets/img/下提供 192x192 和 512x512 的 PNG 图标
4.2 新增 sw.js(Service Worker)
创建 sw.js 实现离线缓存。关键注意点:
// install 事件:预缓存关键资源,添加容错处理
self.addEventListener("install", (event) => {
event.waitUntil(
caches
.open(CACHE_NAME)
.then((cache) =>
cache.addAll(PRECACHE_URLS).catch((err) => {
console.warn("Precache failed (non-fatal):", err);
})
)
.then(() => self.skipWaiting())
);
});
// fetch 事件:Stale-While-Revalidate 策略
// 先返回缓存(保证加载速度),同时后台更新缓存,下次访问即为最新内容
self.addEventListener("fetch", (event) => {
const url = new URL(event.request.url);
if (event.request.method !== "GET" || !url.protocol.startsWith("http")) return;
event.respondWith(
caches.open(CACHE_NAME).then((cache) => {
return cache.match(event.request).then((cached) => {
const fetchPromise = fetch(event.request).then((response) => {
if (response && response.status === 200 && response.type !== "opaque") {
cache.put(event.request, response.clone()).catch(() => {});
}
return response;
});
return cached || fetchPromise; // 有缓存则立即返回,同时后台更新
});
})
);
});
踩坑记录:
-
cache.addAll()如果任何一个 URL 失败,整个 Promise 会 reject,导致 SW 安装失败 → 浏览器不显示安装按钮。必须添加.catch()容错 - Edge/Chrome 浏览器扩展的请求(
chrome-extension://)也会被 SW 的 fetch 事件拦截,cache.put()不支持非 HTTP scheme,导致Uncaught TypeError。必须用url.protocol.startsWith('http')过滤 -
cache.put()也需要.catch(() => {})兜底,防止其他异常场景 - 初版使用 Cache First 策略(命中缓存直接返回),导致部署新版本后用户打开页面仍看到旧内容,必须手动刷新才能更新。改为 Stale-While-Revalidate 策略后,首次打开走缓存保证速度,同时后台拉取最新内容更新缓存,下次访问即可看到新版本
4.3 修改 _includes/head.liquid
在 <head> 中添加 manifest 引用、theme-color、SW 注册脚本和 CSP:
<!-- PWA manifest & theme color -->
<link rel="manifest" href="/manifest.json" />
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff" />
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#1a1a2e" />
<!-- PWA Service Worker registration -->
<script>
if ("serviceWorker" in navigator) {
window.addEventListener("load", function () {
navigator.serviceWorker.register("/sw.js");
});
}
</script>
<!-- CSP: 需要包含 worker-src 'self' -->
<meta http-equiv="Content-Security-Policy" content="... worker-src 'self';" />
注意: CSP 必须包含
worker-src 'self',否则某些浏览器版本会阻止 Service Worker 注册。
5. Prettier 配置
.prettierrc
plugins: ["@shopify/prettier-plugin-liquid"]
printWidth: 150
trailingComma: "es5"
overrides:
- files: "**/*.md"
options:
proseWrap: "preserve"
printWidth: 10000
-
proseWrap: "preserve"防止 prettier 重排 Markdown 段落中的换行和空行 -
printWidth: 10000避免 Markdown 文本被强制换行
.prettierignore
额外添加的忽略项:
# Ignore Jekyll Liquid-templated JSON/JS files
manifest.json
sw.js
# Ignore upstream files that conflict with prettier
CUSTOMIZE.md
manifest.json 和 sw.js 包含 Jekyll Liquid front matter(--- 头部),prettier 无法正确解析,需要忽略。
6. CI/CD 修改(.github/workflows/deploy.yml)
在部署工作流的路径过滤器中添加 **.json,确保 manifest.json 等 JSON 文件的修改能触发自动部署:
on:
push:
paths:
- "**.js"
- "**.json" # 新增
- "**.liquid"
另外升级了 CodeQL action 到 v4 以消除 deprecated action 警告。
7. 其他小修改
| 修改项 | 说明 |
|---|---|
| Category 大小写统一 | RANSAC.md 中 categories: [algorithm] 改为 [Algorithm],避免 Jekyll 路径冲突 |
| 禁用 external_sources | 注释掉 _config.yml 中的 external_sources 配置,移除示例外部文章 |
| 默认 toc 设置 | 在 _config.yml 的 defaults 中为所有 posts 设置 toc: sidebar: right |
8. Markdown 表格样式增强
8.1. 问题
al-folio 默认的表格样式没有边框,表格线不可见,可读性差。
8.2. 改动文件
_sass/_themes.scss — 为亮色和暗色主题分别添加表格相关 CSS 变量:
/* 亮色主题 (:root) */
--global-table-border-color: rgba(0, 0, 0, 0.15);
--global-table-header-bg: rgba(0, 0, 0, 0.06);
--global-table-stripe-bg: rgba(0, 0, 0, 0.03);
--global-table-hover-bg: rgba(0, 0, 0, 0.06);
/* 暗色主题 (html[data-theme="dark"]) */
--global-table-border-color: rgba(255, 255, 255, 0.15);
--global-table-header-bg: rgba(255, 255, 255, 0.08);
--global-table-stripe-bg: rgba(255, 255, 255, 0.04);
--global-table-hover-bg: rgba(255, 255, 255, 0.08);
_sass/_typography.scss — 重写 table 样式规则:
table {
border-collapse: collapse;
border-spacing: 0;
width: 100%;
margin-bottom: 1rem;
overflow-x: auto;
display: block; /* 宽表格横向滚动 */
td,
th {
font-size: 1rem;
padding: 0.5rem 0.75rem; /* 原来是 1px 1rem 1px 0,无视觉边框 */
border: 1px solid var(--global-table-border-color);
text-align: left;
}
th {
font-weight: bold;
background-color: var(--global-table-header-bg);
}
tbody tr:nth-child(even) {
background-color: var(--global-table-stripe-bg); /* 斑马纹 */
}
tbody tr:hover {
background-color: var(--global-table-hover-bg);
}
}
9. 图片宽度约束与点击放大
9.1. 问题
- Markdown 中的图片按原始尺寸显示,大图会超出内容区宽度
- 无法点击图片在页面内单独放大查看
9.2. 解决方案
利用 al-folio 已内置的 medium-zoom 库(_config.yml 中 enable_medium_zoom: true 已默认开启),扩展其选择器覆盖所有 post 内图片。
9.3. 改动文件
_sass/_typography.scss — 添加图片宽度约束:
/* Constrain images to content width and enable zoom cursor */
.post-content img,
#markdown-content img {
max-width: 100%;
height: auto;
display: block;
margin: 0.5rem auto;
cursor: zoom-in;
}
assets/js/zoom.js — 扩展 medium-zoom 初始化逻辑,自动为所有 post 内图片启用放大:
// Initialize medium zoom.
$(document).ready(function () {
const zoomOptions = { background: "#000000dd", margin: 24 };
// 原有:显式标记 data-zoomable 的图片
mediumZoom("[data-zoomable]", zoomOptions);
// 新增:自动处理 markdown 内容区所有裸图片(未在链接内)
document.querySelectorAll("#markdown-content img:not([data-zoomable])").forEach(function (img) {
if (!img.closest("a")) {
mediumZoom(img, zoomOptions);
}
});
});
背景色选择:使用黑色 #000000dd(与原始 al-folio 演示效果一致),替代原来随主题变化的 --global-bg-color。
冲突修复
原始模板中 _scripts/photoswipe-setup.js 是一个带 Liquid front matter 的脚本,Jekyll 会将其编译输出到 assets/js/photoswipe-setup.js。之前错误地在 assets/js/ 下也创建了同名文件,导致编译 warning:
Conflict: The following destination is shared by multiple files.
/home/hxf0223/work/hxf0223.github.io/_site/assets/js/photoswipe-setup.js
- _scripts/photoswipe-setup.js
- /home/hxf0223/work/hxf0223.github.io/assets/js/photoswipe-setup.js
解决方式:删除 assets/js/photoswipe-setup.js,保留原始的 _scripts/photoswipe-setup.js 不变。
提交历史参考
| 日期 | Commit | 说明 |
|---|---|---|
| 2026-02-27 | 9d5407b | 更新个人信息和站点 URL |
| 2026-02-27 | 86b0695 | 初始化仓库 fork 配置 |
| 2026-02-27 | 59860f6 | 迁移博客文章 |
| 2026-02-27 | 3d120b6 | 添加 PWA manifest |
| 2026-02-27 | 5d5993f | 添加 Service Worker 和 theme-color |
| 2026-02-27 | 92c705e | 修复 manifest.json 尾部换行导致 JSON 无效 |
| 2026-02-28 | 4ad9c06 | 完成全部文章迁移(含中文文件名) |
| 2026-02-28 | c16ac46 | Tags/Categories 自动显示 |
| 2026-02-28 | aed979b | PWA 远程修复(SW 容错、manifest scope/id、CSP worker-src) |
| 2026-02-28 | 804f9b0 | 修复 SW fetch 拦截 chrome-extension 协议的错误 |
| 2026-03-09 | — | SW 缓存策略改为 Stale-While-Revalidate,解决页面需刷新才更新问题 |
| 2026-03-10 | — | 修复 blockquote 字号偏大问题,与正文保持一致 |
| 2026-03-25 | — | 切换代码语法高亮主题:亮色 github.light、暗色 gruvbox.dark |
| 2026-03-25 | — | 修复中文 tags/categories 点击后 URL 显示乱码问题 |
10. Blockquote(引用段落)字号修复
10.1. 问题
al-folio 默认的 blockquote 字号为 1.2rem,比正文(1rem)大,在文章中视觉上不协调。
10.2. 改动文件
_sass/_typography.scss — 将 blockquote 的 font-size 从 1.2rem 改为 1rem:
// 修改前
blockquote {
font-size: 1.2rem;
...
}
// 修改后
blockquote {
font-size: 1rem; // 与正文保持一致
...
}
提示: 若希望引用段落比正文略小,可设为
0.9rem,在视觉上形成层次感但不会突兀。
11. 代码语法高亮主题切换
11.1. 背景
al-folio 默认使用 Rouge 高亮引擎,亮色主题为 github,暗色主题为 native。github 主题中 C++ 关键字(if/class/int/void 等)被定义为 #000000(纯黑色),在白色背景上几乎没有颜色区分,函数名、类名虽有颜色但整体高亮效果较弱。
11.2. 主题选择
通过 rougify help style 枚举所有可用主题,并对比各主题对 C++ token 的颜色定义:
| 主题 | 关键字颜色 | 函数名颜色 | 类名颜色 | 背景色 |
|---|---|---|---|---|
| github(原) | #000000 黑色(无差异) | #990000 深红 | #445588 蓝 | 白色 |
| github.light | #cf222e 红色 | #8250df 紫色 | #953800 橙色 | #f6f8fa |
| gruvbox.dark | #fb4934 亮红 | #fabd2f 黄色 | #8ec07c 绿色 | #282828 |
最终选择:亮色用 github.light,暗色用 gruvbox.dark。
11.3. 改动文件
生成新主题 CSS:
rougify style github.light > assets/css/jekyll-pygments-themes-colorful.css
rougify style gruvbox.dark > assets/css/jekyll-pygments-themes-gruvbox-dark.css
assets/css/jekyll-pygments-themes-gruvbox-dark.css — 在文件顶部补充 .highlight pre 规则,确保暗色主题下 pre 元素背景正确:
.highlight pre {
background-color: #282828;
}
原因: Rouge CSS 通过
.highlight设置外层容器背景,但内层pre元素的背景由 SCSSvar(--global-code-bg-color)决定,CSS 特异性(0,1,1)低于高亮 CSS 的.highlight pre规则。若不显式覆盖,暗色主题切换时pre背景可能显示为浅色(#f6f8fa),导致文字不可见。
_includes/head.liquid — 更新两处 CSS 引用路径:
<!-- 亮色主题(原 github.css → 新 colorful.css,内容为 github.light 主题) -->
<link defer rel="stylesheet" href="/assets/css/jekyll-pygments-themes-colorful.css?v=5dad7fda5c0dfe7a1703926ed1cadb62" media="" id="highlight_theme_light" />
<!-- 暗色主题(原 native.css → 新 gruvbox-dark.css) -->
<link defer rel="stylesheet" href="/assets/css/jekyll-pygments-themes-gruvbox-dark.css?v=101562153d60e23aa1c6e016124088f1" media="none" id="highlight_theme_dark" />
两个 <link> 的 id 不变(highlight_theme_light / highlight_theme_dark),theme.js 通过这两个 id 动态切换 media 属性来实现亮暗色主题切换,不需要改动 JS 逻辑。
12. 修复中文 Tags/Categories URL 乱码
12.1. 问题
点击中文 tag 或 category 链接后,浏览器地址栏显示 percent-encoded 乱码,如 /blog/tag/%E5%B7%A5%E5%85%B7 而非 /blog/tag/工具。
12.2. 根本原因
Jekyll 的 relative_url 过滤器内部使用 Addressable::URI 解析 URL,会把路径中的中文字符 percent-encode 后写入 HTML href 属性。模板中大量使用了以下写法:
/blog/tag/
relative_url 再处理含中文的路径时,产生编码后的 URL。
12.3. 改动文件
修改 3 个文件共 8 处,将 relative_url 管道替换为直接字符串拼接:
{# 修改前 #}
/blog/tag/
{# 修改后 #}
/blog/tag/
修改的文件:
-
_pages/blog.md— 4 处(标签栏列表 2 处 + 文章列表每篇的 tag/category 链接 2 处) -
_layouts/post.liquid— 2 处(文章详情页的 tag/category 链接) -
_layouts/book-review.liquid— 2 处(书评页的 tag/category 链接)
说明: 中文字符放在 HTML
href属性中是合法的 HTML。浏览器发起请求时会自动编码,但地址栏显示为原始中文字符。site.baseurl在个人站点(baseurl为空)和 project site(baseurl: /repo-name)下均能正确工作。
Enjoy Reading This Article?
Here are some more articles you might like to read next: