1
+ /* eslint-disable @typescript-eslint/no-misused-promises */
2
+
1
3
/**
2
- * Workaround code for cases in which the Clipboard API is not available.
4
+ * Fallback approach to copy text to the clipboard in case the Clipboard API is not available.
3
5
*/
4
- const domCopy = [
5
- // Define copy function
6
- `function domCopy(text) {` ,
6
+ function domCopy ( text : string ) {
7
7
// Create a new DOM element to copy from and append it to the document,
8
8
// but make sure it's not visible and does not cause reflow
9
- `let n = document.createElement('pre');
10
- Object.assign(n .style, {
9
+ const pre = document . createElement ( 'pre' )
10
+ Object . assign ( pre . style , {
11
11
opacity : '0' ,
12
12
pointerEvents : 'none' ,
13
13
position : 'absolute' ,
@@ -17,112 +17,98 @@ const domCopy = [
17
17
width : '20px' ,
18
18
height : '20px' ,
19
19
webkitUserSelect : 'auto' ,
20
- userSelect: 'all'
21
- });
22
- n.ariaHidden = 'true';
23
- n.textContent = text;
24
- document.body.appendChild(n);` ,
20
+ userSelect : 'all' ,
21
+ } )
22
+ pre . ariaHidden = 'true'
23
+ pre . textContent = text
24
+ document . body . appendChild ( pre )
25
+
25
26
// Select the DOM element's contents
26
- `let r = document.createRange();
27
- r.selectNode(n);
28
- let s = getSelection();
29
- s.removeAllRanges();
30
- s.addRange(r);` ,
27
+ const range = document . createRange ( )
28
+ range . selectNode ( pre )
29
+ const selection = getSelection ( )
30
+ if ( ! selection ) return false
31
+ selection . removeAllRanges ( )
32
+ selection . addRange ( range )
33
+
31
34
// Copy the selection to the clipboard
32
- ` let ok = false;
35
+ let ok = false
33
36
try {
34
- ok = document.execCommand('copy');
37
+ ok = document . execCommand ( 'copy' )
35
38
} finally {
36
- s .removeAllRanges();
37
- document.body.removeChild(n);
39
+ selection . removeAllRanges ( )
40
+ document . body . removeChild ( pre )
38
41
}
39
- return ok;` ,
40
- // End of function body
41
- `}` ,
42
- ]
42
+ return ok
43
+ }
43
44
44
45
/**
45
46
* Function to handle clicks on a single copy button.
46
47
*/
47
- const clickHandler = [
48
- // Define click handler function
49
- `async function clickHandler(event) {` ,
50
- // Attempt to perform copy operation, first using the Clipboard API,
48
+ async function clickHandler ( event : Event ) {
49
+ // Attempt to perform the copy operation, first using the Clipboard API,
51
50
// and then falling back to a DOM-based approach
52
- `let btn = event.currentTarget;
53
- let ok = false;
54
- let code = btn.dataset.code.replace(/\\u007f/g, '\\n');
51
+ const button = event . currentTarget as HTMLButtonElement
52
+ const dataset = button . dataset as { code : string ; copied : string }
53
+ let ok = false
54
+ const code = dataset . code . replace ( / \u007f / g, '\n' )
55
55
try {
56
- await navigator.clipboard.writeText(code);
57
- ok = true;
56
+ await navigator . clipboard . writeText ( code )
57
+ ok = true
58
58
} catch ( err ) {
59
- ok = domCopy(code);
60
- }` ,
59
+ ok = domCopy ( code )
60
+ }
61
+
61
62
// Exit if the copy operation failed or there is already a tooltip present
62
- `if (!ok || btn.parentNode.querySelector('.feedback')) return;` ,
63
+ if ( ! ok || button . parentNode ?. querySelector ( '.feedback' ) ) return
64
+
63
65
// Show feedback tooltip
64
- `let tt = document.createElement('div');
65
- tt.classList.add('feedback');
66
- tt.append(btn.dataset.copied);
67
- btn.before(tt);` ,
66
+ let tooltip : HTMLDivElement | undefined = document . createElement ( 'div' )
67
+ tooltip . classList . add ( 'feedback' )
68
+ tooltip . append ( dataset . copied )
69
+ button . before ( tooltip )
70
+
68
71
// Use offsetWidth and requestAnimationFrame to opt out of DOM batching,
69
72
// which helps to ensure that the transition on 'show' works
70
- `tt.offsetWidth;
71
- requestAnimationFrame(() => tt.classList.add('show'));` ,
73
+ tooltip . offsetWidth
74
+ requestAnimationFrame ( ( ) => tooltip ?. classList . add ( 'show' ) )
75
+
72
76
// Hide & remove the tooltip again when we no longer need it
73
- `let h = () => !tt || tt .classList.remove('show');
74
- let r = () => {
75
- if (!(!tt || parseFloat(getComputedStyle(tt ).opacity) > 0)) {
76
- tt .remove();
77
- tt = null;
77
+ const hideTooltip = ( ) => ! tooltip || tooltip . classList . remove ( 'show' )
78
+ const removeTooltip = ( ) => {
79
+ if ( ! ( ! tooltip || parseFloat ( getComputedStyle ( tooltip ) . opacity ) > 0 ) ) {
80
+ tooltip . remove ( )
81
+ tooltip = undefined
78
82
}
79
- };
80
- setTimeout(h, 1500);
81
- setTimeout(r, 2500);
82
- btn.addEventListener('blur', h);
83
- tt.addEventListener('transitioncancel', r);
84
- tt.addEventListener('transitionend', r);` ,
85
- // End of function body
86
- `}` ,
87
- ]
88
-
89
- /**
90
- * Code to initialize all copy buttons on the page.
91
- *
92
- * It first attaches the click handler to all buttons that exist on the page right now,
93
- * and then registers a MutationObserver that handles any new buttons added later
94
- * (e.g. when the page dynamically loads more content or replaces existing content).
95
- */
96
- const attachHandlers = [
97
- // Define a function that searches a node for matching buttons and initializes them
98
- // unless the node does not support querySelectorAll (e.g. a text node)
99
- `let initButtons = n => !n.querySelectorAll || n.querySelectorAll('[SELECTOR]').forEach(btn =>
100
- btn.addEventListener('click', clickHandler)
101
- );` ,
102
- // Use the function to initialize all buttons that exist right now
103
- `initButtons(document);` ,
104
- // Register a MutationObserver to initialize any new buttons added later
105
- `let obs = new MutationObserver(ms =>
106
- ms.forEach(m =>
107
- m.addedNodes.forEach(n =>
108
- initButtons(n)
109
- )
110
- )
111
- );
112
- obs.observe(document.body, { childList: true, subtree: true });` ,
113
- // Also re-initialize all buttons after view transitions initiated by popular frameworks
114
- `document.addEventListener('astro:page-load', () => initButtons(document));` ,
115
- ]
83
+ }
84
+ setTimeout ( hideTooltip , 1500 )
85
+ setTimeout ( removeTooltip , 2500 )
86
+ button . addEventListener ( 'blur' , hideTooltip )
87
+ tooltip . addEventListener ( 'transitioncancel' , removeTooltip )
88
+ tooltip . addEventListener ( 'transitionend' , removeTooltip )
89
+ }
116
90
117
- export const getCopyJsModule = ( buttonSelector : string ) => {
118
- return [ ...domCopy , ...clickHandler , ...attachHandlers ]
119
- . map ( ( line ) =>
120
- line
121
- . trim ( )
122
- . replace ( / \s * [ \r \n ] \s * / g, '' )
123
- . replace ( / \s * ( [: ; , = { } ( ) < > ] ) \s * / g, '$1' )
124
- . replace ( / ; } / g, '}' )
125
- )
126
- . join ( '' )
127
- . replace ( / \[ S E L E C T O R \] / g, buttonSelector )
91
+ // Define a function that searches a node for matching buttons and initializes them
92
+ // unless the node does not support querySelectorAll (e.g. a text node)
93
+ const initButtons = ( container : ParentNode | Document ) => {
94
+ if ( ! container . querySelectorAll ) return
95
+ container . querySelectorAll ( '[SELECTOR]' ) . forEach ( ( btn ) => btn . addEventListener ( 'click' , clickHandler ) )
128
96
}
97
+
98
+ // Use the function to initialize all buttons that exist right now
99
+ initButtons ( document )
100
+
101
+ // Register a MutationObserver to initialize any new buttons added later
102
+ const newButtonsObserver = new MutationObserver ( ( mutations ) =>
103
+ mutations . forEach ( ( mutation ) =>
104
+ mutation . addedNodes . forEach ( ( node ) => {
105
+ initButtons ( node as ParentNode )
106
+ } )
107
+ )
108
+ )
109
+ newButtonsObserver . observe ( document . body , { childList : true , subtree : true } )
110
+
111
+ // Also re-initialize all buttons after view transitions initiated by popular frameworks
112
+ document . addEventListener ( 'astro:page-load' , ( ) => {
113
+ initButtons ( document )
114
+ } )
0 commit comments