({ node }: NodeViewProps)
| 9 | // - Copy button that lifts text out of the node, writes to clipboard, |
| 10 | // and flashes a "Copied" state for 1.2s |
| 11 | export function CodeBlockView({ node }: NodeViewProps) { |
| 12 | const [copied, setCopied] = useState(false); |
| 13 | const language = (node.attrs.language as string | null) ?? null; |
| 14 | |
| 15 | const onCopy = () => { |
| 16 | const text = node.textContent ?? ""; |
| 17 | void navigator.clipboard.writeText(text).then(() => { |
| 18 | setCopied(true); |
| 19 | window.setTimeout(() => setCopied(false), 1200); |
| 20 | }); |
| 21 | }; |
| 22 | |
| 23 | return ( |
| 24 | <NodeViewWrapper as="pre" className="hb-md-code-wrap" data-language={language || undefined}> |
| 25 | <div className="hb-md-code__chrome" contentEditable={false}> |
| 26 | {language ? ( |
| 27 | <span className="hb-md-code__lang" aria-label={`Language: ${language}`}> |
| 28 | {language} |
| 29 | </span> |
| 30 | ) : ( |
| 31 | <span className="hb-md-code__lang hb-md-code__lang--muted">Plain</span> |
| 32 | )} |
| 33 | <button |
| 34 | type="button" |
| 35 | className="hb-md-code__copy" |
| 36 | onMouseDown={(e) => e.preventDefault()} |
| 37 | onClick={onCopy} |
| 38 | aria-label="Copy code" |
| 39 | data-copied={copied || undefined} |
| 40 | > |
| 41 | {copied ? ( |
| 42 | <> |
| 43 | <IconCheck className="hb-md-code__copy-icon" /> |
| 44 | <span>Copied</span> |
| 45 | </> |
| 46 | ) : ( |
| 47 | <> |
| 48 | <IconCopy className="hb-md-code__copy-icon" /> |
| 49 | <span>Copy</span> |
| 50 | </> |
| 51 | )} |
| 52 | </button> |
| 53 | </div> |
| 54 | <NodeViewContent<"code"> as="code" className={language ? `language-${language}` : undefined} /> |
| 55 | </NodeViewWrapper> |
| 56 | ); |
| 57 | } |
nothing calls this directly
no outgoing calls
no test coverage detected