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;