// Localization support const messages = { 'en': { 'copy': 'Copy', 'copy_to_clipboard': 'Copy to clipboard', 'copy_success': 'Copied!', 'copy_failure': 'Failed to copy', }, 'es' : { 'copy': 'Copiar', 'copy_to_clipboard': 'Copiar al portapapeles', 'copy_success': '¡Copiado!', 'copy_failure': 'Error al copiar', }, 'de' : { 'copy': 'Kopieren', 'copy_to_clipboard': 'In die Zwischenablage kopieren', 'copy_success': 'Kopiert!', 'copy_failure': 'Fehler beim Kopieren', }, 'fr' : { 'copy': 'Copier', 'copy_to_clipboard': 'Copier dans le presse-papier', 'copy_success': 'Copié !', 'copy_failure': 'Échec de la copie', }, 'ru': { 'copy': 'Скопировать', 'copy_to_clipboard': 'Скопировать в буфер', 'copy_success': 'Скопировано!', 'copy_failure': 'Не удалось скопировать', }, 'zh-CN': { 'copy': '复制', 'copy_to_clipboard': '复制到剪贴板', 'copy_success': '复制成功!', 'copy_failure': '复制失败', }, 'it' : { 'copy': 'Copiare', 'copy_to_clipboard': 'Copiato negli appunti', 'copy_success': 'Copiato!', 'copy_failure': 'Errore durante la copia', } } let locale = 'en' if( document.documentElement.lang !== undefined && messages[document.documentElement.lang] !== undefined ) { locale = document.documentElement.lang } let doc_url_root = DOCUMENTATION_OPTIONS.URL_ROOT; if (doc_url_root == '#') { doc_url_root = ''; } /** * SVG files for our copy buttons */ let iconCheck = ` ${messages[locale]['copy_success']} ` // If the user specified their own SVG use that, otherwise use the default let iconCopy = ``; if (!iconCopy) { iconCopy = ` ${messages[locale]['copy_to_clipboard']} ` } /** * Set up copy/paste for code blocks */ const runWhenDOMLoaded = cb => { if (document.readyState != 'loading') { cb() } else if (document.addEventListener) { document.addEventListener('DOMContentLoaded', cb) } else { document.attachEvent('onreadystatechange', function() { if (document.readyState == 'complete') cb() }) } } const codeCellId = index => `codecell${index}` // Clears selected text since ClipboardJS will select the text when copying const clearSelection = () => { if (window.getSelection) { window.getSelection().removeAllRanges() } else if (document.selection) { document.selection.empty() } } // Changes tooltip text for a moment, then changes it back // We want the timeout of our `success` class to be a bit shorter than the // tooltip and icon change, so that we can hide the icon before changing back. var timeoutIcon = 2000; var timeoutSuccessClass = 1500; const temporarilyChangeTooltip = (el, oldText, newText) => { el.setAttribute('data-tooltip', newText) el.classList.add('success') // Remove success a little bit sooner than we change the tooltip // So that we can use CSS to hide the copybutton first setTimeout(() => el.classList.remove('success'), timeoutSuccessClass) setTimeout(() => el.setAttribute('data-tooltip', oldText), timeoutIcon) } // Changes the copy button icon for two seconds, then changes it back const temporarilyChangeIcon = (el) => { el.innerHTML = iconCheck; setTimeout(() => {el.innerHTML = iconCopy}, timeoutIcon) } const addCopyButtonToCodeCells = () => { // If ClipboardJS hasn't loaded, wait a bit and try again. This // happens because we load ClipboardJS asynchronously. if (window.ClipboardJS === undefined) { setTimeout(addCopyButtonToCodeCells, 250) return } // Add copybuttons to all of our code cells const COPYBUTTON_SELECTOR = 'div.highlight pre'; const codeCells = document.querySelectorAll(COPYBUTTON_SELECTOR) codeCells.forEach((codeCell, index) => { const id = codeCellId(index) codeCell.setAttribute('id', id) const clipboardButton = id => `` codeCell.insertAdjacentHTML('afterend', clipboardButton(id)) }) function escapeRegExp(string) { return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string } /** * Removes excluded text from a Node. * * @param {Node} target Node to filter. * @param {string} exclude CSS selector of nodes to exclude. * @returns {DOMString} Text from `target` with text removed. */ function filterText(target, exclude) { const clone = target.cloneNode(true); // clone as to not modify the live DOM if (exclude) { // remove excluded nodes clone.querySelectorAll(exclude).forEach(node => node.remove()); } return clone.innerText; } // Callback when a copy button is clicked. Will be passed the node that was clicked // should then grab the text and replace pieces of text that shouldn't be used in output function formatCopyText(textContent, copybuttonPromptText, isRegexp = false, onlyCopyPromptLines = true, removePrompts = true, copyEmptyLines = true, lineContinuationChar = "", hereDocDelim = "") { var regexp; var match; // Do we check for line continuation characters and "HERE-documents"? var useLineCont = !!lineContinuationChar var useHereDoc = !!hereDocDelim // create regexp to capture prompt and remaining line if (isRegexp) { regexp = new RegExp('^(' + copybuttonPromptText + ')(.*)') } else { regexp = new RegExp('^(' + escapeRegExp(copybuttonPromptText) + ')(.*)') } const outputLines = []; var promptFound = false; var gotLineCont = false; var gotHereDoc = false; const lineGotPrompt = []; for (const line of textContent.split('\n')) { match = line.match(regexp) if (match || gotLineCont || gotHereDoc) { promptFound = regexp.test(line) lineGotPrompt.push(promptFound) if (removePrompts && promptFound) { outputLines.push(match[2]) } else { outputLines.push(line) } gotLineCont = line.endsWith(lineContinuationChar) & useLineCont if (line.includes(hereDocDelim) & useHereDoc) gotHereDoc = !gotHereDoc } else if (!onlyCopyPromptLines) { outputLines.push(line) } else if (copyEmptyLines && line.trim() === '') { outputLines.push(line) } } // If no lines with the prompt were found then just use original lines if (lineGotPrompt.some(v => v === true)) { textContent = outputLines.join('\n'); } // Remove a trailing newline to avoid auto-running when pasting if (textContent.endsWith("\n")) { textContent = textContent.slice(0, -1) } return textContent } var copyTargetText = (trigger) => { var target = document.querySelector(trigger.attributes['data-clipboard-target'].value); // get filtered text let exclude = '.linenos'; let text = filterText(target, exclude); return formatCopyText(text, '', false, true, true, true, '', '') } // Initialize with a callback so we can modify the text before copy const clipboard = new ClipboardJS('.copybtn', {text: copyTargetText}) // Update UI with error/success messages clipboard.on('success', event => { clearSelection() temporarilyChangeTooltip(event.trigger, messages[locale]['copy'], messages[locale]['copy_success']) temporarilyChangeIcon(event.trigger) }) clipboard.on('error', event => { temporarilyChangeTooltip(event.trigger, messages[locale]['copy'], messages[locale]['copy_failure']) }) } runWhenDOMLoaded(addCopyButtonToCodeCells)