feat: SEO complet + analytics Umami + og:image § logo
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/push/woodpecker Pipeline was successful
SEO : - composable useSeoPage() : og:*, Twitter Cards, canonical sur toutes les pages (15 pages) - app.vue : JSON-LD Organization + Book, og:image global og-default.png - og-default.png 1200×630 : logo § calligraphique + texte (Pillow) - nuxt.config.ts : @nuxtjs/sitemap avec 26 URLs statiques Analytics Umami : - useTracking() : helpers typés audio/pdf/player/scroll/cta - useScrollTracking() : scroll depth 25/50/75/100% + liens externes auto - useAudioPlayer : trackAudioPlay/Progress/Complete - BookPdfReader : trackPdfOpen/Close avec durée - BookPlayer : trackPlayerOpen/Chapter/Mode - docker-compose : variables NUXT_PUBLIC_UMAMI_* passées au container Images : - Couv-Economie-du-don.jpg ajoutée dans public/images/ - bookplayer.config.yml + home.yml : références mises à jour Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Tracks scroll depth milestones (25 / 50 / 75 / 100 %) on each page.
|
||||
* Also intercepts external link clicks and tracks them.
|
||||
*
|
||||
* Usage: call once in default.vue layout.
|
||||
*/
|
||||
export function useScrollTracking() {
|
||||
const route = useRoute()
|
||||
const { trackScrollDepth, trackExternalLink } = useTracking()
|
||||
|
||||
// ── Scroll depth ────────────────────────────────────────────────────────
|
||||
const fired = new Set<number>()
|
||||
|
||||
function onScroll() {
|
||||
const el = document.documentElement
|
||||
const scrolled = el.scrollTop + el.clientHeight
|
||||
const total = el.scrollHeight
|
||||
if (total <= el.clientHeight) return
|
||||
const pct = (scrolled / total) * 100
|
||||
|
||||
for (const milestone of [25, 50, 75, 100] as const) {
|
||||
if (pct >= milestone && !fired.has(milestone)) {
|
||||
fired.add(milestone)
|
||||
trackScrollDepth(route.path, milestone)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reset fired milestones on route change
|
||||
watch(() => route.path, () => fired.clear())
|
||||
|
||||
// ── External link tracking ───────────────────────────────────────────────
|
||||
function onLinkClick(e: MouseEvent) {
|
||||
const target = (e.target as HTMLElement).closest('a')
|
||||
if (!target) return
|
||||
const href = target.getAttribute('href') || ''
|
||||
if (!href.startsWith('http') && !href.startsWith('//')) return
|
||||
try {
|
||||
const url = new URL(href)
|
||||
if (url.hostname === 'librodrome.org') return
|
||||
trackExternalLink(href, target.textContent?.trim() || '')
|
||||
}
|
||||
catch { /* invalid URL, ignore */ }
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('scroll', onScroll, { passive: true })
|
||||
document.addEventListener('click', onLinkClick)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('scroll', onScroll)
|
||||
document.removeEventListener('click', onLinkClick)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user