This repository has been archived on 2026-07-21. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
cloud-reader/frontend/src/components/annotations/MarkerThreadsView.tsx
T
2026-06-04 06:42:27 -05:00

109 lines
3.9 KiB
TypeScript

import { useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18n-lite";
import { useAnnotations } from "@/context/AnnotationsContext";
import type { MarkerEntry } from "@/types";
import {
CollapsibleMarkerPassage,
CollapsibleMarkerThought,
} from "@/components/annotations/CollapsibleMarkerText";
import { MarkerPassageActions } from "@/components/annotations/MarkerPassageActions";
interface MarkerThreadsViewProps {
ebookIdFilter?: string;
onGoToPassage: (ebookId: number, epubCfi: string) => void;
}
export function MarkerThreadsView({
ebookIdFilter,
onGoToPassage,
}: MarkerThreadsViewProps) {
const { t } = useTranslation();
const { markersByBook, loadBookmarks, removeBookmark, state } = useAnnotations();
const [collapsed, setCollapsed] = useState<Record<number, boolean>>({});
useEffect(() => {
void loadBookmarks(ebookIdFilter);
}, [loadBookmarks, ebookIdFilter]);
const groups = useMemo(() => {
if (!ebookIdFilter) return markersByBook;
const id = Number(ebookIdFilter);
return markersByBook.filter((g) => g.ebookId === id);
}, [markersByBook, ebookIdFilter]);
if (state.bookmarksLoading) {
return <div className="annotations-loading">{t("annotations.loading")}</div>;
}
if (groups.length === 0) {
return (
<div className="annotations-empty">
<p>{t("annotations.empty")}</p>
<p style={{ fontSize: 14, color: "#6b7280", marginTop: 8 }}>{t("annotations.selectTextHint")}</p>
</div>
);
}
const toggleBook = (ebookId: number) => {
setCollapsed((prev) => ({ ...prev, [ebookId]: !prev[ebookId] }));
};
return (
<div className="marker-books-list">
{groups.map((group) => {
const isCollapsed = collapsed[group.ebookId] ?? false;
return (
<section key={group.ebookId} className="marker-book-group">
<button
type="button"
className="marker-book-header"
onClick={() => toggleBook(group.ebookId)}
>
<span className="marker-book-title">{group.ebookTitle}</span>
<span className="marker-book-count">
{t("annotations.markerCount", { count: String(group.markers.length) })}
</span>
<span className="marker-book-chevron">{isCollapsed ? "▸" : "▾"}</span>
</button>
{!isCollapsed && (
<ul className="marker-thread-list">
{group.markers.map((m: MarkerEntry) => (
<li key={m.id} className="marker-thread">
<div className="marker-thread-meta">
{m.chapter_title ? (
<span className="marker-thread-chapter">{m.chapter_title}</span>
) : (
<span className="marker-thread-chapter">
{t("annotations.chapterIndex", { index: String(m.chapter_index + 1) })}
</span>
)}
</div>
{m.location_text && (
<CollapsibleMarkerPassage
text={m.location_text}
highlightColor={m.highlight_color}
/>
)}
{m.content ? (
<CollapsibleMarkerThought
text={m.content}
className="marker-thread-thought marker-thread-reply"
/>
) : (
<span className="annotation-kind-badge bookmark-badge">{t("annotations.bookmarkOnly")}</span>
)}
<MarkerPassageActions
onGoToPassage={() => onGoToPassage(m.ebook_id, m.epub_cfi)}
onDelete={() => removeBookmark(m.id)}
/>
</li>
))}
</ul>
)}
</section>
);
})}
</div>
);
}