From 350ed0df63cbf5d766569c84b9c259ade91c4a53 Mon Sep 17 00:00:00 2001 From: Mubashir Rahim Date: Wed, 3 Jun 2026 07:16:47 +0500 Subject: [PATCH] fix: copy buttons silently fail on insecure origins (no clipboard fallback) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit copyPrompt() and copyCode() call navigator.clipboard.writeText() directly. In an insecure context — most commonly the page opened via file:// — navigator.clipboard is undefined, so the call throws synchronously: the copy button does nothing and shows no feedback (and there was no .catch on the returned promise either). Route both through a copyToClipboard() helper that uses the async Clipboard API in secure contexts and falls back to a hidden-textarea execCommand('copy') otherwise. Co-Authored-By: Claude Opus 4.8 --- index.html | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/index.html b/index.html index bf0650f..dd00ac8 100644 --- a/index.html +++ b/index.html @@ -1346,12 +1346,37 @@

Get Started in Seconds

}); // ── Copy prompt from use case ── +// Clipboard with a fallback for insecure contexts (e.g. the page opened via +// file://), where navigator.clipboard is undefined and writeText() would throw, +// leaving the copy buttons silently dead. +function copyToClipboard(text) { + if (navigator.clipboard && window.isSecureContext) { + return navigator.clipboard.writeText(text); + } + return new Promise((resolve, reject) => { + try { + const ta = document.createElement('textarea'); + ta.value = text; + ta.setAttribute('readonly', ''); + ta.style.position = 'fixed'; + ta.style.opacity = '0'; + document.body.appendChild(ta); + ta.select(); + const ok = document.execCommand('copy'); + document.body.removeChild(ta); + ok ? resolve() : reject(new Error('copy command was rejected')); + } catch (err) { + reject(err); + } + }); +} + function copyPrompt(el) { const hint = el.querySelector('.uc-copy-hint'); const text = el.textContent.replace(hint.textContent, '').trim(); const copiedText = (i18n['common.copied'] && i18n['common.copied'][currentLang]) || 'Copied!'; const clickText = (i18n['common.click_to_copy'] && i18n['common.click_to_copy'][currentLang]) || 'Click to copy'; - navigator.clipboard.writeText(text).then(() => { + copyToClipboard(text).then(() => { el.classList.add('copied'); hint.textContent = copiedText; setTimeout(() => { el.classList.remove('copied'); hint.textContent = clickText; }, 2000); @@ -1372,7 +1397,7 @@

Get Started in Seconds

const cmd = block.querySelector('.cmd').textContent; const copiedText = (i18n['common.copied'] && i18n['common.copied'][currentLang]) || 'Copied!'; const copyText = (i18n['common.copy'] && i18n['common.copy'][currentLang]) || 'Copy'; - navigator.clipboard.writeText(cmd).then(() => { + copyToClipboard(cmd).then(() => { const btn = block.querySelector('.copy-btn'); btn.textContent = copiedText; btn.classList.add('copied');