From 57d91ebea127b58c3b492ea573803b2bfb566d5a Mon Sep 17 00:00:00 2001 From: neon <18559658+neonsolstice@users.noreply.github.com> Date: Sun, 10 May 2026 23:14:11 +0000 Subject: [PATCH] feat(cover,metadata): ffprobe extraction, cover placeholders, grid refresh, and audiobook search - Replace music-metadata and mediainfo with @ffprobe-installer for all audio metadata extraction; remove mediainfo dependency - Show BookCoverPlaceholder in all missing-cover locations: Similar/Series/Author Books carousel, cover editor panel, metadata search result cards, and metadata diff panel - Extend BookRecommendation and SeriesBookRecommendation shared types with hasCover and authors; update recommendation and book repositories to supply them via parallel queries - Fix cover refresh not updating in grid: BookCoverCard now merges refreshed BookDetail into the local BookCard and emits update:book; VirtualBookGrid forwards the event; Home, Series, Author, and Collection views wire the handler so covers update without a page reload - Add audiobook covers checkbox to the Online Search drawer so users can toggle between square and portrait cover searches; result thumbnails and loading skeletons adapt their aspect ratio to match the selection --- .../author/views/AuthorDetailView.vue | 6 + .../book/components/BookCoverCard.vue | 9 +- .../book/components/VirtualBookGrid.vue | 2 + .../components/detail/BookCarousel.test.ts | 150 +++++ .../book/components/detail/BookCarousel.vue | 11 + .../detail/tabs/CoverEditorPanel.vue | 23 +- .../detail/tabs/CoverSearchDrawer.test.ts | 346 +++++++++++ .../detail/tabs/CoverSearchDrawer.vue | 37 +- .../detail/tabs/MetadataDiffPanel.vue | 9 + .../detail/tabs/MetadataDiffRow.vue | 18 +- .../detail/tabs/MetadataResultCard.vue | 8 +- .../reader/audiobook/AudiobookReaderView.vue | 19 +- .../series/views/SeriesDetailView.vue | 1 + client/src/views/HomeView.vue | 1 + packages/types/src/book.ts | 4 + pnpm-lock.yaml | 321 ++++++---- server/package.json | 4 +- .../src/modules/book/book.repository.test.ts | 45 +- server/src/modules/book/book.repository.ts | 34 +- .../extractors/audio.extractor.test.ts | 575 +++++++++++------- .../metadata/extractors/audio.extractor.ts | 210 ++++--- .../recommendation.repository.test.ts | 52 +- .../recommendation.repository.ts | 40 +- .../recommendation.service.test.ts | 48 +- .../recommendation/recommendation.service.ts | 12 +- 25 files changed, 1491 insertions(+), 494 deletions(-) create mode 100644 client/src/features/book/components/detail/BookCarousel.test.ts create mode 100644 client/src/features/book/components/detail/tabs/CoverSearchDrawer.test.ts diff --git a/client/src/features/author/views/AuthorDetailView.vue b/client/src/features/author/views/AuthorDetailView.vue index 041965e6d..d23ff0e6f 100644 --- a/client/src/features/author/views/AuthorDetailView.vue +++ b/client/src/features/author/views/AuthorDetailView.vue @@ -129,6 +129,11 @@ function handleBookAction(book: BookCard, action: 'quick-view' | 'edit-metadata' } } +function handleBookUpdate(updated: BookCard) { + const idx = books.value.findIndex((b) => b.id === updated.id) + if (idx !== -1) books.value = books.value.map((b, i) => (i === idx ? updated : b)) +} + function loadIfSentinelVisible() { if (loadingBooks.value || !hasMore.value || !sentinel.value) return if (sentinel.value.getBoundingClientRect().top < window.innerHeight + 250) { @@ -546,6 +551,7 @@ watch(authorName, () => { :cover-size="authorBookCoverSize" :grid-gap="authorBookGridGap" @action="handleBookAction" + @update:book="handleBookUpdate" />