diff --git a/ui/src/lib/components/AudioPlayer.svelte b/ui/src/lib/components/AudioPlayer.svelte index 7cceeba..a3cc036 100644 --- a/ui/src/lib/components/AudioPlayer.svelte +++ b/ui/src/lib/components/AudioPlayer.svelte @@ -54,6 +54,7 @@ import { cn } from '$lib/utils'; import type { Voice } from '$lib/types'; import * as m from '$lib/paraglide/messages.js'; + import ChapterPickerOverlay from '$lib/components/ChapterPickerOverlay.svelte'; interface Props { slug: string; @@ -107,22 +108,10 @@ // ── Chapter picker state ───────────────────────────────────────────────── let showChapterPanel = $state(false); - let chapterSearch = $state(''); - const filteredChapters = $derived( - chapterSearch.trim() === '' - ? audioStore.chapters - : audioStore.chapters.filter((ch) => - (ch.title || `Chapter ${ch.number}`) - .toLowerCase() - .includes(chapterSearch.toLowerCase()) || - String(ch.number).includes(chapterSearch) - ) - ); function playChapter(chapterNumber: number) { audioStore.autoStartChapter = chapterNumber; showChapterPanel = false; - chapterSearch = ''; goto(`/books/${slug}/chapters/${chapterNumber}`); } @@ -293,7 +282,7 @@ // Close panels on Escape. function handleKeyDown(e: KeyboardEvent) { if (e.key === 'Escape') { - if (showChapterPanel) { showChapterPanel = false; chapterSearch = ''; } + if (showChapterPanel) { showChapterPanel = false; } else { stopSample(); showVoicePanel = false; } } } @@ -1055,7 +1044,7 @@ {#if voices.length > 0} - - -
-
- - - - -
-
- -
- {#each filteredChapters as ch (ch.number)} - - {/each} - {#if filteredChapters.length === 0} -

No chapters match "{chapterSearch}"

- {/if} -
- + { showChapterPanel = false; }} + /> {/if} +
+ +
+ + Chapters +
+ + +
+
+ + + + +
+
+ + +
+ {#each filtered as ch (ch.number)} + + {/each} + + {#if filtered.length === 0} +

No chapters match "{search}"

+ {/if} +
+
diff --git a/ui/src/lib/components/ListeningMode.svelte b/ui/src/lib/components/ListeningMode.svelte index 15b36ab..ad7f071 100644 --- a/ui/src/lib/components/ListeningMode.svelte +++ b/ui/src/lib/components/ListeningMode.svelte @@ -3,6 +3,7 @@ import { cn } from '$lib/utils'; import { goto } from '$app/navigation'; import type { Voice } from '$lib/types'; + import ChapterPickerOverlay from '$lib/components/ChapterPickerOverlay.svelte'; interface Props { /** Called when the user closes the overlay. */ @@ -92,25 +93,14 @@ const filteredCfai = $derived(cfaiVoices.filter((v) => voiceLabel(v).toLowerCase().includes(voiceSearchLower))); // ── Chapter search ──────────────────────────────────────────────────────── - let chapterSearch = $state(''); + // (search state is managed internally by ChapterPickerOverlay) - // Scroll the current chapter into view instantly (no animation) when the - // chapter modal opens. Applied to every chapter button; only scrolls when - // the chapter number matches the currently playing one. Runs once on mount - // before the browser paints so no scroll animation is ever visible. - function scrollIfActive(node: HTMLElement, isActive: boolean) { - if (isActive) node.scrollIntoView({ block: 'center', behavior: 'instant' }); + // ── Chapter click-to-play ───────────────────────────────────────────────── + function playChapter(chapterNumber: number) { + audioStore.autoStartChapter = chapterNumber; + onclose(); + goto(`/books/${audioStore.slug}/chapters/${chapterNumber}`); } - const filteredChapters = $derived( - chapterSearch.trim() === '' - ? audioStore.chapters - : audioStore.chapters.filter((ch) => - (ch.title || `Chapter ${ch.number}`) - .toLowerCase() - .includes(chapterSearch.toLowerCase()) || - String(ch.number).includes(chapterSearch) - ) - ); function voiceLabel(v: Voice | string): string { if (typeof v === 'string') { @@ -156,13 +146,6 @@ voiceSearch = ''; } - // ── Chapter click-to-play ───────────────────────────────────────────────── - function playChapter(chapterNumber: number) { - audioStore.autoStartChapter = chapterNumber; - onclose(); - goto(`/books/${audioStore.slug}/chapters/${chapterNumber}`); - } - // ── Speed ──────────────────────────────────────────────────────────────── const SPEED_OPTIONS = [0.75, 1, 1.25, 1.5, 2] as const; @@ -453,63 +436,13 @@ {#if showChapterModal && audioStore.chapters.length > 0} - -
-
- - Chapters -
-
-
- - - - -
-
-
- {#each filteredChapters as ch (ch.number)} - - {/each} - {#if filteredChapters.length === 0} -

No chapters match "{chapterSearch}"

- {/if} -
-
+ { showChapterModal = false; }} + /> {/if}