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
+33
View File
@@ -0,0 +1,33 @@
import { defineCollection } from 'astro:content';
import { glob } from 'astro/loaders';
import { z } from 'astro/zod';
// One folder per post: src/content/blog/<slug>/index.md(x) + its images.
const blog = defineCollection({
loader: glob({
pattern: '**/index.{md,mdx}',
base: './src/content/blog',
generateId: ({ entry }) => entry.replace(/\/index\.mdx?$/, ''),
}),
schema: ({ image }) =>
z
.object({
title: z.string().max(70),
description: z.string().min(50).max(160),
pubDate: z.coerce.date(),
updatedDate: z.coerce.date().optional(),
category: z.string(), // must exist in src/data/categories.json (checked in lib/posts.ts)
tags: z.array(z.string()).default([]),
cover: image().optional(),
coverAlt: z.string().optional(),
featured: z.boolean().default(false),
diy: z.boolean().default(false),
draft: z.boolean().default(false),
})
.refine((d) => !d.cover || !!d.coverAlt, {
message: 'coverAlt is required when cover is set',
path: ['coverAlt'],
}),
});
export const collections = { blog };