BookPlayer affiche les paroles du morceau, plus le contenu chapitre

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 <noreply@anthropic.com>
This commit is contained in:
Yvv
2026-02-26 21:06:11 +01:00
parent dd1d8baf4f
commit 922afa2763

View File

@@ -87,7 +87,13 @@
ref="contentEl" ref="contentEl"
:style="contentStyle" :style="contentStyle"
> >
<ContentRenderer v-if="activeChapter" :value="activeChapter" /> <div v-if="chapterLyrics" class="lyrics-content" v-html="chapterLyricsHtml" />
<div v-else-if="chapterSong" class="lyrics-empty">
<p class="op-40 italic">Paroles à venir pour « {{ chapterSong.title }} »</p>
</div>
<div v-else class="lyrics-empty">
<p class="op-40 italic">Aucun morceau associé à ce chapitre</p>
</div>
</div> </div>
<!-- Page turn shadow overlay (paginated only) --> <!-- Page turn shadow overlay (paginated only) -->
<div v-if="!isScrollMode" class="reader-shadow" :class="{ visible: isTurning }" /> <div v-if="!isScrollMode" class="reader-shadow" :class="{ visible: isTurning }" />
@@ -194,27 +200,14 @@ const { init: initBookData, getSongs, getPrimarySong, getChapterForSong, getPlay
const audioPlayer = useAudioPlayer() const audioPlayer = useAudioPlayer()
const playerStore = usePlayerStore() const playerStore = usePlayerStore()
// ── Content from Nuxt Content ── // ── Content loaded flag (lyrics come from bookplayer config) ──
const chaptersContent = ref<any[]>([])
const contentLoaded = ref(false) const contentLoaded = ref(false)
async function loadContent() { async function loadContent() {
if (contentLoaded.value) return if (contentLoaded.value) return
try { contentLoaded.value = true
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)
}
} }
const activeChapter = computed(() => {
if (chapterIdx.value < 0 || !chaptersContent.value.length) return null
return chaptersContent.value[chapterIdx.value] ?? null
})
// ── Chapter metadata ── // ── Chapter metadata ──
const chapters = [ const chapters = [
{ slug: '01-introduction', title: 'Introduction' }, { slug: '01-introduction', title: 'Introduction' },
@@ -256,6 +249,23 @@ const chapterSong = computed(() => {
return getPrimarySong(chapters[chapterIdx.value].slug) 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, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/\[([^\]]+)\]/g, '<span class="lyrics-tag">[$1]</span>')
.replace(/\n\n/g, '</p><p>')
.replace(/\n/g, '<br>')
.replace(/^/, '<p>')
.replace(/$/, '</p>')
})
// ── CSS columns pagination ── // ── CSS columns pagination ──
const contentStyle = computed(() => { const contentStyle = computed(() => {
if (isScrollMode.value) return {} if (isScrollMode.value) return {}
@@ -276,10 +286,9 @@ function recalcPages() {
let resizeObs: ResizeObserver | null = null let resizeObs: ResizeObserver | null = null
// Recalc when chapter content changes // Recalc when chapter changes
watch(activeChapter, async () => { watch(chapterIdx, async () => {
currentPage.value = 0 currentPage.value = 0
// Wait for ContentRenderer to update DOM
await nextTick() await nextTick()
await nextTick() await nextTick()
setTimeout(recalcPages, 100) setTimeout(recalcPages, 100)
@@ -357,7 +366,7 @@ function prevPage() {
audioPlayer.loadAndPlay(song) audioPlayer.loadAndPlay(song)
} }
// After content loads, go to last page // After content loads, go to last page
watch(activeChapter, async () => { watch(chapterIdx, async () => {
await nextTick() await nextTick()
await nextTick() await nextTick()
setTimeout(() => { setTimeout(() => {
@@ -809,7 +818,33 @@ onUnmounted(() => {
.reader-columns :deep(ol) { .reader-columns :deep(ol) {
break-inside: avoid; 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) { .reader-columns :deep(p) {
break-inside: auto; break-inside: auto;
overflow-y: auto; overflow-y: auto;