diff options
| author | Kit Langton <[email protected]> | 2026-02-13 14:57:38 -0500 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-02-13 13:57:38 -0600 |
| commit | d30e91738570ee9ea06ca6f2d49bdae65b0ff3ec (patch) | |
| tree | bb21f9e2ec870d36670fa8ea7ead2c31ca02faad | |
| parent | 72c09e1dcceee8b38476b3541852436fa045b2be (diff) | |
| download | opencode-d30e91738570ee9ea06ca6f2d49bdae65b0ff3ec.tar.gz opencode-d30e91738570ee9ea06ca6f2d49bdae65b0ff3ec.zip | |
fix(ui): support cmd-click links in inline code (#12552)
| -rw-r--r-- | packages/ui/src/components/markdown.css | 5 | ||||
| -rw-r--r-- | packages/ui/src/components/markdown.tsx | 44 |
2 files changed, 49 insertions, 0 deletions
diff --git a/packages/ui/src/components/markdown.css b/packages/ui/src/components/markdown.css index 68ae93bda..27c8f238d 100644 --- a/packages/ui/src/components/markdown.css +++ b/packages/ui/src/components/markdown.css @@ -209,3 +209,8 @@ display: block; } } + +[data-component="markdown"] a.external-link:hover > code { + text-decoration: underline; + text-underline-offset: 2px; +} diff --git a/packages/ui/src/components/markdown.tsx b/packages/ui/src/components/markdown.tsx index 608db818f..4c3d56284 100644 --- a/packages/ui/src/components/markdown.tsx +++ b/packages/ui/src/components/markdown.tsx @@ -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") @@ -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") @@ -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) { |
