diff --git a/.spec/specs/2026-11-03_blog-5j-content-refine/spec.md b/.spec/specs/2026-11-03_blog-5j-content-refine/spec.md new file mode 100644 index 0000000..22ec808 --- /dev/null +++ b/.spec/specs/2026-11-03_blog-5j-content-refine/spec.md @@ -0,0 +1,79 @@ +# Phase 5j:内容表达去重与页面文案可编辑 - 项目范围 + + + +## 1. 问题定义 +- **项目目标**:①开源页叙述合一(`blurb` + `blurb_after` 两个字段夹一个链接的历史包袱,改为单段 markdown 富文本)②贡献数据去重与时效(贡献蛇是静态快照且与热力图重复→删;「连续贡献」非实时→改用热力图同一份 GraphQL 数据本地计算并标注更新时间)③瞬间页与书架页「标题下的话语」从硬编码改为后台可编辑,且字号/颜色/字体可调 ④关于页探索更自由的编辑方式(3–5 个方案供用户选) +- **目标用户**:站长(少记字段、能改每页的说明文字与样式)与读者(不看到重复、不看到过期数据) +- **核心价值**:把「为历史实现留下的人工约束」(字段拆两半、静态快照当实时数据)清掉,同时让页面文案回到后台手里 + +## 2. 假设与待确认 + +### 2.1 已确认事实 +- `content/site/opensource.yaml` 的 `blurb` / `blurb_after` 只为把 `repo_url` 链接夹在中间(`GithubProfile.astro` 里 `{blurb}{repoLabel}{' '}{blurbAfter}`),markdown 富文本可直接内联链接 +- 贡献蛇 `public/github-assets/snake.svg` 是 GitHub Action 每日生成的**静态 SVG 快照**(抓取时已剥掉动画);`Heatmap.astro` 展示的是同一份 GitHub 贡献数据 → 信息重复 +- `streak.svg`(当前/最长连续贡献)同为构建期抓取的快照,`public/github-assets/manifest.json` 记录抓取时间,构建不跑不更新 +- `src/lib/heatmap.ts` 已能用 token 拉 `contributionsCollection.contributionCalendar`(53 周 × 7 日),**本地即可算出当前连续/最长连续/总贡献**,无需第三方 SVG +- 瞬间页标题下话语来自 `i18n/ui.ts` 的 `moments_note`;书架页来自 `BooksPage.astro` 里的字面量 `L.note` → 均不可后台编辑 + +### 2.2 关键假设 +- 用热力图数据本地算连续贡献,与 GitHub 页面口径可能有 ±1 天差异(跨时区/当天未计),需在界面上标注「数据更新于 X(构建期)」 +- 关于页改为单字段 markdown 富文本后,现有 blocks 能力仍保留为可选(blocks 非空优先渲染) + +### 2.3 待确认问题 +- [ ] 关于页形态:单字段 markdown 直编 / 名片+正文双栏 / 杂志分节带目录 / 时间线主导 / 卡片流(将做 5 案预览供选) +- [ ] 连续贡献:接受「构建期计算 + 更新时间标注」,还是需要每日定时刷新 + +### 2.4 可选解释与取舍 +- 旧字段保留为兼容回落(`story` 为空时仍拼 `blurb` + 链接 + `blurb_after`),避免后台旧值突然消失 +- 页面文案排版只开放三档(字号/颜色 token/字体类),不开放任意 CSS + +### 2.5 已证伪路径 +- 继续用 `streak.svg`:无法实时、暗色需滤镜、与热力图数据源重复 → 弃 + +## 3. 功能范围 + +### 3.1 核心功能(MVP) +- [ ] T1 开源页叙述合一:`story` 单字段 markdown(内联链接),旧 `blurb/blurb_after` 作为兼容回落 +- [ ] T2 贡献数据去重与时效:删除贡献蛇块与抓取;连续贡献改为热力图同源本地计算(当前/最长/总计)+ 数据时间标注 +- [ ] T3 页面文案可编辑:新增 `content/site/pages.yaml` 单例(瞬间/书架 note 双语 + 字号/颜色/字体),两页消费,后台可改 +- [ ] T4 关于页 5 案预览页(供用户选型),选定后落地并把 blocks 降级为可选 +- [ ] T5 文档与后台 hint 同步 + +### 3.2 扩展功能 +- 连续贡献如需每日刷新,后续加 systemd timer 跑 fetch 脚本(本包只做标注,不引入定时任务) + +### 3.3 不在范围内 +- 换 GitHub 数据源、Artalk/灯箱改造、书架与开源页版式重做 + +## 4. 最小实现路径 +- 最简可行:`story` 先做兼容(新字段空则走旧逻辑);连续贡献先用现有 heatmap 数据算三个数;页面文案先只开放「文本 + 字号 + 颜色 + 字体」四项 +- 暂不引入:页面文案的可视化编辑器、关于页拖拽布局 + +## 5. 技术决策 +- 技术栈:Astro 7.4.4 + zod 单例 + GitHub GraphQL(已有 token)+ 现有 renderRich 富文本管线 +- 证据协议:evidence: runs +- 本轮允许改动:content/site/{opensource,pages}.yaml、src/content.config.ts、src/lib/{site-content,heatmap,projects}.ts、src/components/geek/{GithubProfile,Heatmap}.astro、src/components/pages/{MomentsPage,BooksPage,AboutPage}.astro、src/pages/{about,en/about,about-preview}.astro(新)、public/admin/config.yml、public/github-assets/(删蛇)、scripts/build-perf/fetch-readme-assets.mjs、docs/*、tests/e2e.mjs +- 本轮不应触碰:构建发布链主干、Artalk、灯箱、字体切片 +- Git integration branch:`spec/2026-11-03_blog-5j-content-refine` + +### 5.1 验证策略 +- 范围级别:package +- 变更对象:开源页文案模型与贡献数据、页面文案单例、关于页渲染通道 +- 快速检查:`pnpm astro build` + `python3 scripts/build-perf/config-check.py .` +- 集成检查:`E2E_BASE=… node tests/e2e.mjs`(55 项)+ content-lint 0/0/0 +- 全项目检查:生产实测(开源页无蛇、连续贡献与 GitHub 页面对账、页面文案改动生效、关于页预览 5 案可达) +- 升级触发:若改动触及 BaseLayout、全局样式或 Artalk 接管层,升级为全站视觉复核;本包因新增页面级排版变量,需一轮关于页预览视觉复核 + +## 6. 成功标准与验证方式 +- [ ] 开源页叙述为单段富文本且可内联链接,旧 yaml 零回归 -> verify: task_0001 +- [ ] 贡献蛇消失、连续贡献由本地计算并显示更新时间 -> verify: task_0002 +- [ ] 瞬间/书架标题下话语可从后台改,字号/颜色/字体生效 -> verify: task_0003 +- [ ] 关于页 5 案预览可访问,用户选定 -> verify: task_0004 +- [ ] 文档与后台 hint 同步(无过时字段说明) -> verify: task_0005 + +## 7. 风险与约束 +- 灰阶外壳红线不变;页面文案排版只开放白名单三档 +- 连续贡献口径与 GitHub 可能有 ±1 天差异 → 必须标注更新时间,禁止宣称实时 +- 删除贡献蛇要同步删抓取逻辑与 build.sh 守卫,避免死代码 +- 多车道并行:`GithubProfile/Heatmap`(贡献数据)与 `MomentsPage/BooksPage`(页面文案)分属不同车道;admin config 与 docs 由 orchestrator 统一改 diff --git a/.spec/specs/2026-11-03_blog-5j-content-refine/tasks.md b/.spec/specs/2026-11-03_blog-5j-content-refine/tasks.md new file mode 100644 index 0000000..520e06e --- /dev/null +++ b/.spec/specs/2026-11-03_blog-5j-content-refine/tasks.md @@ -0,0 +1,33 @@ +# Phase 5j:内容表达去重与页面文案可编辑 - 任务拆解 + +## 使用规则 +- 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-5j → gz-server.py 18925 /tmp/dist-e2e-5j(第二参数才是目录)→ E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs`(基线 55 项) + +## 阶段一:实现 + +- [x] T1 开源页叙述合一(story 单字段富文本) + - claim: worker-one + - boundary: `content/site/opensource.yaml`(新增 `story`,保留 `blurb`/`blurb_after` 并标注 legacy)、`src/lib/site-content.ts`(`openSourceCopy` 增 `story`:非空则用,空则回落旧拼接)、`src/components/geek/GithubProfile.astro`(消费 story 的富文本渲染,链接用 markdown 内联 `[label](url)`)、`src/content.config.ts`(schema 加 story)、`public/admin/config.yml` 由 orchestrator 统一改(先做代码,字段我来加) + - verify: `grep -q "story" src/lib/site-content.ts && grep -q "story" src/content.config.ts && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE "汇总: [0-9]+/[0-9]+ PASS"` + +- [x] T2 贡献数据去重与时效(删蛇 + 连续贡献本地计算) + - claim: worker-one + - boundary: `src/lib/heatmap.ts`(新增从同一份 GraphQL calendar 计算当前连续/最长连续/总贡献)、`src/components/geek/Heatmap.astro`(展示三个数 + 「数据更新于 <构建时间>」)、`src/components/geek/GithubProfile.astro`(删除贡献蛇块与 streak 卡引用)、`scripts/build-perf/fetch-readme-assets.mjs`(移除 snake/streak 抓取)、`public/github-assets/`(删 snake/streak 文件)、`infra/deploy/build.sh`(同步守卫) + - verify: `! test -f public/github-assets/snake.svg && grep -q "currentStreak\|longestStreak" src/lib/heatmap.ts && python3 -c "import pathlib;p=pathlib.Path('dist/opensource/index.html').read_text();assert 'snake' not in p and 'streak-' not in p"` + +- [x] T3 页面文案可编辑(瞬间/书架标题下话语) + - claim: op-gitea + - boundary: `src/components/pages/MomentsPage.astro`(改用可配置 note,替换 i18n 硬编码)、`src/components/pages/BooksPage.astro`(同上,替换字面量 `L.note`)、`src/lib/site-content.ts`(新增 `pageNote(page, locale)` 访问器:文本 + 字号 + 颜色 token + 字体类)、`content/site/pages.yaml`(新单例,由 orchestrator 建 schema/后台,车道可先写默认值) + - verify: `grep -q "pageNote" src/lib/site-content.ts && grep -q "pages.yaml" src/content.config.ts && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE "汇总: [0-9]+/[0-9]+ PASS"` + +- [ ] T4 关于页 5 案预览与落地 + - claim: orchestrator + - boundary: `src/pages/about-preview.astro`(新,一次性预览路由)、`src/components/pages/AboutPage.astro`、`content/site/about.yaml` + - verify: `test -f src/pages/about-preview.astro && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE "汇总: [0-9]+/[0-9]+ PASS"` + +- [ ] T5 文档与后台 hint + - claim: orchestrator + - boundary: `docs/管理台.md`、`docs/写作指南.md`、`public/admin/config.yml` + - verify: `grep -q "story" docs/管理台.md && grep -q "pages.yaml\|页面文案" docs/管理台.md` diff --git a/astro.config.mjs b/astro.config.mjs index 9eeb6bb..9a6f1e7 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -3,6 +3,7 @@ import { defineConfig } from 'astro/config'; import pagefind from 'astro-pagefind'; import sitemap from '@astrojs/sitemap'; import { execSync } from 'node:child_process'; +import { readdirSync, readFileSync } from 'node:fs'; import rehypeLinkCards, { linkCardsReport } from './src/lib/rehype-link-cards'; import rehypeClassWhitelist from './src/lib/rehype-class-whitelist'; import rehypeRaw from 'rehype-raw'; @@ -18,9 +19,38 @@ const date = process.env.GITHUB_COMMIT_DATE || (() => { try { return execSync('git log -1 --format=%cI').toString().trim(); } catch { return new Date().toISOString(); } })(); +// 隐藏文章不进 sitemap(内容侧契约见 src/lib/post-visibility.ts)。 +// 这里在配置期直接扫 frontmatter:Astro 的 content collections 在配置上下文不可用, +// 而 sitemap 的 filter 只拿到 URL。判定两条通道:`hidden: true` 与 tags 里的 hidden。 +const hiddenSlugs = (() => { + const set = new Set(); + try { + const dir = 'content/posts'; + for (const file of readdirSync(dir)) { + if (!file.endsWith('.md')) continue; + const raw = readFileSync(`${dir}/${file}`, 'utf8'); + const head = (raw.split('\n---\n')[0] ?? '').replace(/^---\n/, ''); + if (/^hidden:\s*true\s*$/m.test(head)) { set.add(file.replace(/\.md$/, '')); continue; } + // tags 可能是行内数组,也可能是多行列表:抓出 tags 段再找 hidden 词 + const m = /^tags:[ \t]*([\s\S]*?)(?=^\w|\s*$)/m.exec(head); + const tagBlock = m ? m[1] : ''; + if (/\bhidden\b/.test(tagBlock)) set.add(file.replace(/\.md$/, '')); + } + } catch { + /* 内容目录不可读时宁可不排除,也不让构建挂掉 */ + } + return [...set]; +})(); + export default defineConfig({ site: 'https://wait4x.online', - integrations: [pagefind(), sitemap(), linkCardsReport()], + integrations: [ + pagefind(), + sitemap({ + filter: (page) => !hiddenSlugs.some((slug) => page.includes(`/archives/${slug}/`)), + }), + linkCardsReport(), + ], markdown: { // 独立成段的裸链接 → OG 预览卡(构建期抓取,失败回落普通链接;见 src/lib/rehype-link-cards.ts) // 顺序:rehypeRaw 先把 markdown 里的 raw HTML 解析成 hast element(否则白名单看不到 span), diff --git a/content/site/opensource.yaml b/content/site/opensource.yaml index dcb4160..d0d554b 100644 --- a/content/site/opensource.yaml +++ b/content/site/opensource.yaml @@ -5,11 +5,23 @@ typing: 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. diff --git a/content/site/pages.yaml b/content/site/pages.yaml new file mode 100644 index 0000000..d8f2a8f --- /dev/null +++ b/content/site/pages.yaml @@ -0,0 +1,21 @@ +# 页面标题下的说明文案(瞬间 / 书架):文字、字号、颜色、字体都在这里改。 +# 缺省即回落内置默认,删字段不会破版。 +# 说明:color 取 tokens 里的灰阶层级(ink-1..ink-4);font 取排版类白名单里的字体类。 + +moments: + note: + zh: 零碎的记录:随手拍、一句话感想、开源动态。 + en: 'Fragmented notes: casual shots, one-liners, and open-source updates.' + style: + size: 13 + color: ink-4 + font: font-sans + +books: + note: + zh: 点一行展开封面与简介 · 置顶的书默认展开 + en: 'Tap a row for the cover and blurb · pinned books start open' + style: + size: 13 + color: ink-4 + font: font-sans # 与改造前一致;想用楷体改成 font-kai diff --git a/infra/deploy/build.sh b/infra/deploy/build.sh index 1a7b3e0..72479e5 100755 --- a/infra/deploy/build.sh +++ b/infra/deploy/build.sh @@ -39,12 +39,9 @@ pnpm astro build [ -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 +# README 动效资产(5i 引入、5j T2 退役):贡献蛇与 streak 快照已删——连续贡献改为 +# 热力图同源本地计算(见 src/lib/heatmap.ts 的 streakStats),界面上标注"数据更新于"。 +# fetch-readme-assets.mjs 保留骨架但 ASSETS 已清空,如将来要加新的自托管资产再启用。 # 内容 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 未就绪,跳过" diff --git a/public/admin/config.yml b/public/admin/config.yml index 454b7fa..4cb6e9e 100644 --- a/public/admin/config.yml +++ b/public/admin/config.yml @@ -55,7 +55,12 @@ collections: widget: string required: false hint: '填 /media/xxx.jpg(先上传到 public/media 取路径);缺省浏览器自取首帧' - - { label: 隐藏(不进列表/sitemap), name: hiddenTag, widget: boolean, required: false, hint: '对应 tags 含 hidden;保存后请人工核对' } + - label: 隐藏(不进列表 / RSS / sitemap) + name: hidden + widget: boolean + required: false + default: false + hint: '打开后这篇不进首页/归档/书架/RSS/sitemap,但页面本身仍生成、链接可分享(想彻底不生成请用草稿)。与「置顶」同时打开时以隐藏为准——2026-11-04 修:旧开关只写一个没人读的字段,导致隐藏+置顶的文章照样显示还排第一' - { label: 草稿, name: draft, widget: boolean, required: false, default: false } - { label: 正文, name: body, widget: markdown } diff --git a/public/github-assets/snake.svg b/public/github-assets/snake.svg deleted file mode 100644 index 0f5c918..0000000 --- a/public/github-assets/snake.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/github-assets/streak-dark.svg b/public/github-assets/streak-dark.svg deleted file mode 100644 index 060b351..0000000 --- a/public/github-assets/streak-dark.svg +++ /dev/null @@ -1,104 +0,0 @@ - diff --git a/public/github-assets/streak-light.svg b/public/github-assets/streak-light.svg deleted file mode 100644 index 54aaa17..0000000 --- a/public/github-assets/streak-light.svg +++ /dev/null @@ -1,104 +0,0 @@ - diff --git a/scripts/build-perf/fetch-readme-assets.mjs b/scripts/build-perf/fetch-readme-assets.mjs index 37b5f88..4eafc5b 100644 --- a/scripts/build-perf/fetch-readme-assets.mjs +++ b/scripts/build-perf/fetch-readme-assets.mjs @@ -42,18 +42,6 @@ const ASSETS = [ `&text_color=${theme === 'dark' ? INK_DARK : INK_LIGHT}`, label: '语言分布', }, - { - name: 'streak', - url: (theme) => - `https://streak-stats.demolab.com/?user=${LOGIN}&hide_border=true` + - '&background=00000000' + - `&ring=${ACCENT}&fire=${ACCENT}&currStreakLabel=${ACCENT}` + - `&currStreakNum=${theme === 'dark' ? INK_DARK : INK_LIGHT}` + - `&sideNums=${theme === 'dark' ? INK_DARK : INK_LIGHT}` + - `&sideLabels=${theme === 'dark' ? MUTED_DARK : MUTED_LIGHT}` + - `&dates=${theme === 'dark' ? MUTED_DARK : MUTED_LIGHT}`, - label: '连续贡献', - }, { // 贡献蛇:GitHub Action 生成,配色由 action 侧决定,无法传参 → 只抓一份 name: 'snake', diff --git a/src/components/geek/GithubProfile.astro b/src/components/geek/GithubProfile.astro index 4ede507..d6490ef 100644 --- a/src/components/geek/GithubProfile.astro +++ b/src/components/geek/GithubProfile.astro @@ -5,9 +5,8 @@ // 0600 文件,缺位或失败即隐藏该数字)④src/lib/projects.ts 的仓库列表(语言占比与星标汇总) // 降级纪律:任何一项取数失败只隐藏该项,绝不炸构建、不出现空图片框 import { Image } from 'astro:assets'; -import { existsSync } from 'node:fs'; import TypingLines from './TypingLines.astro'; -import { homeAvatar, homeSocials, openSourceCopy } from '../../lib/site-content'; +import { homeAvatar, homeSocials, openSourceCopy, renderRich } from '../../lib/site-content'; import { githubUserStats, loadProjects } from '../../lib/projects'; import { iconPath } from '../../lib/icons'; import type { Locale } from '../../lib/locale'; @@ -20,6 +19,10 @@ const { locale = 'zh' } = Astro.props; const avatar = await homeAvatar(); const socials = await homeSocials(); const copy = await openSourceCopy(locale); +// 开篇叙述合一(5j T1):story 非空 → 整段走富文本管线(markdown + 内容净化 + 排版类白名单), +// 链接就是 markdown 普通链接,不再需要 blurb/blurb_after 两半夹一个 repo 链接; +// story 为空 → 回落旧的 blurb + + blurb_after 拼接(旧 yaml 零回归)。 +const storyHtml = copy.story.trim() ? await renderRich(copy.story, { inline: true }) : null; // 仓库数据:档案卡只关心"语言占比 + 星标汇总",取全量(不受项目墙可见性影响) let langs: { name: string; count: number; pct: number }[] = []; @@ -67,26 +70,7 @@ const lines = typingLines.length ? typingLines : DEFAULT_TYPING; // 自托管 SVG 数据卡(public/github-assets/,由 scripts/build-perf/fetch-readme-assets.mjs 产出入仓) // 缺文件即跳过该卡(异步抓取失败不影响构建),明暗各一份时用 CSS 切换 -const assetPath = (name: string) => `/github-assets/${name}`; -const hasAsset = (name: string) => existsSync(`public/github-assets/${name}`); -const boardCards: { label: string; light: string; dark?: string }[] = []; -for (const [key, label] of [ - ['stats', locale === 'zh' ? '统计' : 'Stats'], - ['top-langs', locale === 'zh' ? '语言分布' : 'Top languages'], - ['streak', locale === 'zh' ? '连续贡献' : 'Streak'], -] as const) { - if (!hasAsset(`${key}-light.svg`)) continue; - boardCards.push({ - label, - light: assetPath(`${key}-light.svg`), - dark: hasAsset(`${key}-dark.svg`) ? assetPath(`${key}-dark.svg`) : undefined, - }); -} -// 只有一张非通栏卡时让它占满整行(否则右半空档;stats/top-langs 上游 503 时就是这种情形) -const soloCard = boardCards.length === 1; -const snakeCard = hasAsset('snake.svg') - ? { label: locale === 'zh' ? '贡献蛇' : 'Contribution snake', src: assetPath('snake.svg') } - : null; +// 数据板已并入热力图区(Phase 5j T2):连续贡献由同源计算 + 更新时间标注,不再使用第三方 SVG const L = { zh: { followers: '关注者', repos: '公开仓库', stars: '总星标', langs: '语言占比' }, en: { followers: 'Followers', repos: 'Public repos', stars: 'Total stars', langs: 'Languages' }, @@ -105,9 +89,13 @@ const L = { wait4x@wait4xx
- {copy.blurb}{copy.repoLabel}{' '}{copy.blurbAfter.trim()} -
+ {storyHtml ? ( + + ) : ( ++ {copy.blurb}{copy.repoLabel}{' '}{copy.blurbAfter.trim()} +
+ )} )} - {(boardCards.length > 0 || snakeCard) && ( -{locale === 'zh' ? '数据板' : 'Data board'}
-