Files
blog/src/pages/index.astro
T
wait4xandGLM-5.3 859cac19a2 feat(cms): 首页四项可配 + 开源页项目可见性 + GitHub README 开篇
- 首页:subtitle(markdown 短文本)|latestPhotos 0-6(0 隐藏)|
  perTrackLimit 4-8|sections{tracks,photos,heatmap,projects} 开关,
  访问器 homeConfig() 夹取越界值,字段级回落默认(零配置=原形态)
- 开源页项目可见性:mode all|only|exclude + include[]/exclude[] +
  sort pinned-first|stars|updated + perSource both|github|gitea;
  projects.ts 加 LoadProjectsOptions + arrange()(来源→可见性→排序)
- 开源页开篇支持 GitHub README:intro_from off|github-readme,构建期
  取 wait4xx/wait4xx README(token 复用宿主 0600 文件)→ 缓存
  content/.github-readme.md 入仓 → renderMarkdown;失败逐级回落
  (缓存 → yaml blurb)。实测取到 3117 字符,现状仍 off 待用户开启
- 修既有 bug:orderPinned 内硬 slice(0,6) 把开源页 limit=99 也截到 6 条
  → 移除,开源页恢复全量 16 条
- 文档:写作指南加首页/项目墙两张字段表(含 YAML 1.1 `off` 需加引号坑、
  README 行为与外部徽章风险说明)

车道:op-gitea(3/3);收编复验:构建 48 页 + E2E 37/37 + 开源页 16 条 +
首页默认形态(2 轨/热力图/项目墙)无回归

验证: node v26.13.0 构建 48 页 exit 0;E2E 37/37 PASS;playwright 实测

Spec: none (首页与开源页后台扩展,车道级交付)

Co-authored-by: GLM-5.3
2026-11-02 16:45:01 +08:00

162 lines
4.9 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
import { Image } from 'astro:assets';
import { getCollection } from 'astro:content';
import BaseLayout from '../layouts/BaseLayout.astro';
import IdCard from '../components/IdCard.astro';
import PostList from '../components/PostList.astro';
import ProjectWall from '../components/geek/ProjectWall.astro';
import Heatmap from '../components/geek/Heatmap.astro';
import { fetchHeartbeat, agoZh } from '../lib/heartbeat';
import { t } from '../i18n/ui';
import { homeConfig, homeTagline, renderMarkdown } from '../lib/site-content';
import SocialLinks from '../components/SocialLinks.astro';
// 首页可配项(后台主页设置):副标题/影像张数/区块开关/每轨条数,字段级回落
const cfg = await homeConfig('zh');
const subtitleHtml = cfg.subtitle ? await renderMarkdown(cfg.subtitle) : '';
const posts = (await getCollection('posts', ({ data }) => !data.draft && !data.tags.includes('hidden'))).sort(
(a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime(),
);
// 最新影像取集合前 4 张;照片时间序由照片管线(exif 车道)治理,此处只裁切
const photos = (await getCollection('photos')).slice(0, Math.max(1, cfg.latestPhotos));
const heartbeat = await fetchHeartbeat();
// 身份语改由 content/site/home.yaml 供给(后台可改),缺值回落默认文案
const tagline = await homeTagline('zh');
---
<BaseLayout title="wait4x">
<IdCard>
<p class="tagline">{tagline}</p>
{subtitleHtml && <div class="subtitle" set:html={subtitleHtml} />}
<SocialLinks />
</IdCard>
{cfg.sections.tracks && <PostList {posts} perTrack={cfg.perTrackLimit} />}
{cfg.sections.photos && cfg.latestPhotos > 0 && (
<section class="latest" aria-label="最新影像">
<div class="latest-head">
<span class="lab">最新影像</span>
<a class="tap more" href="/gallery/">进入图库 →</a>
</div>
<div class="thumbs">
{photos.map((photo, i) => (
<a class="thumb" href="/gallery/" aria-label={`查看图库:${photo.data.title}`}>
{/* 首两图 eager(Astro Image 默认 lazy 会把 LCP 压到 6s+),首图再拔 fetchpriority */}
<Image
src={photo.data.image}
alt={photo.data.alt ?? photo.data.title}
widths={[180, 360]}
sizes="(min-width: 820px) 20vw, 42vw"
loading={i < 2 ? 'eager' : 'lazy'}
fetchpriority={i === 0 ? 'high' : undefined}
/>
</a>
))}
</div>
</section>
)}
{cfg.sections.heatmap && <Heatmap
labels={{
title: t('heatmap_title', 'zh'),
summary: t('heatmap_summary', 'zh'),
less: t('heatmap_less', 'zh'),
more: t('heatmap_more', 'zh'),
note: t('heatmap_note', 'zh'),
}}
/>}
{cfg.sections.projects && <ProjectWall />}
{heartbeat && (
<p class="heartbeat" aria-label="最近心跳">
<span class="beat-dot" aria-hidden="true"></span>
最近心跳:{agoZh(heartbeat.when)} · {heartbeat.msg}
</p>
)}
</BaseLayout>
<style>
.subtitle {
font-size: 13.5px;
color: var(--ink-4);
margin-top: 2px;
letter-spacing: 0.01em;
}
.subtitle :global(p) { margin: 0; }
.subtitle :global(a) { color: var(--accent); }
.tagline {
font-size: 14px;
color: var(--ink-3);
margin-top: 4px;
letter-spacing: 0.01em;
}
.heartbeat {
font-family: var(--font-mono);
font-size: 12.5px;
color: var(--ink-3);
margin-top: var(--space-2);
display: flex;
align-items: center;
gap: 8px;
}
.beat-dot {
width: 7px; height: 7px; border-radius: 50%;
background: var(--ink-2);
animation: beat 2.4s ease-in-out infinite;
}
@keyframes beat { 0%, 100% { opacity: 0.35; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .beat-dot { animation: none; } }
.socials {
display: flex;
align-items: center;
gap: var(--space-2);
margin-top: var(--space-2);
}
.social {
color: var(--ink-3);
border-radius: 8px;
transition: color var(--dur) var(--ease);
}
.social:hover { color: var(--accent); }
.latest { margin-top: var(--space-6); }
.latest-head {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: var(--space-3);
}
.lab {
font-size: 12.5px;
color: var(--ink-4);
letter-spacing: 0.08em;
}
.more {
font-size: 13px;
color: var(--accent);
padding-inline: var(--space-2);
transition: color var(--dur) var(--ease);
}
.more:hover { color: var(--ink-1); }
.thumbs {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: var(--space-2);
margin-top: var(--space-3);
}
.thumb {
aspect-ratio: 1;
border-radius: 8px;
overflow: hidden;
}
.thumb :global(img) {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform var(--dur) var(--ease);
}
.thumb:hover :global(img) { transform: scale(1.03); }
@media (min-width: 820px) {
.thumbs { grid-template-columns: repeat(4, 1fr); }
}
</style>