import { memo, useCallback, useRef, useState, type ComponentPropsWithoutRef } from "react"; import ReactMarkdown, { type Components } from "react-markdown"; import remarkBreaks from "remark-breaks"; import remarkGfm from "remark-gfm"; import { t } from "./i18n"; import "./markdown.css"; const protocolPattern = /^([a-z][a-z\d+.-]*):/i; const safeProtocols = new Set(["http", "https", "mailto", "tel"]); export function sanitizeMarkdownUrl(url: string): string | undefined { const value = url.trim(); const protocol = value.match(protocolPattern)?.[1]?.toLowerCase(); if (protocol) return safeProtocols.has(protocol) ? value : undefined; if (value.startsWith("#") && !value.toLowerCase().startsWith("#javascript")) return value; return undefined; } function CopyIcon() { return ( ); } function CheckIcon() { return ( ); } function useCopiedFlag() { const [copied, setCopied] = useState(false); const timerRef = useRef(undefined); const markCopied = useCallback(() => { setCopied(true); window.clearTimeout(timerRef.current); timerRef.current = window.setTimeout(() => setCopied(false), 1500); }, []); return {copied, markCopied}; } function copyText(text: string): Promise { if (!navigator.clipboard) return Promise.resolve(false); return navigator.clipboard.writeText(text).then(() => true).catch(() => false); } function CodeBlock(props: ComponentPropsWithoutRef<"pre">) { const preRef = useRef(null); const {copied, markCopied} = useCopiedFlag(); const handleCopy = useCallback(() => { void copyText(preRef.current?.textContent ?? "").then((ok) => { if (ok) markCopied(); }); }, [markCopied]); return (
      
    
); } const components: Components = { a({node: _node, ...props}) { return ; }, img({node: _node, ...props}) { return {props.alt; }, pre({node: _node, ...props}) { return ; }, }; export function CopyMessageButton({text}:{text:string}) { const {copied, markCopied} = useCopiedFlag(); if (!text.trim()) return null; return ( ); } export const ChatMarkdown = memo(function ChatMarkdown({children}:{children:string}) { return (
sanitizeMarkdownUrl(url) ?? ""} > {children}
); });