Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | 2x 7x 6x 15x 15x 5x 5x 4x 4x 4x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 2x 2x 1x 1x 1x 1x 1x 1x 5x 5x 5x 5x 6x 6x 6x 6x 7x | 'use client';
import { useEffect } from 'react';
/**
* Post-hydration hydrator: finds every <pre><code> in the rendered markdown
* and augments it with a copy button + optional filename/language header.
* Runs on mount and re-runs when the document body mutates so newly injected
* blocks (e.g. from client-side navigation) are still wired up.
*/
export function CodeBlockHydrator() {
useEffect(() => {
const decorate = () => {
const pres = document.querySelectorAll<HTMLPreElement>('pre:not([data-cb-hydrated])');
pres.forEach((pre) => {
const code = pre.querySelector('code');
if (!code) return;
pre.dataset.cbHydrated = 'true';
const languageMatch = Array.from(code.classList).find((c) => c.startsWith('language-'));
const language = languageMatch ? languageMatch.replace('language-', '') : '';
const wrapper = document.createElement('div');
wrapper.className = 'code-block';
const header = document.createElement('div');
header.className = 'code-block__header';
const meta = document.createElement('span');
meta.className = 'code-block__lang';
meta.textContent = language || 'text';
header.appendChild(meta);
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'code-block__copy';
btn.textContent = 'Copy';
btn.addEventListener('click', async () => {
try {
await navigator.clipboard.writeText(code.textContent ?? '');
btn.textContent = 'Copied';
btn.setAttribute('data-copied', 'true');
setTimeout(() => {
btn.textContent = 'Copy';
btn.removeAttribute('data-copied');
}, 1500);
} catch {
btn.textContent = 'Error';
}
});
header.appendChild(btn);
pre.parentNode?.insertBefore(wrapper, pre);
wrapper.appendChild(header);
wrapper.appendChild(pre);
});
};
decorate();
const observer = new MutationObserver(decorate);
observer.observe(document.body, { childList: true, subtree: true });
return () => observer.disconnect();
}, []);
return null;
}
|