Archived
feat: uv config other feats
- add uv configuration for the backend - update frontend to make auth work - add new auth endpoints - add bookmars feat - add reader feat
This commit is contained in:
@@ -1,53 +1,83 @@
|
||||
/**
|
||||
* TableOfContents — slide-in drawer listing all chapters.
|
||||
* Tap a chapter to navigate. Current chapter is highlighted.
|
||||
* TableOfContents — slide-in drawer for epub.js navigation tree.
|
||||
*/
|
||||
|
||||
import type { ChapterSummary } from "../../types/reader";
|
||||
import type { KeyboardEvent } from "react";
|
||||
import { useTranslation } from "react-i18n-lite";
|
||||
|
||||
export interface EpubTocItem {
|
||||
label: string;
|
||||
href: string;
|
||||
subitems?: EpubTocItem[];
|
||||
}
|
||||
|
||||
interface TableOfContentsProps {
|
||||
chapters: ChapterSummary[];
|
||||
currentChapterNumber: number;
|
||||
items: EpubTocItem[];
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onNavigate: (number: number) => void;
|
||||
onNavigate: (href: string) => void;
|
||||
}
|
||||
|
||||
function TocEntry({
|
||||
item,
|
||||
depth,
|
||||
onNavigate,
|
||||
}: {
|
||||
item: EpubTocItem;
|
||||
depth: number;
|
||||
onNavigate: (href: string) => void;
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
className="toc-item"
|
||||
style={{ paddingLeft: `${16 + depth * 14}px` }}
|
||||
onClick={() => onNavigate(item.href)}
|
||||
>
|
||||
<span className="toc-item-title">{item.label}</span>
|
||||
</button>
|
||||
{item.subitems?.map((sub) => (
|
||||
<TocEntry key={sub.href} item={sub} depth={depth + 1} onNavigate={onNavigate} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default function TableOfContents({
|
||||
chapters,
|
||||
currentChapterNumber,
|
||||
items,
|
||||
isOpen,
|
||||
onClose,
|
||||
onNavigate,
|
||||
}: TableOfContentsProps) {
|
||||
const handleChapterClick = (number: number) => {
|
||||
onNavigate(number);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleNavigate = (href: string) => {
|
||||
onNavigate(href);
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Overlay */}
|
||||
{isOpen && (
|
||||
<div
|
||||
className="toc-overlay"
|
||||
onClick={onClose}
|
||||
onKeyDown={(e: React.KeyboardEvent) => {
|
||||
onKeyDown={(e: KeyboardEvent) => {
|
||||
if (e.key === "Escape") onClose();
|
||||
}}
|
||||
role="presentation"
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Drawer */}
|
||||
<aside className={`toc-drawer ${isOpen ? "toc-drawer--open" : ""}`}>
|
||||
<div className="toc-header">
|
||||
<h2 className="toc-title">Contents</h2>
|
||||
<h2 className="toc-title">{t("reader.tocTitle")}</h2>
|
||||
<button
|
||||
type="button"
|
||||
className="toc-close-btn"
|
||||
onClick={onClose}
|
||||
aria-label="Close table of contents"
|
||||
aria-label={t("reader.closeTocAria")}
|
||||
>
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<line x1="18" y1="6" x2="6" y2="18" />
|
||||
@@ -57,24 +87,15 @@ export default function TableOfContents({
|
||||
</div>
|
||||
|
||||
<nav className="toc-list">
|
||||
{chapters.length === 0 && (
|
||||
<p className="toc-empty">No chapters available.</p>
|
||||
{items.length === 0 ? (
|
||||
<p className="toc-empty">{t("reader.tocEmpty")}</p>
|
||||
) : (
|
||||
items.map((item) => (
|
||||
<TocEntry key={item.href} item={item} depth={0} onNavigate={handleNavigate} />
|
||||
))
|
||||
)}
|
||||
{chapters.map((chapter) => (
|
||||
<button
|
||||
key={chapter.number}
|
||||
type="button"
|
||||
className={`toc-item ${
|
||||
chapter.number === currentChapterNumber ? "toc-item--active" : ""
|
||||
}`}
|
||||
onClick={() => handleChapterClick(chapter.number)}
|
||||
>
|
||||
<span className="toc-item-number">{chapter.number}</span>
|
||||
<span className="toc-item-title">{chapter.title}</span>
|
||||
</button>
|
||||
))}
|
||||
</nav>
|
||||
</aside>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user