feat: add book transcripts categorisation
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
/**
|
||||
* Book + chapter derivation for transcriptions.
|
||||
*
|
||||
* Transcriptions carry a free-text scripture `reference` (e.g. "SB 1.2.3",
|
||||
* "BG Introduction", "CC Madhya-līlā 20.101") rather than structured
|
||||
* book/chapter columns. This module is the single source of truth for turning
|
||||
* those strings into a book identity and a chapter grouping, reused by the
|
||||
* books grid, the listing page, and the book browser.
|
||||
*
|
||||
* The book registry mirrors `TranscriptionBooks` in the Go BFF
|
||||
* (internal/repository/transcription_repo.go).
|
||||
*/
|
||||
|
||||
export type BookCode = "SB" | "BG" | "CC" | "BS" | "NOD" | "ISO";
|
||||
|
||||
export const BOOKS: Record<BookCode, { title: string; order: number }> = {
|
||||
SB: { title: "Śrīmad-Bhāgavatam", order: 1 },
|
||||
BG: { title: "Bhagavad-gītā", order: 2 },
|
||||
CC: { title: "Caitanya-caritāmṛta", order: 3 },
|
||||
BS: { title: "Brahma-saṁhitā", order: 4 },
|
||||
NOD: { title: "The Nectar of Devotion", order: 5 },
|
||||
ISO: { title: "Śrī Īśopaniṣad", order: 6 },
|
||||
};
|
||||
|
||||
/** Display title for a (possibly unknown) book code; "" if unknown. */
|
||||
export function bookTitle(code: string): string {
|
||||
const b = BOOKS[code.toUpperCase() as BookCode];
|
||||
return b ? b.title : "";
|
||||
}
|
||||
|
||||
/** A section is a canto (SB) or līlā (CC); null for books without that level. */
|
||||
export type ChapterSection = {
|
||||
key: string;
|
||||
label: string;
|
||||
order: number;
|
||||
};
|
||||
|
||||
/** A chapter grouping for the accordion list. */
|
||||
export type ChapterGroup = {
|
||||
key: string;
|
||||
label: string;
|
||||
sort: [number, number];
|
||||
/** Canto/līlā this chapter belongs to (SB, CC); null for flat books. */
|
||||
section: ChapterSection | null;
|
||||
};
|
||||
|
||||
// Caitanya-caritāmṛta līlā ordering (Ādi → Madhya → Antya).
|
||||
const LILA_ORDER: Record<string, { label: string; order: number }> = {
|
||||
ADI: { label: "Ādi-līlā", order: 1 },
|
||||
MADHYA: { label: "Madhya-līlā", order: 2 },
|
||||
ANTYA: { label: "Antya-līlā", order: 3 },
|
||||
};
|
||||
|
||||
const OTHER: ChapterGroup = {
|
||||
key: "other",
|
||||
label: "Other",
|
||||
sort: [9999, 0],
|
||||
section: null,
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse a transcription reference into its chapter group, given the book code.
|
||||
* Tolerates the corpus's noise (en-dash vs hyphen, "Madhya" vs "Madhya-līlā",
|
||||
* a stray "SB 7", etc.). Unparseable / missing references fall into "Other".
|
||||
*/
|
||||
export function parseChapter(code: string, reference: string | null): ChapterGroup {
|
||||
const ref = (reference ?? "").trim();
|
||||
if (!ref) return OTHER;
|
||||
|
||||
switch (code.toUpperCase() as BookCode) {
|
||||
case "BG": {
|
||||
if (/^BG\s+Introduction/i.test(ref)) {
|
||||
return {
|
||||
key: "bg-intro",
|
||||
label: "Introduction",
|
||||
sort: [0, 0],
|
||||
section: null,
|
||||
};
|
||||
}
|
||||
const m = ref.match(/^BG\s+(\d+)/);
|
||||
if (m) {
|
||||
const n = Number(m[1]);
|
||||
return {
|
||||
key: `bg-${n}`,
|
||||
label: `Chapter ${n}`,
|
||||
sort: [n, 0],
|
||||
section: null,
|
||||
};
|
||||
}
|
||||
return OTHER;
|
||||
}
|
||||
case "SB": {
|
||||
const m = ref.match(/^SB\s+(\d+)\.(\d+)/);
|
||||
if (m) {
|
||||
const c = Number(m[1]);
|
||||
const ch = Number(m[2]);
|
||||
return {
|
||||
key: `sb-${c}-${ch}`,
|
||||
label: `Chapter ${ch}`,
|
||||
sort: [c, ch],
|
||||
section: { key: `sb-${c}`, label: `Canto ${c}`, order: c },
|
||||
};
|
||||
}
|
||||
const cantoOnly = ref.match(/^SB\s+(\d+)\b/);
|
||||
if (cantoOnly) {
|
||||
const c = Number(cantoOnly[1]);
|
||||
return {
|
||||
key: `sb-${c}-0`,
|
||||
label: "Other",
|
||||
sort: [c, 0],
|
||||
section: { key: `sb-${c}`, label: `Canto ${c}`, order: c },
|
||||
};
|
||||
}
|
||||
return OTHER;
|
||||
}
|
||||
case "CC": {
|
||||
const m = ref.match(/^CC\s+(Ādi|Adi|Madhya|Antya)(?:-līlā)?\s+(\d+)/i);
|
||||
if (m) {
|
||||
const lila = LILA_ORDER[m[1].toUpperCase().replace("Ā", "A")];
|
||||
const ch = Number(m[2]);
|
||||
if (lila) {
|
||||
return {
|
||||
key: `cc-${lila.order}-${ch}`,
|
||||
label: `Chapter ${ch}`,
|
||||
sort: [lila.order, ch],
|
||||
section: {
|
||||
key: `cc-${lila.order}`,
|
||||
label: lila.label,
|
||||
order: lila.order,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
return OTHER;
|
||||
}
|
||||
case "BS": {
|
||||
const m = ref.match(/^BS\s+(\d+)/);
|
||||
if (m) {
|
||||
const n = Number(m[1]);
|
||||
return {
|
||||
key: `bs-${n}`,
|
||||
label: `Chapter ${n}`,
|
||||
sort: [n, 0],
|
||||
section: null,
|
||||
};
|
||||
}
|
||||
return OTHER;
|
||||
}
|
||||
case "ISO": {
|
||||
const m = ref.match(/^ISO\s+Mantra\s+(\d+)/i);
|
||||
if (m) {
|
||||
const n = Number(m[1]);
|
||||
return {
|
||||
key: `iso-${n}`,
|
||||
label: `Mantra ${n}`,
|
||||
sort: [n, 0],
|
||||
section: null,
|
||||
};
|
||||
}
|
||||
return { key: "iso", label: "Śrī Īśopaniṣad", sort: [0, 0], section: null };
|
||||
}
|
||||
case "NOD":
|
||||
return {
|
||||
key: "nod",
|
||||
label: "The Nectar of Devotion",
|
||||
sort: [0, 0],
|
||||
section: null,
|
||||
};
|
||||
default:
|
||||
return OTHER;
|
||||
}
|
||||
}
|
||||
@@ -59,6 +59,12 @@ export type PlaceGroup = {
|
||||
count: number;
|
||||
};
|
||||
|
||||
export type BookGroup = {
|
||||
code: string;
|
||||
title: string;
|
||||
count: number;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// BFF response types (snake_case from Go)
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -185,6 +191,16 @@ export async function getTranscriptionPlaces(): Promise<PlaceGroup[]> {
|
||||
.sort((a, b) => b.count - a.count);
|
||||
}
|
||||
|
||||
export async function getTranscriptionBooks(): Promise<BookGroup[]> {
|
||||
const raw = await bffList<{ _id: string; title: string; count: number }>(
|
||||
"/transcriptions/books",
|
||||
{ tags: ["transcriptions"] },
|
||||
);
|
||||
return raw
|
||||
.map((item) => ({ code: item._id, title: item.title, count: item.count }))
|
||||
.sort((a, b) => b.count - a.count);
|
||||
}
|
||||
|
||||
export async function getTotalTranscriptionCount(): Promise<number> {
|
||||
const res = await bff<{ count: number }>("/transcriptions", {
|
||||
tags: ["transcriptions"],
|
||||
@@ -248,6 +264,16 @@ export async function getTranscriptionsByType(
|
||||
return raw.map(mapSummary);
|
||||
}
|
||||
|
||||
export async function getTranscriptionsByBook(
|
||||
code: string,
|
||||
): Promise<TranscriptionSummary[]> {
|
||||
const raw = await bffList<BffSummary>(
|
||||
`/transcriptions/book/${encodeURIComponent(code)}`,
|
||||
{ tags: ["transcriptions"] },
|
||||
);
|
||||
return raw.map(mapSummary);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Single transcription
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user