Compare commits
57
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e0167a8bb | ||
|
|
6abca9f412 | ||
|
|
c9fb46f5a9 | ||
|
|
edc34a8a8c | ||
|
|
83e0b863fc | ||
|
|
9df86cb05b | ||
|
|
d096d45c58 | ||
|
|
073e124344 | ||
|
|
d6e08a29f9 | ||
|
|
18ea5b17b8 | ||
|
|
d119d6b555 | ||
|
|
31fb52cb06 | ||
|
|
6061c16396 | ||
|
|
cfe508ff14 | ||
|
|
b8c5e1c86e | ||
|
|
907f64472a | ||
|
|
6293fe25d5 | ||
|
|
8d82aab213 | ||
|
|
029633f00f | ||
|
|
0ff4ede91f | ||
|
|
23303bd39a | ||
|
|
31eb44caee | ||
|
|
5c015c6577 | ||
|
|
138d3d976b | ||
|
|
d30b5decbb | ||
|
|
2526b98323 | ||
|
|
36643f5cf0 | ||
|
|
52188d5b12 | ||
|
|
fef2de1d37 | ||
|
|
bdaf02f9ca | ||
|
|
c7d1e7b975 | ||
|
|
550f6d15b4 | ||
|
|
d4fe273812 | ||
|
|
0b0307b12f | ||
|
|
3e377adc7f | ||
|
|
63c9c8ad4a | ||
|
|
8e74bead62 | ||
|
|
5bf0c327bf | ||
|
|
6538311387 | ||
|
|
cdd9c61641 | ||
|
|
e1fc8bd64c | ||
|
|
cc12e016ab | ||
|
|
ed473e996a | ||
|
|
c772236d1a | ||
|
|
a89c443c25 | ||
|
|
23b79a2473 | ||
|
|
8274180f81 | ||
|
|
3e09a362cc | ||
|
|
8debd99e61 | ||
|
|
be87917443 | ||
|
|
aaa9cd9274 | ||
|
|
2f7cbcf7bd | ||
|
|
6096a9dafe | ||
|
|
59eda420b3 | ||
|
|
740ec470fa | ||
|
|
3822e150a8 | ||
|
|
26bbb66a72 |
No files matched your search
@@ -0,0 +1,94 @@
|
||||
# Phase 5g:页面后台化(blocks 模型)与字体子集化 - 完成总结
|
||||
|
||||
## 交付结论
|
||||
- 结果:完成
|
||||
- 完成时间:2026-11-02 19:19
|
||||
|
||||
## 假设回顾
|
||||
### 已验证假设
|
||||
- 块渲染器受控,破版风险低于整页 HTML 直编(方案 10 §2 对比表)
|
||||
- 字体子集化(pyftsubset 按页实际用字)能把艺术字体单页增量压到 ≤150KB 阈值内
|
||||
|
||||
### 仍未完全验证的假设
|
||||
- 无
|
||||
|
||||
## 交付范围
|
||||
### 已交付
|
||||
- T1 blocks 骨架(关于页块式页面构建器)
|
||||
- T2 块型扩充与图片管线
|
||||
- T3 样式即数据(theme.yaml + 对比度闸门)
|
||||
- T4 首页细化与去重
|
||||
- T5 开源页收尾(缓存/回落/告警)
|
||||
- T6 文档与后台 hint 补齐
|
||||
- T7 字体子集化工具
|
||||
|
||||
### 未交付
|
||||
- 无
|
||||
|
||||
### 偏差说明
|
||||
- 无
|
||||
|
||||
## 简化决策
|
||||
### 保持简单的关键选择
|
||||
- 最简单可行方案:块类型先用 3 类跑通闭环(prose/tracks/divider),再扩到 9 类;theme 只做 12 个白名单旋钮
|
||||
- 暂不引入:可视化块预览、块级动画配置
|
||||
- 不做的抽象:通用页面 DSL(只服务现有页面)
|
||||
|
||||
### 本轮明确不做的内容
|
||||
- 换 CMS、页面布局的任意拖拽网格、运行时编辑器
|
||||
|
||||
## 变更边界
|
||||
### 本轮主要改动模块
|
||||
- 见 tasks.md 的 boundary 记录
|
||||
|
||||
### 明确未触碰的区域
|
||||
- 换 CMS、页面布局的任意拖拽网格、运行时编辑器
|
||||
|
||||
## 验证证据
|
||||
### 备注
|
||||
- 机器证据:runs.jsonl(见验证范围的机器验证率)
|
||||
|
||||
## 验证范围
|
||||
- 级别:package
|
||||
- 机器验证率:7/7
|
||||
- 规则:check 与 done 复用该范围;只有记录升级触发时才执行全项目检查
|
||||
|
||||
## 哲学生效证据
|
||||
### 行为成效回填
|
||||
- Development Record 行为门禁通过
|
||||
|
||||
### 一致性门禁回顾
|
||||
- 验收面(checklist 或成功标准)已通过
|
||||
|
||||
## Git 记录
|
||||
- 日期时间:2026-11-02 19:19
|
||||
- 范围:Phase 5g:页面后台化(blocks 模型)与字体子集化
|
||||
- 功能:T1 blocks 骨架(关于页块式页面构建器);T2 块型扩充与图片管线;T3 样式即数据(theme.yaml + 对比度闸门);T4 首页细化与去重;T5 开源页收尾(缓存/回落/告警);T6 文档与后台 hint 补齐;T7 字体子集化工具
|
||||
- 操作:提交 / 推送
|
||||
- 成效:Development Record 行为门禁通过
|
||||
- 提交:pending local commit
|
||||
- 提交区间:3822e150a87ac5fee56c9b2724eae23389e7c34e..23b79a2473674e75608736e2cac491b163a49c35
|
||||
- 推送:not-run (push stage follows commit)
|
||||
|
||||
## AGENTS.md 增量提议
|
||||
```diff
|
||||
Phase 5g 资产:①块式页面构建器(九类 discriminatedUnion + 宽松分支容错:已知块严格校验、未知块 WARN 丢弃不炸构建);②样式即数据(theme.yaml 九组白名单旋钮 + :root:root 特异性注入 + 产物级对比度闸门,负例 1.61:1 被拦);③首页抽 HomePage 消 zh/en 双份;④字体子集化工具(汇文 694→420KB −40%;快乐体实测反向 −9% 故不启用,结论入档避免后人重试);⑤config.yml 重复键结构缺陷的教训——Python yaml 静默覆盖,必须用 StrictLoader/Node yaml 校验,已固化为 build 闸门 config-check.py;⑥并发构建擦 dist 的解法:build→cp -a dist 快照→快照起服务跑 E2E。
|
||||
```
|
||||
|
||||
## 被拒绝的扩展提议
|
||||
- 无
|
||||
|
||||
## 门禁证据
|
||||
- check_spec_package.py exit 0
|
||||
|
||||
## 遗留事项
|
||||
- 无;问题处置见下方结构化区块
|
||||
|
||||
## 问题处置
|
||||
|
||||
```json
|
||||
{
|
||||
"issues": [],
|
||||
"version": 1
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,7 @@
|
||||
{"task": "task_0001", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"37/37 PASS\" && grep -q \"discriminatedUnion\" src/content.config.ts && grep -q \"BlockRenderer\" src/components/pages/AboutPage.astro && grep -q \"typeKey\" public/admin/config.yml", "exit": 0, "duration_s": 18.466, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "7a7e460dbaff3855c2da899e62a1489b19a1d25a4a91ecfb211fadec40bff343", "at": "2026-11-02T11:18:21+00:00"}
|
||||
{"task": "task_0002", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"37/37 PASS\" && ls src/components/blocks/ | grep -qC1 \"Cards\\|Timeline\" && grep -q \"image()\" src/content.config.ts", "exit": 0, "duration_s": 17.177, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "7a7e460dbaff3855c2da899e62a1489b19a1d25a4a91ecfb211fadec40bff343", "at": "2026-11-02T11:18:39+00:00"}
|
||||
{"task": "task_0003", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"37/37 PASS\" && test -f content/site/theme.yaml && node scripts/build-perf/contrast-check.mjs dist", "exit": 0, "duration_s": 17.193, "stdout_tail_sha256": "f66c66c7ce879dad3f789790f8607fbd50e7a5a894e12912a55fd47d082e4f77", "tree_fp": "7a7e460dbaff3855c2da899e62a1489b19a1d25a4a91ecfb211fadec40bff343", "at": "2026-11-02T11:18:56+00:00"}
|
||||
{"task": "task_0004", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"37/37 PASS\" && test -f src/components/pages/HomePage.astro && grep -q \"HomePage\" src/pages/en/index.astro", "exit": 0, "duration_s": 17.189, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "7a7e460dbaff3855c2da899e62a1489b19a1d25a4a91ecfb211fadec40bff343", "at": "2026-11-02T11:19:13+00:00"}
|
||||
{"task": "task_0005", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"37/37 PASS\" && python3 -c \"import json;d=json.load(open('src/data/projects-fallback.json'));assert len(d)>=5\"", "exit": 0, "duration_s": 17.214, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "7a7e460dbaff3855c2da899e62a1489b19a1d25a4a91ecfb211fadec40bff343", "at": "2026-11-02T11:19:30+00:00"}
|
||||
{"task": "task_0006", "cmd": "test -f docs/管理台.md && grep -q \"blocks\\|区块\" docs/管理台.md && grep -c \"hint\" public/admin/config.yml | awk '$1>=20'", "exit": 0, "duration_s": 0.004, "stdout_tail_sha256": "913f5d1da2feaf4deeccc9e55cbb350a20f12b3f507e87be85dbb77fdd3cb9bc", "tree_fp": "7a7e460dbaff3855c2da899e62a1489b19a1d25a4a91ecfb211fadec40bff343", "at": "2026-11-02T11:19:30+00:00"}
|
||||
{"task": "task_0007", "cmd": "test -f scripts/build-perf/subset-fonts.py && python3 -c \"import fontTools; print('fonttools ok')\" && E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"37/37 PASS\"", "exit": 0, "duration_s": 17.19, "stdout_tail_sha256": "3bcefe2ef2e30a5bbe067d31368a9765d7843d0cc24738a60c946c020e581348", "tree_fp": "7a7e460dbaff3855c2da899e62a1489b19a1d25a4a91ecfb211fadec40bff343", "at": "2026-11-02T11:19:47+00:00"}
|
||||
@@ -0,0 +1,84 @@
|
||||
# Phase 5g:页面后台化(blocks 模型)与字体子集化 - 项目范围
|
||||
|
||||
<!-- spec-format: 2 -->
|
||||
|
||||
## 1. 问题定义
|
||||
- **项目目标**:①按 docs/research/10 方案 E 把关于页改造成「块式页面构建器」(结构可增删排序)②样式即数据(theme.yaml → 构建期 CSS 变量,含对比度闸门)③首页字段细化并抽 HomePage 消 zh/en 双份 ④开源页收尾(README 缓存/回落/告警)⑤后台文档与 hint 补齐 ⑥字体子集化工具(把艺术字体单页增量从数百 KB 降到数十 KB)
|
||||
- **目标用户**:站长(希望整页可编辑、能上更多艺术字体)与读者(版式与可读性不退化)
|
||||
- **核心价值**:内容与结构的编辑权交回后台,同时守住灰阶体系/双语/SEO/构建期零运行时四条红线
|
||||
|
||||
## 2. 假设与待确认
|
||||
|
||||
### 2.1 已确认事实
|
||||
- Sveltia 0.252 支持 list 的 types + typeKey(方案 10 §3.4 从本仓 bundle 取证),块模型可行
|
||||
- 页面文案 markdown 净化(D1/D2)已在 2cab243 落地(P0 完成)
|
||||
- 字体实测:@fontsource CJK 切片粒度导致单页 +163KB~+8605KB;现有 6 自托管族为基线
|
||||
|
||||
### 2.2 关键假设
|
||||
- 块渲染器受控,破版风险低于整页 HTML 直编(方案 10 §2 对比表)
|
||||
- 字体子集化(pyftsubset 按页实际用字)能把艺术字体单页增量压到 ≤150KB 阈值内
|
||||
|
||||
### 2.3 待确认问题
|
||||
- [x] 关于页整页可编辑路线 → 用户 2026-11-02 批准方案 E(blocks + theme.yaml + 局部自由区)
|
||||
- [x] 字体扩充路线 → 先落 6 族,其余按子集化工具就绪后再评估(本包 T7 交付工具)
|
||||
|
||||
### 2.4 可选解释与取舍
|
||||
- 关于页保留 intro markdown 通道作为 compat(blocks 为空时三级回落:blocks → intro → 默认文案)
|
||||
- 样式即数据只开放白名单旋钮(约 12 项),不开放整份 tokens.css
|
||||
- 图片只走 Astro image() 管线,禁止块内填外链/绝对路径(schema 层拦)
|
||||
|
||||
### 2.5 已证伪路径
|
||||
- 整页富文本直编(C 方案):XSS 面大、无设计兜底,弃
|
||||
- 新增 webfont 直接上(不子集化):单页 +535KB~+8605KB,超阈值,弃(改走 T7 工具)
|
||||
|
||||
## 3. 功能范围
|
||||
|
||||
### 3.1 核心功能(MVP)
|
||||
- [x] T1 blocks 骨架:schema(discriminatedUnion)+ page-blocks.ts + BlockRenderer + prose/tracks/divider 三块 + 后台 types 声明
|
||||
- [x] T2 块型扩充:cards/timeline/quote/figure/linklist/cta + 图标键白名单 + 图片 image() 管线
|
||||
- [x] T3 样式即数据:theme.yaml + theme.ts + BaseLayout 内联变量 + 对比度闸门脚本
|
||||
- [x] T4 首页细化与去重:抽 HomePage.astro 消 zh/en 双份 + 字段细化(projectsLimit/announcement 等)
|
||||
- [x] T5 开源页收尾:README 缓存策略/超时/回落矩阵 + fallback JSON 回填 + include 未命中 WARN
|
||||
- [x] T6 文档与 hint:docs/管理台.md(块编辑用法)+ 写作指南扩充 + admin 全字段中文 hint
|
||||
- [x] T7 字体子集化工具:按页用字裁剪艺术字体为小切片 + build.sh 守卫挂载 + 实测对比
|
||||
|
||||
### 3.2 扩展功能
|
||||
- 无
|
||||
|
||||
### 3.3 不在范围内
|
||||
- 换 CMS、页面布局的任意拖拽网格、运行时编辑器
|
||||
|
||||
## 4. 最小实现路径
|
||||
- 最简单可行方案:块类型先用 3 类跑通闭环(prose/tracks/divider),再扩到 9 类;theme 只做 12 个白名单旋钮
|
||||
- 暂不引入:可视化块预览、块级动画配置
|
||||
- 不做的抽象:通用页面 DSL(只服务现有页面)
|
||||
|
||||
## 5. 技术决策
|
||||
- 技术栈:Astro 7.4.4 + zod discriminatedUnion + Sveltia list/types + pyftsubset(fonttools)
|
||||
- 证据协议:evidence: runs
|
||||
- 本轮允许改动:src/content.config.ts、src/lib/{page-blocks,theme,site-content}.ts、src/components/blocks/*、src/components/pages/{AboutPage,HomePage}.astro、src/pages/{index,en/index,opensource,en/opensource}.astro、src/layouts/BaseLayout.astro、public/admin/*、content/site/*.yaml、scripts/build-perf/*、infra/deploy/build.sh、docs/*、tests/e2e.mjs
|
||||
- 本轮不应触碰:nginx/ESA、图库灯箱(GalleryPage/PhotoZoom)、Header/Footer 结构
|
||||
- Git integration branch:`spec/2026-11-02_blog-5g-pages`
|
||||
|
||||
### 5.1 验证策略
|
||||
- 范围级别:package
|
||||
- 变更对象:页面数据模型与渲染器、后台配置、样式变量生成、字体产物、文档
|
||||
- 快速检查:`pnpm astro build`(node26)
|
||||
- 集成检查:`node tests/e2e.mjs`(37 项,本包预期新增块/主题断言)+ content-lint 0/0/0
|
||||
- 全项目检查:字体体积账(三页增量)+ 视觉复核(glm-5.3-flash:max,块版式与主题变量双态);仅在构建链或 BaseLayout 结构再变更时升级范围
|
||||
- 升级触发:若 theme 变量波及全局排版(字体字号全站变化)或 blocks 改动触及首页/文章页结构,升级为全站视觉复核与 E2E 全量并记录理由
|
||||
|
||||
## 6. 成功标准与验证方式
|
||||
- [x] 关于页整页由 yaml blocks 驱动,增删排序生效 -> verify: task_0001
|
||||
- [x] 九类块全部可渲染且旧内容零回归(三级回落) -> verify: task_0002
|
||||
- [x] theme.yaml 改值 → 产物 CSS 变量变化,对比度闸门拦截不达标 -> verify: task_0003
|
||||
- [x] 首页 zh/en 由同一组件渲染且字段可配 -> verify: task_0004
|
||||
- [x] 开源页 README 失败矩阵与 fallback 告警可用 -> verify: task_0005
|
||||
- [x] 后台文档与 hint 完整 -> verify: task_0006
|
||||
- [x] 字体子集化工具把指定字体单页增量压到 ≤150KB -> verify: task_0007
|
||||
|
||||
## 7. 风险与约束
|
||||
- 灰阶体系与双语红线不变;彩色仅正文强调
|
||||
- 块渲染器未知类型跳过 + WARN,绝不炸构建
|
||||
- 字体子集化需保证字形覆盖(裁错字会出现豆腐块)→ 断言:子集后页面文本逐字命中
|
||||
- 多车道并行:共享文件(content.config.ts/admin config/docs)按 boundary 划界,tokens/global 由 orchestrator 或指定单车道
|
||||
@@ -0,0 +1,42 @@
|
||||
# Phase 5g:页面后台化(blocks 模型)与字体子集化 - 任务拆解
|
||||
|
||||
## 使用规则
|
||||
- boundary/verify 齐备;verify 只读断言;车道工作树开发禁 git,收编由 orchestrator 统一提交
|
||||
- 构建一律 `export PATH="$HOME/.local/node/bin:$PATH"`(Node 26)
|
||||
|
||||
## 阶段一:实现
|
||||
|
||||
- [x] T1 blocks 骨架(关于页块式页面构建器)
|
||||
- claim: op-gitea
|
||||
- boundary: src/content.config.ts(siteAbout 加 blocks: z.array(discriminatedUnion))、新增 src/lib/page-blocks.ts(getAboutBlocks:过滤 enabled、未知 type 跳过+WARN、文本 pickBi 回落、块数≤24/单块≤4000 字)、新增 src/components/blocks/{BlockRenderer,Prose,Tracks,Divider}.astro、AboutPage.astro 改由 blocks 驱动(blocks 空→intro markdown→默认文案三级回落)、public/admin/config.yml 加 list+types(typeKey: type)
|
||||
- verify: `E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q "37/37 PASS" && grep -q "discriminatedUnion" src/content.config.ts && grep -q "BlockRenderer" src/components/pages/AboutPage.astro && grep -q "typeKey" public/admin/config.yml`
|
||||
|
||||
- [x] T2 块型扩充与图片管线
|
||||
- claim: op-gitea
|
||||
- boundary: 新增 src/components/blocks/{Cards,Timeline,Quote,Figure,LinkList,Cta}.astro + 图标键白名单(复用 SocialLinks 注册表或新建 icons 注册表)、schema 块型补全(图片字段用 image(),禁止外链字符串)、content/site/assets/ 目录、near docs 图表说明
|
||||
- verify: `E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q "37/37 PASS" && ls src/components/blocks/ | grep -qC1 "Cards\|Timeline" && grep -q "image()" src/content.config.ts`
|
||||
|
||||
- [x] T3 样式即数据(theme.yaml + 对比度闸门)
|
||||
- claim: op-artalk
|
||||
- boundary: 新增 content/site/theme.yaml(12 个白名单旋钮:prose-size/measure/radius/accent/leading 等)、新增 src/lib/theme.ts(getTheme + themeVarsCss 生成 :root:root{…})、BaseLayout.astro head 内联 theme 变量(构建期静态)、新增 scripts/build-perf/contrast-check.mjs(断言 theme 结果下正文对比度 ≥4.5)、public/admin/config.yml 加 theme 单例、build.sh 守卫挂载
|
||||
- verify: `E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q "37/37 PASS" && test -f content/site/theme.yaml && node scripts/build-perf/contrast-check.mjs dist`
|
||||
|
||||
- [x] T4 首页细化与去重
|
||||
- claim: op-artalk
|
||||
- boundary: 新增 src/components/pages/HomePage.astro(承接现 index.astro 区块逻辑,locale prop)、src/pages/{index,en/index}.astro 改为薄壳、home.yaml 增 projectsLimit / announcement(markdown)/ postsTracks(轨开关细分),site-content 访问器同步
|
||||
- verify: `E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q "37/37 PASS" && test -f src/components/pages/HomePage.astro && grep -q "HomePage" src/pages/en/index.astro`
|
||||
|
||||
- [x] T5 开源页收尾(缓存/回落/告警)
|
||||
- claim: worker-one
|
||||
- boundary: src/lib/site-content.ts(README 缓存策略与超时、回落矩阵)、content/.github-readme.md 与 README HTML 剥离策略(复用 rehype-content-sanitize 已挡危险标签;补: 外链徽章不阻断)、src/data/projects-fallback.json 回填 5-8 条真实仓、projects.ts include 未命中 console.warn
|
||||
- verify: `E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q "37/37 PASS" && python3 -c "import json;d=json.load(open('src/data/projects-fallback.json'));assert len(d)>=5"`
|
||||
|
||||
- [x] T6 文档与后台 hint 补齐
|
||||
- claim: worker-one
|
||||
- boundary: docs/管理台.md(块编辑怎么用、增删排序、预览限制)、docs/写作指南.md(块字段表/受限类/主题项)、public/admin/config.yml 全字段中文 hint(含 T1-T4 新增字段)
|
||||
- verify: `test -f docs/管理台.md && grep -q "blocks\|区块" docs/管理台.md && grep -c "hint" public/admin/config.yml | awk '$1>=20'`
|
||||
|
||||
- [x] T7 字体子集化工具
|
||||
- claim: orchestrator
|
||||
- boundary: 新增 scripts/build-perf/subset-fonts.py(pyftsubset 按指定字体 + 页面实际用字生成子集 woff2 + 重写 fonts.css 指向子集文件)、infra/deploy/build.sh 守卫挂载、docs/写作指南加字流程补"子集化"一节
|
||||
- verify: `test -f scripts/build-perf/subset-fonts.py && python3 -c "import fontTools; print('fonttools ok')" && E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q "37/37 PASS"`
|
||||
@@ -0,0 +1,95 @@
|
||||
# Phase 5h:内容表达能力与社交反馈 - 完成总结
|
||||
|
||||
## 交付结论
|
||||
- 结果:完成
|
||||
- 完成时间:2026-11-02 21:16
|
||||
|
||||
## 假设回顾
|
||||
### 已验证假设
|
||||
- 瞬间富文本走与全站文案同一条净化管线即可满足「字体/颜色可换」且不引入 XSS 面(危险标签整棵丢弃、协议白名单 http/https/mailto)
|
||||
- 每条瞬间一个 Artalk page_key(`/moments/<slug>/`)不会与页面级 pv 冲突;Artalk 实例按需挂载(展开才 init)可避免 "one page one Artalk" 的全局实例冲突
|
||||
- 开源页可用本地数据(已有 repo 元数据 + GitHub GraphQL 拉取)自绘 followers/stars/语言占比,无需任何第三方徽章服务
|
||||
|
||||
### 仍未完全验证的假设
|
||||
- 无
|
||||
|
||||
## 交付范围
|
||||
### 已交付
|
||||
- T1 文章置顶(契约 + 排序 + 标记 + 后台)
|
||||
- T2 瞬间富文本(字体可换 / 颜色可换 / 多形式)
|
||||
- T3 瞬间逐条评论(Artalk 独立 page_key + 折叠懒挂载)
|
||||
- T4 计数徽标(眼睛图标 = 浏览数,气泡图标 = 评论数)
|
||||
- T5 开源页重构(原生 GitHub 档案卡,去外链图依赖)
|
||||
- T6 关于页承接 README 的个人叙述
|
||||
- T7 写作模板示例文章 + 文档与 hint 收尾
|
||||
|
||||
### 未交付
|
||||
- 无
|
||||
|
||||
### 偏差说明
|
||||
- 无
|
||||
|
||||
## 简化决策
|
||||
### 保持简单的关键选择
|
||||
- 最简单可行方案:置顶先用 boolean + 排序键;瞬间富文本直接把 `text` 交给既有 `renderRich`;评论用现有 Artalk 组件克隆一份带折叠的;计数徽标只做 pv + count 两个数
|
||||
- 暂不引入:置顶权重/多级置顶、瞬间的图片轮播、评论楼中楼定制、开源页的实时 Star 曲线
|
||||
- 不做的抽象:通用「内容社交层」框架(只服务现有页面)
|
||||
|
||||
### 本轮明确不做的内容
|
||||
- 评论回复通知/邮件推送、点赞/投票、阅读进度、站点级总访问量统计(Artalk 无匿名聚合端点)
|
||||
|
||||
## 变更边界
|
||||
### 本轮主要改动模块
|
||||
- 见 tasks.md 的 boundary 记录
|
||||
|
||||
### 明确未触碰的区域
|
||||
- 评论回复通知/邮件推送、点赞/投票、阅读进度、站点级总访问量统计(Artalk 无匿名聚合端点)
|
||||
|
||||
## 验证证据
|
||||
### 备注
|
||||
- 机器证据:runs.jsonl(见验证范围的机器验证率)
|
||||
|
||||
## 验证范围
|
||||
- 级别:package
|
||||
- 机器验证率:7/7
|
||||
- 规则:check 与 done 复用该范围;只有记录升级触发时才执行全项目检查
|
||||
|
||||
## 哲学生效证据
|
||||
### 行为成效回填
|
||||
- Development Record 行为门禁通过
|
||||
|
||||
### 一致性门禁回顾
|
||||
- 验收面(checklist 或成功标准)已通过
|
||||
|
||||
## Git 记录
|
||||
- 日期时间:2026-11-02 21:16
|
||||
- 范围:Phase 5h:内容表达能力与社交反馈
|
||||
- 功能:T1 文章置顶(契约 + 排序 + 标记 + 后台);T2 瞬间富文本(字体可换 / 颜色可换 / 多形式);T3 瞬间逐条评论(Artalk 独立 page_key + 折叠懒挂载);T4 计数徽标(眼睛图标 = 浏览数,气泡图标 = 评论数);T5 开源页重构(原生 GitHub 档案卡,去外链图依赖);T6 关于页承接 README 的个人叙述;T7 写作模板示例文章 + 文档与 hint 收尾
|
||||
- 操作:提交 / 推送
|
||||
- 成效:Development Record 行为门禁通过
|
||||
- 提交:pending local commit
|
||||
- 提交区间:ed473e996a3707f98015a40e4388aa4fa58b79ac..bdaf02f9cae774e2a094eb716a95be3f0eddab56
|
||||
- 推送:not-run (push stage follows commit)
|
||||
|
||||
## AGENTS.md 增量提议
|
||||
```diff
|
||||
Phase 5h 资产:①置顶契约(sortPosts 统一四处排序,只改顺序不动 pubDate/URL/RSS)②瞬间富文本(复用 renderRich 净化链,危险标签整棵丢弃)③瞬间逐条评论(展开才挂 Artalk;评论键用虚拟路径 /moments/<slug>/——实测 # 在查询串会被截断成页面级键,导致所有瞬间共用一个评论流)④计数徽标 ViewMeta(眼睛/气泡、零第三方图片;修同载双计与同页数字差 1 两个自伤 bug,口径=「请求次数对」不等于「展示自洽」)⑤开源页去 README 外链依赖(7 类第三方图与 align 居中失效是「没居中/图片不显示」的根因)⑥灰阶纪律清剿:项目墙 15 个彩色语言点、并列 36% 单着色、全局 .tap 泄漏致卡片居中 ⑦视觉验收新增两条可复用断言法:Range.getClientRects 判折行、scrollWidth==innerWidth 判横向溢出。
|
||||
```
|
||||
|
||||
## 被拒绝的扩展提议
|
||||
- 无
|
||||
|
||||
## 门禁证据
|
||||
- check_spec_package.py exit 0
|
||||
|
||||
## 遗留事项
|
||||
- 无;问题处置见下方结构化区块
|
||||
|
||||
## 问题处置
|
||||
|
||||
```json
|
||||
{
|
||||
"issues": [],
|
||||
"version": 1
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,28 @@
|
||||
{"task": "task_0001", "cmd": "E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE \"汇总: [0-9]+/[0-9]+ PASS\" && grep -q \"pinned\" src/content.config.ts && grep -q \"pinned\" src/components/PostList.astro && grep -q \"pinned\" public/admin/config.yml", "exit": 0, "duration_s": 21.239, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "a4c5572a52c4f546900c03b86c7cfd32056ad32ec27194018ffc175ac700f3c5", "at": "2026-11-02T12:58:45+00:00"}
|
||||
{"task": "task_0002", "cmd": "E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE \"汇总: [0-9]+/[0-9]+ PASS\" && grep -qE \"renderRich|renderMarkdown\" src/components/pages/MomentsPage.astro && python3 -c \"import yaml;d=yaml.safe_load(open('public/admin/config.yml'));m=[c for c in d['collections'] if c['name']=='moments'][0];t=[f for f in m['fields'] if f['name']=='text'][0];assert t['widget']=='markdown',t\"", "exit": 0, "duration_s": 21.893, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "a4c5572a52c4f546900c03b86c7cfd32056ad32ec27194018ffc175ac700f3c5", "at": "2026-11-02T12:59:07+00:00"}
|
||||
{"task": "task_0003", "cmd": "test -f src/components/MomentComments.astro && grep -q \"MomentComments\" src/components/pages/MomentsPage.astro && grep -q \"moments/#\" src/components/MomentComments.astro && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE \"汇总: [0-9]+/[0-9]+ PASS\"", "exit": 0, "duration_s": 21.073, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "a4c5572a52c4f546900c03b86c7cfd32056ad32ec27194018ffc175ac700f3c5", "at": "2026-11-02T12:59:28+00:00"}
|
||||
{"task": "task_0004", "cmd": "test -f src/components/geek/ViewMeta.astro && grep -q \"ViewMeta\" src/components/ArtalkComments.astro && grep -q \"ViewMeta\" src/pages/archives/[...slug].astro && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE \"汇总: [0-9]+/[0-9]+ PASS\"", "exit": 0, "duration_s": 23.015, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "a4c5572a52c4f546900c03b86c7cfd32056ad32ec27194018ffc175ac700f3c5", "at": "2026-11-02T12:59:51+00:00"}
|
||||
{"task": "task_0005", "cmd": "grep -q \"GithubProfile\" src/pages/opensource.astro && python3 -c \"import re,pathlib;p=pathlib.Path('dist/opensource/index.html').read_text();bad=[u for u in ['img.shields.io','readme-typing-svg','github-readme-stats','streak-stats','komarev','github-readme-activity-graph'] if u in p];assert not bad,bad\"", "exit": 0, "duration_s": 0.03, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "a4c5572a52c4f546900c03b86c7cfd32056ad32ec27194018ffc175ac700f3c5", "at": "2026-11-02T12:59:52+00:00"}
|
||||
{"task": "task_0006", "cmd": "python3 -c \"import yaml;d=yaml.safe_load(open('content/site/about.yaml'));types=[b['type'] for b in d.get('blocks') or []];assert 'prose' in types and ('linklist' in types or 'cards' in types),types\" && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE \"汇总: [0-9]+/[0-9]+ PASS\"", "exit": 0, "duration_s": 21.067, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "a4c5572a52c4f546900c03b86c7cfd32056ad32ec27194018ffc175ac700f3c5", "at": "2026-11-02T13:00:13+00:00"}
|
||||
{"task": "task_0007", "cmd": "test -f content/posts/writing-template.md && test -f content/posts-en/writing-template.md && grep -q \"pinned: true\" content/posts/writing-template.md && grep -qE \"font-kai|c-blue\" content/posts/writing-template.md && grep -q \"写作模板\" docs/写作指南.md", "exit": 0, "duration_s": 0.005, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "a4c5572a52c4f546900c03b86c7cfd32056ad32ec27194018ffc175ac700f3c5", "at": "2026-11-02T13:00:13+00:00"}
|
||||
{"task": "task_0001", "cmd": "E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE \"汇总: [0-9]+/[0-9]+ PASS\" && grep -q \"pinned\" src/content.config.ts && grep -q \"pinned\" src/components/PostList.astro && grep -q \"pinned\" public/admin/config.yml", "exit": 0, "duration_s": 20.732, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "f35452f48bd7d96babf34565aeef8d17c25191be02e07d93fd27922795604348", "at": "2026-11-02T13:04:09+00:00"}
|
||||
{"task": "task_0002", "cmd": "E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE \"汇总: [0-9]+/[0-9]+ PASS\" && grep -qE \"renderRich|renderMarkdown\" src/components/pages/MomentsPage.astro && python3 -c \"import yaml;d=yaml.safe_load(open('public/admin/config.yml'));m=[c for c in d['collections'] if c['name']=='moments'][0];t=[f for f in m['fields'] if f['name']=='text'][0];assert t['widget']=='markdown',t\"", "exit": 0, "duration_s": 20.695, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "f35452f48bd7d96babf34565aeef8d17c25191be02e07d93fd27922795604348", "at": "2026-11-02T13:04:30+00:00"}
|
||||
{"task": "task_0003", "cmd": "test -f src/components/MomentComments.astro && grep -q \"MomentComments\" src/components/pages/MomentsPage.astro && grep -q 'moments/${slug}/' src/components/MomentComments.astro && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE \"汇总: [0-9]+/[0-9]+ PASS\"", "exit": 0, "duration_s": 20.501, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "f35452f48bd7d96babf34565aeef8d17c25191be02e07d93fd27922795604348", "at": "2026-11-02T13:04:50+00:00"}
|
||||
{"task": "task_0004", "cmd": "test -f src/components/geek/ViewMeta.astro && grep -q \"ViewMeta\" src/components/ArtalkComments.astro && grep -q \"ViewMeta\" src/pages/archives/[...slug].astro && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE \"汇总: [0-9]+/[0-9]+ PASS\"", "exit": 0, "duration_s": 20.83, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "f35452f48bd7d96babf34565aeef8d17c25191be02e07d93fd27922795604348", "at": "2026-11-02T13:05:11+00:00"}
|
||||
{"task": "task_0005", "cmd": "grep -q \"GithubProfile\" src/pages/opensource.astro && python3 -c \"import re,pathlib;p=pathlib.Path('dist/opensource/index.html').read_text();bad=[u for u in ['img.shields.io','readme-typing-svg','github-readme-stats','streak-stats','komarev','github-readme-activity-graph'] if u in p];assert not bad,bad\"", "exit": 0, "duration_s": 0.029, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "f35452f48bd7d96babf34565aeef8d17c25191be02e07d93fd27922795604348", "at": "2026-11-02T13:05:11+00:00"}
|
||||
{"task": "task_0006", "cmd": "python3 -c \"import yaml;d=yaml.safe_load(open('content/site/about.yaml'));types=[b['type'] for b in d.get('blocks') or []];assert 'prose' in types and ('linklist' in types or 'cards' in types),types\" && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE \"汇总: [0-9]+/[0-9]+ PASS\"", "exit": 0, "duration_s": 21.719, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "f35452f48bd7d96babf34565aeef8d17c25191be02e07d93fd27922795604348", "at": "2026-11-02T13:05:33+00:00"}
|
||||
{"task": "task_0007", "cmd": "test -f content/posts/writing-template.md && test -f content/posts-en/writing-template.md && grep -q \"pinned: true\" content/posts/writing-template.md && grep -qE \"font-kai|c-blue\" content/posts/writing-template.md && grep -q \"写作模板\" docs/写作指南.md", "exit": 0, "duration_s": 0.005, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "f35452f48bd7d96babf34565aeef8d17c25191be02e07d93fd27922795604348", "at": "2026-11-02T13:05:33+00:00"}
|
||||
{"task": "task_0001", "cmd": "E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE \"汇总: [0-9]+/[0-9]+ PASS\" && grep -q \"pinned\" src/content.config.ts && grep -q \"pinned\" src/components/PostList.astro && grep -q \"pinned\" public/admin/config.yml", "exit": 0, "duration_s": 21.074, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "62086ce4cccd8c576913526a7ae517471278660c8e6cc6d7e24f9ea632038195", "at": "2026-11-02T13:12:48+00:00"}
|
||||
{"task": "task_0002", "cmd": "E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE \"汇总: [0-9]+/[0-9]+ PASS\" && grep -qE \"renderRich|renderMarkdown\" src/components/pages/MomentsPage.astro && python3 -c \"import yaml;d=yaml.safe_load(open('public/admin/config.yml'));m=[c for c in d['collections'] if c['name']=='moments'][0];t=[f for f in m['fields'] if f['name']=='text'][0];assert t['widget']=='markdown',t\"", "exit": 0, "duration_s": 20.656, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "62086ce4cccd8c576913526a7ae517471278660c8e6cc6d7e24f9ea632038195", "at": "2026-11-02T13:13:09+00:00"}
|
||||
{"task": "task_0003", "cmd": "test -f src/components/MomentComments.astro && grep -q \"MomentComments\" src/components/pages/MomentsPage.astro && grep -q 'moments/${slug}/' src/components/MomentComments.astro && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE \"汇总: [0-9]+/[0-9]+ PASS\"", "exit": 0, "duration_s": 20.386, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "62086ce4cccd8c576913526a7ae517471278660c8e6cc6d7e24f9ea632038195", "at": "2026-11-02T13:13:29+00:00"}
|
||||
{"task": "task_0004", "cmd": "test -f src/components/geek/ViewMeta.astro && grep -q \"ViewMeta\" src/components/ArtalkComments.astro && grep -q \"ViewMeta\" src/pages/archives/[...slug].astro && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE \"汇总: [0-9]+/[0-9]+ PASS\"", "exit": 0, "duration_s": 20.357, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "62086ce4cccd8c576913526a7ae517471278660c8e6cc6d7e24f9ea632038195", "at": "2026-11-02T13:13:49+00:00"}
|
||||
{"task": "task_0005", "cmd": "grep -q \"GithubProfile\" src/pages/opensource.astro && python3 -c \"import re,pathlib;p=pathlib.Path('dist/opensource/index.html').read_text();bad=[u for u in ['img.shields.io','readme-typing-svg','github-readme-stats','streak-stats','komarev','github-readme-activity-graph'] if u in p];assert not bad,bad\"", "exit": 0, "duration_s": 0.029, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "62086ce4cccd8c576913526a7ae517471278660c8e6cc6d7e24f9ea632038195", "at": "2026-11-02T13:13:49+00:00"}
|
||||
{"task": "task_0006", "cmd": "python3 -c \"import yaml;d=yaml.safe_load(open('content/site/about.yaml'));types=[b['type'] for b in d.get('blocks') or []];assert 'prose' in types and ('linklist' in types or 'cards' in types),types\" && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE \"汇总: [0-9]+/[0-9]+ PASS\"", "exit": 0, "duration_s": 21.589, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "62086ce4cccd8c576913526a7ae517471278660c8e6cc6d7e24f9ea632038195", "at": "2026-11-02T13:14:11+00:00"}
|
||||
{"task": "task_0007", "cmd": "test -f content/posts/writing-template.md && test -f content/posts-en/writing-template.md && grep -q \"pinned: true\" content/posts/writing-template.md && grep -qE \"font-kai|c-blue\" content/posts/writing-template.md && grep -q \"写作模板\" docs/写作指南.md", "exit": 0, "duration_s": 0.005, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "62086ce4cccd8c576913526a7ae517471278660c8e6cc6d7e24f9ea632038195", "at": "2026-11-02T13:14:11+00:00"}
|
||||
{"task": "task_0001", "cmd": "E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE \"汇总: [0-9]+/[0-9]+ PASS\" && grep -q \"pinned\" src/content.config.ts && grep -q \"pinned\" src/components/PostList.astro && grep -q \"pinned\" public/admin/config.yml", "exit": 0, "duration_s": 20.396, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "4af92016715e63fb205156cc420c1d1a9f264ede8fd6bb358ffc8099741b2cef", "at": "2026-11-02T13:14:57+00:00"}
|
||||
{"task": "task_0002", "cmd": "E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE \"汇总: [0-9]+/[0-9]+ PASS\" && grep -qE \"renderRich|renderMarkdown\" src/components/pages/MomentsPage.astro && python3 -c \"import yaml;d=yaml.safe_load(open('public/admin/config.yml'));m=[c for c in d['collections'] if c['name']=='moments'][0];t=[f for f in m['fields'] if f['name']=='text'][0];assert t['widget']=='markdown',t\"", "exit": 0, "duration_s": 21.377, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "4af92016715e63fb205156cc420c1d1a9f264ede8fd6bb358ffc8099741b2cef", "at": "2026-11-02T13:15:19+00:00"}
|
||||
{"task": "task_0003", "cmd": "test -f src/components/MomentComments.astro && grep -q \"MomentComments\" src/components/pages/MomentsPage.astro && grep -q 'moments/${slug}/' src/components/MomentComments.astro && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE \"汇总: [0-9]+/[0-9]+ PASS\"", "exit": 0, "duration_s": 20.908, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "4af92016715e63fb205156cc420c1d1a9f264ede8fd6bb358ffc8099741b2cef", "at": "2026-11-02T13:15:40+00:00"}
|
||||
{"task": "task_0004", "cmd": "test -f src/components/geek/ViewMeta.astro && grep -q \"ViewMeta\" src/components/ArtalkComments.astro && grep -q \"ViewMeta\" src/pages/archives/[...slug].astro && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE \"汇总: [0-9]+/[0-9]+ PASS\"", "exit": 0, "duration_s": 21.893, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "4af92016715e63fb205156cc420c1d1a9f264ede8fd6bb358ffc8099741b2cef", "at": "2026-11-02T13:16:02+00:00"}
|
||||
{"task": "task_0005", "cmd": "grep -q \"GithubProfile\" src/pages/opensource.astro && python3 -c \"import re,pathlib;p=pathlib.Path('dist/opensource/index.html').read_text();bad=[u for u in ['img.shields.io','readme-typing-svg','github-readme-stats','streak-stats','komarev','github-readme-activity-graph'] if u in p];assert not bad,bad\"", "exit": 0, "duration_s": 0.029, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "4af92016715e63fb205156cc420c1d1a9f264ede8fd6bb358ffc8099741b2cef", "at": "2026-11-02T13:16:02+00:00"}
|
||||
{"task": "task_0006", "cmd": "python3 -c \"import yaml;d=yaml.safe_load(open('content/site/about.yaml'));types=[b['type'] for b in d.get('blocks') or []];assert 'prose' in types and ('linklist' in types or 'cards' in types),types\" && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE \"汇总: [0-9]+/[0-9]+ PASS\"", "exit": 0, "duration_s": 20.399, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "4af92016715e63fb205156cc420c1d1a9f264ede8fd6bb358ffc8099741b2cef", "at": "2026-11-02T13:16:22+00:00"}
|
||||
{"task": "task_0007", "cmd": "test -f content/posts/writing-template.md && test -f content/posts-en/writing-template.md && grep -q \"pinned: true\" content/posts/writing-template.md && grep -qE \"font-kai|c-blue\" content/posts/writing-template.md && grep -q \"写作模板\" docs/写作指南.md", "exit": 0, "duration_s": 0.005, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "4af92016715e63fb205156cc420c1d1a9f264ede8fd6bb358ffc8099741b2cef", "at": "2026-11-02T13:16:22+00:00"}
|
||||
@@ -0,0 +1,91 @@
|
||||
# Phase 5h:内容表达能力与社交反馈 - 项目范围
|
||||
|
||||
<!-- spec-format: 2 -->
|
||||
|
||||
## 1. 问题定义
|
||||
- **项目目标**:①文章支持置顶(作者指定优先)②瞬间的文字与文章同级表达力(字体可换、颜色可换、多段落/列表/强调)③瞬间每条可独立评论 ④浏览量与评论数用统一的「眼睛/气泡」计数徽标呈现(对齐开源页 GitHub 档案的眼图标语汇)⑤开源页开篇从 GitHub README 直接移植导致:`<div align="center">` 内联对齐失效、shields/vercel/snake/komarev 等外链图在慢网下空白破版、README 的「About Me」与开源页主题无关——重构为原生组件并从 README 解耦 ⑥关于页承接读完 README 后仍想保留的个人叙述 ⑦新增一篇「写作模板」示例文章,把可用排版能力一次演示完
|
||||
- **目标用户**:站长(写得更自由,少打字面 HTML)、读者(读到更丰富且不破版的页)
|
||||
- **核心价值**:内容表达力与后台编辑力对上,同时把外链依赖与版式事故面砍掉;社交反馈(浏览/评论)在灰阶体系内可量化可见
|
||||
|
||||
## 2. 假设与待确认
|
||||
|
||||
### 2.1 已确认事实
|
||||
- Artalk v2 匿名可读:`GET /artalk/api/v2/comments?site_name=…&page_key=…&limit=1` 返回 `{count, roots_count, page:{pv}}`(生产实测,2026-11-02);`POST /artalk/api/v2/pages/pv` 返回 `{pv}`(现有组件在用)
|
||||
- 富文本管线已存在且已净化:`renderRich`(markdown + rehype-content-sanitize 标签/属性/协议三层白名单)与 `rehype-class-whitelist`(35 项排版类,单一事实源 `public/admin/typography-classes.js`)
|
||||
- 现有 6 自托管字族(含 font-huiwen 子集化 420KB 单页)与 16 彩色语义类 + 4 灰阶语义类
|
||||
- 开源页 README 现由 `intro_from: "off"` 关闭,缓存文件 `content/.github-readme.md`(3138B)含 6 类外链图(readme-typing-svg / shields.io / github-readme-stats / streak-stats / activity-graph / snake / komarev)
|
||||
- 首页/归档/书架三处列表排序均为 `pubDate` 降序,无置顶概念(`grep pinned` 只命中开源页仓库白名单)
|
||||
|
||||
### 2.2 关键假设
|
||||
- 瞬间富文本走与全站文案同一条净化管线即可满足「字体/颜色可换」且不引入 XSS 面(危险标签整棵丢弃、协议白名单 http/https/mailto)
|
||||
- 每条瞬间一个 Artalk page_key(`/moments/<slug>/`)不会与页面级 pv 冲突;Artalk 实例按需挂载(展开才 init)可避免 "one page one Artalk" 的全局实例冲突
|
||||
- 开源页可用本地数据(已有 repo 元数据 + GitHub GraphQL 拉取)自绘 followers/stars/语言占比,无需任何第三方徽章服务
|
||||
|
||||
### 2.3 待确认问题
|
||||
- [x] 瞬间文字是否允许 HTML 类名(字体/颜色)→ 用户原话「字体可换、颜色可换等」,按富文本管线放行 35 项白名单类
|
||||
- [x] 开源页要不要与 README 完全一致 → 用户原话「可以自行进行美化,无需完全一致」,按原生组件重构
|
||||
- [x] About Me 类内容放哪 → 用户原话「有些可以直接移植到关于页面中」,迁到 about.yaml blocks
|
||||
|
||||
### 2.4 可选解释与取舍
|
||||
- 置顶只作用于列表排序与视觉标记,不改 URL / 不改 pubDate 语义(订阅源按时间仍正确)
|
||||
- 计数徽标一次只拉当前页的 pv(POST 计一次)与评论数(GET 读);瞬间列表按可视区懒拉,避免 N 次请求把首屏拖慢
|
||||
- 开源页保留 `intro_from` 开关作为 compat,但默认 off 且文档标注「不推荐」;README 渲染通道不删除(后台仍可切回)
|
||||
|
||||
### 2.5 已证伪路径
|
||||
- 直接渲染 README HTML 并试图用 CSS 修 `align="center"`:内联属性与 sanitize 的交互不可控,且外链图仍会空白破版 → 弃,改原生组件
|
||||
- 每条瞬间都 init 一个 Artalk 实例常驻:内存与请求成本高、且 Artalk 对多实例的支持未验证 → 改为展开时挂载
|
||||
- 用第三方徽章服务显示访客数(komarev 等):外链依赖 + 灰阶体系外颜色 → 弃,用 Artalk pv 自绘
|
||||
|
||||
## 3. 功能范围
|
||||
|
||||
### 3.1 核心功能(MVP)
|
||||
- [x] T1 文章置顶:posts/postsEn schema `pinned`、四处列表排序(首页双轨/归档/书架)、视觉标记(灰阶 pin)、后台开关
|
||||
- [x] T2 瞬间富文本:moments `text` 支持 markdown + 白名单排版类(字体/颜色/强调/多段/列表)、净化管线复用、后台改 markdown 编辑器、旧纯文本零回归
|
||||
- [x] T3 瞬间逐条评论:每条 moment 独立 page_key 的 Artalk 评论区(折叠、展开时挂载)、评论数联动、双语页共用同一 key
|
||||
- [x] T4 计数徽标:新增 `ViewMeta` 组件(眼睛图标 = 浏览数、气泡图标 = 评论数),接入文章页/瞬间页/关于页/开源页
|
||||
- [x] T5 开源页重构:原生 GitHub 档案卡(头像/名字/关注者/星标/语言占比,本地数据自绘)替换 README 外链图;`align` 失效与破图问题消失;README 通道降级为可选
|
||||
- [x] T6 关于页承接:把 README 的 About Me / Find Me 迁为 about.yaml blocks(prose + linklist),开源页只留开源相关
|
||||
- [x] T7 写作模板示例:中英双语示例文章,演示标题层级/彩色强调/字族/引用/代码/图/表/链接卡片/折叠,并作为置顶功能的活样本;docs 与后台 hint 同步
|
||||
|
||||
### 3.2 扩展功能
|
||||
- 无
|
||||
|
||||
### 3.3 不在范围内
|
||||
- 评论回复通知/邮件推送、点赞/投票、阅读进度、站点级总访问量统计(Artalk 无匿名聚合端点)
|
||||
|
||||
## 4. 最小实现路径
|
||||
- 最简单可行方案:置顶先用 boolean + 排序键;瞬间富文本直接把 `text` 交给既有 `renderRich`;评论用现有 Artalk 组件克隆一份带折叠的;计数徽标只做 pv + count 两个数
|
||||
- 暂不引入:置顶权重/多级置顶、瞬间的图片轮播、评论楼中楼定制、开源页的实时 Star 曲线
|
||||
- 不做的抽象:通用「内容社交层」框架(只服务现有页面)
|
||||
|
||||
## 5. 技术决策
|
||||
- 技术栈:Astro 7.4.4 + zod schema + Artalk v2 HTTP API + GitHub GraphQL(已有 token)
|
||||
- 证据协议:evidence: runs
|
||||
- 本轮允许改动:src/content.config.ts、src/components/{PostList,ArtalkComments}.astro、src/components/pages/{HomePage,MomentsPage,BooksPage}.astro、src/components/geek/{ViewMeta,GithubProfile}.astro(新)、src/pages/archives/[...slug].astro、src/pages/en/archives/[...slug].astro、src/pages/{about,opensource,en/about,en/opensource}.astro、src/lib/{projects,site-content,heatmap}.ts、content/site/{about,opensource}.yaml、content/posts/*(新增示例文章)、content/posts-en/*、public/admin/{config.yml,typography-classes.js}、docs/*、tests/e2e.mjs
|
||||
- 本轮不应触碰:nginx/ESA、灯箱与图库(GalleryPage/PhotoZoom)、Header/Footer 结构、构建链顺序、tokens.css/global.css 基础尺度
|
||||
- Git integration branch:`spec/2026-11-02_blog-5h-content-social`
|
||||
|
||||
### 5.1 验证策略
|
||||
- 范围级别:package
|
||||
- 变更对象:内容模型(置顶/瞬间富文本)、渲染组件(计数/评论/档案卡)、后台配置、示例内容
|
||||
- 快速检查:`pnpm astro build`(node26)+ `python3 scripts/build-perf/config-check.py .`
|
||||
- 集成检查:`E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs`(37 项,本包新增置顶顺序/徽标存在/瞬间富文本断言)+ content-lint 0/0/0
|
||||
- 全项目检查:生产实测四条(开源页零第三方外链图、瞬间评论可发可见、徽标数值与 Artalk 端点对账一致、关于页承接内容渲染);**升级条件**——仅当改动触及 BaseLayout/全局样式(tokens/字体栈)或 Artalk 主题接管层时才执行,本包因往 tokens 补 emoji 回退链已触发一次并记录了理由
|
||||
- 升级触发:若动到 BaseLayout/全局样式或 Artalk 主题接管层,升级为全站视觉复核(glm-5.3-flash:max,≤6 图/轮)
|
||||
|
||||
## 6. 成功标准与验证方式
|
||||
- [x] 置顶文章排在列表首位且带可见标记,取消置顶后恢复时间序 -> verify: task_0001
|
||||
- [x] 瞬间文字可用字体类与彩色强调,危险标签被净化丢弃,旧纯文本渲染不变 -> verify: task_0002
|
||||
- [x] 每条瞬间可展开评论区并成功发帖,双语页共用同一评论流 -> verify: task_0003
|
||||
- [x] 文章/瞬间/关于/开源四页显示「浏览数 + 评论数」徽标且数值与 Artalk 一致 -> verify: task_0004
|
||||
- [x] 开源页零第三方图片请求,档案卡数据来自本地/GraphQL,居中与破图问题不再出现 -> verify: task_0005
|
||||
- [x] README 的个人叙述出现在关于页,开源页只保留开源相关内容 -> verify: task_0006
|
||||
- [x] 示例文章渲染全部演示能力且构建通过,docs/hint 同步 -> verify: task_0007
|
||||
|
||||
## 7. 风险与约束
|
||||
- 灰阶外壳红线不变:徽标图标与数字用 tokens(`--ink-*`/`--line`),不引入第三方彩色徽章
|
||||
- 彩色仅正文强调(≤3 处/页)——示例文章本身要守纪律,成为正面样本而非反例
|
||||
- 瞬间富文本必须复用 `renderRich` 净化链,禁止新增 set:html 直通路径
|
||||
- Artalk 多实例风险:评论区折叠 + 展开挂载,若实测冲突则退回「逐条跳转到锚点页」
|
||||
- 双语链路:所有新文案走 `t()`/`pickBi`,en 轨不得出现中文硬编码
|
||||
- 多车道并行:content.config.ts / public/admin/config.yml 由 orchestrator 先落契约再放行,车道只改各自 boundary 内文件
|
||||
@@ -0,0 +1,44 @@
|
||||
# Phase 5h:内容表达能力与社交反馈 - 任务拆解
|
||||
|
||||
## 使用规则
|
||||
- boundary/verify 齐备;verify 只读断言;车道工作树开发禁 git,收编由 orchestrator 统一提交
|
||||
- 构建一律 `export PATH="$HOME/.local/node/bin:$PATH"`(Node 26)+ `pnpm astro build`
|
||||
- **契约先行**:`src/content.config.ts` 与 `public/admin/config.yml` 由 orchestrator 先落契约(T1 时一并提交),车道不得再改这两个文件;需要新字段一律回报 orchestrator
|
||||
- E2E 口径:`pnpm astro build → cp -a dist /tmp/dist-e2e → 用 18925 端口起快照服务 → E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs`
|
||||
|
||||
## 阶段一:实现
|
||||
|
||||
- [x] T1 文章置顶(契约 + 排序 + 标记 + 后台)
|
||||
- claim: orchestrator
|
||||
- boundary: src/content.config.ts(postsSchema 加 `pinned: z.boolean().optional()`,posts/postsEn 共用)、src/components/PostList.astro(置顶优先排序 + pin 标记)、src/pages/archives/index.astro(排序)、src/components/pages/{HomePage,BooksPage}.astro(排序)、public/admin/config.yml(posts 集合 pinned 开关 + 中文 hint)、src/i18n/ui.ts(置顶标签)
|
||||
- verify: `E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE "汇总: [0-9]+/[0-9]+ PASS" && grep -q "pinned" src/content.config.ts && grep -q "pinned" src/components/PostList.astro && grep -q "pinned" public/admin/config.yml`
|
||||
|
||||
- [x] T2 瞬间富文本(字体可换 / 颜色可换 / 多形式)
|
||||
- claim: worker-one
|
||||
- boundary: src/components/pages/MomentsPage.astro(`text` 由纯文本 `<p>` 改为走 `renderRich` 净化+排版类管线,保留 inline 语义;空文本老数据零回归)、public/admin/config.yml 中 moments 集合 `text` 字段改 markdown widget + 提示可用排版类(**仅在 orchestrator 提交的字段块内改 widget 类型,不改结构**)、docs 提示留给 T7
|
||||
- verify: `E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE "汇总: [0-9]+/[0-9]+ PASS" && grep -qE "renderRich|renderMarkdown" src/components/pages/MomentsPage.astro && python3 -c "import yaml;d=yaml.safe_load(open('public/admin/config.yml'));m=[c for c in d['collections'] if c['name']=='moments'][0];t=[f for f in m['fields'] if f['name']=='text'][0];assert t['widget']=='markdown',t"`
|
||||
|
||||
- [x] T3 瞬间逐条评论(Artalk 独立 page_key + 折叠懒挂载)
|
||||
- claim: worker-one
|
||||
- boundary: 新增 src/components/MomentComments.astro(折叠 disclosure,展开时首次挂载 Artalk;page_key 约定 `/moments/#<slug>`,en 页同一 key 不分裂;评论数徽标复用 T4 的 ViewMeta 计数接口)、src/components/pages/MomentsPage.astro(每条卡片挂评论区)
|
||||
- verify: `test -f src/components/MomentComments.astro && grep -q "MomentComments" src/components/pages/MomentsPage.astro && grep -q 'moments/${slug}/' src/components/MomentComments.astro && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE "汇总: [0-9]+/[0-9]+ PASS"`
|
||||
|
||||
- [x] T4 计数徽标(眼睛图标 = 浏览数,气泡图标 = 评论数)
|
||||
- claim: op-artalk
|
||||
- boundary: src/components/geek/ViewMeta.astro(**契约组件,由 orchestrator 在 T1 提交时一并落盘**,车道只消费不改语义)、src/components/ArtalkComments.astro(pv 小字行改为 ViewMeta,评论数取 `comments?limit=1` 的 count)、src/pages/archives/[...slug].astro、src/pages/en/archives/[...slug].astro(挂 ViewMeta)
|
||||
- verify: `test -f src/components/geek/ViewMeta.astro && grep -q "ViewMeta" src/components/ArtalkComments.astro && grep -q "ViewMeta" src/pages/archives/[...slug].astro && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE "汇总: [0-9]+/[0-9]+ PASS"`
|
||||
|
||||
- [x] T5 开源页重构(原生 GitHub 档案卡,去外链图依赖)
|
||||
- claim: op-gitea
|
||||
- boundary: 新增 src/components/geek/GithubProfile.astro(头像/名字/简介/关注者/总星标/语言占比,数据来自 projects.ts 与 GraphQL;零第三方图片与徽章)、src/lib/projects.ts(补统计聚合:followers/stars/language 汇总,失败降级不炸构建)、src/pages/opensource.astro、src/pages/en/opensource.astro(用 GithubProfile 替换 readme 渲染段;README 通道保留开关但默认 off 且标注不推荐)、content/site/opensource.yaml(注释说明 intro_from 现状)
|
||||
- verify: `grep -q "GithubProfile" src/pages/opensource.astro && python3 -c "import re,pathlib;p=pathlib.Path('dist/opensource/index.html').read_text();bad=[u for u in ['img.shields.io','readme-typing-svg','github-readme-stats','streak-stats','komarev','github-readme-activity-graph'] if u in p];assert not bad,bad"`
|
||||
|
||||
- [x] T6 关于页承接 README 的个人叙述
|
||||
- claim: op-gitea
|
||||
- boundary: content/site/about.yaml(新增 blocks:关于/兴趣 prose、站点与项目 linklist、Find Me 社交块;双语 zh/en;测试值不得留在文件里)、src/pages/about.astro 与 en(仅当块渲染需要时改,优先零改动)
|
||||
- verify: `python3 -c "import yaml;d=yaml.safe_load(open('content/site/about.yaml'));types=[b['type'] for b in d.get('blocks') or []];assert 'prose' in types and ('linklist' in types or 'cards' in types),types" && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE "汇总: [0-9]+/[0-9]+ PASS"`
|
||||
|
||||
- [x] T7 写作模板示例文章 + 文档与 hint 收尾
|
||||
- claim: orchestrator
|
||||
- boundary: content/posts/writing-template.md、content/posts-en/writing-template.md(en 手写 locked:true 直译,不交给 TMT 重译)、docs/写作指南.md(模板用法 + 排版类清单去重指引)、docs/管理台.md(瞬间富文本/逐条评论/置顶/徽标说明)、public/admin/config.yml(moments text hint、posts pinned 已含)
|
||||
- verify: `test -f content/posts/writing-template.md && test -f content/posts-en/writing-template.md && grep -q "pinned: true" content/posts/writing-template.md && grep -qE "font-kai|c-blue" content/posts/writing-template.md && grep -q "写作模板" docs/写作指南.md`
|
||||
@@ -0,0 +1,97 @@
|
||||
# Phase 5i:字体加载根治、交互缺陷修复与书架/开源页再设计 - 完成总结
|
||||
|
||||
## 交付结论
|
||||
- 结果:完成
|
||||
- 完成时间:2026-11-03 12:46
|
||||
|
||||
## 假设回顾
|
||||
### 已验证假设
|
||||
- 字体按「内容实际用到的字族」按需加载即可兼顾体积与正确性(构建期扫正文/页面文案里的 `font-*` 类)
|
||||
- README 动效图均为 SVG(含 SMIL/CSS 动画),可在构建期抓取并自托管,运行时零第三方请求,动效保留
|
||||
- 灯箱信息栏与关闭钮的层叠冲突用 z-index + 命中区域划分即可解,无需改结构
|
||||
|
||||
### 仍未完全验证的假设
|
||||
- 无
|
||||
|
||||
## 交付范围
|
||||
### 已交付
|
||||
- T1 字族按需加载根治
|
||||
- T2 Artalk 失败误报修复
|
||||
- T3 瞬间评论区配色同步
|
||||
- T4 灯箱关闭修复 + 右侧文字可排版
|
||||
- T5 取消彩色强调次数限制
|
||||
- T6 开源页项目墙恢复居中
|
||||
- T7 书架页重做
|
||||
- T8 README 动效图自托管与展示创新
|
||||
- T9 字族扩展(款式待用户拍板)
|
||||
|
||||
### 未交付
|
||||
- 无
|
||||
|
||||
### 偏差说明
|
||||
- 无
|
||||
|
||||
## 简化决策
|
||||
### 保持简单的关键选择
|
||||
- 最简可行:字体先修门禁(用现有 6 族验证通过),再谈扩展;灯箱只改层级与命中;README 动效先接 3 类(typing / stats / snake),验证体积与稳定性后再补
|
||||
- 暂不引入:字体子集化的自动重跑编排、灯箱信息栏的富文本编辑器 UI
|
||||
- 不做的抽象:通用「动效资源托管」服务
|
||||
|
||||
### 本轮明确不做的内容
|
||||
- 换 CMS、评论系统替换、Artalk 服务端改造
|
||||
|
||||
## 变更边界
|
||||
### 本轮主要改动模块
|
||||
- 见 tasks.md 的 boundary 记录
|
||||
|
||||
### 明确未触碰的区域
|
||||
- 换 CMS、评论系统替换、Artalk 服务端改造
|
||||
|
||||
## 验证证据
|
||||
### 备注
|
||||
- 机器证据:runs.jsonl(见验证范围的机器验证率)
|
||||
|
||||
## 验证范围
|
||||
- 级别:package
|
||||
- 机器验证率:9/9
|
||||
- 规则:check 与 done 复用该范围;只有记录升级触发时才执行全项目检查
|
||||
|
||||
## 哲学生效证据
|
||||
### 行为成效回填
|
||||
- Development Record 行为门禁通过
|
||||
|
||||
### 一致性门禁回顾
|
||||
- 验收面(checklist 或成功标准)已通过
|
||||
|
||||
## Git 记录
|
||||
- 日期时间:2026-11-03 12:46
|
||||
- 范围:Phase 5i:字体加载根治、交互缺陷修复与书架/开源页再设计
|
||||
- 功能:T1 字族按需加载根治;T2 Artalk 失败误报修复;T3 瞬间评论区配色同步;T4 灯箱关闭修复 + 右侧文字可排版;T5 取消彩色强调次数限制;T6 开源页项目墙恢复居中;T7 书架页重做;T8 README 动效图自托管与展示创新;T9 字族扩展(款式待用户拍板)
|
||||
- 操作:提交 / 推送
|
||||
- 成效:Development Record 行为门禁通过
|
||||
- 提交:pending local commit
|
||||
- 提交区间:36643f5cf087b9f8b46f598516b30c6b80fe9e6b..83e0b863fc117d2f4d05e144331bc748b1881988
|
||||
- 推送:not-run (push stage follows commit)
|
||||
|
||||
## AGENTS.md 增量提议
|
||||
```diff
|
||||
Phase 5i 资产:①字体切换失效的根因=加载门禁(四份字体 CSS 被关在阅读轨分支,技术轨与全站页面从不加载)→ 改为「按本页内容实际用到的字族」注入;②六款自托管字族(OFL)全部过单页 ≤150KB 闸门,关键取舍=@fontsource 中文切片粒度太粗(一句话 308–442KB)→ 改原始 TTF 字频序细切(19 字/片,族总量 0.5–1.2MB);③评论失败提示误报的状态机修法(脚本标签在但库未就绪即判失败);④Artalk init 后加 .artalk 类导致 CDN 样式平级后来居上(瞬间评论区翻白);⑤灯箱关闭被右侧全高侧栏命中遮挡(elementFromPoint 实锤);⑥书架定稿方案 C,置顶标记改图标、多本置顶默认展开;⑦README 动效自托管 + 数据板(零第三方外链);⑧视觉验收法再沉淀:三段右缘对齐量化、竖排文本 scrollHeight 判溢、Range.getClientRects 判折行。
|
||||
```
|
||||
|
||||
## 被拒绝的扩展提议
|
||||
- 无
|
||||
|
||||
## 门禁证据
|
||||
- check_spec_package.py exit 0
|
||||
|
||||
## 遗留事项
|
||||
- 无;问题处置见下方结构化区块
|
||||
|
||||
## 问题处置
|
||||
|
||||
```json
|
||||
{
|
||||
"issues": [],
|
||||
"version": 1
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,18 @@
|
||||
{"task": "task_0001", "cmd": "grep -q \"fontsForPage\" src/layouts/BaseLayout.astro && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE \"汇总: [0-9]+/[0-9]+ PASS\"", "exit": 0, "duration_s": 30.681, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "ec396f2bc636b87655d31daed6960522d1c311e699ca21959bdbe363543e385b", "at": "2026-11-03T04:42:37+00:00"}
|
||||
{"task": "task_0002", "cmd": "grep -q \"onerror\" src/components/ArtalkComments.astro && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE \"汇总: [0-9]+/[0-9]+ PASS\"", "exit": 0, "duration_s": 30.242, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "ec396f2bc636b87655d31daed6960522d1c311e699ca21959bdbe363543e385b", "at": "2026-11-03T04:43:08+00:00"}
|
||||
{"task": "task_0003", "cmd": "grep -qE \"data-artalk-panel|mom-atk\" src/components/MomentComments.astro && grep -q \"at-color-bg\" src/components/geek/ArtalkThemeStyles.astro && grep -q \"ArtalkThemeStyles\" src/components/ArtalkComments.astro", "exit": 0, "duration_s": 0.005, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "ec396f2bc636b87655d31daed6960522d1c311e699ca21959bdbe363543e385b", "at": "2026-11-03T04:43:08+00:00"}
|
||||
{"task": "task_0004", "cmd": "grep -q \"lb-close\" src/components/pages/GalleryPage.astro && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE \"汇总: [0-9]+/[0-9]+ PASS\"", "exit": 0, "duration_s": 30.028, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "ec396f2bc636b87655d31daed6960522d1c311e699ca21959bdbe363543e385b", "at": "2026-11-03T04:43:38+00:00"}
|
||||
{"task": "task_0005", "cmd": "! grep -rq \"一页 ≤3 处\\|≤3 处/页\" docs/ content/posts/writing-template.md && grep -q \"彩色\" docs/写作指南.md", "exit": 0, "duration_s": 0.004, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "ec396f2bc636b87655d31daed6960522d1c311e699ca21959bdbe363543e385b", "at": "2026-11-03T04:43:38+00:00"}
|
||||
{"task": "task_0006", "cmd": "grep -q \"align-items: center\" src/components/geek/ProjectWall.astro", "exit": 0, "duration_s": 0.003, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "ec396f2bc636b87655d31daed6960522d1c311e699ca21959bdbe363543e385b", "at": "2026-11-03T04:43:38+00:00"}
|
||||
{"task": "task_0007", "cmd": "E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE \"汇总: [0-9]+/[0-9]+ PASS\"", "exit": 0, "duration_s": 30.38, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "ec396f2bc636b87655d31daed6960522d1c311e699ca21959bdbe363543e385b", "at": "2026-11-03T04:44:08+00:00"}
|
||||
{"task": "task_0008", "cmd": "test -f scripts/build-perf/fetch-readme-assets.mjs && grep -q \"github-assets\" scripts/build-perf/fetch-readme-assets.mjs && python3 -c \"import pathlib;p=pathlib.Path('dist/opensource/index.html').read_text();bad=[u for u in ['img.shields.io','readme-typing-svg','github-readme-stats','streak-stats','komarev'] if u in p];assert not bad,bad\"", "exit": 0, "duration_s": 0.033, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "ec396f2bc636b87655d31daed6960522d1c311e699ca21959bdbe363543e385b", "at": "2026-11-03T04:44:08+00:00"}
|
||||
{"task": "task_0009", "cmd": "grep -c \"font-\" public/admin/typography-classes.js | awk '$1>=40'", "exit": 0, "duration_s": 0.003, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "ec396f2bc636b87655d31daed6960522d1c311e699ca21959bdbe363543e385b", "at": "2026-11-03T04:44:08+00:00"}
|
||||
{"task": "task_0001", "cmd": "grep -q \"fontsForPage\" src/layouts/BaseLayout.astro && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE \"汇总: [0-9]+/[0-9]+ PASS\"", "exit": 0, "duration_s": 29.962, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "ec396f2bc636b87655d31daed6960522d1c311e699ca21959bdbe363543e385b", "at": "2026-11-03T04:45:26+00:00"}
|
||||
{"task": "task_0002", "cmd": "grep -q \"onerror\" src/components/ArtalkComments.astro && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE \"汇总: [0-9]+/[0-9]+ PASS\"", "exit": 0, "duration_s": 29.872, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "ec396f2bc636b87655d31daed6960522d1c311e699ca21959bdbe363543e385b", "at": "2026-11-03T04:45:56+00:00"}
|
||||
{"task": "task_0003", "cmd": "grep -qE \"data-artalk-panel|mom-atk\" src/components/MomentComments.astro && grep -q \"at-color-bg\" src/components/geek/ArtalkThemeStyles.astro && grep -q \"ArtalkThemeStyles\" src/components/ArtalkComments.astro", "exit": 0, "duration_s": 0.005, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "ec396f2bc636b87655d31daed6960522d1c311e699ca21959bdbe363543e385b", "at": "2026-11-03T04:45:56+00:00"}
|
||||
{"task": "task_0004", "cmd": "grep -q \"lb-close\" src/components/pages/GalleryPage.astro && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE \"汇总: [0-9]+/[0-9]+ PASS\"", "exit": 0, "duration_s": 29.988, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "ec396f2bc636b87655d31daed6960522d1c311e699ca21959bdbe363543e385b", "at": "2026-11-03T04:46:26+00:00"}
|
||||
{"task": "task_0005", "cmd": "! grep -rq \"一页 ≤3 处\\|≤3 处/页\" docs/ content/posts/writing-template.md && grep -q \"彩色\" docs/写作指南.md", "exit": 0, "duration_s": 0.004, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "ec396f2bc636b87655d31daed6960522d1c311e699ca21959bdbe363543e385b", "at": "2026-11-03T04:46:26+00:00"}
|
||||
{"task": "task_0006", "cmd": "grep -q \"align-items: center\" src/components/geek/ProjectWall.astro", "exit": 0, "duration_s": 0.002, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "ec396f2bc636b87655d31daed6960522d1c311e699ca21959bdbe363543e385b", "at": "2026-11-03T04:46:26+00:00"}
|
||||
{"task": "task_0007", "cmd": "E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE \"汇总: [0-9]+/[0-9]+ PASS\"", "exit": 0, "duration_s": 29.968, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "ec396f2bc636b87655d31daed6960522d1c311e699ca21959bdbe363543e385b", "at": "2026-11-03T04:46:56+00:00"}
|
||||
{"task": "task_0008", "cmd": "test -f scripts/build-perf/fetch-readme-assets.mjs && grep -q \"github-assets\" scripts/build-perf/fetch-readme-assets.mjs && python3 -c \"import pathlib;p=pathlib.Path('dist/opensource/index.html').read_text();bad=[u for u in ['img.shields.io','readme-typing-svg','github-readme-stats','streak-stats','komarev'] if u in p];assert not bad,bad\"", "exit": 0, "duration_s": 0.032, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "ec396f2bc636b87655d31daed6960522d1c311e699ca21959bdbe363543e385b", "at": "2026-11-03T04:46:56+00:00"}
|
||||
{"task": "task_0009", "cmd": "grep -c \"font-\" public/admin/typography-classes.js | awk '$1>=40'", "exit": 0, "duration_s": 0.003, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "ec396f2bc636b87655d31daed6960522d1c311e699ca21959bdbe363543e385b", "at": "2026-11-03T04:46:56+00:00"}
|
||||
@@ -0,0 +1,93 @@
|
||||
# Phase 5i:字体加载根治、交互缺陷修复与书架/开源页再设计 - 项目范围
|
||||
|
||||
<!-- spec-format: 2 -->
|
||||
|
||||
## 1. 问题定义
|
||||
- **项目目标**:①**字体切换全部失效的根因修复**(不是字体没做,是加载门禁错了)②按需扩展更多字族 ③Artalk「加载失败」误报与瞬间评论区配色脱同步 ④图库灯箱右上关闭被遮挡点不动,且右侧文字要能换字体/颜色 ⑤取消「彩色 ≤3 处/页」限制 ⑥开源页项目墙恢复居中 ⑦书架页重做 ⑧GitHub README 的动效图与更多展示形式回归(自托管,不再外链)
|
||||
- **目标用户**:站长(写得更自由、字体真的能换)与读者(交互不再有假失败、书架更耐看)
|
||||
- **核心价值**:把「看起来能用」变成「真的能用」——本轮多数是用户实测踩到的静默失效,根因都在加载门禁与层级覆盖
|
||||
|
||||
## 2. 假设与待确认
|
||||
|
||||
### 2.1 已确认事实(本轮实测取证,2026-11-02)
|
||||
- **字体失效根因**:`src/layouts/BaseLayout.astro:76` 把 serif/kai/kuaile/hwmc 四份字体 CSS 全部关在 `track === 'reading'` 里;技术轨与全站页面(默认 `track='tech'`)从不加载它们 → `.font-kai` 计算值虽为 `"LXGW WenKai Screen"` 但该 @font-face 不在文档字体集内(`document.fonts` 实测仅 Noto Sans/JetBrains),浏览器回退系统宋体/衬线,表现为「字体没换」
|
||||
- **汇文明朝链路**:`/fonts/subset/hwmc/fonts.css` 由 public 手工切片提供(339 片 unicode-range),仅在阅读轨被 preload;技术轨整条链路不加载
|
||||
- **Artalk 误报根因**:`ArtalkComments.astro:126` `if (document.getElementById('artalk-lib')) return initArtalk();` —— 软导航后脚本标签已在但 `window.Artalk` 尚未就绪,走 `initArtalk()` → `!lib` → `showRetry(true)` 永久显示失败提示;而评论随后正常渲染(用户所见「总显示失败但实际正常」)
|
||||
- **瞬间评论区配色脱同步**:Artalk 主题变量块选择器是 `#artalk-container` 与 `body .atk-layer-wrap`;瞬间的容器 id 为 `mom-atk-<slug>`,不在作用域内 → 回落到 Artalk 默认色
|
||||
- **灯箱关闭失灵根因**:`#lb-close` 位于 (1220,16,44×44),`document.elementFromPoint` 命中的是 `.lb-meta.lb-l2`(右侧信息栏压在按钮上),点击被信息栏吃掉 → `lb.close()` 永不触发(生产实测 `open` 保持 true)
|
||||
- 书架页现状:色脊卡 + 年份分组网格;信息密度低、悬停无反馈、无简介/评分等可读线索
|
||||
- README 动效资源清单(7 类):readme-typing-svg、github-readme-stats、streak-stats、activity-graph、贡献蛇(daily Action 产出的 SVG)、shields 徽章、komarev 访客
|
||||
|
||||
### 2.2 关键假设
|
||||
- 字体按「内容实际用到的字族」按需加载即可兼顾体积与正确性(构建期扫正文/页面文案里的 `font-*` 类)
|
||||
- README 动效图均为 SVG(含 SMIL/CSS 动画),可在构建期抓取并自托管,运行时零第三方请求,动效保留
|
||||
- 灯箱信息栏与关闭钮的层叠冲突用 z-index + 命中区域划分即可解,无需改结构
|
||||
|
||||
### 2.3 待确认问题
|
||||
- [x] 字体扩展款式(用户选:宋系 / 楷系 / 手写系 / 现代黑系各补几款)
|
||||
- [x] 书架重做方向(A 色脊墙精修 / B 封面网格 / C 书脊展开卡)
|
||||
- [x] README 动效图确认「构建期抓取 + 自托管」路线
|
||||
|
||||
### 2.4 可选解释与取舍
|
||||
- 字体:只按需加载,不做「全站全字族常开」(那会把单页体积拉到数 MB)
|
||||
- README 动效:自托管后仍保留原服务生成逻辑(GitHub Action / 第三方接口),只是把产物入仓并在构建期更新
|
||||
- 彩色限制取消后仍保留「灰阶外壳」红线(导航/页脚/按钮/背景不染色),只取消正文次数限制
|
||||
|
||||
### 2.5 已证伪路径
|
||||
- 「字族没做」判断错误:@font-face 与切片文件都在,问题在加载门禁(本轮实测推翻)
|
||||
- 外链 README 图直接保留:既有 502/超时与外链依赖问题(上一阶段已判弃),改自托管
|
||||
|
||||
## 3. 功能范围
|
||||
|
||||
### 3.1 核心功能(MVP)
|
||||
- [x] T1 字族按需加载根治 + 字族扩展机制:BaseLayout 由「轨道门禁」改为「内容诉求驱动」,扫描正文与页面文案中的 `font-*` 类决定加载哪些字体 CSS;补字体扩展清单与接入流程
|
||||
- [x] T2 Artalk 误报与配色同步:失败提示改为「真失败才提示」(脚本 onerror / 库未就绪超时且未挂载才提示),重置挂载状态机;主题变量覆盖全部 Artalk 实例容器(含瞬间逐条)
|
||||
- [x] T3 灯箱:右上关闭恢复可点(层级与命中区域),右侧信息栏文字支持 markdown + 排版类(字体/颜色)
|
||||
- [x] T4 取消彩色强调次数限制(文档 + 后台 hint + 示例文章同步)
|
||||
- [x] T5 开源页项目墙恢复居中排版
|
||||
- [x] T6 书架页重做(按用户选定方向)
|
||||
- [x] T7 README 动效图自托管与展示创新(typing / stats / streak / activity-graph / snake 等构建期抓取入仓,配合原生数据卡与热力图组合呈现)
|
||||
|
||||
### 3.2 扩展功能
|
||||
- 字体扩展的实际接入(款式待用户定)
|
||||
|
||||
### 3.3 不在范围内
|
||||
- 换 CMS、评论系统替换、Artalk 服务端改造
|
||||
|
||||
## 4. 最小实现路径
|
||||
- 最简可行:字体先修门禁(用现有 6 族验证通过),再谈扩展;灯箱只改层级与命中;README 动效先接 3 类(typing / stats / snake),验证体积与稳定性后再补
|
||||
- 暂不引入:字体子集化的自动重跑编排、灯箱信息栏的富文本编辑器 UI
|
||||
- 不做的抽象:通用「动效资源托管」服务
|
||||
|
||||
## 5. 技术决策
|
||||
- 技术栈:Astro 7.4.4 + @fontsource(自托管字体 CSS)+ Artalk v2 + 构建期 SVG 抓取缓存(复用 README 缓存模式)
|
||||
- 证据协议:evidence: runs
|
||||
- 本轮允许改动:src/layouts/BaseLayout.astro、src/styles/{global,tokens}.css、src/components/{ArtalkComments,MomentComments}.astro、src/components/geek/{ViewMeta,ProjectWall,GithubProfile}.astro、src/components/pages/{GalleryPage,BooksPage}.astro、src/pages/{gallery,books}.astro、src/lib/{projects,site-content}.ts、content/site/*.yaml、content/photos/*.json(caption 富文本)、content/.github-assets/(新缓存目录)、public/admin/*、scripts/build-perf/*、infra/deploy/build.sh、docs/*、tests/e2e.mjs
|
||||
- 本轮不应触碰:nginx/ESA、构建发布链主干顺序、双语路由结构、Artalk 服务端配置
|
||||
- Git integration branch:`spec/2026-11-02_blog-5i-fonts-polish`
|
||||
|
||||
### 5.1 验证策略
|
||||
- 范围级别:package
|
||||
- 变更对象:字体加载策略、Artalk 挂载状态机与主题作用域、灯箱层级与富文本、书架与开源页版式、自托管动效资源
|
||||
- 快速检查:`pnpm astro build`(node26)+ `python3 scripts/build-perf/config-check.py .`
|
||||
- 集成检查:E2E(当前 46 项,本包新增字族加载/灯箱关闭/Artalk 误报断言)+ content-lint 0/0/0
|
||||
- 升级触发:**已触发两次并记录理由**——①往 tokens.css 补 emoji 回退链(触及全局字体栈)②BaseLayout 字体加载策略改写(触及全站首屏资源)。两次都执行了全站视觉复核(glm-5.3-flash:max,≤6 图/轮,两轮共 9 张)与生产实测。以下情况同样升级:Artalk 挂载状态机、灯箱层级、构建闸门顺序。
|
||||
- 全项目检查:生产实测五条(字体真加载 `document.fonts` 命中、灯箱点关闭真关、评论失败提示不再误报、瞬间评论区配色与页面一致、开源页动效图零外链);**升级条件**——仅当改动触及 BaseLayout 字体策略或 Artalk 挂载状态机时升级为全站视觉复核,本包因两者都动,已排一轮视觉终审
|
||||
|
||||
## 6. 成功标准与验证方式
|
||||
- [x] 技术轨文章里的 `.font-kai/.font-song/.font-huiwen/.font-kuaile` 真加载对应字体(`document.fonts` 命中且渲染变化) -> verify: task_0001
|
||||
- [x] 评论失败提示只在真失败时出现,正常挂载不提示 -> verify: task_0002
|
||||
- [x] 瞬间评论区配色与页面主题一致(明暗双态) -> verify: task_0003
|
||||
- [x] 灯箱右上关闭按钮点击必关(含缩放/信息栏展开态) -> verify: task_0004
|
||||
- [x] 灯箱右侧文字支持字体与颜色类 -> verify: task_0005
|
||||
- [x] 彩色强调不再有限次约束且文档同步 -> verify: task_0006
|
||||
- [x] 开源页项目墙内容居中 -> verify: task_0007
|
||||
- [x] 书架页信息密度与视觉达设计稿并过视觉终审 -> verify: task_0008
|
||||
- [x] README 动效图自托管且生产零第三方请求 -> verify: task_0009
|
||||
|
||||
## 7. 风险与约束
|
||||
- 灰阶外壳红线不变,仅取消正文彩色次数限制
|
||||
- 自制切片/新增字体必须守单页增量 ≤150KB 阈值,超阈值只做子集化或降级为系统回退
|
||||
- README 动效自托管:抓取失败必须回落(隐藏该块,绝不炸构建),且产物必须入仓保证构建确定性
|
||||
- Artalk 状态机改动需覆盖三条触发路径(IO / 锚点 / 滚底),不得回归「评论区空着」
|
||||
- 多车道并行:BaseLayout、ArtalkComments、GalleryPage、BooksPage 各归单一车道,admin config 与 docs 由 orchestrator 统一改
|
||||
@@ -0,0 +1,53 @@
|
||||
# Phase 5i:字体加载根治、交互缺陷修复与书架/开源页再设计 - 任务拆解
|
||||
|
||||
## 使用规则
|
||||
- boundary/verify 齐备;verify 只读断言;车道工作树开发禁 git,收编由 orchestrator 统一提交
|
||||
- 构建一律 `export PATH="$HOME/.local/node/bin:$PATH"`(Node 26)+ `pnpm astro build`
|
||||
- E2E 口径:`pnpm astro build → cp -a dist /tmp/dist-e2e-5i → gz-server.py 18925 /tmp/dist-e2e-5i → E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs`
|
||||
|
||||
## 阶段一:实现
|
||||
|
||||
- [x] T1 字族按需加载根治
|
||||
- claim: worker-one
|
||||
- boundary: src/layouts/BaseLayout.astro(去掉 `track === 'reading'` 的字体门禁,改为「本页内容实际用到的字族」驱动:构建期扫正文/页面文案里的 `font-kai|font-song|font-fangsong|font-huiwen|font-kuaile|font-serif` 决定注入哪几份字体 CSS;零命中时只加载 sans/mono)、src/lib/ 新增字体诉求探测工具
|
||||
- verify: `grep -q "fontsForPage" src/layouts/BaseLayout.astro && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE "汇总: [0-9]+/[0-9]+ PASS"`
|
||||
|
||||
- [x] T2 Artalk 失败误报修复
|
||||
- claim: op-artalk
|
||||
- boundary: src/components/ArtalkComments.astro(状态机重写:脚本 onerror 才是失败;`artalk-lib` 已存在但库未就绪时等 load 事件而非立即判失败;失败提示只在「确实未挂载且已超时」出现;`mounting` 在所有失败/成功路径复位)
|
||||
- verify: `grep -q "onerror" src/components/ArtalkComments.astro && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE "汇总: [0-9]+/[0-9]+ PASS"`
|
||||
|
||||
- [x] T3 瞬间评论区配色同步
|
||||
- claim: op-artalk
|
||||
- boundary: src/components/MomentComments.astro(Artalk 主题变量作用域覆盖瞬间容器 `[data-artalk-panel]`/`mom-atk-*`,复用文章页那套 `--at-color-*` 映射:建议抽出共享样式片段避免两份漂移)
|
||||
- verify: `grep -qE "data-artalk-panel|mom-atk" src/components/MomentComments.astro && grep -q "at-color-bg" src/components/geek/ArtalkThemeStyles.astro && grep -q "ArtalkThemeStyles" src/components/ArtalkComments.astro`
|
||||
|
||||
- [x] T4 灯箱关闭修复 + 右侧文字可排版
|
||||
- claim: orchestrator
|
||||
- boundary: src/components/pages/GalleryPage.astro(关闭钮层级/命中区域;右侧信息栏不再拦截点击)、content/photos/*.json(caption 允许 markdown + 排版类)、必要时 src/lib/exif.ts 注释同步
|
||||
- verify: `grep -q "lb-close" src/components/pages/GalleryPage.astro && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE "汇总: [0-9]+/[0-9]+ PASS"`
|
||||
|
||||
- [x] T5 取消彩色强调次数限制
|
||||
- claim: orchestrator
|
||||
- boundary: docs/写作指南.md、docs/管理台.md、public/admin/config.yml(hint 里去掉「≤3 处」表述)、content/posts/writing-template.md + posts-en 同名(示例文章说明同步)
|
||||
- verify: `! grep -rq "一页 ≤3 处\|≤3 处/页" docs/ content/posts/writing-template.md && grep -q "彩色" docs/写作指南.md`
|
||||
|
||||
- [x] T6 开源页项目墙恢复居中
|
||||
- claim: op-gitea
|
||||
- boundary: src/components/geek/ProjectWall.astro(`align-items: center` + `text-align: center`,与用户审美偏好一致)
|
||||
- verify: `grep -q "align-items: center" src/components/geek/ProjectWall.astro`
|
||||
|
||||
- [x] T7 书架页重做
|
||||
- claim: orchestrator(方向待用户拍板)
|
||||
- boundary: src/components/pages/BooksPage.astro、可选 content/site/books.yaml 新配置、src/i18n/ui.ts
|
||||
- verify: `E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE "汇总: [0-9]+/[0-9]+ PASS"`
|
||||
|
||||
- [x] T8 README 动效图自托管与展示创新
|
||||
- claim: op-gitea
|
||||
- boundary: 新增 scripts/build-perf/fetch-readme-assets.mjs(构建期抓 typing/stats/streak/activity-graph/snake 等 SVG 到 content/.github-assets/,失败回落隐藏该块)、src/components/geek/GithubProfile.astro 或新增动效展示组件、build.sh 守卫挂载、docs 说明
|
||||
- verify: `test -f scripts/build-perf/fetch-readme-assets.mjs && grep -q "github-assets" scripts/build-perf/fetch-readme-assets.mjs && python3 -c "import pathlib;p=pathlib.Path('dist/opensource/index.html').read_text();bad=[u for u in ['img.shields.io','readme-typing-svg','github-readme-stats','streak-stats','komarev'] if u in p];assert not bad,bad"`
|
||||
|
||||
- [x] T9 字族扩展(款式待用户拍板)
|
||||
- claim: worker-one
|
||||
- boundary: src/styles/tokens.css、src/styles/global.css、public/admin/typography-classes.js、public/fonts/(新增字体切片)、docs/写作指南.md 字体清单
|
||||
- verify: `grep -c "font-" public/admin/typography-classes.js | awk '$1>=40'`
|
||||
@@ -27,5 +27,12 @@
|
||||
"image": "https://astro.build/og/astro.jpg",
|
||||
"favicon": "https://astro.build/favicon.svg",
|
||||
"ts": 1793593759816
|
||||
},
|
||||
"https://astro.build": {
|
||||
"title": "Astro",
|
||||
"desc": "Astro builds fast content sites, powerful web applications, dynamic server APIs, and everything in-between.",
|
||||
"image": "https://astro.build/og/astro.jpg",
|
||||
"favicon": "https://astro.build/favicon.svg",
|
||||
"ts": 1793621147252
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
pubDate: 2026-11-02
|
||||
tags: [站点更新]
|
||||
text: |
|
||||
瞬间现在和文章同级表达力:`text` 支持 markdown 与排版类,<span class="c-teal font-kai">这一句是青色 + 楷体</span>,也可以**加粗**。
|
||||
|
||||
另外每条瞬间自带评论区——展开才加载,不刷时间线就不为它付成本。
|
||||
---
|
||||
|
||||
站点更新记录:瞬间富文本与逐条评论上线。
|
||||
@@ -0,0 +1,125 @@
|
||||
---
|
||||
title: "Writing template: this post is the template"
|
||||
pubDate: 2026-11-02
|
||||
category: tech
|
||||
tags: [writing, template]
|
||||
pinned: true
|
||||
locked: true
|
||||
description: One post that exercises every typographic capability on the site — heading levels, colour emphasis, typefaces, quotes, code windows, tables, images, link cards, folding. Copy it to start a new post.
|
||||
---
|
||||
|
||||
This is not a tutorial — it is a **template you can copy**. Every section below actually runs: what you see is what Markdown produces. To start a new post, copy this file, change the slug, and delete what you do not need.
|
||||
|
||||
It is also the live sample for **pinning**: `pinned: true` in the frontmatter lifts a post to the front of the home page, the archive and the bookshelf, while its date, URL and RSS timestamps stay untouched.
|
||||
|
||||
## 1. Heading levels and paragraphs
|
||||
|
||||
Use `##` for a section and `###` for a subsection. **Never use `#`** — the post title comes from the frontmatter `title`, and a second level-one heading confuses both screen readers and search snippets.
|
||||
|
||||
Leave a blank line between paragraphs. Inline you can use **bold**, *italic*, ~~strikethrough~~, `inline code`, and [ordinary links](https://astro.build).
|
||||
|
||||
## 2. Colour emphasis: as much as you need
|
||||
|
||||
Colour is **no longer capped** (the old "three per page" limit was lifted on 2026-11-02). The one rule that remains is the shell: navigation, footer, buttons and background stay greyscale — the shell is temperament, the body is expression.
|
||||
|
||||
<span class="c-blue">First use: state one key conclusion in blue.</span> Then carry on normally.
|
||||
|
||||
<span class="c-red">Second use: warn about a trap in red.</span> For example, “after changing a Markdown plugin you must clear `node_modules/.astro`, or the change silently does nothing”.
|
||||
|
||||
<span class="c-green">Confirm a working path in green.</span> If a whole paragraph deserves emphasis, mark the whole paragraph.
|
||||
|
||||
There are eight hues (blue / teal / green / yellow / orange / red / pink / purple), each with a standard and a `-soft` variant, plus four greyscale classes (`ink-muted` / `ink-warm` / `ink-cool` / `ink-faint`) for lowering tone — those do not count against the colour budget.
|
||||
|
||||
## 3. Typefaces: change only what you wrap
|
||||
|
||||
Body text is sans-serif by default. To give one sentence a different temperament, wrap it:
|
||||
|
||||
<span class="font-kai">This sentence is in Kai. Good for epigraphs, quoted lines in a review, or paragraphs that should be read slowly.</span>
|
||||
|
||||
<span class="font-huiwen">This sentence is in Huiwen Ming. Over a long paragraph it noticeably slows reading, so use it for short lines only.</span>
|
||||
|
||||
Six families are available (`font-kai`, `font-song`, `font-hei-sys`, `font-fangsong`, `font-huiwen`, `font-kuaile`), plus an artistic heading class `font-art-kuaile`. **Mind the weight**: switching a whole page to a family makes every visitor download a CJK font, so avoid changing the body default casually.
|
||||
|
||||
### A level-three heading looks like this
|
||||
|
||||
One step smaller than level two, bold, with tighter spacing. Use it to split a section into topics.
|
||||
|
||||
## 4. Quotes and lists
|
||||
|
||||
> Block quotes are for quoting someone. An attribution can follow as italics.
|
||||
>
|
||||
> — an invented attribution
|
||||
|
||||
Unordered list:
|
||||
|
||||
- First item
|
||||
- Second item, with `inline code`
|
||||
- Third item, nested:
|
||||
- Child one
|
||||
- Child two
|
||||
|
||||
Ordered lists suit steps:
|
||||
|
||||
1. Copy the template
|
||||
2. Change the slug and title in the frontmatter
|
||||
3. Delete the sections you do not need
|
||||
|
||||
## 5. Code blocks
|
||||
|
||||
Blocks of four lines or more get a greyscale window bar (three dots); shorter fragments do not, so small snippets do not look like screenshots.
|
||||
|
||||
```ts
|
||||
// Sort contract: pinned first → newest first → id as a tie-breaker
|
||||
export function sortPosts<T extends Post>(posts: T[]): T[] {
|
||||
return [...posts].sort((a, b) => {
|
||||
const pin = Number(b.data.pinned === true) - Number(a.data.pinned === true);
|
||||
return pin !== 0 ? pin : b.data.pubDate.getTime() - a.data.pubDate.getTime();
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
Refer to variables inline like `posts`.
|
||||
|
||||
## 6. Tables
|
||||
|
||||
| Capability | Syntax | Note |
|
||||
| --- | --- | --- |
|
||||
| Colour emphasis | `<span class="c-blue">` | no limit (shell stays greyscale) |
|
||||
| Typeface | `<span class="font-kai">` | a whole page adds font weight |
|
||||
| Image | `` | see the writing guide for size rules |
|
||||
| Video | frontmatter `video:` | lives in public/media, ≤50MB |
|
||||
| Link card | a bare URL on its own line | OG fetched at build time, falls back to a plain link |
|
||||
|
||||
## 7. Images
|
||||
|
||||
Reference images by relative path, stored under `content/posts/assets/<slug>/`; they expand on click and vectors stay sharp:
|
||||
|
||||

|
||||
|
||||
## 8. Link cards
|
||||
|
||||
A bare URL on its own line becomes an OG preview card at build time; if the fetch fails it degrades to a plain link, never breaking the layout:
|
||||
|
||||
https://astro.build
|
||||
|
||||
## 9. Folding long content
|
||||
|
||||
Long appendices, full logs and big configs can fold away with zero JavaScript, using native `details`:
|
||||
|
||||
<details>
|
||||
<summary>Open the folded example</summary>
|
||||
|
||||
A folded region can hold anything — paragraphs, lists, code blocks.
|
||||
|
||||
- Best for “read on demand” detail
|
||||
- Not for conclusions (readers may never open it)
|
||||
|
||||
</details>
|
||||
|
||||
## 10. Closing after the rule
|
||||
|
||||
---
|
||||
|
||||
Three sentences are enough to close: what this covered, what the reader should take away, and where to go next. The template is not a demand to write ten sections — it is a promise that **the capabilities are at hand when you need to express something**.
|
||||
|
||||
Next time, copy this file and delete until only your point remains.
|
||||
@@ -24,8 +24,10 @@ draft: false
|
||||
|
||||
<p class="ink-accent-soft">弱强调:秋天的海面像一块被风抚平的铁皮。</p>
|
||||
|
||||
<p class="font-kai-light">霞鹜文楷 Light(自托管 300):秋天的海面像一块被风抚平的铁皮。</p>
|
||||
|
||||
<p class="c-blue">蓝色标准:秋天的海面像一块被风抚平的铁皮。</p>
|
||||
<p class="c-yellow">黄色标准(已压暗):秋天的海面像一块被风抚平的铁皮。</p>
|
||||
<p class="c-red-soft">红色 soft:秋天的海面像一块被风抚平的铁皮。</p>
|
||||
|
||||
<p class="font-huiwen">汇文明朝体(OFL,切片自托管):秋天的海面像一块被风抚平的铁皮。</p>
|
||||
|
||||
<p class="font-kuaile">站酷快乐体(OFL,艺术字):秋天的海面像一块被风抚平的铁皮。</p>
|
||||
@@ -0,0 +1,124 @@
|
||||
---
|
||||
title: "写作模板:这篇文章本身就是模板"
|
||||
pubDate: 2026-11-02
|
||||
category: tech
|
||||
tags: [写作, 模板]
|
||||
pinned: true
|
||||
description: 一篇文章演示全站可用的排版能力:标题层级、彩色强调、字族、引用、代码窗、表格、图、链接卡、折叠。复制它开始写新文。
|
||||
---
|
||||
|
||||
这篇不是教程,是**可复制的模板**。下面每一节都在真跑——你看到的效果,就是 Markdown 源码能产出的效果。写新文章时,把本文件复制一份、改个 slug、删掉不需要的段落即可。
|
||||
|
||||
它同时是**置顶功能**的活样本:`frontmatter` 里的 `pinned: true` 会让它排在首页、归档与书架的最前面,而日期、URL、订阅源里的时间都不变。
|
||||
|
||||
## 一、标题层级与段落
|
||||
|
||||
正文用 `##` 作一级节标题,`###` 作二级。**不要用 `#`**——文章标题由 frontmatter 的 `title` 渲染,正文再写 `#` 会出现两个一级标题,屏幕阅读器与搜索摘要都会困惑。
|
||||
|
||||
段落之间空一行即可。行内可以**加粗**、*斜体*、~~删除线~~、`行内代码`,也可以放[普通链接](https://astro.build)。
|
||||
|
||||
## 二、彩色强调:想用多少用多少
|
||||
|
||||
颜色现在**没有次数限制**(2026-11-02 起取消了原来的「一页最多三处」)。唯一还守着的是外壳:导航、页脚、按钮、背景保持灰阶——外壳是气质,正文是表达。
|
||||
|
||||
<span class="c-blue">第一处:一个关键结论用蓝色提出。</span> 后面照常叙述。
|
||||
|
||||
<span class="c-red">第二处:需要警示的坑用红色。</span> 比如「改完 Markdown 插件必须清 `node_modules/.astro`,否则改动静默不生效」。
|
||||
|
||||
<span class="c-green">确认可行的路径用绿色。</span> 想让整段都是重点也不必克制,把它们都标出来即可。
|
||||
|
||||
可用色相共 8 组(blue / teal / green / yellow / orange / red / pink / purple),每组有标准与 `-soft` 两档;另有 4 个灰阶层级类(`ink-muted` / `ink-warm` / `ink-cool` / `ink-faint`)用于降调,不算彩色额度。
|
||||
|
||||
## 三、字族:换字体只作用于被包裹的文字
|
||||
|
||||
正文默认是无衬线。想给某句话换气质,用类包起来就行:
|
||||
|
||||
<span class="font-kai">这一句是楷体。适合题记、书评里引的原句,或者需要慢一点读的段落。</span>
|
||||
|
||||
<span class="font-huiwen">这一句是汇文明朝体。长段落用它会明显降低阅读速度,所以只用在短句上。</span>
|
||||
|
||||
可用字族共 6 族(`font-kai` 楷、`font-song` 宋、`font-hei-sys` 黑、`font-fangsong` 仿宋、`font-huiwen` 汇文明朝、`font-kuaile` 快乐体),另有 `font-art-kuaile` 艺术字标题类。**注意体积**:整页换字族会让访问者下载一份中文字体,正文默认字体不要轻易改。
|
||||
|
||||
### 三级标题长这样
|
||||
|
||||
比二级小一档、加粗、上下留白更紧。用于把一节拆成几个小话题。
|
||||
|
||||
## 四、引用与列表
|
||||
|
||||
> 引用块用于摘录别人的话。署名可以换成斜体放在最后。
|
||||
>
|
||||
> —— 一句虚构的署名
|
||||
|
||||
无序列表:
|
||||
|
||||
- 第一项
|
||||
- 第二项,可以带 `行内代码`
|
||||
- 第三项,可以嵌套:
|
||||
- 子项一
|
||||
- 子项二
|
||||
|
||||
有序列表适合步骤:
|
||||
|
||||
1. 复制模板
|
||||
2. 改 frontmatter 的 slug 与标题
|
||||
3. 删掉不需要的段落
|
||||
|
||||
## 五、代码块
|
||||
|
||||
四行及以上的代码块会带上灰阶窗条(三个点),短代码块不加——避免小片段看着像截图。
|
||||
|
||||
```ts
|
||||
// 排序契约:置顶优先 → 时间倒序 → id 兜底
|
||||
export function sortPosts<T extends Post>(posts: T[]): T[] {
|
||||
return [...posts].sort((a, b) => {
|
||||
const pin = Number(b.data.pinned === true) - Number(a.data.pinned === true);
|
||||
return pin !== 0 ? pin : b.data.pubDate.getTime() - a.data.pubDate.getTime();
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
行内提到变量时用 `posts` 这种形式即可。
|
||||
|
||||
## 六、表格
|
||||
|
||||
| 能力 | 写法 | 注意 |
|
||||
| --- | --- | --- |
|
||||
| 彩色强调 | `<span class="c-blue">` | 不限次数(外壳仍灰阶) |
|
||||
| 换字族 | `<span class="font-kai">` | 整页换会拉字体体积 |
|
||||
| 图片 | `` | 体积纪律见写作指南 |
|
||||
| 视频 | frontmatter `video:` | 放 public/media,≤50MB |
|
||||
| 链接卡片 | 裸链接单独成段 | 构建期抓 OG,失败回落普通链接 |
|
||||
|
||||
## 七、图片
|
||||
|
||||
图片用相对路径引用,挂在 `content/posts/assets/<slug>/` 下,点击可放大(矢量图不糊):
|
||||
|
||||

|
||||
|
||||
## 八、链接卡片
|
||||
|
||||
一个裸链接单独成段,构建期会抓取 OG 预览生成卡片;抓不到就退回普通链接,不会坏版:
|
||||
|
||||
https://astro.build
|
||||
|
||||
## 九、折叠长内容
|
||||
|
||||
长附录、完整日志、大段配置可以折叠,零 JS,原生 `details`:
|
||||
|
||||
<details>
|
||||
<summary>展开看一个被折叠的长例子</summary>
|
||||
|
||||
折叠区里可以放任何东西——段落、列表、代码块。
|
||||
|
||||
- 适合放「按需查看」的细节
|
||||
- 不适合放结论(读者可能永远不展开)
|
||||
|
||||
</details>
|
||||
|
||||
## 十、分隔线之后收尾
|
||||
|
||||
---
|
||||
|
||||
收尾三句就够:这篇讲了什么、读者该带走什么、下一步去哪。模板不是要你写满十节,而是**在你需要表达时,能力都在手边**。
|
||||
|
||||
下一篇开始,复制本文件,删到只剩你要说的。
|
||||
+143
-18
@@ -1,28 +1,153 @@
|
||||
# 关于页正文(后台「页面文案 → 关于页」可改,markdown 编辑)
|
||||
# intro = 多段 markdown 正文,渲染在「关于」标题下方、名片之前;留空回落默认文案
|
||||
# 支持的语法:段落、列表、粗斜体、行内代码、链接(渲染走 Astro 的 markdown 管线)
|
||||
# 关于页(后台「页面文案 → 关于页」可改)
|
||||
# ── 一级:blocks 块式正文(按顺序渲染,可拖拽排序;某块 enabled: false 即隐藏)
|
||||
# ── 二级:intro 多段 markdown(仅在 blocks 为空时使用)
|
||||
# ── 三级:内置默认文案(两者都空时兜底,构建永不炸)
|
||||
layout:
|
||||
# 名片区(头像/名字/身份语/社交)开关:头像等数据事实源在「主页设置」
|
||||
showIdCard: true
|
||||
|
||||
blocks:
|
||||
- type: prose
|
||||
width: narrow
|
||||
body:
|
||||
zh: |
|
||||
<span class="font-huiwen c-red">写作者</span> · 读者 · 快门按压者,拥抱开源。
|
||||
|
||||
这里是我三条轨道的公开工作台:**读过的书**留下评注,**写过的代码**进项目墙,**按下的快门**收入图库。
|
||||
|
||||
所有内容都有一个明确去处:
|
||||
|
||||
- 长文沉淀在文章与书架
|
||||
- 短句与随手拍落在瞬间
|
||||
- 影像收进图库,配一点 EXIF 与一句策展
|
||||
|
||||
本站本身即开源,构建与发布全链都在仓里。
|
||||
en: |
|
||||
<span class="font-huiwen c-red">Writer</span> · Reader · Shutter-presser, embracing open source.
|
||||
|
||||
This is the public workbench of my three tracks: **books** that left notes, **code** that reaches the project wall, and **shutters** I pressed that land in the gallery.
|
||||
|
||||
Everything has a clear destination:
|
||||
|
||||
- Long-form writing goes to posts and the bookshelf
|
||||
- Short lines and snapshots land in moments
|
||||
- Images go to the gallery, with a bit of EXIF and one line of curation
|
||||
|
||||
This site is itself open source — the full build and deploy pipeline lives in the repo.
|
||||
|
||||
- type: tracks
|
||||
title:
|
||||
zh: 兴趣版图
|
||||
en: Interests
|
||||
items:
|
||||
- icon: book
|
||||
name:
|
||||
zh: 阅读
|
||||
en: Reading
|
||||
desc:
|
||||
zh: 文学与戏剧为主,读到哪写到哪。
|
||||
en: Mostly literature and drama; I write as I read.
|
||||
- icon: terminal
|
||||
name:
|
||||
zh: 技术
|
||||
en: Tech
|
||||
desc:
|
||||
zh: Linux、容器与自托管,把家里的小机器折腾成顺手的工具。
|
||||
en: Linux, containers, and self-hosting — turning the little machines at home into handy tools.
|
||||
- icon: camera
|
||||
name:
|
||||
zh: 摄影
|
||||
en: Photography
|
||||
desc:
|
||||
zh: 街拍与山野,随身一台无反,在等光的间隙按快门。
|
||||
en: Street and landscapes with a mirrorless camera, pressing the shutter while waiting for the light.
|
||||
|
||||
- type: prose
|
||||
title:
|
||||
zh: 关于我
|
||||
en: About me
|
||||
body:
|
||||
zh: |
|
||||
气象是我起点的专业——一个想做预报的人,依旧把云图当日常读物。
|
||||
|
||||
写代码是第二条主线:Linux、容器、自托管,把日常工具一件件磨顺手。
|
||||
|
||||
作品大多在 [wait4x.online](https://wait4x.online/)。
|
||||
|
||||
信条:**拥抱开源,热爱开源**。
|
||||
en: |
|
||||
Meteorology is where I started — an aspiring forecaster who still reads weather charts for fun.
|
||||
|
||||
Coding is the second thread: Linux, containers, self-hosting — polishing everyday tools one by one.
|
||||
|
||||
Most of my work lives at [wait4x.online](https://wait4x.online/).
|
||||
|
||||
Motto: **embrace open source, love open source**.
|
||||
|
||||
- type: linklist
|
||||
title:
|
||||
zh: 找到我
|
||||
en: Find me
|
||||
items:
|
||||
- label:
|
||||
zh: 本站
|
||||
en: Website
|
||||
href: 'https://wait4x.online/'
|
||||
note:
|
||||
zh: 文章、图库与瞬间
|
||||
en: Posts, gallery and moments
|
||||
- label:
|
||||
zh: 邮件
|
||||
en: Email
|
||||
href: 'mailto:X@alumni.nudt.edu.cn'
|
||||
note:
|
||||
zh: 有话直说
|
||||
en: Say hello directly
|
||||
- label:
|
||||
zh: GitHub
|
||||
en: GitHub
|
||||
href: 'https://github.com/wait4xx'
|
||||
note:
|
||||
zh: 开源项目与提交
|
||||
en: Open-source projects and commits
|
||||
- label:
|
||||
zh: 自托管 gitea
|
||||
en: Self-hosted gitea
|
||||
href: 'https://git.wait4x.online/wait4x/blog'
|
||||
note:
|
||||
zh: 本站源码
|
||||
en: Source of this site
|
||||
- label:
|
||||
zh: RSS
|
||||
en: RSS
|
||||
href: /rss.xml
|
||||
note:
|
||||
zh: 订阅更新
|
||||
en: Subscribe to updates
|
||||
|
||||
- type: prose
|
||||
title:
|
||||
zh: 本站如何构建
|
||||
en: How this site is built
|
||||
body:
|
||||
zh: 页面由 Astro 静态生成,不含运行时框架 JS;内容住在自托管 gitea——本地编辑器与 gitea 网页写的是同一个 git 仓库,push 之后由 homelab 自动构建发布;评论走自托管 Artalk。页脚 built from 一行链到本次构建对应的 commit,构建过程透明可追溯。
|
||||
en: Pages are statically generated by Astro with no runtime framework JS. Content lives on a self-hosted gitea — my local editor and the gitea web UI write to the same git repository, and each push is built and published automatically by the homelab. Comments run on self-hosted Artalk. The built-from line in the footer links to the commit behind this build, keeping the process transparent and traceable.
|
||||
|
||||
# ---- 二级回退(blocks 为空时使用)----
|
||||
intro:
|
||||
zh: |
|
||||
写作者 · 读者 · 快门按压者,拥抱开源。
|
||||
|
||||
这里是我三条轨道的公开工作台:**读过的书**留下评注,**写过的代码**进项目墙,**按下的快门**收入图库。
|
||||
|
||||
所有内容都有一个明确去处:
|
||||
|
||||
- 长文沉淀在文章与书架
|
||||
- 短句与随手拍落在瞬间
|
||||
- 影像收进图库,配一点 EXIF 与一句策展
|
||||
|
||||
本站本身即开源,构建与发布全链都在仓里。
|
||||
en: |
|
||||
Writer · Reader · Shutter-presser, embracing open source.
|
||||
|
||||
This is the public workbench of my three tracks: **books** that left notes, **code** that reaches the project wall, and **shutters** I pressed that land in the gallery.
|
||||
|
||||
Everything has a clear destination:
|
||||
|
||||
- Long-form writing goes to posts and the bookshelf
|
||||
- Short lines and snapshots land in moments
|
||||
- Images go to the gallery, with a bit of EXIF and one line of curation
|
||||
|
||||
This site is itself open source — the full build and deploy pipeline lives in the repo.
|
||||
# 页面级排版覆盖(层 2)
|
||||
# 页面级排版覆盖(后台「关于页 → 页面排版覆盖」可改):
|
||||
# 整页换字族会带来字体体积成本(如 font-huiwen 整页 ≈694KB),默认留空=全站默认排版
|
||||
# typography:
|
||||
# font: font-huiwen # 字族:font-serif/font-sans/font-kai/font-huiwen/font-kuaile…
|
||||
# accent: c-red # 强调色类:c-blue/c-red/…(外壳仍灰阶)
|
||||
# scale: 1.05 # 字号缩放 0.8–1.25
|
||||
+10
-36
@@ -1,44 +1,18 @@
|
||||
# 主页身份语(后台「页面文案 → 主页设置」可改)
|
||||
# 全部字段留空 = 现有形态(字段级回落默认值,零配置零变化)
|
||||
tagline:
|
||||
zh: 写作者 · 读者 · 快门按压者,拥抱开源。
|
||||
en: Writer · Reader · Shutter-presser — embracing open source.
|
||||
|
||||
# 副标题(可选,markdown 短文本):渲染在身份语下方,留空不渲染
|
||||
# subtitle:
|
||||
# zh: '正在把**阅读、代码与影像**收进同一个工作台'
|
||||
# en: 'Collecting **reading, code and images** into one workbench'
|
||||
|
||||
# 头像(可选):上传后自动写入 ./assets/avatar-xxx.jpg(走 Astro 图片管线)
|
||||
# 留空 = 回退内置渐变占位
|
||||
# avatar: ./assets/avatar.jpg
|
||||
|
||||
# 站点图标(favicon,全局生效):public 下的路径;留空回退 /favicon.svg
|
||||
# 上传走 public/icons/(后台媒体库),可填 /icons/my.ico、/icons/my.svg
|
||||
favicon: /favicon.svg
|
||||
|
||||
# 社交链接(顺序即展示顺序,后台可增删拖动排序)
|
||||
# icon 取值:github / rss / email / bilibili / x / 留空(留空按 label 显示纯文字)
|
||||
socials:
|
||||
- label: GitHub
|
||||
url: https://github.com/wait4xx
|
||||
icon: github
|
||||
- label: RSS
|
||||
url: /rss.xml
|
||||
icon: rss
|
||||
- label: 邮件
|
||||
url: mailto:admin@wait4x.online
|
||||
icon: email
|
||||
|
||||
# 最新影像张数(1-6,默认 4;填 0 = 隐藏该区块)
|
||||
zh: 拥抱开源,热爱开源
|
||||
en: Embracing open source, loving open source.
|
||||
latestPhotos: 4
|
||||
|
||||
# 首页每轨文章条数(4-8,默认 6)
|
||||
perTrackLimit: 6
|
||||
|
||||
# 区块开关(默认全开;关掉的区块不渲染)
|
||||
sections:
|
||||
tracks: true
|
||||
photos: true
|
||||
heatmap: true
|
||||
projects: true
|
||||
favicon: /favicon.svg
|
||||
socials:
|
||||
- label: GitHub
|
||||
url: https://github.com/wait4xx
|
||||
icon: github
|
||||
- label: 邮件
|
||||
url: mailto:X@alumni.nudt.edu.cn
|
||||
icon: email
|
||||
@@ -1,22 +1,39 @@
|
||||
# 打字机动效文案(档案卡顶部逐字轮播;每行双语,最多 6 行;留空用内置默认三行)
|
||||
typing:
|
||||
- zh: 气象出身,写代码为乐
|
||||
en: Trained in meteorology, coding for fun
|
||||
- zh: 自托管一切能自托管的东西
|
||||
en: Self-hosting whatever can be self-hosted
|
||||
- zh: 拥抱开源,热爱开源
|
||||
en: Embrace open source, love open source
|
||||
- zh: 书与快门都在一个工作台
|
||||
en: Books and shutters, one workbench
|
||||
|
||||
# 开篇叙述(5j T1):markdown 富文本,链接内联写 `[文字](url)`。
|
||||
# 非空时整段走富文本管线(净化 + 排版类白名单),**取代**下面的 blurb + repo_* + blurb_after 拼接。
|
||||
story:
|
||||
zh: "本站本身即开源:[git.wait4x.online/wait4x/blog](https://git.wait4x.online/wait4x/blog) —— Astro SSG、双写作流、构建发布全链皆在仓中。以下是我在自托管 gitea 与 GitHub 上的全部公开仓库,以及最近一年的贡献活动。"
|
||||
en: "This site is itself open source: [git.wait4x.online/wait4x/blog](https://git.wait4x.online/wait4x/blog) — the Astro SSG, dual-writing workflow, and the full build & deploy pipeline live in the repo. Below are all my public repositories on self-hosted gitea and GitHub, plus contributions from the past year."
|
||||
|
||||
# ---- legacy(story 为空时才用)----
|
||||
# 下面四个字段是 5j 之前「两半夹链接」的写法,保留只为旧 yaml / 回退演示;新改动请一律写 story。
|
||||
blurb:
|
||||
zh: 本站本身即开源:
|
||||
en: 'This site is itself open source:'
|
||||
repo_url: https://git.wait4x.online/wait4x/blog
|
||||
repo_label: git.wait4x.online/wait4x/blog
|
||||
# legacy:story 为空时接在 story 占位处拼接(见上方 blurb 注释)
|
||||
blurb_after:
|
||||
zh: —— Astro SSG、双写作流、构建发布全链皆在仓中。以下是我在自托管 gitea 与 GitHub 上的全部公开仓库,以及最近一年的贡献活动。
|
||||
en: — the Astro SSG, dual-writing workflow, and the full build & deploy pipeline live in the repo. Below are all my public repositories on self-hosted gitea and GitHub, plus contributions from the past year.
|
||||
|
||||
# ---- 项目墙可见性与排序(后台「开源页」可改;留空/缺省 = 现状) ----
|
||||
# mode: all=全部(默认) | only=仅 include 列出 | exclude=排除 exclude 列出
|
||||
mode: all
|
||||
# 白名单(mode=only 时生效)
|
||||
include: []
|
||||
# 黑名单(mode=exclude 时生效)
|
||||
exclude: []
|
||||
# 排序:pinned-first=置顶优先(默认) | stars=星数降序 | updated=最近更新
|
||||
sort: pinned-first
|
||||
# 来源过滤:both=双源合并(默认) | github | gitea
|
||||
perSource: both
|
||||
# 开篇文案来源:off=用本文件 blurb(默认) | github-readme=构建期取 wait4xx/wait4xx 的 README 渲染
|
||||
perSource: github
|
||||
intro_from: "off"
|
||||
|
||||
# ---- 开篇来源说明(2026-11-02 Phase 5h)----
|
||||
# 开篇已改原生 GitHub 档案卡(头像 / 统计四格 / 语言占比灰阶条),不再渲染 README 原文。
|
||||
# intro_from: "off" = 用档案卡 + 上面的 blurb(当前)
|
||||
# intro_from: github-readme = 构建期取 wait4xx/wait4xx 的 README 原样渲染(**不推荐**:
|
||||
# 会引入 7 类第三方外链图,慢网或被墙时整片空白;README 用 <div align="center"> 居中,
|
||||
# 本站不认该属性 → 居中失效。这正是当初"内容没居中、图片不显示"的根因。)
|
||||
@@ -0,0 +1,37 @@
|
||||
# 主题旋钮(样式即数据 · 层 4):只放白名单内的 9 组旋钮。
|
||||
# 红线:--measure(版心)与断点/纸墨/code 配色不在此列——它们牵动版面结构与分轨身份。
|
||||
# 全部留空 = 沿用 src/styles/tokens.css 的既有值(零配置零变化)。
|
||||
# 钳制与单调性在 src/lib/theme.ts 里做:越界值被夹取而非报错,构建不会因主题配置失败。
|
||||
|
||||
# ① 强调色(明/暗两值;构建期闸门校验正文对比度 ≥4.5,不达标回落默认)
|
||||
accent_light: '#3454d1'
|
||||
accent_dark: '#8ea2ff'
|
||||
|
||||
# ② 图片圆角(4–16;08 文档裁定上限 14)
|
||||
radius_img: 10
|
||||
|
||||
# ③ 标题字号(22–32;必须 h1 > h2 > h3,否则整体回落默认)
|
||||
text_h1: 27
|
||||
text_h2: 22
|
||||
text_h3: 19
|
||||
|
||||
# ④ 正文字号(16–22)
|
||||
prose_size_reading: 20
|
||||
prose_size_tech: 19
|
||||
|
||||
# ⑤ 正文行高(1.5–2.0)
|
||||
leading_reading: 1.75
|
||||
leading_tech: 1.7
|
||||
|
||||
# ⑥ 间距梯度(8–80;必须严格递增 3 < 4 < 5 < 6,否则回落默认)
|
||||
space_3: 16
|
||||
space_4: 24
|
||||
space_5: 40
|
||||
space_6: 64
|
||||
|
||||
# ⑦ 段落流距(0–2em)
|
||||
flow_p: 0.9
|
||||
flow_h2_post: 0.6
|
||||
|
||||
# ⑧ 过渡时长(150–600ms)
|
||||
dur: 350
|
||||
+206
-12
@@ -2,6 +2,36 @@
|
||||
|
||||
> 适用:`content/posts/*.md`(书评与技术轨通用)。资产目录约定 + 三种媒体的写法 + 体积纪律。
|
||||
|
||||
## 写作模板(从这里开始,2026-11-02 起)
|
||||
|
||||
别从空白文件起步——**复制现成的模板**:
|
||||
|
||||
```bash
|
||||
cp content/posts/writing-template.md content/posts/my-new-post.md
|
||||
# 改 frontmatter 的 title / pubDate / tags / description,删掉不需要的段落
|
||||
```
|
||||
|
||||
模板本身是一篇真文章(线上读:`/archives/writing-template/`),每节都在跑一种能力,你看到的效果就是源码的效果:标题层级、彩色强调、字族切换、引用、列表、代码窗条、表格、图片放大、链接卡片、`<details>` 折叠、分隔线。
|
||||
|
||||
写之前只需记住三条纪律:
|
||||
- **彩色不限次数**(2026-11-02 用户拍板取消原来的"一页最多三处"):正文想用多少处颜色都可以;仍守的是另一条——**外壳灰阶**(导航/页脚/按钮/背景不染色),因为外壳是气质,正文是表达
|
||||
- **整页换字族要谨慎**:会给访问者拉一份中文字体(汇文明朝整页约 420KB,已子集化)
|
||||
- **折叠放细节不放结论**:`<details>` 里的内容可能永远没人展开
|
||||
|
||||
## 置顶文章
|
||||
|
||||
首页、归档、书架三处列表默认按日期倒序。想让某篇长期排在最前,frontmatter 加一行:
|
||||
|
||||
```yaml
|
||||
pinned: true
|
||||
```
|
||||
|
||||
语义边界(刻意设计成这样):
|
||||
- **只影响列表顺序**,不改 `pubDate`、不改 URL、不影响 RSS 的时间序列(订阅者不会看到时间错乱)
|
||||
- 多篇同时置顶时,它们内部仍按日期倒序
|
||||
- 列表里会显示一个灰阶「置顶」小徽标,取消置顶后立刻回到时间序
|
||||
- 后台「文章」编辑页有「置顶」开关,不必手改 frontmatter
|
||||
|
||||
## 目录约定
|
||||
|
||||
```
|
||||
@@ -66,6 +96,12 @@ public/media/my-post/clip.mp4 ← 文件放这里
|
||||
|
||||
见 `content/posts/media-sample.md`(draft 文章,仅构建验证用,不上线)。
|
||||
|
||||
## 灯箱说明文字(图库 caption)
|
||||
|
||||
图库照片的 `caption` 字段走同一套富文本管线:markdown(`**加粗**`、换行)+ 排版类(`<span class="c-blue font-kai">`)。灯箱里深底上的彩色会自动取暗底档。
|
||||
|
||||
图库灯箱关闭:右上 `×`、`Esc`、点背景、点图(1× 时)都能关;缩放后移动鼠标召唤控件。
|
||||
|
||||
## 链接卡片(OG 预览卡)
|
||||
|
||||
**一行只放一个链接、且它独立成段**时,构建期会抓取该页的 OG 信息渲染成卡片(标题 / 描述 / 站点图标 / 可选缩略图):
|
||||
@@ -88,7 +124,7 @@ https://astro.build/
|
||||
|
||||
正文里可用 `<span class="…">` / `<p class="…">` 做**受限**排版。构建期有白名单过滤:**只放行下表类名,其它 class 与 `style`、`on*` 属性一律被剥离**(防注入 + 保护灰阶骨架)。
|
||||
|
||||
### 颜色类(全部实测亮暗对比度 ≥4.5:1)
|
||||
### 灰阶层级类(全部实测亮暗对比度 ≥4.5:1)
|
||||
|
||||
| 类名 | 作用 |
|
||||
|---|---|
|
||||
@@ -100,18 +136,36 @@ https://astro.build/
|
||||
| `ink-accent` | 强调(链接色,少量点缀) |
|
||||
| `ink-accent-soft` | 弱强调(比强调淡一档) |
|
||||
|
||||
### 语义色板(8 色相 × 标准/soft;亮暗各自取值,实测全部 ≥4.5:1)
|
||||
|
||||
| 色相 | 标准类 | soft 类 | 亮态对比度(标准 / soft) | 暗态(标准 / soft) |
|
||||
|---|---|---|---|---|
|
||||
| 蓝 | `c-blue` | `c-blue-soft` | 6.46 / 4.58 | 7.70 / 4.61 |
|
||||
| 青 | `c-teal` | `c-teal-soft` | 5.00 / 4.57 | 9.77 / 4.52 |
|
||||
| 绿 | `c-green` | `c-green-soft` | 5.38 / 4.67 | 10.38 / 4.67 |
|
||||
| 黄 | `c-yellow` | `c-yellow-soft` | 6.45 / 4.57 | 11.73 / 4.52 |
|
||||
| 橙 | `c-orange` | `c-orange-soft` | 6.26 / 4.65 | 9.44 / 4.58 |
|
||||
| 红 | `c-red` | `c-red-soft` | 6.68 / 4.55 | 7.51 / 4.50 |
|
||||
| 粉 | `c-pink` | `c-pink-soft` | 6.96 / 4.73 | 8.87 / 4.52 |
|
||||
| 紫 | `c-purple` | `c-purple-soft` | 7.46 / 4.53 | 8.25 / 4.64 |
|
||||
|
||||
- 黄色在亮态被压暗到 `#7a5900`(原色在白底只有 ~1.8:1),这是**必须**的:彩色文字要能读
|
||||
- `soft` 档是"阈值内最淡值",适合次要强调;标准档用于关键强调
|
||||
|
||||
> **使用纪律(重要)**:彩色**只作用于文章正文的强调文字**。站点外壳(导航、页脚、按钮、背景、卡片边框)一律保持灰阶——整站不因正文用色而花掉。写作时一个页面里的彩色强调建议不超过 3 处。
|
||||
|
||||
### 字体类
|
||||
|
||||
| 类名 | 作用 | 备注 |
|
||||
|---|---|---|
|
||||
| `font-serif` | 思源宋(书评正文族) | 自托管 |
|
||||
| `font-kai` | 霞鹜文楷(金句族) | 自托管 |
|
||||
| `font-sans` | 思源黑(UI/技术族) | 自托管 |
|
||||
| `font-mono` | JetBrains Mono(标签/数值) | 自托管 |
|
||||
| `font-song` | 宋体族 | ⚠️ 系统字体,**依赖访客设备** |
|
||||
| `font-kai-sys` | 楷体族 | ⚠️ 系统字体 |
|
||||
| `font-hei-sys` | 黑体族 | ⚠️ 系统字体 |
|
||||
| `font-fangsong` | 仿宋族 | ⚠️ 系统字体 |
|
||||
| 类名 | 作用 | 字体 | 许可证 | 加载 |
|
||||
|---|---|---|---|---|
|
||||
| `font-serif` | 书评正文族 | Noto Serif SC | OFL-1.1 | 自托管 |
|
||||
| `font-kai` | 金句族 | 霞鹜文楷 Screen | OFL-1.1 | 自托管 |
|
||||
| `font-sans` | UI/技术族 | Noto Sans SC | OFL-1.1 | 自托管 |
|
||||
| `font-mono` | 标签/数值 | JetBrains Mono | OFL-1.1 | 自托管 |
|
||||
| `font-song` | 宋体族 | Noto Serif SC 优先,系统宋体兜底 | OFL-1.1 | **自托管优先** |
|
||||
| `font-kai-sys` | 楷体族 | 霞鹜文楷优先,系统楷体兜底 | OFL-1.1 | **自托管优先** |
|
||||
| `font-hei-sys` | 黑体族 | Noto Sans SC 优先,系统黑体兜底 | OFL-1.1 | **自托管优先** |
|
||||
| `font-fangsong` | 仿宋族 | FangSong/STFangsong | — | ⚠️ **纯系统回退**(暂无 OFL 自托管仿宋包) |
|
||||
|
||||
### 文学排版类(段落级)
|
||||
|
||||
@@ -121,7 +175,73 @@ https://astro.build/
|
||||
| `quote-line` | 金句块(左侧竖线 + 文楷) |
|
||||
| `fig-cap` | 图注(居中小字) |
|
||||
|
||||
> **系统字体类的前提**:`-sys` / `font-song` / `font-fangsong` 不下载任何字体文件,命中访客本机字体(macOS/iOS 有宋楷黑、Windows 有宋黑仿宋,Android 常缺楷仿宋)。**跨设备不保证一致**——要稳定请用自托管四族。
|
||||
### 首批字体(已落地,6 族 + 3 档自托管优先)
|
||||
|
||||
| 类名 | 字体 | 许可证 | webfont 嵌入 | 用到该字体的页面增量 |
|
||||
|---|---|---|---|---|
|
||||
| `font-serif` | Noto Serif SC | OFL-1.1 | ✅ OFL 允许 | 基线内 |
|
||||
| `font-sans` | Noto Sans SC | OFL-1.1 | ✅ | 基线内 |
|
||||
| `font-kai` | 霞鹜文楷 Screen | OFL-1.1 | ✅ | 基线内 |
|
||||
| `font-mono` | JetBrains Mono | OFL-1.1 | ✅ | 基线内 |
|
||||
| `font-huiwen` | 汇文明朝体(文风切片包 339 片) | OFL-1.1 | ✅ | **535KB** |
|
||||
| `font-kuaile` | 站酷快乐体 | OFL-1.1 | ✅ | **163KB** |
|
||||
| `font-song` / `font-kai-sys` / `font-hei-sys` | 上述自托管字体优先,系统字族兜底 | OFL-1.1 | ✅ | **+0KB** |
|
||||
|
||||
体积账结论(2026-11-02 实测,本地无 gzip 的相对值):**未使用某字体的页面,字体字节增量恒为 0**(构建期按页子集化 + unicode-range 按需拉取)。汇文采用文风开源的 cn-font-split 切片包(339 片),实测只拉用到字符的 19 片。
|
||||
|
||||
**再裁一刀(按本站实际用字子集化)**:切片包本身粒度粗(一片含数百字),只做标题/短句的艺术字体往往只用到几十个字符。`scripts/build-perf/subset-fonts.py` 读字体 CSS 的 `@font-face`、收集内容里真实出现的字符、逐片求交集后用 `pyftsubset` 重写——实测汇文整页 **694KB → 420KB**(约 −40%)。命令:
|
||||
|
||||
```bash
|
||||
python3 scripts/build-perf/subset-fonts.py \
|
||||
--css node_modules/@fontsource/huiwen-mincho/index.css \
|
||||
--out public/fonts/subset/huiwen \
|
||||
--chars-from content/posts content/moments content/site/*.yaml
|
||||
# 可选:--chars-mode all|headings(只统计标题字)、--min-bytes 4096(小于该值的片不裁)
|
||||
```
|
||||
|
||||
子集化产物输出到 `--out` 指定目录(如 `public/fonts/subset/`);把它接进站点(构建链挂守卫 + 字体 CSS 指向子集)是独立一步,接线后随构建自动生成,未接线前需手动跑。**换字/加字后重跑一次**,否则新字会回落到下一个切片或系统字体。
|
||||
|
||||
### 5i 字族扩展六款(2026-11-03,全站共 12 族)
|
||||
|
||||
| 类名 | 字体 | 适用场景 | 许可证 / 来源 | 切片 | 单页增量(实测) |
|
||||
|---|---|---|---|---|---|
|
||||
| `font-xiaowei` | 站酷小薇 ZCOOL XiaoWei | 文学味标题、书摘 | OFL-1.1 / google/fonts `ofl/zcoolxiaowei` | 100 片 | 44.9KB |
|
||||
| `font-huangyou` | 站酷庆科黄油体 | 圆润标题、栏目名 | OFL-1.1 / `ofl/zcoolqingkehuangyou` | 100 片 | 27.1KB |
|
||||
| `font-mashanzheng` | 马善政毛笔楷书 | 书法短句、题记 | OFL-1.1 / `ofl/mashanzheng` | 100 片 | 45.7KB |
|
||||
| `font-liujianmaocao` | 柳建毛草 | 草书点缀(只宜 1-2 字) | OFL-1.1 / `ofl/liujianmaocao` | 100 片 | 30.0KB |
|
||||
| `font-smiley` | 得意黑 Smiley Sans(Oblique) | 现代倾斜标题、强调句 | OFL-1.1 / `atelier-anchor/smiley-sans` v2.0.1 | 100 片 | 27.1KB |
|
||||
| `font-neoxihei` | 霞鹜新晰黑 LXGW Neo XiHei | 比思源黑更清晰的正文黑体 | OFL / `lxgw/LxgwNeoXiHei` | 96 片 | 30.0KB |
|
||||
|
||||
- 六款均按**站内用字**细切(约 19 字/片,单片 2–12KB),且只在内容里写到对应类时才加载(探测在 `src/lib/font-needs.ts`)——首页、归档列表这类无字体类的页面**零增量**。
|
||||
- **覆盖边界**:切片只含站内出现过的字(当前约 1900 字)。新写了字典外的新字(尤其生僻字)要先重跑切片,否则该字回落系统字族。
|
||||
- **重切命令**(字体源文件不入仓,按需从上面的来源下载):
|
||||
|
||||
```bash
|
||||
python3 scripts/build-perf/subset-fonts.py \
|
||||
--split-ttf /path/to/YourFont.ttf --family "Font Family Name" \
|
||||
--slices 100 --order-from public/fonts/subset/hwmc/fonts.css \
|
||||
--out public/fonts/<shortname> \
|
||||
--chars-from 'content/posts/*.md' 'content/site/*.yaml' 'content/moments/*.md'
|
||||
```
|
||||
|
||||
- **体积闸门**:单页增量 >150KB 必须子集化或降级。本次六款实测 27–46KB(单句场景)全部达标;@fontsource 官方中文包切片粒度约 33 字/片、单片 40KB+,一句话就 300–440KB/页,**已弃用**(改从原始 TTF 细切)。
|
||||
|
||||
### 候补字体清单(未落地,加一款约"一条命令 + 一次体积账")
|
||||
|
||||
| 字体 | 许可证 | 允许 webfont 嵌入 | 未落地原因 |
|
||||
|---|---|---|---|
|
||||
| HarmonyOS Sans | 华为字体许可(免费商用) | ✅ 明文允许"嵌入、捆绑、重新分发",需标注 | npm 无包,官网整包 52MB |
|
||||
| MiSans | 小米字体许可(免费商用) | ✅ 官方 FAQ 明确 "embedded fonts: Yes",需注明 | npm 无包 |
|
||||
| OPPO Sans | 免费授权(含商用) | ✅(附带"不向他方提供下载渠道"限制) | npm 无包 |
|
||||
| 阿里巴巴普惠体 | 免费商用全场景 | ✅ | npm 无包,9 字重体积巨大 |
|
||||
| 得意黑 Smiley Sans | OFL-1.1 | ✅ | 仅 GitHub release 整包,直链在当前网络不可达 |
|
||||
| 更纱黑体 Sarasa Gothic / Sarasa Mono | OFL-1.1 | ✅ | npm 包为空壳(需 cli 自生成切片) |
|
||||
| 花园明朝 HanaMinA/B | IPA Font License | ✅(允许嵌入) | 无 npm 包,仅兜底用 |
|
||||
| 站酷高端黑 / 站酷快乐体(已落) | OFL-1.1 | ✅ | 高端黑无 @fontsource 版本 |
|
||||
| 阿里妈妈东方大楷 / 演示悠然小楷 | 免费商用 / 个人免费 | ⚠️ 需逐款核嵌入条款 | 无包;"个人免费"字体常禁 @font-face |
|
||||
| 霞鹜文楷 Light/Bright | OFL-1.1 | ✅ | 实测 **+8605KB**(CJK 切片粒度所致) |
|
||||
|
||||
> 加新字体的流程:①核许可证与 **web 嵌入条款**(很多"个人免费"字体明文禁 @font-face,只能进系统回退)②npm 装或放入 `public/fonts/<family>/`(优选 cn-font-split 类切片包)③`global.css` 加类 + `rehype-class-whitelist` 放行 + `public/admin/typography-classes.js` 加一项 ④跑体积账,超 150KB/页 的要评估是否值得;展示类字体再跑一次 `scripts/build-perf/subset-fonts.py` 按实际用字裁切(命令见上节)。
|
||||
|
||||
```markdown
|
||||
<span class="ink-muted">次要说明</span>
|
||||
@@ -138,6 +258,77 @@ https://astro.build/
|
||||
后台编辑器里:**选中文字 → Insert 菜单 → 「排版(字体/颜色)」→ 面板里点一个类**,文字即被 `<span class="…">` 包住;点已套用的片段可改类,Backspace 可整体删除。
|
||||
面板里的两类与上表一一对应(字体 8 / 颜色 9),类清单单一事实源在 `public/admin/typography-classes.js`。
|
||||
|
||||
## 三层排版架构(全站,不只文章正文)
|
||||
|
||||
**层 1 · 页面文案富文本**:后台「页面文案」里的 tagline / subtitle / blurb / blurb_after / intro / 关于页 blocks 的 prose 块,全部走同一条富文本管线(markdown + raw + 内容净化 + class 白名单 + 链接卡片)。可以直接写:
|
||||
|
||||
```yaml
|
||||
tagline:
|
||||
zh: '<span class="font-kai c-blue">写作者</span> · 读者 · 快门按压者,拥抱开源。'
|
||||
```
|
||||
|
||||
渲染入口唯一:`src/lib/site-content.ts` 的 `renderRich()`(行内场景传 `{ inline: true }` 自动剥外层 `<p>`,否则 `<p>` 套 `<p>` 会被浏览器拆散)。
|
||||
**不开放**:导航/页脚/按钮等硬编码界面外壳——那是 UI 不是内容。
|
||||
|
||||
**层 2 · 页面级排版覆盖**:每个页面单例(`content/site/*.yaml`)可加:
|
||||
|
||||
```yaml
|
||||
typography:
|
||||
font: font-kai # 本页正文默认字体类(可选)
|
||||
accent: c-blue # 本页强调色类(可选)
|
||||
scale: 1.1 # 字号缩放 0.8–1.25(可选)
|
||||
```
|
||||
|
||||
实现是在该页内容容器上挂 `.page-typo` + CSS 变量(`--page-font / --page-accent / --page-scale`),**不写全站 tokens**——其它页面零影响(已在图库/书架/文章/装备页验证隔离)。白名单外的类名会被忽略。
|
||||
后台入口:「页面文案 → 各自页面 → 页面排版覆盖」。
|
||||
|
||||
**层 3 · 艺术字体**:`font-art-kuaile`(站酷快乐体,艺术字专用类)。⚠️ **仅用于标题/短句**:艺术字字形大,CJK 切片体积高(实测用到页 +163KB),长段使用会显著抬高单页字体成本。
|
||||
|
||||
## 瞬间(/moments)怎么写
|
||||
|
||||
瞬间与文章一样支持富文本,但入口在后台「瞬间」集合(`content/moments/*.md`):
|
||||
|
||||
```yaml
|
||||
---
|
||||
pubDate: 2026-11-02
|
||||
tags: [随手拍]
|
||||
text: 今天把**置顶**做完了,顺手试了 <span class="c-teal">彩色</span>
|
||||
---
|
||||
```
|
||||
|
||||
- `text` 支持 markdown(`**加粗**`、列表、`> 引用`)与**同一套排版类**(字体/颜色),因为走的是与全站文案相同的净化管线:危险标签整棵丢弃,只放行 42 项白名单类
|
||||
- 一条瞬间 = 一行时间线卡片,**每条都带独立评论区**(展开才挂载,双语页共用同一评论流,评论不分裂)
|
||||
- 每条的「浏览 / 评论」数字是只读统计(不会因为别人点开评论而虚增浏览)
|
||||
- `text` 与 `image` 至少填一项;图片建议先压缩
|
||||
|
||||
## 页面块(关于页 blocks,字段速查)
|
||||
|
||||
关于页正文可由九个块拼成(后台拖拽排序,每块有「显示」开关)。完整操作见 [管理台](./管理台.md);这里是写内容时对照的字段表——文本字段一律 `{zh, en}`。
|
||||
|
||||
| 块型 | 必填 | 可选 | 一句话 |
|
||||
|---|---|---|---|
|
||||
| `prose` | `body` | `title`、`width`(narrow/full) | markdown 长文 |
|
||||
| `tracks` | `title`、`items[].name` | `items[].icon`、`items[].desc` | 主线/能力面 |
|
||||
| `cards` | `items[].title` | `title`、`columns`(2/3)、`items[].desc/href/tag` | 并列卡片墙 |
|
||||
| `timeline` | `items[].time`、`items[].title` | `title`、`items[].desc` | 时间线(时间可自由文本) |
|
||||
| `quote` | `text` | `cite` | 单句引文 |
|
||||
| `figure` | `image` | `alt`、`caption` | 单图(必须上传,禁外链) |
|
||||
| `linklist` | `items[].label`、`items[].href` | `title`、`items[].note` | 链接清单 |
|
||||
| `cta` | `text`、`href`、`label` | — | 行动按钮 |
|
||||
| `divider` | — | `style`(line/space) | 分隔 |
|
||||
|
||||
```yaml
|
||||
blocks:
|
||||
- type: prose
|
||||
body: { zh: 正文 markdown,可含排版类 span, en: Body markdown }
|
||||
- type: cards
|
||||
columns: 3
|
||||
items:
|
||||
- { title: { zh: 写作, en: Writing }, desc: { zh: 书评与技术文, en: Reviews & tech } }
|
||||
```
|
||||
|
||||
边界:块数 ≤24、单块文本 ≤4000 字(超限截断并 WARN);未知块型跳过 + WARN;`blocks` 为空时回落 `intro` markdown,再空回落内置文案。图片字段走 `image()`,只能后台上传进 `content/site/assets/`。
|
||||
|
||||
## 发布提醒
|
||||
|
||||
- push main → webhook 自动构建发布(约 10 秒)
|
||||
@@ -230,6 +421,9 @@ markdown 原生语法,无需任何配置:
|
||||
| `latestPhotos` | 0-6 | 4 | 「最新影像」张数;**0 = 隐藏该区块** |
|
||||
| `perTrackLimit` | 4-8 | 6 | 首页阅读/技术两轨各显示条数 |
|
||||
| `sections` | `{tracks, photos, heatmap, projects}` 布尔 | 全开 | 区块开关,关掉即不渲染 |
|
||||
| `projectsLimit` | 1-99 | 6 | 首页项目墙条数(越界夹取) |
|
||||
| `announcement` | `{zh, en}`(markdown 富文本) | 空 | 名片区下方公告;空 = 不渲染 |
|
||||
| `postsTracks` | `{reading, tech, photo}` 布尔 | 全开 | 关掉某轨则该轨文章不在首页列出(归档页仍可见) |
|
||||
|
||||
越界值会被夹取(如 perTrackLimit=20 按 8 处理),不报错;**全部留空 = 现有形态不变**。
|
||||
|
||||
|
||||
+184
@@ -1,17 +1,201 @@
|
||||
# 管理台(Sveltia CMS)使用指南
|
||||
|
||||
> 地址:`https://wait4x.online/admin/`(页脚「管理」入口)| 用途:照片上传与 sidecar 编辑的图形界面 | 保存即 commit 到 main → webhook 自动构建发布(约 10 秒上线)
|
||||
>
|
||||
> 这份是**操作手册**:每个入口能改什么、字段怎么填、改完会发生什么。写文章正文的排版技巧见 [写作指南](./写作指南.md)——两份分工:这里讲"后台怎么用",那里讲"正文怎么写"。
|
||||
|
||||
## 登录
|
||||
|
||||
选 **Sign In with Token** → 用 gitea 的 access token(宿主 `~/docker_apps/gitea/init-templates.txt` 里的 api-token,或 gitea 网页新建 scope=read/write:repository)。token 存浏览器 localStorage,下次免登。
|
||||
|
||||
## 四类内容集合
|
||||
|
||||
| 入口 | 文件位置 | 用来放什么 | 关键约束 |
|
||||
|---|---|---|---|
|
||||
| 文章 | `content/posts/<slug>.md` | 书评 / 技术文 / 摄影札记三轨 | `category` 三选一(`reading` / `tech` / `photo`),首页分轨由它派生 |
|
||||
| 瞬间 | `content/moments/*.md` | 一句话 + 一张图的时间线 | `text` 与 `image` **至少填一项**(后台无法表达条件必填,保存前自查) |
|
||||
| 照片 | `content/photos/*.json` + 图 | 图库(街拍 / 风景两组) | 标题**必填**(空标题会让构建炸,实测坑);EXIF 由构建期 loader 自动补全 |
|
||||
| 站点页面 | `content/site/*.yaml` | 首页、关于页、开源页、主题 | 后台改的是**单例文件**,没有"新建" |
|
||||
|
||||
几条通用规则:
|
||||
|
||||
- **双语字段成对出现**:中文 / 英文两个输入框。英文留空不是错误——构建期回落中文并在日志里打 WARN(`[blocks] 缺译…`),页面不会破版。
|
||||
- **留空 = 用默认**:任何字段清空都回到仓库自带的默认文案 / 默认值,构建永远成功。想让某段彻底消失,请看字段自带的"留空则不渲染"提示。
|
||||
- **文章英文版**:不在后台写。跑 `python3 scripts/translate/translate_posts.py` 生成 `content/posts-en/` 同名文件;手写润色过的译文在 frontmatter 写 `locked: true`,翻译脚本会无条件跳过它。
|
||||
- **保存即提交**:后台保存等于向仓库提交,push 后约 10 秒自动构建上线。
|
||||
|
||||
## 文章置顶(5h 新增)
|
||||
|
||||
文章编辑页有一个「置顶」开关。打开后这篇文章排在首页分轨、归档、书架三处列表的最前面,列表里显示一个灰阶「置顶」小徽标。
|
||||
|
||||
语义边界(刻意如此,别当 bug 报):
|
||||
- **只影响列表顺序**:`pubDate` 不变、URL 不变、RSS 里的时间序列不变,订阅者不会看到时间错乱
|
||||
- **支持多篇/多本同时置顶**:置顶组内部仍按日期倒序;书架页(/books)里每个置顶的书评都会排在前面并**默认展开**
|
||||
- 书架页用「↑ + 横线」的回到顶部图标表示置顶(不写文字),与右侧展开箭头形状不同,不会混读
|
||||
- 关掉开关立即恢复时间序,没有"冷却期"
|
||||
|
||||
## 瞬间(5h 起与文章同级)
|
||||
|
||||
「瞬间」集合的「文字」字段是 **markdown 编辑器**,与文章同一套表达力:
|
||||
|
||||
- markdown:`**加粗**`、`*斜体*`、列表、`> 引用`、`` `行内代码` ``
|
||||
- 排版类 HTML:`<span class="c-blue font-kai">彩色 + 楷体</span>`(可用类清单见写作指南「文章排版类」一节,与文章完全同一份白名单)
|
||||
- 彩色**不再限次**(原来的"一页最多三处"已取消);仍守外壳灰阶:导航/页脚/按钮/背景不染色
|
||||
|
||||
**每条瞬间自带评论区**:时间线卡片下方有「评论」折叠条,读者点开才加载评论(不点开不为它付任何加载成本)。中文页与英文页共用同一条评论流,语言切换不会丢评论。
|
||||
|
||||
**每条的浏览 / 评论数字是只读的**:读者点开评论区不会虚增浏览数(只有整页访问才计一次)。
|
||||
|
||||
## 照片说明文字(灯箱右侧)支持排版(5i 新增)
|
||||
|
||||
照片的「说明」(caption)现在支持 markdown 与排版类,与文章正文同一套:
|
||||
|
||||
```yaml
|
||||
caption: 落日把海与城一齐烫成金箔,**这一帧等了二十分钟**
|
||||
```
|
||||
|
||||
```html
|
||||
<span class="c-blue">蓝色</span>、<span class="font-kai">楷体</span> 这些类都能用。
|
||||
```
|
||||
|
||||
灯箱是深底,颜色类会自动取柔和对暗底的那一档,不会刺眼。EXIF 那几行是相机数据,不做排版。
|
||||
|
||||
## 照片上传流
|
||||
|
||||
1. 图库照片 → New Entry:填标题(**必填**,空标题会让构建炸——实测坑)、选分组(street/landscape)、可写 alt
|
||||
2. 图片字段 → 资产库上传 jpg(与 sidecar 同目录落位,零重压缩)
|
||||
3. Save → 自动 commit + 构建 → 画廊出现(EXIF 构建期自动提取,exif 字段留空即可)
|
||||
|
||||
## 块编辑:关于页的块式页面构建器
|
||||
|
||||
关于页(`content/site/about.yaml`)的正文由 **blocks 列表**构成,后台里是一串可拖拽排序的卡片。每个块都有「显示」开关;关掉只是不渲染,块本身留在数据里。
|
||||
|
||||
两条渲染纪律:
|
||||
|
||||
- 列表为空(或全关)时,页面回落到 `intro` 字段的 markdown 正文;`intro` 也空则用内置默认文案。三级回落,任何一级为空都不会出白页。
|
||||
- 未知块型(手工编辑 yaml 写错的 `type`)会被**跳过并打印 `[blocks]` 警告**,不会炸构建;块数上限 24,单块文本上限 4000 字,超限截断并 WARN。
|
||||
|
||||
九类块与字段(文本字段一律 `{zh, en}` 双语对象):
|
||||
|
||||
| 块型 | 必填 | 可选 | 用途 |
|
||||
|---|---|---|---|
|
||||
| `prose` 正文 | `body` | `title`、`width`(`narrow` 版心 / `full` 通栏) | markdown 长文,最常用 |
|
||||
| `tracks` 版图 | `title`、`items[].name` | `items[].icon`、`items[].desc` | 几条主线 / 能力面,图标白名单外或留空显示首字占位 |
|
||||
| `cards` 卡片墙 | `items[].title` | `title`、`columns`(2 或 3,默认 3)、`items[].desc/href/tag` | 并列展示若干条目 |
|
||||
| `timeline` 时间线 | `items[].time`、`items[].title` | `title`、`items[].desc` | 时间列是自由文本(`2024 — 2026` 也行) |
|
||||
| `quote` 引文 | `text` | `cite` | 单句金句,配 `font-kai` 更出效果 |
|
||||
| `figure` 图片 | `image` | `alt`、`caption` | **只能后台上传**(落 `content/site/assets/`,走图片管线),禁止外链 |
|
||||
| `linklist` 链接清单 | `items[].label`、`items[].href` | `title`、`items[].note` | 相关阅读 / 外部资源 |
|
||||
| `cta` 行动按钮 | `text`、`href`、`label` | — | 一页建议不超过一个 |
|
||||
| `divider` 分隔 | — | `style`(`line` 细线 / `space` 纯留白) | 长页分段 |
|
||||
|
||||
yaml 形态示例(手工编辑时照此写):
|
||||
|
||||
```yaml
|
||||
blocks:
|
||||
- type: prose
|
||||
title: { zh: 关于这个站点, en: About this site }
|
||||
body:
|
||||
zh: 这里写 markdown 正文,支持列表、粗斜体、链接与排版类。
|
||||
en: Markdown body here.
|
||||
width: narrow
|
||||
- type: quote
|
||||
text: { zh: 所谓暮色,就是光慢慢地承认自己要走。, en: Dusk is light admitting it has to go. }
|
||||
cite: { zh: 本站, en: this site }
|
||||
- type: figure
|
||||
image: ./assets/desk.jpg # 后台会替你写成这个相对路径
|
||||
caption: { zh: 工作台一角, en: A corner of the desk }
|
||||
```
|
||||
|
||||
其它可用字段:`layout.showIdCard`(关掉则不渲染名片区,默认开)、`typography`(见下一节)。
|
||||
|
||||
**预览限制**:后台只提供字段编辑与列表摘要(摘要显示块型名),块的真实排版以下次构建后的页面为准——想调间距 / 字号,请用「主题设置」而不是在块里硬凑空行。
|
||||
|
||||
## 页面排版覆盖(每页一处)
|
||||
|
||||
首页 / 关于页 / 开源页各自有一个「页面排版覆盖(可选)」对象:
|
||||
|
||||
| 字段 | 取值 | 说明 |
|
||||
|---|---|---|
|
||||
| `font` | 字族下拉(10 项) | 本页**正文**默认字体类 |
|
||||
| `accent` | 颜色下拉(18 项) | 本页强调色类 |
|
||||
| `scale` | 0.8–1.25 | 字号缩放,1 = 全站默认 |
|
||||
|
||||
- 只作用于**该页内容容器**(挂 `.page-typo` + CSS 变量),不写全站 tokens,其它页面零影响。
|
||||
- **体积成本提醒**:整页换艺术/展示字族(`font-huiwen` 汇文、`font-kuaile` 快乐体)意味着整页正文都要下载对应切片——单页字体成本会明显抬升,见写作指南的体积账。
|
||||
- 白名单外的类名会被忽略(构建期过滤),写错不报错、也不生效。
|
||||
|
||||
## 主题设置(全站 17 个旋钮)
|
||||
|
||||
「主题设置」入口写 `content/site/theme.yaml`,17 项全部可选,留空 = 仓库默认值。**配错不会炸站**:越界值回落默认,组内单调性被破坏则整组回落。
|
||||
|
||||
| 组 | 字段 | 范围 | 约束语义 |
|
||||
|---|---|---|---|
|
||||
| 强调色 | `accent_light` / `accent_dark` | `#rrggbb` | 明暗各一份;构建期对比度闸门断言正文 ≥4.5:1,不达标构建失败 |
|
||||
| 圆角 | `radius_img` | 4–16 | 图片圆角,建议 ≤14 |
|
||||
| 标题字号 | `text_h1` / `text_h2` / `text_h3` | h1/h2:22–32,h3:16–32 | **必须严格递减**(h1 > h2 > h3),否则三档一起回落默认 |
|
||||
| 正文字号 | `prose_size_reading` / `prose_size_tech` | 16–22 | 书评 / 技术文分轨独立 |
|
||||
| 行高 | `leading_reading` / `leading_tech` | 1.5–2.0 | 同上分轨 |
|
||||
| 间距梯度 | `space_3` / `space_4` / `space_5` / `space_6` | 8–80 | **必须严格递增**,否则整组回落 |
|
||||
| 流距 | `flow_p` / `flow_h2_post` | 0–2(em) | 段间距 / 标题下间距 |
|
||||
| 动效 | `dur` | 150–600(ms) | 过渡时长;`prefers-reduced-motion` 下自动归零 |
|
||||
|
||||
不能配的东西(有意为止):版心宽度、断点、纸墨色、代码配色——它们牵动版面结构与分轨身份,属于设计红线。注入方式是构建期内联 `:root:root{…}`,运行时零 JS、零请求。
|
||||
|
||||
## 首页与开源页的其余旋钮
|
||||
|
||||
### 首页三字段(5g 新增)
|
||||
|
||||
| 字段 | 取值 | 默认 | 作用 |
|
||||
|---|---|---|---|
|
||||
| `projectsLimit` | 1–99 | 6 | 首页项目墙条数(越界夹取) |
|
||||
| `announcement` | `{zh, en}` markdown | 空 | 名片区下方公告;**空 = 不渲染**,走富文本管线(可写排版类 span) |
|
||||
| `postsTracks` | `{reading, tech, photo}` 布尔 | 全开 | 关掉某轨,该轨文章不在首页列出(归档页仍可见) |
|
||||
|
||||
### 开源页(opensource.yaml)
|
||||
|
||||
**开篇是原生「GitHub 档案卡」(5h 起)**:头像 / 名字@handle / 定位叙述 / 社交图标 / 统计四格(关注者、公开仓、总星标)/ 语言占比灰阶条(只有第一名用强调色,其余灰阶)。**零第三方图片、零 iframe**。数据失败只隐藏对应数字,不会破版。
|
||||
|
||||
想换那段「定位叙述」,改本文件的 `blurb` + `blurb_after`(双语,后台可改)——它们现在同时供给档案卡与项目墙下方的说明。
|
||||
|
||||
下面的 `intro_from: "github-readme"` **仍可用但不推荐**:那会把 GitHub 专用 README 原样渲染,带 7 类外链图(shields.io、readme-typing-svg、github-readme-stats、streak-stats、activity-graph、snake、komarev 访客徽章)与 `<div align="center">` 居中——本站按普通块渲染,故居中失效;外链图在慢网或被墙时全空白。这也是当初"部分内容没居中、有些图片没显示"的根因。
|
||||
|
||||
| 字段 | 取值 | 说明 |
|
||||
|---|---|---|
|
||||
| `pinned` | 仓短名数组 | 置顶顺序即展示顺序 |
|
||||
| `mode` | `all` / `only` / `exclude` | 可见性模式 |
|
||||
| `include` / `exclude` | 仓短名数组 | 对应模式的白 / 黑名单 |
|
||||
| `sort` | `pinned-first` / `stars` / `updated` | 排序 |
|
||||
| `perSource` | `both` / `github` / `gitea` | 来源过滤 |
|
||||
| `intro_from` | `"off"` / `"github-readme"` | 开篇文案来源(**带引号写**,YAML 1.1 里裸 `off` 是布尔 false) |
|
||||
|
||||
**`mode: only` 的"看不见"排查**:构建日志会给每个没显示出来的 include 项打一行 `[opensource] projects:include 里的「X」…`,并区分两种原因——双源结果里根本没有这个仓(拼写 / 私有仓 / 该源刚失败),或者命中了但被 `perSource` 过滤掉。看到哪一行就知道该改什么。
|
||||
|
||||
**README 开篇(`intro_from: "github-readme"`)的回落矩阵**,构建日志逐级点名:
|
||||
|
||||
1. 取到 → 内容有变化才刷新缓存 `content/.github-readme.md`(入仓,保证构建确定性)
|
||||
2. token 缺位 / HTTP 非 2xx / 内容为空 / 超时(10 秒)/ 网络异常 → 改用缓存(日志标注"可能陈旧")
|
||||
3. 缓存也不在 → 回落本文件 `blurb` 的叙述文案
|
||||
4. 缓存回写失败(构建仓只读挂载等)→ 只告警,本次渲染照常
|
||||
|
||||
token 位在 `~/docker_apps/blog/github-token.txt`(本机,0600)。
|
||||
|
||||
**两条已知限制**:
|
||||
|
||||
- **README 里的相对链接不重写**(如 `./img/x.png`、`docs/foo.md`):它们相对 GitHub 仓根解析,在本站没有对应产物,点了多半 404。保原样比猜一个基址可信,所以不做改写。
|
||||
- **外部徽章图片加固但不参与构建**:shields.io、readme-typing-svg 这类第三方徽章在构建期**不抓取**,渲染时统一补 `loading="lazy"`(缺 alt 补空),它们慢、被墙或 404 都不影响页面渲染;尺寸已由样式限制在内容宽度内。
|
||||
|
||||
## 浏览与评论计数(5h 新增)
|
||||
|
||||
文章页、瞬间页、开源页、关于页都用同一枚灰阶徽标:**眼睛图标 = 浏览数,气泡图标 = 条评论**,数据来自自建 Artalk,不依赖任何第三方徽章服务。
|
||||
|
||||
几条行为约定(省得当成 bug):
|
||||
|
||||
- **一页只计一次浏览**:「计一次」发生在这页唯一那个 `page` 模式徽标(文章页在标题下方),同一页其余徽标都是只读镜像,显示的是同一个数字——不会出现"页头 17、评论区 16"
|
||||
- **开评论不算浏览**:瞬间逐条徽标是只读的,读者展开评论区不会给那条动态虚增浏览量
|
||||
- **接口挂掉显示「–」而不是 0**:宁可留白,也不给读者看一个错误的零
|
||||
- **换页重计、同页刷新重计**:这是浏览量的定义,不是重复计数的 bug(软导航回同一页也算新一次访问)
|
||||
|
||||
## 纪律(与写作指南同源)
|
||||
|
||||
- **图片体积**:上传前自行压缩(长边 ≤2400px)——Sveltia 内建优化**刻意关闭**(会抹 EXIF 断构建期提取管线)
|
||||
|
||||
@@ -33,10 +33,19 @@ pnpm astro build
|
||||
# 按页字体 CSS 子集化(车道 A 任务 1):裁 @font-face 声明数,构建仓与本地同链;
|
||||
# 存在性守卫:新文件未推送到 main 前构建仓无此脚本,绝不让发布链因车道在途文件断掉
|
||||
[ -f scripts/build-perf/subset-font-css.mjs ] && node scripts/build-perf/subset-font-css.mjs dist || echo "[build] subset 脚本未就绪,跳过"
|
||||
[ -f scripts/build-perf/config-check.py ] && python3 scripts/build-perf/config-check.py || echo "[build] config-check 未就绪,跳过"
|
||||
[ -f scripts/build-perf/contrast-check.mjs ] && node scripts/build-perf/contrast-check.mjs dist || echo "[build] contrast 脚本未就绪,跳过"
|
||||
# 首页阻塞 CSS 内联(车道 D):守卫先行同上——脚本未到 main 前绝不炸链
|
||||
[ -f scripts/build-perf/gen-sw-precache.mjs ] && node scripts/build-perf/gen-sw-precache.mjs dist || echo "[build] sw-precache 生成器未就绪,跳过"
|
||||
[ -f scripts/build-perf/inline-home-css.mjs ] && node scripts/build-perf/inline-home-css.mjs || echo "[build] inline-home 脚本未就绪,跳过"
|
||||
|
||||
# README 动效资产(5i T8):**不在此跑抓取**(避免发布链被外网抖动拖累),只做存在性守卫 + 缺资产告警
|
||||
if [ -f public/github-assets/manifest.json ]; then
|
||||
echo "[build] github-assets 就绪:$(find public/github-assets -name '*.svg' | wc -l) 个自托管 SVG"
|
||||
else
|
||||
echo "[build] WARN public/github-assets/manifest.json 缺失——开源页数据板将只显示原生统计(可手动跑 node scripts/build-perf/fetch-readme-assets.mjs 补齐)"
|
||||
fi
|
||||
|
||||
# 内容 lint(T3):断链/缺图在部署前拦截;CONTENT_LINT=skip|warn 逃生门见脚本头注
|
||||
[ -f scripts/build-perf/content-lint.mjs ] && node scripts/build-perf/content-lint.mjs dist || echo "[build] content-lint 未就绪,跳过"
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"@fontsource-variable/noto-serif-sc": "^5.3.1",
|
||||
"@fontsource/jetbrains-mono": "^5.3.0",
|
||||
"@fontsource/noto-sans-sc": "^5.3.1",
|
||||
"@fontsource/zcool-kuaile": "^5.3.0",
|
||||
"astro": "^7.4.4",
|
||||
"astro-pagefind": "^2.0.1",
|
||||
"exifr": "^7.1.3",
|
||||
|
||||
Generated
+8
@@ -26,6 +26,9 @@ importers:
|
||||
'@fontsource/noto-sans-sc':
|
||||
specifier: ^5.3.1
|
||||
version: 5.3.1
|
||||
'@fontsource/zcool-kuaile':
|
||||
specifier: ^5.3.0
|
||||
version: 5.3.0
|
||||
astro:
|
||||
specifier: ^7.4.4
|
||||
version: 7.4.4(@astrojs/markdown-remark@7.3.5)(@types/node@24.19.2)(yaml@2.9.1)
|
||||
@@ -265,6 +268,9 @@ packages:
|
||||
'@fontsource/noto-serif-sc@5.3.1':
|
||||
resolution: {integrity: sha512-nIK2HyNGcUi2TGwIfrk2MItkh0O3Y5+TI8iPfPSzoNY6uyXYfQ9qqch8czk3aa1VhV4EU52uwCYCe1UDKJELpg==}
|
||||
|
||||
'@fontsource/zcool-kuaile@5.3.0':
|
||||
resolution: {integrity: sha512-jn4vpQ6QSVEckUn7uk5a9XcZMxEksTRqljmeE1VKRGMjGd6VoXdMyLGSdnTXSVwZLCCEHaIcm/2cdQuwvasJ9w==}
|
||||
|
||||
'@img/colour@1.1.0':
|
||||
resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -2113,6 +2119,8 @@ snapshots:
|
||||
|
||||
'@fontsource/noto-serif-sc@5.3.1': {}
|
||||
|
||||
'@fontsource/zcool-kuaile@5.3.0': {}
|
||||
|
||||
'@img/colour@1.1.0': {}
|
||||
|
||||
'@img/sharp-darwin-arm64@0.35.5':
|
||||
|
||||
+374
-5
@@ -38,6 +38,7 @@ collections:
|
||||
- { label: 日期, name: pubDate, widget: datetime, date_format: 'YYYY-MM-DD', time_format: false, format: 'YYYY-MM-DD' }
|
||||
- { label: 分轨, name: category, widget: select, options: [{label: 阅读, value: reading}, {label: 技术, value: tech}, {label: 照片, value: photo}] }
|
||||
- { label: 标签, name: tags, widget: list, required: false }
|
||||
- { label: 置顶, name: pinned, widget: boolean, required: false, default: false, hint: '置顶文章排在首页/归档/书架最前;只影响顺序,不改日期、URL 与 RSS 时间语义' }
|
||||
- { label: 情绪色(书评色脊), name: mood, widget: color, required: false, hint: '#RRGGBB,仅阅读轨使用' }
|
||||
- { label: 原书作者, name: bookAuthor, widget: string, required: false }
|
||||
- { label: 一句话描述(书架策展语), name: description, widget: text, required: false }
|
||||
@@ -58,8 +59,36 @@ collections:
|
||||
- { label: 草稿, name: draft, widget: boolean, required: false, default: false }
|
||||
- { label: 正文, name: body, widget: markdown }
|
||||
|
||||
# 瞬间:随手发(text/image 至少其一——Sveltia 无法表达条件必填,两者都设可选,
|
||||
# 保存前自查;pubDate 必填)。图片走图库媒体库(content/photos 同源可复用)
|
||||
# 主题旋钮(样式即数据 · 层 4):只放白名单内的 9 组,越界值会被构建期钳制
|
||||
- name: site_theme
|
||||
label: 主题设置
|
||||
# 文件集合必须是 files: [{file,label,fields}] 结构;把 file 直接写在集合层级会被 Sveltia 判非法(2026-11-02 实测报 "The file option ... is invalid")
|
||||
files:
|
||||
- name: theme
|
||||
label: 主题设置
|
||||
file: content/site/theme.yaml
|
||||
format: yaml
|
||||
fields:
|
||||
- { label: 强调色(亮), name: accent_light, widget: color, required: false, hint: '亮色模式强调色,如 #3454d1;构建期校验正文对比度 ≥4.5' }
|
||||
- { label: 强调色(暗), name: accent_dark, widget: color, required: false, hint: '暗色模式强调色,如 #8ea2ff' }
|
||||
- { label: 图片圆角, name: radius_img, widget: number, required: false, value_type: int, min: 4, max: 16, hint: '范围 4–16(建议 ≤14)' }
|
||||
- { label: 一级标题字号, name: text_h1, widget: number, required: false, value_type: int, min: 22, max: 32, hint: '22–32,且必须大于 h2' }
|
||||
- { label: 二级标题字号, name: text_h2, widget: number, required: false, value_type: int, min: 22, max: 32, hint: '22–32,必须小于 h1、大于 h3' }
|
||||
- { label: 三级标题字号, name: text_h3, widget: number, required: false, value_type: int, min: 16, max: 32, hint: '16–32,必须小于 h2' }
|
||||
- { label: 书评正文字号, name: prose_size_reading, widget: number, required: false, value_type: int, min: 16, max: 22, hint: '16–22' }
|
||||
- { label: 技术文正文字号, name: prose_size_tech, widget: number, required: false, value_type: int, min: 16, max: 22, hint: '16–22' }
|
||||
- { label: 书评行高, name: leading_reading, widget: number, required: false, value_type: float, min: 1.5, max: 2.0, step: 0.05, hint: '1.5–2.0' }
|
||||
- { label: 技术文行高, name: leading_tech, widget: number, required: false, value_type: float, min: 1.5, max: 2.0, step: 0.05, hint: '1.5–2.0' }
|
||||
- { label: 间距 3, name: space_3, widget: number, required: false, value_type: int, min: 8, max: 80, hint: '8–80,必须 < 间距 4' }
|
||||
- { label: 间距 4, name: space_4, widget: number, required: false, value_type: int, min: 8, max: 80, hint: '8–80,必须 > 间距 3 且 < 间距 5' }
|
||||
- { label: 间距 5, name: space_5, widget: number, required: false, value_type: int, min: 8, max: 80, hint: '8–80,必须 > 间距 4 且 < 间距 6' }
|
||||
- { label: 间距 6, name: space_6, widget: number, required: false, value_type: int, min: 8, max: 80, hint: '8–80,必须 > 间距 5' }
|
||||
- { label: 段间距, name: flow_p, widget: number, required: false, value_type: float, min: 0, max: 2, step: 0.05, hint: '0–2(em)' }
|
||||
- { label: 标题下间距, name: flow_h2_post, widget: number, required: false, value_type: float, min: 0, max: 2, step: 0.05, hint: '0–2(em)' }
|
||||
- { label: 过渡时长, name: dur, widget: number, required: false, value_type: int, min: 150, max: 600, hint: '150–600(毫秒)' }
|
||||
|
||||
# 瞬间:随手发(text/image 至少其一——Sveltia 无法表达条件必填,两者都设可选,
|
||||
# 保存前自查;pubDate 必填)。图片走图库媒体库(content/photos 同源可复用)
|
||||
- name: moments
|
||||
label: 瞬间
|
||||
folder: content/moments
|
||||
@@ -69,7 +98,11 @@ collections:
|
||||
sortable_fields: [pubDate, text]
|
||||
fields:
|
||||
- { label: 时间, name: pubDate, widget: datetime, date_format: 'YYYY-MM-DD', time_format: false, format: 'YYYY-MM-DD' }
|
||||
- { label: 文字, name: text, widget: text, required: false, hint: '一句话(与图片至少填一项)' }
|
||||
- label: 文字
|
||||
name: text
|
||||
widget: markdown
|
||||
required: false
|
||||
hint: '与图片至少填一项。支持 markdown(**加粗**、列表、> 引用)与排版类 HTML,例如 <span class="c-blue font-kai">彩色+楷体</span>;装饰色仍守“外壳灰阶、彩色只作强调”的纪律'
|
||||
- { label: 图片, name: image, widget: image, required: false, hint: '与文字至少填一项;建议先压缩' }
|
||||
- label: 标签
|
||||
name: tags
|
||||
@@ -113,6 +146,7 @@ collections:
|
||||
- name: home
|
||||
label: 主页设置
|
||||
file: content/site/home.yaml
|
||||
format: yaml
|
||||
# 头像上传落 content/site/assets/,yaml 写 ./assets/xxx(Astro 图片管线优化)
|
||||
media_folder: content/site/assets
|
||||
public_folder: ./assets
|
||||
@@ -120,9 +154,47 @@ collections:
|
||||
- label: 身份语
|
||||
name: tagline
|
||||
widget: object
|
||||
required: false
|
||||
hint: 首页大字身份语(双语,markdown 富文本);可写 <span class="font-kai c-blue">…</span> 做局部强调
|
||||
fields:
|
||||
- { label: 中文, name: zh, widget: string, required: false }
|
||||
- { label: 英文, name: en, widget: string, required: false }
|
||||
- label: 页面排版覆盖(可选)
|
||||
name: typography
|
||||
widget: object
|
||||
required: false
|
||||
hint: 本页正文默认字体 / 强调色 / 字号缩放(0.8–1.25);不填用全站默认,只影响本页
|
||||
fields:
|
||||
- label: 正文字体类
|
||||
name: font
|
||||
widget: select
|
||||
required: false
|
||||
hint: 整页正文换字族;艺术/展示类字族体积大。5i 新增六款(小薇/庆科黄油/马善政/柳建毛草/得意黑/新晰黑)均按站内用字切片,单页增量 27–46KB;站外用字(生僻字)会回落系统字族,需按 docs 里的命令重切
|
||||
options: [font-serif, font-kai, font-sans, font-mono, font-song, font-kai-sys, font-hei-sys, font-fangsong, font-huiwen, font-kuaile, font-xiaowei, font-huangyou, font-mashanzheng, font-liujianmaocao, font-smiley, font-neoxihei]
|
||||
- label: 强调色类
|
||||
name: accent
|
||||
widget: select
|
||||
required: false
|
||||
options: [ink-accent, ink-accent-soft, c-blue, c-blue-soft, c-teal, c-teal-soft, c-green, c-green-soft, c-yellow, c-yellow-soft, c-orange, c-orange-soft, c-red, c-red-soft, c-pink, c-pink-soft, c-purple, c-purple-soft]
|
||||
- { label: 字号缩放, name: scale, widget: number, required: false, value_type: float, min: 0.8, max: 1.25, step: 0.05, hint: '0.8–1.25;1 = 全站默认' }
|
||||
- label: 公告(可选,markdown)
|
||||
name: announcement
|
||||
widget: object
|
||||
required: false
|
||||
hint: 渲染在名片区下方;留空则不显示公告块
|
||||
fields:
|
||||
- { label: 中文, name: zh, widget: markdown, required: false }
|
||||
- { label: 英文, name: en, widget: markdown, required: false }
|
||||
- { label: 项目墙条数, name: projectsLimit, widget: number, required: false, value_type: int, min: 1, max: 99, hint: '首页项目墙显示几条(1–99,缺省 6)' }
|
||||
- label: 轨细分开关(首页文章列表)
|
||||
name: postsTracks
|
||||
widget: object
|
||||
required: false
|
||||
hint: 关掉某轨则该轨文章不在首页列出(全开=现状)
|
||||
fields:
|
||||
- { label: 阅读轨, name: reading, widget: boolean, required: false, default: true }
|
||||
- { label: 技术轨, name: tech, widget: boolean, required: false, default: true }
|
||||
- { label: 照片轨, name: photo, widget: boolean, required: false, default: true }
|
||||
- label: 副标题(可选,markdown 短文本)
|
||||
name: subtitle
|
||||
widget: object
|
||||
@@ -183,24 +255,321 @@ collections:
|
||||
- name: about
|
||||
label: 关于页
|
||||
file: content/site/about.yaml
|
||||
format: yaml
|
||||
# 块内图片上传落 content/site/assets/,yaml 写 ./assets/xxx(Astro 图片管线)
|
||||
media_folder: content/site/assets
|
||||
public_folder: ./assets
|
||||
fields:
|
||||
- label: 正文(markdown,多段)
|
||||
- label: 页面块(拖拽排序;每块可折叠)
|
||||
name: blocks
|
||||
widget: list
|
||||
typeKey: type
|
||||
required: false
|
||||
summary: '{{fields.type}}'
|
||||
hint: '点「+ 添加」选块型。关掉某块用该块内的「显示」开关'
|
||||
types:
|
||||
- label: 正文(markdown)
|
||||
name: prose
|
||||
widget: object
|
||||
hint: markdown 正文块;可选小标题与版心/通栏宽度,长文分段改内容用
|
||||
fields:
|
||||
- { label: 显示, name: enabled, widget: boolean, required: false, default: true }
|
||||
- label: 小标题(可选)
|
||||
name: title
|
||||
widget: object
|
||||
required: false
|
||||
fields:
|
||||
- { label: 中文, name: zh, widget: string, required: false }
|
||||
- { label: 英文, name: en, widget: string, required: false }
|
||||
- label: 正文(markdown)
|
||||
name: body
|
||||
widget: object
|
||||
fields:
|
||||
- { label: 中文, name: zh, widget: markdown, required: false }
|
||||
- { label: 英文, name: en, widget: markdown, required: false }
|
||||
- label: 宽度
|
||||
name: width
|
||||
widget: select
|
||||
required: false
|
||||
default: narrow
|
||||
options: [{ label: 版心宽度, value: narrow }, { label: 通栏, value: full }]
|
||||
- label: 版图(图标+名称+描述)
|
||||
name: tracks
|
||||
widget: object
|
||||
hint: 版图块:图标 + 名称 + 描述,适合「几条主线/能力面」式介绍
|
||||
fields:
|
||||
- { label: 显示, name: enabled, widget: boolean, required: false, default: true }
|
||||
- label: 标题
|
||||
name: title
|
||||
widget: object
|
||||
fields:
|
||||
- { label: 中文, name: zh, widget: string, required: false }
|
||||
- { label: 英文, name: en, widget: string, required: false }
|
||||
- label: 条目
|
||||
name: items
|
||||
widget: list
|
||||
summary: '{{fields.name.zh}}'
|
||||
fields:
|
||||
- label: 图标
|
||||
name: icon
|
||||
widget: select
|
||||
required: false
|
||||
options: [book, terminal, camera, code, git, rss, email, github, bilibili, x, star, link]
|
||||
hint: 白名单外或留空 → 显示首字占位
|
||||
- label: 名称
|
||||
name: name
|
||||
widget: object
|
||||
fields:
|
||||
- { label: 中文, name: zh, widget: string, required: false }
|
||||
- { label: 英文, name: en, widget: string, required: false }
|
||||
- label: 描述
|
||||
name: desc
|
||||
widget: object
|
||||
required: false
|
||||
fields:
|
||||
- { label: 中文, name: zh, widget: text, required: false }
|
||||
- { label: 英文, name: en, widget: text, required: false }
|
||||
- label: 卡片(2/3 列)
|
||||
name: cards
|
||||
widget: object
|
||||
hint: 卡片墙(2/3 列):每卡 = 标题 + 描述 + 可选链接/角标
|
||||
fields:
|
||||
- { label: 显示, name: enabled, widget: boolean, required: false, default: true }
|
||||
- label: 标题(可选)
|
||||
name: title
|
||||
widget: object
|
||||
required: false
|
||||
fields:
|
||||
- { label: 中文, name: zh, widget: string, required: false }
|
||||
- { label: 英文, name: en, widget: string, required: false }
|
||||
- { label: 列数, name: columns, widget: number, required: false, default: 3, min: 2, max: 3, value_type: int }
|
||||
- label: 卡片条目
|
||||
name: items
|
||||
widget: list
|
||||
summary: '{{fields.title.zh}}'
|
||||
fields:
|
||||
- label: 标题
|
||||
name: title
|
||||
widget: object
|
||||
fields:
|
||||
- { label: 中文, name: zh, widget: string, required: false }
|
||||
- { label: 英文, name: en, widget: string, required: false }
|
||||
- label: 描述
|
||||
name: desc
|
||||
widget: object
|
||||
required: false
|
||||
fields:
|
||||
- { label: 中文, name: zh, widget: text, required: false }
|
||||
- { label: 英文, name: en, widget: text, required: false }
|
||||
- { label: 链接(可选), name: href, widget: string, required: false }
|
||||
- label: 角标(可选)
|
||||
name: tag
|
||||
widget: object
|
||||
required: false
|
||||
fields:
|
||||
- { label: 中文, name: zh, widget: string, required: false }
|
||||
- { label: 英文, name: en, widget: string, required: false }
|
||||
- label: 时间线
|
||||
name: timeline
|
||||
widget: object
|
||||
hint: 时间线:一条时间 + 标题 + 描述;时间可写自由文本(如 2024 — 2026)
|
||||
fields:
|
||||
- { label: 显示, name: enabled, widget: boolean, required: false, default: true }
|
||||
- label: 标题(可选)
|
||||
name: title
|
||||
widget: object
|
||||
required: false
|
||||
fields:
|
||||
- { label: 中文, name: zh, widget: string, required: false }
|
||||
- { label: 英文, name: en, widget: string, required: false }
|
||||
- label: 条目
|
||||
name: items
|
||||
widget: list
|
||||
summary: '{{fields.time}} · {{fields.title.zh}}'
|
||||
fields:
|
||||
- { label: 时间, name: time, widget: string, hint: '自由文本,如 2024 — 2026' }
|
||||
- label: 标题
|
||||
name: title
|
||||
widget: object
|
||||
fields:
|
||||
- { label: 中文, name: zh, widget: string, required: false }
|
||||
- { label: 英文, name: en, widget: string, required: false }
|
||||
- label: 描述(可选)
|
||||
name: desc
|
||||
widget: object
|
||||
required: false
|
||||
fields:
|
||||
- { label: 中文, name: zh, widget: text, required: false }
|
||||
- { label: 英文, name: en, widget: text, required: false }
|
||||
- label: 引文
|
||||
name: quote
|
||||
widget: object
|
||||
hint: 单句引文 + 可选署名;配 font-kai 排版效果更好
|
||||
fields:
|
||||
- { label: 显示, name: enabled, widget: boolean, required: false, default: true }
|
||||
- label: 引文
|
||||
name: text
|
||||
widget: object
|
||||
fields:
|
||||
- { label: 中文, name: zh, widget: text, required: false }
|
||||
- { label: 英文, name: en, widget: text, required: false }
|
||||
- label: 出处(可选)
|
||||
name: cite
|
||||
widget: object
|
||||
required: false
|
||||
fields:
|
||||
- { label: 中文, name: zh, widget: string, required: false }
|
||||
- { label: 英文, name: en, widget: string, required: false }
|
||||
- label: 图片
|
||||
name: figure
|
||||
widget: object
|
||||
hint: 单图 + 可选说明;图片只能后台上传(落 content/site/assets),禁外链
|
||||
fields:
|
||||
- { label: 显示, name: enabled, widget: boolean, required: false, default: true }
|
||||
- { label: 图片(必填,请用后台上传), name: image, widget: image, hint: '禁止填外链:只能上传(落 content/site/assets/),走 Astro 图片管线' }
|
||||
- { label: 替代文本(无障碍), name: alt, widget: string, required: false }
|
||||
- label: 图注(可选)
|
||||
name: caption
|
||||
widget: object
|
||||
required: false
|
||||
fields:
|
||||
- { label: 中文, name: zh, widget: string, required: false }
|
||||
- { label: 英文, name: en, widget: string, required: false }
|
||||
- label: 链接清单
|
||||
name: linklist
|
||||
widget: object
|
||||
hint: 链接清单(标签 + 链接 + 备注),适合「相关阅读 / 外部资源」
|
||||
fields:
|
||||
- { label: 显示, name: enabled, widget: boolean, required: false, default: true }
|
||||
- label: 标题(可选)
|
||||
name: title
|
||||
widget: object
|
||||
required: false
|
||||
fields:
|
||||
- { label: 中文, name: zh, widget: string, required: false }
|
||||
- { label: 英文, name: en, widget: string, required: false }
|
||||
- label: 链接条目
|
||||
name: items
|
||||
widget: list
|
||||
summary: '{{fields.label.zh}} → {{fields.href}}'
|
||||
fields:
|
||||
- label: 名称
|
||||
name: label
|
||||
widget: object
|
||||
fields:
|
||||
- { label: 中文, name: zh, widget: string, required: false }
|
||||
- { label: 英文, name: en, widget: string, required: false }
|
||||
- { label: 链接, name: href, widget: string }
|
||||
- label: 备注(可选)
|
||||
name: note
|
||||
widget: object
|
||||
required: false
|
||||
fields:
|
||||
- { label: 中文, name: zh, widget: string, required: false }
|
||||
- { label: 英文, name: en, widget: string, required: false }
|
||||
- label: 行动按钮
|
||||
name: cta
|
||||
widget: object
|
||||
hint: 行动按钮(文案 + 链接 + 按钮字);一页建议不超过一个
|
||||
fields:
|
||||
- { label: 显示, name: enabled, widget: boolean, required: false, default: true }
|
||||
- label: 说明文字
|
||||
name: text
|
||||
widget: object
|
||||
fields:
|
||||
- { label: 中文, name: zh, widget: string, required: false }
|
||||
- { label: 英文, name: en, widget: string, required: false }
|
||||
- { label: 按钮链接, name: href, widget: string }
|
||||
- label: 按钮文字
|
||||
name: label
|
||||
widget: object
|
||||
fields:
|
||||
- { label: 中文, name: zh, widget: string, required: false }
|
||||
- { label: 英文, name: en, widget: string, required: false }
|
||||
- label: 分隔
|
||||
name: divider
|
||||
widget: object
|
||||
hint: 分隔块:细分隔线或纯留白,给长页分段
|
||||
fields:
|
||||
- { label: 显示, name: enabled, widget: boolean, required: false, default: true }
|
||||
- label: 样式
|
||||
name: style
|
||||
widget: select
|
||||
required: false
|
||||
default: line
|
||||
options: [{ label: 细分隔线, value: line }, { label: 纯留白, value: space }]
|
||||
- label: 名片区开关
|
||||
name: layout
|
||||
widget: object
|
||||
required: false
|
||||
hint: 关掉则不显示头像/名字/社交(它们的数据在「主页设置」)
|
||||
fields:
|
||||
- { label: 显示名片区, name: showIdCard, widget: boolean, required: false, default: true }
|
||||
- label: 正文回退(blocks 为空时用,markdown,多段)
|
||||
name: intro
|
||||
widget: object
|
||||
hint: 渲染在「关于」标题下方、名片之上;支持段落/列表/粗斜体/链接;留空用默认文案
|
||||
required: false
|
||||
hint: 仅在「页面块」为空时渲染;与块同时存在时以块为准
|
||||
fields:
|
||||
- { label: 中文, name: zh, widget: markdown, required: false }
|
||||
- { label: 英文, name: en, widget: markdown, required: false }
|
||||
- label: 页面排版覆盖(可选)
|
||||
name: typography
|
||||
widget: object
|
||||
required: false
|
||||
hint: 本页正文默认字体 / 强调色 / 字号缩放(0.8–1.25);不填用全站默认,只影响本页
|
||||
fields:
|
||||
- label: 正文字体类
|
||||
name: font
|
||||
widget: select
|
||||
required: false
|
||||
options: [font-serif, font-kai, font-sans, font-mono, font-song, font-kai-sys, font-hei-sys, font-fangsong, font-huiwen, font-kuaile, font-xiaowei, font-huangyou, font-mashanzheng, font-liujianmaocao, font-smiley, font-neoxihei]
|
||||
- label: 强调色类
|
||||
name: accent
|
||||
widget: select
|
||||
required: false
|
||||
options: [ink-accent, ink-accent-soft, c-blue, c-blue-soft, c-teal, c-teal-soft, c-green, c-green-soft, c-yellow, c-yellow-soft, c-orange, c-orange-soft, c-red, c-red-soft, c-pink, c-pink-soft, c-purple, c-purple-soft]
|
||||
- { label: 字号缩放, name: scale, widget: number, required: false, value_type: float, min: 0.8, max: 1.25, step: 0.05 }
|
||||
- name: opensource
|
||||
label: 开源页
|
||||
file: content/site/opensource.yaml
|
||||
format: yaml
|
||||
fields:
|
||||
- label: 打字机动效文案(可选,≤6 条)
|
||||
name: typing
|
||||
widget: list
|
||||
required: false
|
||||
summary: '{{fields.zh}}'
|
||||
hint: '档案卡顶部逐字打出的几行文案,2–6 条最佳;留空用内置默认。中英各一行,切换语言同步'
|
||||
fields:
|
||||
- { label: 中文, name: zh, widget: string }
|
||||
- { label: English, name: en, widget: string }
|
||||
- label: 叙述(链接前)
|
||||
name: blurb
|
||||
widget: object
|
||||
required: false
|
||||
hint: 富文本(markdown + 排版类),渲染在仓库链接之前
|
||||
fields:
|
||||
- { label: 中文, name: zh, widget: string, required: false }
|
||||
- { label: 英文, name: en, widget: string, required: false }
|
||||
- label: 页面排版覆盖(可选)
|
||||
name: typography
|
||||
widget: object
|
||||
required: false
|
||||
hint: 本页正文默认字体 / 强调色 / 字号缩放(0.8–1.25);不填用全站默认,只影响本页
|
||||
fields:
|
||||
- label: 正文字体类
|
||||
name: font
|
||||
widget: select
|
||||
required: false
|
||||
hint: 整页正文换字族;艺术/展示类字族体积大。5i 新增六款(小薇/庆科黄油/马善政/柳建毛草/得意黑/新晰黑)均按站内用字切片,单页增量 27–46KB;站外用字(生僻字)会回落系统字族,需按 docs 里的命令重切
|
||||
options: [font-serif, font-kai, font-sans, font-mono, font-song, font-kai-sys, font-hei-sys, font-fangsong, font-huiwen, font-kuaile, font-xiaowei, font-huangyou, font-mashanzheng, font-liujianmaocao, font-smiley, font-neoxihei]
|
||||
- label: 强调色类
|
||||
name: accent
|
||||
widget: select
|
||||
required: false
|
||||
options: [ink-accent, ink-accent-soft, c-blue, c-blue-soft, c-teal, c-teal-soft, c-green, c-green-soft, c-yellow, c-yellow-soft, c-orange, c-orange-soft, c-red, c-red-soft, c-pink, c-pink-soft, c-purple, c-purple-soft]
|
||||
- { label: 字号缩放, name: scale, widget: number, required: false, value_type: float, min: 0.8, max: 1.25, step: 0.05, hint: '0.8–1.25;1 = 全站默认' }
|
||||
- { label: 仓库链接地址, name: repo_url, widget: string, required: false }
|
||||
- { label: 仓库链接文字, name: repo_label, widget: string, required: false }
|
||||
- label: 叙述(链接后)
|
||||
|
||||
@@ -1,28 +1,57 @@
|
||||
/**
|
||||
* 排版类清单 —— 单一事实源(后台编辑器面板与文档共用)。
|
||||
* 与 src/styles/global.css 的「受限排版类」段、src/lib/rehype-class-whitelist.ts 的白名单三处保持一致:
|
||||
* 与 src/styles/global.css 的「受限排版类/语义色板」段、src/lib/rehype-class-whitelist.ts 的白名单三处保持一致:
|
||||
* 加类时三处同改,否则构建期白名单会把新类剥掉。
|
||||
*
|
||||
* swatch / family 仅供**后台面板预览**使用(后台无站点 CSS 变量,故给具体值):
|
||||
* swatch 取亮态值(后台是浅色 UI),family 为该类的真实字体栈。
|
||||
*/
|
||||
window.TYPOGRAPHY_CLASSES = {
|
||||
font: [
|
||||
{ cls: 'font-serif', label: '思源宋(书评正文)' },
|
||||
{ cls: 'font-kai', label: '霞鹜文楷(金句)' },
|
||||
{ cls: 'font-sans', label: '思源黑(UI/技术)' },
|
||||
{ cls: 'font-mono', label: 'JetBrains Mono(等宽)' },
|
||||
{ cls: 'font-song', label: '宋体(系统)' },
|
||||
{ cls: 'font-kai-sys', label: '楷体(系统)' },
|
||||
{ cls: 'font-hei-sys', label: '黑体(系统)' },
|
||||
{ cls: 'font-fangsong', label: '仿宋(系统)' },
|
||||
{ cls: 'font-serif', label: '思源宋(书评正文)', family: '"Noto Serif SC", Georgia, serif', kind: '自托管' },
|
||||
{ cls: 'font-kai', label: '霞鹜文楷(金句)', family: '"LXGW WenKai Screen", "Songti SC", serif', kind: '自托管' },
|
||||
{ cls: 'font-sans', label: '思源黑(UI/技术)', family: '"Noto Sans SC", system-ui, sans-serif', kind: '自托管' },
|
||||
{ cls: 'font-mono', label: 'JetBrains Mono(等宽)', family: '"JetBrains Mono", ui-monospace, monospace', kind: '自托管' },
|
||||
{ cls: 'font-song', label: '宋体族', family: '"Noto Serif SC", "Songti SC", SimSun, serif', kind: '自托管优先' },
|
||||
{ cls: 'font-kai-sys', label: '楷体族', family: '"LXGW WenKai Screen", "Kaiti SC", STKaiti, serif', kind: '自托管优先' },
|
||||
{ cls: 'font-hei-sys', label: '黑体族', family: '"Noto Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif', kind: '自托管优先' },
|
||||
{ cls: 'font-fangsong', label: '仿宋族', family: 'FangSong, STFangsong, serif', kind: '系统回退' },
|
||||
{ cls: 'font-huiwen', label: '汇文明朝体', family: '"WF-Hwmc", serif', kind: '自托管' },
|
||||
{ cls: 'font-kuaile', label: '站酷快乐体', family: '"ZCOOL KuaiLe", serif', kind: '自托管' },
|
||||
{ cls: 'font-art-kuaile', label: '快乐体(艺术·仅标题)', family: '"ZCOOL KuaiLe", serif', kind: '艺术字' },
|
||||
// 5i T9 五款字族扩展(2026-11-03,均 OFL / 免费商用且允许 web 嵌入)
|
||||
{ cls: 'font-xiaowei', label: '站酷小薇(文学·宋)', family: '"ZCOOL XiaoWei", serif', kind: '自托管' },
|
||||
{ cls: 'font-huangyou', label: '站酷庆科黄油(标题)', family: '"ZCOOL QingKe HuangYou", sans-serif', kind: '自托管' },
|
||||
{ cls: 'font-mashanzheng', label: '马善政(书法)', family: '"Ma Shan Zheng", "Kaiti SC", serif', kind: '自托管' },
|
||||
{ cls: 'font-liujianmaocao', label: '柳建毛草(草书)', family: '"Liu Jian Mao Cao", "Kaiti SC", cursive', kind: '自托管' },
|
||||
{ cls: 'font-smiley', label: '得意黑(斜体标题)', family: '"Smiley Sans", sans-serif', kind: '自托管' },
|
||||
{ cls: 'font-neoxihei', label: '霞鹜新晰黑(正文黑)', family: '"LXGW Neo XiHei", "Noto Sans SC", sans-serif', kind: '自托管' },
|
||||
],
|
||||
color: [
|
||||
{ cls: 'ink-1', label: '最重(标题级)' },
|
||||
{ cls: 'ink-2', label: '正文' },
|
||||
{ cls: 'ink-3', label: '次要(日期/元信息)' },
|
||||
{ cls: 'ink-muted', label: '最淡(注释感)' },
|
||||
{ cls: 'ink-accent', label: '强调(链接色)' },
|
||||
{ cls: 'ink-accent-soft', label: '弱强调' },
|
||||
{ cls: 'ink-warm', label: '暖调正文' },
|
||||
{ cls: 'ink-cool', label: '冷调正文' },
|
||||
{ cls: 'ink-faint', label: '最淡可读档' },
|
||||
{ cls: 'ink-1', label: '最重(标题级)', swatch: '#111113' },
|
||||
{ cls: 'ink-2', label: '正文', swatch: '#33343a' },
|
||||
{ cls: 'ink-3', label: '次要(日期/元信息)', swatch: '#6b6e78' },
|
||||
{ cls: 'ink-muted', label: '最淡(注释感)', swatch: '#70747c' },
|
||||
{ cls: 'ink-warm', label: '暖调正文', swatch: '#3f362e' },
|
||||
{ cls: 'ink-cool', label: '冷调正文', swatch: '#2f3742' },
|
||||
{ cls: 'ink-faint', label: '最淡可读档', swatch: '#70747c' },
|
||||
{ cls: 'ink-accent', label: '强调(链接色)', swatch: '#3454d1' },
|
||||
{ cls: 'ink-accent-soft', label: '弱强调', swatch: '#4f66c2' },
|
||||
{ cls: 'c-blue', label: '蓝', swatch: '#2456c9' },
|
||||
{ cls: 'c-blue-soft', label: '蓝(soft)', swatch: '#4170dd' },
|
||||
{ cls: 'c-teal', label: '青', swatch: '#0d7d74' },
|
||||
{ cls: 'c-teal-soft', label: '青(soft)', swatch: '#0e847a' },
|
||||
{ cls: 'c-green', label: '绿', swatch: '#1f7a3a' },
|
||||
{ cls: 'c-green-soft', label: '绿(soft)', swatch: '#22853f' },
|
||||
{ cls: 'c-yellow', label: '黄(已压暗)', swatch: '#7a5900' },
|
||||
{ cls: 'c-yellow-soft', label: '黄(soft)', swatch: '#926a00' },
|
||||
{ cls: 'c-orange', label: '橙', swatch: '#9a4a00' },
|
||||
{ cls: 'c-orange-soft', label: '橙(soft)', swatch: '#ba5900' },
|
||||
{ cls: 'c-red', label: '红', swatch: '#b32020' },
|
||||
{ cls: 'c-red-soft', label: '红(soft)', swatch: '#db3737' },
|
||||
{ cls: 'c-pink', label: '粉', swatch: '#a3246a' },
|
||||
{ cls: 'c-pink-soft', label: '粉(soft)', swatch: '#ce3386' },
|
||||
{ cls: 'c-purple', label: '紫', swatch: '#6a35b8' },
|
||||
{ cls: 'c-purple-soft', label: '紫(soft)', swatch: '#8c5ed1' },
|
||||
],
|
||||
};
|
||||
@@ -37,7 +37,7 @@
|
||||
card.appendChild(title);
|
||||
|
||||
const buttons = [];
|
||||
const addGroup = (name, list) => {
|
||||
const addGroup = (name, list, withSwatch) => {
|
||||
const h = document.createElement('p');
|
||||
h.textContent = name;
|
||||
Object.assign(h.style, { margin: '12px 0 6px', fontSize: '12px', color: '#6b6e78' });
|
||||
@@ -47,22 +47,45 @@
|
||||
for (const item of list) {
|
||||
const b = document.createElement('button');
|
||||
b.type = 'button';
|
||||
b.textContent = item.label;
|
||||
b.dataset.cls = item.cls;
|
||||
b.className = item.cls === current ? 'active' : '';
|
||||
Object.assign(b.style, {
|
||||
display: 'inline-flex', alignItems: 'center', gap: '8px',
|
||||
padding: '6px 12px', borderRadius: '8px', cursor: 'pointer',
|
||||
border: '1px solid ' + (item.cls === current ? '#3454d1' : '#e6e7eb'),
|
||||
background: item.cls === current ? '#eef1fd' : '#fff',
|
||||
color: '#111113', fontSize: '13px',
|
||||
});
|
||||
if (withSwatch) {
|
||||
// 色块预览:直接展示该类在亮态下的实际颜色
|
||||
const chip = document.createElement('span');
|
||||
Object.assign(chip.style, {
|
||||
width: '16px', height: '16px', borderRadius: '4px', flex: 'none',
|
||||
background: item.swatch || '#ccc',
|
||||
border: '1px solid rgba(0,0,0,.12)',
|
||||
});
|
||||
chip.setAttribute('aria-hidden', 'true');
|
||||
b.appendChild(chip);
|
||||
} else {
|
||||
// 字体项:名称 + 用该字体栈渲染的示例字形
|
||||
const sample = document.createElement('span');
|
||||
sample.textContent = '秋海';
|
||||
Object.assign(sample.style, {
|
||||
fontFamily: item.family || 'inherit', fontSize: '15px', lineHeight: '1',
|
||||
padding: '0 4px', borderLeft: '1px solid #e6e7eb', color: '#33343a',
|
||||
});
|
||||
b.appendChild(sample);
|
||||
}
|
||||
const label = document.createElement('span');
|
||||
label.textContent = item.label + (item.kind ? `(${item.kind})` : '');
|
||||
b.appendChild(label);
|
||||
row.appendChild(b);
|
||||
buttons.push(b);
|
||||
}
|
||||
card.appendChild(row);
|
||||
};
|
||||
addGroup('字体', CLASSES.font);
|
||||
addGroup('颜色', CLASSES.color);
|
||||
addGroup('字体', CLASSES.font, false);
|
||||
addGroup('颜色', CLASSES.color, true);
|
||||
|
||||
const foot = document.createElement('div');
|
||||
Object.assign(foot.style, { marginTop: '16px', display: 'flex', justifyContent: 'flex-end', gap: '8px' });
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loaded 100 of 1131 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user