A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
at lambda-fork/main 97 lines 3.8 kB view raw
1import {focusByRange} from "../util/selection"; 2 3declare global { 4 interface Window { 5 protyleSpeechRange: Range; 6 } 7} 8export const speechRender = (element: HTMLElement, lang: string) => { 9 if (typeof speechSynthesis === "undefined" || typeof SpeechSynthesisUtterance === "undefined") { 10 return; 11 } 12 const playSVG = '<svg><use xlink:href="#iconPlay"></use></svg>'; 13 const pauseSVG = '<svg><use xlink:href="#iconPause"></use></svg>'; 14 let speechDom: HTMLDivElement = document.querySelector(".protyle-speech"); 15 if (!speechDom) { 16 speechDom = document.createElement("div"); 17 speechDom.className = "protyle-speech"; 18 document.body.insertAdjacentElement("beforeend", speechDom); 19 20 const getVoice = () => { 21 const voices = speechSynthesis.getVoices(); 22 let currentVoice; 23 let defaultVoice; 24 voices.forEach((item) => { 25 if (item.lang === lang.replace("_", "-")) { 26 currentVoice = item; 27 } 28 if (item.default) { 29 defaultVoice = item; 30 } 31 }); 32 if (!currentVoice) { 33 currentVoice = defaultVoice; 34 } 35 return currentVoice; 36 }; 37 38 if (speechSynthesis.onvoiceschanged !== undefined) { 39 speechSynthesis.onvoiceschanged = getVoice; 40 } 41 42 const voice = getVoice(); 43 speechDom.onclick = () => { 44 if (speechDom.className === "protyle-speech") { 45 const utterThis = new SpeechSynthesisUtterance(speechDom.getAttribute("data-text")); 46 utterThis.voice = voice; 47 utterThis.onend = () => { 48 speechDom.className = "protyle-speech"; 49 speechSynthesis.cancel(); 50 speechDom.innerHTML = playSVG; 51 }; 52 speechSynthesis.speak(utterThis); 53 speechDom.className = "protyle-speech protyle-speech--current"; 54 speechDom.innerHTML = pauseSVG; 55 } else { 56 if (speechSynthesis.speaking) { 57 if (speechSynthesis.paused) { 58 speechSynthesis.resume(); 59 speechDom.innerHTML = pauseSVG; 60 } else { 61 speechSynthesis.pause(); 62 speechDom.innerHTML = playSVG; 63 } 64 } 65 } 66 67 focusByRange(window.protyleSpeechRange); 68 }; 69 70 document.body.addEventListener("click", () => { 71 if (getSelection().toString().trim() === "" && speechDom.style.display === "block") { 72 speechDom.className = "protyle-speech"; 73 speechSynthesis.cancel(); 74 speechDom.style.display = "none"; 75 } 76 }); 77 } 78 79 element.addEventListener("mouseup", (event: MouseEvent) => { 80 const text = getSelection().toString().trim(); 81 speechSynthesis.cancel(); 82 if (getSelection().toString().trim() === "") { 83 if (speechDom.style.display === "block") { 84 speechDom.className = "protyle-speech"; 85 speechDom.style.display = "none"; 86 } 87 return; 88 } 89 window.protyleSpeechRange = getSelection().getRangeAt(0).cloneRange(); 90 const rect = getSelection().getRangeAt(0).getBoundingClientRect(); 91 speechDom.innerHTML = playSVG; 92 speechDom.style.display = "block"; 93 speechDom.style.top = (rect.top + rect.height + document.querySelector("html").scrollTop - 20) + "px"; 94 speechDom.style.left = (event.screenX + 2) + "px"; 95 speechDom.setAttribute("data-text", text); 96 }); 97};