mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-06 13:10:37 -04:00
On non-secure origins the copy button falls back to execCommand('copy')
with a temporary textarea. The textarea was appended to <body>, outside
the x-trap focus trap of the DNS entries modal, so the trap pulled focus
back and the selection was lost before the copy. The button showed
success, but nothing was copied.
Append the textarea next to the button, select it explicitly, and
restore focus afterwards. "Copy all" now uses the shared copyButton
handler instead of its own duplicate fallback that ignored failures.
Closes #12075
43 lines
2.0 KiB
JavaScript
43 lines
2.0 KiB
JavaScript
// Alpine data provider for the <x-copy-button> component (x-data="copyButton").
|
|
export function initializeCopyButtonComponent() {
|
|
window.Alpine.data('copyButton', () => ({
|
|
copied: false,
|
|
async copy(value) {
|
|
if (value === null || value === undefined) {
|
|
window.toast('Value is not available.', { type: 'warning' });
|
|
return;
|
|
}
|
|
try {
|
|
if (navigator.clipboard?.writeText && window.isSecureContext) {
|
|
await navigator.clipboard.writeText(value);
|
|
} else {
|
|
// Deprecated, but the only copy path on plain http (non-secure contexts).
|
|
// The textarea goes next to the button, not into <body>: a modal's x-trap
|
|
// pulls focus back from nodes outside it, which clears the selection.
|
|
const previousFocus = document.activeElement;
|
|
const textarea = document.createElement('textarea');
|
|
textarea.value = value;
|
|
textarea.setAttribute('readonly', '');
|
|
textarea.setAttribute('tabindex', '-1');
|
|
textarea.style.position = 'fixed';
|
|
textarea.style.left = '-9999px';
|
|
this.$el.after(textarea);
|
|
textarea.focus({ preventScroll: true });
|
|
textarea.select();
|
|
textarea.setSelectionRange(0, textarea.value.length);
|
|
const ok = document.execCommand('copy');
|
|
textarea.remove();
|
|
previousFocus?.focus?.({ preventScroll: true });
|
|
if (!ok) {
|
|
throw new Error('Copy command was rejected.');
|
|
}
|
|
}
|
|
this.copied = true;
|
|
setTimeout(() => (this.copied = false), 1200);
|
|
} catch (e) {
|
|
window.toast('Could not copy to clipboard.', { type: 'warning' });
|
|
}
|
|
},
|
|
}));
|
|
}
|