Skip to content

Commit 80d22da

Browse files
committed
🎨 fix #1611
1 parent c9b3603 commit 80d22da

File tree

1 file changed

+60
-54
lines changed

1 file changed

+60
-54
lines changed

src/ts/markdown/speechRender.ts

Lines changed: 60 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -5,56 +5,63 @@ declare global {
55
vditorSpeechRange: Range;
66
}
77
}
8+
89
export const speechRender = (element: HTMLElement, lang: keyof II18n = "zh_CN") => {
910
if (typeof speechSynthesis === "undefined" || typeof SpeechSynthesisUtterance === "undefined") {
1011
return;
1112
}
13+
14+
const getVoice = () => {
15+
const voices = speechSynthesis.getVoices();
16+
let currentVoice: SpeechSynthesisVoice;
17+
let defaultVoice;
18+
voices.forEach((item) => {
19+
if (item.lang === lang.replace("_", "-")) {
20+
currentVoice = item;
21+
}
22+
if (item.default) {
23+
defaultVoice = item;
24+
}
25+
});
26+
if (!currentVoice) {
27+
currentVoice = defaultVoice;
28+
}
29+
return currentVoice;
30+
};
31+
1232
let playSVG = '<svg><use xlink:href="#vditor-icon-play"></use></svg>';
1333
let pauseSVG = '<svg><use xlink:href="#vditor-icon-pause"></use></svg>';
1434
if (!document.getElementById("vditorIconScript")) {
1535
playSVG = '<svg viewBox="0 0 32 32"><path d="M3.436 0l25.128 16-25.128 16v-32z"></path></svg>';
1636
pauseSVG = '<svg viewBox="0 0 32 32"><path d="M20.617 0h9.128v32h-9.128v-32zM2.255 32v-32h9.128v32h-9.128z"></path></svg>';
1737
}
18-
let speechDom: HTMLDivElement = document.querySelector(".vditor-speech");
38+
39+
let speechDom: HTMLButtonElement = document.querySelector(".vditor-speech");
1940
if (!speechDom) {
20-
speechDom = document.createElement("div");
41+
speechDom = document.createElement("button");
2142
speechDom.className = "vditor-speech";
22-
document.body.insertAdjacentElement("beforeend", speechDom);
23-
24-
const getVoice = () => {
25-
const voices = speechSynthesis.getVoices();
26-
let currentVoice;
27-
let defaultVoice;
28-
voices.forEach((item) => {
29-
if (item.lang === lang.replace("_", "-")) {
30-
currentVoice = item;
31-
}
32-
if (item.default) {
33-
defaultVoice = item;
34-
}
35-
});
36-
if (!currentVoice) {
37-
currentVoice = defaultVoice;
38-
}
39-
return currentVoice;
40-
};
41-
43+
element.insertAdjacentElement("beforeend", speechDom);
4244
if (speechSynthesis.onvoiceschanged !== undefined) {
4345
speechSynthesis.onvoiceschanged = getVoice;
4446
}
47+
}
48+
const voice = getVoice();
49+
const utterThis = new SpeechSynthesisUtterance();
50+
utterThis.voice = voice;
51+
utterThis.onend = utterThis.onerror = () => {
52+
speechDom.style.display = "none";
53+
speechSynthesis.cancel();
54+
speechDom.classList.remove("vditor-speech--current");
55+
speechDom.innerHTML = playSVG;
56+
};
4557

46-
const voice = getVoice();
47-
speechDom.onclick = () => {
48-
if (speechDom.className === "vditor-speech") {
49-
const utterThis = new SpeechSynthesisUtterance(speechDom.getAttribute("data-text"));
50-
utterThis.voice = voice;
51-
utterThis.onend = () => {
52-
speechDom.className = "vditor-speech";
53-
speechSynthesis.cancel();
54-
speechDom.innerHTML = playSVG;
55-
};
58+
element.addEventListener(window.ontouchstart !== undefined ? "touchend" : "click", (event) => {
59+
const target = event.target as HTMLElement
60+
if (target.classList.contains("vditor-speech") || target.parentElement.classList.contains("vditor-speech")) {
61+
if (!speechDom.classList.contains("vditor-speech--current")) {
62+
utterThis.text = speechDom.getAttribute("data-text");
5663
speechSynthesis.speak(utterThis);
57-
speechDom.className = "vditor-speech vditor-speech--current";
64+
speechDom.classList.add("vditor-speech--current");
5865
speechDom.innerHTML = pauseSVG;
5966
} else {
6067
if (speechSynthesis.speaking) {
@@ -67,35 +74,34 @@ export const speechRender = (element: HTMLElement, lang: keyof II18n = "zh_CN")
6774
}
6875
}
6976
}
70-
7177
setSelectionFocus(window.vditorSpeechRange);
72-
};
73-
74-
document.body.addEventListener("click", () => {
75-
if (getSelection().toString().trim() === "" && speechDom.style.display === "block") {
76-
speechDom.className = "vditor-speech";
77-
speechSynthesis.cancel();
78-
speechDom.style.display = "none";
79-
}
80-
});
81-
}
78+
element.focus();
79+
return;
80+
}
8281

83-
element.addEventListener("mouseup", (event: MouseEvent) => {
84-
const text = getSelection().toString().trim();
82+
speechDom.style.display = "none";
8583
speechSynthesis.cancel();
86-
if (getSelection().toString().trim() === "") {
87-
if (speechDom.style.display === "block") {
88-
speechDom.className = "vditor-speech";
89-
speechDom.style.display = "none";
90-
}
84+
speechDom.classList.remove("vditor-speech--current");
85+
speechDom.innerHTML = playSVG;
86+
87+
if (getSelection().rangeCount === 0) {
9188
return;
9289
}
93-
window.vditorSpeechRange = getSelection().getRangeAt(0).cloneRange();
94-
const rect = getSelection().getRangeAt(0).getBoundingClientRect();
90+
const range = getSelection().getRangeAt(0)
91+
const text = range.toString().trim();
92+
if (!text) {
93+
return;
94+
}
95+
window.vditorSpeechRange = range.cloneRange();
96+
const rect = range.getBoundingClientRect();
9597
speechDom.innerHTML = playSVG;
9698
speechDom.style.display = "block";
9799
speechDom.style.top = (rect.top + rect.height + document.querySelector("html").scrollTop - 20) + "px";
98-
speechDom.style.left = (event.clientX + 2) + "px";
100+
if (window.ontouchstart !== undefined) {
101+
speechDom.style.left = ((event as TouchEvent).changedTouches[(event as TouchEvent).changedTouches.length - 1].pageX + 2) + "px";
102+
} else {
103+
speechDom.style.left = ((event as MouseEvent).clientX + 2) + "px";
104+
}
99105
speechDom.setAttribute("data-text", text);
100106
});
101107
};

0 commit comments

Comments
 (0)