Skip to content

Commit 848dc92

Browse files
Mikarina13claude
andcommitted
fix(dashboard): copy buttons fall back to execCommand when clipboard API blocked
Three pages had `navigator.clipboard.writeText(...)` with no fallback and no .catch() — when the modern API is unavailable (HTTP non-localhost, permissions denied, focus loss, browser blocking) the click was a silent no-op. User reported the chat-prompt copy button doing nothing. Fixed in: codec_chat.html copyMsgText() (chat message copy buttons) codec_dashboard.html copyText() (overview / recents card copy buttons) codec_vibe.html doCopy() (code editor copy) Each now: 1. Tries navigator.clipboard.writeText() if present 2. On rejection / unavailability, falls back to a hidden textarea + document.execCommand('copy') 3. Always surfaces a toast — "Copied" on success, "Copy failed" on total failure (instead of silent no-op) Dashboard serves the HTML fresh per-request with _NO_CACHE headers, so no PM2 restart is required — a browser refresh picks up the fix. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 72942c8 commit 848dc92

3 files changed

Lines changed: 85 additions & 6 deletions

File tree

codec_chat.html

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -488,7 +488,28 @@ <h1><a href="/" style="color:inherit;text-decoration:none">CODEC</a></h1>
488488
var thinkingEnabled=true;
489489

490490
function showToast(msg){var t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');setTimeout(function(){t.classList.remove('show')},1800)}
491-
function copyMsgText(text,btn){navigator.clipboard.writeText(text).then(function(){btn.classList.add('copied');showToast('Copied');setTimeout(function(){btn.classList.remove('copied')},1500)})}
491+
function _copyFallback(text){
492+
try{
493+
var ta=document.createElement('textarea');
494+
ta.value=text;
495+
ta.setAttribute('readonly','');
496+
ta.style.position='fixed';
497+
ta.style.top='0';ta.style.left='0';
498+
ta.style.opacity='0';ta.style.pointerEvents='none';
499+
document.body.appendChild(ta);
500+
ta.focus();ta.select();ta.setSelectionRange(0,ta.value.length);
501+
var ok=document.execCommand('copy');
502+
document.body.removeChild(ta);
503+
return ok;
504+
}catch(e){return false}
505+
}
506+
function copyMsgText(text,btn){
507+
function ok(){btn.classList.add('copied');showToast('Copied');setTimeout(function(){btn.classList.remove('copied')},1500)}
508+
function fail(){if(_copyFallback(text)){ok()}else{showToast('Copy failed')}}
509+
if(navigator.clipboard&&navigator.clipboard.writeText){
510+
navigator.clipboard.writeText(text).then(ok,fail).catch(fail)
511+
}else{fail()}
512+
}
492513

493514
function setMode(mode){
494515
var changed=chatMode!==mode;

codec_dashboard.html

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -776,12 +776,48 @@ <h2>MENU</h2>
776776
}
777777

778778
// ── Copy to clipboard ──
779+
// Two-path implementation: prefer navigator.clipboard (secure-context API),
780+
// fall back to a hidden textarea + document.execCommand('copy') if the
781+
// modern API is unavailable (HTTP non-localhost) or the promise rejects
782+
// (permissions, focus loss, etc.). Always surfaces a toast — success or
783+
// failure — so the click is never silent.
784+
function _copyFallback(text) {
785+
try {
786+
var ta = document.createElement('textarea');
787+
ta.value = text;
788+
ta.setAttribute('readonly', '');
789+
ta.style.position = 'fixed';
790+
ta.style.top = '0';
791+
ta.style.left = '0';
792+
ta.style.opacity = '0';
793+
ta.style.pointerEvents = 'none';
794+
document.body.appendChild(ta);
795+
ta.focus();
796+
ta.select();
797+
ta.setSelectionRange(0, ta.value.length);
798+
var ok = document.execCommand('copy');
799+
document.body.removeChild(ta);
800+
return ok;
801+
} catch (e) {
802+
return false;
803+
}
804+
}
805+
779806
function copyText(text, btn) {
780-
navigator.clipboard.writeText(text).then(function() {
807+
function onOk() {
781808
btn.classList.add('copied');
782809
showToast('Copied to clipboard');
783810
setTimeout(function() { btn.classList.remove('copied'); }, 1500);
784-
});
811+
}
812+
function onFail() {
813+
if (_copyFallback(text)) { onOk(); }
814+
else { showToast('Copy failed — clipboard not available'); }
815+
}
816+
if (navigator.clipboard && navigator.clipboard.writeText) {
817+
navigator.clipboard.writeText(text).then(onOk, onFail).catch(onFail);
818+
} else {
819+
onFail();
820+
}
785821
}
786822

787823
// ── Theme ──

codec_vibe.html

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -957,10 +957,32 @@ <h1><a href="/" style="color:inherit;text-decoration:none">CODEC</a></h1>
957957
});
958958
}
959959

960+
function _copyFallback(text){
961+
try{
962+
var ta=document.createElement('textarea');
963+
ta.value=text;ta.setAttribute('readonly','');
964+
ta.style.position='fixed';ta.style.top='0';ta.style.left='0';
965+
ta.style.opacity='0';ta.style.pointerEvents='none';
966+
document.body.appendChild(ta);ta.focus();ta.select();
967+
ta.setSelectionRange(0,ta.value.length);
968+
var ok=document.execCommand('copy');
969+
document.body.removeChild(ta);return ok;
970+
}catch(e){return false}
971+
}
960972
function doCopy() {
961-
navigator.clipboard.writeText(ed.getValue());
962-
document.getElementById('rs').textContent = 'Copied!';
963-
setTimeout(function() { document.getElementById('rs').textContent = ''; }, 2000);
973+
var text = ed.getValue();
974+
function ok(){
975+
document.getElementById('rs').textContent = 'Copied!';
976+
setTimeout(function() { document.getElementById('rs').textContent = ''; }, 2000);
977+
}
978+
function fail(){
979+
if(_copyFallback(text)){ok()}
980+
else{document.getElementById('rs').textContent='Copy failed';
981+
setTimeout(function(){document.getElementById('rs').textContent='';},2000);}
982+
}
983+
if (navigator.clipboard && navigator.clipboard.writeText) {
984+
navigator.clipboard.writeText(text).then(ok, fail).catch(fail);
985+
} else { fail(); }
964986
}
965987

966988
// Sidebar

0 commit comments

Comments
 (0)