Various improvements & Epub support

This commit is contained in:
Stefan Hardegger
2025-08-08 14:09:14 +02:00
parent 090b858a54
commit 379c8c170f
37 changed files with 4069 additions and 298 deletions

View File

@@ -11,16 +11,17 @@ import TagFilter from '../../components/stories/TagFilter';
import LoadingSpinner from '../../components/ui/LoadingSpinner';
type ViewMode = 'grid' | 'list';
type SortOption = 'createdAt' | 'title' | 'authorName' | 'rating' | 'wordCount';
type SortOption = 'createdAt' | 'title' | 'authorName' | 'rating' | 'wordCount' | 'lastRead';
export default function LibraryPage() {
const [stories, setStories] = useState<Story[]>([]);
const [tags, setTags] = useState<Tag[]>([]);
const [loading, setLoading] = useState(false);
const [searchLoading, setSearchLoading] = useState(false);
const [searchQuery, setSearchQuery] = useState('');
const [selectedTags, setSelectedTags] = useState<string[]>([]);
const [viewMode, setViewMode] = useState<ViewMode>('list');
const [sortOption, setSortOption] = useState<SortOption>('createdAt');
const [sortOption, setSortOption] = useState<SortOption>('lastRead');
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('desc');
const [page, setPage] = useState(0);
const [totalPages, setTotalPages] = useState(1);
@@ -47,7 +48,13 @@ export default function LibraryPage() {
const debounceTimer = setTimeout(() => {
const performSearch = async () => {
try {
setLoading(true);
// Use searchLoading for background search, loading only for initial load
const isInitialLoad = stories.length === 0 && !searchQuery && selectedTags.length === 0;
if (isInitialLoad) {
setLoading(true);
} else {
setSearchLoading(true);
}
// Always use search API for consistency - use '*' for match-all when no query
const result = await searchApi.search({
@@ -73,11 +80,12 @@ export default function LibraryPage() {
setStories([]);
} finally {
setLoading(false);
setSearchLoading(false);
}
};
performSearch();
}, searchQuery ? 300 : 0); // Debounce search, but not other changes
}, searchQuery ? 500 : 0); // 500ms debounce for search, immediate for other changes
return () => clearTimeout(debounceTimer);
}, [searchQuery, selectedTags, page, sortOption, sortDirection, refreshTrigger]);
@@ -154,16 +162,21 @@ export default function LibraryPage() {
</p>
</div>
<Button href="/add-story">
Add New Story
</Button>
<div className="flex gap-2">
<Button href="/add-story">
Add New Story
</Button>
<Button href="/stories/import/epub" variant="secondary">
📖 Import EPUB
</Button>
</div>
</div>
{/* Search and Filters */}
<div className="space-y-4">
{/* Search Bar */}
<div className="flex flex-col sm:flex-row gap-4">
<div className="flex-1">
<div className="flex-1 relative">
<Input
type="search"
placeholder="Search by title, author, or tags..."
@@ -171,6 +184,11 @@ export default function LibraryPage() {
onChange={handleSearchChange}
className="w-full"
/>
{searchLoading && (
<div className="absolute right-3 top-1/2 transform -translate-y-1/2">
<div className="animate-spin h-4 w-4 border-2 border-theme-accent border-t-transparent rounded-full"></div>
</div>
)}
</div>
{/* View Mode Toggle */}
@@ -215,6 +233,7 @@ export default function LibraryPage() {
<option value="authorName">Author</option>
<option value="rating">Rating</option>
<option value="wordCount">Word Count</option>
<option value="lastRead">Last Read</option>
</select>
{/* Sort Direction Toggle */}