declare var Notyf: any; /* ******************************************************** * ******************* CONFIGURATION ******************* * ******************************************************** */ interface ReaderConfig { rate: number; pitch: number; volume: number; highlightColor: string; highlightStyle: string; } const DEFAULT_CONFIG: ReaderConfig = { rate: 1.2, pitch: 1.1, volume: 1, highlightColor: '#f1fcf9', highlightStyle: 'background-color: #f1fcf9; padding: 2px 4px; border-radius: 3px; color: #136F63;', }; export default function handleArticleReader() { // ✅ DÉCLARATION DES VARIABLES const playPauseButton = document.getElementById('listen-article'); const stopReadingButton = document.getElementById('stop-reading'); const article = document.querySelector('.article-content'); let utterance: SpeechSynthesisUtterance; let voices: SpeechSynthesisVoice[] = []; let thomasVoice: SpeechSynthesisVoice | null = null; let originalContent = ''; let words: string[] = []; let currentWordIndex = 0; /* ******************************************************** * ************** MÉTHODES INTERNES ********************* * ******************************************************** */ // Fonctions de gestion du bouton function setReaderButtonReading(): void { if (!playPauseButton) return; playPauseButton.setAttribute('data-reading-status', 'playing'); playPauseButton.setAttribute('title', 'Arrêter la lecture vocale'); playPauseButton.setAttribute('aria-label', 'Arrêter la lecture vocale'); } function setReaderButtonStopped(): void { if (!playPauseButton) return; playPauseButton.setAttribute('data-reading-status', 'stopped'); playPauseButton.setAttribute('title', "Lancer la lecture vocale de l'article"); playPauseButton.setAttribute('aria-label', "Lecture vocale de l'article"); } function setReaderButtonPaused(): void { if (!playPauseButton) return; playPauseButton.setAttribute('data-reading-status', 'paused'); playPauseButton.setAttribute('title', 'Reprendre la lecture vocale'); playPauseButton.setAttribute('aria-label', 'Reprendre la lecture vocale'); } // Charger les voix et trouver Thomas function loadVoices(): void { voices = speechSynthesis.getVoices(); // Chercher la voix Thomas (peut être "Thomas" ou contenir "Thomas") thomasVoice = voices.find( (voice) => voice.name.toLowerCase().includes('thomas') || voice.name === 'Thomas' ) || null; // Si Thomas n'est pas trouvé, prendre une voix française par défaut if (!thomasVoice) { thomasVoice = voices.find((voice) => voice.lang.startsWith('fr')) || null; } } // Fonctions de highlighting function highlightWord(index: number): void { if (!article || index >= words.length) return; // Restaurer le contenu original si c'est le premier mot if (index === 0) { article.innerHTML = originalContent; } // Créer le nouveau HTML avec le mot highlighted const highlightedWords = words.map((word, i) => { if (i === index) { return `${word}`; } return word; }); article.innerHTML = highlightedWords.join(' '); } function removeHighlight(): void { if (article && originalContent) { article.innerHTML = originalContent; } } // Création de l'utterance function createUtterance(text: string): SpeechSynthesisUtterance { // Sauvegarder le contenu original et préparer les mots originalContent = article ? article.innerHTML : ''; words = text.split(/\s+/).filter((word) => word.trim().length > 0); currentWordIndex = 0; utterance = new SpeechSynthesisUtterance(text); utterance.lang = 'fr-FR'; // Appliquer la configuration utterance.rate = DEFAULT_CONFIG.rate; utterance.pitch = DEFAULT_CONFIG.pitch; utterance.volume = DEFAULT_CONFIG.volume; // Utiliser Thomas par défaut if (thomasVoice) { utterance.voice = thomasVoice; } // Event pour highlighter les mots pendant la lecture utterance.onboundary = (event) => { if (event.name === 'word') { highlightWord(currentWordIndex); currentWordIndex++; } }; utterance.onend = () => { setReaderButtonStopped(); removeHighlight(); currentWordIndex = 0; }; utterance.onerror = (event) => { console.error('Erreur lors de la lecture:', event); setReaderButtonStopped(); removeHighlight(); currentWordIndex = 0; }; return utterance; } // Fonctions de contrôle de lecture function playReading(): void { if (article) { const text = (article as HTMLElement).innerText.trim(); if (text) { createUtterance(text); speechSynthesis.speak(utterance); setReaderButtonReading(); showReaderStartedMessage(); } } } function pauseReading(): void { speechSynthesis.pause(); setReaderButtonPaused(); } function stopReading(): void { speechSynthesis.cancel(); setReaderButtonStopped(); removeHighlight(); currentWordIndex = 0; } /* ******************************************************** * *************** INITIALISATION ********************** * ******************************************************** */ // Vérifier si la synthèse vocale est déjà en cours au chargement de la page if (speechSynthesis.speaking) { speechSynthesis.cancel(); setReaderButtonStopped(); } // Arrêter la lecture vocale quand l'utilisateur quitte la page window.addEventListener('beforeunload', () => { if (speechSynthesis.speaking) { speechSynthesis.cancel(); setReaderButtonStopped(); } }); // Arrêter la lecture vocale quand l'onglet devient invisible document.addEventListener('visibilitychange', () => { if (document.hidden && speechSynthesis.speaking) { speechSynthesis.pause(); } else if (!document.hidden && speechSynthesis.paused) { speechSynthesis.resume(); setReaderButtonReading(); } }); // Charger les voix au démarrage et quand elles changent loadVoices(); speechSynthesis.onvoiceschanged = loadVoices; // Event listener principal playPauseButton?.addEventListener('click', () => { if (speechSynthesis.speaking && !speechSynthesis.paused) { // En cours de lecture → Pause pauseReading(); } else if (speechSynthesis.paused) { // En pause → Reprendre speechSynthesis.resume(); setReaderButtonReading(); } else { // Arrêté → Démarrer playReading(); } }); stopReadingButton?.addEventListener('click', () => { stopReading(); setReaderButtonStopped(); }); } function showReaderStartedMessage() { const notyf = new Notyf({ duration: 2000, ripple: false, dismissible: true, types: [ { type: 'success', background: '#10B981', icon: { className: 'notyf__icon--success', tagName: 'i', text: '🔊', }, }, ], position: { x: 'right', y: 'top', }, }); notyf.success(`Lecture de l'article lancée !`); }