Files
Jyotisha/frontend/src/components/markdown-code-block.tsx
T
Jesse_ChenandCursor 2fdcb14fd6 fix(report): draw North Indian charts from fences after skipHtml dropped SVG
Longform report pages kept the D1/D9/Moon and varga headings but skipHtml
stripped the engine's inline SVG. Emit a jyotish-chart JSON fence beside
each SVG and render a diamond chart on the reader without relaxing HTML
sanitization. BUG-607.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-09 15:11:00 +08:00

59 lines
2.0 KiB
TypeScript

"use client";
import { Check, Copy } from "lucide-react";
import { isValidElement, useEffect, useRef, useState, type ReactNode } from "react";
/** The fenced language, from the `language-xxx` class react-markdown puts on <code>. */
export function fencedLanguage(node: ReactNode): string {
if (!isValidElement<{ className?: string }>(node)) return "";
return /language-([\w+-]+)/.exec(node.props.className ?? "")?.[1] ?? "";
}
/** The raw source inside the fence, for the copy button. */
export function fencedSource(node: ReactNode): string {
if (typeof node === "string") return node;
if (Array.isArray(node)) return node.map(fencedSource).join("");
if (isValidElement<{ children?: ReactNode }>(node)) return fencedSource(node.props.children);
return "";
}
export function MarkdownCodeBlock({ children }: { readonly children?: ReactNode }) {
const [copied, setCopied] = useState(false);
const resetTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
const language = fencedLanguage(children);
const source = fencedSource(children);
useEffect(() => () => {
if (resetTimer.current) clearTimeout(resetTimer.current);
}, []);
async function copy() {
try {
await navigator.clipboard.writeText(source);
} catch {
return;
}
setCopied(true);
if (resetTimer.current) clearTimeout(resetTimer.current);
resetTimer.current = setTimeout(() => setCopied(false), 1600);
}
return (
<div className="markdown-code">
<div className="markdown-code-bar">
<span className="markdown-code-language">{language || "代码"}</span>
<button
aria-label={copied ? "已复制代码" : "复制代码"}
className="markdown-code-copy"
onClick={() => void copy()}
type="button"
>
{copied ? <Check aria-hidden="true" /> : <Copy aria-hidden="true" />}
<span>{copied ? "已复制" : "复制"}</span>
</button>
</div>
<pre>{children}</pre>
</div>
);
}