Переглянути джерело

report: per-block copy buttons in rendered md (hover-only, full-text btn yields)

Alexander Korolev 1 місяць тому
батько
коміт
6a2f3c34b5
1 змінених файлів з 24 додано та 2 видалено
  1. 24 2
      src/report/view.html

+ 24 - 2
src/report/view.html

@@ -18,6 +18,16 @@
              border: 1px solid color-mix(in srgb, currentColor 30%, transparent);
              background: canvas; color: inherit; border-radius: 4px; }
   #copybtn:hover { opacity: 1; }
+  /* per-block copy buttons in rendered markdown: hidden until the block
+     is hovered; while a block copy is visible, the full-text button hides
+     so the two never compete for the cursor */
+  body:has(article pre:hover) #copybtn { opacity: 0; pointer-events: none; }
+  .copyblk { position: absolute; top: 6px; right: 8px; opacity: 0; cursor: pointer;
+             padding: 1px 8px; border: 1px solid color-mix(in srgb, currentColor 30%, transparent);
+             background: canvas; color: inherit; border-radius: 4px;
+             font: 11px system-ui, sans-serif; transition: opacity .15s; }
+  article pre:hover .copyblk, .copyblk:focus-visible { opacity: 1; }
+  .copyblk:hover { background: color-mix(in srgb, currentColor 12%, canvas); }
   #tabs button { padding: 2px 10px; cursor: pointer; border: 1px solid color-mix(in srgb, currentColor 30%, transparent);
                  background: canvas; color: inherit; border-radius: 4px 0 0 4px; }
   #tabs button + button { border-radius: 0 4px 4px 0; border-left: 0; }
@@ -29,7 +39,7 @@
   article th, article td { border: 1px solid color-mix(in srgb, currentColor 25%, transparent); padding: 3px 10px; }
   article th { background: color-mix(in srgb, currentColor 8%, canvas); }
   article code { font: 12px ui-monospace, monospace; background: color-mix(in srgb, currentColor 10%, canvas); padding: 1px 4px; border-radius: 3px; }
-  article pre { background: color-mix(in srgb, currentColor 8%, canvas); padding: 10px 12px; border-radius: 6px; }
+  article pre { background: color-mix(in srgb, currentColor 8%, canvas); padding: 10px 12px; border-radius: 6px; position: relative; }
   article pre code { background: none; padding: 0; }
   article blockquote { margin: 0.6em 0; padding: 4px 14px; border-left: 3px solid color-mix(in srgb, currentColor 35%, transparent); color: color-mix(in srgb, currentColor 75%, canvas); }
   article li { margin: 2px 0; }
@@ -95,7 +105,7 @@ function mdBlocks(lines) {
     if (/^```/.test(l)) {
       let j = i + 1;
       while (j < lines.length && !/^```/.test(lines[j])) j++;
-      out.push("<pre><code>" + esc(lines.slice(i + 1, j).join("\n")) + "</code></pre>");
+      out.push('<pre><button class="copyblk" title="copy block">⧉</button><code>' + esc(lines.slice(i + 1, j).join("\n")) + "</code></pre>");
       i = j + 1; continue;
     }
     if (/^\s*\|.*\|\s*$/.test(l) && i + 1 < lines.length && /^\s*\|[\s:|-]+\|\s*$/.test(lines[i + 1])) {
@@ -196,6 +206,18 @@ cpBtn.onclick = () => {
   if (navigator.clipboard) navigator.clipboard.writeText(META.content).then(() => done(true), () => done(fallbackCopy(META.content)));
   else done(fallbackCopy(META.content));
 };
+// per-block copy: copies the block's code text, feedback swaps the glyph
+document.getElementById("out").addEventListener("click", (e) => {
+  const b = e.target.closest(".copyblk");
+  if (!b) return;
+  const text = b.parentElement.querySelector("code").textContent;
+  const done = (ok) => {
+    b.textContent = ok ? "✓" : "✗";
+    setTimeout(() => (b.textContent = "⧉"), 900);
+  };
+  if (navigator.clipboard) navigator.clipboard.writeText(text).then(() => done(true), () => done(fallbackCopy(text)));
+  else done(fallbackCopy(text));
+});
 
 // ---------- render ----------
 let mode = EXT === "md" ? "rendered" : "raw";