diff --git a/src/components/PostList.astro b/src/components/PostList.astro index e6ba575..0bab26f 100644 --- a/src/components/PostList.astro +++ b/src/components/PostList.astro @@ -1,6 +1,9 @@ --- -// 年份分组 + 轨道分节的文章列表(首页与 /archives/ 共用) +// 年份分组 + 轨道分节的文章列表(首页与归档页共用) // 轨道标签单一事实源:content.config 的 POST_TRACKS,此处不另立词表 +// 双形态(2026-11-02 用户拍板折叠需求): +// 默认形态 = 首页/列表平铺(年 → 轨道分节),可 limit 截断 + moreHref 出口 +// fold 形态 = 归档页:按年
(最新年默认开)+ 年内按月分组,零 JS 原生折叠 import type { CollectionEntry } from 'astro:content'; import { POST_TRACKS, type PostCategory } from '../content.config'; import { t } from '../i18n/ui'; @@ -11,8 +14,15 @@ interface Props { locale?: 'zh' | 'en'; /** slug → 译题(en 列表显示英文标题,视觉复核范围外观察) */ titleOverrides?: Record; + /** 截断条数(首页 6 篇);缺省全量 */ + limit?: number; + /** 截断后的「全部文章 →」出口链接;缺省不渲染 */ + moreHref?: string; + /** true = 归档折叠形态:按年 details(最新年开)+年内按月分组 */ + fold?: boolean; } -const { posts, locale = 'zh', titleOverrides } = Astro.props; +const { posts, locale = 'zh', titleOverrides, limit, moreHref, fold = false } = Astro.props; +const visible = typeof limit === 'number' ? posts.slice(0, limit) : posts; // 轨道标签走字典(en 首页复用本组件时的双语位);zh 保持原词表语义 const TRACK_LABEL: Record = { reading: t('category_reading', locale), @@ -22,59 +32,125 @@ const TRACK_LABEL: Record = { const hrefOf = (id: string) => (locale === 'en' ? `/en/archives/${id}/` : `/archives/${id}/`); const byYear = new Map[]>(); -for (const post of posts) { +for (const post of visible) { const year = post.data.pubDate.getUTCFullYear(); const bucket = byYear.get(year); if (bucket) bucket.push(post); else byYear.set(year, [post]); } +const units = t('unit_posts', locale); const mmdd = (d: Date) => `${String(d.getUTCMonth() + 1).padStart(2, '0')}-${String(d.getUTCDate()).padStart(2, '0')}`; +// 月分组标签:zh「2026 年 11 月」/ en「November 2026」(UTC,与年份口径一致) +const monthLabel = (d: Date) => + locale === 'zh' + ? `${d.getUTCFullYear()} 年 ${d.getUTCMonth() + 1} 月` + : `${d.toLocaleString('en-US', { month: 'long', timeZone: 'UTC' })} ${d.getUTCFullYear()}`; +const byMonth = (list: CollectionEntry<'posts'>[]) => { + const m = new Map[]>(); + for (const post of list) { + const key = `${post.data.pubDate.getUTCFullYear()}-${post.data.pubDate.getUTCMonth()}`; + const bucket = m.get(key); + if (bucket) bucket.push(post); + else m.set(key, [post]); + } + return [...m.entries()]; +}; --- -
- {[...byYear.entries()].map(([year, list]) => ( -
-

{year}

- {(Object.keys(POST_TRACKS) as PostCategory[]).map((cat) => { - const rows = list.filter((post) => post.data.category === cat); - if (rows.length === 0) return null; - return ( -
-

+{fold ? ( +
+ {[...byYear.entries()].map(([year, list], i) => ( +
+ + {year} + · {list.length} {units} + +
+ {byMonth(list).map(([key, rows]) => ( +
+

+ {monthLabel(rows[0].data.pubDate)} + · {rows.length} {units} +

+