lecteur-digiy.js

(function () { if (window.__DIGIY_READER_BOOTED__) return; window.__DIGIY_READER_BOOTED__ = true; if (!(« speechSynthesis » in window) || !(« SpeechSynthesisUtterance » in window)) { return; } function onReady(fn) { if (document.readyState === « loading ») { document.addEventListener(« DOMContentLoaded », fn, { once: true }); } else { fn(); } } onReady(function () { var synth = window.speechSynthesis; var currentSession = 0; var currentChunks = []; var currentIndex = 0; var isPaused = false; injectStyle(); buildReaderBar(); var bar = document.getElementById(« digiy-reader-bar »); var toggleBtn = document.getElementById(« digiy-reader-toggle »); var stopBtn = document.getElementById(« digiy-reader-stop »); if (!bar || !toggleBtn || !stopBtn) return; toggleBtn.addEventListener(« click », function () { if (synth.speaking && !synth.paused) { synth.pause(); isPaused = true; renderState(« paused »); return; } if (synth.paused) { synth.resume(); isPaused = false; renderState(« reading »); return; } startReading(); }); stopBtn.addEventListener(« click », function () { stopReading(); }); function buildReaderBar() { var barEl = document.createElement(« div »); barEl.id = « digiy-reader-bar »; barEl.className = « digiy-reader-bar »; barEl.setAttribute(« data-digiy-reader-ignore », «  »); barEl.innerHTML = ‘‘ + ‘‘; document.body.appendChild(barEl); } function injectStyle() { var style = document.createElement(« style »); style.setAttribute(« data-digiy-reader-ignore », «  »); style.textContent = ` .digiy-reader-bar{ position:fixed; left:16px; bottom:18px; z-index:10000; display:flex; gap:10px; align-items:center; flex-wrap:wrap; } .digiy-reader-btn{ appearance:none; border:none; cursor:pointer; display:inline-flex; align-items:center; gap:10px; min-height:48px; padding:14px 18px; border-radius:999px; background:linear-gradient(135deg,#0f9d58,#0b7a45); color:#ffffff; font-weight:900; font-size:15px; line-height:1; box-shadow:0 14px 34px rgba(0,0,0,.22); transition:transform .18s ease, box-shadow .18s ease, opacity .18s ease; -webkit-tap-highlight-color:transparent; } .digiy-reader-btn:hover{ transform:translateY(-2px); box-shadow:0 18px 38px rgba(0,0,0,.26); } .digiy-reader-btn:focus-visible{ outline:3px solid rgba(15,157,88,.25); outline-offset:2px; } .digiy-reader-btn-stop{ background:#111827; color:#ffffff; } .digiy-reader-dot{ width:10px; height:10px; border-radius:50%; background:#f2d487; flex:0 0 10px; } @media (max-width:640px){ .digiy-reader-bar{ left:12px; right:auto; bottom:76px; max-width:calc(100vw – 24px); } .digiy-reader-btn{ justify-content:center; font-size:14px; padding:14px 16px; } } `; document.head.appendChild(style); } function renderState(state) { var label = document.getElementById(« digiy-reader-toggle-label »); if (!label) return; if (state === « idle ») label.textContent = « Lire la page »; if (state === « reading ») label.textContent = « Pause lecture »; if (state === « paused ») label.textContent = « Reprendre lecture »; } function stopReading() { currentSession++; currentChunks = []; currentIndex = 0; isPaused = false; synth.cancel(); renderState(« idle »); } function startReading() { stopReading(); var text = extractReadableText(); if (!text) return; currentChunks = splitText(text, 1400); currentIndex = 0; isPaused = false; if (!currentChunks.length) return; renderState(« reading »); speakChunk(currentSession, currentIndex); } function speakChunk(sessionId, index) { if (sessionId !== currentSession) return; if (index >= currentChunks.length) { renderState(« idle »); return; } var utterance = new SpeechSynthesisUtterance(currentChunks[index]); utterance.lang = pickVoiceLang(); utterance.rate = 0.98; utterance.pitch = 1; utterance.volume = 1; var voice = pickVoice(); if (voice) utterance.voice = voice; utterance.onend = function () { if (sessionId !== currentSession) return; currentIndex = index + 1; if (!isPaused) { speakChunk(sessionId, currentIndex); } }; utterance.onerror = function () { if (sessionId !== currentSession) return; currentIndex = index + 1; if (!isPaused) { speakChunk(sessionId, currentIndex); } }; synth.speak(utterance); } function pickVoiceLang() { var voices = synth.getVoices ? synth.getVoices() : []; var fr = voices.find(function (v) { return v && v.lang && v.lang.toLowerCase().indexOf(« fr ») === 0; }); return fr && fr.lang ? fr.lang : « fr-FR »; } function pickVoice() { var voices = synth.getVoices ? synth.getVoices() : []; var preferred = voices.find(function (v) { return v && v.lang && v.lang.toLowerCase().indexOf(« fr ») === 0; }); return preferred || null; } if (« onvoiceschanged » in synth) { synth.onvoiceschanged = function () {}; } function splitText(text, maxLen) { var sentences = text.match(/[^.!?\n]+[.!?\n]+|[^.!?\n]+$/g) || [text]; var chunks = []; var current = «  »; for (var i = 0; i < sentences.length; i++) { var sentence = sentences[i].replace(/\s+/g, " ").trim(); if (!sentence) continue; if ((current + " " + sentence).trim().length > maxLen) { if (current.trim()) chunks.push(current.trim()); current = sentence; } else { current = (current +  »  » + sentence).trim(); } } if (current.trim()) chunks.push(current.trim()); return chunks; } function extractReadableText() { var walker = document.createTreeWalker( document.body, NodeFilter.SHOW_TEXT, { acceptNode: function (node) { var value = (node.nodeValue || «  »).replace(/\s+/g,  » « ).trim(); if (!value) return NodeFilter.FILTER_REJECT; var parent = node.parentElement; if (!parent) return NodeFilter.FILTER_REJECT; if ( parent.closest(« [data-digiy-reader-ignore] ») || parent.closest(« .digiy-reader-bar ») || parent.closest(« .digiy-floating-home-menu ») || parent.closest(« .digiy-floating-menu ») ) { return NodeFilter.FILTER_REJECT; } var tag = parent.tagName ? parent.tagName.toUpperCase() : «  »; if ( tag === « SCRIPT » || tag === « STYLE » || tag === « NOSCRIPT » || tag === « SVG » || tag === « PATH » || tag === « BUTTON » ) { return NodeFilter.FILTER_REJECT; } var style = window.getComputedStyle(parent); if ( style.display === « none » || style.visibility === « hidden » || parent.hidden || parent.getAttribute(« aria-hidden ») === « true » ) { return NodeFilter.FILTER_REJECT; } return NodeFilter.FILTER_ACCEPT; } } ); var lines = []; var seen = new Set(); var node; while ((node = walker.nextNode())) { var text = (node.nodeValue || «  »).replace(/\s+/g,  » « ).trim(); if (!text) continue; if (!seen.has(text)) { seen.add(text); lines.push(text); } } return lines.join(« . « ); } }); })();