UNPKG

3.93 kBJavaScriptView Raw
1(function () {
2
3 if (typeof Prism === 'undefined' || typeof document === 'undefined') {
4 return;
5 }
6
7 if (!Prism.plugins.toolbar) {
8 console.warn('Copy to Clipboard plugin loaded before Toolbar plugin.');
9
10 return;
11 }
12
13 /**
14 * When the given elements is clicked by the user, the given text will be copied to clipboard.
15 *
16 * @param {HTMLElement} element
17 * @param {CopyInfo} copyInfo
18 *
19 * @typedef CopyInfo
20 * @property {() => string} getText
21 * @property {() => void} success
22 * @property {(reason: unknown) => void} error
23 */
24 function registerClipboard(element, copyInfo) {
25 element.addEventListener('click', function () {
26 copyTextToClipboard(copyInfo);
27 });
28 }
29
30 // https://stackoverflow.com/a/30810322/7595472
31
32 /** @param {CopyInfo} copyInfo */
33 function fallbackCopyTextToClipboard(copyInfo) {
34 var textArea = document.createElement('textarea');
35 textArea.value = copyInfo.getText();
36
37 // Avoid scrolling to bottom
38 textArea.style.top = '0';
39 textArea.style.left = '0';
40 textArea.style.position = 'fixed';
41
42 document.body.appendChild(textArea);
43 textArea.focus();
44 textArea.select();
45
46 try {
47 var successful = document.execCommand('copy');
48 setTimeout(function () {
49 if (successful) {
50 copyInfo.success();
51 } else {
52 copyInfo.error();
53 }
54 }, 1);
55 } catch (err) {
56 setTimeout(function () {
57 copyInfo.error(err);
58 }, 1);
59 }
60
61 document.body.removeChild(textArea);
62 }
63 /** @param {CopyInfo} copyInfo */
64 function copyTextToClipboard(copyInfo) {
65 if (navigator.clipboard) {
66 navigator.clipboard.writeText(copyInfo.getText()).then(copyInfo.success, function () {
67 // try the fallback in case `writeText` didn't work
68 fallbackCopyTextToClipboard(copyInfo);
69 });
70 } else {
71 fallbackCopyTextToClipboard(copyInfo);
72 }
73 }
74
75 /**
76 * Selects the text content of the given element.
77 *
78 * @param {Element} element
79 */
80 function selectElementText(element) {
81 // https://stackoverflow.com/a/20079910/7595472
82 window.getSelection().selectAllChildren(element);
83 }
84
85 /**
86 * Traverses up the DOM tree to find data attributes that override the default plugin settings.
87 *
88 * @param {Element} startElement An element to start from.
89 * @returns {Settings} The plugin settings.
90 * @typedef {Record<"copy" | "copy-error" | "copy-success" | "copy-timeout", string | number>} Settings
91 */
92 function getSettings(startElement) {
93 /** @type {Settings} */
94 var settings = {
95 'copy': 'Copy',
96 'copy-error': 'Press Ctrl+C to copy',
97 'copy-success': 'Copied!',
98 'copy-timeout': 5000
99 };
100
101 var prefix = 'data-prismjs-';
102 for (var key in settings) {
103 var attr = prefix + key;
104 var element = startElement;
105 while (element && !element.hasAttribute(attr)) {
106 element = element.parentElement;
107 }
108 if (element) {
109 settings[key] = element.getAttribute(attr);
110 }
111 }
112 return settings;
113 }
114
115 Prism.plugins.toolbar.registerButton('copy-to-clipboard', function (env) {
116 var element = env.element;
117
118 var settings = getSettings(element);
119
120 var linkCopy = document.createElement('button');
121 linkCopy.className = 'copy-to-clipboard-button';
122 linkCopy.setAttribute('type', 'button');
123 var linkSpan = document.createElement('span');
124 linkCopy.appendChild(linkSpan);
125
126 setState('copy');
127
128 registerClipboard(linkCopy, {
129 getText: function () {
130 return element.textContent;
131 },
132 success: function () {
133 setState('copy-success');
134
135 resetText();
136 },
137 error: function () {
138 setState('copy-error');
139
140 setTimeout(function () {
141 selectElementText(element);
142 }, 1);
143
144 resetText();
145 }
146 });
147
148 return linkCopy;
149
150 function resetText() {
151 setTimeout(function () { setState('copy'); }, settings['copy-timeout']);
152 }
153
154 /** @param {"copy" | "copy-error" | "copy-success"} state */
155 function setState(state) {
156 linkSpan.textContent = settings[state];
157 linkCopy.setAttribute('data-copy-state', state);
158 }
159 });
160}());