Merge pull request #3311 from SillyTavern/fix-http-copy Fix copy actions for non-HTTPS/localhost

df28c2c676d62d2e9791839d336a5aa823b210ec

Cohee <18619528+Cohee1207@users.noreply.github.com>

Signed
2 files changed, +46 -33Ignore whitespace
public/script.js+26 -33
@@ -168,6 +168,7 @@ import {
168168 isTrueBoolean,
169169 toggleDrawer,
170170 isElementInViewport,
171+ copyText,
171172} from './scripts/utils.js';
172173import { debounce_timeout } from './scripts/constants.js';
173174
@@ -2315,16 +2316,15 @@ export function addCopyToCodeBlocks(messageElement) {
23152316 const codeBlocks = $(messageElement).find('pre code');
23162317 for (let i = 0; i < codeBlocks.length; i++) {
23172318 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);
23252326 toastr.info(t`Copied!`, '', { timeOut: 2000 });
23262327 });
2327- }
23282328 }
23292329}
23302330
@@ -2724,7 +2724,7 @@ export function getStoppingStrings(isImpersonate, isContinue) {
27242724export async function generateQuietPrompt(quiet_prompt, quietToLoud, skipWIAN, quietImage = null, quietName = null, responseLength = null, force_chid = null) {
27252725 console.log('got into genQuietPrompt');
27262726 const responseLengthCustomized = typeof responseLength === 'number' && responseLength > 0;
27272727 let eventHook = () => { };
27282728 try {
27292729 /** @type {GenerateOptions} */
27302730 const options = {
@@ -3390,7 +3390,7 @@ export async function generateRaw(prompt, api, instructOverride, quietToLoud, sy
33903390 const responseLengthCustomized = typeof responseLength === 'number' && responseLength > 0;
33913391 const isInstruct = power_user.instruct.enabled && api !== 'openai' && api !== 'novel' && !instructOverride;
33923392 const isQuiet = true;
33933393 let eventHook = () => { };
33943394
33953395 if (systemPrompt) {
33963396 systemPrompt = substituteParams(systemPrompt);
@@ -5469,7 +5469,7 @@ async function promptItemize(itemizedPrompts, requestedMesId) {
54695469 } else {
54705470 diffPrevPrompt.style.display = 'none';
54715471 }
54725472 popup.dlg.querySelector('#copyPromptToClipboard').addEventListener('clickpointerup', async function () {
54735473 let rawPrompt = itemizedPrompts[PromptArrayItemForRawPromptDisplay].rawPrompt;
54745474 let rawPromptValues = rawPrompt;
54755475
@@ -5477,7 +5477,7 @@ async function promptItemize(itemizedPrompts, requestedMesId) {
54775477 rawPromptValues = rawPrompt.map(x => x.content).join('\n');
54785478 }
54795479
54805480 navigator.clipboard.writeTextawait copyText(rawPromptValues);
54815481 toastr.info(t`Copied!`);
54825482 });
54835483
@@ -9507,7 +9507,7 @@ API Settings: ${JSON.stringify(getSettingsContents[getSettingsContents.main_api
95079507 //console.log(logMessage);
95089508
95099509 try {
95109510 await navigator.clipboard.writeTextcopyText(logMessage);
95119511 toastr.info('Your ST API setup data has been copied to the clipboard.');
95129512 } catch (error) {
95139513 toastr.error('Failed to copy ST Setup to clipboard:', error);
@@ -10572,24 +10572,18 @@ jQuery(async function () {
1057210572 setTimeout(function () { $('#shadow_select_chat_popup').css('display', 'none'); }, animation_duration);
1057310573 });
1057410574
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- }
1059010584 }
1059110585 });
1059210586 });
1059310587
1059410588 $(document).on('pointerup', '.mes_prompt', async function () {
1059510589 let mesIdForItemization = $(this).closest('.mes').attr('mesId');
@@ -11483,4 +11477,3 @@ jQuery(async function () {
1148311477
1148411478 initCustomSelectedSamplers();
1148511479});
11486-
public/scripts/utils.js+20 -0
@@ -392,6 +392,26 @@ export function getStringHash(str, seed = 0) {
392392}
393393
394394/**
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+/**
395415 * Map of debounced functions to their timers.
396416 * Weak map is used to avoid memory leaks.
397417 * @type {WeakMap<function, any>}