Fix copy actions for non-HTTPS/localhost Fixes #2352
| @@ -168,6 +168,7 @@ import { | ||
| 168 | 168 | isTrueBoolean, |
| 169 | 169 | toggleDrawer, |
| 170 | 170 | isElementInViewport, |
| 171 | + copyText, | |
| 171 | 172 | } from './scripts/utils.js'; |
| 172 | 173 | import { debounce_timeout } from './scripts/constants.js'; |
| 173 | 174 | |
| @@ -2315,16 +2316,15 @@ export function addCopyToCodeBlocks(messageElement) { | ||
| 2315 | 2316 | const codeBlocks = $(messageElement).find('pre code'); |
| 2316 | 2317 | for (let i = 0; i < codeBlocks.length; i++) { |
| 2317 | 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', async function () { | |
| 2323 | - copyButton.addEventListener('pointerup', function (event) { | |
| 2324 | + const text = codeBlocks.get(i).innerText; | |
| 2324 | - navigator.clipboard.writeText(codeBlocks.get(i).innerText); | |
| 2325 | + await copyText(text); | |
| 2325 | 2326 | toastr.info(t`Copied!`, '', { timeOut: 2000 }); |
| 2326 | 2327 | }); |
| 2327 | - } | |
| 2328 | 2328 | } |
| 2329 | 2329 | } |
| 2330 | 2330 | |
| @@ -2724,7 +2724,7 @@ export function getStoppingStrings(isImpersonate, isContinue) { | ||
| 2724 | 2724 | export async function generateQuietPrompt(quiet_prompt, quietToLoud, skipWIAN, quietImage = null, quietName = null, responseLength = null, force_chid = null) { |
| 2725 | 2725 | console.log('got into genQuietPrompt'); |
| 2726 | 2726 | const responseLengthCustomized = typeof responseLength === 'number' && responseLength > 0; |
| 2727 | 2727 | let eventHook = () => { }; |
| 2728 | 2728 | try { |
| 2729 | 2729 | /** @type {GenerateOptions} */ |
| 2730 | 2730 | const options = { |
| @@ -3390,7 +3390,7 @@ export async function generateRaw(prompt, api, instructOverride, quietToLoud, sy | ||
| 3390 | 3390 | const responseLengthCustomized = typeof responseLength === 'number' && responseLength > 0; |
| 3391 | 3391 | const isInstruct = power_user.instruct.enabled && api !== 'openai' && api !== 'novel' && !instructOverride; |
| 3392 | 3392 | const isQuiet = true; |
| 3393 | 3393 | let eventHook = () => { }; |
| 3394 | 3394 | |
| 3395 | 3395 | if (systemPrompt) { |
| 3396 | 3396 | systemPrompt = substituteParams(systemPrompt); |
| @@ -5469,7 +5469,7 @@ async function promptItemize(itemizedPrompts, requestedMesId) { | ||
| 5469 | 5469 | } else { |
| 5470 | 5470 | diffPrevPrompt.style.display = 'none'; |
| 5471 | 5471 | } |
| 5472 | 5472 | popup.dlg.querySelector('#copyPromptToClipboard').addEventListener('clickpointerup', async function () { |
| 5473 | 5473 | let rawPrompt = itemizedPrompts[PromptArrayItemForRawPromptDisplay].rawPrompt; |
| 5474 | 5474 | let rawPromptValues = rawPrompt; |
| 5475 | 5475 | |
| @@ -5477,7 +5477,7 @@ async function promptItemize(itemizedPrompts, requestedMesId) { | ||
| 5477 | 5477 | rawPromptValues = rawPrompt.map(x => x.content).join('\n'); |
| 5478 | 5478 | } |
| 5479 | 5479 | |
| 5480 | 5480 | navigator.clipboard.writeTextawait copyText(rawPromptValues); |
| 5481 | 5481 | toastr.info(t`Copied!`); |
| 5482 | 5482 | }); |
| 5483 | 5483 | |
| @@ -9507,7 +9507,7 @@ API Settings: ${JSON.stringify(getSettingsContents[getSettingsContents.main_api | ||
| 9507 | 9507 | //console.log(logMessage); |
| 9508 | 9508 | |
| 9509 | 9509 | try { |
| 9510 | 9510 | await navigator.clipboard.writeTextcopyText(logMessage); |
| 9511 | 9511 | toastr.info('Your ST API setup data has been copied to the clipboard.'); |
| 9512 | 9512 | } catch (error) { |
| 9513 | 9513 | toastr.error('Failed to copy ST Setup to clipboard:', error); |
| @@ -10572,24 +10572,18 @@ jQuery(async function () { | ||
| 10572 | 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 | |
| 10576 | + if (this_chid !== undefined || selected_group || name2 === neutralCharacterName) { | |
| 10577 | - $('.mes_copy').remove(); | |
| 10577 | + try { | |
| 10578 | - } | |
| 10578 | + const messageId = $(this).closest('.mes').attr('mesid'); | |
| 10579 | - else { | |
| 10579 | + const text = chat[messageId]['mes']; | |
| 10580 | - $(document).on('pointerup', '.mes_copy', function () { | |
| 10580 | + await copyText(text); | |
| 10581 | - if (this_chid !== undefined || selected_group || name2 === neutralCharacterName) { | |
| 10581 | + toastr.info('Copied!', '', { timeOut: 2000 }); | |
| 10582 | - try { | |
| 10582 | + } catch (err) { | |
| 10583 | - const messageId = $(this).closest('.mes').attr('mesid'); | |
| 10583 | + console.error('Failed to copy: ', err); | |
| 10584 | - const text = chat[messageId]['mes']; | |
| 10585 | - navigator.clipboard.writeText(text); | |
| 10586 | - toastr.info('Copied!', '', { timeOut: 2000 }); | |
| 10587 | - } catch (err) { | |
| 10588 | - console.error('Failed to copy: ', err); | |
| 10589 | - } | |
| 10590 | 10584 | } |
| 10591 | 10585 | }); |
| 10592 | 10586 | }); |
| 10593 | 10587 | |
| 10594 | 10588 | $(document).on('pointerup', '.mes_prompt', async function () { |
| 10595 | 10589 | let mesIdForItemization = $(this).closest('.mes').attr('mesId'); |
| @@ -11483,4 +11477,3 @@ jQuery(async function () { | ||
| 11483 | 11477 | |
| 11484 | 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 | 415 | * Map of debounced functions to their timers. |
| 396 | 416 | * Weak map is used to avoid memory leaks. |
| 397 | 417 | * @type {WeakMap<function, any>} |