Files
librodrome/app/components/book/ChapterHeader.vue
Yvv 7ea19e2247
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Viewer PDF.js mode livre avec signets, fix hydratation SSR
- Viewer PDF.js mode livre : double page côte à côte, navigation prev/next visuelle et clavier
- Panneau signets (outline) avec tout déplier/replier, highlight du spread courant
- Page 1 = couverture seule, puis paires 2-3, 4-5, etc.
- Navigation clavier : flèches, espace, Home/End
- Redimensionnement auto des canvas au resize
- Fix hydratation SSR : bookData.init() sans await dans ChapterHeader et SongBadges
- BookPdfReader : iframe vers /pdfjs/viewer.html au lieu du viewer natif
- Script postinstall pour copier pdf.min.mjs depuis node_modules

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 18:16:47 +01:00

129 lines
3.1 KiB
Vue

<template>
<header class="mb-8">
<div class="mb-2 flex items-center gap-2">
<span class="font-mono text-sm text-primary/70">
Chapitre {{ order }}
</span>
<span v-if="readingTime" class="text-xs text-white/30">
· {{ readingTime }}
</span>
</div>
<h1 class="chapter-title font-display font-bold leading-tight tracking-tight text-white">
{{ title }}
</h1>
<p v-if="description" class="mt-3 text-lg text-white/60">
{{ description }}
</p>
<!-- Songs + PDF actions row -->
<div class="mt-4 flex flex-wrap items-center gap-2">
<!-- Associated songs badges -->
<button
v-for="song in songs"
:key="song.id"
class="song-badge-btn"
@click="playSong(song)"
>
<div class="i-lucide-music h-3 w-3" />
{{ song.title }}
</button>
<!-- PDF button -->
<button v-if="pdfPage" class="pdf-btn" @click="showPdf = true">
<div class="i-lucide-book-open h-3.5 w-3.5" />
<span>Lire dans le PDF</span>
<span class="pdf-page-badge">p.{{ pdfPage }}</span>
</button>
</div>
<LazyBookPdfReader v-if="showPdf" v-model="showPdf" :page="pdfPage" />
</header>
</template>
<script setup lang="ts">
import type { Song } from '~/types/song'
const props = defineProps<{
title: string
description?: string
order: number
readingTime?: string
chapterSlug: string
}>()
const bookData = useBookData()
const { loadAndPlay } = useAudioPlayer()
bookData.init()
const songs = computed(() => bookData.getChapterSongs(props.chapterSlug))
const pdfPage = computed(() => bookData.getChapterPage(props.chapterSlug))
const showPdf = ref(false)
function playSong(song: Song) {
loadAndPlay(song)
}
</script>
<style scoped>
.chapter-title {
font-size: clamp(2rem, 5vw, 2.75rem);
padding-bottom: 0.75rem;
border-bottom: 2px solid hsl(var(--color-primary) / 0.4);
}
.song-badge-btn {
display: inline-flex;
align-items: center;
gap: 0.375rem;
border-radius: 9999px;
padding: 0.375rem 0.875rem;
font-size: 0.8rem;
font-weight: 600;
background: hsl(var(--color-primary) / 0.12);
color: hsl(var(--color-primary) / 0.85);
border: 1px solid hsl(var(--color-primary) / 0.25);
cursor: pointer;
transition: all 0.2s;
}
.song-badge-btn:hover {
background: hsl(var(--color-primary) / 0.2);
border-color: hsl(var(--color-primary) / 0.4);
box-shadow: 0 2px 8px hsl(var(--color-primary) / 0.15);
transform: translateY(-1px);
}
.pdf-btn {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
border-radius: 0.5rem;
background: hsl(0 0% 96%);
color: hsl(0 0% 15%);
font-size: 0.8rem;
font-weight: 600;
border: 1px solid hsl(0 0% 88%);
cursor: pointer;
transition: all 0.2s;
}
.pdf-btn:hover {
background: white;
box-shadow: 0 2px 12px hsl(0 0% 100% / 0.25);
transform: translateY(-1px);
}
.pdf-page-badge {
font-family: var(--font-mono, monospace);
font-size: 0.7rem;
font-weight: 500;
padding: 0.1rem 0.4rem;
border-radius: 0.25rem;
background: hsl(0 0% 88%);
color: hsl(0 0% 35%);
}
</style>