Download packages/session-ui/src/components/markdown.tsx from SaylorTwift/opencode: direct link, hf CLI and curl.
- Browser
- Download file 22.7 kB
-
https://huggingface.co/SaylorTwift/opencode/resolve/main/packages/session-ui/src/components/markdown.tsx
- Command line
-
hf download hf://SaylorTwift/opencode/packages/session-ui/src/components/markdown.tsx
-
curl -L -o markdown.tsx https://huggingface.co/SaylorTwift/opencode/resolve/main/packages/session-ui/src/components/markdown.tsx
22.7 kB
| import { useI18n } from "@opencode-ai/ui/context/i18n" | |
| import morphdom from "morphdom" | |
| import { checksum } from "@opencode-ai/core/util/encode" | |
| import { | |
| type Accessor, | |
| type ComponentProps, | |
| createEffect, | |
| createResource, | |
| createSignal, | |
| createUniqueId, | |
| onCleanup, | |
| type Setter, | |
| splitProps, | |
| } from "solid-js" | |
| import { isServer, render } from "solid-js/web" | |
| import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon" | |
| import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2" | |
| import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2" | |
| import { canReusePendingBlock, completedProjection } from "./markdown-projection" | |
| import type { Block, Projection } from "./markdown-stream" | |
| import { | |
| disposeMarkdownProjection, | |
| disposeStreamingCode, | |
| highlightStreamingCode, | |
| MarkdownWorkerDisposedError, | |
| MarkdownWorkerSupersededError, | |
| MarkdownWorkerUnavailableError, | |
| parseMarkdown, | |
| projectMarkdown, | |
| } from "./markdown-worker" | |
| import { markdownBlockKey, type MarkdownToken } from "./markdown-worker-protocol" | |
| import { shouldResetCodeTokens, type RenderedCodeState } from "./markdown-code-state" | |
| import { getCachedMarkdown, sanitizeMarkdown, touchCachedMarkdown, type MarkdownCacheEntry } from "./markdown-cache" | |
| import { inlineCodeKind } from "./markdown-inline-code-kind" | |
| type RenderedBlock = | |
| | (MarkdownCacheEntry & { key: string; mode: Exclude<Block["mode"], "code"> }) | |
| | { | |
| key: string | |
| mode: "code" | |
| raw: string | |
| hash: string | |
| language: string | |
| complete: boolean | |
| generation: number | |
| stable: MarkdownToken[] | |
| unstable: MarkdownToken[] | |
| } | |
| type RenderResult = { | |
| text: string | |
| blocks: RenderedBlock[] | |
| } | |
| const renderedCodeTokens = new WeakMap<HTMLDivElement, RenderedCodeState>() | |
| function escape(text: string) { | |
| return text | |
| .replace(/&/g, "&") | |
| .replace(/</g, "<") | |
| .replace(/>/g, ">") | |
| .replace(/"/g, """) | |
| .replace(/'/g, "'") | |
| } | |
| function fallback(markdown: string) { | |
| return escape(markdown).replace(/\r\n?/g, "\n").replace(/\n/g, "<br>") | |
| } | |
| async function code(text: string, language: string | undefined, key: string, complete = false) { | |
| try { | |
| const result = await highlightStreamingCode(key, text, language ?? "text", complete) | |
| return { | |
| language: result.language, | |
| generation: result.generation, | |
| stable: result.stable, | |
| unstable: result.unstable, | |
| } | |
| } catch (error) { | |
| if ( | |
| !(error instanceof MarkdownWorkerDisposedError) && | |
| !(error instanceof MarkdownWorkerSupersededError) && | |
| !(error instanceof MarkdownWorkerUnavailableError) | |
| ) | |
| console.error("Markdown highlighting worker failed", error) | |
| return { language: language ?? "text", generation: 0, stable: [], unstable: [[text, ""] as MarkdownToken] } | |
| } | |
| } | |
| type CopyLabels = { | |
| copy: string | |
| copied: string | |
| } | |
| type CopyButtonState = { | |
| setLabels: Setter<CopyLabels> | |
| setCopied: Setter<boolean> | |
| dispose: () => void | |
| } | |
| const copyButtonState = new WeakMap<HTMLElement, CopyButtonState>() | |
| const urlPattern = /^https?:\/\/[^\s<>()`"']+$/ | |
| function codeUrl(text: string) { | |
| const href = text.trim().replace(/[),.;!?]+$/, "") | |
| if (!urlPattern.test(href)) return | |
| try { | |
| const url = new URL(href) | |
| return url.toString() | |
| } catch { | |
| return | |
| } | |
| } | |
| function createCopyButton(labels: CopyLabels) { | |
| const host = document.createElement("div") | |
| host.setAttribute("data-slot", "markdown-copy-button") | |
| const state: Partial<CopyButtonState> = {} | |
| const dispose = render(() => { | |
| const [labelState, setLabels] = createSignal(labels, { equals: false }) | |
| const [copied, setCopied] = createSignal(false) | |
| state.setLabels = setLabels | |
| state.setCopied = setCopied | |
| return <MarkdownCopyButton labels={labelState} copied={copied} /> | |
| }, host) | |
| state.dispose = dispose | |
| copyButtonState.set(host, state as CopyButtonState) | |
| return host | |
| } | |
| function MarkdownCopyButton(props: { labels: Accessor<CopyLabels>; copied: Accessor<boolean> }) { | |
| const label = () => (props.copied() ? props.labels().copied : props.labels().copy) | |
| return ( | |
| <TooltipV2 placement="top" value={label()}> | |
| <IconButtonV2 | |
| type="button" | |
| size="normal" | |
| variant="ghost-muted" | |
| aria-label={label()} | |
| icon={ | |
| <> | |
| <IconV2 name="outline-copy" data-copy-icon /> | |
| <IconV2 name="check" data-check-icon /> | |
| </> | |
| } | |
| /> | |
| </TooltipV2> | |
| ) | |
| } | |
| function setCopyState(host: HTMLElement, labels: CopyLabels, copied: boolean) { | |
| const state = copyButtonState.get(host) | |
| state?.setLabels(labels) | |
| state?.setCopied(copied) | |
| if (copied) { | |
| host.setAttribute("data-copied", "true") | |
| return | |
| } | |
| host.removeAttribute("data-copied") | |
| } | |
| function disposeCopyButton(host: HTMLElement) { | |
| copyButtonState.get(host)?.dispose() | |
| copyButtonState.delete(host) | |
| } | |
| function disposeCopyButtons(root: Element) { | |
| const hosts = [ | |
| ...(root instanceof HTMLElement && root.getAttribute("data-slot") === "markdown-copy-button" ? [root] : []), | |
| ...Array.from(root.querySelectorAll('[data-slot="markdown-copy-button"]')).filter( | |
| (el): el is HTMLElement => el instanceof HTMLElement, | |
| ), | |
| ] | |
| hosts.forEach(disposeCopyButton) | |
| } | |
| const shellLanguages = new Set(["bash", "sh", "shell", "zsh", "fish", "console", "terminal"]) | |
| function codeKind(language: string | undefined) { | |
| const value = language?.toLowerCase() | |
| if (!value) return | |
| if (shellLanguages.has(value)) return "shell" | |
| } | |
| function codeLanguage(block: HTMLPreElement) { | |
| const code = block.querySelector("code") | |
| if (!(code instanceof HTMLElement)) return | |
| return code.className.match(/(?:^|\s)language-([^\s]+)/)?.[1] | |
| } | |
| function applyCodeMetadata(wrapper: HTMLElement, language: string | undefined) { | |
| if (!document.body.hasAttribute("data-new-layout")) { | |
| delete wrapper.dataset.language | |
| delete wrapper.dataset.codeKind | |
| return | |
| } | |
| if (language) wrapper.dataset.language = language | |
| else delete wrapper.dataset.language | |
| const kind = codeKind(language) | |
| if (kind) wrapper.dataset.codeKind = kind | |
| else delete wrapper.dataset.codeKind | |
| } | |
| function ensureCodeWrapper(block: HTMLPreElement, labels: CopyLabels) { | |
| const parent = block.parentElement | |
| if (!parent) return | |
| const wrapped = parent.getAttribute("data-component") === "markdown-code" | |
| if (!wrapped) { | |
| const wrapper = document.createElement("div") | |
| wrapper.setAttribute("data-component", "markdown-code") | |
| applyCodeMetadata(wrapper, codeLanguage(block)) | |
| parent.replaceChild(wrapper, block) | |
| wrapper.appendChild(block) | |
| wrapper.appendChild(createCopyButton(labels)) | |
| return | |
| } | |
| applyCodeMetadata(parent, codeLanguage(block)) | |
| const buttons = Array.from(parent.querySelectorAll('[data-slot="markdown-copy-button"]')).filter( | |
| (el): el is HTMLButtonElement => el instanceof HTMLButtonElement, | |
| ) | |
| if (buttons.length === 0) { | |
| parent.appendChild(createCopyButton(labels)) | |
| return | |
| } | |
| for (const button of buttons.slice(1)) { | |
| disposeCopyButton(button) | |
| button.remove() | |
| } | |
| } | |
| function markCodeLinks(root: HTMLDivElement) { | |
| const codeNodes = Array.from(root.querySelectorAll(":not(pre) > code")) | |
| for (const code of codeNodes) { | |
| const href = codeUrl(code.textContent ?? "") | |
| const parentLink = | |
| code.parentElement instanceof HTMLAnchorElement && code.parentElement.classList.contains("external-link") | |
| ? code.parentElement | |
| : null | |
| if (!href) { | |
| if (parentLink) parentLink.replaceWith(code) | |
| continue | |
| } | |
| if (parentLink) { | |
| parentLink.href = href | |
| continue | |
| } | |
| const link = document.createElement("a") | |
| link.href = href | |
| link.className = "external-link" | |
| link.target = "_blank" | |
| link.rel = "noopener noreferrer" | |
| code.parentNode?.replaceChild(link, code) | |
| link.appendChild(code) | |
| } | |
| } | |
| function markInlineCode(root: HTMLDivElement) { | |
| const codeNodes = Array.from(root.querySelectorAll(":not(pre) > code")) | |
| for (const code of codeNodes) { | |
| if (!(code instanceof HTMLElement)) continue | |
| delete code.dataset.inlineCodeKind | |
| const kind = inlineCodeKind(code.textContent ?? "") | |
| if (kind) code.dataset.inlineCodeKind = kind | |
| } | |
| } | |
| function decorate(root: HTMLDivElement, labels: CopyLabels) { | |
| const blocks = Array.from(root.querySelectorAll("pre")) | |
| for (const block of blocks) { | |
| ensureCodeWrapper(block, labels) | |
| } | |
| if (!document.body.hasAttribute("data-new-layout")) return | |
| markInlineCode(root) | |
| markCodeLinks(root) | |
| } | |
| function setupCodeCopy(root: HTMLDivElement, getLabels: () => CopyLabels) { | |
| const timeouts = new Map<HTMLElement, ReturnType<typeof setTimeout>>() | |
| const updateLabel = (button: HTMLElement) => { | |
| const labels = getLabels() | |
| const copied = button.getAttribute("data-copied") === "true" | |
| setCopyState(button, labels, copied) | |
| } | |
| const handleClick = async (event: MouseEvent) => { | |
| const target = event.target | |
| if (!(target instanceof Element)) return | |
| const button = target.closest('[data-slot="markdown-copy-button"]') | |
| if (!(button instanceof HTMLElement)) return | |
| const code = button.closest('[data-component="markdown-code"]')?.querySelector("code") | |
| const content = code?.textContent ?? "" | |
| if (!content) return | |
| const clipboard = navigator?.clipboard | |
| if (!clipboard) return | |
| await clipboard.writeText(content) | |
| const labels = getLabels() | |
| setCopyState(button, labels, true) | |
| const existing = timeouts.get(button) | |
| if (existing) clearTimeout(existing) | |
| const timeout = setTimeout(() => setCopyState(button, labels, false), 2000) | |
| timeouts.set(button, timeout) | |
| } | |
| const buttons = Array.from(root.querySelectorAll('[data-slot="markdown-copy-button"]')) | |
| for (const button of buttons) { | |
| if (button instanceof HTMLElement) updateLabel(button) | |
| } | |
| root.addEventListener("click", handleClick) | |
| return () => { | |
| root.removeEventListener("click", handleClick) | |
| for (const timeout of timeouts.values()) { | |
| clearTimeout(timeout) | |
| } | |
| disposeCopyButtons(root) | |
| } | |
| } | |
| function initialResult(text: string, key: string | undefined, projection: Projection, owner: string): RenderResult { | |
| if (!text) return { text, blocks: [] } | |
| const base = key ?? checksum(text) | |
| if (base) { | |
| const blocks = projection.blocks.flatMap((block, index) => { | |
| if (block.mode === "code") return [] | |
| const cacheKey = `${base}:${index}:${block.mode}` | |
| const cached = getCachedMarkdown(cacheKey) | |
| if (cached?.raw !== block.raw) return [] | |
| return [{ key: `${owner}:${cacheKey}`, mode: block.mode, ...cached }] | |
| }) | |
| if (blocks.length === projection.blocks.length) return { text, blocks } | |
| } | |
| return { | |
| text, | |
| blocks: [ | |
| { | |
| key: "initial", | |
| mode: "full", | |
| raw: text, | |
| hash: checksum(text) ?? "", | |
| html: fallback(text), | |
| }, | |
| ], | |
| } | |
| } | |
| function pendingProjection(text: string): Projection { | |
| return { text, blocks: text ? [{ raw: text, src: text, mode: "live" }] : [] } | |
| } | |
| export function Markdown( | |
| props: ComponentProps<"div"> & { | |
| text: string | |
| cacheKey?: string | |
| streaming?: boolean | |
| class?: string | |
| classList?: Record<string, boolean> | |
| }, | |
| ) { | |
| const [local, others] = splitProps(props, ["text", "cacheKey", "streaming", "class", "classList"]) | |
| const i18n = useI18n() | |
| const [root, setRoot] = createSignal<HTMLDivElement>() | |
| const owner = createUniqueId() | |
| const activeCodeKeys = new Set<string>() | |
| const completedCode = new Map<string, Extract<RenderedBlock, { mode: "code" }>>() | |
| let streamed = false | |
| const [projection] = createResource( | |
| () => { | |
| if (isServer) return | |
| const live = local.streaming ?? false | |
| if (live) streamed = true | |
| if (!live && !streamed) return | |
| return { key: owner, text: local.text, live } | |
| }, | |
| (src) => projectMarkdown(src.key, src.text, src.live), | |
| { initialValue: pendingProjection("") }, | |
| ) | |
| const currentProjection = () => { | |
| if (!(local.streaming ?? false) && !streamed) return completedProjection(local.text) | |
| const value = projection.latest | |
| if (value?.text === local.text) return value | |
| if (value?.text) return value | |
| return pendingProjection(local.text) | |
| } | |
| const [html] = createResource( | |
| () => { | |
| if (isServer) | |
| return { | |
| text: local.text, | |
| key: local.cacheKey, | |
| projection: pendingProjection(local.text), | |
| } | |
| const value = !(local.streaming ?? false) && !streamed ? completedProjection(local.text) : projection.latest | |
| if (!value || value.text !== local.text) return | |
| return { | |
| text: local.text, | |
| key: local.cacheKey, | |
| projection: value, | |
| } | |
| }, | |
| async (src) => { | |
| if (isServer) | |
| return { | |
| text: src.text, | |
| blocks: [ | |
| { | |
| key: "server", | |
| mode: "full" as const, | |
| raw: src.text, | |
| hash: checksum(src.text) ?? "", | |
| html: fallback(src.text), | |
| }, | |
| ], | |
| } satisfies RenderResult | |
| if (!src.text) return { text: src.text, blocks: [] } satisfies RenderResult | |
| const base = src.key ?? checksum(src.text) | |
| return Promise.all( | |
| src.projection.blocks.map(async (block, index) => { | |
| const key = base ? `${base}:${index}:${block.mode}` : undefined | |
| const blockKey = markdownBlockKey(owner, src.key, index, block.mode) | |
| if (block.mode === "code") { | |
| const cached = completedCode.get(blockKey) | |
| if (block.complete && cached?.raw === block.raw) return cached | |
| const result = await code(block.src, block.language, blockKey, block.complete) | |
| const rendered = { | |
| key: blockKey, | |
| mode: block.mode, | |
| raw: block.raw, | |
| hash: String(block.raw.length), | |
| complete: !!block.complete, | |
| ...result, | |
| } | |
| if (block.complete) completedCode.set(blockKey, rendered) | |
| return rendered | |
| } | |
| if (key) { | |
| const cached = getCachedMarkdown(key) | |
| if (cached?.raw === block.raw) { | |
| touchCachedMarkdown(key, cached) | |
| return { key: blockKey, mode: block.mode, ...cached } | |
| } | |
| } | |
| const hash = checksum(block.raw) | |
| const safe = sanitizeMarkdown(await parseMarkdown(block.src)) | |
| if (key && hash) touchCachedMarkdown(key, { raw: block.raw, hash, html: safe }) | |
| return { key: blockKey, mode: block.mode, raw: block.raw, hash: hash ?? "", html: safe } | |
| }), | |
| ) | |
| .then((blocks) => ({ text: src.text, blocks }) satisfies RenderResult) | |
| .catch( | |
| () => | |
| ({ | |
| text: src.text, | |
| blocks: [ | |
| { | |
| key: base ?? "fallback", | |
| mode: "full" as const, | |
| raw: src.text, | |
| hash: checksum(src.text) ?? "", | |
| html: fallback(src.text), | |
| }, | |
| ], | |
| }) satisfies RenderResult, | |
| ) | |
| }, | |
| { | |
| initialValue: initialResult( | |
| local.text, | |
| local.cacheKey, | |
| local.streaming ? pendingProjection(local.text) : completedProjection(local.text), | |
| owner, | |
| ), | |
| }, | |
| ) | |
| let copyCleanup: (() => void) | undefined | |
| createEffect(() => { | |
| const container = root() | |
| const result = html.latest ?? html() | |
| const projected = currentProjection() | |
| const content = local.text ? pendingBlocks(result, projected, local.cacheKey, owner) : [] | |
| if (!container) return | |
| if (isServer) return | |
| if (content.length === 0) { | |
| disposeCopyButtons(container) | |
| container.innerHTML = "" | |
| return | |
| } | |
| const labels = { | |
| copy: i18n.t("ui.message.copy"), | |
| copied: i18n.t("ui.message.copied"), | |
| } | |
| const nextCodeKeys = new Set(content.filter((block) => block.mode === "code").map((block) => block.key)) | |
| activeCodeKeys.forEach((key) => { | |
| if (!nextCodeKeys.has(key)) disposeCode(key) | |
| }) | |
| activeCodeKeys.clear() | |
| nextCodeKeys.forEach((key) => activeCodeKeys.add(key)) | |
| content.forEach((block, index) => updateBlock(container, index, block, labels)) | |
| while (container.children.length > content.length) { | |
| const child = container.lastElementChild | |
| if (!child) break | |
| disposeCopyButtons(child) | |
| child.remove() | |
| } | |
| container | |
| .querySelectorAll<HTMLElement>('[data-slot="markdown-copy-button"]') | |
| .forEach((button) => setCopyState(button, labels, button.dataset.copied === "true")) | |
| if (!copyCleanup) | |
| copyCleanup = setupCodeCopy(container, () => ({ | |
| copy: i18n.t("ui.message.copy"), | |
| copied: i18n.t("ui.message.copied"), | |
| })) | |
| }) | |
| onCleanup(() => { | |
| if (copyCleanup) copyCleanup() | |
| disposeMarkdownProjection(owner) | |
| activeCodeKeys.forEach(disposeCode) | |
| completedCode.clear() | |
| }) | |
| return ( | |
| <div | |
| data-component="markdown" | |
| dir="auto" | |
| classList={{ | |
| ...local.classList, | |
| [local.class ?? ""]: !!local.class, | |
| }} | |
| ref={setRoot} | |
| {...others} | |
| /> | |
| ) | |
| } | |
| function pendingBlocks( | |
| result: RenderResult | undefined, | |
| projection: Projection | undefined, | |
| cacheKey: string | undefined, | |
| owner: string, | |
| ) { | |
| if (!result) return [] | |
| if (!projection || result.text === projection.text) return result.blocks | |
| const initial = result.blocks.length === 1 && result.blocks[0]?.key === "initial" | |
| return projection.blocks.map((block, index) => { | |
| const current = initial ? undefined : result.blocks[index] | |
| if (current && canReusePendingBlock(current, block)) return current | |
| const key = markdownBlockKey(owner, cacheKey, index, block.mode) | |
| if (block.mode !== "code") | |
| return { key, mode: block.mode, raw: block.raw, hash: String(block.raw.length), html: fallback(block.src) } | |
| return { | |
| key, | |
| mode: block.mode, | |
| raw: block.raw, | |
| hash: String(block.raw.length), | |
| language: block.language ?? "text", | |
| complete: !!block.complete, | |
| stable: [], | |
| generation: 0, | |
| unstable: [[block.src, ""] as MarkdownToken], | |
| } | |
| }) | |
| } | |
| function disposeCode(key: string) { | |
| disposeStreamingCode(key) | |
| } | |
| function updateBlock(container: HTMLDivElement, index: number, block: RenderedBlock, labels: CopyLabels) { | |
| const current = container.children[index] | |
| if (block.mode === "code") { | |
| updateCodeBlock(container, current, block, labels) | |
| return | |
| } | |
| if ( | |
| current instanceof HTMLDivElement && | |
| current.dataset.markdownKey === block.key && | |
| current.dataset.markdownHash === block.hash | |
| ) | |
| return | |
| const next = document.createElement("div") | |
| next.dataset.markdownBlock = "" | |
| next.dataset.markdownKey = block.key | |
| next.dataset.markdownHash = block.hash | |
| next.style.display = "contents" | |
| next.innerHTML = block.html | |
| decorate(next, labels) | |
| if (!(current instanceof HTMLDivElement)) { | |
| container.appendChild(next) | |
| return | |
| } | |
| morphdom(current, next, { | |
| onBeforeElUpdated: (fromEl, toEl) => { | |
| if ( | |
| fromEl instanceof HTMLElement && | |
| toEl instanceof HTMLElement && | |
| fromEl.getAttribute("data-slot") === "markdown-copy-button" && | |
| toEl.getAttribute("data-slot") === "markdown-copy-button" | |
| ) { | |
| return false | |
| } | |
| if (fromEl.isEqualNode(toEl)) return false | |
| return true | |
| }, | |
| onBeforeNodeDiscarded: (node) => { | |
| if (node instanceof Element) disposeCopyButtons(node) | |
| return true | |
| }, | |
| }) | |
| } | |
| function updateCodeBlock( | |
| container: HTMLDivElement, | |
| current: Element | undefined, | |
| block: Extract<RenderedBlock, { mode: "code" }>, | |
| labels: CopyLabels, | |
| ) { | |
| const existing = current instanceof HTMLDivElement && current.dataset.markdownKey === block.key ? current : undefined | |
| const next = existing ?? document.createElement("div") | |
| next.dataset.markdownBlock = "" | |
| next.dataset.markdownKey = block.key | |
| next.dataset.markdownHash = block.hash | |
| next.dataset.markdownComplete = block.complete ? "true" : "false" | |
| next.style.display = "contents" | |
| const code = existing?.querySelector("code") | |
| if (code instanceof HTMLElement) { | |
| const wrapper = code.closest('[data-component="markdown-code"]') | |
| if (wrapper instanceof HTMLElement) applyCodeMetadata(wrapper, block.language) | |
| code.className = `language-${block.language}` | |
| const previous = renderedCodeTokens.get(next) | |
| const reset = shouldResetCodeTokens(previous, { | |
| language: block.language, | |
| generation: block.generation, | |
| stableCount: block.stable.length, | |
| raw: block.raw, | |
| }) | |
| const stableCount = reset ? 0 : previous!.stableCount | |
| const tail = [...block.stable.slice(stableCount), ...block.unstable] | |
| const prior = reset ? [] : previous!.unstable | |
| const prefix = prior.findIndex((token, index) => !sameToken(token, tail[index])) | |
| const keep = stableCount + (prefix < 0 ? Math.min(prior.length, tail.length) : prefix) | |
| while (code.children.length > keep) code.lastElementChild?.remove() | |
| tail | |
| .slice(keep - stableCount) | |
| .map(createTokenSpan) | |
| .forEach((span) => code.appendChild(span)) | |
| renderedCodeTokens.set(next, { | |
| language: block.language, | |
| generation: block.generation, | |
| stableCount: block.stable.length, | |
| unstable: block.unstable, | |
| raw: block.raw, | |
| }) | |
| return | |
| } | |
| const wrapper = document.createElement("div") | |
| wrapper.setAttribute("data-component", "markdown-code") | |
| applyCodeMetadata(wrapper, block.language) | |
| const pre = document.createElement("pre") | |
| pre.className = "shiki OpenCode" | |
| const codeElement = document.createElement("code") | |
| codeElement.className = `language-${block.language}` | |
| ;[...block.stable, ...block.unstable].map(createTokenSpan).forEach((span) => codeElement.appendChild(span)) | |
| pre.appendChild(codeElement) | |
| wrapper.appendChild(pre) | |
| wrapper.appendChild(createCopyButton(labels)) | |
| next.appendChild(wrapper) | |
| renderedCodeTokens.set(next, { | |
| language: block.language, | |
| generation: block.generation, | |
| stableCount: block.stable.length, | |
| unstable: block.unstable, | |
| raw: block.raw, | |
| }) | |
| if (current) { | |
| disposeCopyButtons(current) | |
| current.replaceWith(next) | |
| return | |
| } | |
| container.appendChild(next) | |
| } | |
| function sameToken(left: MarkdownToken, right: MarkdownToken | undefined) { | |
| return !!right && left[0] === right[0] && left[1] === right[1] | |
| } | |
| function createTokenSpan(token: MarkdownToken) { | |
| const span = document.createElement("span") | |
| span.setAttribute("style", token[1]) | |
| span.textContent = token[0] | |
| return span | |
| } | |