|
1 | 1 | import { marked } from "marked";
|
2 |
| -import { markedHighlight } from "marked-highlight"; |
3 | 2 | import hljs from "highlight.js";
|
| 3 | +import { markedHighlight } from "marked-highlight"; |
4 | 4 |
|
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); |
47 | 12 | }
|
48 |
| - result.push(line); |
49 |
| - } else { |
50 |
| - result.push(line); |
51 | 13 | }
|
| 14 | + return code; // Use the original code if language isn't found |
52 | 15 | }
|
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>'; |
63 | 40 | }
|
| 41 | + |
| 42 | + // Insert the icon after the link |
| 43 | + return originalLink.slice(0, -4) + iconSvg + '</a>'; |
| 44 | +}; |
64 | 45 |
|
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); |
120 | 48 | }
|
0 commit comments