Update web/templates/entry_view.html
This commit is contained in:
parent
266eec88e7
commit
a42a95368c
@ -278,4 +278,129 @@
|
||||
{% endif %}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<script>
|
||||
(function(){
|
||||
const btn = document.getElementById('ttsBtn');
|
||||
if (!btn) return;
|
||||
|
||||
// Graceful feature check
|
||||
const synth = window.speechSynthesis;
|
||||
if (!('speechSynthesis' in window)) {
|
||||
btn.disabled = true;
|
||||
btn.title = 'Text-to-Speech not supported in this browser';
|
||||
return;
|
||||
}
|
||||
|
||||
// Build the combined text (Illustration + two spaces + Application).
|
||||
function buildParagraph() {
|
||||
const ill = (btn.dataset.illustration || '').trim();
|
||||
const app = (btn.dataset.application || '').trim();
|
||||
|
||||
// Ensure Illustration ends with punctuation if it has text
|
||||
let illFixed = ill;
|
||||
if (illFixed && !/[.!?…]$/.test(illFixed)) {
|
||||
illFixed += '.';
|
||||
}
|
||||
// Two spaces between illustration and application (if both exist)
|
||||
const spacer = (illFixed && app) ? ' ' : '';
|
||||
return (illFixed + spacer + app).replace(/\s+/g, ' ').trim();
|
||||
}
|
||||
|
||||
// Try to pick a natural-sounding voice if available
|
||||
let voices = [];
|
||||
function loadVoices() {
|
||||
voices = synth.getVoices();
|
||||
}
|
||||
loadVoices();
|
||||
if (typeof speechSynthesis.onvoiceschanged !== 'undefined') {
|
||||
speechSynthesis.onvoiceschanged = loadVoices;
|
||||
}
|
||||
|
||||
function chooseVoice() {
|
||||
if (!voices || !voices.length) return null;
|
||||
// Prefer a Google or high-quality en voice if present
|
||||
const prefs = [
|
||||
/Google\s.+English/i,
|
||||
/en-US/i,
|
||||
/English/i
|
||||
];
|
||||
for (const rx of prefs) {
|
||||
const v = voices.find(v => rx.test(v.name) || rx.test(v.lang));
|
||||
if (v) return v;
|
||||
}
|
||||
return voices[0] || null;
|
||||
}
|
||||
|
||||
let isSpeaking = false;
|
||||
let currentUtterances = [];
|
||||
|
||||
function chunkBySentences(text, maxLen=300) {
|
||||
// Split by sentence-ish boundaries, merge small pieces to respect character limits
|
||||
const raw = text.split(/(?<=[.!?…])\s+/);
|
||||
const chunks = [];
|
||||
let buf = '';
|
||||
raw.forEach(part => {
|
||||
if ((buf + ' ' + part).trim().length <= maxLen) {
|
||||
buf = (buf ? (buf + ' ' + part) : part);
|
||||
} else {
|
||||
if (buf) chunks.push(buf.trim());
|
||||
buf = part;
|
||||
}
|
||||
});
|
||||
if (buf) chunks.push(buf.trim());
|
||||
return chunks.length ? chunks : [text];
|
||||
}
|
||||
|
||||
function speak(text) {
|
||||
stop(); // ensure we start fresh
|
||||
|
||||
const voice = chooseVoice();
|
||||
const chunks = chunkBySentences(text);
|
||||
|
||||
chunks.forEach((chunk, i) => {
|
||||
const u = new SpeechSynthesisUtterance(chunk);
|
||||
if (voice) u.voice = voice;
|
||||
u.rate = 1.0; // adjust to taste (0.5 - 2)
|
||||
u.pitch = 1.0; // 0 - 2
|
||||
u.onstart = () => { isSpeaking = true; btn.classList.add('speaking'); };
|
||||
u.onend = () => {
|
||||
// When the last utterance ends, clear state
|
||||
if (i === chunks.length - 1) {
|
||||
isSpeaking = false;
|
||||
btn.classList.remove('speaking');
|
||||
}
|
||||
};
|
||||
u.onerror = () => {
|
||||
isSpeaking = false;
|
||||
btn.classList.remove('speaking');
|
||||
};
|
||||
currentUtterances.push(u);
|
||||
});
|
||||
|
||||
// Queue them in order
|
||||
currentUtterances.forEach(u => synth.speak(u));
|
||||
}
|
||||
|
||||
function stop() {
|
||||
if (synth.speaking || synth.pending) synth.cancel();
|
||||
currentUtterances = [];
|
||||
isSpeaking = false;
|
||||
btn.classList.remove('speaking');
|
||||
}
|
||||
|
||||
// Click to toggle: play if idle; stop if already speaking
|
||||
btn.addEventListener('click', function(){
|
||||
if (isSpeaking || synth.speaking || synth.pending) {
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
const text = buildParagraph();
|
||||
if (text) speak(text);
|
||||
});
|
||||
|
||||
// Optional: stop if navigating away
|
||||
window.addEventListener('beforeunload', stop);
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
Loading…
Reference in New Issue
Block a user