import type { CollectionEntry } from 'astro:content'; import { getCollection } from 'astro:content'; export type BlogPost = { entry: CollectionEntry<'posts'>; title: string; slug: string; date: Date; dateText: string; tags: string[]; categories: string[]; summary: string; comments: boolean; cover?: string; href: string; body: string; }; const categorySlugMap: Record = { '技术': 'tech', '建站手札': 'site-notes', '学习': 'study', '生活': 'life', '杂谈': 'notes', }; function asArray(value: unknown): string[] { if (Array.isArray(value)) return value.map(String).filter(Boolean); if (typeof value === 'string' && value.trim()) return [value.trim()]; return []; } function getCategories(entry: CollectionEntry<'posts'>): string[] { const data = entry.data as Record; return [...asArray(data.categories), ...asArray(data.category)]; } function toDate(value: unknown): Date { if (value instanceof Date) return value; if (typeof value === 'string' || typeof value === 'number') { const date = new Date(value); if (!Number.isNaN(date.valueOf())) return date; } return new Date(0); } function formatDate(date: Date): string { return new Intl.DateTimeFormat('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', }).format(date); } function fallbackSlug(id: string): string { return id .split('/') .pop() ?.replace(/\.md$/, '') .toLowerCase() ?? 'post'; } function makeSummary(entry: CollectionEntry<'posts'>): string { const explicit = entry.data.summary ?? entry.data.description; if (typeof explicit === 'string' && explicit.trim()) return explicit.trim(); return entry.body .replace(/```[\s\S]*?```/g, '') .replace(/<[^>]+>/g, '') .replace(/[#>*_`~\-\[\]\(\)]/g, '') .replace(/\s+/g, ' ') .trim() .slice(0, 120); } export async function getAllPosts(): Promise { const entries = await getCollection('posts'); return entries .map((entry) => { const date = toDate(entry.data.date); const slug = String(entry.data.abbrlink ?? fallbackSlug(entry.id)); const title = String(entry.data.title ?? slug); return { entry, title, slug, date, dateText: formatDate(date), tags: asArray(entry.data.tags), categories: getCategories(entry), summary: makeSummary(entry), comments: entry.data.comments !== false, cover: typeof entry.data.cover === 'string' ? entry.data.cover : undefined, href: `/posts/${slug}/`, body: entry.body, }; }) .sort((a, b) => b.date.valueOf() - a.date.valueOf()); } export async function getAllTags(): Promise { return [...new Set((await getAllPosts()).flatMap((post) => post.tags))].sort((a, b) => a.localeCompare(b, 'zh-CN')); } export async function getAllCategories(): Promise { return [...new Set((await getAllPosts()).flatMap((post) => post.categories))].sort((a, b) => a.localeCompare(b, 'zh-CN')); } export function getCategorySlug(category: string): string { const mapped = categorySlugMap[category]; if (mapped) return mapped; const slug = category .trim() .toLowerCase() .normalize('NFKD') .replace(/[\u0300-\u036f]/g, '') .replace(/[^a-z0-9]+/g, '-') .replace(/^-|-$/g, ''); return slug || 'category'; } export function getCategoryHref(category: string): string { return `/categories/${getCategorySlug(category)}/`; }