FEATURE Optimizing article reader behaviour
This commit is contained in:
parent
38c5184203
commit
25f29045e6
|
|
@ -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 {
|
||||
&[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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
6
resources/img/icons/carhop-pause.svg
Normal file
6
resources/img/icons/carhop-pause.svg
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<svg width="16" height="23" viewBox="0 0 16 23" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1459_14367)">
|
||||
<path d="M0.941406 0V23" stroke="#136F63" stroke-width="2"/>
|
||||
<path d="M15.0586 0V23" stroke="#136F63" stroke-width="2"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 268 B |
4
resources/img/icons/carhop-stop-reading.svg
Normal file
4
resources/img/icons/carhop-stop-reading.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Calque_1" data-name="Calque 1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<rect width="16" height="16" style="fill: #fff;"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 217 B |
|
|
@ -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 `<span class="speech-highlight" style="background-color: yellow; padding: 2px 4px; border-radius: 3px;">${word}</span>`;
|
||||
return `<span class="speech-highlight" style="${DEFAULT_CONFIG.highlightStyle}">${word}</span>`;
|
||||
}
|
||||
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 {
|
||||
// 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);
|
||||
button.classList.add('is-active');
|
||||
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 !`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,9 +47,12 @@ $pdf_url = isset($pdf_version) && !empty($pdf_version['url']) ? $pdf_version['ur
|
|||
<?php endif; ?>
|
||||
|
||||
|
||||
<button id="listen-article" type="button">
|
||||
<img class="icon" src="<?php echo get_stylesheet_directory_uri(); ?>/resources/img/icons/carhop-ecouter.svg" alt="Lecture vocale de l'article">
|
||||
|
||||
<button id="listen-article" type="button" data-reading-status="stopped" title="Lancer la lecture vocale de l'article">
|
||||
<img id="play-reading" class="icon" src="<?php echo get_stylesheet_directory_uri(); ?>/resources/img/icons/carhop-ecouter.svg" alt="Lecture vocale de l'article">
|
||||
<img id="pause-reading" class="icon" src="<?php echo get_stylesheet_directory_uri(); ?>/resources/img/icons/carhop-pause.svg" alt="Arrêter la lecture vocale de l'article">
|
||||
</button>
|
||||
<button id="stop-reading" type="button" title="Arrêter la lecture vocale de l'article">
|
||||
<img class="icon" src="<?php echo get_stylesheet_directory_uri(); ?>/resources/img/icons/carhop-stop-reading.svg" alt="Arrêter la lecture vocale de l'article">
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user