用户实测:「有的文章同时选择了置顶和隐藏,还会显示并置顶」。
根因:后台「隐藏」开关(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
138 lines
4.6 KiB
Plaintext
138 lines
4.6 KiB
Plaintext
---
|
||
// 贡献热力图(极客层 P0):纯 CSS grid,零运行时 JS
|
||
// 数据聚合见 src/lib/heatmap.ts;灰阶四档直接复用 tokens --ink-1..4(亮暗两套自动跟随)
|
||
import { loadHeatmap, levelOf, streakStats } from '../../lib/heatmap';
|
||
import { localeOf } from '../../lib/locale';
|
||
|
||
// 文案全部由页面层注入(locale 无关挂法),keys 与 ui.ts heatmap_* 一一对应
|
||
interface Labels {
|
||
title: string;
|
||
summary: string; // 含 {n} 占位符
|
||
less: string;
|
||
more: string;
|
||
note: string;
|
||
}
|
||
interface Props {
|
||
labels: Labels;
|
||
}
|
||
|
||
const { labels } = Astro.props;
|
||
const locale = localeOf(Astro.url.pathname);
|
||
let weeks: import('../../lib/heatmap').HeatDay[][] = [];
|
||
let total = 0;
|
||
try {
|
||
({ weeks, total } = await loadHeatmap());
|
||
} catch {
|
||
// 三级回落已在 lib 内处理;此处再守一层:空网格不破构建
|
||
}
|
||
// 连续贡献(Phase 5j T2):与热力图同源本地计算 + 标注更新时间(不宣称实时)
|
||
const streak = streakStats(weeks.flat());
|
||
const updatedOn = new Date().toISOString().slice(0, 10);
|
||
const summary = labels.summary.replace('{n}', String(total));
|
||
---
|
||
|
||
<section class="heatmap">
|
||
<h2 class="hm-title">{labels.title}</h2>
|
||
<!-- 53 列窄栏触屏放不下,容器横滚(格子原生 title 提示,无 JS) -->
|
||
<p class="hm-streak mono">
|
||
<span class="hm-streak-item"><strong>{streak.current}</strong>{locale === 'en' ? 'day streak' : '天连续'}</span>
|
||
<span class="hm-streak-item"><strong>{streak.longest}</strong>{locale === 'en' ? 'longest' : '最长'}</span>
|
||
<span class="hm-streak-item"><strong>{streak.total}</strong>{locale === 'en' ? 'total' : '总贡献'}</span>
|
||
<span class="hm-streak-note">数据更新于 {updatedOn}</span>
|
||
</p>
|
||
<div class="hm-scroll" data-anchor-latest>
|
||
<div class="hm-grid" role="img" aria-label={summary}>
|
||
{weeks.map((week) =>
|
||
week.map((day) => (
|
||
<span
|
||
class={`hm-c hm-l${levelOf(day.count)}`}
|
||
title={`${day.date} · ${day.count}`}
|
||
aria-hidden="true"
|
||
/>
|
||
))
|
||
)}
|
||
</div>
|
||
</div>
|
||
<p class="hm-legend">
|
||
<span aria-hidden="true">{labels.less}</span>
|
||
{[0, 1, 2, 3, 4].map((l) => (
|
||
<span class={`hm-c hm-l${l}`} aria-hidden="true" />
|
||
))}
|
||
<span aria-hidden="true">{labels.more}</span>
|
||
<span class="hm-note">{labels.note}</span>
|
||
</p>
|
||
</section>
|
||
<script is:inline>
|
||
// 移动端横滚时锚定到最右(最新)列——数据在"今天",默认 scrollLeft=0 会展示一年前的空区
|
||
document.querySelectorAll('[data-anchor-latest]').forEach((el) => { el.scrollLeft = el.scrollWidth; });
|
||
</script>
|
||
|
||
<style>
|
||
.heatmap { margin-top: var(--space-6); }
|
||
.hm-title {
|
||
font-size: 14px;
|
||
font-family: var(--font-mono);
|
||
color: var(--ink-3);
|
||
letter-spacing: 0.08em;
|
||
margin-bottom: var(--space-3);
|
||
}
|
||
.hm-scroll {
|
||
overflow-x: auto;
|
||
-webkit-overflow-scrolling: touch;
|
||
scrollbar-width: thin;
|
||
}
|
||
/* 列优先流:子元素按周展平后自上而下再向右,得 7 行 × 53 列 */
|
||
.hm-grid {
|
||
display: grid;
|
||
grid-auto-flow: column;
|
||
grid-template-rows: repeat(7, 11px);
|
||
grid-auto-columns: 11px;
|
||
gap: 3px;
|
||
width: max-content;
|
||
}
|
||
.hm-c {
|
||
width: 11px;
|
||
height: 11px;
|
||
border-radius: 2px;
|
||
background: transparent;
|
||
}
|
||
/* 空档 l0 用极淡描边标位;l1-l4 = ink-4 → ink-1(亮暗模式各自成对,多则更醒目) */
|
||
.hm-l0 { border: 1px solid var(--line); }
|
||
.hm-l1 { background: var(--ink-4); }
|
||
.hm-l2 { background: var(--ink-3); }
|
||
.hm-l3 { background: var(--ink-2); }
|
||
.hm-l4 { background: var(--ink-1); }
|
||
.hm-streak {
|
||
display: flex; align-items: baseline; gap: var(--space-4); flex-wrap: wrap;
|
||
margin: 0 0 var(--space-2); font-size: 12.5px; color: var(--ink-3);
|
||
}
|
||
.hm-streak-item strong {
|
||
font-size: 20px; color: var(--ink-1); font-variant-numeric: tabular-nums;
|
||
margin-right: 4px;
|
||
}
|
||
.hm-streak-note { color: var(--ink-4); font-size: 11.5px; }
|
||
.hm-legend {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
margin: var(--space-2) 0 0;
|
||
font-size: 11px;
|
||
color: var(--ink-3);
|
||
}
|
||
.hm-legend .hm-c { width: 9px; height: 9px; }
|
||
/* 桌面收进版心:10px 格 + 2px 缝 = 12px 节距 × 53 列 ≈ 634px,
|
||
无横滚(视觉复核:唯一有数据的最新的列曾恰被裁在滚出区) */
|
||
@media (min-width: 820px) {
|
||
.hm-grid { gap: 2px; }
|
||
.hm-c { width: 10px; height: 10px; }
|
||
.hm-scroll { overflow-x: visible; }
|
||
}
|
||
.hm-note {
|
||
margin-left: var(--space-3);
|
||
font-family: var(--font-mono);
|
||
font-size: 10px;
|
||
color: var(--ink-4);
|
||
letter-spacing: 0.04em;
|
||
}
|
||
</style>
|