diff --git a/web/src/components/chat/chat-messages-panel.tsx b/web/src/components/chat/chat-messages-panel.tsx
index 0ba25f8..f03d928 100644
--- a/web/src/components/chat/chat-messages-panel.tsx
+++ b/web/src/components/chat/chat-messages-panel.tsx
@@ -478,6 +478,7 @@ export function ChatMessagesPanel({ messages, isLoading, isSending }: Props) {
) : message.content.trim() ? (
) : null}
diff --git a/web/src/components/markdown/markdown-content.tsx b/web/src/components/markdown/markdown-content.tsx
index 1a0e066..d28baf5 100644
--- a/web/src/components/markdown/markdown-content.tsx
+++ b/web/src/components/markdown/markdown-content.tsx
@@ -10,6 +10,7 @@ type Props = {
className?: string;
mode?: MarkdownMode;
resolveCitationIndex?: (href: string) => number | undefined;
+ openLinksInNewTab?: boolean;
};
function replaceMarkdownLinksWithCitationTokens(markdown: string, resolveCitationIndex?: (href: string) => number | undefined) {
@@ -28,17 +29,30 @@ markdownRenderer.table = (token) => {
return `
${renderTable(token)}
`;
};
-function renderMarkdown(markdown: string) {
- const rawHtml = marked.parse(markdown, { gfm: true, breaks: true, renderer: markdownRenderer }) as string;
- return DOMPurify.sanitize(rawHtml, { ADD_ATTR: ["class", "target", "rel"] });
+function setNewTabLinkAttributes(currentNode: Element) {
+ if (currentNode.tagName !== "A") return;
+ currentNode.setAttribute("target", "_blank");
+ currentNode.setAttribute("rel", "noopener noreferrer");
}
-export function MarkdownContent({ markdown, className, mode = "default", resolveCitationIndex }: Props) {
+function renderMarkdown(markdown: string, openLinksInNewTab: boolean) {
+ const rawHtml = marked.parse(markdown, { gfm: true, breaks: true, renderer: markdownRenderer }) as string;
+ if (!openLinksInNewTab) return DOMPurify.sanitize(rawHtml, { ADD_ATTR: ["class", "target", "rel"] });
+
+ DOMPurify.addHook("afterSanitizeAttributes", setNewTabLinkAttributes);
+ try {
+ return DOMPurify.sanitize(rawHtml, { ADD_ATTR: ["class", "target", "rel"] });
+ } finally {
+ DOMPurify.removeHook("afterSanitizeAttributes", setNewTabLinkAttributes);
+ }
+}
+
+export function MarkdownContent({ markdown, className, mode = "default", resolveCitationIndex, openLinksInNewTab = false }: Props) {
const html = useMemo(() => {
const prepared =
mode === "citationTokens" ? replaceMarkdownLinksWithCitationTokens(markdown, resolveCitationIndex) : markdown;
- return renderMarkdown(prepared);
- }, [markdown, mode, resolveCitationIndex]);
+ return renderMarkdown(prepared, openLinksInNewTab);
+ }, [markdown, mode, openLinksInNewTab, resolveCitationIndex]);
return ;
}