fix: SVA chapters

This commit is contained in:
gosangam
2026-05-02 17:45:32 +05:30
parent ae6a752787
commit 47a2dc375e
6 changed files with 869 additions and 505 deletions
+68 -99
View File
@@ -3,6 +3,7 @@ import { notFound } from "next/navigation";
import {
getSvaContent,
getSvaSidebarItems,
getNavigableItems,
type SvaChapter,
type SvaSidebarItem,
} from "@/lib/sva";
@@ -28,18 +29,24 @@ export default async function SvaChapterPage({
if (!content) notFound();
const sidebarItems = await getSvaSidebarItems();
const currentIdx = sidebarItems.findIndex((item) => item.slug === slug);
const prev = currentIdx > 0 ? sidebarItems[currentIdx - 1] : null;
// Build a flat list of navigable items (skip "parent" type — they're labels, not pages)
const navigableItems = getNavigableItems(sidebarItems);
const currentIdx = navigableItems.findIndex((item) => item.slug === slug);
const prev = currentIdx > 0 ? navigableItems[currentIdx - 1] : null;
const next =
currentIdx < sidebarItems.length - 1 ? sidebarItems[currentIdx + 1] : null;
currentIdx < navigableItems.length - 1
? navigableItems[currentIdx + 1]
: null;
// Build the page title
const isChapter =
"chapter" in content &&
typeof (content as SvaChapter).chapter === "number" &&
(content as SvaChapter).chapter !== null;
const chapterNum = isChapter ? (content as SvaChapter).chapter : null;
// Determine if this is a sub-chapter (has parentChapter)
const isSubChapter =
"parentChapter" in content &&
typeof (content as SvaChapter).parentChapter === "number";
const parentChapterNum = isSubChapter
? (content as SvaChapter).parentChapter
: null;
return (
<div className="min-h-screen bg-background">
@@ -51,8 +58,8 @@ export default async function SvaChapterPage({
&larr; Songs of the Vaiṣṇava Ācāryas
</Link>
<h1 className="mt-3 text-2xl font-bold text-foreground">
{chapterNum != null && (
<span className="text-primary">Part {chapterNum}: </span>
{parentChapterNum != null && (
<span className="text-primary">Part {parentChapterNum}: </span>
)}
{content.title}
</h1>
@@ -111,33 +118,21 @@ function SvaSidebar({
items: SvaSidebarItem[];
currentSlug: string;
}) {
const frontMatterItems: SvaSidebarItem[] = [];
const chapterItems: SvaSidebarItem[] = [];
const backMatterItems: SvaSidebarItem[] = [];
let pastChapters = false;
for (const item of items) {
if (item.type === "chapter") {
pastChapters = true;
chapterItems.push(item);
} else if (!pastChapters) {
frontMatterItems.push(item);
} else {
backMatterItems.push(item);
}
}
// Split items by section
const frontMatter = items.filter((i) => i.type === "intro-segment");
const parents = items.filter((i) => i.type === "parent");
const subChapters = items.filter((i) => i.type === "sub-chapter");
return (
<aside className="hidden lg:block w-56 shrink-0 sticky top-[89px] self-start max-h-[calc(100vh-89px)] overflow-y-auto py-8 pr-4">
{/* Front Matter */}
{frontMatterItems.length > 0 && (
{frontMatter.length > 0 && (
<>
<h2 className="text-xs font-bold uppercase tracking-wider text-muted-foreground mb-3 px-2">
Front Matter
</h2>
<nav className="flex flex-col gap-0.5 mb-4">
{frontMatterItems.map((item) => (
{frontMatter.map((item) => (
<SidebarLink
key={item.slug}
item={item}
@@ -148,36 +143,45 @@ function SvaSidebar({
</>
)}
{/* Parts */}
{chapterItems.length > 0 && (
{/* Parts — grouped by parent */}
{parents.length > 0 && (
<>
<h2 className="text-xs font-bold uppercase tracking-wider text-muted-foreground mb-3 px-2">
Parts
Songs
</h2>
<nav className="flex flex-col gap-0.5 mb-4">
{chapterItems.map((item) => (
<SidebarLink
key={item.slug}
item={item}
isActive={item.slug === currentSlug}
/>
{parents.map((parent) => (
<div key={parent.slug}>
{/* Parent heading — not a link, just a bold label */}
<div className="px-2 py-1.5 text-xs font-bold text-muted-foreground mt-3 first:mt-0">
{parent.label}
</div>
{/* Sub-chapters (songs) indented */}
{subChapters
.filter((sub) => sub.parentChapter === parent.parentChapter)
.map((sub) => {
const isActive = sub.slug === currentSlug;
return (
<Link
key={sub.slug}
href={`/books/sva/${sub.slug}`}
className={cn(
"pl-4 px-2 py-1.5 rounded-md text-sm transition-colors truncate block",
isActive
? "bg-primary/10 text-primary font-medium"
: "text-muted-foreground hover:bg-muted hover:text-foreground",
)}
title={sub.label}
>
{sub.label}
</Link>
);
})}
</div>
))}
</nav>
</>
)}
{/* Back Matter */}
{backMatterItems.length > 0 && (
<nav className="flex flex-col gap-0.5">
{backMatterItems.map((item) => (
<SidebarLink
key={item.slug}
item={item}
isActive={item.slug === currentSlug}
/>
))}
</nav>
)}
</aside>
);
}
@@ -189,15 +193,6 @@ function SidebarLink({
item: SvaSidebarItem;
isActive: boolean;
}) {
let label: string;
if (item.type === "chapter" && item.chapter != null) {
// Extract just the title portion after "Part N: "
const titlePart = item.label.replace(/^Part \d+:\s*/, "");
label = `${item.chapter}. ${titlePart}`;
} else {
label = item.label;
}
return (
<Link
href={`/books/sva/${item.slug}`}
@@ -207,9 +202,9 @@ function SidebarLink({
? "bg-primary/10 text-primary font-medium"
: "text-muted-foreground hover:bg-muted hover:text-foreground",
)}
title={label}
title={item.label}
>
{label}
{item.label}
</Link>
);
}
@@ -231,26 +226,11 @@ function SvaNav({
}) {
const mobileNavGroups: MobileNavGroup[] = [];
const frontMatterItems: SvaSidebarItem[] = [];
const chapterItems: SvaSidebarItem[] = [];
const backMatterItems: SvaSidebarItem[] = [];
let pastChapters = false;
for (const item of sidebarItems) {
if (item.type === "chapter") {
pastChapters = true;
chapterItems.push(item);
} else if (!pastChapters) {
frontMatterItems.push(item);
} else {
backMatterItems.push(item);
}
}
if (frontMatterItems.length > 0) {
const frontMatter = sidebarItems.filter((i) => i.type === "intro-segment");
if (frontMatter.length > 0) {
mobileNavGroups.push({
title: "Front Matter",
items: frontMatterItems.map((i) => ({
items: frontMatter.map((i) => ({
slug: i.slug,
label: i.label,
href: `/books/sva/${i.slug}`,
@@ -259,26 +239,19 @@ function SvaNav({
});
}
const chapterItems = sidebarItems.filter(
(i) => i.type === "parent" || i.type === "sub-chapter",
);
if (chapterItems.length > 0) {
mobileNavGroups.push({
title: "Parts",
title: "Songs",
items: chapterItems.map((i) => ({
slug: i.slug,
label: i.label,
href: `/books/sva/${i.slug}`,
type: i.type,
})),
});
}
if (backMatterItems.length > 0) {
mobileNavGroups.push({
title: "Back Matter",
items: backMatterItems.map((i) => ({
slug: i.slug,
label: i.label,
href: `/books/sva/${i.slug}`,
type: i.type,
parentChapter: i.parentChapter,
isParent: i.type === "parent",
})),
});
}
@@ -301,9 +274,7 @@ function SvaNav({
>
<ChevronLeft className="size-4" />
<span className="hidden sm:inline truncate max-w-[180px]">
{prev.type === "chapter" && prev.chapter != null
? `Part ${prev.chapter}`
: prev.label}
{prev.label}
</span>
<span className="sm:hidden">Previous</span>
</Link>
@@ -321,9 +292,7 @@ function SvaNav({
)}
>
<span className="hidden sm:inline truncate max-w-[180px]">
{next.type === "chapter" && next.chapter != null
? `Part ${next.chapter}`
: next.label}
{next.label}
</span>
<span className="sm:hidden">Next</span>
<ChevronRight className="size-4" />