fix: floating bookmarks

- update floating bookmarks
- fix search suggestions
- fix filters
This commit is contained in:
2026-06-03 22:33:24 -05:00
parent 054e7689bd
commit d9c66e68a6
11 changed files with 296 additions and 75 deletions
+12 -3
View File
@@ -4,9 +4,15 @@ import { booksApi } from "../api/books";
import { getApiErrorMessage } from "../api/errors";
import { useToast } from "../hooks/useToast";
import type { BookListItem } from "../types/book";
import type { SupportedLanguage } from "../locales";
import { subjectsFromMetadata } from "../utils/ebookLibrary";
import styles from "./BookContextMenu.module.css";
type LibraryBook = BookListItem & { format: string; progressPercent: number | null };
type LibraryBook = BookListItem & {
format: string;
progressPercent: number | null;
subjects: string[];
};
interface BookContextMenuProps {
book: LibraryBook;
@@ -37,7 +43,8 @@ export function BookContextMenu({
onBookUpdated,
onBookRemoved,
}: BookContextMenuProps) {
const { t } = useTranslation();
const { t, language } = useTranslation();
const locale = language as SupportedLanguage;
const { showToast } = useToast();
const menuRef = useRef<HTMLDivElement>(null);
const [position, setPosition] = useState({ left: x, top: y });
@@ -75,12 +82,14 @@ export function BookContextMenu({
setActiveAction("sync");
try {
const updated = await booksApi.enrichEBookMetadata(book.id);
const subjects = subjectsFromMetadata(updated.metadata, locale);
onBookUpdated({
...book,
id: updated.id,
title: updated.title,
author: updated.author,
genre: updated.format ? updated.format.toUpperCase() : "",
subjects,
genre: subjects[0] ?? "",
format: updated.format,
cover_image: updated.cover_image,
progressPercent: book.progressPercent,
@@ -3,20 +3,29 @@ import { useNavigate } from "react-router-dom";
import { useTranslation } from "react-i18n-lite";
import { booksApi } from "../../api/books";
import { useDebounce } from "../../hooks/useDebounce";
import type { BookListItem } from "../../types/book";
import { ebookMatchesQuery } from "../../utils/ebookLibrary";
import styles from "./SearchSuggestions.module.css";
interface SuggestionItem {
id: number;
title: string;
author: string;
cover_image: string | null;
format: string;
}
interface SearchSuggestionsProps {
query: string;
visible: boolean;
onClose: () => void;
onSelectSuggestion: () => void;
onPdfBook?: () => void;
}
export function SearchSuggestions({ query, visible, onClose, onSelectSuggestion }: SearchSuggestionsProps) {
export function SearchSuggestions({ query, visible, onClose, onSelectSuggestion, onPdfBook }: SearchSuggestionsProps) {
const { t } = useTranslation();
const navigate = useNavigate();
const [suggestions, setSuggestions] = useState<BookListItem[]>([]);
const [suggestions, setSuggestions] = useState<SuggestionItem[]>([]);
const [loading, setLoading] = useState(false);
const containerRef = useRef<HTMLDivElement>(null);
const debouncedQuery = useDebounce(query, 200);
@@ -28,12 +37,21 @@ export function SearchSuggestions({ query, visible, onClose, onSelectSuggestion
}
let cancelled = false;
setLoading(true);
void booksApi.searchBooks({ q: debouncedQuery.trim(), page_size: 5 }).then(
(res) => {
if (!cancelled) {
setSuggestions(res.results);
setLoading(false);
}
void booksApi.getEBooks().then(
(ebooks) => {
if (cancelled) return;
const items = ebooks
.filter((e) => ebookMatchesQuery({ title: e.title, author: e.author, subjects: e.subjects ?? [] }, debouncedQuery))
.slice(0, 5)
.map((e) => ({
id: e.id,
title: e.title,
author: e.author,
cover_image: e.cover_image,
format: e.format,
}));
setSuggestions(items);
setLoading(false);
},
() => {
if (!cancelled) {
@@ -72,6 +90,15 @@ export function SearchSuggestions({ query, visible, onClose, onSelectSuggestion
if (!visible || !query.trim()) return null;
const handleSelect = (book: SuggestionItem) => {
onSelectSuggestion();
if (book.format === "pdf") {
onPdfBook?.();
return;
}
navigate(`/read/${book.id}`);
};
return (
<div ref={containerRef} className={styles.container}>
{loading && (
@@ -88,10 +115,7 @@ export function SearchSuggestions({ query, visible, onClose, onSelectSuggestion
<div
key={book.id}
className={styles.suggestionItem}
onClick={() => {
onSelectSuggestion();
navigate(`/books/${book.id}`);
}}
onClick={() => handleSelect(book)}
>
<span className={styles.coverPlaceholder}>
{book.cover_image ? (