import { getCollection, type CollectionEntry } from 'astro:content'; import { categories, type Category } from './data'; import { slugify } from './slug'; export type Post = CollectionEntry<'blog'>; /** Published posts (drafts only in dev), newest `pubDate` first. Unknown categories fail the build. */ export async function getPosts(): Promise { const posts = await getCollection('blog', ({ data }) => import.meta.env.DEV || !data.draft); for (const post of posts) { if (!categories.some((c) => c.slug === post.data.category)) { throw new Error( `Post "${post.id}" uses unknown category "${post.data.category}" (see src/data/categories.json)`, ); } } return posts.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf()); } export const postUrl = (post: Post) => `/blog/${post.id}/`; export const categoryUrl = (slug: string) => `/blog/kategori/${slug}/`; export const tagUrl = (tag: string) => `/blog/etiket/${slugify(tag)}/`; export const tagSlug = (tag: string) => slugify(tag); export function categoryOf(post: Post): Category { return categories.find((c) => c.slug === post.data.category)!; } /** ~200 words per minute on the raw Markdown body (code blocks included). */ export function readingMinutes(post: Post): number { const words = (post.body ?? '').split(/\s+/).filter(Boolean).length; return Math.max(1, Math.round(words / 200)); } export function categoryCounts(posts: Post[]) { return categories .map((category) => ({ category, count: posts.filter((p) => p.data.category === category.slug).length, })) .filter((c) => c.count > 0); } export interface TagCount { tag: string; slug: string; count: number; } /** Tags grouped by slug (first spelling wins), most used first. */ export function tagCounts(posts: Post[]): TagCount[] { const map = new Map(); for (const tag of posts.flatMap((p) => p.data.tags)) { const slug = slugify(tag); const entry = map.get(slug) ?? { tag, slug, count: 0 }; entry.count++; map.set(slug, entry); } return [...map.values()].sort((a, b) => b.count - a.count || a.tag.localeCompare(b.tag, 'tr')); } /** Fallback "featured" list: frontmatter `featured`, else the latest posts. */ export function featuredPosts(posts: Post[], limit = 3): Post[] { const featured = posts.filter((p) => p.data.featured); return (featured.length ? featured : posts).slice(0, limit); } /** prev = older post, next = newer post. */ export function adjacentPosts(posts: Post[], post: Post) { const i = posts.findIndex((p) => p.id === post.id); return { prev: posts[i + 1], next: i > 0 ? posts[i - 1] : undefined }; } /** Chunks a list into pages; always at least one (possibly empty) page. */ export function chunk(items: T[], size: number): T[][] { const pages: T[][] = []; for (let i = 0; i < items.length; i += size) pages.push(items.slice(i, i + size)); return pages.length ? pages : [[]]; } /** Page 1 lives at the base URL, others at `sayfa//`. */ export const pageUrl = (base: string, n: number) => (n <= 1 ? base : `${base}sayfa/${n}/`); /** `[...page]` param value for page n (undefined = page 1). */ export const pageParam = (n: number) => (n <= 1 ? undefined : `sayfa/${n}`);