Merge pull request #3311 from SillyTavern/fix-http-copy Fix copy actions for non-HTTPS/localhost
Signed| @@ -168,6 +168,7 @@ import { | |||
| 168 | isTrueBoolean, | 168 | isTrueBoolean, |
| 169 | toggleDrawer, | 169 | toggleDrawer, |
| 170 | isElementInViewport, | 170 | isElementInViewport, |
| 171 | copyText, | ||
| 171 | } from './scripts/utils.js'; | 172 | } from './scripts/utils.js'; |
| 172 | import { debounce_timeout } from './scripts/constants.js'; | 173 | import { debounce_timeout } from './scripts/constants.js'; |
| 173 | 174 | ||
| @@ -2315,18 +2316,17 @@ export function addCopyToCodeBlocks(messageElement) { | |||
| 2315 | const codeBlocks = $(messageElement).find('pre code'); | 2316 | const codeBlocks = $(messageElement).find('pre code'); |
| 2316 | for (let i = 0; i < codeBlocks.length; i++) { | 2317 | for (let i = 0; i < codeBlocks.length; i++) { |
| 2317 | hljs.highlightElement(codeBlocks.get(i)); | 2318 | hljs.highlightElement(codeBlocks.get(i)); |
| 2318 | if (navigator.clipboard !== undefined) { | ||
| 2319 | const copyButton = document.createElement('i'); | 2319 | const copyButton = document.createElement('i'); |
| 2320 | copyButton.classList.add('fa-solid', 'fa-copy', 'code-copy', 'interactable'); | 2320 | copyButton.classList.add('fa-solid', 'fa-copy', 'code-copy', 'interactable'); |
| 2321 | copyButton.title = 'Copy code'; | 2321 | copyButton.title = 'Copy code'; |
| 2322 | codeBlocks.get(i).appendChild(copyButton); | 2322 | codeBlocks.get(i).appendChild(copyButton); |
| 2323 | copyButton.addEventListener('pointerup', function (event) { | 2323 | copyButton.addEventListener('pointerup', async function () { |
| 2324 | navigator.clipboard.writeText(codeBlocks.get(i).innerText); | 2324 | const text = codeBlocks.get(i).innerText; |
| 2325 | await copyText(text); | ||
| 2325 | toastr.info(t`Copied!`, '', { timeOut: 2000 }); | 2326 | toastr.info(t`Copied!`, '', { timeOut: 2000 }); |
| 2326 | }); | 2327 | }); |
| 2327 | } | 2328 | } |
| 2328 | } | 2329 | } |
| 2329 | } | ||
| 2330 | 2330 | ||
| 2331 | 2331 | ||
| 2332 | /** | 2332 | /** |
| @@ -5469,7 +5469,7 @@ async function promptItemize(itemizedPrompts, requestedMesId) { | |||
| 5469 | } else { | 5469 | } else { |
| 5470 | diffPrevPrompt.style.display = 'none'; | 5470 | diffPrevPrompt.style.display = 'none'; |
| 5471 | } | 5471 | } |
| 5472 | popup.dlg.querySelector('#copyPromptToClipboard').addEventListener('click', function () { | 5472 | popup.dlg.querySelector('#copyPromptToClipboard').addEventListener('pointerup', async function () { |
| 5473 | let rawPrompt = itemizedPrompts[PromptArrayItemForRawPromptDisplay].rawPrompt; | 5473 | let rawPrompt = itemizedPrompts[PromptArrayItemForRawPromptDisplay].rawPrompt; |
| 5474 | let rawPromptValues = rawPrompt; | 5474 | let rawPromptValues = rawPrompt; |
| 5475 | 5475 | ||
| @@ -5477,7 +5477,7 @@ async function promptItemize(itemizedPrompts, requestedMesId) { | |||
| 5477 | rawPromptValues = rawPrompt.map(x => x.content).join('\n'); | 5477 | rawPromptValues = rawPrompt.map(x => x.content).join('\n'); |
| 5478 | } | 5478 | } |
| 5479 | 5479 | ||
| 5480 | navigator.clipboard.writeText(rawPromptValues); | 5480 | await copyText(rawPromptValues); |
| 5481 | toastr.info(t`Copied!`); | 5481 | toastr.info(t`Copied!`); |
| 5482 | }); | 5482 | }); |
| 5483 | 5483 | ||
| @@ -9507,7 +9507,7 @@ API Settings: ${JSON.stringify(getSettingsContents[getSettingsContents.main_api | |||
| 9507 | //console.log(logMessage); | 9507 | //console.log(logMessage); |
| 9508 | 9508 | ||
| 9509 | try { | 9509 | try { |
| 9510 | await navigator.clipboard.writeText(logMessage); | 9510 | await copyText(logMessage); |
| 9511 | toastr.info('Your ST API setup data has been copied to the clipboard.'); | 9511 | toastr.info('Your ST API setup data has been copied to the clipboard.'); |
| 9512 | } catch (error) { | 9512 | } catch (error) { |
| 9513 | toastr.error('Failed to copy ST Setup to clipboard:', error); | 9513 | toastr.error('Failed to copy ST Setup to clipboard:', error); |
| @@ -10572,24 +10572,18 @@ jQuery(async function () { | |||
| 10572 | setTimeout(function () { $('#shadow_select_chat_popup').css('display', 'none'); }, animation_duration); | 10572 | setTimeout(function () { $('#shadow_select_chat_popup').css('display', 'none'); }, animation_duration); |
| 10573 | }); | 10573 | }); |
| 10574 | 10574 | ||
| 10575 | if (navigator.clipboard === undefined) { | 10575 | $(document).on('pointerup', '.mes_copy', async function () { |
| 10576 | // No clipboard support | ||
| 10577 | $('.mes_copy').remove(); | ||
| 10578 | } | ||
| 10579 | else { | ||
| 10580 | $(document).on('pointerup', '.mes_copy', function () { | ||
| 10581 | if (this_chid !== undefined || selected_group || name2 === neutralCharacterName) { | 10576 | if (this_chid !== undefined || selected_group || name2 === neutralCharacterName) { |
| 10582 | try { | 10577 | try { |
| 10583 | const messageId = $(this).closest('.mes').attr('mesid'); | 10578 | const messageId = $(this).closest('.mes').attr('mesid'); |
| 10584 | const text = chat[messageId]['mes']; | 10579 | const text = chat[messageId]['mes']; |
| 10585 | navigator.clipboard.writeText(text); | 10580 | await copyText(text); |
| 10586 | toastr.info('Copied!', '', { timeOut: 2000 }); | 10581 | toastr.info('Copied!', '', { timeOut: 2000 }); |
| 10587 | } catch (err) { | 10582 | } catch (err) { |
| 10588 | console.error('Failed to copy: ', err); | 10583 | console.error('Failed to copy: ', err); |
| 10589 | } | 10584 | } |
| 10590 | } | 10585 | } |
| 10591 | }); | 10586 | }); |
| 10592 | } | ||
| 10593 | 10587 | ||
| 10594 | $(document).on('pointerup', '.mes_prompt', async function () { | 10588 | $(document).on('pointerup', '.mes_prompt', async function () { |
| 10595 | let mesIdForItemization = $(this).closest('.mes').attr('mesId'); | 10589 | let mesIdForItemization = $(this).closest('.mes').attr('mesId'); |
| @@ -11483,4 +11477,3 @@ jQuery(async function () { | |||
| 11483 | 11477 | ||
| 11484 | initCustomSelectedSamplers(); | 11478 | initCustomSelectedSamplers(); |
| 11485 | }); | 11479 | }); |
| 11486 | |||
| @@ -392,6 +392,26 @@ export function getStringHash(str, seed = 0) { | |||
| 392 | } | 392 | } |
| 393 | 393 | ||
| 394 | /** | 394 | /** |
| 395 | * Copy text to clipboard. Use navigator.clipboard.writeText if available, otherwise use document.execCommand. | ||
| 396 | * @param {string} text - The text to copy to the clipboard. | ||
| 397 | * @returns {Promise<void>} A promise that resolves when the text has been copied to the clipboard. | ||
| 398 | */ | ||
| 399 | export function copyText(text) { | ||
| 400 | if (navigator.clipboard) { | ||
| 401 | return navigator.clipboard.writeText(text); | ||
| 402 | } | ||
| 403 | |||
| 404 | const parent = document.querySelector('dialog[open]:last-of-type') ?? document.body; | ||
| 405 | const textArea = document.createElement('textarea'); | ||
| 406 | textArea.value = text; | ||
| 407 | parent.appendChild(textArea); | ||
| 408 | textArea.focus(); | ||
| 409 | textArea.select(); | ||
| 410 | document.execCommand('copy'); | ||
| 411 | parent.removeChild(textArea); | ||
| 412 | } | ||
| 413 | |||
| 414 | /** | ||
| 395 | * Map of debounced functions to their timers. | 415 | * Map of debounced functions to their timers. |
| 396 | * Weak map is used to avoid memory leaks. | 416 | * Weak map is used to avoid memory leaks. |
| 397 | * @type {WeakMap<function, any>} | 417 | * @type {WeakMap<function, any>} |