From 922afa2763500f91c0848178b71867f57f4a8e9b Mon Sep 17 00:00:00 2001 From: Yvv Date: Thu, 26 Feb 2026 21:06:11 +0100 Subject: [PATCH] BookPlayer affiche les paroles du morceau, plus le contenu chapitre MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Le BookPlayer chargeait les .md via Nuxt Content — qui contenaient avant les paroles par erreur. Maintenant que les .md ont le vrai contenu du livre, le BookPlayer doit afficher les lyrics depuis bookplayer.config.yml. Supprime queryCollection('book') du BookPlayer, remplace ContentRenderer par un rendu HTML des paroles avec tags stylisés. Co-Authored-By: Claude Opus 4.6 --- app/components/book/BookPlayer.vue | 77 ++++++++++++++++++++++-------- 1 file changed, 56 insertions(+), 21 deletions(-) diff --git a/app/components/book/BookPlayer.vue b/app/components/book/BookPlayer.vue index 29a90dd..f652877 100644 --- a/app/components/book/BookPlayer.vue +++ b/app/components/book/BookPlayer.vue @@ -87,7 +87,13 @@ ref="contentEl" :style="contentStyle" > - +
+
+

Paroles à venir pour « {{ chapterSong.title }} »

+
+
+

Aucun morceau associé à ce chapitre

+
@@ -194,27 +200,14 @@ const { init: initBookData, getSongs, getPrimarySong, getChapterForSong, getPlay const audioPlayer = useAudioPlayer() const playerStore = usePlayerStore() -// ── Content from Nuxt Content ── -const chaptersContent = ref([]) +// ── Content loaded flag (lyrics come from bookplayer config) ── const contentLoaded = ref(false) async function loadContent() { if (contentLoaded.value) return - try { - const data = await queryCollection('book').order('order', 'ASC').all() - chaptersContent.value = data - contentLoaded.value = true - } - catch (err) { - console.error('Failed to load book content:', err) - } + contentLoaded.value = true } -const activeChapter = computed(() => { - if (chapterIdx.value < 0 || !chaptersContent.value.length) return null - return chaptersContent.value[chapterIdx.value] ?? null -}) - // ── Chapter metadata ── const chapters = [ { slug: '01-introduction', title: 'Introduction' }, @@ -256,6 +249,23 @@ const chapterSong = computed(() => { return getPrimarySong(chapters[chapterIdx.value].slug) }) +const chapterLyrics = computed(() => { + return chapterSong.value?.lyrics?.trim() || '' +}) + +const chapterLyricsHtml = computed(() => { + if (!chapterLyrics.value) return '' + return chapterLyrics.value + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/\[([^\]]+)\]/g, '[$1]') + .replace(/\n\n/g, '

') + .replace(/\n/g, '
') + .replace(/^/, '

') + .replace(/$/, '

') +}) + // ── CSS columns pagination ── const contentStyle = computed(() => { if (isScrollMode.value) return {} @@ -276,10 +286,9 @@ function recalcPages() { let resizeObs: ResizeObserver | null = null -// Recalc when chapter content changes -watch(activeChapter, async () => { +// Recalc when chapter changes +watch(chapterIdx, async () => { currentPage.value = 0 - // Wait for ContentRenderer to update DOM await nextTick() await nextTick() setTimeout(recalcPages, 100) @@ -357,7 +366,7 @@ function prevPage() { audioPlayer.loadAndPlay(song) } // After content loads, go to last page - watch(activeChapter, async () => { + watch(chapterIdx, async () => { await nextTick() await nextTick() setTimeout(() => { @@ -809,7 +818,33 @@ onUnmounted(() => { .reader-columns :deep(ol) { break-inside: avoid; } -/* p with pre-line (lyrics) can be taller than a column — allow break */ +/* Lyrics content */ +.lyrics-content { + white-space: pre-line; + line-height: 1.9; + font-size: clamp(0.9rem, 2vw, 1.05rem); +} +.lyrics-content :deep(.lyrics-tag) { + display: block; + margin-top: 1.5rem; + margin-bottom: 0.5rem; + font-size: 0.75rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; + opacity: 0.35; +} +.lyrics-content :deep(p) { + break-inside: auto; + overflow-y: auto; +} +.lyrics-empty { + display: flex; + align-items: center; + justify-content: center; + min-height: 200px; +} + .reader-columns :deep(p) { break-inside: auto; overflow-y: auto;