diff --git a/frontend/js/viewer.js b/frontend/js/viewer.js index 5469344..d802e2d 100644 --- a/frontend/js/viewer.js +++ b/frontend/js/viewer.js @@ -672,6 +672,31 @@ export function renderFile(data) { safeHighlight(block); }); + // Add copy buttons to code blocks + area.querySelectorAll("pre").forEach((pre) => { + if (pre.parentElement?.classList.contains("code-block-wrapper")) return; + const wrapper = el("div", { class: "code-block-wrapper" }, []); + wrapper.style.position = "relative"; + pre.replaceWith(wrapper); + wrapper.appendChild(pre); + + const btn = el("button", { class: "copy-code-btn", title: "Copier le code" }, [document.createTextNode("Copier")]); + btn.addEventListener("click", async (e) => { + e.stopPropagation(); + const code = pre.querySelector("code")?.textContent || pre.textContent; + try { + await navigator.clipboard.writeText(code); + btn.textContent = "✓ Copié"; + btn.classList.add("copied"); + if (typeof showToast === "function") showToast("Code copié dans le presse-papiers", "success"); + setTimeout(() => { btn.textContent = "Copier"; btn.classList.remove("copied"); }, 2000); + } catch { + if (typeof showToast === "function") showToast("Erreur lors de la copie", "error"); + } + }); + wrapper.appendChild(btn); + }); + // Render Mermaid diagrams renderMermaidBlocks(area); diff --git a/frontend/popout.html b/frontend/popout.html index 22c4066..ea147a5 100644 --- a/frontend/popout.html +++ b/frontend/popout.html @@ -893,24 +893,47 @@ const RightSidebarManager = { OutlineManager.init(); RightSidebarManager.init(); // Mermaid + code highlighting handled below FIRST (before highlight.js) + function highlightCode() { + document.querySelectorAll('pre code:not(.language-mermaid)').forEach((el) => { + if (typeof hljs !== 'undefined') hljs.highlightElement(el); + }); + } + function addCopyButtons() { + document.querySelectorAll('pre').forEach((pre) => { + if (pre.parentElement && pre.parentElement.classList.contains('code-block-wrapper')) return; + var wrapper = document.createElement('div'); + wrapper.className = 'code-block-wrapper'; + wrapper.style.position = 'relative'; + pre.replaceWith(wrapper); + wrapper.appendChild(pre); + var btn = document.createElement('button'); + btn.className = 'copy-code-btn'; + btn.title = 'Copier le code'; + btn.textContent = 'Copier'; + btn.addEventListener('click', function(e) { + e.stopPropagation(); + var code = (pre.querySelector('code') || pre).textContent; + navigator.clipboard.writeText(code).then(function() { + btn.textContent = '✓ Copié'; + btn.classList.add('copied'); + setTimeout(function() { btn.textContent = 'Copier'; btn.classList.remove('copied'); }, 2000); + }).catch(function() {}); + }); + wrapper.appendChild(btn); + }); + } if (typeof mermaid !== 'undefined') { mermaid.initialize({ startOnLoad: false, theme: document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'default' }); mermaid.run({ querySelector: '.language-mermaid' }).then(() => { - // After Mermaid renders, highlight remaining code blocks (skip mermaid SVGs) - document.querySelectorAll('pre code:not(.language-mermaid)').forEach((el) => { - if (typeof hljs !== 'undefined') hljs.highlightElement(el); - }); + highlightCode(); + addCopyButtons(); }).catch(() => { - // Mermaid failed — highlight everything else anyway - document.querySelectorAll('pre code:not(.language-mermaid)').forEach((el) => { - if (typeof hljs !== 'undefined') hljs.highlightElement(el); - }); + highlightCode(); + addCopyButtons(); }); } else { - // No mermaid — just highlight code - document.querySelectorAll('pre code').forEach((el) => { - if (typeof hljs !== 'undefined') hljs.highlightElement(el); - }); + highlightCode(); + addCopyButtons(); } // Hide loading diff --git a/frontend/style.css b/frontend/style.css index 2dc7cf1..83dd43d 100644 --- a/frontend/style.css +++ b/frontend/style.css @@ -8528,3 +8528,35 @@ body.popup-mode .content-area { .pane-resize-handle:hover { background: var(--accent); } +.code-block-wrapper:hover .copy-code-btn { + opacity: 1; +} +.copy-code-btn { + position: absolute; + top: 8px; + right: 8px; + display: flex; + align-items: center; + gap: 4px; + padding: 4px 10px; + background: var(--bg-secondary); + border: 1px solid var(--border); + border-radius: var(--radius); + color: var(--text-secondary); + font-size: 11px; + font-family: var(--mono); + cursor: pointer; + opacity: 0; + transition: opacity 150ms ease, background 150ms ease, color 150ms ease; + z-index: 5; +} +.copy-code-btn:hover { + background: var(--accent); + color: #fff; + border-color: var(--accent); +} +.copy-code-btn.copied { + background: var(--accent-green); + color: #fff; + border-color: var(--accent-green); +}