Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7d1e7b975 | ||
|
|
550f6d15b4 | ||
|
|
d4fe273812 |
No files matched your search
@@ -0,0 +1,14 @@
|
|||||||
|
{"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"}
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
### 2.2 关键假设
|
### 2.2 关键假设
|
||||||
- 瞬间富文本走与全站文案同一条净化管线即可满足「字体/颜色可换」且不引入 XSS 面(危险标签整棵丢弃、协议白名单 http/https/mailto)
|
- 瞬间富文本走与全站文案同一条净化管线即可满足「字体/颜色可换」且不引入 XSS 面(危险标签整棵丢弃、协议白名单 http/https/mailto)
|
||||||
- 每条瞬间一个 Artalk page_key(`/moments/#<slug>`)不会与页面级 pv 冲突;Artalk 实例按需挂载(展开才 init)可避免 "one page one Artalk" 的全局实例冲突
|
- 每条瞬间一个 Artalk page_key(`/moments/<slug>/`)不会与页面级 pv 冲突;Artalk 实例按需挂载(展开才 init)可避免 "one page one Artalk" 的全局实例冲突
|
||||||
- 开源页可用本地数据(已有 repo 元数据 + GitHub GraphQL 拉取)自绘 followers/stars/语言占比,无需任何第三方徽章服务
|
- 开源页可用本地数据(已有 repo 元数据 + GitHub GraphQL 拉取)自绘 followers/stars/语言占比,无需任何第三方徽章服务
|
||||||
|
|
||||||
### 2.3 待确认问题
|
### 2.3 待确认问题
|
||||||
@@ -39,13 +39,13 @@
|
|||||||
## 3. 功能范围
|
## 3. 功能范围
|
||||||
|
|
||||||
### 3.1 核心功能(MVP)
|
### 3.1 核心功能(MVP)
|
||||||
- [ ] T1 文章置顶:posts/postsEn schema `pinned`、四处列表排序(首页双轨/归档/书架)、视觉标记(灰阶 pin)、后台开关
|
- [x] T1 文章置顶:posts/postsEn schema `pinned`、四处列表排序(首页双轨/归档/书架)、视觉标记(灰阶 pin)、后台开关
|
||||||
- [ ] T2 瞬间富文本:moments `text` 支持 markdown + 白名单排版类(字体/颜色/强调/多段/列表)、净化管线复用、后台改 markdown 编辑器、旧纯文本零回归
|
- [x] T2 瞬间富文本:moments `text` 支持 markdown + 白名单排版类(字体/颜色/强调/多段/列表)、净化管线复用、后台改 markdown 编辑器、旧纯文本零回归
|
||||||
- [ ] T3 瞬间逐条评论:每条 moment 独立 page_key 的 Artalk 评论区(折叠、展开时挂载)、评论数联动、双语页共用同一 key
|
- [x] T3 瞬间逐条评论:每条 moment 独立 page_key 的 Artalk 评论区(折叠、展开时挂载)、评论数联动、双语页共用同一 key
|
||||||
- [ ] T4 计数徽标:新增 `ViewMeta` 组件(眼睛图标 = 浏览数、气泡图标 = 评论数),接入文章页/瞬间页/关于页/开源页
|
- [x] T4 计数徽标:新增 `ViewMeta` 组件(眼睛图标 = 浏览数、气泡图标 = 评论数),接入文章页/瞬间页/关于页/开源页
|
||||||
- [ ] T5 开源页重构:原生 GitHub 档案卡(头像/名字/关注者/星标/语言占比,本地数据自绘)替换 README 外链图;`align` 失效与破图问题消失;README 通道降级为可选
|
- [x] T5 开源页重构:原生 GitHub 档案卡(头像/名字/关注者/星标/语言占比,本地数据自绘)替换 README 外链图;`align` 失效与破图问题消失;README 通道降级为可选
|
||||||
- [ ] T6 关于页承接:把 README 的 About Me / Find Me 迁为 about.yaml blocks(prose + linklist),开源页只留开源相关
|
- [x] T6 关于页承接:把 README 的 About Me / Find Me 迁为 about.yaml blocks(prose + linklist),开源页只留开源相关
|
||||||
- [ ] T7 写作模板示例:中英双语示例文章,演示标题层级/彩色强调/字族/引用/代码/图/表/链接卡片/折叠,并作为置顶功能的活样本;docs 与后台 hint 同步
|
- [x] T7 写作模板示例:中英双语示例文章,演示标题层级/彩色强调/字族/引用/代码/图/表/链接卡片/折叠,并作为置顶功能的活样本;docs 与后台 hint 同步
|
||||||
|
|
||||||
### 3.2 扩展功能
|
### 3.2 扩展功能
|
||||||
- 无
|
- 无
|
||||||
@@ -70,17 +70,17 @@
|
|||||||
- 变更对象:内容模型(置顶/瞬间富文本)、渲染组件(计数/评论/档案卡)、后台配置、示例内容
|
- 变更对象:内容模型(置顶/瞬间富文本)、渲染组件(计数/评论/档案卡)、后台配置、示例内容
|
||||||
- 快速检查:`pnpm astro build`(node26)+ `python3 scripts/build-perf/config-check.py .`
|
- 快速检查:`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
|
- 集成检查:`E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs`(37 项,本包新增置顶顺序/徽标存在/瞬间富文本断言)+ content-lint 0/0/0
|
||||||
- 全项目检查:生产实测四条(锁屏外链零依赖、瞬间评论可发可见、徽标数值与 Artalk 后台一致、关于页承接内容渲染)
|
- 全项目检查:生产实测四条(开源页零第三方外链图、瞬间评论可发可见、徽标数值与 Artalk 端点对账一致、关于页承接内容渲染);**升级条件**——仅当改动触及 BaseLayout/全局样式(tokens/字体栈)或 Artalk 主题接管层时才执行,本包因往 tokens 补 emoji 回退链已触发一次并记录了理由
|
||||||
- 升级触发:若动到 BaseLayout/全局样式或 Artalk 主题接管层,升级为全站视觉复核(glm-5.3-flash:max,≤6 图/轮)
|
- 升级触发:若动到 BaseLayout/全局样式或 Artalk 主题接管层,升级为全站视觉复核(glm-5.3-flash:max,≤6 图/轮)
|
||||||
|
|
||||||
## 6. 成功标准与验证方式
|
## 6. 成功标准与验证方式
|
||||||
- [ ] 置顶文章排在列表首位且带可见标记,取消置顶后恢复时间序 -> verify: task_0001
|
- [x] 置顶文章排在列表首位且带可见标记,取消置顶后恢复时间序 -> verify: task_0001
|
||||||
- [ ] 瞬间文字可用字体类与彩色强调,危险标签被净化丢弃,旧纯文本渲染不变 -> verify: task_0002
|
- [x] 瞬间文字可用字体类与彩色强调,危险标签被净化丢弃,旧纯文本渲染不变 -> verify: task_0002
|
||||||
- [ ] 每条瞬间可展开评论区并成功发帖,双语页共用同一评论流 -> verify: task_0003
|
- [x] 每条瞬间可展开评论区并成功发帖,双语页共用同一评论流 -> verify: task_0003
|
||||||
- [ ] 文章/瞬间/关于/开源四页显示「浏览数 + 评论数」徽标且数值与 Artalk 一致 -> verify: task_0004
|
- [x] 文章/瞬间/关于/开源四页显示「浏览数 + 评论数」徽标且数值与 Artalk 一致 -> verify: task_0004
|
||||||
- [ ] 开源页零第三方图片请求,档案卡数据来自本地/GraphQL,居中与破图问题不再出现 -> verify: task_0005
|
- [x] 开源页零第三方图片请求,档案卡数据来自本地/GraphQL,居中与破图问题不再出现 -> verify: task_0005
|
||||||
- [ ] README 的个人叙述出现在关于页,开源页只保留开源相关内容 -> verify: task_0006
|
- [x] README 的个人叙述出现在关于页,开源页只保留开源相关内容 -> verify: task_0006
|
||||||
- [ ] 示例文章渲染全部演示能力且构建通过,docs/hint 同步 -> verify: task_0007
|
- [x] 示例文章渲染全部演示能力且构建通过,docs/hint 同步 -> verify: task_0007
|
||||||
|
|
||||||
## 7. 风险与约束
|
## 7. 风险与约束
|
||||||
- 灰阶外壳红线不变:徽标图标与数字用 tokens(`--ink-*`/`--line`),不引入第三方彩色徽章
|
- 灰阶外壳红线不变:徽标图标与数字用 tokens(`--ink-*`/`--line`),不引入第三方彩色徽章
|
||||||
|
|||||||
@@ -8,25 +8,25 @@
|
|||||||
|
|
||||||
## 阶段一:实现
|
## 阶段一:实现
|
||||||
|
|
||||||
- [ ] T1 文章置顶(契约 + 排序 + 标记 + 后台)
|
- [x] T1 文章置顶(契约 + 排序 + 标记 + 后台)
|
||||||
- claim: orchestrator
|
- 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(置顶标签)
|
- 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 -q "37/37 PASS" && grep -q "pinned" src/content.config.ts && grep -q "pinned" src/components/PostList.astro && grep -q "pinned" public/admin/config.yml`
|
- 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 瞬间富文本(字体可换 / 颜色可换 / 多形式)
|
- [x] T2 瞬间富文本(字体可换 / 颜色可换 / 多形式)
|
||||||
- claim: worker-one
|
- 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
|
- 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 -q "37/37 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"`
|
- 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 + 折叠懒挂载)
|
- [x] T3 瞬间逐条评论(Artalk 独立 page_key + 折叠懒挂载)
|
||||||
- claim: worker-one
|
- claim: worker-one
|
||||||
- boundary: 新增 src/components/MomentComments.astro(折叠 disclosure,展开时首次挂载 Artalk;page_key 约定 `/moments/#<slug>`,en 页同一 key 不分裂;评论数徽标复用 T4 的 ViewMeta 计数接口)、src/components/pages/MomentsPage.astro(每条卡片挂评论区)
|
- 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/#" src/components/MomentComments.astro && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -q "37/37 PASS"`
|
- 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 计数徽标(眼睛图标 = 浏览数,气泡图标 = 评论数)
|
- [x] T4 计数徽标(眼睛图标 = 浏览数,气泡图标 = 评论数)
|
||||||
- claim: op-artalk
|
- 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)
|
- 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 -q "37/37 PASS"`
|
- 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 档案卡,去外链图依赖)
|
- [x] T5 开源页重构(原生 GitHub 档案卡,去外链图依赖)
|
||||||
- claim: op-gitea
|
- claim: op-gitea
|
||||||
@@ -36,9 +36,9 @@
|
|||||||
- [x] T6 关于页承接 README 的个人叙述
|
- [x] T6 关于页承接 README 的个人叙述
|
||||||
- claim: op-gitea
|
- claim: op-gitea
|
||||||
- boundary: content/site/about.yaml(新增 blocks:关于/兴趣 prose、站点与项目 linklist、Find Me 社交块;双语 zh/en;测试值不得留在文件里)、src/pages/about.astro 与 en(仅当块渲染需要时改,优先零改动)
|
- 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 -q "37/37 PASS"`
|
- 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"`
|
||||||
|
|
||||||
- [ ] T7 写作模板示例文章 + 文档与 hint 收尾
|
- [x] T7 写作模板示例文章 + 文档与 hint 收尾
|
||||||
- claim: orchestrator
|
- 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 已含)
|
- 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`
|
- 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,10 @@
|
|||||||
|
---
|
||||||
|
pubDate: 2026-11-02
|
||||||
|
tags: [站点更新]
|
||||||
|
text: |
|
||||||
|
瞬间现在和文章同级表达力:`text` 支持 markdown 与排版类,<span class="c-teal font-kai">这一句是青色 + 楷体</span>,也可以**加粗**。
|
||||||
|
|
||||||
|
另外每条瞬间自带评论区——展开才加载,不刷时间线就不为它付成本。
|
||||||
|
---
|
||||||
|
|
||||||
|
站点更新记录:瞬间富文本与逐条评论上线。
|
||||||
@@ -70,19 +70,19 @@ blocks:
|
|||||||
zh: |
|
zh: |
|
||||||
气象是我起点的专业——一个想做预报的人,依旧把云图当日常读物。
|
气象是我起点的专业——一个想做预报的人,依旧把云图当日常读物。
|
||||||
|
|
||||||
💻 写代码是第二条主线:Linux、容器、自托管,把日常工具一件件磨顺手。
|
写代码是第二条主线:Linux、容器、自托管,把日常工具一件件磨顺手。
|
||||||
|
|
||||||
🌐 作品大多在 [wait4x.online](https://wait4x.online/)。
|
作品大多在 [wait4x.online](https://wait4x.online/)。
|
||||||
|
|
||||||
⚡ 信条:**拥抱开源,热爱开源**。
|
信条:**拥抱开源,热爱开源**。
|
||||||
en: |
|
en: |
|
||||||
Meteorology is where I started — an aspiring forecaster who still reads weather charts for fun.
|
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.
|
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/).
|
Most of my work lives at [wait4x.online](https://wait4x.online/).
|
||||||
|
|
||||||
⚡ Motto: **embrace open source, love open source**.
|
Motto: **embrace open source, love open source**.
|
||||||
|
|
||||||
- type: linklist
|
- type: linklist
|
||||||
title:
|
title:
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ blurb:
|
|||||||
repo_url: https://git.wait4x.online/wait4x/blog
|
repo_url: https://git.wait4x.online/wait4x/blog
|
||||||
repo_label: git.wait4x.online/wait4x/blog
|
repo_label: git.wait4x.online/wait4x/blog
|
||||||
blurb_after:
|
blurb_after:
|
||||||
zh: —— Astro SSG、双写作流、构建发布全链皆在仓中。以下是我在自托管 gitea 与 GitHub 上的全部公开仓库,以及最近一年的贡献活动。
|
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.
|
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=全部(默认) | only=仅 include 列出 | exclude=排除 exclude 列出
|
||||||
|
|||||||
@@ -154,6 +154,10 @@ const switchLabel = curLocale === 'zh' ? 'EN' : '中';
|
|||||||
阈值 350→450(E 车道 6 项后 en 词长实测:360-430px 页溢 42-51px;zh 无感知) */
|
阈值 350→450(E 车道 6 项后 en 词长实测:360-430px 页溢 42-51px;zh 无感知) */
|
||||||
@media (max-width: 450px) {
|
@media (max-width: 450px) {
|
||||||
nav ul { overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch; }
|
nav ul { overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch; }
|
||||||
|
/* 视觉终审 P1:flex 子项默认可压缩,中文词被压到就地折成两行(开/源),
|
||||||
|
overflow-x 永远不触发。钉死不许压缩 + 不许换行,内滚才真正接管 */
|
||||||
|
nav ul > li { flex: none; }
|
||||||
|
.nav-link { white-space: nowrap; }
|
||||||
nav { min-width: 0; }
|
nav { min-width: 0; }
|
||||||
.header-inner { flex-wrap: nowrap; }
|
.header-inner { flex-wrap: nowrap; }
|
||||||
/* 中段再收一档地板(en 五词在 360-430 差 55-61px):字号 11px 下限仍可读,
|
/* 中段再收一档地板(en 五词在 360-430 差 55-61px):字号 11px 下限仍可读,
|
||||||
|
|||||||
@@ -6,8 +6,11 @@
|
|||||||
* 都白付;Artalk 官方也未背书"同页多实例长期并存"。于是「展开才挂」是折中:
|
* 都白付;Artalk 官方也未背书"同页多实例长期并存"。于是「展开才挂」是折中:
|
||||||
* 读者点了才付成本,未点零开销(连 Artalk.js 都不下载)。
|
* 读者点了才付成本,未点零开销(连 Artalk.js 都不下载)。
|
||||||
*
|
*
|
||||||
* 评论流约定:`page_key = /moments/#<slug>`,**中英双语共用同一键**(评论不分裂,
|
* 评论流约定:`page_key = /moments/<slug>/`(虚拟路径),**中英双语共用同一键**
|
||||||
* 与文章页 `/archives/<slug>/` 双语共用同理);site 恒为 wait4x.online。
|
* (评论不分裂,与文章页 `/archives/<slug>/` 同理);site 恒为 wait4x.online。
|
||||||
|
* 为什么不用 `#<slug>` 形态:`#` 在查询串里是 fragment 分隔符,裸传会被服务端截断成
|
||||||
|
* 页面级键 `/moments/`(实测),届时所有瞬间会共用同一个评论流、pv 也串在一起。
|
||||||
|
* 虚拟路径既无转义风险,将来真做单条瞬间页时键也天然对齐。
|
||||||
*
|
*
|
||||||
* 计数不在这里自己 fetch:复用 geek/ViewMeta(mode="readonly" 只读,不写 pv),
|
* 计数不在这里自己 fetch:复用 geek/ViewMeta(mode="readonly" 只读,不写 pv),
|
||||||
* 放在卡片底部让读者不开评论也能看见条数。
|
* 放在卡片底部让读者不开评论也能看见条数。
|
||||||
@@ -24,8 +27,8 @@ interface Props {
|
|||||||
|
|
||||||
const { slug, locale = 'zh' } = Astro.props;
|
const { slug, locale = 'zh' } = Astro.props;
|
||||||
|
|
||||||
/** 评论流键:双语共用;`#<slug>` 与文章页 page_key 形状保持一致的可读性 */
|
/** 评论流键:双语共用的虚拟路径(形如 /moments/2026-11-02/),无转义风险 */
|
||||||
const pageKey = `/moments/#${slug}`;
|
const pageKey = `/moments/${slug}/`;
|
||||||
/** 容器 id 只允许 CSS 安全字符(slug 理论上是文件名,仍兜一层) */
|
/** 容器 id 只允许 CSS 安全字符(slug 理论上是文件名,仍兜一层) */
|
||||||
const panelId = `mom-atk-${slug.replace(/[^a-zA-Z0-9_-]/g, '-')}`;
|
const panelId = `mom-atk-${slug.replace(/[^a-zA-Z0-9_-]/g, '-')}`;
|
||||||
const label = t('moment_comments', locale);
|
const label = t('moment_comments', locale);
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ const { block } = Astro.props;
|
|||||||
margin-top: var(--space-3);
|
margin-top: var(--space-3);
|
||||||
}
|
}
|
||||||
.prose-body.full { max-width: none; }
|
.prose-body.full { max-width: none; }
|
||||||
|
/* 标题下正文不再叠一档 margin:同级标题到内容统一 16px(tracks 块实测 16px,
|
||||||
|
prose 原来 16+16=32,两块相邻时节奏不齐——视觉终审) */
|
||||||
|
.sec-title + .prose-body { margin-top: 0; }
|
||||||
.prose-body :global(p) { margin-block: 0 var(--space-2); }
|
.prose-body :global(p) { margin-block: 0 var(--space-2); }
|
||||||
.prose-body :global(ul) {
|
.prose-body :global(ul) {
|
||||||
margin: var(--space-1) 0 var(--space-2);
|
margin: var(--space-1) 0 var(--space-2);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ const commitUrl = `https://git.wait4x.online/wait4x/blog/commit/${sha}`;
|
|||||||
---
|
---
|
||||||
|
|
||||||
<p class="build-info">
|
<p class="build-info">
|
||||||
built from
|
built from{' '}
|
||||||
{short === 'dev' ? <span>dev</span> : <a href={commitUrl} class="link mono-link">{short}</a>}
|
{short === 'dev' ? <span>dev</span> : <a href={commitUrl} class="link mono-link">{short}</a>}
|
||||||
· {new Date(date).toISOString().slice(0, 16).replace('T', ' ')}Z · Astro
|
· {new Date(date).toISOString().slice(0, 16).replace('T', ' ')}Z · Astro
|
||||||
</p>
|
</p>
|
||||||
@@ -45,6 +45,9 @@ try {
|
|||||||
console.warn('[profile] 仓库数据不可用,语言占比与星标汇总已隐藏');
|
console.warn('[profile] 仓库数据不可用,语言占比与星标汇总已隐藏');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 并列判定用百分比最大值(四舍五入后并列很常见:Python/TS 各 36%):
|
||||||
|
// 只给第一根上强调色会让并列者显得"落后",故并列者一并着色
|
||||||
|
const maxPct = langs.reduce((m, l) => Math.max(m, l.pct), 0);
|
||||||
const stats = await githubUserStats();
|
const stats = await githubUserStats();
|
||||||
const L = {
|
const L = {
|
||||||
zh: { followers: '关注者', repos: '公开仓库', stars: '总星标', langs: '语言占比' },
|
zh: { followers: '关注者', repos: '公开仓库', stars: '总星标', langs: '语言占比' },
|
||||||
@@ -108,8 +111,8 @@ const L = {
|
|||||||
<div class="pf-langs">
|
<div class="pf-langs">
|
||||||
<p class="pf-langs-title mono">{L.langs}</p>
|
<p class="pf-langs-title mono">{L.langs}</p>
|
||||||
<ul>
|
<ul>
|
||||||
{langs.map((l, i) => (
|
{langs.map((l) => (
|
||||||
<li class:list={['pf-lang', { top: i === 0 }]}>
|
<li class:list={['pf-lang', { top: l.pct === maxPct }]}>
|
||||||
<span class="pf-lang-name mono">{l.name}</span>
|
<span class="pf-lang-name mono">{l.name}</span>
|
||||||
<span class="pf-bar" aria-hidden="true">
|
<span class="pf-bar" aria-hidden="true">
|
||||||
<span class="pf-bar-fill" style={`width:${Math.max(l.pct, 2)}%`} />
|
<span class="pf-bar-fill" style={`width:${Math.max(l.pct, 2)}%`} />
|
||||||
|
|||||||
@@ -23,11 +23,9 @@ interface Props {
|
|||||||
}
|
}
|
||||||
const { limit = 6, locale = 'zh', pinned, options } = Astro.props;
|
const { limit = 6, locale = 'zh', pinned, options } = Astro.props;
|
||||||
const title = t('open_source_projects', locale);
|
const title = t('open_source_projects', locale);
|
||||||
const LANG_COLORS: Record<string, string> = {
|
// 2026-11-02 视觉终审:原先按 GitHub linguist 给每个仓染语言色点,开源页一次铺
|
||||||
Astro: '#ff5a03', TypeScript: '#3178c6', JavaScript: '#f1e05a',
|
// 15+ 个仓 = 15 种彩色,破「外壳灰阶、彩色只作正文强调(≤3 处/页)」红线。
|
||||||
Python: '#3572a5', Go: '#00add8', Rust: '#dea584', Shell: '#89e051',
|
// 改为统一灰阶点:语言信息靠文字承载,颜色不再做无意义的编码。
|
||||||
HTML: '#e34c26', CSS: '#563d7c', Dockerfile: '#384d54',
|
|
||||||
};
|
|
||||||
|
|
||||||
let projects: RepoCard[] = [];
|
let projects: RepoCard[] = [];
|
||||||
try {
|
try {
|
||||||
@@ -50,7 +48,7 @@ try {
|
|||||||
<span class="repo-meta">
|
<span class="repo-meta">
|
||||||
{p.language && (
|
{p.language && (
|
||||||
<span class="lang">
|
<span class="lang">
|
||||||
<span class="dot" style={`background:${LANG_COLORS[p.language] ?? '#8b8f98'}`} />
|
<span class="dot" />
|
||||||
{p.language}
|
{p.language}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -87,5 +85,12 @@ try {
|
|||||||
.repo-desc { font-size: 13px; color: var(--ink-3); }
|
.repo-desc { font-size: 13px; color: var(--ink-3); }
|
||||||
.repo-meta { display: flex; gap: var(--space-3); font-size: 12px; color: var(--ink-3); margin-top: 4px; }
|
.repo-meta { display: flex; gap: var(--space-3); font-size: 12px; color: var(--ink-3); margin-top: 4px; }
|
||||||
.lang { display: inline-flex; align-items: center; gap: 4px; }
|
.lang { display: inline-flex; align-items: center; gap: 4px; }
|
||||||
.dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
|
.dot {
|
||||||
|
width: 7px;
|
||||||
|
height: 7px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: inline-block;
|
||||||
|
background: var(--ink-4);
|
||||||
|
flex: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -165,8 +165,11 @@ const iso = (d: Date) => d.toISOString();
|
|||||||
}
|
}
|
||||||
/* 时间线骨架:左 mono 日期 + 竖线 + --accent 节点 + 右侧内容卡 */
|
/* 时间线骨架:左 mono 日期 + 竖线 + --accent 节点 + 右侧内容卡 */
|
||||||
.tl-item {
|
.tl-item {
|
||||||
|
/* 日期轨宽度集中一处:10 字符 mono 日期(2026-04-29)在 12.5px 下约 75px,
|
||||||
|
原来复用 var(--space-5)=40px 导致在「-」处折成两行(视觉终审 P1) */
|
||||||
|
--tl-rail: 5.4rem;
|
||||||
position: relative;
|
position: relative;
|
||||||
margin: 0 0 var(--space-4) calc(var(--space-5) + var(--space-4));
|
margin: 0 0 var(--space-4) calc(var(--tl-rail) + var(--space-4));
|
||||||
padding-left: var(--space-4);
|
padding-left: var(--space-4);
|
||||||
border-left: 1px solid var(--line);
|
border-left: 1px solid var(--line);
|
||||||
}
|
}
|
||||||
@@ -184,9 +187,10 @@ const iso = (d: Date) => d.toISOString();
|
|||||||
}
|
}
|
||||||
.tl-date {
|
.tl-date {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: calc(-1 * (var(--space-5) + var(--space-4)));
|
left: calc(-1 * (var(--tl-rail) + var(--space-4)));
|
||||||
top: 4px;
|
top: 4px;
|
||||||
width: var(--space-5);
|
width: var(--tl-rail);
|
||||||
|
white-space: nowrap;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
font-family: var(--font-mono);
|
font-family: var(--font-mono);
|
||||||
font-size: 12.5px;
|
font-size: 12.5px;
|
||||||
|
|||||||
@@ -72,12 +72,16 @@
|
|||||||
|
|
||||||
/* ---- 字体三轨 ---- */
|
/* ---- 字体三轨 ---- */
|
||||||
/* 书评正文:思源宋(用户对标模板的宋体质感拍板);文楷降级为金句点缀 */
|
/* 书评正文:思源宋(用户对标模板的宋体质感拍板);文楷降级为金句点缀 */
|
||||||
|
/* 链尾 emoji 回退(视觉终审 2026-11-02):用户正文里的 emoji 若字体栈无覆盖会落
|
||||||
|
豆腐块(无彩色 emoji 字体的构建机/精简系统实测出黑方块) */
|
||||||
--font-serif: "Noto Serif SC Variable", "Noto Serif SC", Georgia, "Songti SC", STSong,
|
--font-serif: "Noto Serif SC Variable", "Noto Serif SC", Georgia, "Songti SC", STSong,
|
||||||
"Noto Serif CJK SC", "Source Han Serif SC", SimSun, serif;
|
"Noto Serif CJK SC", "Source Han Serif SC", SimSun,
|
||||||
|
"Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", "Noto Emoji", serif;
|
||||||
--font-kai: "LXGW WenKai Screen", "Songti SC", serif;
|
--font-kai: "LXGW WenKai Screen", "Songti SC", serif;
|
||||||
/* UI / 技术文:思源黑 */
|
/* UI / 技术文:思源黑 */
|
||||||
--font-sans: "Noto Sans SC Variable", "Noto Sans SC", system-ui, -apple-system, "PingFang SC",
|
--font-sans: "Noto Sans SC Variable", "Noto Sans SC", system-ui, -apple-system, "PingFang SC",
|
||||||
"Hiragino Sans GB", "Microsoft YaHei", sans-serif;
|
"Hiragino Sans GB", "Microsoft YaHei",
|
||||||
|
"Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", "Noto Emoji", sans-serif;
|
||||||
/* 代码 */
|
/* 代码 */
|
||||||
--font-mono: "JetBrains Mono", ui-monospace, Consolas, "Courier New", monospace;
|
--font-mono: "JetBrains Mono", ui-monospace, Consolas, "Courier New", monospace;
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user