diff --git a/nodescraper/command_artifact_html.py b/nodescraper/command_artifact_html.py
index 5bf3c5be..18e93606 100644
--- a/nodescraper/command_artifact_html.py
+++ b/nodescraper/command_artifact_html.py
@@ -121,6 +121,17 @@
}
pre.stderr { color: var(--fail-fg); }
.no-results { color: var(--muted); padding: 24px; text-align: center; display: none; }
+ .copy-btn {
+ flex: 0 0 auto;
+ display: inline-flex; align-items: center; justify-content: center;
+ background: var(--bg); color: var(--muted);
+ border: 1px solid var(--border); border-radius: 6px;
+ padding: 5px 7px; cursor: pointer; line-height: 0;
+ transition: color .15s ease, border-color .15s ease, background .15s ease;
+ }
+ .copy-btn:hover { background: var(--panel-hover); border-color: var(--accent); color: var(--text); }
+ .copy-btn.copied { color: var(--ok-fg); border-color: var(--ok-fg); }
+ .copy-btn svg { display: block; }
@@ -143,6 +154,9 @@
{stdout_block}
@@ -176,6 +190,51 @@
document.getElementById('collapseAll').addEventListener('click', () => {
items.forEach(d => d.open = false);
});
+
+ const copyButtons = Array.from(document.querySelectorAll('.copy-btn'));
+ const COPY_SVG = copyButtons.length ? copyButtons[0].innerHTML : '';
+ const CHECK_SVG = '
';
+
+ function showCopied(btn) {
+ btn.classList.add('copied');
+ btn.innerHTML = CHECK_SVG;
+ if (btn._resetTimer) clearTimeout(btn._resetTimer);
+ btn._resetTimer = setTimeout(() => {
+ btn.classList.remove('copied');
+ btn.innerHTML = COPY_SVG;
+ }, 1200);
+ }
+
+ function fallbackCopy(text, btn) {
+ const ta = document.createElement('textarea');
+ ta.value = text;
+ ta.style.position = 'fixed';
+ ta.style.top = '-1000px';
+ ta.style.opacity = '0';
+ document.body.appendChild(ta);
+ ta.focus();
+ ta.select();
+ try { document.execCommand('copy'); showCopied(btn); } catch (e) {}
+ document.body.removeChild(ta);
+ }
+
+ copyButtons.forEach(btn => {
+ btn.addEventListener('click', (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ const card = btn.closest('details.cmd');
+ const parts = [];
+ card.querySelectorAll('pre').forEach(pre => {
+ if (!pre.classList.contains('empty')) parts.push(pre.textContent);
+ });
+ const text = parts.join('\\n');
+ if (navigator.clipboard && navigator.clipboard.writeText) {
+ navigator.clipboard.writeText(text).then(() => showCopied(btn)).catch(() => fallbackCopy(text, btn));
+ } else {
+ fallbackCopy(text, btn);
+ }
+ });
+ });