|
@@ -5,56 +5,63 @@ declare global {
|
|
|
vditorSpeechRange: Range;
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
export const speechRender = (element: HTMLElement, lang: keyof II18n = "zh_CN") => {
|
|
|
if (typeof speechSynthesis === "undefined" || typeof SpeechSynthesisUtterance === "undefined") {
|
|
|
return;
|
|
|
}
|
|
|
+
|
|
|
+ const getVoice = () => {
|
|
|
+ const voices = speechSynthesis.getVoices();
|
|
|
+ let currentVoice: SpeechSynthesisVoice;
|
|
|
+ let defaultVoice;
|
|
|
+ voices.forEach((item) => {
|
|
|
+ if (item.lang === lang.replace("_", "-")) {
|
|
|
+ currentVoice = item;
|
|
|
+ }
|
|
|
+ if (item.default) {
|
|
|
+ defaultVoice = item;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ if (!currentVoice) {
|
|
|
+ currentVoice = defaultVoice;
|
|
|
+ }
|
|
|
+ return currentVoice;
|
|
|
+ };
|
|
|
+
|
|
|
let playSVG = '<svg><use xlink:href="#vditor-icon-play"></use></svg>';
|
|
|
let pauseSVG = '<svg><use xlink:href="#vditor-icon-pause"></use></svg>';
|
|
|
if (!document.getElementById("vditorIconScript")) {
|
|
|
playSVG = '<svg viewBox="0 0 32 32"><path d="M3.436 0l25.128 16-25.128 16v-32z"></path></svg>';
|
|
|
pauseSVG = '<svg viewBox="0 0 32 32"><path d="M20.617 0h9.128v32h-9.128v-32zM2.255 32v-32h9.128v32h-9.128z"></path></svg>';
|
|
|
}
|
|
|
- let speechDom: HTMLDivElement = document.querySelector(".vditor-speech");
|
|
|
+
|
|
|
+ let speechDom: HTMLButtonElement = document.querySelector(".vditor-speech");
|
|
|
if (!speechDom) {
|
|
|
- speechDom = document.createElement("div");
|
|
|
+ speechDom = document.createElement("button");
|
|
|
speechDom.className = "vditor-speech";
|
|
|
- document.body.insertAdjacentElement("beforeend", speechDom);
|
|
|
-
|
|
|
- const getVoice = () => {
|
|
|
- const voices = speechSynthesis.getVoices();
|
|
|
- let currentVoice;
|
|
|
- let defaultVoice;
|
|
|
- voices.forEach((item) => {
|
|
|
- if (item.lang === lang.replace("_", "-")) {
|
|
|
- currentVoice = item;
|
|
|
- }
|
|
|
- if (item.default) {
|
|
|
- defaultVoice = item;
|
|
|
- }
|
|
|
- });
|
|
|
- if (!currentVoice) {
|
|
|
- currentVoice = defaultVoice;
|
|
|
- }
|
|
|
- return currentVoice;
|
|
|
- };
|
|
|
-
|
|
|
+ element.insertAdjacentElement("beforeend", speechDom);
|
|
|
if (speechSynthesis.onvoiceschanged !== undefined) {
|
|
|
speechSynthesis.onvoiceschanged = getVoice;
|
|
|
}
|
|
|
+ }
|
|
|
+ const voice = getVoice();
|
|
|
+ const utterThis = new SpeechSynthesisUtterance();
|
|
|
+ utterThis.voice = voice;
|
|
|
+ utterThis.onend = utterThis.onerror = () => {
|
|
|
+ speechDom.style.display = "none";
|
|
|
+ speechSynthesis.cancel();
|
|
|
+ speechDom.classList.remove("vditor-speech--current");
|
|
|
+ speechDom.innerHTML = playSVG;
|
|
|
+ };
|
|
|
|
|
|
- const voice = getVoice();
|
|
|
- speechDom.onclick = () => {
|
|
|
- if (speechDom.className === "vditor-speech") {
|
|
|
- const utterThis = new SpeechSynthesisUtterance(speechDom.getAttribute("data-text"));
|
|
|
- utterThis.voice = voice;
|
|
|
- utterThis.onend = () => {
|
|
|
- speechDom.className = "vditor-speech";
|
|
|
- speechSynthesis.cancel();
|
|
|
- speechDom.innerHTML = playSVG;
|
|
|
- };
|
|
|
+ element.addEventListener(window.ontouchstart !== undefined ? "touchend" : "click", (event) => {
|
|
|
+ const target = event.target as HTMLElement
|
|
|
+ if (target.classList.contains("vditor-speech") || target.parentElement.classList.contains("vditor-speech")) {
|
|
|
+ if (!speechDom.classList.contains("vditor-speech--current")) {
|
|
|
+ utterThis.text = speechDom.getAttribute("data-text");
|
|
|
speechSynthesis.speak(utterThis);
|
|
|
- speechDom.className = "vditor-speech vditor-speech--current";
|
|
|
+ speechDom.classList.add("vditor-speech--current");
|
|
|
speechDom.innerHTML = pauseSVG;
|
|
|
} else {
|
|
|
if (speechSynthesis.speaking) {
|
|
@@ -67,35 +74,34 @@ export const speechRender = (element: HTMLElement, lang: keyof II18n = "zh_CN")
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
setSelectionFocus(window.vditorSpeechRange);
|
|
|
- };
|
|
|
-
|
|
|
- document.body.addEventListener("click", () => {
|
|
|
- if (getSelection().toString().trim() === "" && speechDom.style.display === "block") {
|
|
|
- speechDom.className = "vditor-speech";
|
|
|
- speechSynthesis.cancel();
|
|
|
- speechDom.style.display = "none";
|
|
|
- }
|
|
|
- });
|
|
|
- }
|
|
|
+ element.focus();
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
- element.addEventListener("mouseup", (event: MouseEvent) => {
|
|
|
- const text = getSelection().toString().trim();
|
|
|
+ speechDom.style.display = "none";
|
|
|
speechSynthesis.cancel();
|
|
|
- if (getSelection().toString().trim() === "") {
|
|
|
- if (speechDom.style.display === "block") {
|
|
|
- speechDom.className = "vditor-speech";
|
|
|
- speechDom.style.display = "none";
|
|
|
- }
|
|
|
+ speechDom.classList.remove("vditor-speech--current");
|
|
|
+ speechDom.innerHTML = playSVG;
|
|
|
+
|
|
|
+ if (getSelection().rangeCount === 0) {
|
|
|
return;
|
|
|
}
|
|
|
- window.vditorSpeechRange = getSelection().getRangeAt(0).cloneRange();
|
|
|
- const rect = getSelection().getRangeAt(0).getBoundingClientRect();
|
|
|
+ const range = getSelection().getRangeAt(0)
|
|
|
+ const text = range.toString().trim();
|
|
|
+ if (!text) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ window.vditorSpeechRange = range.cloneRange();
|
|
|
+ const rect = range.getBoundingClientRect();
|
|
|
speechDom.innerHTML = playSVG;
|
|
|
speechDom.style.display = "block";
|
|
|
speechDom.style.top = (rect.top + rect.height + document.querySelector("html").scrollTop - 20) + "px";
|
|
|
- speechDom.style.left = (event.clientX + 2) + "px";
|
|
|
+ if (window.ontouchstart !== undefined) {
|
|
|
+ speechDom.style.left = ((event as TouchEvent).changedTouches[(event as TouchEvent).changedTouches.length - 1].pageX + 2) + "px";
|
|
|
+ } else {
|
|
|
+ speechDom.style.left = ((event as MouseEvent).clientX + 2) + "px";
|
|
|
+ }
|
|
|
speechDom.setAttribute("data-text", text);
|
|
|
});
|
|
|
};
|