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 ( )
27
15
}
28
16
}
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 ( )
29
24
}
30
25
31
-
32
26
const 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" )
40
28
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
45
35
// 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 ;
53
40
}
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
60
51
}
61
- buttonEl . title = hide_text
62
- buttonEl . dataset . hidden = "false"
52
+
53
+ buttonEl . innerText = _ ( "Copied!" )
54
+ timeout = setTimeout ( ( ) => {
55
+ buttonEl . innerText = _ ( "Copy" )
56
+ } , 1500 )
63
57
}
64
58
}
65
59
@@ -78,10 +72,8 @@ const loadCopyButton = () => {
78
72
// if we find a console prompt (.gp), prepend the (deeply cloned) button
79
73
const clonedButton = button . cloneNode ( true )
80
74
// 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 )
85
77
} )
86
78
}
87
79
0 commit comments