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
  • 保留原始 titledatecategoriestags 字段

分两轮处理:

  1. 第一轮处理 112 个非中文文件名的文件
  2. 第二轮使用 git -c core.quotepath=false 处理 43 个中文文件名的文件

3. Tags/Categories 自动显示(_pages/blog.md)

问题

al-folio 默认使用 _config.yml 中的 display_tagsdisplay_categories 手动列表控制博客页面的标签过滤栏。新添加文章的 tags 不会自动出现。

解决方案

修改 _pages/blog.md,将手动列表替换为 Jekyll 自动收集的 site.tagssite.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>&bull;</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" }
  ]
}
  • idscope 是新版 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; // 有缓存则立即返回,同时后台更新
      });
    })
  );
});

踩坑记录:

  1. cache.addAll() 如果任何一个 URL 失败,整个 Promise 会 reject,导致 SW 安装失败 → 浏览器不显示安装按钮。必须添加 .catch() 容错
  2. Edge/Chrome 浏览器扩展的请求(chrome-extension://)也会被 SW 的 fetch 事件拦截,cache.put() 不支持非 HTTP scheme,导致 Uncaught TypeError。必须用 url.protocol.startsWith('http') 过滤
  3. cache.put() 也需要 .catch(() => {}) 兜底,防止其他异常场景
  4. 初版使用 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.jsonsw.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.mdcategories: [algorithm] 改为 [Algorithm],避免 Jekyll 路径冲突
禁用 external_sources 注释掉 _config.yml 中的 external_sources 配置,移除示例外部文章
默认 toc 设置 _config.ymldefaults 中为所有 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. 问题

  1. Markdown 中的图片按原始尺寸显示,大图会超出内容区宽度
  2. 无法点击图片在页面内单独放大查看

9.2. 解决方案

利用 al-folio 已内置的 medium-zoom 库(_config.ymlenable_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 — 将 blockquotefont-size1.2rem 改为 1rem

// 修改前
blockquote {
  font-size: 1.2rem;
  ...
}

// 修改后
blockquote {
  font-size: 1rem; // 与正文保持一致
  ...
}

提示: 若希望引用段落比正文略小,可设为 0.9rem,在视觉上形成层次感但不会突兀。


11. 代码语法高亮主题切换

11.1. 背景

al-folio 默认使用 Rouge 高亮引擎,亮色主题为 github,暗色主题为 nativegithub 主题中 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 元素的背景由 SCSS var(--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:

  • NVIDIA GPU 架构:SP、SM 与 LSU 工作原理详解
  • al-folio 本地部署记录(Ubuntu 24.04)
  • C++ Traits
  • 道格拉斯-普克算法(Douglas–Peucker algorithm)
  • CMake支持库收集