---
/**
* 书评索引(/books):方案 C「抽书阅读」(用户 2026-11-03 拍板定稿)。
*
* 为什么是这一版:五个方案里它是唯一在「书少、封面少」时也不露怯的——
* 一排放得下、长书名不裁、没有 4+1 孤儿位;点一行抽出封面与简介,靠原生
* details 零 JS。
*
* 置顶契约(与文章同一套,见 src/lib/post-order.ts):
* - frontmatter `pinned: true` → 排在最前(置顶优先 → 日期倒序)
* - **支持多本置顶**:所有 pinned 书都默认展开,不限于一本
* - 置顶标记用「↑ + 横线」的回到顶部图标,不写文字(用户要求);
* 展开/收起是另一个形状的 chevron,避免两个箭头混读
*/
import { getCollection } from 'astro:content';
import { isVisiblePost } from '../../lib/post-visibility';
import { Image } from 'astro:assets';
import BaseLayout from '../../layouts/BaseLayout.astro';
import { t } from '../../i18n/ui';
import type { Locale } from '../../lib/locale';
import { sortPosts } from '../../lib/post-order';
import { pageNote } from '../../lib/site-content';
interface Props {
locale?: Locale;
}
const { locale = 'zh' } = Astro.props;
// 标题下说明:文字与样式都可后台改(白名单校验,非法值回落)
const note = await pageNote('books', locale);
const books = sortPosts(
await getCollection(
'posts',
({ data }) => isVisiblePost(data) && data.category === 'reading',
),
);
/**
* description 回退契约:缺省取正文第一个非空段落(去 markdown 记号)前 60 字。
* 与历史实现保持一致——书架卡不能出现空白简介。
*/
const firstPara = (body: string | undefined): string => {
const line =
(body ?? '')
.split(/\n\s*\n/)
.map((l) => l.trim())
.find((l) => l && !l.startsWith('#') && !l.startsWith('---')) ?? '';
return line.replace(/[*_>`[\]]/g, '').slice(0, 60);
};
const items = books.map((b) => ({
id: b.id,
title: b.data.title,
author: b.data.bookAuthor ?? '',
desc: b.data.description ?? firstPara(b.body),
cover: b.data.cover,
mood: b.data.mood ?? 'var(--ink-4)',
pinned: b.data.pinned === true,
href: locale === 'en' ? `/en/archives/${b.id}/` : `/archives/${b.id}/`,
}));
const pinnedCount = items.filter((i) => i.pinned).length;
const L = {
shelf: t('books_title', locale),
count: locale === 'zh' ? `${items.length} 本` : `${items.length} books`,
read: locale === 'zh' ? '读这篇书评 →' : 'Read the review →',
pinned: t('pinned_label', locale),
expand: locale === 'zh' ? '展开封面与简介' : 'Expand cover and blurb',
};
---
{L.shelf}{L.count}
{pinnedCount > 0 && {pinnedCount} {L.pinned}}
{items.map((b) => (
-
{b.title}
{b.pinned && (
{/* 回到顶部图标(↑ + 横线):不写文字,形状与右侧 chevron 区分 */}
)}
{b.author && {b.author}}
))}