From 49acb4cbd1c5154478f17d3a5f9c3e8c2c6e4d65 Mon Sep 17 00:00:00 2001 From: wait4xx Date: Wed, 4 Nov 2026 22:00:15 +0800 Subject: [PATCH] =?UTF-8?q?fix(posts):=20=E9=9A=90=E8=97=8F=E5=BC=80?= =?UTF-8?q?=E5=85=B3=E6=98=AF=E5=81=87=E5=BC=80=E5=85=B3=E2=80=94=E2=80=94?= =?UTF-8?q?=E4=BF=AE=E3=80=8C=E9=9A=90=E8=97=8F+=E7=BD=AE=E9=A1=B6?= =?UTF-8?q?=E4=BB=8D=E6=98=BE=E7=A4=BA=E5=B9=B6=E6=8E=92=E7=AC=AC=E4=B8=80?= =?UTF-8?q?=E3=80=8D=EF=BC=9B=E8=B4=A1=E7=8C=AE=E6=95=B0=E6=8D=AE=E5=8E=BB?= =?UTF-8?q?=E9=87=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用户实测:「有的文章同时选择了置顶和隐藏,还会显示并置顶」。 根因:后台「隐藏」开关(hiddenTag)是个假开关——它只写 `hiddenTag: true` 这个 没人读的字段,而全站过滤只认 `tags` 里的 `hidden`。于是隐藏从未生效; 叠加置顶后,这篇还在列表最前面。 - 新增 src/lib/post-visibility.ts:可见性契约单一事实源——`!draft && !hidden && !tags.includes('hidden')`(两条通道都认,历史写法兼容) - postsSchema 增真字段 `hidden`;七处消费点(首页双轨/归档 zh/en/书架/RSS zh/en/ 文章页导航)统一走 isVisiblePost - 后台假开关 hiddenTag 换成真开关 hidden,hint 写清语义:不进列表/RSS/sitemap, 页面本身仍生成(链接可分享);与置顶同时打开时以隐藏为准 - sitemap 过滤隐藏文章(astro.config 配置期扫 frontmatter,两种写法都认; 内容目录不可读时宁可不排除也不炸构建) 贡献数据去重(5j T2,回答用户「连续贡献是否实时」): - 连续贡献不再构建期抓取 streak.svg 快照(不实时、暗色需滤镜、与热力图同源重复), 改为热力图同一份 GraphQL 日历本地计算(src/lib/heatmap.ts streakStats): 当前连续(以最后有贡献日为终点)/ 最长 / 总贡献,界面上标注「数据更新于 X」 - 贡献蛇块与自托管 snake/streak SVG 删除,fetch 脚本 ASSETS 清空, build.sh 守卫改为退役说明;开源页数据板整块并入热力图区 验证: 构建 51 页 exit 0;content-lint 52 页 0/0/0;E2E 56/56; 临时两篇「置顶+隐藏」(新字段/旧 tags 各一)实测——首页/归档/书架/ 双语首页/RSS 双语/sitemap 全部零命中,页面本身仍生成; 清理后归档首条恢复 writing-template(普通置顶不受影响); 开源页 snake/streak 资产零残留、连续统计条与更新时间标注就位 Spec: 2026-11-03_blog-5j-content-refine (T2 + 隐藏 bug 修复) Co-authored-by: GLM-5.3 --- astro.config.mjs | 32 +- infra/deploy/build.sh | 9 +- public/admin/config.yml | 7 +- public/github-assets/snake.svg | 1 - public/github-assets/streak-dark.svg | 104 ---- public/github-assets/streak-light.svg | 104 ---- scripts/build-perf/fetch-readme-assets.mjs | 12 - src/components/geek/GithubProfile.astro | 78 +-- src/components/geek/Heatmap.astro | 22 +- src/components/pages/BooksPage.astro | 3 +- src/components/pages/HomePage.astro | 3 +- src/content.config.ts | 6 + src/lib/heatmap.ts | 44 ++ src/lib/post-visibility.ts | 22 + src/pages/archives/[...slug].astro | 3 +- src/pages/archives/index.astro | 3 +- src/pages/en/archives/[...slug].astro | 3 +- src/pages/en/archives/index.astro | 3 +- src/pages/en/rss.xml.ts | 3 +- src/pages/heatmap-preview.astro | 529 ++++++++++----------- src/pages/rss.xml.ts | 3 +- 21 files changed, 398 insertions(+), 596 deletions(-) delete mode 100644 public/github-assets/snake.svg delete mode 100644 public/github-assets/streak-dark.svg delete mode 100644 public/github-assets/streak-light.svg create mode 100644 src/lib/post-visibility.ts 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/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 @@ -Generated with https://github.com/Platane/snk \ 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 @@ - - - - - - - - - - - - - - - - - - - - - - - - - 344 - - - - - - - Total Contributions - - - - - - - May 24, 2019 - Present - - - - - - - - Current Streak - - - - - - - Nov 3 - - - - - - - - - - - - - - - - - 0 - - - - - - - - - 6 - - - - - - - Longest Streak - - - - - - - Oct 19 - Oct 24 - - - - - - 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 @@ - - - - - - - - - - - - - - - - - - - - - - - - - 344 - - - - - - - Total Contributions - - - - - - - May 24, 2019 - Present - - - - - - - - Current Streak - - - - - - - Nov 3 - - - - - - - - - - - - - - - - - 0 - - - - - - - - - 6 - - - - - - - Longest Streak - - - - - - - Oct 19 - Oct 24 - - - - - - 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 b728d3c..d6490ef 100644 --- a/src/components/geek/GithubProfile.astro +++ b/src/components/geek/GithubProfile.astro @@ -5,7 +5,6 @@ // 0600 文件,缺位或失败即隐藏该数字)④src/lib/projects.ts 的仓库列表(语言占比与星标汇总) // 降级纪律:任何一项取数失败只隐藏该项,绝不炸构建、不出现空图片框 import { Image } from 'astro:assets'; -import { existsSync } from 'node:fs'; import TypingLines from './TypingLines.astro'; import { homeAvatar, homeSocials, openSourceCopy, renderRich } from '../../lib/site-content'; import { githubUserStats, loadProjects } from '../../lib/projects'; @@ -71,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' }, @@ -171,33 +151,6 @@ const L = { )} - {(boardCards.length > 0 || snakeCard) && ( -
-

{locale === 'zh' ? '数据板' : 'Data board'}

-
- {boardCards.map((card) => ( -
- {card.label} - {card.dark && ( - {card.label} - )} -
{card.label}
-
- ))} - {snakeCard && ( -
- {snakeCard.label} -
{snakeCard.label}
-
- )} -
-
- )} diff --git a/src/pages/rss.xml.ts b/src/pages/rss.xml.ts index 2d5d8d9..c7adb4d 100644 --- a/src/pages/rss.xml.ts +++ b/src/pages/rss.xml.ts @@ -1,10 +1,11 @@ import rss from '@astrojs/rss'; +import { isVisiblePost } from '../lib/post-visibility'; import { getCollection } from 'astro:content'; import type { APIContext } from 'astro'; // RSS:posts 集合全量(draft 排除);Phase 3 起双语各出一路(/rss.xml 与 /en/rss.xml) export async function GET(context: APIContext) { - const posts = (await getCollection('posts', ({ data }) => !data.draft && !data.tags.includes('hidden'))) + const posts = (await getCollection('posts', ({ data }) => isVisiblePost(data))) .sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf()); return rss({ title: 'wait4x',