FEATURE Updating speech reading article
Some checks failed
continuous-integration/drone/push Build is failing
Some checks failed
continuous-integration/drone/push Build is failing
This commit is contained in:
parent
8a2b4005e5
commit
a9765891ff
|
|
@ -30,5 +30,21 @@
|
||||||
a.cta--download-pdf {
|
a.cta--download-pdf {
|
||||||
@apply ml-auto;
|
@apply ml-auto;
|
||||||
}
|
}
|
||||||
|
#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;
|
||||||
|
&:hover {
|
||||||
|
@apply scale-110;
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
@apply w-6 h-6;
|
||||||
|
}
|
||||||
|
&.is-active {
|
||||||
|
@apply bg-red-500;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-actions {
|
||||||
|
@apply flex items-center gap-3 ml-auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
14
resources/img/icons/carhop-ecouter.svg
Normal file
14
resources/img/icons/carhop-ecouter.svg
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
<svg width="29" height="27" viewBox="0 0 29 27" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0_1459_14383)">
|
||||||
|
<path d="M14.1953 0V27" stroke="white" stroke-width="2"/>
|
||||||
|
<path d="M7.93359 5.05997V21.9333" stroke="white" stroke-width="2"/>
|
||||||
|
<path d="M0.691406 10.3231V16.6769" stroke="white" stroke-width="2"/>
|
||||||
|
<path d="M21.0664 5.05997V21.9333" stroke="white" stroke-width="2"/>
|
||||||
|
<path d="M28.3086 10.3231V16.6769" stroke="white" stroke-width="2"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_1459_14383">
|
||||||
|
<rect width="29" height="27" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 586 B |
|
|
@ -3,7 +3,7 @@ export function handleArticleToolbar() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function observeTabsButtons(): void {
|
function observeTabsButtons(): void {
|
||||||
const toolbarButtons = document.querySelectorAll('#article-toolbar button');
|
const toolbarButtons = document.querySelectorAll('#article-toolbar button[role="tab"]');
|
||||||
|
|
||||||
toolbarButtons.forEach((toolbarButton) => {
|
toolbarButtons.forEach((toolbarButton) => {
|
||||||
toolbarButton.addEventListener('click', () => {
|
toolbarButton.addEventListener('click', () => {
|
||||||
|
|
@ -20,7 +20,7 @@ function toggleActiveTab(toolbarButton: HTMLElement): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetActiveToolbarButtons(): void {
|
function resetActiveToolbarButtons(): void {
|
||||||
const toolbarButtons = document.querySelectorAll('#article-toolbar button');
|
const toolbarButtons = document.querySelectorAll('#article-toolbar button[role="tab"]');
|
||||||
toolbarButtons.forEach((toolbarButton) => {
|
toolbarButtons.forEach((toolbarButton) => {
|
||||||
toolbarButton.setAttribute('aria-selected', 'false');
|
toolbarButton.setAttribute('aria-selected', 'false');
|
||||||
});
|
});
|
||||||
|
|
@ -30,4 +30,5 @@ function handleActiveTabContent(tab: string): void {
|
||||||
const contentWrapper = document.querySelector('.content-wrapper');
|
const contentWrapper = document.querySelector('.content-wrapper');
|
||||||
contentWrapper?.setAttribute('data-active-tab', tab);
|
contentWrapper?.setAttribute('data-active-tab', tab);
|
||||||
console.log(tab);
|
console.log(tab);
|
||||||
|
console.log('contentWrapper');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,31 @@
|
||||||
export default function handleArticleReader() {
|
export default function handleArticleReader() {
|
||||||
const button = document.getElementById('speak-btn');
|
const button = document.getElementById('listen-article');
|
||||||
const article = document.querySelector('.article-content');
|
const article = document.querySelector('.article-content');
|
||||||
|
|
||||||
// Contrôles de paramètres (optionnels)
|
// Paramètres fixes définis dans le code
|
||||||
const rateControl = document.getElementById('speech-rate') as HTMLInputElement;
|
const speechSettings = {
|
||||||
const pitchControl = document.getElementById('speech-pitch') as HTMLInputElement;
|
rate: 1.2, // Vitesse de lecture (0.1 à 2)
|
||||||
const volumeControl = document.getElementById('speech-volume') as HTMLInputElement;
|
pitch: 1.1, // Tonalité (0 à 2)
|
||||||
const voiceSelect = document.getElementById('speech-voice') as HTMLSelectElement;
|
volume: 1, // Volume (0 à 1)
|
||||||
|
};
|
||||||
|
|
||||||
let utterance: SpeechSynthesisUtterance;
|
let utterance: SpeechSynthesisUtterance;
|
||||||
let voices: SpeechSynthesisVoice[] = [];
|
let voices: SpeechSynthesisVoice[] = [];
|
||||||
|
let thomasVoice: SpeechSynthesisVoice | null = null;
|
||||||
|
|
||||||
// Charger les voix disponibles
|
// Charger les voix et trouver Thomas
|
||||||
function loadVoices() {
|
function loadVoices() {
|
||||||
voices = speechSynthesis.getVoices();
|
voices = speechSynthesis.getVoices();
|
||||||
if (voiceSelect && voices.length > 0) {
|
|
||||||
voiceSelect.innerHTML = '';
|
// Chercher la voix Thomas (peut être "Thomas" ou contenir "Thomas")
|
||||||
voices
|
thomasVoice =
|
||||||
.filter((voice) => voice.lang.startsWith('fr')) // Filtrer les voix françaises
|
voices.find(
|
||||||
.forEach((voice, index) => {
|
(voice) => voice.name.toLowerCase().includes('thomas') || voice.name === 'Thomas'
|
||||||
const option = document.createElement('option');
|
) || null;
|
||||||
option.value = index.toString();
|
|
||||||
option.textContent = `${voice.name} (${voice.lang})`;
|
// Si Thomas n'est pas trouvé, prendre une voix française par défaut
|
||||||
voiceSelect.appendChild(option);
|
if (!thomasVoice) {
|
||||||
});
|
thomasVoice = voices.find((voice) => voice.lang.startsWith('fr')) || null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -31,59 +33,94 @@ export default function handleArticleReader() {
|
||||||
loadVoices();
|
loadVoices();
|
||||||
speechSynthesis.onvoiceschanged = loadVoices;
|
speechSynthesis.onvoiceschanged = loadVoices;
|
||||||
|
|
||||||
// Configuration par défaut
|
// Variables pour le highlighting
|
||||||
const defaultSettings = {
|
let originalContent = '';
|
||||||
rate: 0.9, // Vitesse (0.1 à 10)
|
let words: string[] = [];
|
||||||
pitch: 1, // Tonalité (0 à 2)
|
let currentWordIndex = 0;
|
||||||
volume: 1, // Volume (0 à 1)
|
|
||||||
voice: null, // Voix (null = voix par défaut)
|
function highlightWord(index: number) {
|
||||||
};
|
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 `<span class="speech-highlight" style="background-color: yellow; padding: 2px 4px; border-radius: 3px;">${word}</span>`;
|
||||||
|
}
|
||||||
|
return word;
|
||||||
|
});
|
||||||
|
|
||||||
|
article.innerHTML = highlightedWords.join(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeHighlight() {
|
||||||
|
if (article && originalContent) {
|
||||||
|
article.innerHTML = originalContent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function createUtterance(text: string): SpeechSynthesisUtterance {
|
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 = new SpeechSynthesisUtterance(text);
|
||||||
utterance.lang = 'fr-FR';
|
utterance.lang = 'fr-FR';
|
||||||
|
|
||||||
// Appliquer les paramètres depuis les contrôles ou les valeurs par défaut
|
// Appliquer les paramètres fixes
|
||||||
utterance.rate = rateControl ? parseFloat(rateControl.value) : defaultSettings.rate;
|
utterance.rate = speechSettings.rate;
|
||||||
utterance.pitch = pitchControl ? parseFloat(pitchControl.value) : defaultSettings.pitch;
|
utterance.pitch = speechSettings.pitch;
|
||||||
utterance.volume = volumeControl ? parseFloat(volumeControl.value) : defaultSettings.volume;
|
utterance.volume = speechSettings.volume;
|
||||||
|
|
||||||
// Sélectionner la voix
|
// Utiliser Thomas par défaut
|
||||||
if (voiceSelect && voices.length > 0) {
|
if (thomasVoice) {
|
||||||
const selectedVoiceIndex = parseInt(voiceSelect.value);
|
utterance.voice = thomasVoice;
|
||||||
const frenchVoices = voices.filter((voice) => voice.lang.startsWith('fr'));
|
|
||||||
if (frenchVoices[selectedVoiceIndex]) {
|
|
||||||
utterance.voice = frenchVoices[selectedVoiceIndex];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Event pour highlighter les mots pendant la lecture
|
||||||
|
utterance.onboundary = (event) => {
|
||||||
|
if (event.name === 'word') {
|
||||||
|
highlightWord(currentWordIndex);
|
||||||
|
currentWordIndex++;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
utterance.onend = () => {
|
utterance.onend = () => {
|
||||||
button.textContent = '🔊 Lire en vocal';
|
button.textContent = '🔊 Lire en vocal';
|
||||||
|
removeHighlight();
|
||||||
|
currentWordIndex = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
utterance.onerror = (event) => {
|
utterance.onerror = (event) => {
|
||||||
console.error('Erreur lors de la lecture:', event);
|
console.error('Erreur lors de la lecture:', event);
|
||||||
button.textContent = '🔊 Lire en vocal';
|
button.textContent = '🔊 Lire en vocal';
|
||||||
|
removeHighlight();
|
||||||
|
currentWordIndex = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
return utterance;
|
return utterance;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (button) {
|
button?.addEventListener('click', () => {
|
||||||
button.addEventListener('click', () => {
|
if (speechSynthesis.speaking) {
|
||||||
if (speechSynthesis.speaking) {
|
speechSynthesis.cancel();
|
||||||
speechSynthesis.cancel();
|
button.classList.remove('is-active');
|
||||||
button.textContent = '🔊 Lire en vocal';
|
removeHighlight(); // Retirer le highlight quand on arrête
|
||||||
} else {
|
currentWordIndex = 0;
|
||||||
if (article) {
|
} else {
|
||||||
const text = article.innerText.trim();
|
if (article) {
|
||||||
if (text) {
|
const text = (article as HTMLElement).innerText.trim();
|
||||||
createUtterance(text);
|
if (text) {
|
||||||
speechSynthesis.speak(utterance);
|
createUtterance(text);
|
||||||
button.textContent = '⏹️ Arrêter la lecture';
|
speechSynthesis.speak(utterance);
|
||||||
}
|
button.classList.add('is-active');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,18 +22,7 @@ $revueID = get_field('related_revue', get_the_ID());
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<div class="content-area">
|
<div class="content-area">
|
||||||
<button id="speak-btn">🔊 Lire en vocal</button>
|
|
||||||
<!-- Vitesse de lecture -->
|
|
||||||
<input type="range" id="speech-rate" min="0.1" max="2" step="0.1" value="0.9">
|
|
||||||
|
|
||||||
<!-- Tonalité -->
|
|
||||||
<input type="range" id="speech-pitch" min="0" max="2" step="0.1" value="1">
|
|
||||||
|
|
||||||
<!-- Volume -->
|
|
||||||
<input type="range" id="speech-volume" min="0" max="1" step="0.1" value="1">
|
|
||||||
|
|
||||||
<!-- Sélection de voix -->
|
|
||||||
<select id="speech-voice"></select>
|
|
||||||
<?php get_template_part('template-parts/articles/article-references', null, array(
|
<?php get_template_part('template-parts/articles/article-references', null, array(
|
||||||
'postId' => get_the_ID()
|
'postId' => get_the_ID()
|
||||||
)); ?>
|
)); ?>
|
||||||
|
|
|
||||||
|
|
@ -37,11 +37,21 @@ $pdf_url = isset($pdf_version) ? $pdf_version['url'] : null;
|
||||||
Informations
|
Informations
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<?php if ($pdf_url) : ?>
|
<div class="toolbar-actions">
|
||||||
<a id="tab-6" href="<?php echo $pdf_url; ?>" target="_blank" class="cta cta--classic cta--has-icon cta--rounded cta--download-pdf ">
|
|
||||||
<img class="icon" src="<?php echo get_template_directory_uri(); ?>/resources/img/icons/carhop-fleche-telecharger.svg" alt="">
|
<?php if ($pdf_url) : ?>
|
||||||
PDF
|
<a href="<?php echo $pdf_url; ?>" target="_blank" class="cta cta--classic cta--has-icon cta--rounded cta--download-pdf ">
|
||||||
</a>
|
<img class="icon" src="<?php echo get_template_directory_uri(); ?>/resources/img/icons/carhop-fleche-telecharger.svg" alt="">
|
||||||
<?php endif; ?>
|
PDF
|
||||||
|
</a>
|
||||||
|
<?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>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
Loading…
Reference in New Issue
Block a user