@huyooo/ai-chat-shared 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,144 @@
1
+ /**
2
+ * AI Chat 共享类型定义
3
+ */
4
+ /** 内容块类型 */
5
+ type ContentBlockType = 'text' | 'code';
6
+ /** 内容块基础接口 */
7
+ interface ContentBlockBase {
8
+ id: string;
9
+ type: ContentBlockType;
10
+ }
11
+ /** 文本块 */
12
+ interface TextBlock extends ContentBlockBase {
13
+ type: 'text';
14
+ content: string;
15
+ }
16
+ /** 代码块 */
17
+ interface CodeBlock extends ContentBlockBase {
18
+ type: 'code';
19
+ content: string;
20
+ language?: string;
21
+ filename?: string;
22
+ }
23
+ /** 内容块联合类型 */
24
+ type ContentBlock = TextBlock | CodeBlock;
25
+ /** 工具渲染器 Props */
26
+ interface ToolRendererProps {
27
+ /** 工具名称 */
28
+ toolName: string;
29
+ /** 工具参数 */
30
+ toolArgs: Record<string, unknown>;
31
+ /** 工具返回结果 */
32
+ toolResult: unknown;
33
+ /** 执行状态 */
34
+ status: 'running' | 'completed' | 'error';
35
+ }
36
+ /** 天气数据 */
37
+ interface WeatherData {
38
+ city: string;
39
+ temperature: number;
40
+ condition: string;
41
+ humidity?: number;
42
+ wind?: string;
43
+ icon?: string;
44
+ forecast?: WeatherForecastItem[];
45
+ }
46
+ /** 天气预报项 */
47
+ interface WeatherForecastItem {
48
+ date: string;
49
+ high: number;
50
+ low: number;
51
+ condition: string;
52
+ icon?: string;
53
+ }
54
+ /** 搜索结果项 */
55
+ interface SearchResultItem {
56
+ title: string;
57
+ url: string;
58
+ snippet?: string;
59
+ }
60
+ /** 代码执行结果 */
61
+ interface CodeExecutionResult {
62
+ output: string;
63
+ exitCode: number;
64
+ error?: string;
65
+ }
66
+ /** 文件操作结果 */
67
+ interface FileOperationResult {
68
+ success: boolean;
69
+ path: string;
70
+ operation: 'read' | 'write' | 'delete' | 'create' | 'move';
71
+ content?: string;
72
+ error?: string;
73
+ }
74
+
75
+ /**
76
+ * 内容解析器
77
+ * 将 AI 输出的原始文本解析为结构化的内容块
78
+ */
79
+
80
+ /**
81
+ * 解析原始内容为内容块列表
82
+ * @param raw 原始文本内容
83
+ * @returns 内容块列表
84
+ */
85
+ declare function parseContent(raw: string): ContentBlock[];
86
+ /** 流式解析状态 */
87
+ interface StreamParseState {
88
+ /** 未完成的缓冲区 */
89
+ buffer: string;
90
+ /** 已解析的块 */
91
+ blocks: ContentBlock[];
92
+ /** 是否在代码块中 */
93
+ inCodeBlock: boolean;
94
+ /** 当前代码块语言 */
95
+ codeLanguage?: string;
96
+ /** 当前代码块内容 */
97
+ codeContent: string;
98
+ }
99
+ /** 创建初始流式解析状态 */
100
+ declare function createStreamParseState(): StreamParseState;
101
+ /**
102
+ * 流式解析(增量更新)
103
+ * @param chunk 新增的文本块
104
+ * @param state 当前解析状态
105
+ * @returns 更新后的状态
106
+ */
107
+ declare function parseContentStream(chunk: string, state: StreamParseState): StreamParseState;
108
+ /**
109
+ * 完成流式解析(处理剩余缓冲区)
110
+ * @param state 当前解析状态
111
+ * @returns 最终的内容块列表
112
+ */
113
+ declare function finishStreamParse(state: StreamParseState): ContentBlock[];
114
+
115
+ /**
116
+ * 代码高亮工具
117
+ * 使用 highlight.js 提供代码语法高亮
118
+ */
119
+ /**
120
+ * 高亮代码
121
+ * @param code 代码内容
122
+ * @param language 语言标识
123
+ * @returns 高亮后的 HTML
124
+ */
125
+ declare function highlightCode(code: string, language?: string): string;
126
+ /**
127
+ * 获取语言显示名称
128
+ * @param language 语言标识
129
+ * @returns 显示名称
130
+ */
131
+ declare function getLanguageDisplayName(language?: string): string;
132
+
133
+ /**
134
+ * Markdown 渲染工具
135
+ * 提供统一的 Markdown 渲染函数,供 React 和 Vue 版本使用
136
+ */
137
+ /**
138
+ * 渲染 Markdown 为 HTML
139
+ * @param markdown Markdown 文本
140
+ * @returns 安全的 HTML 字符串
141
+ */
142
+ declare function renderMarkdown(markdown: string): string;
143
+
144
+ export { type CodeBlock, type CodeExecutionResult, type ContentBlock, type ContentBlockBase, type ContentBlockType, type FileOperationResult, type SearchResultItem, type StreamParseState, type TextBlock, type ToolRendererProps, type WeatherData, type WeatherForecastItem, createStreamParseState, finishStreamParse, getLanguageDisplayName, highlightCode, parseContent, parseContentStream, renderMarkdown };
package/dist/index.js ADDED
@@ -0,0 +1,262 @@
1
+ // src/parser.ts
2
+ function generateId() {
3
+ return Math.random().toString(36).slice(2, 11);
4
+ }
5
+ var CODE_BLOCK_REGEX = /```(\w*)\n([\s\S]*?)```/g;
6
+ function parseContent(raw) {
7
+ if (!raw) return [];
8
+ const blocks = [];
9
+ let lastIndex = 0;
10
+ let match;
11
+ const regex = new RegExp(CODE_BLOCK_REGEX.source, "g");
12
+ while ((match = regex.exec(raw)) !== null) {
13
+ if (match.index > lastIndex) {
14
+ const textContent = raw.slice(lastIndex, match.index);
15
+ if (textContent.trim()) {
16
+ blocks.push(createTextBlock(textContent));
17
+ }
18
+ }
19
+ const language = match[1] || void 0;
20
+ const code = match[2];
21
+ blocks.push(createCodeBlock(code, language));
22
+ lastIndex = match.index + match[0].length;
23
+ }
24
+ if (lastIndex < raw.length) {
25
+ const textContent = raw.slice(lastIndex);
26
+ if (textContent.trim()) {
27
+ blocks.push(createTextBlock(textContent));
28
+ }
29
+ }
30
+ return blocks;
31
+ }
32
+ function createTextBlock(content) {
33
+ return {
34
+ id: generateId(),
35
+ type: "text",
36
+ content: content.trim()
37
+ };
38
+ }
39
+ function createCodeBlock(content, language, filename) {
40
+ return {
41
+ id: generateId(),
42
+ type: "code",
43
+ content,
44
+ language,
45
+ filename
46
+ };
47
+ }
48
+ function createStreamParseState() {
49
+ return {
50
+ buffer: "",
51
+ blocks: [],
52
+ inCodeBlock: false,
53
+ codeLanguage: void 0,
54
+ codeContent: ""
55
+ };
56
+ }
57
+ function parseContentStream(chunk, state) {
58
+ const newState = { ...state };
59
+ newState.buffer += chunk;
60
+ while (true) {
61
+ if (newState.inCodeBlock) {
62
+ const endIndex = newState.buffer.indexOf("```");
63
+ if (endIndex !== -1) {
64
+ newState.codeContent += newState.buffer.slice(0, endIndex);
65
+ newState.blocks.push(createCodeBlock(
66
+ newState.codeContent.trim(),
67
+ newState.codeLanguage
68
+ ));
69
+ newState.buffer = newState.buffer.slice(endIndex + 3);
70
+ newState.inCodeBlock = false;
71
+ newState.codeLanguage = void 0;
72
+ newState.codeContent = "";
73
+ } else {
74
+ newState.codeContent += newState.buffer;
75
+ newState.buffer = "";
76
+ break;
77
+ }
78
+ } else {
79
+ const startIndex = newState.buffer.indexOf("```");
80
+ if (startIndex !== -1) {
81
+ const beforeCode = newState.buffer.slice(0, startIndex);
82
+ if (beforeCode.trim()) {
83
+ newState.blocks.push(createTextBlock(beforeCode));
84
+ }
85
+ const afterStart = newState.buffer.slice(startIndex + 3);
86
+ const newlineIndex = afterStart.indexOf("\n");
87
+ if (newlineIndex !== -1) {
88
+ newState.codeLanguage = afterStart.slice(0, newlineIndex).trim() || void 0;
89
+ newState.buffer = afterStart.slice(newlineIndex + 1);
90
+ newState.inCodeBlock = true;
91
+ newState.codeContent = "";
92
+ } else {
93
+ break;
94
+ }
95
+ } else {
96
+ const lastBackticks = newState.buffer.lastIndexOf("`");
97
+ if (lastBackticks !== -1 && newState.buffer.length - lastBackticks < 3) {
98
+ const safeText = newState.buffer.slice(0, lastBackticks);
99
+ if (safeText.trim()) {
100
+ newState.blocks.push(createTextBlock(safeText));
101
+ }
102
+ newState.buffer = newState.buffer.slice(lastBackticks);
103
+ } else {
104
+ if (newState.buffer.trim()) {
105
+ newState.blocks.push(createTextBlock(newState.buffer));
106
+ }
107
+ newState.buffer = "";
108
+ }
109
+ break;
110
+ }
111
+ }
112
+ }
113
+ return newState;
114
+ }
115
+ function finishStreamParse(state) {
116
+ const blocks = [...state.blocks];
117
+ if (state.inCodeBlock) {
118
+ blocks.push(createCodeBlock(
119
+ (state.codeContent + state.buffer).trim(),
120
+ state.codeLanguage
121
+ ));
122
+ } else if (state.buffer.trim()) {
123
+ blocks.push(createTextBlock(state.buffer));
124
+ }
125
+ return blocks;
126
+ }
127
+
128
+ // src/highlighter.ts
129
+ import hljs from "highlight.js";
130
+ var LANGUAGE_ALIASES = {
131
+ js: "javascript",
132
+ ts: "typescript",
133
+ tsx: "typescript",
134
+ jsx: "javascript",
135
+ py: "python",
136
+ rb: "ruby",
137
+ sh: "bash",
138
+ shell: "bash",
139
+ yml: "yaml",
140
+ md: "markdown"
141
+ };
142
+ function highlightCode(code, language) {
143
+ if (!language) {
144
+ try {
145
+ const result = hljs.highlightAuto(code);
146
+ return result.value;
147
+ } catch {
148
+ return escapeHtml(code);
149
+ }
150
+ }
151
+ const normalizedLang = LANGUAGE_ALIASES[language.toLowerCase()] || language.toLowerCase();
152
+ try {
153
+ if (hljs.getLanguage(normalizedLang)) {
154
+ return hljs.highlight(code, { language: normalizedLang }).value;
155
+ }
156
+ return escapeHtml(code);
157
+ } catch {
158
+ return escapeHtml(code);
159
+ }
160
+ }
161
+ function escapeHtml(text) {
162
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
163
+ }
164
+ function getLanguageDisplayName(language) {
165
+ if (!language) return "Plain Text";
166
+ const normalizedLang = LANGUAGE_ALIASES[language.toLowerCase()] || language.toLowerCase();
167
+ const langDef = hljs.getLanguage(normalizedLang);
168
+ if (langDef) {
169
+ return normalizedLang.charAt(0).toUpperCase() + normalizedLang.slice(1);
170
+ }
171
+ return language;
172
+ }
173
+
174
+ // src/markdown.ts
175
+ import { marked, Renderer } from "marked";
176
+ import DOMPurify from "dompurify";
177
+ function renderMarkdown(markdown) {
178
+ if (!markdown) return "";
179
+ const renderer = new Renderer();
180
+ renderer.code = (code, language) => {
181
+ const highlighted = highlightCode(code, language);
182
+ const lang = language || "plaintext";
183
+ return `<pre class="markdown-code-block"><code class="language-${lang}">${highlighted}</code></pre>`;
184
+ };
185
+ renderer.table = (header, body) => {
186
+ return `<div class="markdown-table-wrapper"><table class="markdown-table"><thead>${header}</thead><tbody>${body}</tbody></table></div>`;
187
+ };
188
+ renderer.link = (href, title, text) => {
189
+ const titleAttr = title ? ` title="${title}"` : "";
190
+ return `<a href="${href}"${titleAttr} target="_blank" rel="noopener noreferrer" class="markdown-link">${text}</a>`;
191
+ };
192
+ renderer.listitem = (text, task, checked) => {
193
+ if (task) {
194
+ const checkboxText = checked ? "[x]" : "[ ]";
195
+ const textWithoutCheckbox = text.replace(/<input[^>]*>/g, "").trim();
196
+ return `<li class="markdown-task-item"><span class="markdown-task-checkbox">${checkboxText}</span> ${textWithoutCheckbox}</li>`;
197
+ }
198
+ return `<li>${text}</li>`;
199
+ };
200
+ marked.setOptions({
201
+ breaks: true,
202
+ // 支持换行
203
+ gfm: true
204
+ // GitHub Flavored Markdown
205
+ });
206
+ let html = marked(markdown, { renderer });
207
+ html = DOMPurify.sanitize(html, {
208
+ ALLOWED_TAGS: [
209
+ "p",
210
+ "br",
211
+ "strong",
212
+ "em",
213
+ "u",
214
+ "s",
215
+ "code",
216
+ "pre",
217
+ "h1",
218
+ "h2",
219
+ "h3",
220
+ "h4",
221
+ "h5",
222
+ "h6",
223
+ "ul",
224
+ "ol",
225
+ "li",
226
+ "blockquote",
227
+ "table",
228
+ "thead",
229
+ "tbody",
230
+ "tr",
231
+ "th",
232
+ "td",
233
+ "a",
234
+ "img",
235
+ "hr",
236
+ "div",
237
+ "span"
238
+ ],
239
+ ALLOWED_ATTR: [
240
+ "href",
241
+ "title",
242
+ "target",
243
+ "rel",
244
+ "class",
245
+ "src",
246
+ "alt",
247
+ "width",
248
+ "height"
249
+ ]
250
+ });
251
+ return html;
252
+ }
253
+ export {
254
+ createStreamParseState,
255
+ finishStreamParse,
256
+ getLanguageDisplayName,
257
+ highlightCode,
258
+ parseContent,
259
+ parseContentStream,
260
+ renderMarkdown
261
+ };
262
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/parser.ts","../src/highlighter.ts","../src/markdown.ts"],"sourcesContent":["/**\n * 内容解析器\n * 将 AI 输出的原始文本解析为结构化的内容块\n */\n\nimport type { ContentBlock, TextBlock, CodeBlock } from './types'\n\n/** 生成唯一 ID */\nfunction generateId(): string {\n return Math.random().toString(36).slice(2, 11)\n}\n\n/** 代码块正则:匹配 ```language\\ncode\\n``` 或 ```\\ncode\\n``` */\nconst CODE_BLOCK_REGEX = /```(\\w*)\\n([\\s\\S]*?)```/g\n\n/**\n * 解析原始内容为内容块列表\n * @param raw 原始文本内容\n * @returns 内容块列表\n */\nexport function parseContent(raw: string): ContentBlock[] {\n if (!raw) return []\n\n const blocks: ContentBlock[] = []\n let lastIndex = 0\n\n // 使用正则匹配所有代码块\n let match: RegExpExecArray | null\n const regex = new RegExp(CODE_BLOCK_REGEX.source, 'g')\n\n while ((match = regex.exec(raw)) !== null) {\n // 添加代码块前的文本\n if (match.index > lastIndex) {\n const textContent = raw.slice(lastIndex, match.index)\n if (textContent.trim()) {\n blocks.push(createTextBlock(textContent))\n }\n }\n\n // 添加代码块\n const language = match[1] || undefined\n const code = match[2]\n blocks.push(createCodeBlock(code, language))\n\n lastIndex = match.index + match[0].length\n }\n\n // 添加最后一段文本\n if (lastIndex < raw.length) {\n const textContent = raw.slice(lastIndex)\n if (textContent.trim()) {\n blocks.push(createTextBlock(textContent))\n }\n }\n\n return blocks\n}\n\n/** 创建文本块 */\nfunction createTextBlock(content: string): TextBlock {\n return {\n id: generateId(),\n type: 'text',\n content: content.trim(),\n }\n}\n\n/** 创建代码块 */\nfunction createCodeBlock(content: string, language?: string, filename?: string): CodeBlock {\n return {\n id: generateId(),\n type: 'code',\n content,\n language,\n filename,\n }\n}\n\n// ============ 流式解析支持 ============\n\n/** 流式解析状态 */\nexport interface StreamParseState {\n /** 未完成的缓冲区 */\n buffer: string\n /** 已解析的块 */\n blocks: ContentBlock[]\n /** 是否在代码块中 */\n inCodeBlock: boolean\n /** 当前代码块语言 */\n codeLanguage?: string\n /** 当前代码块内容 */\n codeContent: string\n}\n\n/** 创建初始流式解析状态 */\nexport function createStreamParseState(): StreamParseState {\n return {\n buffer: '',\n blocks: [],\n inCodeBlock: false,\n codeLanguage: undefined,\n codeContent: '',\n }\n}\n\n/**\n * 流式解析(增量更新)\n * @param chunk 新增的文本块\n * @param state 当前解析状态\n * @returns 更新后的状态\n */\nexport function parseContentStream(chunk: string, state: StreamParseState): StreamParseState {\n const newState = { ...state }\n newState.buffer += chunk\n\n // 处理缓冲区\n while (true) {\n if (newState.inCodeBlock) {\n // 在代码块中,查找结束标记\n const endIndex = newState.buffer.indexOf('```')\n if (endIndex !== -1) {\n // 找到结束标记\n newState.codeContent += newState.buffer.slice(0, endIndex)\n newState.blocks.push(createCodeBlock(\n newState.codeContent.trim(),\n newState.codeLanguage\n ))\n newState.buffer = newState.buffer.slice(endIndex + 3)\n newState.inCodeBlock = false\n newState.codeLanguage = undefined\n newState.codeContent = ''\n } else {\n // 没找到结束标记,继续累积\n newState.codeContent += newState.buffer\n newState.buffer = ''\n break\n }\n } else {\n // 不在代码块中,查找开始标记\n const startIndex = newState.buffer.indexOf('```')\n if (startIndex !== -1) {\n // 找到开始标记\n const beforeCode = newState.buffer.slice(0, startIndex)\n if (beforeCode.trim()) {\n newState.blocks.push(createTextBlock(beforeCode))\n }\n\n // 解析语言标识\n const afterStart = newState.buffer.slice(startIndex + 3)\n const newlineIndex = afterStart.indexOf('\\n')\n if (newlineIndex !== -1) {\n newState.codeLanguage = afterStart.slice(0, newlineIndex).trim() || undefined\n newState.buffer = afterStart.slice(newlineIndex + 1)\n newState.inCodeBlock = true\n newState.codeContent = ''\n } else {\n // 语言行还没完整,等待更多数据\n break\n }\n } else {\n // 没有代码块标记,检查是否有不完整的 ``` 开头\n const lastBackticks = newState.buffer.lastIndexOf('`')\n if (lastBackticks !== -1 && newState.buffer.length - lastBackticks < 3) {\n // 可能是不完整的 ```,保留\n const safeText = newState.buffer.slice(0, lastBackticks)\n if (safeText.trim()) {\n newState.blocks.push(createTextBlock(safeText))\n }\n newState.buffer = newState.buffer.slice(lastBackticks)\n } else {\n // 安全的纯文本\n if (newState.buffer.trim()) {\n newState.blocks.push(createTextBlock(newState.buffer))\n }\n newState.buffer = ''\n }\n break\n }\n }\n }\n\n return newState\n}\n\n/**\n * 完成流式解析(处理剩余缓冲区)\n * @param state 当前解析状态\n * @returns 最终的内容块列表\n */\nexport function finishStreamParse(state: StreamParseState): ContentBlock[] {\n const blocks = [...state.blocks]\n\n if (state.inCodeBlock) {\n // 未闭合的代码块,作为代码块处理\n blocks.push(createCodeBlock(\n (state.codeContent + state.buffer).trim(),\n state.codeLanguage\n ))\n } else if (state.buffer.trim()) {\n // 剩余文本\n blocks.push(createTextBlock(state.buffer))\n }\n\n return blocks\n}\n","/**\n * 代码高亮工具\n * 使用 highlight.js 提供代码语法高亮\n */\n\nimport hljs from 'highlight.js'\n\n/** 支持的语言别名映射 */\nconst LANGUAGE_ALIASES: Record<string, string> = {\n js: 'javascript',\n ts: 'typescript',\n tsx: 'typescript',\n jsx: 'javascript',\n py: 'python',\n rb: 'ruby',\n sh: 'bash',\n shell: 'bash',\n yml: 'yaml',\n md: 'markdown',\n}\n\n/**\n * 高亮代码\n * @param code 代码内容\n * @param language 语言标识\n * @returns 高亮后的 HTML\n */\nexport function highlightCode(code: string, language?: string): string {\n if (!language) {\n // 无语言指定,尝试自动检测\n try {\n const result = hljs.highlightAuto(code)\n return result.value\n } catch {\n return escapeHtml(code)\n }\n }\n\n // 处理语言别名\n const normalizedLang = LANGUAGE_ALIASES[language.toLowerCase()] || language.toLowerCase()\n\n try {\n if (hljs.getLanguage(normalizedLang)) {\n return hljs.highlight(code, { language: normalizedLang }).value\n }\n // 语言不支持,返回转义后的原文\n return escapeHtml(code)\n } catch {\n return escapeHtml(code)\n }\n}\n\n/** HTML 转义 */\nfunction escapeHtml(text: string): string {\n return text\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#039;')\n}\n\n/**\n * 获取语言显示名称\n * @param language 语言标识\n * @returns 显示名称\n */\nexport function getLanguageDisplayName(language?: string): string {\n if (!language) return 'Plain Text'\n \n const normalizedLang = LANGUAGE_ALIASES[language.toLowerCase()] || language.toLowerCase()\n const langDef = hljs.getLanguage(normalizedLang)\n \n if (langDef) {\n // 首字母大写\n return normalizedLang.charAt(0).toUpperCase() + normalizedLang.slice(1)\n }\n \n return language\n}\n","/**\n * Markdown 渲染工具\n * 提供统一的 Markdown 渲染函数,供 React 和 Vue 版本使用\n */\n\nimport { marked, Renderer } from 'marked'\nimport DOMPurify from 'dompurify'\nimport { highlightCode } from './highlighter'\n\n/**\n * 渲染 Markdown 为 HTML\n * @param markdown Markdown 文本\n * @returns 安全的 HTML 字符串\n */\nexport function renderMarkdown(markdown: string): string {\n if (!markdown) return ''\n\n // 创建自定义渲染器(继承默认渲染器)\n const renderer = new Renderer()\n\n // 自定义代码块渲染\n renderer.code = (code: string, language?: string) => {\n const highlighted = highlightCode(code, language)\n const lang = language || 'plaintext'\n return `<pre class=\"markdown-code-block\"><code class=\"language-${lang}\">${highlighted}</code></pre>`\n }\n\n // 自定义表格渲染(添加样式类和滚动容器)\n renderer.table = (header: string, body: string) => {\n return `<div class=\"markdown-table-wrapper\"><table class=\"markdown-table\"><thead>${header}</thead><tbody>${body}</tbody></table></div>`\n }\n\n // 自定义链接渲染(添加 target=\"_blank\" 和 rel=\"noopener noreferrer\")\n renderer.link = (href: string, title: string | null, text: string) => {\n const titleAttr = title ? ` title=\"${title}\"` : ''\n return `<a href=\"${href}\"${titleAttr} target=\"_blank\" rel=\"noopener noreferrer\" class=\"markdown-link\">${text}</a>`\n }\n\n // 自定义列表项渲染,将 checkbox 替换为文本 [x] 或 [ ]\n renderer.listitem = (text: string, task: boolean, checked: boolean) => {\n if (task) {\n // 任务列表:将 checkbox 替换为文本\n const checkboxText = checked ? '[x]' : '[ ]'\n // 移除 marked 生成的 checkbox input,替换为文本\n const textWithoutCheckbox = text.replace(/<input[^>]*>/g, '').trim()\n return `<li class=\"markdown-task-item\"><span class=\"markdown-task-checkbox\">${checkboxText}</span> ${textWithoutCheckbox}</li>`\n }\n // 普通列表项\n return `<li>${text}</li>`\n }\n\n // 配置 marked(marked 12.x 使用新的配置方式)\n marked.setOptions({\n breaks: true, // 支持换行\n gfm: true, // GitHub Flavored Markdown\n })\n\n // 渲染 Markdown(marked 12.x 返回字符串,不是 Promise)\n let html = marked(markdown, { renderer }) as string\n\n // 使用 DOMPurify 清理 HTML,防止 XSS\n html = DOMPurify.sanitize(html, {\n ALLOWED_TAGS: [\n 'p', 'br', 'strong', 'em', 'u', 's', 'code', 'pre',\n 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',\n 'ul', 'ol', 'li', 'blockquote',\n 'table', 'thead', 'tbody', 'tr', 'th', 'td',\n 'a', 'img', 'hr',\n 'div', 'span',\n ],\n ALLOWED_ATTR: [\n 'href', 'title', 'target', 'rel', 'class',\n 'src', 'alt', 'width', 'height',\n ],\n })\n\n return html\n}\n\n"],"mappings":"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","names":[]}