fix(posts): 隐藏开关是假开关——修「隐藏+置顶仍显示并排第一」;贡献数据去重

用户实测:「有的文章同时选择了置顶和隐藏,还会显示并置顶」。

根因:后台「隐藏」开关(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
This commit is contained in:
wait4xandGLM-5.3 committed 2026-11-04 22:00:15 +08:00
1 parent 34a78dacb9
commit 49acb4cbd1
21 files changed
+398 -596

No files matched your search

+31 -1
View File
@@ -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),