PageSourceSearch

https://docs.jax.dev/en/latest/_static/copybutton.js?v=30646c52

js jax.dev collected 2026-09-24 09:11:18 UTC 8,529 bytes, 248 lines download raw bytes

1// Localization support
2const messages = {
3  'en': {
4    'copy': 'Copy',
5    'copy_to_clipboard': 'Copy to clipboard',
6    'copy_success': 'Copied!',
7    'copy_failure': 'Failed to copy',
8  },
9  'es' : {
10    'copy': 'Copiar',
11    'copy_to_clipboard': 'Copiar al portapapeles',
12    'copy_success': '¡Copiado!',
13    'copy_failure': 'Error al copiar',
14  },
15  'de' : {
16    'copy': 'Kopieren',
17    'copy_to_clipboard': 'In die Zwischenablage kopieren',
18    'copy_success': 'Kopiert!',
19    'copy_failure': 'Fehler beim Kopieren',
20  },
21  'fr' : {
22    'copy': 'Copier',
23    'copy_to_clipboard': 'Copier dans le presse-papier',
24    'copy_success': 'Copié !',
25    'copy_failure': 'Échec de la copie',
26  },
27  'ru': {
28    'copy': 'Скопировать',
29    'copy_to_clipboard': 'Скопировать в буфер',
30    'copy_success': 'Скопировано!',
31    'copy_failure': 'Не удалось скопировать',
32  },
33  'zh-CN': {
34    'copy': '复制',
35    'copy_to_clipboard': '复制到剪贴板',
36    'copy_success': '复制成功!',
37    'copy_failure': '复制失败',
38  },
39  'it' : {
40    'copy': 'Copiare',
41    'copy_to_clipboard': 'Copiato negli appunti',
42    'copy_success': 'Copiato!',
43    'copy_failure': 'Errore durante la copia',
44  }
45}
46
47let locale = 'en'
48if( document.documentElement.lang !== undefined
49    && messages[document.documentElement.lang] !== undefined ) {
50  locale = document.documentElement.lang
51}
52
53let doc_url_root = DOCUMENTATION_OPTIONS.URL_ROOT;
54if (doc_url_root == '#') {
55    doc_url_root = '';
56}
57
58/**
59 * SVG files for our copy buttons
60 */
61let iconCheck = `<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-check" width="44" height="44" viewBox="0 0 24 24" stroke-width="2" stroke="#22863a" fill="none" stroke-linecap="round" stroke-linejoin="round">
62  <title>${messages[locale]['copy_success']}</title>
63  <path stroke="none" d="M0 0h24v24H0z" fill="none"/>
64  <path d="M5 12l5 5l10 -10" />
65</svg>`
66
67// If the user specified their own SVG use that, otherwise use the default
68let iconCopy = ``;
69if (!iconCopy) {
70  iconCopy = `<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-copy" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="#000000" fill="none" stroke-linecap="round" stroke-linejoin="round">
71  <title>${messages[locale]['copy_to_clipboard']}</title>
72  <path stroke="none" d="M0 0h24v24H0z" fill="none"/>
73  <rect x="8" y="8" width="12" height="12" rx="2" />
74  <path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2" />
75</svg>`
76}
77
78/**
79 * Set up copy/paste for code blocks
80 */
81
82const runWhenDOMLoaded = cb => {
83  if (document.readyState != 'loading') {
84    cb()
85  } else if (document.addEventListener) {
86    document.addEventListener('DOMContentLoaded', cb)
87  } else {
88    document.attachEvent('onreadystatechange', function() {
89      if (document.readyState == 'complete') cb()
90    })
91  }
92}
93
94const codeCellId = index => `codecell${index}`
95
96// Clears selected text since ClipboardJS will select the text when copying
97const clearSelection = () => {
98  if (window.getSelection) {
99    window.getSelection().removeAllRanges()
100  } else if (document.selection) {
101    document.selection.empty()
102  }
103}
104
105// Changes tooltip text for a moment, then changes it back
106// We want the timeout of our `success` class to be a bit shorter than the
107// tooltip and icon change, so that we can hide the icon before changing back.
108var timeoutIcon = 2000;
109var timeoutSuccessClass = 1500;
110
111const temporarilyChangeTooltip = (el, oldText, newText) => {
112  el.setAttribute('data-tooltip', newText)
113  el.classList.add('success')
114  // Remove success a little bit sooner than we change the tooltip
115  // So that we can use CSS to hide the copybutton first
116  setTimeout(() => el.classList.remove('success'), timeoutSuccessClass)
117  setTimeout(() => el.setAttribute('data-tooltip', oldText), timeoutIcon)
118}
119
120// Changes the copy button icon for two seconds, then changes it back
121const temporarilyChangeIcon = (el) => {
122  el.innerHTML = iconCheck;
123  setTimeout(() => {el.innerHTML = iconCopy}, timeoutIcon)
124}
125
126const addCopyButtonToCodeCells = () => {
127  // If ClipboardJS hasn't loaded, wait a bit and try again. This
128  // happens because we load ClipboardJS asynchronously.
129  if (window.ClipboardJS === undefined) {
130    setTimeout(addCopyButtonToCodeCells, 250)
131    return
132  }
133
134  // Add copybuttons to all of our code cells
135  const COPYBUTTON_SELECTOR = 'div.highlight pre';
136  const codeCells = document.querySelectorAll(COPYBUTTON_SELECTOR)
137  codeCells.forEach((codeCell, index) => {
138    const id = codeCellId(index)
139    codeCell.setAttribute('id', id)
140
141    const clipboardButton = id =>
142    `<button class="copybtn o-tooltip--left" data-tooltip="${messages[locale]['copy']}" data-clipboard-target="#${id}">
143      ${iconCopy}
144    </button>`
145    codeCell.insertAdjacentHTML('afterend', clipboardButton(id))
146  })
147
148function escapeRegExp(string) {
149    return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
150}
151
152/**
153 * Removes excluded text from a Node.
154 *
155 * @param {Node} target Node to filter.
156 * @param {string} exclude CSS selector of nodes to exclude.
157 * @returns {DOMString} Text from `target` with text removed.
158 */
159function filterText(target, exclude) {
160    const clone = target.cloneNode(true);  // clone as to not modify the live DOM
161    if (exclude) {
162        // remove excluded nodes
163        clone.querySelectorAll(exclude).forEach(node => node.remove());
164    }
165    return clone.innerText;
166}
167
168// Callback when a copy button is clicked. Will be passed the node that was clicked
169// should then grab the text and replace pieces of text that shouldn't be used in output
170function formatCopyText(textContent, copybuttonPromptText, isRegexp = false, onlyCopyPromptLines = true, removePrompts = true, copyEmptyLines = true, lineContinuationChar = "", hereDocDelim = "") {
171    var regexp;
172    var match;
173
174    // Do we check for line continuation characters and "HERE-documents"?
175    var useLineCont = !!lineContinuationChar
176    var useHereDoc = !!hereDocDelim
177
178    // create regexp to capture prompt and remaining line
179    if (isRegexp) {
180        regexp = new RegExp('^(' + copybuttonPromptText + ')(.*)')
181    } else {
182        regexp = new RegExp('^(' + escapeRegExp(copybuttonPromptText) + ')(.*)')
183    }
184
185    const outputLines = [];
186    var promptFound = false;
187    var gotLineCont = false;
188    var gotHereDoc = false;
189    const lineGotPrompt = [];
190    for (const line of textContent.split('\n')) {
191        match = line.match(regexp)
192        if (match || gotLineCont || gotHereDoc) {
193            promptFound = regexp.test(line)
194            lineGotPrompt.push(promptFound)
195            if (removePrompts && promptFound) {
196                outputLines.push(match[2])
197            } else {
198                outputLines.push(line)
199            }
200            gotLineCont = line.endsWith(lineContinuationChar) & useLineCont
201            if (line.includes(hereDocDelim) & useHereDoc)
202                gotHereDoc = !gotHereDoc
203        } else if (!onlyCopyPromptLines) {
204            outputLines.push(line)
205        } else if (copyEmptyLines && line.trim() === '') {
206            outputLines.push(line)
207        }
208    }
209
210    // If no lines with the prompt were found then just use original lines
211    if (lineGotPrompt.some(v => v === true)) {
212        textContent = outputLines.join('\n');
213    }
214
215    // Remove a trailing newline to avoid auto-running when pasting
216    if (textContent.endsWith("\n")) {
217        textContent = textContent.slice(0, -1)
218    }
219    return textContent
220}
221
222
223var copyTargetText = (trigger) => {
224  var target = document.querySelector(trigger.attributes['data-clipboard-target'].value);
225
226  // get filtered text
227  let exclude = '.linenos';
228
229  let text = filterText(target, exclude);
230  return formatCopyText(text, '>>> |\\.\\.\\. |\\$ |In \\[\\d*\\]: | {2,5}\\.\\.\\.: | {5,8}: ', true, true, true, true, '', '')
231}
232
233  // Initialize with a callback so we can modify the text before copy
234  const clipboard = new ClipboardJS('.copybtn', {text: copyTargetText})
235
236  // Update UI with error/success messages
237  clipboard.on('success', event => {
238    clearSelection()
239    temporarilyChangeTooltip(event.trigger, messages[locale]['copy'], messages[locale]['copy_success'])
240    temporarilyChangeIcon(event.trigger)
241  })
242
243  clipboard.on('error', event => {
244    temporarilyChangeTooltip(event.trigger, messages[locale]['copy'], messages[locale]['copy_failure'])
245  })
246}
247
248runWhenDOMLoaded(addCopyButtonToCodeCells)

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.