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(« . « );
}
});
})();