You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
160 lines
3.8 KiB
160 lines
3.8 KiB
(function () { |
|
|
|
if (typeof Prism === 'undefined' || typeof document === 'undefined') { |
|
return; |
|
} |
|
|
|
if (!Prism.plugins.toolbar) { |
|
console.warn('Copy to Clipboard plugin loaded before Toolbar plugin.'); |
|
|
|
return; |
|
} |
|
|
|
/** |
|
* When the given elements is clicked by the user, the given text will be copied to clipboard. |
|
* |
|
* @param {HTMLElement} element |
|
* @param {CopyInfo} copyInfo |
|
* |
|
* @typedef CopyInfo |
|
* @property {() => string} getText |
|
* @property {() => void} success |
|
* @property {(reason: unknown) => void} error |
|
*/ |
|
function registerClipboard(element, copyInfo) { |
|
element.addEventListener('click', function () { |
|
copyTextToClipboard(copyInfo); |
|
}); |
|
} |
|
|
|
// https://stackoverflow.com/a/30810322/7595472 |
|
|
|
/** @param {CopyInfo} copyInfo */ |
|
function fallbackCopyTextToClipboard(copyInfo) { |
|
var textArea = document.createElement('textarea'); |
|
textArea.value = copyInfo.getText(); |
|
|
|
// Avoid scrolling to bottom |
|
textArea.style.top = '0'; |
|
textArea.style.left = '0'; |
|
textArea.style.position = 'fixed'; |
|
|
|
document.body.appendChild(textArea); |
|
textArea.focus(); |
|
textArea.select(); |
|
|
|
try { |
|
var successful = document.execCommand('copy'); |
|
setTimeout(function () { |
|
if (successful) { |
|
copyInfo.success(); |
|
} else { |
|
copyInfo.error(); |
|
} |
|
}, 1); |
|
} catch (err) { |
|
setTimeout(function () { |
|
copyInfo.error(err); |
|
}, 1); |
|
} |
|
|
|
document.body.removeChild(textArea); |
|
} |
|
/** @param {CopyInfo} copyInfo */ |
|
function copyTextToClipboard(copyInfo) { |
|
if (navigator.clipboard) { |
|
navigator.clipboard.writeText(copyInfo.getText()).then(copyInfo.success, function () { |
|
// try the fallback in case `writeText` didn't work |
|
fallbackCopyTextToClipboard(copyInfo); |
|
}); |
|
} else { |
|
fallbackCopyTextToClipboard(copyInfo); |
|
} |
|
} |
|
|
|
/** |
|
* Selects the text content of the given element. |
|
* |
|
* @param {Element} element |
|
*/ |
|
function selectElementText(element) { |
|
// https://stackoverflow.com/a/20079910/7595472 |
|
window.getSelection().selectAllChildren(element); |
|
} |
|
|
|
/** |
|
* Traverses up the DOM tree to find data attributes that override the default plugin settings. |
|
* |
|
* @param {Element} startElement An element to start from. |
|
* @returns {Settings} The plugin settings. |
|
* @typedef {Record<"copy" | "copy-error" | "copy-success" | "copy-timeout", string | number>} Settings |
|
*/ |
|
function getSettings(startElement) { |
|
/** @type {Settings} */ |
|
var settings = { |
|
'copy': 'Copy', |
|
'copy-error': 'Press Ctrl+C to copy', |
|
'copy-success': 'Copied!', |
|
'copy-timeout': 5000 |
|
}; |
|
|
|
var prefix = 'data-prismjs-'; |
|
for (var key in settings) { |
|
var attr = prefix + key; |
|
var element = startElement; |
|
while (element && !element.hasAttribute(attr)) { |
|
element = element.parentElement; |
|
} |
|
if (element) { |
|
settings[key] = element.getAttribute(attr); |
|
} |
|
} |
|
return settings; |
|
} |
|
|
|
Prism.plugins.toolbar.registerButton('copy-to-clipboard', function (env) { |
|
var element = env.element; |
|
|
|
var settings = getSettings(element); |
|
|
|
var linkCopy = document.createElement('button'); |
|
linkCopy.className = 'copy-to-clipboard-button'; |
|
linkCopy.setAttribute('type', 'button'); |
|
var linkSpan = document.createElement('span'); |
|
linkCopy.appendChild(linkSpan); |
|
|
|
setState('copy'); |
|
|
|
registerClipboard(linkCopy, { |
|
getText: function () { |
|
return element.textContent; |
|
}, |
|
success: function () { |
|
setState('copy-success'); |
|
|
|
resetText(); |
|
}, |
|
error: function () { |
|
setState('copy-error'); |
|
|
|
setTimeout(function () { |
|
selectElementText(element); |
|
}, 1); |
|
|
|
resetText(); |
|
} |
|
}); |
|
|
|
return linkCopy; |
|
|
|
function resetText() { |
|
setTimeout(function () { setState('copy'); }, settings['copy-timeout']); |
|
} |
|
|
|
/** @param {"copy" | "copy-error" | "copy-success"} state */ |
|
function setState(state) { |
|
linkSpan.textContent = settings[state]; |
|
linkCopy.setAttribute('data-copy-state', state); |
|
} |
|
}); |
|
}());
|
|
|