1- // ``function*`` denotes a generator in JavaScript, see 
2- // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function* 
3- function *  getHideableCopyButtonElements ( rootElement )  { 
4-     // yield all elements with the "go" (Generic.Output), 
5-     // "gp" (Generic.Prompt), or "gt" (Generic.Traceback) CSS class 
6-     for  ( const  el  of  rootElement . querySelectorAll ( '.go, .gp, .gt' ) )  { 
7-         yield  el 
8-     } 
9-     // tracebacks (.gt) contain bare text elements that need to be 
10-     // wrapped in a span to hide or show the element 
11-     for  ( let  el  of  rootElement . querySelectorAll ( '.gt' ) )  { 
12-         while  ( ( el  =  el . nextSibling )  &&  el . nodeType  !==  Node . DOCUMENT_NODE )  { 
13-             // stop wrapping text nodes when we hit the next output or 
14-             // prompt element 
15-             if  ( el . nodeType  ===  Node . ELEMENT_NODE  &&  el . matches ( ".gp, .go" ) )  { 
16-                 break 
17-             } 
18-             // if the node is a text node with content, wrap it in a 
19-             // span element so that we can control visibility 
20-             if  ( el . nodeType  ===  Node . TEXT_NODE  &&  el . textContent . trim ( ) )  { 
21-                 const  wrapper  =  document . createElement ( "span" ) 
22-                 el . after ( wrapper ) 
23-                 wrapper . appendChild ( el ) 
24-                 el  =  wrapper 
25-             } 
26-             yield  el 
1+ // Extract copyable text from the code block ignoring the 
2+ // prompts and output. 
3+ function  getCopyableText ( rootElement )  { 
4+     rootElement  =  rootElement . cloneNode ( true ) 
5+     // tracebacks (.gt) contain bare text elements that 
6+     // need to be removed 
7+     const  tracebacks  =  rootElement . querySelectorAll ( ".gt" ) 
8+     for  ( const  el  of  tracebacks )  { 
9+         while  ( 
10+             el . nextSibling  && 
11+             ( el . nextSibling . nodeType  !==  Node . DOCUMENT_NODE  || 
12+                 ! el . nextSibling . matches ( ".gp, .go" ) ) 
13+         )  { 
14+             el . nextSibling . remove ( ) 
2715        } 
2816    } 
17+     // Remove all elements with the "go" (Generic.Output), 
18+     // "gp" (Generic.Prompt), or "gt" (Generic.Traceback) CSS class 
19+     const  elements  =  rootElement . querySelectorAll ( ".gp, .go, .gt" ) 
20+     for  ( const  el  of  elements )  { 
21+         el . remove ( ) 
22+     } 
23+     return  rootElement . innerText . trim ( ) 
2924} 
3025
31- 
3226const  loadCopyButton  =  ( )  =>  { 
33-     /* Add a [>>>] button in the top-right corner of code samples to hide 
34-      * the >>> and ... prompts and the output and thus make the code 
35-      * copyable. */ 
36-     const  hide_text  =  _ ( "Hide the prompts and output" ) 
37-     const  show_text  =  _ ( "Show the prompts and output" ) 
38- 
39-     const  button  =  document . createElement ( "span" ) 
27+     const  button  =  document . createElement ( "button" ) 
4028    button . classList . add ( "copybutton" ) 
41-     button . innerText  =  ">>>" 
42-     button . title  =  hide_text 
43-     button . dataset . hidden  =  "false" 
44-     const  buttonClick  =  event  =>  { 
29+     button . type  =  "button" 
30+     button . innerText  =  _ ( "Copy" ) 
31+     button . title  =  _ ( "Copy to clipboard" ) 
32+ 
33+     const  makeOnButtonClick  =  ( )  =>  { 
34+         let  timeout  =  null 
4535        // define the behavior of the button when it's clicked 
46-         event . preventDefault ( ) 
47-         const  buttonEl  =  event . currentTarget 
48-         const  codeEl  =  buttonEl . nextElementSibling 
49-         if  ( buttonEl . dataset . hidden  ===  "false" )  { 
50-             // hide the code output 
51-             for  ( const  el  of  getHideableCopyButtonElements ( codeEl ) )  { 
52-                 el . hidden  =  true 
36+         return  async  event  =>  { 
37+             // check if the clipboard is available 
38+             if  ( ! navigator . clipboard  ||  ! navigator . clipboard . writeText )  { 
39+                 return ; 
5340            } 
54-             buttonEl . title  =  show_text 
55-             buttonEl . dataset . hidden  =  "true" 
56-         }  else  { 
57-             // show the code output 
58-             for  ( const  el  of  getHideableCopyButtonElements ( codeEl ) )  { 
59-                 el . hidden  =  false 
41+ 
42+             clearTimeout ( timeout ) 
43+             const  buttonEl  =  event . currentTarget 
44+             const  codeEl  =  buttonEl . nextElementSibling 
45+ 
46+             try  { 
47+                 await  navigator . clipboard . writeText ( getCopyableText ( codeEl ) ) 
48+             }  catch  ( e )  { 
49+                 console . error ( e . message ) 
50+                 return 
6051            } 
61-             buttonEl . title  =  hide_text 
62-             buttonEl . dataset . hidden  =  "false" 
52+ 
53+             buttonEl . innerText  =  _ ( "Copied!" ) 
54+             timeout  =  setTimeout ( ( )  =>  { 
55+                 buttonEl . innerText  =  _ ( "Copy" ) 
56+             } ,  1500 ) 
6357        } 
6458    } 
6559
@@ -78,10 +72,8 @@ const loadCopyButton = () => {
7872        // if we find a console prompt (.gp), prepend the (deeply cloned) button 
7973        const  clonedButton  =  button . cloneNode ( true ) 
8074        // the onclick attribute is not cloned, set it on the new element 
81-         clonedButton . onclick  =  buttonClick 
82-         if  ( el . querySelector ( ".gp" )  !==  null )  { 
83-             el . prepend ( clonedButton ) 
84-         } 
75+         clonedButton . onclick  =  makeOnButtonClick ( ) 
76+         el . prepend ( clonedButton ) 
8577    } ) 
8678} 
8779
0 commit comments