|
|
@@ -103,68 +103,76 @@ function setCopyState(button: HTMLButtonElement, labels: CopyLabels, copied: boo
|
|
|
button.setAttribute("data-tooltip", labels.copy)
|
|
|
}
|
|
|
|
|
|
-function setupCodeCopy(root: HTMLDivElement, labels: CopyLabels) {
|
|
|
- const timeouts = new Map<HTMLButtonElement, ReturnType<typeof setTimeout>>()
|
|
|
-
|
|
|
- const updateLabel = (button: HTMLButtonElement) => {
|
|
|
- const copied = button.getAttribute("data-copied") === "true"
|
|
|
- setCopyState(button, labels, copied)
|
|
|
- }
|
|
|
-
|
|
|
- const ensureWrapper = (block: HTMLPreElement) => {
|
|
|
- const parent = block.parentElement
|
|
|
- if (!parent) return
|
|
|
- const wrapped = parent.getAttribute("data-component") === "markdown-code"
|
|
|
- if (wrapped) {
|
|
|
- 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)) {
|
|
|
- button.remove()
|
|
|
- }
|
|
|
-
|
|
|
- return
|
|
|
- }
|
|
|
+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")
|
|
|
parent.replaceChild(wrapper, block)
|
|
|
wrapper.appendChild(block)
|
|
|
wrapper.appendChild(createCopyButton(labels))
|
|
|
+ return
|
|
|
}
|
|
|
|
|
|
- const markCodeLinks = () => {
|
|
|
- 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
|
|
|
- }
|
|
|
+ const buttons = Array.from(parent.querySelectorAll('[data-slot="markdown-copy-button"]')).filter(
|
|
|
+ (el): el is HTMLButtonElement => el instanceof HTMLButtonElement,
|
|
|
+ )
|
|
|
|
|
|
- if (parentLink) {
|
|
|
- parentLink.href = href
|
|
|
- continue
|
|
|
- }
|
|
|
+ if (buttons.length === 0) {
|
|
|
+ parent.appendChild(createCopyButton(labels))
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ for (const button of buttons.slice(1)) {
|
|
|
+ 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
|
|
|
+ }
|
|
|
|
|
|
- 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)
|
|
|
+ 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 decorate(root: HTMLDivElement, labels: CopyLabels) {
|
|
|
+ const blocks = Array.from(root.querySelectorAll("pre"))
|
|
|
+ for (const block of blocks) {
|
|
|
+ ensureCodeWrapper(block, labels)
|
|
|
+ }
|
|
|
+ markCodeLinks(root)
|
|
|
+}
|
|
|
+
|
|
|
+function setupCodeCopy(root: HTMLDivElement, labels: CopyLabels) {
|
|
|
+ const timeouts = new Map<HTMLButtonElement, ReturnType<typeof setTimeout>>()
|
|
|
+
|
|
|
+ const updateLabel = (button: HTMLButtonElement) => {
|
|
|
+ const copied = button.getAttribute("data-copied") === "true"
|
|
|
+ setCopyState(button, labels, copied)
|
|
|
}
|
|
|
|
|
|
const handleClick = async (event: MouseEvent) => {
|
|
|
@@ -186,11 +194,7 @@ function setupCodeCopy(root: HTMLDivElement, labels: CopyLabels) {
|
|
|
timeouts.set(button, timeout)
|
|
|
}
|
|
|
|
|
|
- const blocks = Array.from(root.querySelectorAll("pre"))
|
|
|
- for (const block of blocks) {
|
|
|
- ensureWrapper(block)
|
|
|
- }
|
|
|
- markCodeLinks()
|
|
|
+ decorate(root, labels)
|
|
|
|
|
|
const buttons = Array.from(root.querySelectorAll('[data-slot="markdown-copy-button"]'))
|
|
|
for (const button of buttons) {
|
|
|
@@ -207,20 +211,6 @@ function setupCodeCopy(root: HTMLDivElement, labels: CopyLabels) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-function wrapCodeBlocks(root: HTMLDivElement) {
|
|
|
- const blocks = Array.from(root.querySelectorAll("pre"))
|
|
|
- for (const block of blocks) {
|
|
|
- const parent = block.parentElement
|
|
|
- if (!parent) continue
|
|
|
- const wrapped = parent.getAttribute("data-component") === "markdown-code"
|
|
|
- if (wrapped) continue
|
|
|
- const wrapper = document.createElement("div")
|
|
|
- wrapper.setAttribute("data-component", "markdown-code")
|
|
|
- parent.replaceChild(wrapper, block)
|
|
|
- wrapper.appendChild(block)
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
function touch(key: string, value: Entry) {
|
|
|
cache.delete(key)
|
|
|
cache.set(key, value)
|
|
|
@@ -284,23 +274,15 @@ export function Markdown(
|
|
|
|
|
|
const temp = document.createElement("div")
|
|
|
temp.innerHTML = content
|
|
|
- wrapCodeBlocks(temp)
|
|
|
+ decorate(temp, {
|
|
|
+ copy: i18n.t("ui.message.copy"),
|
|
|
+ copied: i18n.t("ui.message.copied"),
|
|
|
+ })
|
|
|
|
|
|
morphdom(container, temp, {
|
|
|
childrenOnly: true,
|
|
|
onBeforeElUpdated: (fromEl, toEl) => {
|
|
|
if (fromEl.isEqualNode(toEl)) return false
|
|
|
-
|
|
|
- const fromWrapped = fromEl.getAttribute("data-component") === "markdown-code"
|
|
|
- const toWrapped = toEl.getAttribute("data-component") === "markdown-code"
|
|
|
- if (fromWrapped && toWrapped) {
|
|
|
- const fromPre = fromEl.querySelector("pre")
|
|
|
- const toPre = toEl.querySelector("pre")
|
|
|
- if (fromPre && toPre && !fromPre.isEqualNode(toPre)) {
|
|
|
- morphdom(fromPre, toPre)
|
|
|
- }
|
|
|
- return false
|
|
|
- }
|
|
|
return true
|
|
|
},
|
|
|
})
|