Skip to content

Commit d30e917

Browse files
authored
fix(ui): support cmd-click links in inline code (#12552)
1 parent 72c09e1 commit d30e917

File tree

2 files changed

+49
-0
lines changed

2 files changed

+49
-0
lines changed

packages/ui/src/components/markdown.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,3 +209,8 @@
209209
display: block;
210210
}
211211
}
212+
213+
[data-component="markdown"] a.external-link:hover > code {
214+
text-decoration: underline;
215+
text-underline-offset: 2px;
216+
}

packages/ui/src/components/markdown.tsx

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,19 @@ type CopyLabels = {
4949
copied: string
5050
}
5151

52+
const urlPattern = /^https?:\/\/[^\s<>()`"']+$/
53+
54+
function codeUrl(text: string) {
55+
const href = text.trim().replace(/[),.;!?]+$/, "")
56+
if (!urlPattern.test(href)) return
57+
try {
58+
const url = new URL(href)
59+
return url.toString()
60+
} catch {
61+
return
62+
}
63+
}
64+
5265
function createIcon(path: string, slot: string) {
5366
const icon = document.createElement("div")
5467
icon.setAttribute("data-component", "icon")
@@ -110,9 +123,39 @@ function setupCodeCopy(root: HTMLDivElement, labels: CopyLabels) {
110123
wrapper.appendChild(createCopyButton(labels))
111124
}
112125

126+
const markCodeLinks = () => {
127+
const codeNodes = Array.from(root.querySelectorAll(":not(pre) > code"))
128+
for (const code of codeNodes) {
129+
const href = codeUrl(code.textContent ?? "")
130+
const parentLink =
131+
code.parentElement instanceof HTMLAnchorElement && code.parentElement.classList.contains("external-link")
132+
? code.parentElement
133+
: null
134+
135+
if (!href) {
136+
if (parentLink) parentLink.replaceWith(code)
137+
continue
138+
}
139+
140+
if (parentLink) {
141+
parentLink.href = href
142+
continue
143+
}
144+
145+
const link = document.createElement("a")
146+
link.href = href
147+
link.className = "external-link"
148+
link.target = "_blank"
149+
link.rel = "noopener noreferrer"
150+
code.parentNode?.replaceChild(link, code)
151+
link.appendChild(code)
152+
}
153+
}
154+
113155
const handleClick = async (event: MouseEvent) => {
114156
const target = event.target
115157
if (!(target instanceof Element)) return
158+
116159
const button = target.closest('[data-slot="markdown-copy-button"]')
117160
if (!(button instanceof HTMLButtonElement)) return
118161
const code = button.closest('[data-component="markdown-code"]')?.querySelector("code")
@@ -132,6 +175,7 @@ function setupCodeCopy(root: HTMLDivElement, labels: CopyLabels) {
132175
for (const block of blocks) {
133176
ensureWrapper(block)
134177
}
178+
markCodeLinks()
135179

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

0 commit comments

Comments
 (0)