Compare commits
67
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f855699e72 | ||
|
|
52cd48b12e | ||
|
|
0a23ab59f7 | ||
|
|
3af4e1bcd3 | ||
|
|
fed12b0d46 | ||
|
|
d64485daa4 | ||
|
|
0968b6046d | ||
|
|
8dd72de359 | ||
|
|
8537eb6ce2 | ||
|
|
09dc734613 | ||
|
|
5832125ab2 | ||
|
|
0e2e0d7300 | ||
|
|
6a6143ceb2 | ||
|
|
9f67da37a6 | ||
|
|
cce8a4c05f | ||
|
|
b391e1c575 | ||
|
|
3ce08428c8 | ||
|
|
bf8db28a7c | ||
|
|
cc9ea45c61 | ||
|
|
7a9d362b15 | ||
|
|
c001eeab45 | ||
|
|
2b0f966f90 | ||
|
|
2597c6f2ec | ||
|
|
8c7afe596d | ||
|
|
0f382ed885 | ||
|
|
03c8799f22 | ||
|
|
011b5bba91 | ||
|
|
3e6a1c6f39 | ||
|
|
76cd106ef7 | ||
|
|
028a8d7e1c | ||
|
|
290ceb048f | ||
|
|
523b6fa7e8 | ||
|
|
3257efb06a | ||
|
|
6083a485de | ||
|
|
baf36d3d67 | ||
|
|
df49e7afcc | ||
|
|
ce373e3e03 | ||
|
|
9b7758da99 | ||
|
|
6ff29db68a | ||
|
|
274c735b4d | ||
|
|
1984797ad3 | ||
|
|
1582df0ed5 | ||
|
|
1683abc2e9 | ||
|
|
12a5c15121 | ||
|
|
8bc27bb8a1 | ||
|
|
89391279bb | ||
|
|
7f716397a0 | ||
|
|
4586f1c22c | ||
|
|
b6471f9430 | ||
|
|
380c23e469 | ||
|
|
0b67a1142f | ||
|
|
be28585294 | ||
|
|
1462844e95 | ||
|
|
9916810d18 | ||
|
|
7b81bbc791 | ||
|
|
f92b41384e | ||
|
|
5cd75c27f9 | ||
|
|
5679f7a639 | ||
|
|
25db9d0eee | ||
|
|
0178f89f2b | ||
|
|
b6dc762984 | ||
|
|
1fe10adb1e | ||
|
|
84cb273941 | ||
|
|
071eb4d58a | ||
|
|
bc1798b31b | ||
|
|
fc2aef0c3d | ||
|
|
89f94c1a6e |
+1
-1
@@ -4,7 +4,7 @@ import { PageHeader } from "@/components/layout/page-header";
|
|||||||
import { FindInPage } from "@/components/layout/find-in-page";
|
import { FindInPage } from "@/components/layout/find-in-page";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
import { getBookMeta, getSingleVerse, getChapterVerseIndex } from "@/lib/api";
|
import { getBookMeta, getSingleVerse, getChapterVerseIndex } from "@/lib/api";
|
||||||
import { ChapterViewer } from "@/app/(app)/books/chapter-viewer";
|
import { ChapterViewer } from "@/app/(app)/(reader)/books/chapter-viewer";
|
||||||
import type { MobileNavGroup } from "@/components/layout/mobile-nav";
|
import type { MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||||
import { ogForVerse, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
import { ogForVerse, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||||
|
|
||||||
@@ -72,11 +72,16 @@ export function BooksGrid({ books }: { books: BookInfo[] }) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const filtered = useMemo(() => {
|
const filtered = useMemo(() => {
|
||||||
if (!query.trim()) return books;
|
const base = query.trim()
|
||||||
const q = normalize(query.trim());
|
? normalizedBooks
|
||||||
return normalizedBooks
|
.filter((entry) =>
|
||||||
.filter((entry) => entry.searchText.includes(q))
|
entry.searchText.includes(normalize(query.trim())),
|
||||||
.map((entry) => entry.book);
|
)
|
||||||
|
.map((entry) => entry.book)
|
||||||
|
: books;
|
||||||
|
return [...base].sort((a, b) =>
|
||||||
|
normalize(a.title).localeCompare(normalize(b.title)),
|
||||||
|
);
|
||||||
}, [books, normalizedBooks, query]);
|
}, [books, normalizedBooks, query]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
+1
-1
@@ -12,7 +12,7 @@ import {
|
|||||||
type VerseIndexEntry,
|
type VerseIndexEntry,
|
||||||
type VerseRecord,
|
type VerseRecord,
|
||||||
} from "@/lib/api";
|
} from "@/lib/api";
|
||||||
import { ChapterViewer } from "@/app/(app)/books/chapter-viewer";
|
import { ChapterViewer } from "@/app/(app)/(reader)/books/chapter-viewer";
|
||||||
import type { MobileNavGroup } from "@/components/layout/mobile-nav";
|
import type { MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||||
import { ogForVerse, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
import { ogForVerse, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||||
|
|
||||||
+1
-1
@@ -6,7 +6,7 @@ import { PageHeader } from "@/components/layout/page-header";
|
|||||||
import { FindInPage } from "@/components/layout/find-in-page";
|
import { FindInPage } from "@/components/layout/find-in-page";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
import { getBookMeta, getSingleVerse, getChapterVerseIndex } from "@/lib/api";
|
import { getBookMeta, getSingleVerse, getChapterVerseIndex } from "@/lib/api";
|
||||||
import { ChapterViewer } from "@/app/(app)/books/chapter-viewer";
|
import { ChapterViewer } from "@/app/(app)/(reader)/books/chapter-viewer";
|
||||||
import type { MobileNavGroup } from "@/components/layout/mobile-nav";
|
import type { MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||||
import { ogForVerse, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
import { ogForVerse, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||||
|
|
||||||
@@ -219,9 +219,17 @@ function VerseCard({
|
|||||||
Purport
|
Purport
|
||||||
</h3>
|
</h3>
|
||||||
<div className="text-base text-foreground leading-relaxed space-y-3">
|
<div className="text-base text-foreground leading-relaxed space-y-3">
|
||||||
{verse.purport.map((p, i) => (
|
{verse.purport.map((p, i) =>
|
||||||
<p key={i} dangerouslySetInnerHTML={{ __html: p }} />
|
// Cited ślokas come wrapped in <blockquote class="shloka"> — a
|
||||||
))}
|
// block element that can't live inside a <p>, so render those
|
||||||
|
// in a <div> (styled centered + accent via globals.css); plain
|
||||||
|
// prose paragraphs stay as <p>.
|
||||||
|
/^\s*<blockquote/i.test(p) ? (
|
||||||
|
<div key={i} dangerouslySetInnerHTML={{ __html: p }} />
|
||||||
|
) : (
|
||||||
|
<p key={i} dangerouslySetInnerHTML={{ __html: p }} />
|
||||||
|
),
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
+1
-1
@@ -11,7 +11,7 @@ import {
|
|||||||
type VerseIndexEntry,
|
type VerseIndexEntry,
|
||||||
type VerseRecord,
|
type VerseRecord,
|
||||||
} from "@/lib/api";
|
} from "@/lib/api";
|
||||||
import { ChapterViewer } from "@/app/(app)/books/chapter-viewer";
|
import { ChapterViewer } from "@/app/(app)/(reader)/books/chapter-viewer";
|
||||||
import type { MobileNavGroup } from "@/components/layout/mobile-nav";
|
import type { MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { ogForVerse, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
import { ogForVerse, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||||
+1
-1
@@ -15,7 +15,7 @@ import {
|
|||||||
type VerseRecord,
|
type VerseRecord,
|
||||||
} from "@/lib/api";
|
} from "@/lib/api";
|
||||||
import { ogForVerse, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
import { ogForVerse, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||||
import { ChapterViewer } from "@/app/(app)/books/chapter-viewer";
|
import { ChapterViewer } from "@/app/(app)/(reader)/books/chapter-viewer";
|
||||||
import type { MobileNavGroup } from "@/components/layout/mobile-nav";
|
import type { MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||||
|
|
||||||
type NbsMeta = {
|
type NbsMeta = {
|
||||||
+1
-1
@@ -6,7 +6,7 @@ import { PageHeader } from "@/components/layout/page-header";
|
|||||||
import { FindInPage } from "@/components/layout/find-in-page";
|
import { FindInPage } from "@/components/layout/find-in-page";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
import { getBookMeta, getSingleVerse, getChapterVerseIndex } from "@/lib/api";
|
import { getBookMeta, getSingleVerse, getChapterVerseIndex } from "@/lib/api";
|
||||||
import { ChapterViewer } from "@/app/(app)/books/chapter-viewer";
|
import { ChapterViewer } from "@/app/(app)/(reader)/books/chapter-viewer";
|
||||||
import type { MobileNavGroup } from "@/components/layout/mobile-nav";
|
import type { MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||||
import { ogForVerse, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
import { ogForVerse, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||||
|
|
||||||
+1
-1
@@ -4,7 +4,7 @@ import { PageHeader } from "@/components/layout/page-header";
|
|||||||
import { FindInPage } from "@/components/layout/find-in-page";
|
import { FindInPage } from "@/components/layout/find-in-page";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
import { getBookMeta, getSingleVerse, getChapterVerseIndex } from "@/lib/api";
|
import { getBookMeta, getSingleVerse, getChapterVerseIndex } from "@/lib/api";
|
||||||
import { ChapterViewer } from "@/app/(app)/books/chapter-viewer";
|
import { ChapterViewer } from "@/app/(app)/(reader)/books/chapter-viewer";
|
||||||
import type { MobileNavGroup } from "@/components/layout/mobile-nav";
|
import type { MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||||
import { ogForVerse, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
import { ogForVerse, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||||
|
|
||||||
+118
-35
@@ -5,6 +5,7 @@ import Link from "next/link";
|
|||||||
import { Card, CardContent } from "@/components/ui/card";
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { UserAvatar } from "@/components/ui/user-avatar";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import {
|
import {
|
||||||
Search,
|
Search,
|
||||||
@@ -32,6 +33,14 @@ export type DiscipleSummary = {
|
|||||||
isVerified: boolean;
|
isVerified: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** A disciple who has a platform account (linked by an admin). */
|
||||||
|
export type DiscipleLink = {
|
||||||
|
discipleId: string;
|
||||||
|
handle: string;
|
||||||
|
name: string;
|
||||||
|
avatarURL: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
export type LetterCount = { letter: string; count: number };
|
export type LetterCount = { letter: string; count: number };
|
||||||
export type PlaceGroup = { name: string; count: number };
|
export type PlaceGroup = { name: string; count: number };
|
||||||
export type YearGroup = { year: number; count: number };
|
export type YearGroup = { year: number; count: number };
|
||||||
@@ -49,6 +58,9 @@ type Props = {
|
|||||||
years: YearGroup[];
|
years: YearGroup[];
|
||||||
totalCount: number;
|
totalCount: number;
|
||||||
filteredCount: number;
|
filteredCount: number;
|
||||||
|
/** Disciples who have joined the platform — their cards are highlighted and
|
||||||
|
* link through to the profile. Small set (dozens at most). */
|
||||||
|
links: DiscipleLink[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type SortMode = "name-asc" | "name-desc" | "date-asc" | "date-desc" | "place";
|
type SortMode = "name-asc" | "name-desc" | "date-asc" | "date-desc" | "place";
|
||||||
@@ -306,6 +318,97 @@ function Pagination({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Disciple card
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One disciple in the grid.
|
||||||
|
*
|
||||||
|
* When `link` is set the disciple has joined the platform, and the card is
|
||||||
|
* highlighted and becomes a link to their profile. The highlight is drawn
|
||||||
|
* entirely from theme tokens (`--primary`, `--ring`) rather than fixed colours,
|
||||||
|
* so it picks up whichever palette the reader has chosen — peacock green under
|
||||||
|
* Tulasī Garden, deep indigo under Yamunā Mist, and so on.
|
||||||
|
*/
|
||||||
|
function DiscipleCard({
|
||||||
|
disciple,
|
||||||
|
link,
|
||||||
|
}: {
|
||||||
|
disciple: DiscipleSummary;
|
||||||
|
link?: DiscipleLink;
|
||||||
|
}) {
|
||||||
|
const locationYear = formatLocationYear(
|
||||||
|
disciple.initiationPlace,
|
||||||
|
disciple.initiationDate,
|
||||||
|
);
|
||||||
|
|
||||||
|
const card = (
|
||||||
|
<Card
|
||||||
|
size="sm"
|
||||||
|
className={cn(
|
||||||
|
"h-full transition-all",
|
||||||
|
link
|
||||||
|
? // On the platform: tinted surface + primary ring so it reads as
|
||||||
|
// "one of us" at a glance across the grid.
|
||||||
|
"bg-primary/[0.06] ring-2 ring-primary/40 hover:ring-primary/70 hover:shadow-md"
|
||||||
|
: "hover:shadow-md hover:ring-foreground/20",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<CardContent className="py-3 px-4 space-y-1.5">
|
||||||
|
<div className="flex items-start justify-between gap-2">
|
||||||
|
<p
|
||||||
|
className={cn(
|
||||||
|
"font-semibold leading-snug truncate",
|
||||||
|
link ? "text-primary" : "text-foreground",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{disciple.initiatedName}
|
||||||
|
</p>
|
||||||
|
{disciple.suffix && (
|
||||||
|
<Badge
|
||||||
|
variant={link ? "default" : "secondary"}
|
||||||
|
className="shrink-0 text-[10px]"
|
||||||
|
>
|
||||||
|
{disciple.suffix}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{locationYear && (
|
||||||
|
<p className="text-xs text-muted-foreground truncate">
|
||||||
|
{locationYear}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{link && (
|
||||||
|
<div className="flex items-center gap-1.5 pt-0.5">
|
||||||
|
<UserAvatar
|
||||||
|
src={link.avatarURL}
|
||||||
|
name={link.name}
|
||||||
|
className="size-5"
|
||||||
|
textClassName="text-[9px]"
|
||||||
|
/>
|
||||||
|
<span className="truncate text-xs font-medium text-primary">
|
||||||
|
@{link.handle}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!link) return card;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
href={`/u/${link.handle}`}
|
||||||
|
aria-label={`${disciple.initiatedName} — view @${link.handle}'s profile`}
|
||||||
|
className="block rounded-xl outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
||||||
|
>
|
||||||
|
{card}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Component
|
// Component
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -318,6 +421,7 @@ export function DisciplesBrowser({
|
|||||||
suffixCounts,
|
suffixCounts,
|
||||||
totalCount,
|
totalCount,
|
||||||
filteredCount,
|
filteredCount,
|
||||||
|
links,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const [query, setQuery] = useState("");
|
const [query, setQuery] = useState("");
|
||||||
const [sort, setSort] = useState<SortMode>("name-asc");
|
const [sort, setSort] = useState<SortMode>("name-asc");
|
||||||
@@ -334,6 +438,13 @@ export function DisciplesBrowser({
|
|||||||
setPage(1);
|
setPage(1);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Disciple id → their platform account, for the highlight + profile link.
|
||||||
|
const linkByDiscipleId = useMemo(() => {
|
||||||
|
const map = new Map<string, DiscipleLink>();
|
||||||
|
for (const l of links) map.set(l.discipleId, l);
|
||||||
|
return map;
|
||||||
|
}, [links]);
|
||||||
|
|
||||||
// Build a lookup map for letter counts
|
// Build a lookup map for letter counts
|
||||||
const countByLetter = useMemo(() => {
|
const countByLetter = useMemo(() => {
|
||||||
const map = new Map<string, number>();
|
const map = new Map<string, number>();
|
||||||
@@ -539,41 +650,13 @@ export function DisciplesBrowser({
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3">
|
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3">
|
||||||
{pageData.map((disciple) => {
|
{pageData.map((disciple) => (
|
||||||
const locationYear = formatLocationYear(
|
<DiscipleCard
|
||||||
disciple.initiationPlace,
|
key={disciple.id}
|
||||||
disciple.initiationDate,
|
disciple={disciple}
|
||||||
);
|
link={linkByDiscipleId.get(disciple.id)}
|
||||||
|
/>
|
||||||
return (
|
))}
|
||||||
<Card
|
|
||||||
key={disciple.id}
|
|
||||||
size="sm"
|
|
||||||
className="transition-all hover:shadow-md hover:ring-foreground/20"
|
|
||||||
>
|
|
||||||
<CardContent className="py-3 px-4 space-y-1.5">
|
|
||||||
<div className="flex items-start justify-between gap-2">
|
|
||||||
<p className="font-semibold text-foreground leading-snug truncate">
|
|
||||||
{disciple.initiatedName}
|
|
||||||
</p>
|
|
||||||
{disciple.suffix && (
|
|
||||||
<Badge
|
|
||||||
variant="secondary"
|
|
||||||
className="shrink-0 text-[10px]"
|
|
||||||
>
|
|
||||||
{disciple.suffix}
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{locationYear && (
|
|
||||||
<p className="text-xs text-muted-foreground truncate">
|
|
||||||
{locationYear}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
getDiscipleYears,
|
getDiscipleYears,
|
||||||
getDiscipleSuffixCounts,
|
getDiscipleSuffixCounts,
|
||||||
getFilteredDiscipleCount,
|
getFilteredDiscipleCount,
|
||||||
|
getDiscipleLinks,
|
||||||
} from "@/lib/disciples";
|
} from "@/lib/disciples";
|
||||||
import type { SuffixFilter } from "@/lib/disciples";
|
import type { SuffixFilter } from "@/lib/disciples";
|
||||||
import { DisciplesBrowser } from "./disciples-browser";
|
import { DisciplesBrowser } from "./disciples-browser";
|
||||||
@@ -62,6 +63,7 @@ export default async function DisciplesPage({
|
|||||||
places,
|
places,
|
||||||
years,
|
years,
|
||||||
suffixCounts,
|
suffixCounts,
|
||||||
|
links,
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
getTotalDiscipleCount(),
|
getTotalDiscipleCount(),
|
||||||
getFilteredDisciples(letterFilter, activeSuffix),
|
getFilteredDisciples(letterFilter, activeSuffix),
|
||||||
@@ -70,6 +72,7 @@ export default async function DisciplesPage({
|
|||||||
getDisciplePlaces(),
|
getDisciplePlaces(),
|
||||||
getDiscipleYears(),
|
getDiscipleYears(),
|
||||||
getDiscipleSuffixCounts(),
|
getDiscipleSuffixCounts(),
|
||||||
|
getDiscipleLinks(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Serialize dates to ISO strings for the client component
|
// Serialize dates to ISO strings for the client component
|
||||||
@@ -105,6 +108,7 @@ export default async function DisciplesPage({
|
|||||||
years={years}
|
years={years}
|
||||||
totalCount={totalCount}
|
totalCount={totalCount}
|
||||||
filteredCount={filteredCount}
|
filteredCount={filteredCount}
|
||||||
|
links={links}
|
||||||
/>
|
/>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { Suspense } from "react";
|
||||||
|
import { TextSelectionToolbar } from "@/components/text-selection-toolbar";
|
||||||
|
import { TextHighlighter } from "@/components/text-highlighter";
|
||||||
|
import { PageBookmarks } from "@/components/social/page-bookmarks";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reader layout — the annotation surface for the actual scripture library
|
||||||
|
* (books, letters, lectures, essays, glossary, journals, magazines, other
|
||||||
|
* documents, disciples).
|
||||||
|
*
|
||||||
|
* The text-selection toolbar and its two companions (the `?hl=` highlighter
|
||||||
|
* and the persistent in-text bookmark marks) only make sense over long-form
|
||||||
|
* scripture prose. Mounting them in this nested route group — rather than the
|
||||||
|
* parent `(app)` layout — keeps them off the home feed, search, profiles,
|
||||||
|
* settings, downloads, Kautilya, etc., so a stray text selection on chrome
|
||||||
|
* text no longer pops the toolbar.
|
||||||
|
*
|
||||||
|
* This is a route group: it adds NO path segment, so every URL underneath is
|
||||||
|
* unchanged (`/books/...`, `/letters/...`, …). All the outer `(app)` chrome
|
||||||
|
* (header, sidebar, footer, bottom nav) still wraps these pages.
|
||||||
|
*/
|
||||||
|
export default function ReaderLayout({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{children}
|
||||||
|
<TextSelectionToolbar />
|
||||||
|
<Suspense fallback={null}>
|
||||||
|
<TextHighlighter />
|
||||||
|
</Suspense>
|
||||||
|
<PageBookmarks />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user