import { createEffect, createSignal, onMount, onCleanup } from "solid-js" import { renderMarkdown, onLanguagesLoaded, initMarkdown } from "../lib/markdown" import type { TextPart } from "../types/message" interface MarkdownProps { part: TextPart isDark?: boolean size?: "base" | "sm" | "tight" } export function Markdown(props: MarkdownProps) { const [html, setHtml] = createSignal("") let containerRef: HTMLDivElement | undefined let latestRequestedText = "" createEffect(async () => { const part = props.part const text = part.text || "" const dark = Boolean(props.isDark) const themeKey = dark ? "dark" : "light" latestRequestedText = text await initMarkdown(dark) const cache = part.renderCache if (cache && cache.text === text && cache.theme === themeKey) { setHtml(cache.html) return } try { const rendered = await renderMarkdown(text) if (latestRequestedText === text) { setHtml(rendered) part.renderCache = { text, html: rendered, theme: themeKey } } } catch (error) { console.error("Failed to render markdown:", error) if (latestRequestedText === text) { setHtml(text) part.renderCache = { text, html: text, theme: themeKey } } } }) onMount(() => { const handleClick = async (e: Event) => { const target = e.target as HTMLElement const copyButton = target.closest(".code-block-copy") as HTMLButtonElement if (copyButton) { e.preventDefault() const code = copyButton.getAttribute("data-code") if (code) { const decodedCode = decodeURIComponent(code) await navigator.clipboard.writeText(decodedCode) const copyText = copyButton.querySelector(".copy-text") if (copyText) { copyText.textContent = "Copied!" setTimeout(() => { copyText.textContent = "Copy" }, 2000) } } } } containerRef?.addEventListener("click", handleClick) // Register listener for language loading completion const cleanupLanguageListener = onLanguagesLoaded(async () => { const part = props.part const text = part.text || "" if (latestRequestedText !== text) { return } try { const rendered = await renderMarkdown(text) if (latestRequestedText === text) { setHtml(rendered) part.renderCache = { text, html: rendered } } } catch (error) { console.error("Failed to re-render markdown after language load:", error) } }) onCleanup(() => { containerRef?.removeEventListener("click", handleClick) cleanupLanguageListener() }) }) const proseClass = () => { const classes = ["prose", "dark:prose-invert", "max-w-none"] if (props.size === "tight") { classes.push("prose-sm", "prose-tight") } else if (props.size === "sm") { classes.push("prose-sm") } return classes.join(" ") } return
}