File size: 1,848 Bytes
3e05655 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | import { checksum } from "@opencode-ai/core/util/encode"
import DOMPurify from "dompurify"
import { parseMarkdown } from "./markdown-worker"
export type MarkdownCacheEntry = {
raw: string
hash: string
html: string
}
const max = 200
const cache = new Map<string, MarkdownCacheEntry>()
const config = {
USE_PROFILES: { html: true, mathMl: true },
SANITIZE_NAMED_PROPS: true,
FORBID_TAGS: ["style"],
FORBID_CONTENTS: ["style", "script"],
ADD_TAGS: ["svg", "path"],
ADD_ATTR: ["d", "viewBox", "preserveAspectRatio", "xmlns", "target"],
}
if (typeof window !== "undefined" && DOMPurify.isSupported) {
DOMPurify.addHook("afterSanitizeAttributes", (node: Element) => {
if (!(node instanceof HTMLAnchorElement)) return
if (node.target !== "_blank") return
const rel = node.getAttribute("rel") ?? ""
const set = new Set(rel.split(/\s+/).filter(Boolean))
set.add("noopener")
set.add("noreferrer")
node.setAttribute("rel", Array.from(set).join(" "))
})
}
export function sanitizeMarkdown(html: string) {
if (!DOMPurify.isSupported) return ""
return DOMPurify.sanitize(html, config)
}
export function getCachedMarkdown(key: string) {
return cache.get(key)
}
export function touchCachedMarkdown(key: string, value: MarkdownCacheEntry) {
cache.delete(key)
cache.set(key, value)
if (cache.size <= max) return
const first = cache.keys().next().value
if (!first) return
cache.delete(first)
}
export async function preloadMarkdown(text: string, cacheKey: string) {
const key = `${cacheKey}:0:full`
const cached = getCachedMarkdown(key)
if (cached?.raw === text) {
touchCachedMarkdown(key, cached)
return
}
const hash = checksum(text)
if (!hash) return
touchCachedMarkdown(key, {
raw: text,
hash,
html: sanitizeMarkdown(await parseMarkdown(text)),
})
}
|