A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
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};