diff --git a/resources/css/components/article-revues-toolbar.css b/resources/css/components/article-revues-toolbar.css
index baa0828..bca3405 100644
--- a/resources/css/components/article-revues-toolbar.css
+++ b/resources/css/components/article-revues-toolbar.css
@@ -30,16 +30,63 @@
a.cta--download-pdf {
@apply ml-auto;
}
+
+ #listen-article,
+ #stop-reading {
+ @apply rounded-full w-12 h-12 flex items-center justify-center m-0 p-0 transition-all duration-300;
+ }
#listen-article {
- @apply bg-primary text-white rounded-full w-12 h-12 flex items-center justify-center m-0 p-0 transition-all duration-300;
+ @apply bg-primary text-white;
&:hover {
@apply scale-110;
}
img {
@apply w-6 h-6;
}
- &.is-active {
- @apply bg-red-500;
+ &[data-reading-status='playing'] {
+ /* @apply bg-blue-500; */
+ @apply bg-white border border-primary;
+ /* &:hover {
+ @apply bg-red-500;
+ } */
+ #play-reading {
+ @apply hidden;
+ }
+ #pause-reading {
+ @apply block;
+ }
+ }
+
+ &[data-reading-status='stopped'] {
+ #play-reading {
+ @apply block;
+ }
+ #pause-reading {
+ @apply hidden;
+ }
+ }
+ &[data-reading-status='paused'] {
+ @apply bg-yellow-500;
+ #play-reading {
+ @apply block;
+ }
+ #pause-reading {
+ @apply hidden;
+ }
+ }
+ }
+ #stop-reading {
+ @apply bg-primary hidden;
+ img {
+ @apply w-4 h-4;
+ }
+ }
+ &:has(
+ #listen-article[data-reading-status='playing'],
+ #listen-article[data-reading-status='paused']
+ ) {
+ #stop-reading {
+ @apply flex;
}
}
}
diff --git a/resources/img/icons/carhop-pause.svg b/resources/img/icons/carhop-pause.svg
new file mode 100644
index 0000000..cbc3618
--- /dev/null
+++ b/resources/img/icons/carhop-pause.svg
@@ -0,0 +1,6 @@
+
diff --git a/resources/img/icons/carhop-stop-reading.svg b/resources/img/icons/carhop-stop-reading.svg
new file mode 100644
index 0000000..d8c39b4
--- /dev/null
+++ b/resources/img/icons/carhop-stop-reading.svg
@@ -0,0 +1,4 @@
+
+
\ No newline at end of file
diff --git a/resources/js/singles/reader.ts b/resources/js/singles/reader.ts
index 26d51be..80a28cb 100644
--- a/resources/js/singles/reader.ts
+++ b/resources/js/singles/reader.ts
@@ -1,20 +1,68 @@
-export default function handleArticleReader() {
- const button = document.getElementById('listen-article');
- const article = document.querySelector('.article-content');
+declare var Notyf: any;
- // Paramètres fixes définis dans le code
- const speechSettings = {
- rate: 1.2, // Vitesse de lecture (0.1 à 2)
- pitch: 1.1, // Tonalité (0 à 2)
- volume: 1, // Volume (0 à 1)
- };
+/* ********************************************************
+ * ******************* 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() {
+ function loadVoices(): void {
voices = speechSynthesis.getVoices();
// Chercher la voix Thomas (peut être "Thomas" ou contenir "Thomas")
@@ -29,16 +77,8 @@ export default function handleArticleReader() {
}
}
- // Charger les voix au démarrage et quand elles changent
- loadVoices();
- speechSynthesis.onvoiceschanged = loadVoices;
-
- // Variables pour le highlighting
- let originalContent = '';
- let words: string[] = [];
- let currentWordIndex = 0;
-
- function highlightWord(index: number) {
+ // Fonctions de highlighting
+ function highlightWord(index: number): void {
if (!article || index >= words.length) return;
// Restaurer le contenu original si c'est le premier mot
@@ -49,7 +89,7 @@ export default function handleArticleReader() {
// Créer le nouveau HTML avec le mot highlighted
const highlightedWords = words.map((word, i) => {
if (i === index) {
- return `${word}`;
+ return `${word}`;
}
return word;
});
@@ -57,12 +97,13 @@ export default function handleArticleReader() {
article.innerHTML = highlightedWords.join(' ');
}
- function removeHighlight() {
+ 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 : '';
@@ -72,10 +113,10 @@ export default function handleArticleReader() {
utterance = new SpeechSynthesisUtterance(text);
utterance.lang = 'fr-FR';
- // Appliquer les paramètres fixes
- utterance.rate = speechSettings.rate;
- utterance.pitch = speechSettings.pitch;
- utterance.volume = speechSettings.volume;
+ // 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) {
@@ -91,14 +132,14 @@ export default function handleArticleReader() {
};
utterance.onend = () => {
- button.textContent = '🔊 Lire en vocal';
+ setReaderButtonStopped();
removeHighlight();
currentWordIndex = 0;
};
utterance.onerror = (event) => {
console.error('Erreur lors de la lecture:', event);
- button.textContent = '🔊 Lire en vocal';
+ setReaderButtonStopped();
removeHighlight();
currentWordIndex = 0;
};
@@ -106,21 +147,105 @@ export default function handleArticleReader() {
return utterance;
}
- button?.addEventListener('click', () => {
- if (speechSynthesis.speaking) {
- speechSynthesis.cancel();
- button.classList.remove('is-active');
- removeHighlight(); // Retirer le highlight quand on arrête
- currentWordIndex = 0;
- } else {
- if (article) {
- const text = (article as HTMLElement).innerText.trim();
- if (text) {
- createUtterance(text);
- speechSynthesis.speak(utterance);
- button.classList.add('is-active');
- }
+ // 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 !`);
+}
diff --git a/template-parts/articles/article-toolbar.php b/template-parts/articles/article-toolbar.php
index aa96caf..1ff7a9c 100644
--- a/template-parts/articles/article-toolbar.php
+++ b/template-parts/articles/article-toolbar.php
@@ -47,9 +47,12 @@ $pdf_url = isset($pdf_version) && !empty($pdf_version['url']) ? $pdf_version['ur
-