Archived
fix: floating bookmarks
- update floating bookmarks - fix search suggestions - fix filters
This commit is contained in:
@@ -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 ? (
|
||||
|
||||
Reference in New Issue
Block a user