Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/ui/src/components/markdown.css
Original file line number Diff line number Diff line change
Expand Up @@ -209,3 +209,8 @@
display: block;
}
}

[data-component="markdown"] a.external-link:hover > code {
text-decoration: underline;
text-underline-offset: 2px;
}
44 changes: 44 additions & 0 deletions packages/ui/src/components/markdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,19 @@ type CopyLabels = {
copied: string
}

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 createIcon(path: string, slot: string) {
const icon = document.createElement("div")
icon.setAttribute("data-component", "icon")
Expand Down Expand Up @@ -110,9 +123,39 @@ function setupCodeCopy(root: HTMLDivElement, labels: CopyLabels) {
wrapper.appendChild(createCopyButton(labels))
}

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
}

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)
}
}

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 HTMLButtonElement)) return
const code = button.closest('[data-component="markdown-code"]')?.querySelector("code")
Expand All @@ -132,6 +175,7 @@ function setupCodeCopy(root: HTMLDivElement, labels: CopyLabels) {
for (const block of blocks) {
ensureWrapper(block)
}
markCodeLinks()

const buttons = Array.from(root.querySelectorAll('[data-slot="markdown-copy-button"]'))
for (const button of buttons) {
Expand Down
Loading