Fix: revert speakText to sync speechSynthesis; robust copyText fallback
- speakText: revert async ElevenLabs fetch chain — browser requires speechSynthesis.speak() to be called synchronously within user gesture; async .catch() context caused silent failure across browsers - copyText: fix fallback that always showed 'Copied!' even when it failed; now shows real error message, uses selectNodeContents for accuracy, handles missing clipboard API gracefully
This commit is contained in:
parent
7069c90465
commit
d58d13812f
1 changed files with 43 additions and 44 deletions
|
|
@ -117,18 +117,41 @@ function showToast(message, type) {
|
||||||
function copyText(elementId) {
|
function copyText(elementId) {
|
||||||
var el = document.getElementById(elementId);
|
var el = document.getElementById(elementId);
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
var text = el.innerText || el.textContent;
|
var text = el.innerText || el.textContent || '';
|
||||||
navigator.clipboard.writeText(text).then(function() {
|
if (!text.trim()) { showToast('Nothing to copy', 'error'); return; }
|
||||||
showToast('Copied!', 'success');
|
|
||||||
}).catch(function() {
|
// Try modern clipboard API (requires HTTPS or localhost)
|
||||||
var range = document.createRange();
|
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||||
range.selectNode(el);
|
navigator.clipboard.writeText(text).then(function() {
|
||||||
window.getSelection().removeAllRanges();
|
showToast('Copied!', 'success');
|
||||||
window.getSelection().addRange(range);
|
}).catch(function(err) {
|
||||||
document.execCommand('copy');
|
// Fallback: select + execCommand
|
||||||
window.getSelection().removeAllRanges();
|
try {
|
||||||
showToast('Copied!', 'success');
|
var range = document.createRange();
|
||||||
});
|
range.selectNodeContents(el);
|
||||||
|
window.getSelection().removeAllRanges();
|
||||||
|
window.getSelection().addRange(range);
|
||||||
|
var ok = document.execCommand('copy');
|
||||||
|
window.getSelection().removeAllRanges();
|
||||||
|
showToast(ok ? 'Copied!' : 'Copy failed — please select text manually', ok ? 'success' : 'error');
|
||||||
|
} catch (e) {
|
||||||
|
showToast('Copy not supported in this browser', 'error');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// No clipboard API — use execCommand directly
|
||||||
|
try {
|
||||||
|
var range = document.createRange();
|
||||||
|
range.selectNodeContents(el);
|
||||||
|
window.getSelection().removeAllRanges();
|
||||||
|
window.getSelection().addRange(range);
|
||||||
|
var ok = document.execCommand('copy');
|
||||||
|
window.getSelection().removeAllRanges();
|
||||||
|
showToast(ok ? 'Copied!' : 'Copy failed — please select text manually', ok ? 'success' : 'error');
|
||||||
|
} catch (e) {
|
||||||
|
showToast('Copy not supported in this browser', 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Speak / Stop
|
// Speak / Stop
|
||||||
|
|
@ -142,41 +165,17 @@ function speakText(elementId) {
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
var text = (el.innerText || el.textContent).trim();
|
var text = (el.innerText || el.textContent).trim();
|
||||||
if (!text) { showToast('Nothing to read', 'error'); return; }
|
if (!text) { showToast('Nothing to read', 'error'); return; }
|
||||||
|
if (!('speechSynthesis' in window)) { showToast('TTS not supported', 'error'); return; }
|
||||||
|
window.speechSynthesis.cancel();
|
||||||
|
var utter = new SpeechSynthesisUtterance(text);
|
||||||
|
utter.rate = 0.9;
|
||||||
|
utter.onend = function() { stopReading(); };
|
||||||
|
utter.onerror = function() { stopReading(); };
|
||||||
currentlyReadingId = elementId;
|
currentlyReadingId = elementId;
|
||||||
var btn = findReadButton(elementId);
|
var btn = findReadButton(elementId);
|
||||||
if (btn) { btn.classList.add('btn-reading'); btn.innerHTML = '<i class="fas fa-stop"></i> Stop'; }
|
if (btn) { btn.classList.add('btn-reading'); btn.innerHTML = '<i class="fas fa-stop"></i> Stop'; }
|
||||||
|
window.speechSynthesis.speak(utter);
|
||||||
// Try ElevenLabs backend first
|
showToast('Reading — click Stop to end', 'info');
|
||||||
var token = window.AUTH_TOKEN || localStorage.getItem('ped_scribe_token') || '';
|
|
||||||
fetch('/api/text-to-speech', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token },
|
|
||||||
body: JSON.stringify({ text: text })
|
|
||||||
})
|
|
||||||
.then(function(r) {
|
|
||||||
if (!r.ok) throw new Error('not configured');
|
|
||||||
return r.blob();
|
|
||||||
})
|
|
||||||
.then(function(blob) {
|
|
||||||
var url = URL.createObjectURL(blob);
|
|
||||||
currentAudio = new Audio(url);
|
|
||||||
currentAudio.onended = function() { URL.revokeObjectURL(url); stopReading(); };
|
|
||||||
currentAudio.onerror = function() { URL.revokeObjectURL(url); stopReading(); };
|
|
||||||
currentAudio.play();
|
|
||||||
showToast('Reading — click Stop to end', 'info');
|
|
||||||
})
|
|
||||||
.catch(function() {
|
|
||||||
// Fallback to native speech synthesis
|
|
||||||
if (!('speechSynthesis' in window)) { stopReading(); showToast('TTS not supported', 'error'); return; }
|
|
||||||
window.speechSynthesis.cancel();
|
|
||||||
var utter = new SpeechSynthesisUtterance(text);
|
|
||||||
utter.rate = 0.9;
|
|
||||||
utter.onend = function() { stopReading(); };
|
|
||||||
utter.onerror = function() { stopReading(); };
|
|
||||||
window.speechSynthesis.speak(utter);
|
|
||||||
showToast('Reading — click Stop to end', 'info');
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function stopReading() {
|
function stopReading() {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue