diff options
| author | Rahul A Mistry <[email protected]> | 2026-01-25 05:59:58 +0530 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-01-24 18:29:58 -0600 |
| commit | 399fec770f4f56c1df85a6f2d4858bc3f8c11c9a (patch) | |
| tree | f25fad49b5b7842abf7f7d88ab76314e0da6d6ea | |
| parent | 8d1a66d04333edda5288007cb7fbdae40d204d86 (diff) | |
| download | opencode-399fec770f4f56c1df85a6f2d4858bc3f8c11c9a.tar.gz opencode-399fec770f4f56c1df85a6f2d4858bc3f8c11c9a.zip | |
fix(app): markdown rendering with morphdom for better dom functions (#10373)
| -rw-r--r-- | bun.lock | 3 | ||||
| -rw-r--r-- | packages/ui/package.json | 1 | ||||
| -rw-r--r-- | packages/ui/src/components/markdown.tsx | 55 |
3 files changed, 53 insertions, 6 deletions
@@ -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": ["[email protected]", "", { "dependencies": { "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw=="], + "morphdom": ["[email protected]", "", {}, "sha512-D/fR4xgGUyVRbdMGU6Nejea1RFzYxYtyurG4Fbv2Fi/daKlWKuXGLOdXtl+3eIwL110cI2hz1ZojGICjjFLgTg=="], + "mrmime": ["[email protected]", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="], "ms": ["[email protected]", "", {}, "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<typeof setTimeout> | 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 ( <div data-component="markdown" @@ -213,7 +257,6 @@ export function Markdown( ...(local.classList ?? {}), [local.class ?? ""]: !!local.class, }} - innerHTML={html.latest} ref={setRoot} {...others} /> |
