From 399fec770f4f56c1df85a6f2d4858bc3f8c11c9a Mon Sep 17 00:00:00 2001 From: Rahul A Mistry <149420892+ProdigyRahul@users.noreply.github.com> Date: Sun, 25 Jan 2026 05:59:58 +0530 Subject: [PATCH] fix(app): markdown rendering with morphdom for better dom functions (#10373) --- bun.lock | 3 ++ packages/ui/package.json | 1 + packages/ui/src/components/markdown.tsx | 55 ++++++++++++++++++++++--- 3 files changed, 53 insertions(+), 6 deletions(-) diff --git a/bun.lock b/bun.lock index 34a6488ba..22002fcc0 100644 --- a/bun.lock +++ b/bun.lock @@ -423,6 +423,7 @@ "marked": "catalog:", "marked-katex-extension": "5.1.6", "marked-shiki": "catalog:", + "morphdom": "2.7.8", "remeda": "catalog:", "shiki": "catalog:", "solid-js": "catalog:", @@ -3102,6 +3103,8 @@ "mkdirp": ["mkdirp@0.5.6", "", { "dependencies": { "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw=="], + "morphdom": ["morphdom@2.7.8", "", {}, "sha512-D/fR4xgGUyVRbdMGU6Nejea1RFzYxYtyurG4Fbv2Fi/daKlWKuXGLOdXtl+3eIwL110cI2hz1ZojGICjjFLgTg=="], + "mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="], "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], diff --git a/packages/ui/package.json b/packages/ui/package.json index 20709f160..f384797f4 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -56,6 +56,7 @@ "marked": "catalog:", "marked-katex-extension": "5.1.6", "marked-shiki": "catalog:", + "morphdom": "2.7.8", "remeda": "catalog:", "shiki": "catalog:", "solid-js": "catalog:", diff --git a/packages/ui/src/components/markdown.tsx b/packages/ui/src/components/markdown.tsx index f7a1ec16f..e3102214b 100644 --- a/packages/ui/src/components/markdown.tsx +++ b/packages/ui/src/components/markdown.tsx @@ -1,6 +1,7 @@ import { useMarked } from "../context/marked" import { useI18n } from "../context/i18n" import DOMPurify from "dompurify" +import morphdom from "morphdom" import { checksum } from "@opencode-ai/util/encode" import { ComponentProps, createEffect, createResource, createSignal, onCleanup, splitProps } from "solid-js" import { isServer } from "solid-js/web" @@ -194,18 +195,61 @@ export function Markdown( { initialValue: "" }, ) + let copySetupTimer: ReturnType | undefined + let copyCleanup: (() => void) | undefined + createEffect(() => { const container = root() const content = html() if (!container) return - if (!content) return if (isServer) return - const cleanup = setupCodeCopy(container, { - copy: i18n.t("ui.message.copy"), - copied: i18n.t("ui.message.copied"), + + if (!content) { + container.innerHTML = "" + return + } + + const temp = document.createElement("div") + temp.innerHTML = content + + morphdom(container, temp, { + childrenOnly: true, + onBeforeElUpdated: (fromEl, toEl) => { + if (fromEl.isEqualNode(toEl)) return false + if (fromEl.getAttribute("data-component") === "markdown-code") { + const fromPre = fromEl.querySelector("pre") + const toPre = toEl.querySelector("pre") + if (fromPre && toPre && !fromPre.isEqualNode(toPre)) { + morphdom(fromPre, toPre) + } + return false + } + return true + }, + onBeforeNodeDiscarded: (node) => { + if (node instanceof Element) { + if (node.getAttribute("data-slot") === "markdown-copy-button") return false + if (node.getAttribute("data-component") === "markdown-code") return false + } + return true + }, }) - onCleanup(cleanup) + + if (copySetupTimer) clearTimeout(copySetupTimer) + copySetupTimer = setTimeout(() => { + if (copyCleanup) copyCleanup() + copyCleanup = setupCodeCopy(container, { + copy: i18n.t("ui.message.copy"), + copied: i18n.t("ui.message.copied"), + }) + }, 150) }) + + onCleanup(() => { + if (copySetupTimer) clearTimeout(copySetupTimer) + if (copyCleanup) copyCleanup() + }) + return (