export const dynamic = "force-dynamic"; import type { Metadata } from "next"; import Link from "next/link"; import { notFound } from "next/navigation"; import { getProseChapter, getProseChapters } from "@/lib/api"; import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og"; import { BOOK_NAMES } from "@/lib/constants"; import { PageHeader } from "@/components/layout/page-header"; import { JsonLd } from "@/components/seo/json-ld"; import { articleJsonLd } from "@/lib/jsonld"; import { Card, CardContent } from "@/components/ui/card"; import { buttonVariants } from "@/components/ui/button"; import { ChevronLeft, ChevronRight } from "lucide-react"; import { cn } from "@/lib/utils"; import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav"; import { FindInPage } from "@/components/layout/find-in-page"; export async function generateMetadata({ params, searchParams, }: { params: Promise<{ volume: string; slug: string }>; searchParams: Promise>; }): Promise { const { volume, slug } = await params; const sp = await searchParams; const vol = parseInt(volume); const chapter = !isNaN(vol) ? await getProseChapter("kb", vol, slug) : null; const hlQuote = extractQuoteFromHl(sp, chapter?.paragraphs); if (hlQuote) { return buildOgMeta({ title: BOOK_NAMES["kb"], detail: chapter ? `Volume ${vol} — ${chapter.title}` : `Volume ${vol}`, quote: hlQuote, }); } return ogForChapter( "kb", chapter ? `Volume ${vol} — ${chapter.title}` : `Volume ${vol}`, ); } export default async function KbChapterPage({ params, }: { params: Promise<{ volume: string; slug: string }>; }) { const { volume, slug } = await params; const vol = parseInt(volume); if (isNaN(vol) || vol < 1 || vol > 2) notFound(); const chapter = await getProseChapter("kb", vol, slug); if (!chapter) notFound(); // Fetch all chapters/segments in this volume for sidebar + prev/next const allChapters = await getProseChapters("kb", vol); const currentIdx = allChapters.findIndex((ch) => ch.slug === slug); const prev = currentIdx > 0 ? allChapters[currentIdx - 1] : null; const next = currentIdx < allChapters.length - 1 ? allChapters[currentIdx + 1] : null; const chapterLabel = chapter.type === "chapter" && chapter.chapter != null ? `Chapter ${chapter.chapter}` : null; return (
← Volume {vol}

{chapterLabel && ( {chapterLabel}: )} {chapter.title}

{/* Left sidebar — chapter index */} {/* Main content */}
{/* Top navigation */} {/* Chapter content */}
{chapter.paragraphs.map((paragraph, i) => (
))}
{/* Bottom navigation */}
); } // --------------------------------------------------------------------------- // Sidebar — chapter index // --------------------------------------------------------------------------- type SidebarChapter = { slug: string; title: string; type: string; chapter: number | null; }; function ChapterSidebar({ chapters, currentSlug, volume, }: { chapters: SidebarChapter[]; currentSlug: string; volume: number; }) { return ( ); } // --------------------------------------------------------------------------- // Prev / Next navigation // --------------------------------------------------------------------------- type NavChapter = { slug: string; title: string; type: string; chapter: number | null; }; function ChapterNav({ prev, next, volume, chapters, currentSlug, }: { prev: NavChapter | null; next: NavChapter | null; volume: number; chapters: SidebarChapter[]; currentSlug: string; }) { const mobileNavGroups: MobileNavGroup[] = []; const introItems = chapters.filter((i) => i.type !== "chapter"); if (introItems.length > 0) { mobileNavGroups.push({ title: "Front Matter", items: introItems.map((i) => ({ slug: i.slug, label: i.title, href: `/books/kb/${volume}/${i.slug}`, type: i.type, })), }); } const chapterItems = chapters.filter((i) => i.type === "chapter"); if (chapterItems.length > 0) { mobileNavGroups.push({ title: "Chapters", items: chapterItems.map((i) => ({ slug: i.slug, label: i.chapter != null ? `${i.chapter}. ${i.title}` : i.title, href: `/books/kb/${volume}/${i.slug}`, type: i.type, })), }); } return (
{prev ? ( {prev.type === "chapter" && prev.chapter != null ? `Ch. ${prev.chapter}` : prev.title} Previous ) : (
)}
{next ? ( {next.type === "chapter" && next.chapter != null ? `Ch. ${next.chapter}` : next.title} Next ) : (
)}
); }