diff --git a/src/client/lib/markdown.ts b/src/client/lib/markdown.ts index c730150..f6d2a19 100644 --- a/src/client/lib/markdown.ts +++ b/src/client/lib/markdown.ts @@ -18,6 +18,15 @@ marked.use( }) ); +// Custom renderer to wrap code blocks with copy button +const renderer = new marked.Renderer(); +renderer.code = function ({ text, lang }: { text: string; lang?: string | undefined; escaped?: boolean }) { + const langLabel = lang ? `${escapeHtml(lang)}` : ""; + return `
${langLabel}
${text}
`; +}; + +marked.use({ renderer }); + export function renderMarkdown(text: string): string { if (!text) return ""; try { diff --git a/src/client/main.tsx b/src/client/main.tsx index 0b11abd..eaf3b04 100644 --- a/src/client/main.tsx +++ b/src/client/main.tsx @@ -3,6 +3,23 @@ import ReactDOM from "react-dom/client"; import { App } from "./app.js"; import "./styles/main.css"; +// Delegated click handler for code block copy buttons +document.addEventListener("click", (e) => { + const btn = (e.target as HTMLElement).closest("[data-copy-code]") as HTMLButtonElement | null; + if (!btn) return; + e.stopPropagation(); + const wrapper = btn.closest(".code-block-wrapper"); + const code = wrapper?.querySelector("code"); + if (!code) return; + navigator.clipboard.writeText(code.textContent || "").then(() => { + btn.textContent = "Copied!"; + setTimeout(() => { btn.textContent = "Copy"; }, 1500); + }).catch(() => { + btn.textContent = "Failed"; + setTimeout(() => { btn.textContent = "Copy"; }, 1500); + }); +}); + ReactDOM.createRoot(document.getElementById("root")!).render(