@affanhamid/markdown-renderer 2.0.0
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.
- package/dist/index.cjs +1050 -0
- package/dist/index.d.cts +21 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +1010 -0
- package/package.json +40 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1050 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
MATH_MARKDOWN_RULES_APPENDIX: () => MATH_MARKDOWN_RULES_APPENDIX,
|
|
34
|
+
MarkdownRenderer: () => markdown_renderer_default,
|
|
35
|
+
normalizeMathMarkdownDelimiters: () => normalizeMathMarkdownDelimiters,
|
|
36
|
+
renderMarkdownToHtml: () => renderMarkdownToHtml
|
|
37
|
+
});
|
|
38
|
+
module.exports = __toCommonJS(index_exports);
|
|
39
|
+
|
|
40
|
+
// src/markdown-renderer.tsx
|
|
41
|
+
var import_react = __toESM(require("react"), 1);
|
|
42
|
+
var import_katex_min = require("katex/dist/katex.min.css");
|
|
43
|
+
var import_katex = __toESM(require("katex"), 1);
|
|
44
|
+
var import_shiki = require("shiki");
|
|
45
|
+
|
|
46
|
+
// src/math-markdown.ts
|
|
47
|
+
var INLINE_CODE_SPLIT_REGEX = /(`[^`]*`)/g;
|
|
48
|
+
function normalizeMathSegment(segment) {
|
|
49
|
+
let normalized = segment.replace(/\\\(([\s\S]*?)\\\)/g, (_match, expr) => `$${expr.trim()}$`).replace(/\\\[([^\n]+?)\\\]/g, (_match, expr) => `$${expr.trim()}$`);
|
|
50
|
+
normalized = normalized.replace(/\$\$([^$\n]+?)\$\$/g, (match, expr, offset) => {
|
|
51
|
+
const before = normalized.slice(0, offset);
|
|
52
|
+
const after = normalized.slice(offset + match.length);
|
|
53
|
+
if (!before.trim() && !after.trim()) {
|
|
54
|
+
return `$$${expr.trim()}$$`;
|
|
55
|
+
}
|
|
56
|
+
return `$${expr.trim()}$`;
|
|
57
|
+
});
|
|
58
|
+
return normalized;
|
|
59
|
+
}
|
|
60
|
+
function normalizeInlineMathLine(line) {
|
|
61
|
+
const parts = line.split(INLINE_CODE_SPLIT_REGEX);
|
|
62
|
+
return parts.map((part, index) => {
|
|
63
|
+
if (index % 2 === 1) return part;
|
|
64
|
+
return normalizeMathSegment(part);
|
|
65
|
+
}).join("");
|
|
66
|
+
}
|
|
67
|
+
function normalizeMathMarkdownDelimiters(markdown) {
|
|
68
|
+
const lines = markdown.replace(/\r\n/g, "\n").split("\n");
|
|
69
|
+
const normalized = [];
|
|
70
|
+
let inCodeFence = false;
|
|
71
|
+
let inBracketMathBlock = false;
|
|
72
|
+
let bracketMathBuffer = [];
|
|
73
|
+
for (const line of lines) {
|
|
74
|
+
const trimmed = line.trim();
|
|
75
|
+
if (trimmed.startsWith("```")) {
|
|
76
|
+
if (inBracketMathBlock) {
|
|
77
|
+
normalized.push("\\[");
|
|
78
|
+
normalized.push(...bracketMathBuffer);
|
|
79
|
+
inBracketMathBlock = false;
|
|
80
|
+
bracketMathBuffer = [];
|
|
81
|
+
}
|
|
82
|
+
inCodeFence = !inCodeFence;
|
|
83
|
+
normalized.push(line);
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (inCodeFence) {
|
|
87
|
+
normalized.push(line);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (inBracketMathBlock) {
|
|
91
|
+
const closeIdx = line.indexOf("\\]");
|
|
92
|
+
if (closeIdx !== -1) {
|
|
93
|
+
const beforeClose = line.slice(0, closeIdx);
|
|
94
|
+
if (beforeClose.trim()) {
|
|
95
|
+
bracketMathBuffer.push(beforeClose.trimEnd());
|
|
96
|
+
}
|
|
97
|
+
normalized.push("$$");
|
|
98
|
+
const mathContent = bracketMathBuffer.join("\n").trim();
|
|
99
|
+
if (mathContent) {
|
|
100
|
+
normalized.push(mathContent);
|
|
101
|
+
}
|
|
102
|
+
normalized.push("$$");
|
|
103
|
+
inBracketMathBlock = false;
|
|
104
|
+
bracketMathBuffer = [];
|
|
105
|
+
const remainder = line.slice(closeIdx + 2);
|
|
106
|
+
if (remainder.trim()) {
|
|
107
|
+
normalized.push(normalizeInlineMathLine(remainder));
|
|
108
|
+
}
|
|
109
|
+
} else {
|
|
110
|
+
bracketMathBuffer.push(line);
|
|
111
|
+
}
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const openIdx = line.indexOf("\\[");
|
|
115
|
+
if (openIdx !== -1) {
|
|
116
|
+
const closeIdx = line.indexOf("\\]", openIdx + 2);
|
|
117
|
+
if (closeIdx !== -1) {
|
|
118
|
+
const expr = line.slice(openIdx + 2, closeIdx).trim();
|
|
119
|
+
const before2 = line.slice(0, openIdx);
|
|
120
|
+
const after = line.slice(closeIdx + 2);
|
|
121
|
+
if (!before2.trim() && !after.trim()) {
|
|
122
|
+
normalized.push("$$");
|
|
123
|
+
if (expr) {
|
|
124
|
+
normalized.push(expr);
|
|
125
|
+
}
|
|
126
|
+
normalized.push("$$");
|
|
127
|
+
} else {
|
|
128
|
+
normalized.push(normalizeInlineMathLine(`${before2}$${expr}$${after}`));
|
|
129
|
+
}
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
const before = line.slice(0, openIdx);
|
|
133
|
+
if (!before.trim()) {
|
|
134
|
+
const afterOpen = line.slice(openIdx + 2);
|
|
135
|
+
inBracketMathBlock = true;
|
|
136
|
+
bracketMathBuffer = [];
|
|
137
|
+
if (afterOpen.trim()) {
|
|
138
|
+
bracketMathBuffer.push(afterOpen.trimEnd());
|
|
139
|
+
}
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
normalized.push(normalizeInlineMathLine(line));
|
|
144
|
+
}
|
|
145
|
+
if (inBracketMathBlock) {
|
|
146
|
+
normalized.push("\\[");
|
|
147
|
+
normalized.push(...bracketMathBuffer);
|
|
148
|
+
}
|
|
149
|
+
return normalized.join("\n");
|
|
150
|
+
}
|
|
151
|
+
var MATH_MARKDOWN_RULES_APPENDIX = `Math formatting rules (must follow):
|
|
152
|
+
- Inline math: use single-dollar delimiters like $...$.
|
|
153
|
+
- Display math: use $$ delimiters on their own lines with nothing else on those lines.
|
|
154
|
+
- Do not use \\(...\\) or \\[...\\] delimiters.
|
|
155
|
+
- Do not place display-math $$...$$ inside bullets or table cells; use inline $...$ there.
|
|
156
|
+
- Escape non-math currency dollars as \\$.`;
|
|
157
|
+
|
|
158
|
+
// src/markdown-renderer.tsx
|
|
159
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
160
|
+
var highlightCache = /* @__PURE__ */ new Map();
|
|
161
|
+
var getCacheKey = (code, lang) => `${lang}:${code}`;
|
|
162
|
+
function escapeHtml(text) {
|
|
163
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
164
|
+
}
|
|
165
|
+
function preprocessLatex(latex) {
|
|
166
|
+
return latex.replace(/\\text\{([^}]*)\}/g, (match, content) => {
|
|
167
|
+
const escapedContent = content.replace(/(?<!\\)&/g, "\\&");
|
|
168
|
+
return `\\text{${escapedContent}}`;
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
function getColorClass(colorName) {
|
|
172
|
+
const colorMap = {
|
|
173
|
+
important: "text-red-700",
|
|
174
|
+
definition: "text-sky-700",
|
|
175
|
+
example: "text-green-700",
|
|
176
|
+
note: "text-amber-700",
|
|
177
|
+
formula: "text-violet-600"
|
|
178
|
+
};
|
|
179
|
+
return colorMap[colorName.toLowerCase()] || "";
|
|
180
|
+
}
|
|
181
|
+
function hasMatchingDelimiter(text, startIndex, delimiter) {
|
|
182
|
+
let i = startIndex - 1;
|
|
183
|
+
let depth = 0;
|
|
184
|
+
while (i >= 0) {
|
|
185
|
+
if (delimiter === "*" && text[i] === "*") {
|
|
186
|
+
const nextChar = i + 1 < text.length ? text[i + 1] : null;
|
|
187
|
+
const prevChar = i > 0 ? text[i - 1] : null;
|
|
188
|
+
if (nextChar !== "*" && prevChar !== "*") {
|
|
189
|
+
if (depth === 0) {
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
depth--;
|
|
193
|
+
}
|
|
194
|
+
} else if (delimiter === "**" && i >= 1 && text.slice(i - 1, i + 1) === "**") {
|
|
195
|
+
const prevChar = i > 1 ? text[i - 2] : null;
|
|
196
|
+
const nextNextChar = i + 2 < text.length && i + 2 < startIndex ? text[i + 2] : null;
|
|
197
|
+
if (prevChar !== "*" && nextNextChar !== "*") {
|
|
198
|
+
if (depth === 0) {
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
depth--;
|
|
202
|
+
}
|
|
203
|
+
i--;
|
|
204
|
+
} else if (delimiter === "***" && i >= 2 && text.slice(i - 2, i + 1) === "***") {
|
|
205
|
+
if (depth === 0) {
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
depth--;
|
|
209
|
+
i -= 2;
|
|
210
|
+
} else if (delimiter === "$" && text[i] === "$") {
|
|
211
|
+
const nextChar = i + 1 < text.length ? text[i + 1] : null;
|
|
212
|
+
const prevChar = i > 0 ? text[i - 1] : null;
|
|
213
|
+
if (nextChar !== "$" && prevChar !== "$" && prevChar !== "\\") {
|
|
214
|
+
if (depth === 0) {
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
depth--;
|
|
218
|
+
}
|
|
219
|
+
} else if (delimiter === "`" && text[i] === "`") {
|
|
220
|
+
if (depth === 0) {
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
depth--;
|
|
224
|
+
}
|
|
225
|
+
i--;
|
|
226
|
+
}
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
var IMG_PLACEHOLDER = "IMG";
|
|
230
|
+
var format = (text) => {
|
|
231
|
+
const images = [];
|
|
232
|
+
text = text.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (_, alt, url) => {
|
|
233
|
+
const idx = images.length;
|
|
234
|
+
images.push(
|
|
235
|
+
`<img src="${escapeHtml(url)}" alt="${escapeHtml(alt)}" style="display:inline;max-width:100%;border-radius:0.25rem" />`
|
|
236
|
+
);
|
|
237
|
+
return `${IMG_PLACEHOLDER}${idx}`;
|
|
238
|
+
});
|
|
239
|
+
let inLatex = false;
|
|
240
|
+
let inBoldItalics = false;
|
|
241
|
+
let inBold = false;
|
|
242
|
+
let inItalic = false;
|
|
243
|
+
let inCode = false;
|
|
244
|
+
let i = text.length - 1;
|
|
245
|
+
let currText = "";
|
|
246
|
+
const parts = [];
|
|
247
|
+
let needsLeftRight = null;
|
|
248
|
+
while (i >= 0) {
|
|
249
|
+
if (inCode) {
|
|
250
|
+
if (text[i] === "`") {
|
|
251
|
+
parts.unshift(`<code>${escapeHtml(currText)}</code>`);
|
|
252
|
+
currText = "";
|
|
253
|
+
inCode = false;
|
|
254
|
+
i--;
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
currText = text[i] + currText;
|
|
258
|
+
i--;
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
if (text[i] === "`" && i > 0 && text[i - 1] === "\\") {
|
|
262
|
+
currText = "`" + currText;
|
|
263
|
+
i -= 2;
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
if (text[i] === "`" && !inLatex && !inBoldItalics && !inBold && !inItalic) {
|
|
267
|
+
if (hasMatchingDelimiter(text, i, "`")) {
|
|
268
|
+
if (currText) {
|
|
269
|
+
parts.unshift(escapeHtml(currText));
|
|
270
|
+
currText = "";
|
|
271
|
+
}
|
|
272
|
+
inCode = true;
|
|
273
|
+
i--;
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
if (text[i] === "$" && i > 0 && text[i - 1] === "\\") {
|
|
278
|
+
if (inLatex) {
|
|
279
|
+
currText = "\\$" + currText;
|
|
280
|
+
} else {
|
|
281
|
+
currText = "$" + currText;
|
|
282
|
+
}
|
|
283
|
+
i -= 2;
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
if (inLatex) {
|
|
287
|
+
if (text[i] === "$") {
|
|
288
|
+
const nextChar = i + 1 < text.length ? text[i + 1] : null;
|
|
289
|
+
const prevChar = i > 0 ? text[i - 1] : null;
|
|
290
|
+
if (nextChar !== "$" && prevChar !== "$" && prevChar !== "\\") {
|
|
291
|
+
let mathContent = currText;
|
|
292
|
+
if (needsLeftRight) {
|
|
293
|
+
const leftMap = {
|
|
294
|
+
"(": "\\left(",
|
|
295
|
+
"[": "\\left[",
|
|
296
|
+
"{": "\\left\\{"
|
|
297
|
+
};
|
|
298
|
+
const rightMap = {
|
|
299
|
+
")": "\\right)",
|
|
300
|
+
"]": "\\right]",
|
|
301
|
+
"}": "\\right\\}"
|
|
302
|
+
};
|
|
303
|
+
if (prevChar && prevChar === needsLeftRight.open) {
|
|
304
|
+
mathContent = leftMap[prevChar] + mathContent + rightMap[needsLeftRight.close];
|
|
305
|
+
i--;
|
|
306
|
+
} else if (needsLeftRight) {
|
|
307
|
+
parts.unshift(escapeHtml(needsLeftRight.close));
|
|
308
|
+
needsLeftRight = null;
|
|
309
|
+
}
|
|
310
|
+
} else {
|
|
311
|
+
needsLeftRight = null;
|
|
312
|
+
}
|
|
313
|
+
try {
|
|
314
|
+
const mathHtml = import_katex.default.renderToString(preprocessLatex(mathContent), {
|
|
315
|
+
displayMode: false,
|
|
316
|
+
throwOnError: false
|
|
317
|
+
});
|
|
318
|
+
parts.unshift(mathHtml);
|
|
319
|
+
currText = "";
|
|
320
|
+
inLatex = false;
|
|
321
|
+
needsLeftRight = null;
|
|
322
|
+
i--;
|
|
323
|
+
continue;
|
|
324
|
+
} catch {
|
|
325
|
+
currText = "$" + currText + "$";
|
|
326
|
+
inLatex = false;
|
|
327
|
+
needsLeftRight = null;
|
|
328
|
+
i--;
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
if (needsLeftRight && text[i] === needsLeftRight.close) {
|
|
334
|
+
i--;
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
currText = text[i] + currText;
|
|
338
|
+
i--;
|
|
339
|
+
} else if (inBoldItalics) {
|
|
340
|
+
if (i >= 2 && text.slice(i - 2, i + 1) === "***") {
|
|
341
|
+
parts.unshift(`<strong><em>${format(currText)}</em></strong>`);
|
|
342
|
+
currText = "";
|
|
343
|
+
inBoldItalics = false;
|
|
344
|
+
i -= 3;
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
currText = text[i] + currText;
|
|
348
|
+
i--;
|
|
349
|
+
} else if (inBold) {
|
|
350
|
+
if (i >= 1 && text.slice(i - 1, i + 1) === "**") {
|
|
351
|
+
parts.unshift(`<strong>${format(currText)}</strong>`);
|
|
352
|
+
currText = "";
|
|
353
|
+
inBold = false;
|
|
354
|
+
i -= 2;
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
currText = text[i] + currText;
|
|
358
|
+
i--;
|
|
359
|
+
} else if (inItalic) {
|
|
360
|
+
if (text[i] === "*") {
|
|
361
|
+
const nextChar = i + 1 < text.length ? text[i + 1] : null;
|
|
362
|
+
const prevChar = i > 0 ? text[i - 1] : null;
|
|
363
|
+
if (nextChar !== "*" && prevChar !== "*") {
|
|
364
|
+
parts.unshift(`<em>${format(currText)}</em>`);
|
|
365
|
+
currText = "";
|
|
366
|
+
inItalic = false;
|
|
367
|
+
i--;
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
currText = text[i] + currText;
|
|
372
|
+
i--;
|
|
373
|
+
} else {
|
|
374
|
+
const prevCharInOriginal = i > 0 ? text[i - 1] : null;
|
|
375
|
+
if (text[i] && [")", "]", "}"].includes(text[i]) && prevCharInOriginal === "$") {
|
|
376
|
+
i--;
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
if (text[i] === "}" && i >= 7 && text.slice(i - 7, i + 1) === "{/color}") {
|
|
380
|
+
if (currText) {
|
|
381
|
+
parts.unshift(escapeHtml(currText));
|
|
382
|
+
currText = "";
|
|
383
|
+
}
|
|
384
|
+
parts.unshift("</span>");
|
|
385
|
+
i -= 8;
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
if (text[i] === "}" && i >= 8) {
|
|
389
|
+
const searchStart = Math.max(0, i - 30);
|
|
390
|
+
const segment = text.slice(searchStart, i + 1);
|
|
391
|
+
const match = segment.match(/\{color:([a-zA-Z]+)\}$/);
|
|
392
|
+
if (match && match[1]) {
|
|
393
|
+
const colorClass = getColorClass(match[1]);
|
|
394
|
+
if (colorClass) {
|
|
395
|
+
if (currText) {
|
|
396
|
+
parts.unshift(escapeHtml(currText));
|
|
397
|
+
currText = "";
|
|
398
|
+
}
|
|
399
|
+
parts.unshift(`<span class="${colorClass}">`);
|
|
400
|
+
i -= match[0].length;
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
if (i >= 2 && text.slice(i - 2, i + 1) === "***") {
|
|
406
|
+
if (hasMatchingDelimiter(text, i - 2, "***")) {
|
|
407
|
+
if (currText) {
|
|
408
|
+
parts.unshift(escapeHtml(currText));
|
|
409
|
+
currText = "";
|
|
410
|
+
}
|
|
411
|
+
inBoldItalics = true;
|
|
412
|
+
i -= 3;
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
if (i >= 1 && text.slice(i - 1, i + 1) === "**") {
|
|
417
|
+
if (hasMatchingDelimiter(text, i - 1, "**")) {
|
|
418
|
+
if (currText) {
|
|
419
|
+
parts.unshift(escapeHtml(currText));
|
|
420
|
+
currText = "";
|
|
421
|
+
}
|
|
422
|
+
inBold = true;
|
|
423
|
+
i -= 2;
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
if (text[i] === "*") {
|
|
428
|
+
const nextChar = i + 1 < text.length ? text[i + 1] : null;
|
|
429
|
+
const prevChar = i > 0 ? text[i - 1] : null;
|
|
430
|
+
if (nextChar !== "*" && prevChar !== "*") {
|
|
431
|
+
if (hasMatchingDelimiter(text, i, "*")) {
|
|
432
|
+
if (currText) {
|
|
433
|
+
parts.unshift(escapeHtml(currText));
|
|
434
|
+
currText = "";
|
|
435
|
+
}
|
|
436
|
+
inItalic = true;
|
|
437
|
+
i--;
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
if (text[i] === "$") {
|
|
443
|
+
const nextChar = i + 1 < text.length ? text[i + 1] : null;
|
|
444
|
+
const prevChar = i > 0 ? text[i - 1] : null;
|
|
445
|
+
if (nextChar !== "$" && prevChar !== "$" && prevChar !== "\\") {
|
|
446
|
+
if (nextChar && /[a-zA-Z0-9]/.test(nextChar)) {
|
|
447
|
+
currText = text[i] + currText;
|
|
448
|
+
i--;
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
if (!nextChar || [
|
|
452
|
+
" ",
|
|
453
|
+
" ",
|
|
454
|
+
".",
|
|
455
|
+
",",
|
|
456
|
+
")",
|
|
457
|
+
"]",
|
|
458
|
+
"}",
|
|
459
|
+
";",
|
|
460
|
+
":",
|
|
461
|
+
"!",
|
|
462
|
+
"?",
|
|
463
|
+
"-",
|
|
464
|
+
'"',
|
|
465
|
+
"'",
|
|
466
|
+
"%",
|
|
467
|
+
"\u2014",
|
|
468
|
+
// Chinese/fullwidth punctuation
|
|
469
|
+
"\uFF08",
|
|
470
|
+
// (
|
|
471
|
+
"\uFF09",
|
|
472
|
+
// )
|
|
473
|
+
"\uFF0C",
|
|
474
|
+
// ,
|
|
475
|
+
"\u3002",
|
|
476
|
+
// 。
|
|
477
|
+
"\uFF1A",
|
|
478
|
+
// :
|
|
479
|
+
"\uFF1B",
|
|
480
|
+
// ;
|
|
481
|
+
"\uFF01",
|
|
482
|
+
// !
|
|
483
|
+
"\uFF1F",
|
|
484
|
+
// ?
|
|
485
|
+
"\u3001",
|
|
486
|
+
// 、
|
|
487
|
+
"\u300B",
|
|
488
|
+
// 》
|
|
489
|
+
"\u300A",
|
|
490
|
+
// 《
|
|
491
|
+
"\u201C",
|
|
492
|
+
// \u201c
|
|
493
|
+
"\u201D",
|
|
494
|
+
// \u201d
|
|
495
|
+
"\u2018",
|
|
496
|
+
// \u2018
|
|
497
|
+
"\u2019",
|
|
498
|
+
// \u2019
|
|
499
|
+
"\u3010",
|
|
500
|
+
// 【
|
|
501
|
+
"\u3011",
|
|
502
|
+
// 】
|
|
503
|
+
// Hindi/Devanagari punctuation
|
|
504
|
+
"\u0964",
|
|
505
|
+
// । (Devanagari Danda - full stop)
|
|
506
|
+
"\u0965"
|
|
507
|
+
// ॥ (Devanagari Double Danda)
|
|
508
|
+
].includes(nextChar) || /[a-zA-Z]/.test(nextChar) || // Allow CJK characters (Chinese, Japanese, Korean) after $
|
|
509
|
+
/[\u4e00-\u9fff\u3400-\u4dbf\uac00-\ud7af\u3040-\u309f\u30a0-\u30ff]/.test(nextChar)) {
|
|
510
|
+
if (currText) {
|
|
511
|
+
parts.unshift(escapeHtml(currText));
|
|
512
|
+
currText = "";
|
|
513
|
+
}
|
|
514
|
+
if (nextChar && [")", "]", "}"].includes(nextChar)) {
|
|
515
|
+
const bracketMap = {
|
|
516
|
+
")": "(",
|
|
517
|
+
"]": "[",
|
|
518
|
+
"}": "{"
|
|
519
|
+
};
|
|
520
|
+
const openBracket = bracketMap[nextChar];
|
|
521
|
+
if (openBracket) {
|
|
522
|
+
needsLeftRight = {
|
|
523
|
+
open: openBracket,
|
|
524
|
+
close: nextChar
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
inLatex = true;
|
|
529
|
+
i--;
|
|
530
|
+
continue;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
currText = text[i] + currText;
|
|
535
|
+
i--;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
if (currText) {
|
|
539
|
+
parts.unshift(escapeHtml(currText));
|
|
540
|
+
}
|
|
541
|
+
let result = parts.join("");
|
|
542
|
+
for (let idx = 0; idx < images.length; idx++) {
|
|
543
|
+
result = result.replace(
|
|
544
|
+
escapeHtml(`${IMG_PLACEHOLDER}${idx}`),
|
|
545
|
+
images[idx]
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
return result;
|
|
549
|
+
};
|
|
550
|
+
var getIndentLevel = (line) => {
|
|
551
|
+
let indent = 0;
|
|
552
|
+
for (let i = 0; i < line.length; i++) {
|
|
553
|
+
if (line[i] === " ") indent++;
|
|
554
|
+
else if (line[i] === " ")
|
|
555
|
+
indent += 4;
|
|
556
|
+
else break;
|
|
557
|
+
}
|
|
558
|
+
return indent;
|
|
559
|
+
};
|
|
560
|
+
var isTableSeparatorRow = (line) => {
|
|
561
|
+
const trimmed = line.trim();
|
|
562
|
+
if (!trimmed.includes("|")) return false;
|
|
563
|
+
const cells = trimmed.split("|").map((cell) => cell.trim()).filter((cell) => cell.length > 0);
|
|
564
|
+
if (cells.length === 0) return false;
|
|
565
|
+
return cells.every((cell) => /^:?-{3,}:?$/.test(cell));
|
|
566
|
+
};
|
|
567
|
+
var splitTableRow = (line) => {
|
|
568
|
+
let row = line.trim();
|
|
569
|
+
if (row.startsWith("|")) row = row.slice(1);
|
|
570
|
+
if (row.endsWith("|")) row = row.slice(0, -1);
|
|
571
|
+
return row.split("|").map((cell) => cell.trim());
|
|
572
|
+
};
|
|
573
|
+
var renderTableBlock = (rows) => {
|
|
574
|
+
if (rows.length < 2) return rows.map((line) => `<p>${format(line.trim())}</p>`).join("");
|
|
575
|
+
const headerCells = splitTableRow(rows[0] || "");
|
|
576
|
+
const separatorCells = splitTableRow(rows[1] || "");
|
|
577
|
+
const bodyRows = rows.slice(2);
|
|
578
|
+
if (headerCells.length === 0 || separatorCells.length === 0) {
|
|
579
|
+
return rows.map((line) => `<p>${format(line.trim())}</p>`).join("");
|
|
580
|
+
}
|
|
581
|
+
const alignments = separatorCells.map((cell) => {
|
|
582
|
+
const startsWithColon = cell.startsWith(":");
|
|
583
|
+
const endsWithColon = cell.endsWith(":");
|
|
584
|
+
if (startsWithColon && endsWithColon) return "center";
|
|
585
|
+
if (endsWithColon) return "right";
|
|
586
|
+
return "left";
|
|
587
|
+
});
|
|
588
|
+
const cellBorder = "border:1px solid var(--color-paper-200);padding:0.5rem 0.75rem;";
|
|
589
|
+
const headerHtml = headerCells.map((cell, index) => {
|
|
590
|
+
const alignment = alignments[index] || "left";
|
|
591
|
+
return `<th style="${cellBorder}text-align:${alignment};font-weight:600">${format(cell)}</th>`;
|
|
592
|
+
}).join("");
|
|
593
|
+
const bodyHtml = bodyRows.filter((row) => row.trim().includes("|")).map((row) => {
|
|
594
|
+
const cells = splitTableRow(row);
|
|
595
|
+
const cellHtml = cells.map((cell, index) => {
|
|
596
|
+
const alignment = alignments[index] || "left";
|
|
597
|
+
return `<td style="${cellBorder}text-align:${alignment};vertical-align:top">${format(cell)}</td>`;
|
|
598
|
+
}).join("");
|
|
599
|
+
return `<tr>${cellHtml}</tr>`;
|
|
600
|
+
}).join("");
|
|
601
|
+
return `<div style="margin:1rem 0;overflow-x:auto"><table style="width:100%;border-collapse:collapse;font-size:0.875rem"><thead><tr>${headerHtml}</tr></thead><tbody>${bodyHtml}</tbody></table></div>`;
|
|
602
|
+
};
|
|
603
|
+
var parseListItems = (lines, startIndex, baseIndent, listType, depth = 0) => {
|
|
604
|
+
const items = [];
|
|
605
|
+
let i = startIndex;
|
|
606
|
+
while (i < lines.length) {
|
|
607
|
+
const line = lines[i];
|
|
608
|
+
if (!line) {
|
|
609
|
+
i++;
|
|
610
|
+
continue;
|
|
611
|
+
}
|
|
612
|
+
const indent = getIndentLevel(line);
|
|
613
|
+
const trimmed = line.trim();
|
|
614
|
+
if (indent < baseIndent) {
|
|
615
|
+
break;
|
|
616
|
+
}
|
|
617
|
+
if (indent === baseIndent) {
|
|
618
|
+
if (listType === "ul" && (trimmed.startsWith("* ") || trimmed.startsWith("- "))) {
|
|
619
|
+
const content = format(trimmed.slice(2));
|
|
620
|
+
let itemContent = `<li>${content}`;
|
|
621
|
+
i++;
|
|
622
|
+
const continuationLines = [];
|
|
623
|
+
while (i < lines.length) {
|
|
624
|
+
const nextLine = lines[i];
|
|
625
|
+
if (!nextLine) {
|
|
626
|
+
i++;
|
|
627
|
+
continue;
|
|
628
|
+
}
|
|
629
|
+
const nextIndent = getIndentLevel(nextLine);
|
|
630
|
+
const nextTrimmed = nextLine.trim();
|
|
631
|
+
if (nextIndent === baseIndent && (nextTrimmed.startsWith("* ") || nextTrimmed.startsWith("- "))) {
|
|
632
|
+
break;
|
|
633
|
+
}
|
|
634
|
+
if (nextIndent <= baseIndent) {
|
|
635
|
+
break;
|
|
636
|
+
}
|
|
637
|
+
if (nextIndent > baseIndent && (nextTrimmed.startsWith("* ") || nextTrimmed.startsWith("- ") || nextTrimmed.match(/^\d+\. /))) {
|
|
638
|
+
const nestedType = nextTrimmed.startsWith("* ") || nextTrimmed.startsWith("- ") ? "ul" : "ol";
|
|
639
|
+
const nested = parseListItems(lines, i, nextIndent, nestedType, depth + 1);
|
|
640
|
+
itemContent += nested.html;
|
|
641
|
+
i = nested.nextIndex;
|
|
642
|
+
continue;
|
|
643
|
+
}
|
|
644
|
+
continuationLines.push(nextLine);
|
|
645
|
+
i++;
|
|
646
|
+
}
|
|
647
|
+
if (continuationLines.length > 0) {
|
|
648
|
+
const continuationHtml = renderMarkdownToHtml(continuationLines.join("\n"));
|
|
649
|
+
const match = continuationHtml.match(/<div class="prose[^"]*">(.*)<\/div>/s);
|
|
650
|
+
if (match && match[1]) {
|
|
651
|
+
itemContent += match[1];
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
itemContent += "</li>";
|
|
655
|
+
items.push(itemContent);
|
|
656
|
+
} else if (listType === "ol" && trimmed.match(/^\d+\. /)) {
|
|
657
|
+
const match = trimmed.match(/^(\d+)\. (.+)$/);
|
|
658
|
+
if (match && match[2]) {
|
|
659
|
+
const content = format(match[2]);
|
|
660
|
+
let itemContent = `<li>${content}`;
|
|
661
|
+
i++;
|
|
662
|
+
const continuationLines = [];
|
|
663
|
+
while (i < lines.length) {
|
|
664
|
+
const nextLine = lines[i];
|
|
665
|
+
if (!nextLine) {
|
|
666
|
+
i++;
|
|
667
|
+
continue;
|
|
668
|
+
}
|
|
669
|
+
const nextIndent = getIndentLevel(nextLine);
|
|
670
|
+
const nextTrimmed = nextLine.trim();
|
|
671
|
+
if (nextIndent === baseIndent && nextTrimmed.match(/^\d+\. /)) {
|
|
672
|
+
break;
|
|
673
|
+
}
|
|
674
|
+
if (nextIndent <= baseIndent) {
|
|
675
|
+
break;
|
|
676
|
+
}
|
|
677
|
+
if (nextIndent > baseIndent && (nextTrimmed.startsWith("* ") || nextTrimmed.startsWith("- ") || nextTrimmed.match(/^\d+\. /))) {
|
|
678
|
+
const nestedType = nextTrimmed.startsWith("* ") || nextTrimmed.startsWith("- ") ? "ul" : "ol";
|
|
679
|
+
const nested = parseListItems(lines, i, nextIndent, nestedType, depth + 1);
|
|
680
|
+
itemContent += nested.html;
|
|
681
|
+
i = nested.nextIndex;
|
|
682
|
+
continue;
|
|
683
|
+
}
|
|
684
|
+
continuationLines.push(nextLine);
|
|
685
|
+
i++;
|
|
686
|
+
}
|
|
687
|
+
if (continuationLines.length > 0) {
|
|
688
|
+
const continuationHtml = renderMarkdownToHtml(continuationLines.join("\n"));
|
|
689
|
+
const match2 = continuationHtml.match(/<div class="prose[^"]*">(.*)<\/div>/s);
|
|
690
|
+
if (match2 && match2[1]) {
|
|
691
|
+
itemContent += match2[1];
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
itemContent += "</li>";
|
|
695
|
+
items.push(itemContent);
|
|
696
|
+
} else {
|
|
697
|
+
break;
|
|
698
|
+
}
|
|
699
|
+
} else {
|
|
700
|
+
break;
|
|
701
|
+
}
|
|
702
|
+
} else {
|
|
703
|
+
i++;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
const tag = listType === "ul" ? "ul" : "ol";
|
|
707
|
+
let styleClass = "ml-5 marker:text-current marker:font-bold ";
|
|
708
|
+
if (listType === "ol") {
|
|
709
|
+
if (depth === 0) styleClass += "list-decimal";
|
|
710
|
+
else if (depth === 1) styleClass += "list-[lower-alpha]";
|
|
711
|
+
else styleClass += "list-[lower-roman]";
|
|
712
|
+
} else {
|
|
713
|
+
if (depth === 0) styleClass += "list-disc";
|
|
714
|
+
else if (depth === 1) styleClass += "list-['\u203A_']";
|
|
715
|
+
else styleClass += "list-[square]";
|
|
716
|
+
}
|
|
717
|
+
return {
|
|
718
|
+
html: `<${tag} class="${styleClass}">${items.join("")}</${tag}>`,
|
|
719
|
+
nextIndex: i
|
|
720
|
+
};
|
|
721
|
+
};
|
|
722
|
+
function renderMarkdownToHtml(markdown, options) {
|
|
723
|
+
const normalizedMarkdown = normalizeMathMarkdownDelimiters(markdown);
|
|
724
|
+
const lines = normalizedMarkdown.split("\n");
|
|
725
|
+
const parts = [];
|
|
726
|
+
let i = 0;
|
|
727
|
+
let codeBlockIndex = 0;
|
|
728
|
+
while (i < lines.length) {
|
|
729
|
+
const line = lines[i];
|
|
730
|
+
if (!line) {
|
|
731
|
+
i++;
|
|
732
|
+
continue;
|
|
733
|
+
}
|
|
734
|
+
const trimmed = line.trim();
|
|
735
|
+
if (trimmed.startsWith("# ")) {
|
|
736
|
+
const content2 = format(trimmed.slice(2));
|
|
737
|
+
parts.push(`<h1 class="text-xl">${content2}</h1>`);
|
|
738
|
+
i++;
|
|
739
|
+
continue;
|
|
740
|
+
} else if (trimmed.startsWith("## ")) {
|
|
741
|
+
const content2 = format(trimmed.slice(3));
|
|
742
|
+
parts.push(`<h2 class="text-lg">${content2}</h2>`);
|
|
743
|
+
i++;
|
|
744
|
+
continue;
|
|
745
|
+
} else if (trimmed.startsWith("### ")) {
|
|
746
|
+
const content2 = format(trimmed.slice(4));
|
|
747
|
+
parts.push(`<h3 class="text-base">${content2}</h3>`);
|
|
748
|
+
i++;
|
|
749
|
+
continue;
|
|
750
|
+
} else if (trimmed.startsWith("#### ")) {
|
|
751
|
+
const content2 = format(trimmed.slice(5));
|
|
752
|
+
parts.push(`<h4>${content2}</h4>`);
|
|
753
|
+
i++;
|
|
754
|
+
continue;
|
|
755
|
+
} else if (trimmed.startsWith("##### ")) {
|
|
756
|
+
const content2 = format(trimmed.slice(6));
|
|
757
|
+
parts.push(`<h5>${content2}</h5>`);
|
|
758
|
+
i++;
|
|
759
|
+
continue;
|
|
760
|
+
} else if (trimmed.startsWith("* ") || trimmed.startsWith("- ")) {
|
|
761
|
+
const indent = getIndentLevel(line);
|
|
762
|
+
const result = parseListItems(lines, i, indent, "ul");
|
|
763
|
+
parts.push(result.html);
|
|
764
|
+
i = result.nextIndex;
|
|
765
|
+
continue;
|
|
766
|
+
} else if (trimmed.match(/^\d+\. /)) {
|
|
767
|
+
const indent = getIndentLevel(line);
|
|
768
|
+
const result = parseListItems(lines, i, indent, "ol");
|
|
769
|
+
parts.push(result.html);
|
|
770
|
+
i = result.nextIndex;
|
|
771
|
+
continue;
|
|
772
|
+
} else if (trimmed.startsWith("$$") && trimmed.endsWith("$$") && trimmed.length >= 4) {
|
|
773
|
+
const mathContent = trimmed.slice(2, -2).trim();
|
|
774
|
+
try {
|
|
775
|
+
const mathHtml = import_katex.default.renderToString(preprocessLatex(mathContent), {
|
|
776
|
+
displayMode: true,
|
|
777
|
+
throwOnError: false
|
|
778
|
+
});
|
|
779
|
+
parts.push(`<div>${mathHtml}</div>`);
|
|
780
|
+
} catch {
|
|
781
|
+
parts.push(`<div>${format(trimmed)}</div>`);
|
|
782
|
+
}
|
|
783
|
+
i++;
|
|
784
|
+
continue;
|
|
785
|
+
} else if (trimmed === "$$") {
|
|
786
|
+
const mathLines = [];
|
|
787
|
+
i++;
|
|
788
|
+
while (i < lines.length) {
|
|
789
|
+
const mathLine = lines[i];
|
|
790
|
+
const mathTrimmed = mathLine?.trim() || "";
|
|
791
|
+
if (mathTrimmed === "$$") {
|
|
792
|
+
const mathContent = mathLines.join("\n");
|
|
793
|
+
try {
|
|
794
|
+
const mathHtml = import_katex.default.renderToString(preprocessLatex(mathContent), {
|
|
795
|
+
displayMode: true,
|
|
796
|
+
throwOnError: false
|
|
797
|
+
});
|
|
798
|
+
parts.push(`<div>${mathHtml}</div>`);
|
|
799
|
+
} catch {
|
|
800
|
+
parts.push(`<div>${format(mathContent)}</div>`);
|
|
801
|
+
}
|
|
802
|
+
i++;
|
|
803
|
+
break;
|
|
804
|
+
}
|
|
805
|
+
mathLines.push(mathLine || "");
|
|
806
|
+
i++;
|
|
807
|
+
}
|
|
808
|
+
continue;
|
|
809
|
+
} else if (trimmed.startsWith("```")) {
|
|
810
|
+
const language = trimmed.slice(3).trim();
|
|
811
|
+
const codeLines = [];
|
|
812
|
+
i++;
|
|
813
|
+
while (i < lines.length) {
|
|
814
|
+
const codeLine = lines[i];
|
|
815
|
+
const codeTrimmed = codeLine?.trim() || "";
|
|
816
|
+
if (codeTrimmed === "```") {
|
|
817
|
+
const codeContent = codeLines.join("\n");
|
|
818
|
+
const escapedCode = escapeHtml(codeContent);
|
|
819
|
+
const escapedLang = escapeHtml(language || "text");
|
|
820
|
+
const isExecutable = options?.executableLanguages && language && options.executableLanguages.includes(language.toLowerCase());
|
|
821
|
+
const currentIndex = codeBlockIndex;
|
|
822
|
+
codeBlockIndex++;
|
|
823
|
+
if (isExecutable) {
|
|
824
|
+
parts.push(
|
|
825
|
+
`<div class="md-code-block" data-language="${escapedLang}" data-code-index="${currentIndex}" data-executable="true"><div class="md-code-block-header" style="display:flex;align-items:center;justify-content:space-between;padding:0.25rem 0.75rem;background:#f0f0f0;border-radius:0.375rem 0.375rem 0 0;border:1px solid #e0e0e0;border-bottom:none"><span style="font-size:0.75rem;color:#666;font-family:monospace">${escapedLang}</span><button class="md-run-btn" data-code-index="${currentIndex}" style="padding:0.2rem 0.6rem;font-size:0.75rem;border-radius:0.25rem;border:1px solid #ccc;background:#fff;cursor:pointer;font-family:inherit">Run</button></div><pre style="overflow-x:auto;border-radius:0 0 0.375rem 0.375rem;background:#f7f7f7;color:#1f2937;padding:0.75rem;font-size:0.875rem;margin:0;border:1px solid #e0e0e0;border-top:none"><code class="language-${escapedLang}" data-executable="true">${escapedCode}</code></pre><div class="md-code-output" data-output-for="${currentIndex}" style="display:none"></div></div>`
|
|
826
|
+
);
|
|
827
|
+
} else {
|
|
828
|
+
parts.push(
|
|
829
|
+
`<div class="code-block-wrapper relative group">
|
|
830
|
+
<button class="copy-btn absolute top-2 right-2 p-1.5 rounded bg-paper-200 hover:bg-paper-300 dark:bg-oxford-700 dark:hover:bg-oxford-600 opacity-0 group-hover:opacity-100 transition-opacity" data-code="${escapeHtml(codeContent)}" title="Copy code">
|
|
831
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="copy-icon"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>
|
|
832
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="check-icon hidden text-green-600"><polyline points="20 6 9 17 4 12"/></svg>
|
|
833
|
+
</button>
|
|
834
|
+
<pre data-lang="${escapeHtml(language || "text")}" data-code="${escapeHtml(codeContent)}"><code class="language-${escapeHtml(language || "text")}">${escapedCode}</code></pre>
|
|
835
|
+
</div>`
|
|
836
|
+
);
|
|
837
|
+
}
|
|
838
|
+
i++;
|
|
839
|
+
break;
|
|
840
|
+
}
|
|
841
|
+
codeLines.push(codeLine || "");
|
|
842
|
+
i++;
|
|
843
|
+
}
|
|
844
|
+
continue;
|
|
845
|
+
} else if (trimmed === "---") {
|
|
846
|
+
parts.push("<hr />");
|
|
847
|
+
i++;
|
|
848
|
+
continue;
|
|
849
|
+
}
|
|
850
|
+
const imageMatch = trimmed.match(/^!\[([^\]]*)\]\(([^)]+)\)$/);
|
|
851
|
+
if (imageMatch && imageMatch[2]) {
|
|
852
|
+
const alt = escapeHtml(imageMatch[1] ?? "");
|
|
853
|
+
const src = escapeHtml(imageMatch[2]);
|
|
854
|
+
parts.push(`<img src="${src}" alt="${alt}" style="max-width:100%;border-radius:0.25rem;margin:0.75rem 0" />`);
|
|
855
|
+
i++;
|
|
856
|
+
continue;
|
|
857
|
+
}
|
|
858
|
+
const nextNonEmptyIndex = (() => {
|
|
859
|
+
let j = i + 1;
|
|
860
|
+
while (j < lines.length && !(lines[j] || "").trim()) {
|
|
861
|
+
j++;
|
|
862
|
+
}
|
|
863
|
+
return j;
|
|
864
|
+
})();
|
|
865
|
+
if (trimmed.includes("|") && nextNonEmptyIndex < lines.length && isTableSeparatorRow((lines[nextNonEmptyIndex] || "").trim())) {
|
|
866
|
+
const tableLines = [trimmed];
|
|
867
|
+
tableLines.push((lines[nextNonEmptyIndex] || "").trim());
|
|
868
|
+
i = nextNonEmptyIndex + 1;
|
|
869
|
+
while (i < lines.length) {
|
|
870
|
+
const candidate = (lines[i] || "").trim();
|
|
871
|
+
if (!candidate) {
|
|
872
|
+
const lookahead = i + 1;
|
|
873
|
+
if (lookahead < lines.length && (lines[lookahead] || "").trim().includes("|")) {
|
|
874
|
+
i++;
|
|
875
|
+
continue;
|
|
876
|
+
}
|
|
877
|
+
break;
|
|
878
|
+
}
|
|
879
|
+
if (!candidate.includes("|")) {
|
|
880
|
+
break;
|
|
881
|
+
}
|
|
882
|
+
tableLines.push(candidate);
|
|
883
|
+
i++;
|
|
884
|
+
}
|
|
885
|
+
parts.push(renderTableBlock(tableLines));
|
|
886
|
+
continue;
|
|
887
|
+
}
|
|
888
|
+
const content = format(trimmed);
|
|
889
|
+
parts.push(`<p>${content}</p>`);
|
|
890
|
+
i++;
|
|
891
|
+
}
|
|
892
|
+
return `<div class="prose max-w-none">${parts.join("")}</div>`;
|
|
893
|
+
}
|
|
894
|
+
var MarkdownRenderer = ({
|
|
895
|
+
markdown,
|
|
896
|
+
onRunCode,
|
|
897
|
+
executableLanguages = ["python", "r"]
|
|
898
|
+
}) => {
|
|
899
|
+
const containerRef = (0, import_react.useRef)(null);
|
|
900
|
+
const onRunCodeRef = (0, import_react.useRef)(onRunCode);
|
|
901
|
+
onRunCodeRef.current = onRunCode;
|
|
902
|
+
const [html, setHtml] = import_react.default.useState("");
|
|
903
|
+
const hasRunCode = !!onRunCode;
|
|
904
|
+
import_react.default.useEffect(() => {
|
|
905
|
+
const rendered = renderMarkdownToHtml(
|
|
906
|
+
markdown,
|
|
907
|
+
hasRunCode ? { executableLanguages } : void 0
|
|
908
|
+
);
|
|
909
|
+
setHtml(rendered);
|
|
910
|
+
}, [markdown, hasRunCode, executableLanguages]);
|
|
911
|
+
(0, import_react.useEffect)(() => {
|
|
912
|
+
if (!containerRef.current) return;
|
|
913
|
+
const highlightCodeBlocks = async () => {
|
|
914
|
+
const codeBlocks = containerRef.current?.querySelectorAll("pre[data-lang]");
|
|
915
|
+
if (!codeBlocks) return;
|
|
916
|
+
for (const block of Array.from(codeBlocks)) {
|
|
917
|
+
const preElement = block;
|
|
918
|
+
const lang = preElement.getAttribute("data-lang") || "plaintext";
|
|
919
|
+
const code = preElement.getAttribute("data-code") || "";
|
|
920
|
+
const effectiveLang = lang === "text" || lang === "" ? "plaintext" : lang;
|
|
921
|
+
const cacheKey = getCacheKey(code, effectiveLang);
|
|
922
|
+
let highlighted = highlightCache.get(cacheKey);
|
|
923
|
+
if (!highlighted) {
|
|
924
|
+
try {
|
|
925
|
+
highlighted = await (0, import_shiki.codeToHtml)(code, {
|
|
926
|
+
lang: effectiveLang,
|
|
927
|
+
theme: "github-light"
|
|
928
|
+
});
|
|
929
|
+
highlightCache.set(cacheKey, highlighted);
|
|
930
|
+
} catch (error) {
|
|
931
|
+
console.warn(`Failed to highlight code block with language '${effectiveLang}':`, error);
|
|
932
|
+
preElement.classList.add("code-plain");
|
|
933
|
+
continue;
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
const tempDiv = document.createElement("div");
|
|
937
|
+
tempDiv.innerHTML = highlighted;
|
|
938
|
+
const newPre = tempDiv.firstElementChild;
|
|
939
|
+
if (newPre) {
|
|
940
|
+
preElement.replaceWith(newPre);
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
};
|
|
944
|
+
const copyButtons = containerRef.current.querySelectorAll(".copy-btn");
|
|
945
|
+
copyButtons.forEach((btn) => {
|
|
946
|
+
btn.addEventListener("click", async (e) => {
|
|
947
|
+
e.preventDefault();
|
|
948
|
+
e.stopPropagation();
|
|
949
|
+
const button = btn;
|
|
950
|
+
const code = button.getAttribute("data-code") || "";
|
|
951
|
+
try {
|
|
952
|
+
await navigator.clipboard.writeText(code);
|
|
953
|
+
const copyIcon = button.querySelector(".copy-icon");
|
|
954
|
+
const checkIcon = button.querySelector(".check-icon");
|
|
955
|
+
if (copyIcon && checkIcon) {
|
|
956
|
+
copyIcon.classList.add("hidden");
|
|
957
|
+
checkIcon.classList.remove("hidden");
|
|
958
|
+
setTimeout(() => {
|
|
959
|
+
copyIcon.classList.remove("hidden");
|
|
960
|
+
checkIcon.classList.add("hidden");
|
|
961
|
+
}, 2e3);
|
|
962
|
+
}
|
|
963
|
+
} catch (err) {
|
|
964
|
+
console.error("Failed to copy code:", err);
|
|
965
|
+
}
|
|
966
|
+
});
|
|
967
|
+
});
|
|
968
|
+
highlightCodeBlocks();
|
|
969
|
+
}, [html]);
|
|
970
|
+
const handleRun = (0, import_react.useCallback)(
|
|
971
|
+
async (button, block) => {
|
|
972
|
+
const codeEl = block.querySelector("code[data-executable]");
|
|
973
|
+
const outputEl = block.querySelector(".md-code-output");
|
|
974
|
+
const language = block.getAttribute("data-language") || "";
|
|
975
|
+
const code = codeEl?.textContent || "";
|
|
976
|
+
if (!onRunCodeRef.current || !outputEl) return;
|
|
977
|
+
button.disabled = true;
|
|
978
|
+
button.textContent = "Running...";
|
|
979
|
+
outputEl.style.display = "block";
|
|
980
|
+
outputEl.textContent = "Running...";
|
|
981
|
+
outputEl.style.background = "#f7f7f7";
|
|
982
|
+
outputEl.style.color = "#333";
|
|
983
|
+
outputEl.className = "md-code-output";
|
|
984
|
+
try {
|
|
985
|
+
const result = await onRunCodeRef.current(code, language);
|
|
986
|
+
outputEl.textContent = "";
|
|
987
|
+
outputEl.className = "md-code-output";
|
|
988
|
+
if (result.error) {
|
|
989
|
+
outputEl.className = "md-code-output md-code-error";
|
|
990
|
+
outputEl.style.background = "#fef2f2";
|
|
991
|
+
outputEl.style.color = "#dc2626";
|
|
992
|
+
outputEl.textContent = result.error;
|
|
993
|
+
} else if (result.output) {
|
|
994
|
+
outputEl.style.background = "#f7f7f7";
|
|
995
|
+
outputEl.style.color = "#333";
|
|
996
|
+
outputEl.textContent = result.output;
|
|
997
|
+
}
|
|
998
|
+
if (result.images && result.images.length > 0) {
|
|
999
|
+
for (const src of result.images) {
|
|
1000
|
+
const img = document.createElement("img");
|
|
1001
|
+
img.src = src;
|
|
1002
|
+
img.style.maxWidth = "100%";
|
|
1003
|
+
img.style.borderRadius = "0.25rem";
|
|
1004
|
+
img.style.marginTop = "0.5rem";
|
|
1005
|
+
outputEl.appendChild(img);
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
if (!result.output && !result.error && (!result.images || result.images.length === 0)) {
|
|
1009
|
+
outputEl.style.display = "none";
|
|
1010
|
+
}
|
|
1011
|
+
} catch (err) {
|
|
1012
|
+
outputEl.className = "md-code-output md-code-error";
|
|
1013
|
+
outputEl.style.background = "#fef2f2";
|
|
1014
|
+
outputEl.style.color = "#dc2626";
|
|
1015
|
+
outputEl.textContent = err instanceof Error ? err.message : "Execution failed";
|
|
1016
|
+
} finally {
|
|
1017
|
+
button.disabled = false;
|
|
1018
|
+
button.textContent = "Run";
|
|
1019
|
+
}
|
|
1020
|
+
},
|
|
1021
|
+
[]
|
|
1022
|
+
);
|
|
1023
|
+
(0, import_react.useEffect)(() => {
|
|
1024
|
+
const container = containerRef.current;
|
|
1025
|
+
if (!container || !onRunCodeRef.current) return;
|
|
1026
|
+
const buttons = container.querySelectorAll(".md-run-btn");
|
|
1027
|
+
const handlers = [];
|
|
1028
|
+
buttons.forEach((btn) => {
|
|
1029
|
+
const block = btn.closest(".md-code-block");
|
|
1030
|
+
if (!block) return;
|
|
1031
|
+
const handler = () => handleRun(btn, block);
|
|
1032
|
+
btn.addEventListener("click", handler);
|
|
1033
|
+
handlers.push([btn, handler]);
|
|
1034
|
+
});
|
|
1035
|
+
return () => {
|
|
1036
|
+
handlers.forEach(
|
|
1037
|
+
([btn, handler]) => btn.removeEventListener("click", handler)
|
|
1038
|
+
);
|
|
1039
|
+
};
|
|
1040
|
+
}, [html, handleRun]);
|
|
1041
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref: containerRef, dangerouslySetInnerHTML: { __html: html } });
|
|
1042
|
+
};
|
|
1043
|
+
var markdown_renderer_default = MarkdownRenderer;
|
|
1044
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1045
|
+
0 && (module.exports = {
|
|
1046
|
+
MATH_MARKDOWN_RULES_APPENDIX,
|
|
1047
|
+
MarkdownRenderer,
|
|
1048
|
+
normalizeMathMarkdownDelimiters,
|
|
1049
|
+
renderMarkdownToHtml
|
|
1050
|
+
});
|