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" )
4028button . 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
7973const 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