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:
@@ -0,0 +1,119 @@
|
||||
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";
|
||||
|
||||
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>
|
||||
)}
|
||||
<div className="annotation-actions">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-sm"
|
||||
onClick={() => onGoToPassage(m.ebook_id, m.epub_cfi)}
|
||||
>
|
||||
{t("annotations.goToPassage")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-sm btn-danger"
|
||||
onClick={() => removeBookmark(m.id)}
|
||||
>
|
||||
{t("common.delete")}
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user