@@ -5,56 +5,63 @@ declare global {
55        vditorSpeechRange : Range ; 
66    } 
77} 
8+ 
89export  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