1/// <reference types="@astrojs/starlight/locals" />
2import { defineRouteMiddleware } from "@astrojs/starlight/route-data";
3import type { StarlightRouteData } from "@astrojs/starlight/route-data";
4import { getCollection } from "astro:content";
5import type { BlogCategory } from "./content.config";
6
7type SidebarItem = StarlightRouteData["sidebar"][number];
8type SidebarGroup = Extract<SidebarItem, { type: "group" }>;
9
10const groupLabels: Record<BlogCategory, string> = {
11 release: "Releases",
12 post: "Posts",
13};
14const groupOrder: BlogCategory[] = ["post", "release"];
15
16let categoryById: Map<string, BlogCategory> | undefined;
17
18async function loadCategoryById(): Promise<Map<string, BlogCategory>> {
19 if (categoryById) return categoryById;
20 const entries = await getCollection("docs", (entry) =>
21 entry.id.startsWith("blog/"),
22 );
23 const map = new Map<string, BlogCategory>();
24 for (const entry of entries) {
25 const data = entry.data as { category?: BlogCategory };
26 map.set(entry.id, data.category ?? "post");
27 }
28 categoryById = map;
29 return map;
30}
31
32function extractBlogId(href: string): string | undefined {
33 const match = href.match(/\/blog\/([^/]+)\/?$/);
34 return match ? `blog/${match[1]}` : undefined;
35}
36
37export const onRequest = defineRouteMiddleware(async (context, next) => {
38 await next();
39
40 const { starlightRoute, t } = context.locals;
41 const recentLabel = t("starlightBlog.sidebar.recent");
42
43 const recentIndex = starlightRoute.sidebar.findIndex(
44 (item): item is SidebarGroup =>
45 item.type === "group" && item.label === recentLabel,
46 );
47 if (recentIndex === -1) return;
48
49 const recentGroup = starlightRoute.sidebar[recentIndex] as SidebarGroup;
50 const categories = await loadCategoryById();
51
52 const buckets = new Map<BlogCategory, SidebarItem[]>();
53 for (const category of groupOrder) buckets.set(category, []);
54
55 for (const item of recentGroup.entries) {
56 if (item.type !== "link") continue;
57 const id = extractBlogId(item.href);
58 const category: BlogCategory =
59 (id !== undefined ? categories.get(id) : undefined) ?? "post";
60 buckets.get(category)!.push(item);
61 }
62
63 const replacement: SidebarGroup[] = [];
64 for (const category of groupOrder) {
65 const entries = buckets.get(category)!;
66 if (entries.length === 0) continue;
67 replacement.push({
68 type: "group",
69 label: groupLabels[category],
70 entries,
71 collapsed: false,
72 badge: undefined,
73 });
74 }
75
76 starlightRoute.sidebar.splice(recentIndex, 1, ...replacement);
77});