Skip to content

Commit 9154813

Browse files
Add external link icons to messages
Implement icons next to external links in messages to indicate they are external. Use a GitHub icon for GitHub links and a custom "W" icon for Wikipedia links. [skip gpt_engineer]
1 parent a57be6e commit 9154813

File tree

1 file changed

+39
-111
lines changed

1 file changed

+39
-111
lines changed

src/utils/markdownUtils.ts

Lines changed: 39 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -1,120 +1,48 @@
11
import { marked } from "marked";
2-
import { markedHighlight } from "marked-highlight";
32
import hljs from "highlight.js";
3+
import { markedHighlight } from "marked-highlight";
44

5-
marked.setOptions({
6-
gfm: true,
7-
breaks: true,
8-
silent: true,
9-
});
10-
11-
marked.use(
12-
markedHighlight({
13-
langPrefix: "hljs language-",
14-
highlight(code, lang, info) {
15-
lang = info.split(".").pop() || lang;
16-
if (lang == "shell") lang = "bash";
17-
if (lang == "result") lang = "markdown";
18-
const language = hljs.getLanguage(lang) ? lang : "plaintext";
19-
return hljs.highlight(code, { language }).value;
20-
},
21-
})
22-
);
23-
24-
export function processNestedCodeBlocks(content: string) {
25-
// If no code blocks or only one code block, return as-is
26-
if (content.split('```').length < 3) {
27-
const match = content.match(/```(\S*)/);
28-
return {
29-
processedContent: content,
30-
langtags: match ? [match[1]] : []
31-
};
32-
}
33-
34-
const lines = content.split('\n');
35-
const langtags: string[] = [];
36-
const result: string[] = [];
37-
38-
for (let i = 0; i < lines.length; i++) {
39-
const line = lines[i];
40-
const strippedLine = line.trim();
41-
42-
if (strippedLine.startsWith('```')) {
43-
if (strippedLine !== '```') {
44-
// Start of a code block with a language
45-
const lang = strippedLine.slice(3);
46-
langtags.push(lang);
5+
marked.use(markedHighlight({
6+
highlight: (code, lang) => {
7+
if (lang && hljs.getLanguage(lang)) {
8+
try {
9+
return hljs.highlight(code, { language: lang }).value;
10+
} catch (err) {
11+
console.error("Error highlighting code:", err);
4712
}
48-
result.push(line);
49-
} else {
50-
result.push(line);
5113
}
14+
return code; // Use the original code if language isn't found
5215
}
53-
54-
return {
55-
processedContent: result.join('\n'),
56-
langtags: langtags.filter(Boolean)
57-
};
58-
}
59-
60-
export function transformThinkingTags(content: string) {
61-
if (content.startsWith('`') && content.endsWith('`')) {
62-
return content;
16+
}));
17+
18+
const renderer = new marked.Renderer();
19+
20+
// Store the original link renderer
21+
const originalLinkRenderer = renderer.link.bind(renderer);
22+
23+
// Customize the link renderer to add icons
24+
renderer.link = (href: string, title: string | null | undefined, text: string) => {
25+
const originalLink = originalLinkRenderer(href, title, text);
26+
27+
if (!href) return originalLink;
28+
29+
let iconSvg = '';
30+
31+
if (href.includes('github.com')) {
32+
// GitHub icon
33+
iconSvg = '<svg class="inline-block ml-1 w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg>';
34+
} else if (href.includes('wikipedia.org')) {
35+
// Simple "W" icon for Wikipedia
36+
iconSvg = '<span class="inline-flex items-center justify-center ml-1 w-4 h-4 text-xs font-bold bg-gray-200 dark:bg-gray-700 rounded-full">W</span>';
37+
} else {
38+
// Generic external link icon for other links
39+
iconSvg = '<svg class="inline-block ml-1 w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><polyline points="15 3 21 3 21 9"></polyline><line x1="10" y1="14" x2="21" y2="3"></line></svg>';
6340
}
41+
42+
// Insert the icon after the link
43+
return originalLink.slice(0, -4) + iconSvg + '</a>';
44+
};
6445

65-
return content.replace(
66-
/<thinking>([\s\S]*?)<\/thinking>/g,
67-
(_match: string, thinkingContent: string) =>
68-
`<details><summary>💭 Thinking</summary>\n\n${thinkingContent}\n\n</details>`
69-
);
70-
}
71-
72-
export function parseMarkdownContent(content: string) {
73-
const processedContent = transformThinkingTags(content);
74-
const { processedContent: transformedContent, langtags } = processNestedCodeBlocks(processedContent);
75-
76-
let parsedResult = marked.parse(transformedContent, {
77-
async: false,
78-
});
79-
80-
parsedResult = parsedResult.replace(
81-
/<pre><code(?:\s+class="([^"]+)")?>([^]*?)<\/code><\/pre>/g,
82-
(_, classes = "", code) => {
83-
const langtag_fallback = ((classes || "").split(" ")[1] || "Code").replace("language-", "");
84-
const langtag = langtags?.shift() || langtag_fallback;
85-
const emoji = getCodeBlockEmoji(langtag);
86-
return `
87-
<details>
88-
<summary>${emoji} ${langtag}</summary>
89-
<pre><code class="${classes}">${code}</code></pre>
90-
</details>
91-
`;
92-
}
93-
);
94-
95-
return parsedResult;
96-
}
97-
98-
function getCodeBlockEmoji(langtag: string): string {
99-
if (isPath(langtag)) return "📄";
100-
if (isTool(langtag)) return "🛠️";
101-
if (isOutput(langtag)) return "📤";
102-
if (isWrite(langtag)) return "📝";
103-
return "💻";
104-
}
105-
106-
function isPath(langtag: string): boolean {
107-
return (langtag.includes("/") || langtag.includes("\\") || langtag.includes(".")) && langtag.split(" ").length === 1;
108-
}
109-
110-
function isTool(langtag: string): boolean {
111-
return ["ipython", "shell", "tmux"].includes(langtag.split(" ")[0].toLowerCase());
112-
}
113-
114-
function isOutput(langtag: string): boolean {
115-
return ["stdout", "stderr", "result"].includes(langtag.toLowerCase());
116-
}
117-
118-
function isWrite(langtag: string): boolean {
119-
return ["save", "patch", "append"].includes(langtag.split(" ")[0].toLowerCase());
46+
export function parseMarkdownContent(content: string): string {
47+
return marked(content);
12048
}

0 commit comments

Comments
 (0)