From 650249e1a8fe7d6645bb712026930dd7e8906ef8 Mon Sep 17 00:00:00 2001 From: Dawid Rycerz Date: Fri, 30 Jan 2026 20:45:07 +0100 Subject: feat(blog): add next/previous post navigation scoped by category Navigate between posts within the same category (regular, microblog, archived). Newer post links left, older post links right. Includes i18n support for English and Polish. Co-Authored-By: Claude Opus 4.5 --- src/components/blog/PostNavigation.astro | 39 ++++++++++++++++++++++++++++++++ src/data/post.ts | 39 ++++++++++++++++++++++++++++++++ src/i18n/translations.ts | 8 +++++++ src/layouts/BlogPost.astro | 6 ++++- src/pages/posts/[...slug].astro | 17 ++++++++------ 5 files changed, 101 insertions(+), 8 deletions(-) create mode 100644 src/components/blog/PostNavigation.astro (limited to 'src') diff --git a/src/components/blog/PostNavigation.astro b/src/components/blog/PostNavigation.astro new file mode 100644 index 0000000..037836f --- /dev/null +++ b/src/components/blog/PostNavigation.astro @@ -0,0 +1,39 @@ +--- +import type { CollectionEntry } from "astro:content"; +import { t } from "@/i18n/translations"; + +interface Props { + prevPost: CollectionEntry<"post"> | null | undefined; + nextPost: CollectionEntry<"post"> | null | undefined; + language?: string | undefined; +} + +const { prevPost, nextPost, language } = Astro.props; +--- + +{ + (prevPost || nextPost) && ( + + ) +} diff --git a/src/data/post.ts b/src/data/post.ts index 85cc0d0..08a6678 100644 --- a/src/data/post.ts +++ b/src/data/post.ts @@ -1,4 +1,5 @@ import { type CollectionEntry, getCollection } from "astro:content"; +import { collectionDateSort } from "@/utils/date"; /** filter out draft posts based on the environment and optionally archived posts */ export async function getAllPosts(includeArchived = false): Promise[]> { @@ -62,3 +63,41 @@ export function getUniqueTagsWithCount(posts: CollectionEntry<"post">[]): [strin ), ].sort((a, b) => b[1] - a[1]); } + +export type PostCategory = "regular" | "microblog" | "archived"; + +/** Determine the category of a post based on its tags. "archived" takes precedence over "microblog". */ +export function getPostCategory(post: CollectionEntry<"post">): PostCategory { + const tags = post.data.tags; + if (tags.includes("archived")) return "archived"; + if (tags.includes("microblog")) return "microblog"; + return "regular"; +} + +export interface PostNavigation { + prevPost: CollectionEntry<"post"> | null; + nextPost: CollectionEntry<"post"> | null; +} + +/** Get previous (newer) and next (older) posts within the same category. + * Posts are sorted newest-first. prevPost = newer, nextPost = older. + */ +export function getPostNavigation( + allPosts: CollectionEntry<"post">[], + currentPost: CollectionEntry<"post">, +): PostNavigation { + const category = getPostCategory(currentPost); + const sameCategoryPosts = allPosts + .filter((p) => getPostCategory(p) === category) + .sort(collectionDateSort); + + const currentIndex = sameCategoryPosts.findIndex((p) => p.id === currentPost.id); + + return { + prevPost: currentIndex > 0 ? (sameCategoryPosts[currentIndex - 1] ?? null) : null, + nextPost: + currentIndex < sameCategoryPosts.length - 1 + ? (sameCategoryPosts[currentIndex + 1] ?? null) + : null, + }; +} diff --git a/src/i18n/translations.ts b/src/i18n/translations.ts index 5659257..0cead23 100644 --- a/src/i18n/translations.ts +++ b/src/i18n/translations.ts @@ -4,12 +4,20 @@ export const translations = { backToTop: "Back to top", updated: "Updated:", viewMoreWithTag: "View more blogs with the tag", + newerPost: "← Newer post", + olderPost: "Older post →", + newerPostSr: "Go to newer post:", + olderPostSr: "Go to older post:", }, pl: { viewOriginalPost: "Zobacz oryginalny wpis na Fediversum →", backToTop: "Powrót na górę", updated: "Zaktualizowano:", viewMoreWithTag: "Zobacz więcej wpisów z tagiem", + newerPost: "← Następny wpis", + olderPost: "Poprzedni wpis →", + newerPostSr: "Przejdź do następnego wpisu:", + olderPostSr: "Przejdź do poprzedniego wpisu:", }, } as const; diff --git a/src/layouts/BlogPost.astro b/src/layouts/BlogPost.astro index 764cd80..73f994b 100644 --- a/src/layouts/BlogPost.astro +++ b/src/layouts/BlogPost.astro @@ -2,15 +2,18 @@ import type { CollectionEntry } from "astro:content"; import Masthead from "@/components/blog/Masthead.astro"; +import PostNavigation from "@/components/blog/PostNavigation.astro"; import { t } from "@/i18n/translations"; import BaseLayout from "./Base.astro"; interface Props { post: CollectionEntry<"post">; + prevPost: CollectionEntry<"post"> | null | undefined; + nextPost: CollectionEntry<"post"> | null | undefined; } -const { post } = Astro.props; +const { post, prevPost, nextPost } = Astro.props; const { ogImage, title, description, updatedDate, publishDate, language } = post.data; const socialImage = ogImage ?? `/og-image/${post.id}.png`; const articleDate = updatedDate?.toISOString() ?? publishDate.toISOString(); @@ -49,6 +52,7 @@ const articleDate = updatedDate?.toISOString() ?? publishDate.toISOString(); } +