fix(app): markdown rendering with morphdom for better dom functions (#10373)
This commit is contained in:
3
bun.lock
3
bun.lock
@@ -423,6 +423,7 @@
|
|||||||
"marked": "catalog:",
|
"marked": "catalog:",
|
||||||
"marked-katex-extension": "5.1.6",
|
"marked-katex-extension": "5.1.6",
|
||||||
"marked-shiki": "catalog:",
|
"marked-shiki": "catalog:",
|
||||||
|
"morphdom": "2.7.8",
|
||||||
"remeda": "catalog:",
|
"remeda": "catalog:",
|
||||||
"shiki": "catalog:",
|
"shiki": "catalog:",
|
||||||
"solid-js": "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=="],
|
"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=="],
|
"mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="],
|
||||||
|
|
||||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||||
|
|||||||
@@ -56,6 +56,7 @@
|
|||||||
"marked": "catalog:",
|
"marked": "catalog:",
|
||||||
"marked-katex-extension": "5.1.6",
|
"marked-katex-extension": "5.1.6",
|
||||||
"marked-shiki": "catalog:",
|
"marked-shiki": "catalog:",
|
||||||
|
"morphdom": "2.7.8",
|
||||||
"remeda": "catalog:",
|
"remeda": "catalog:",
|
||||||
"shiki": "catalog:",
|
"shiki": "catalog:",
|
||||||
"solid-js": "catalog:",
|
"solid-js": "catalog:",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useMarked } from "../context/marked"
|
import { useMarked } from "../context/marked"
|
||||||
import { useI18n } from "../context/i18n"
|
import { useI18n } from "../context/i18n"
|
||||||
import DOMPurify from "dompurify"
|
import DOMPurify from "dompurify"
|
||||||
|
import morphdom from "morphdom"
|
||||||
import { checksum } from "@opencode-ai/util/encode"
|
import { checksum } from "@opencode-ai/util/encode"
|
||||||
import { ComponentProps, createEffect, createResource, createSignal, onCleanup, splitProps } from "solid-js"
|
import { ComponentProps, createEffect, createResource, createSignal, onCleanup, splitProps } from "solid-js"
|
||||||
import { isServer } from "solid-js/web"
|
import { isServer } from "solid-js/web"
|
||||||
@@ -194,18 +195,61 @@ export function Markdown(
|
|||||||
{ initialValue: "" },
|
{ initialValue: "" },
|
||||||
)
|
)
|
||||||
|
|
||||||
|
let copySetupTimer: ReturnType<typeof setTimeout> | undefined
|
||||||
|
let copyCleanup: (() => void) | undefined
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const container = root()
|
const container = root()
|
||||||
const content = html()
|
const content = html()
|
||||||
if (!container) return
|
if (!container) return
|
||||||
if (!content) return
|
|
||||||
if (isServer) return
|
if (isServer) return
|
||||||
const cleanup = setupCodeCopy(container, {
|
|
||||||
copy: i18n.t("ui.message.copy"),
|
if (!content) {
|
||||||
copied: i18n.t("ui.message.copied"),
|
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 (
|
return (
|
||||||
<div
|
<div
|
||||||
data-component="markdown"
|
data-component="markdown"
|
||||||
@@ -213,7 +257,6 @@ export function Markdown(
|
|||||||
...(local.classList ?? {}),
|
...(local.classList ?? {}),
|
||||||
[local.class ?? ""]: !!local.class,
|
[local.class ?? ""]: !!local.class,
|
||||||
}}
|
}}
|
||||||
innerHTML={html.latest}
|
|
||||||
ref={setRoot}
|
ref={setRoot}
|
||||||
{...others}
|
{...others}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user