imaging: copy button HTTP fallback (execCommand)
navigator.clipboard.writeText is gated on isSecureContext - HTTPS
or localhost only. PXE dashboard is served over plain HTTP
(10.9.100.1:9009) so the API was undefined and the chain threw
before .catch fired - user saw nothing. Wrap clipboard write in
copyText() that prefers the modern API and falls back to the
classic invisible-textarea + document.execCommand('copy') path
which works on HTTP. Visual flash logic moved into flashCopied()
for reuse.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -138,14 +138,36 @@ Content-Type: application/json
|
|||||||
|
|
||||||
{% block extra_scripts %}
|
{% block extra_scripts %}
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('click', function(e) {
|
function copyText(text) {
|
||||||
var btn = e.target.closest('.copy-btn');
|
// Modern path - only works over HTTPS or localhost
|
||||||
if (!btn) return;
|
if (navigator.clipboard && window.isSecureContext) {
|
||||||
var text = btn.getAttribute('data-copy-text');
|
return navigator.clipboard.writeText(text);
|
||||||
if (!text) return;
|
}
|
||||||
navigator.clipboard.writeText(text).then(function() {
|
// Legacy fallback for plain HTTP
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
var ta = document.createElement('textarea');
|
||||||
|
ta.value = text;
|
||||||
|
ta.style.position = 'fixed';
|
||||||
|
ta.style.left = '-9999px';
|
||||||
|
ta.style.top = '0';
|
||||||
|
document.body.appendChild(ta);
|
||||||
|
ta.focus();
|
||||||
|
ta.select();
|
||||||
|
try {
|
||||||
|
var ok = document.execCommand('copy');
|
||||||
|
document.body.removeChild(ta);
|
||||||
|
if (ok) resolve(); else reject(new Error('execCommand returned false'));
|
||||||
|
} catch (err) {
|
||||||
|
document.body.removeChild(ta);
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function flashCopied(btn, success) {
|
||||||
var origText = btn.dataset.origText || btn.textContent;
|
var origText = btn.dataset.origText || btn.textContent;
|
||||||
btn.dataset.origText = origText;
|
btn.dataset.origText = origText;
|
||||||
|
if (success) {
|
||||||
btn.textContent = 'copied!';
|
btn.textContent = 'copied!';
|
||||||
btn.classList.remove('btn-outline-secondary');
|
btn.classList.remove('btn-outline-secondary');
|
||||||
btn.classList.add('btn-success');
|
btn.classList.add('btn-success');
|
||||||
@@ -156,16 +178,25 @@ document.addEventListener('click', function(e) {
|
|||||||
btn.classList.add('btn-outline-secondary');
|
btn.classList.add('btn-outline-secondary');
|
||||||
btn.style.transform = 'scale(1)';
|
btn.style.transform = 'scale(1)';
|
||||||
}, 1200);
|
}, 1200);
|
||||||
}).catch(function(err) {
|
} else {
|
||||||
btn.textContent = 'failed';
|
btn.textContent = 'failed';
|
||||||
btn.classList.remove('btn-outline-secondary');
|
btn.classList.remove('btn-outline-secondary');
|
||||||
btn.classList.add('btn-danger');
|
btn.classList.add('btn-danger');
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
btn.textContent = btn.dataset.origText || 'copy';
|
btn.textContent = origText;
|
||||||
btn.classList.remove('btn-danger');
|
btn.classList.remove('btn-danger');
|
||||||
btn.classList.add('btn-outline-secondary');
|
btn.classList.add('btn-outline-secondary');
|
||||||
}, 1500);
|
}, 1500);
|
||||||
});
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('click', function(e) {
|
||||||
|
var btn = e.target.closest('.copy-btn');
|
||||||
|
if (!btn) return;
|
||||||
|
var text = btn.getAttribute('data-copy-text');
|
||||||
|
if (!text) return;
|
||||||
|
copyText(text).then(function() { flashCopied(btn, true); })
|
||||||
|
.catch(function(err) { console.error('copy failed:', err); flashCopied(btn, false); });
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
Reference in New Issue
Block a user