Skip to content

Commit 333ded2

Browse files
msukkaricursoragentclaude
authored
feat(web): open chat links in new tab with external link icon (#1059)
* feat(web): open chat links in new tab with external link icon - Add custom anchor renderer to MarkdownRenderer component - Set target='_blank' and rel='noopener noreferrer' on all links - Display subtle ExternalLinkIcon (↗) after link text - Icon uses opacity-60 for muted appearance in both themes Fixes SOU-822 Co-authored-by: Michael Sukkarieh <msukkari@users.noreply.github.com> * docs: add changelog entry for external link icon feature Co-authored-by: Michael Sukkarieh <msukkari@users.noreply.github.com> * fix: resolve duplicate renderAnchor from merge conflict Merged the two renderAnchor implementations: Linear issue card rendering from main with external link icon styling from this branch. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Michael Sukkarieh <msukkari@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent cfaea87 commit 333ded2

File tree

2 files changed

+28
-14
lines changed

2 files changed

+28
-14
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Added
1111
- Linear issue links in chat responses now render as a rich card-style UI showing the Linear logo, issue identifier, and title instead of plain hyperlinks. [#1060](https://github.com/sourcebot-dev/sourcebot/pull/1060)
1212

13+
### Changed
14+
- Links in Ask Sourcebot chat responses now open in a new tab with a subtle external link icon indicator. [#1059](https://github.com/sourcebot-dev/sourcebot/pull/1059)
15+
1316
## [4.16.7] - 2026-04-03
1417

1518
### Fixed

packages/web/src/features/chat/components/chatThread/markdownRenderer.tsx

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { SearchQueryParams } from '@/lib/types';
55
import { cn, createPathWithQueryParams } from '@/lib/utils';
66
import type { Element, Root } from "hast";
77
import { Schema as SanitizeSchema } from 'hast-util-sanitize';
8-
import { CopyIcon, SearchIcon } from 'lucide-react';
8+
import { CopyIcon, ExternalLinkIcon, SearchIcon } from 'lucide-react';
99
import type { Heading, Nodes } from "mdast";
1010
import { findAndReplace } from 'mdast-util-find-and-replace';
1111
import { useRouter } from 'next/navigation';
@@ -174,6 +174,30 @@ const MarkdownRendererComponent = forwardRef<HTMLDivElement, MarkdownRendererPro
174174
)
175175
}, []);
176176

177+
const renderAnchor = useCallback(({ href, children, ...rest }: React.JSX.IntrinsicElements['a']) => {
178+
if (href) {
179+
const match = LINEAR_ISSUE_URL_REGEX.exec(href);
180+
if (match) {
181+
const identifier = match[1];
182+
const titleSlug = match[2];
183+
const title = titleSlug.replace(/-/g, ' ');
184+
return <LinearIssueCard identifier={identifier} title={title} href={href} />;
185+
}
186+
}
187+
return (
188+
<a
189+
href={href}
190+
target="_blank"
191+
rel="noopener noreferrer"
192+
className="inline-flex items-center gap-0.5"
193+
{...rest}
194+
>
195+
{children}
196+
<ExternalLinkIcon className="inline w-3 h-3 mb-0.5 opacity-60" />
197+
</a>
198+
);
199+
}, []);
200+
177201
const renderCode = useCallback(({ className, children, node, ...rest }: React.JSX.IntrinsicElements['code'] & { node?: Element }) => {
178202
const text = children?.toString().trimEnd() ?? '';
179203

@@ -231,19 +255,6 @@ const MarkdownRendererComponent = forwardRef<HTMLDivElement, MarkdownRendererPro
231255

232256
}, [router]);
233257

234-
const renderAnchor = useCallback(({ href, children, ...rest }: React.JSX.IntrinsicElements['a']) => {
235-
if (href) {
236-
const match = LINEAR_ISSUE_URL_REGEX.exec(href);
237-
if (match) {
238-
const identifier = match[1];
239-
const titleSlug = match[2];
240-
const title = titleSlug.replace(/-/g, ' ');
241-
return <LinearIssueCard identifier={identifier} title={title} href={href} />;
242-
}
243-
}
244-
return <a href={href} target="_blank" rel="noopener noreferrer" {...rest}>{children}</a>;
245-
}, []);
246-
247258
return (
248259
<div
249260
ref={ref}

0 commit comments

Comments
 (0)