feat: add blog with archives, pagination, search, sharing and RSS

- Content collection (one folder per post) with zod schema
- /blog, category and tag archives with /sayfa/<n>/ pagination
- Post page: breadcrumbs, date badges, share bar, TOC, DIY notice,
  code copy, license line, prev/next, BlogPosting + BreadcrumbList JSON-LD
- Sidebar: Pagefind search, categories, featured fallback, tag cloud
- Sätteri HAST plugins for figure/figcaption and external links
- Dual-theme Shiki, themed prose, RSS feed, latest posts on home
- Three sample posts marked as examples

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-16 01:06:07 +03:00
parent f2d943c31b
commit 809f89af08
27 changed files with 1599 additions and 27 deletions
+41
View File
@@ -0,0 +1,41 @@
---
import { blog, fmt } from '../../lib/data';
import { dateBadges, formatDate, isoDay, type DateBadge } from '../../lib/dates';
import Badge from '../Badge.astro';
export interface Props {
pubDate: Date;
updatedDate?: Date;
/** Reading time in minutes, shown after the dates. */
minutes?: number;
class?: string;
}
const { pubDate, updatedDate, minutes, class: className } = Astro.props;
const { primary, secondary } = dateBadges(pubDate, updatedDate);
const meta = (b: DateBadge) =>
b.kind === 'updated'
? { label: blog.dates.updated, icon: 'tabler:refresh' }
: { label: blog.dates.created, icon: 'tabler:calendar-plus' };
const p = meta(primary);
const s = secondary && meta(secondary);
---
<div class:list={['flex flex-wrap items-center gap-2', className]}>
<Badge variant="solid" icon={p.icon}>
{p.label}
<time datetime={isoDay(primary.date)}>{formatDate(primary.date)}</time>
</Badge>
{secondary && s && (
<Badge variant="outline" icon={s.icon}>
{s.label}
<time datetime={isoDay(secondary.date)}>{formatDate(secondary.date)}</time>
</Badge>
)}
{minutes && (
<Badge variant="outline" icon="tabler:clock">
{fmt(blog.card.readingTime, { n: minutes })}
</Badge>
)}
</div>