@changerawr/markdown 1.0.0 → 1.0.2
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.js +0 -13
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +0 -13
- package/dist/index.mjs.map +1 -1
- package/dist/standalone.d.mts +3 -5
- package/dist/standalone.d.ts +3 -5
- package/dist/standalone.js +1426 -75
- package/dist/standalone.js.map +1 -1
- package/dist/standalone.mjs +1383 -75
- package/dist/standalone.mjs.map +1 -1
- package/dist/tailwind/index.d.mts +27 -0
- package/dist/tailwind/index.d.ts +27 -0
- package/dist/tailwind/index.js +224 -0
- package/dist/tailwind/index.js.map +1 -0
- package/dist/tailwind/index.mjs +187 -0
- package/dist/tailwind/index.mjs.map +1 -0
- package/package.json +1 -1
- package/dist/standalone.global.js +0 -193
- package/dist/standalone.global.js.map +0 -1
package/dist/standalone.mjs
CHANGED
|
@@ -1,117 +1,1203 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
1
|
+
// src/parser.ts
|
|
2
|
+
var MarkdownParser = class {
|
|
3
|
+
constructor(config) {
|
|
4
|
+
this.rules = [];
|
|
5
|
+
this.warnings = [];
|
|
6
|
+
this.config = config || {};
|
|
7
|
+
}
|
|
8
|
+
addRule(rule) {
|
|
9
|
+
this.rules.push(rule);
|
|
10
|
+
this.rules.sort((a, b) => {
|
|
11
|
+
if (a.name.includes("alert") || a.name.includes("embed") || a.name.includes("button")) return -1;
|
|
12
|
+
if (b.name.includes("alert") || b.name.includes("embed") || b.name.includes("button")) return 1;
|
|
13
|
+
if (a.name === "task-list") return -1;
|
|
14
|
+
if (b.name === "task-list") return 1;
|
|
15
|
+
if (a.name === "list" && b.name === "task-list") return 1;
|
|
16
|
+
if (b.name === "list" && a.name === "task-list") return -1;
|
|
17
|
+
return a.name.localeCompare(b.name);
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
setupDefaultRulesIfEmpty() {
|
|
21
|
+
const hasDefaultRules = this.rules.some(
|
|
22
|
+
(rule) => !["alert", "button", "embed"].includes(rule.name)
|
|
23
|
+
);
|
|
24
|
+
if (!hasDefaultRules) {
|
|
25
|
+
this.setupDefaultRules();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
hasRule(name) {
|
|
29
|
+
return this.rules.some((rule) => rule.name === name);
|
|
30
|
+
}
|
|
31
|
+
parse(markdown2) {
|
|
32
|
+
this.warnings = [];
|
|
33
|
+
this.setupDefaultRulesIfEmpty();
|
|
34
|
+
const processedMarkdown = this.preprocessMarkdown(markdown2);
|
|
35
|
+
const tokens = [];
|
|
36
|
+
let remaining = processedMarkdown;
|
|
37
|
+
let iterationCount = 0;
|
|
38
|
+
const maxIterations = this.config.maxIterations || markdown2.length * 2;
|
|
39
|
+
while (remaining.length > 0 && iterationCount < maxIterations) {
|
|
40
|
+
iterationCount++;
|
|
41
|
+
let matched = false;
|
|
42
|
+
let bestMatch = null;
|
|
43
|
+
for (const rule of this.rules) {
|
|
44
|
+
try {
|
|
45
|
+
const pattern = new RegExp(rule.pattern.source, rule.pattern.flags.replace("g", ""));
|
|
46
|
+
const match = remaining.match(pattern);
|
|
47
|
+
if (match && match.index !== void 0) {
|
|
48
|
+
const priority = match.index === 0 ? 1e3 : 1e3 - match.index;
|
|
49
|
+
if (!bestMatch || priority > bestMatch.priority || priority === bestMatch.priority && match.index < (bestMatch.match.index || 0)) {
|
|
50
|
+
bestMatch = { rule, match, priority };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
} catch (error) {
|
|
54
|
+
if (this.config.debugMode) {
|
|
55
|
+
console.warn(`Error in rule "${rule.name}":`, error);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (bestMatch && bestMatch.match.index !== void 0) {
|
|
60
|
+
const { rule, match } = bestMatch;
|
|
61
|
+
const matchIndex = match.index;
|
|
62
|
+
if (matchIndex > 0) {
|
|
63
|
+
const textBefore = remaining.slice(0, matchIndex);
|
|
64
|
+
tokens.push({
|
|
65
|
+
type: "text",
|
|
66
|
+
content: textBefore,
|
|
67
|
+
raw: textBefore
|
|
68
|
+
});
|
|
69
|
+
remaining = remaining.slice(matchIndex);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
const token = rule.render(match);
|
|
74
|
+
tokens.push({
|
|
75
|
+
...token,
|
|
76
|
+
raw: match[0] || ""
|
|
77
|
+
});
|
|
78
|
+
remaining = remaining.slice(match[0]?.length || 0);
|
|
79
|
+
matched = true;
|
|
80
|
+
} catch (error) {
|
|
81
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
82
|
+
this.warnings.push(`Failed to render ${rule.name}: ${errorMessage}`);
|
|
83
|
+
const char = remaining[0];
|
|
84
|
+
if (char) {
|
|
85
|
+
tokens.push({
|
|
86
|
+
type: "text",
|
|
87
|
+
content: char,
|
|
88
|
+
raw: char
|
|
89
|
+
});
|
|
90
|
+
remaining = remaining.slice(1);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (!matched) {
|
|
95
|
+
const char = remaining[0];
|
|
96
|
+
if (char) {
|
|
97
|
+
tokens.push({
|
|
98
|
+
type: "text",
|
|
99
|
+
content: char,
|
|
100
|
+
raw: char
|
|
101
|
+
});
|
|
102
|
+
remaining = remaining.slice(1);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (iterationCount >= maxIterations) {
|
|
107
|
+
this.warnings.push("Parser hit maximum iterations - possible infinite loop detected");
|
|
108
|
+
}
|
|
109
|
+
const processedTokens = this.postProcessTokens(tokens);
|
|
110
|
+
return processedTokens;
|
|
111
|
+
}
|
|
112
|
+
getWarnings() {
|
|
113
|
+
return [...this.warnings];
|
|
114
|
+
}
|
|
115
|
+
getConfig() {
|
|
116
|
+
return { ...this.config };
|
|
117
|
+
}
|
|
118
|
+
updateConfig(config) {
|
|
119
|
+
this.config = { ...this.config, ...config };
|
|
120
|
+
}
|
|
121
|
+
setDebugMode(enabled) {
|
|
122
|
+
this.config.debugMode = enabled;
|
|
123
|
+
}
|
|
124
|
+
clearWarnings() {
|
|
125
|
+
this.warnings = [];
|
|
126
|
+
}
|
|
127
|
+
getIterationCount() {
|
|
128
|
+
return 0;
|
|
129
|
+
}
|
|
130
|
+
preprocessMarkdown(markdown2) {
|
|
131
|
+
if (this.config.validateMarkdown) {
|
|
132
|
+
this.validateMarkdown(markdown2);
|
|
133
|
+
}
|
|
134
|
+
return markdown2.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
135
|
+
}
|
|
136
|
+
validateMarkdown(markdown2) {
|
|
137
|
+
const boldMatches = markdown2.match(/\*\*/g);
|
|
138
|
+
if (boldMatches && boldMatches.length % 2 !== 0) {
|
|
139
|
+
this.warnings.push("Unclosed bold markers (**) detected - some bold formatting may not work");
|
|
140
|
+
}
|
|
141
|
+
const italicMatches = markdown2.match(/(?<!\*)\*(?!\*)/g);
|
|
142
|
+
if (italicMatches && italicMatches.length % 2 !== 0) {
|
|
143
|
+
this.warnings.push("Unclosed italic markers (*) detected - some italic formatting may not work");
|
|
144
|
+
}
|
|
145
|
+
const codeBlockMatches = markdown2.match(/```/g);
|
|
146
|
+
if (codeBlockMatches && codeBlockMatches.length % 2 !== 0) {
|
|
147
|
+
this.warnings.push("Unclosed code blocks (```) detected - some code formatting may not work");
|
|
148
|
+
}
|
|
149
|
+
const inlineCodeMatches = markdown2.match(/`/g);
|
|
150
|
+
if (inlineCodeMatches && inlineCodeMatches.length % 2 !== 0) {
|
|
151
|
+
this.warnings.push("Unclosed inline code markers (`) detected - some code formatting may not work");
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
postProcessTokens(tokens) {
|
|
155
|
+
const processed = [];
|
|
156
|
+
let i = 0;
|
|
157
|
+
while (i < tokens.length) {
|
|
158
|
+
const token = tokens[i];
|
|
159
|
+
if (token && token.type === "text") {
|
|
160
|
+
let textContent = token.content || token.raw || "";
|
|
161
|
+
let j = i + 1;
|
|
162
|
+
while (j < tokens.length && tokens[j] && tokens[j].type === "text") {
|
|
163
|
+
const nextToken = tokens[j];
|
|
164
|
+
textContent += nextToken.content || nextToken.raw || "";
|
|
165
|
+
j++;
|
|
166
|
+
}
|
|
167
|
+
if (textContent.trim().length > 0 || textContent.includes("\n")) {
|
|
168
|
+
processed.push({
|
|
169
|
+
type: "text",
|
|
170
|
+
content: textContent,
|
|
171
|
+
raw: textContent
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
i = j;
|
|
175
|
+
} else if (token) {
|
|
176
|
+
processed.push(token);
|
|
177
|
+
i++;
|
|
178
|
+
} else {
|
|
179
|
+
i++;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return processed;
|
|
183
|
+
}
|
|
184
|
+
setupDefaultRules() {
|
|
185
|
+
this.addRule({
|
|
186
|
+
name: "heading",
|
|
187
|
+
pattern: /^(#{1,6})\s+(.+)$/m,
|
|
188
|
+
render: (match) => ({
|
|
189
|
+
type: "heading",
|
|
190
|
+
content: match[2]?.trim() || "",
|
|
191
|
+
raw: match[0] || "",
|
|
192
|
+
attributes: {
|
|
193
|
+
level: String(match[1]?.length || 1)
|
|
194
|
+
}
|
|
195
|
+
})
|
|
196
|
+
});
|
|
197
|
+
this.addRule({
|
|
198
|
+
name: "codeblock",
|
|
199
|
+
pattern: /```(\w+)?\s*\n([\s\S]*?)\n```/,
|
|
200
|
+
render: (match) => ({
|
|
201
|
+
type: "codeblock",
|
|
202
|
+
content: match[2] || "",
|
|
203
|
+
raw: match[0] || "",
|
|
204
|
+
attributes: {
|
|
205
|
+
language: match[1] || "text"
|
|
206
|
+
}
|
|
207
|
+
})
|
|
208
|
+
});
|
|
209
|
+
this.addRule({
|
|
210
|
+
name: "hard-break-backslash",
|
|
211
|
+
pattern: /\\\s*\n/,
|
|
212
|
+
render: (match) => ({
|
|
213
|
+
type: "line-break",
|
|
214
|
+
content: "",
|
|
215
|
+
raw: match[0] || ""
|
|
216
|
+
})
|
|
217
|
+
});
|
|
218
|
+
this.addRule({
|
|
219
|
+
name: "hard-break-spaces",
|
|
220
|
+
pattern: / +\n/,
|
|
221
|
+
render: (match) => ({
|
|
222
|
+
type: "line-break",
|
|
223
|
+
content: "",
|
|
224
|
+
raw: match[0] || ""
|
|
225
|
+
})
|
|
226
|
+
});
|
|
227
|
+
this.addRule({
|
|
228
|
+
name: "paragraph-break",
|
|
229
|
+
pattern: /\n\s*\n/,
|
|
230
|
+
render: (match) => ({
|
|
231
|
+
type: "paragraph-break",
|
|
232
|
+
content: "",
|
|
233
|
+
raw: match[0] || ""
|
|
234
|
+
})
|
|
235
|
+
});
|
|
236
|
+
this.addRule({
|
|
237
|
+
name: "bold",
|
|
238
|
+
pattern: /\*\*((?:(?!\*\*).)+)\*\*/,
|
|
239
|
+
render: (match) => ({
|
|
240
|
+
type: "bold",
|
|
241
|
+
content: match[1] || "",
|
|
242
|
+
raw: match[0] || ""
|
|
243
|
+
})
|
|
244
|
+
});
|
|
245
|
+
this.addRule({
|
|
246
|
+
name: "italic",
|
|
247
|
+
pattern: /\*((?:(?!\*).)+)\*/,
|
|
248
|
+
render: (match) => ({
|
|
249
|
+
type: "italic",
|
|
250
|
+
content: match[1] || "",
|
|
251
|
+
raw: match[0] || ""
|
|
252
|
+
})
|
|
253
|
+
});
|
|
254
|
+
this.addRule({
|
|
255
|
+
name: "code",
|
|
256
|
+
pattern: /`([^`]+)`/,
|
|
257
|
+
render: (match) => ({
|
|
258
|
+
type: "code",
|
|
259
|
+
content: match[1] || "",
|
|
260
|
+
raw: match[0] || ""
|
|
261
|
+
})
|
|
262
|
+
});
|
|
263
|
+
this.addRule({
|
|
264
|
+
name: "image",
|
|
265
|
+
pattern: /!\[([^\]]*)\]\(([^)]+?)(?:\s+"([^"]+)")?\)/,
|
|
266
|
+
render: (match) => ({
|
|
267
|
+
type: "image",
|
|
268
|
+
content: match[1] || "",
|
|
269
|
+
raw: match[0] || "",
|
|
270
|
+
attributes: {
|
|
271
|
+
alt: match[1] || "",
|
|
272
|
+
src: match[2] || "",
|
|
273
|
+
title: match[3] || ""
|
|
274
|
+
}
|
|
275
|
+
})
|
|
276
|
+
});
|
|
277
|
+
this.addRule({
|
|
278
|
+
name: "link",
|
|
279
|
+
pattern: /\[([^\]]+)\]\(([^)]+)\)/,
|
|
280
|
+
render: (match) => ({
|
|
281
|
+
type: "link",
|
|
282
|
+
content: match[1] || "",
|
|
283
|
+
raw: match[0] || "",
|
|
284
|
+
attributes: {
|
|
285
|
+
href: match[2] || ""
|
|
286
|
+
}
|
|
287
|
+
})
|
|
288
|
+
});
|
|
289
|
+
this.addRule({
|
|
290
|
+
name: "task-list",
|
|
291
|
+
pattern: /^(\s*)-\s*\[([ xX])\]\s*(.+)$/m,
|
|
292
|
+
render: (match) => ({
|
|
293
|
+
type: "task-item",
|
|
294
|
+
content: match[3] || "",
|
|
295
|
+
raw: match[0] || "",
|
|
296
|
+
attributes: {
|
|
297
|
+
checked: String((match[2] || "").toLowerCase() === "x")
|
|
298
|
+
}
|
|
299
|
+
})
|
|
300
|
+
});
|
|
301
|
+
this.addRule({
|
|
302
|
+
name: "list",
|
|
303
|
+
pattern: /^(\s*)[-*+]\s+(.+)$/m,
|
|
304
|
+
render: (match) => ({
|
|
305
|
+
type: "list-item",
|
|
306
|
+
content: match[2] || "",
|
|
307
|
+
raw: match[0] || ""
|
|
308
|
+
})
|
|
309
|
+
});
|
|
310
|
+
this.addRule({
|
|
311
|
+
name: "blockquote",
|
|
312
|
+
pattern: /^>\s+(.+)$/m,
|
|
313
|
+
render: (match) => ({
|
|
314
|
+
type: "blockquote",
|
|
315
|
+
content: match[1] || "",
|
|
316
|
+
raw: match[0] || ""
|
|
317
|
+
})
|
|
318
|
+
});
|
|
319
|
+
this.addRule({
|
|
320
|
+
name: "hr",
|
|
321
|
+
pattern: /^---$/m,
|
|
322
|
+
render: (match) => ({
|
|
323
|
+
type: "hr",
|
|
324
|
+
content: "",
|
|
325
|
+
raw: match[0] || ""
|
|
326
|
+
})
|
|
327
|
+
});
|
|
328
|
+
this.addRule({
|
|
329
|
+
name: "soft-break",
|
|
330
|
+
pattern: /\n/,
|
|
331
|
+
render: (match) => ({
|
|
332
|
+
type: "soft-break",
|
|
333
|
+
content: " ",
|
|
334
|
+
// Convert to space for inline text
|
|
335
|
+
raw: match[0] || ""
|
|
336
|
+
})
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
// src/utils.ts
|
|
342
|
+
var DOMPurify = {
|
|
343
|
+
sanitize: (html) => html
|
|
344
|
+
};
|
|
345
|
+
if (typeof window !== "undefined") {
|
|
346
|
+
import("dompurify").then((module) => {
|
|
347
|
+
DOMPurify = module.default;
|
|
348
|
+
}).catch((err) => {
|
|
349
|
+
console.error("Failed to load DOMPurify", err);
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
var ALLOWED_TAGS = [
|
|
353
|
+
// Standard HTML
|
|
354
|
+
"h1",
|
|
355
|
+
"h2",
|
|
356
|
+
"h3",
|
|
357
|
+
"h4",
|
|
358
|
+
"h5",
|
|
359
|
+
"h6",
|
|
360
|
+
"p",
|
|
361
|
+
"br",
|
|
362
|
+
"strong",
|
|
363
|
+
"em",
|
|
364
|
+
"del",
|
|
365
|
+
"ins",
|
|
366
|
+
"a",
|
|
367
|
+
"img",
|
|
368
|
+
"ul",
|
|
369
|
+
"ol",
|
|
370
|
+
"li",
|
|
371
|
+
"blockquote",
|
|
372
|
+
"pre",
|
|
373
|
+
"code",
|
|
374
|
+
"table",
|
|
375
|
+
"thead",
|
|
376
|
+
"tbody",
|
|
377
|
+
"tr",
|
|
378
|
+
"th",
|
|
379
|
+
"td",
|
|
380
|
+
"div",
|
|
381
|
+
"span",
|
|
382
|
+
"sup",
|
|
383
|
+
"sub",
|
|
384
|
+
"hr",
|
|
385
|
+
"input",
|
|
386
|
+
// Embeds
|
|
387
|
+
"iframe",
|
|
388
|
+
"embed",
|
|
389
|
+
"object",
|
|
390
|
+
"param",
|
|
391
|
+
"video",
|
|
392
|
+
"audio",
|
|
393
|
+
"source",
|
|
394
|
+
// SVG
|
|
395
|
+
"svg",
|
|
396
|
+
"path",
|
|
397
|
+
"polyline",
|
|
398
|
+
"line",
|
|
399
|
+
"circle",
|
|
400
|
+
"rect",
|
|
401
|
+
"g",
|
|
402
|
+
"defs",
|
|
403
|
+
"use",
|
|
404
|
+
// Form elements
|
|
405
|
+
"form",
|
|
406
|
+
"fieldset",
|
|
407
|
+
"legend",
|
|
408
|
+
"label",
|
|
409
|
+
"select",
|
|
410
|
+
"option",
|
|
411
|
+
"textarea",
|
|
412
|
+
"button"
|
|
413
|
+
];
|
|
414
|
+
var ALLOWED_ATTR = [
|
|
415
|
+
// Standard attributes
|
|
416
|
+
"href",
|
|
417
|
+
"title",
|
|
418
|
+
"alt",
|
|
419
|
+
"src",
|
|
420
|
+
"class",
|
|
421
|
+
"id",
|
|
422
|
+
"target",
|
|
423
|
+
"rel",
|
|
424
|
+
"type",
|
|
425
|
+
"checked",
|
|
426
|
+
"disabled",
|
|
427
|
+
"loading",
|
|
428
|
+
"width",
|
|
429
|
+
"height",
|
|
430
|
+
"style",
|
|
431
|
+
"role",
|
|
432
|
+
// Iframe attributes
|
|
433
|
+
"frameborder",
|
|
434
|
+
"allowfullscreen",
|
|
435
|
+
"allow",
|
|
436
|
+
"sandbox",
|
|
437
|
+
"scrolling",
|
|
438
|
+
"allowtransparency",
|
|
439
|
+
"name",
|
|
440
|
+
"seamless",
|
|
441
|
+
"srcdoc",
|
|
442
|
+
// Data attributes (for embeds)
|
|
443
|
+
"data-*",
|
|
444
|
+
// SVG attributes
|
|
445
|
+
"viewBox",
|
|
446
|
+
"fill",
|
|
447
|
+
"stroke",
|
|
448
|
+
"stroke-width",
|
|
449
|
+
"stroke-linecap",
|
|
450
|
+
"stroke-linejoin",
|
|
451
|
+
"d",
|
|
452
|
+
"points",
|
|
453
|
+
"x1",
|
|
454
|
+
"y1",
|
|
455
|
+
"x2",
|
|
456
|
+
"y2",
|
|
457
|
+
"cx",
|
|
458
|
+
"cy",
|
|
459
|
+
"r",
|
|
460
|
+
"rx",
|
|
461
|
+
"ry",
|
|
462
|
+
// Media attributes
|
|
463
|
+
"autoplay",
|
|
464
|
+
"controls",
|
|
465
|
+
"loop",
|
|
466
|
+
"muted",
|
|
467
|
+
"preload",
|
|
468
|
+
"poster",
|
|
469
|
+
// Form attributes
|
|
470
|
+
"value",
|
|
471
|
+
"placeholder",
|
|
472
|
+
"required",
|
|
473
|
+
"readonly",
|
|
474
|
+
"maxlength",
|
|
475
|
+
"minlength",
|
|
476
|
+
"max",
|
|
477
|
+
"min",
|
|
478
|
+
"step",
|
|
479
|
+
"pattern",
|
|
480
|
+
"autocomplete",
|
|
481
|
+
"autofocus"
|
|
482
|
+
];
|
|
483
|
+
function escapeHtml(text) {
|
|
484
|
+
const map = {
|
|
485
|
+
"&": "&",
|
|
486
|
+
"<": "<",
|
|
487
|
+
">": ">",
|
|
488
|
+
'"': """,
|
|
489
|
+
"'": "'"
|
|
490
|
+
};
|
|
491
|
+
return text.replace(/[&<>"']/g, (char) => map[char] || char);
|
|
492
|
+
}
|
|
493
|
+
function generateId(text) {
|
|
494
|
+
return text.toLowerCase().replace(/[^\w\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").trim();
|
|
495
|
+
}
|
|
496
|
+
function sanitizeHtml(html) {
|
|
497
|
+
try {
|
|
498
|
+
if (html.includes("codepen.io/") || html.includes("youtube.com/embed/")) {
|
|
499
|
+
return html;
|
|
500
|
+
}
|
|
501
|
+
if (typeof DOMPurify?.sanitize === "function") {
|
|
502
|
+
const sanitized = DOMPurify.sanitize(html, {
|
|
503
|
+
ALLOWED_TAGS,
|
|
504
|
+
ALLOWED_ATTR,
|
|
505
|
+
ALLOW_DATA_ATTR: true,
|
|
506
|
+
ALLOW_UNKNOWN_PROTOCOLS: false,
|
|
507
|
+
SAFE_FOR_TEMPLATES: false,
|
|
508
|
+
WHOLE_DOCUMENT: false,
|
|
509
|
+
RETURN_DOM: false,
|
|
510
|
+
RETURN_DOM_FRAGMENT: false,
|
|
511
|
+
FORCE_BODY: false,
|
|
512
|
+
SANITIZE_DOM: false,
|
|
513
|
+
SANITIZE_NAMED_PROPS: false,
|
|
514
|
+
FORBID_ATTR: ["onload", "onerror", "onclick", "onmouseover", "onmouseout", "onfocus", "onblur"],
|
|
515
|
+
ADD_TAGS: ["iframe", "embed", "object", "param"],
|
|
516
|
+
ADD_ATTR: [
|
|
517
|
+
"allow",
|
|
518
|
+
"allowfullscreen",
|
|
519
|
+
"frameborder",
|
|
520
|
+
"scrolling",
|
|
521
|
+
"allowtransparency",
|
|
522
|
+
"sandbox",
|
|
523
|
+
"loading",
|
|
524
|
+
"style",
|
|
525
|
+
"title",
|
|
526
|
+
"name",
|
|
527
|
+
"seamless",
|
|
528
|
+
"srcdoc"
|
|
529
|
+
],
|
|
530
|
+
ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp|xxx):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i
|
|
531
|
+
});
|
|
532
|
+
if (sanitized.length < html.length * 0.7) {
|
|
533
|
+
return basicSanitize(html);
|
|
534
|
+
}
|
|
535
|
+
return sanitized;
|
|
536
|
+
}
|
|
537
|
+
return basicSanitize(html);
|
|
538
|
+
} catch (error) {
|
|
539
|
+
console.error("Sanitization failed:", error);
|
|
540
|
+
return basicSanitize(html);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
function basicSanitize(html) {
|
|
544
|
+
return html.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "").replace(/on\w+\s*=\s*"[^"]*"/gi, "").replace(/on\w+\s*=\s*'[^']*'/gi, "").replace(/javascript:/gi, "");
|
|
545
|
+
}
|
|
546
|
+
function extractDomain(url) {
|
|
547
|
+
try {
|
|
548
|
+
return new URL(url).hostname;
|
|
549
|
+
} catch {
|
|
550
|
+
return url;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
function parseOptions(options) {
|
|
554
|
+
const parsed = {};
|
|
555
|
+
if (!options) return parsed;
|
|
556
|
+
options.split(",").forEach((option) => {
|
|
557
|
+
const [key, value] = option.split(":").map((s) => s.trim());
|
|
558
|
+
if (key && value) {
|
|
559
|
+
parsed[key] = value;
|
|
560
|
+
}
|
|
561
|
+
});
|
|
562
|
+
return parsed;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// src/renderer.ts
|
|
566
|
+
var MarkdownRenderer = class {
|
|
567
|
+
constructor(config) {
|
|
568
|
+
this.rules = /* @__PURE__ */ new Map();
|
|
569
|
+
this.warnings = [];
|
|
570
|
+
this.config = {
|
|
571
|
+
format: "tailwind",
|
|
572
|
+
sanitize: true,
|
|
573
|
+
allowUnsafeHtml: false,
|
|
574
|
+
debugMode: false,
|
|
575
|
+
...config
|
|
576
|
+
};
|
|
577
|
+
this.setupDefaultRules();
|
|
578
|
+
}
|
|
579
|
+
addRule(rule) {
|
|
580
|
+
this.rules.set(rule.type, rule);
|
|
581
|
+
}
|
|
582
|
+
hasRule(type) {
|
|
583
|
+
return this.rules.has(type);
|
|
584
|
+
}
|
|
585
|
+
render(tokens) {
|
|
586
|
+
this.warnings = [];
|
|
587
|
+
const htmlParts = tokens.map((token) => this.renderToken(token));
|
|
588
|
+
const combinedHtml = htmlParts.join("");
|
|
589
|
+
if (this.config.sanitize && !this.config.allowUnsafeHtml) {
|
|
590
|
+
return sanitizeHtml(combinedHtml);
|
|
591
|
+
}
|
|
592
|
+
return combinedHtml;
|
|
593
|
+
}
|
|
594
|
+
getWarnings() {
|
|
595
|
+
return [...this.warnings];
|
|
596
|
+
}
|
|
597
|
+
getConfig() {
|
|
598
|
+
return { ...this.config };
|
|
599
|
+
}
|
|
600
|
+
updateConfig(config) {
|
|
601
|
+
this.config = { ...this.config, ...config };
|
|
602
|
+
}
|
|
603
|
+
setDebugMode(enabled) {
|
|
604
|
+
this.config.debugMode = enabled;
|
|
605
|
+
}
|
|
606
|
+
clearWarnings() {
|
|
607
|
+
this.warnings = [];
|
|
608
|
+
}
|
|
609
|
+
renderToken(token) {
|
|
610
|
+
const rule = this.rules.get(token.type);
|
|
611
|
+
if (rule) {
|
|
612
|
+
try {
|
|
613
|
+
return rule.render(token);
|
|
614
|
+
} catch (error) {
|
|
615
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
616
|
+
this.warnings.push(`Render error for ${token.type}: ${errorMessage}`);
|
|
617
|
+
return this.createErrorBlock(`Render error for ${token.type}: ${errorMessage}`);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
if (token.type === "text") {
|
|
621
|
+
return escapeHtml(token.content || token.raw || "");
|
|
622
|
+
}
|
|
623
|
+
if (this.config.debugMode) {
|
|
624
|
+
return this.createDebugBlock(token);
|
|
625
|
+
}
|
|
626
|
+
return escapeHtml(token.content || token.raw || "");
|
|
627
|
+
}
|
|
628
|
+
createErrorBlock(message) {
|
|
629
|
+
if (this.config.format === "html") {
|
|
630
|
+
return `<div style="background-color: #fee; border: 1px solid #fcc; color: #c66; padding: 8px; border-radius: 4px; margin: 8px 0; font-size: 14px;">
|
|
631
|
+
<strong>Render Error:</strong> ${escapeHtml(message)}
|
|
632
|
+
</div>`;
|
|
633
|
+
}
|
|
634
|
+
return `<div class="bg-red-100 border border-red-300 text-red-800 p-2 rounded text-sm mb-2">
|
|
635
|
+
<strong>Render Error:</strong> ${escapeHtml(message)}
|
|
636
|
+
</div>`;
|
|
637
|
+
}
|
|
638
|
+
createDebugBlock(token) {
|
|
639
|
+
if (this.config.format === "html") {
|
|
640
|
+
return `<div style="background-color: #fffbf0; border: 1px solid #fed; color: #b8860b; padding: 8px; border-radius: 4px; margin: 8px 0; font-size: 14px;">
|
|
641
|
+
<strong>Unknown token type:</strong> ${escapeHtml(token.type)}<br>
|
|
642
|
+
<strong>Content:</strong> ${escapeHtml(token.content || token.raw || "")}
|
|
643
|
+
</div>`;
|
|
644
|
+
}
|
|
645
|
+
return `<div class="bg-yellow-100 border border-yellow-300 text-yellow-800 p-2 rounded text-sm mb-2">
|
|
646
|
+
<strong>Unknown token type:</strong> ${escapeHtml(token.type)}<br>
|
|
647
|
+
<strong>Content:</strong> ${escapeHtml(token.content || token.raw || "")}
|
|
648
|
+
</div>`;
|
|
649
|
+
}
|
|
650
|
+
setupDefaultRules() {
|
|
651
|
+
this.addRule({
|
|
652
|
+
type: "heading",
|
|
653
|
+
render: (token) => {
|
|
654
|
+
const level = parseInt(token.attributes?.level || "1");
|
|
655
|
+
const text = token.content;
|
|
656
|
+
const id = generateId(text);
|
|
657
|
+
const escapedContent = escapeHtml(text);
|
|
658
|
+
if (this.config.format === "html") {
|
|
659
|
+
return `<h${level} id="${id}">${escapedContent}</h${level}>`;
|
|
660
|
+
}
|
|
661
|
+
let headingClasses = "group relative flex items-center gap-2";
|
|
662
|
+
switch (level) {
|
|
663
|
+
case 1:
|
|
664
|
+
headingClasses += " text-3xl font-bold mt-8 mb-4";
|
|
665
|
+
break;
|
|
666
|
+
case 2:
|
|
667
|
+
headingClasses += " text-2xl font-semibold mt-6 mb-3";
|
|
668
|
+
break;
|
|
669
|
+
case 3:
|
|
670
|
+
headingClasses += " text-xl font-medium mt-5 mb-3";
|
|
671
|
+
break;
|
|
672
|
+
case 4:
|
|
673
|
+
headingClasses += " text-lg font-medium mt-4 mb-2";
|
|
674
|
+
break;
|
|
675
|
+
case 5:
|
|
676
|
+
headingClasses += " text-base font-medium mt-3 mb-2";
|
|
677
|
+
break;
|
|
678
|
+
case 6:
|
|
679
|
+
headingClasses += " text-sm font-medium mt-3 mb-2";
|
|
680
|
+
break;
|
|
681
|
+
}
|
|
682
|
+
return `<h${level} id="${id}" class="${headingClasses}">
|
|
683
|
+
${escapedContent}
|
|
684
|
+
<a href="#${id}" class="opacity-0 group-hover:opacity-100 text-muted-foreground transition-opacity">
|
|
17
685
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
|
18
686
|
<path d="M7.5 4H5.75A3.75 3.75 0 002 7.75v.5a3.75 3.75 0 003.75 3.75h1.5m-1.5-4h3m1.5-4h1.75A3.75 3.75 0 0114 7.75v.5a3.75 3.75 0 01-3.75 3.75H8.5"/>
|
|
19
687
|
</svg>
|
|
20
688
|
</a>
|
|
21
|
-
</h${
|
|
22
|
-
|
|
689
|
+
</h${level}>`;
|
|
690
|
+
}
|
|
691
|
+
});
|
|
692
|
+
this.addRule({
|
|
693
|
+
type: "bold",
|
|
694
|
+
render: (token) => {
|
|
695
|
+
const content = escapeHtml(token.content);
|
|
696
|
+
if (this.config.format === "html") {
|
|
697
|
+
return `<strong>${content}</strong>`;
|
|
698
|
+
}
|
|
699
|
+
return `<strong class="font-bold">${content}</strong>`;
|
|
700
|
+
}
|
|
701
|
+
});
|
|
702
|
+
this.addRule({
|
|
703
|
+
type: "italic",
|
|
704
|
+
render: (token) => {
|
|
705
|
+
const content = escapeHtml(token.content);
|
|
706
|
+
if (this.config.format === "html") {
|
|
707
|
+
return `<em>${content}</em>`;
|
|
708
|
+
}
|
|
709
|
+
return `<em class="italic">${content}</em>`;
|
|
710
|
+
}
|
|
711
|
+
});
|
|
712
|
+
this.addRule({
|
|
713
|
+
type: "code",
|
|
714
|
+
render: (token) => {
|
|
715
|
+
const content = escapeHtml(token.content);
|
|
716
|
+
if (this.config.format === "html") {
|
|
717
|
+
return `<code style="background-color: #f3f4f6; padding: 2px 6px; border-radius: 4px; font-family: monospace; font-size: 0.875rem;">${content}</code>`;
|
|
718
|
+
}
|
|
719
|
+
return `<code class="bg-muted px-1.5 py-0.5 rounded text-sm font-mono">${content}</code>`;
|
|
720
|
+
}
|
|
721
|
+
});
|
|
722
|
+
this.addRule({
|
|
723
|
+
type: "codeblock",
|
|
724
|
+
render: (token) => {
|
|
725
|
+
const language = token.attributes?.language || "text";
|
|
726
|
+
const escapedCode = escapeHtml(token.content);
|
|
727
|
+
if (this.config.format === "html") {
|
|
728
|
+
return `<pre style="background-color: #f3f4f6; padding: 16px; border-radius: 6px; overflow-x: auto; margin: 16px 0;"><code class="language-${escapeHtml(language)}">${escapedCode}</code></pre>`;
|
|
729
|
+
}
|
|
730
|
+
return `<pre class="bg-muted p-4 rounded-md overflow-x-auto my-4"><code class="language-${escapeHtml(language)}">${escapedCode}</code></pre>`;
|
|
731
|
+
}
|
|
732
|
+
});
|
|
733
|
+
this.addRule({
|
|
734
|
+
type: "link",
|
|
735
|
+
render: (token) => {
|
|
736
|
+
const href = token.attributes?.href || "#";
|
|
737
|
+
const escapedHref = escapeHtml(href);
|
|
738
|
+
const escapedText = escapeHtml(token.content);
|
|
739
|
+
if (this.config.format === "html") {
|
|
740
|
+
return `<a href="${escapedHref}" target="_blank" rel="noopener noreferrer">${escapedText}</a>`;
|
|
741
|
+
}
|
|
742
|
+
return `<a href="${escapedHref}" class="text-primary hover:underline inline-flex items-center gap-1" target="_blank" rel="noopener noreferrer">
|
|
743
|
+
${escapedText}
|
|
23
744
|
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-external-link">
|
|
24
745
|
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>
|
|
25
746
|
<polyline points="15 3 21 3 21 9"></polyline>
|
|
26
747
|
<line x1="10" y1="14" x2="21" y2="3"></line>
|
|
27
748
|
</svg>
|
|
28
|
-
</a
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
749
|
+
</a>`;
|
|
750
|
+
}
|
|
751
|
+
});
|
|
752
|
+
this.addRule({
|
|
753
|
+
type: "list-item",
|
|
754
|
+
render: (token) => `<li>${escapeHtml(token.content)}</li>`
|
|
755
|
+
});
|
|
756
|
+
this.addRule({
|
|
757
|
+
type: "blockquote",
|
|
758
|
+
render: (token) => {
|
|
759
|
+
const content = escapeHtml(token.content);
|
|
760
|
+
if (this.config.format === "html") {
|
|
761
|
+
return `<blockquote style="border-left: 2px solid #d1d5db; padding: 8px 0 8px 16px; margin: 16px 0; font-style: italic; color: #6b7280;">${content}</blockquote>`;
|
|
762
|
+
}
|
|
763
|
+
return `<blockquote class="pl-4 py-2 border-l-2 border-border italic text-muted-foreground my-4">${content}</blockquote>`;
|
|
764
|
+
}
|
|
765
|
+
});
|
|
766
|
+
this.addRule({
|
|
767
|
+
type: "text",
|
|
768
|
+
render: (token) => {
|
|
769
|
+
if (!token.content) return "";
|
|
770
|
+
return escapeHtml(token.content);
|
|
771
|
+
}
|
|
772
|
+
});
|
|
773
|
+
this.addRule({
|
|
774
|
+
type: "paragraph",
|
|
775
|
+
render: (token) => {
|
|
776
|
+
if (!token.content) return "";
|
|
777
|
+
const content = token.content.trim();
|
|
778
|
+
if (!content) return "";
|
|
779
|
+
const processedContent = content.includes("<br>") ? content : escapeHtml(content);
|
|
780
|
+
if (this.config.format === "html") {
|
|
781
|
+
return `<p style="line-height: 1.75; margin-bottom: 16px;">${processedContent}</p>`;
|
|
782
|
+
}
|
|
783
|
+
return `<p class="leading-7 mb-4">${processedContent}</p>`;
|
|
784
|
+
}
|
|
785
|
+
});
|
|
786
|
+
this.addRule({
|
|
787
|
+
type: "task-item",
|
|
788
|
+
render: (token) => {
|
|
789
|
+
const isChecked = token.attributes?.checked === "true";
|
|
790
|
+
const escapedContent = escapeHtml(token.content);
|
|
791
|
+
if (this.config.format === "html") {
|
|
792
|
+
return `<div style="display: flex; align-items: center; gap: 8px; margin: 8px 0;">
|
|
793
|
+
<input type="checkbox" ${isChecked ? "checked" : ""} disabled style="margin: 0;" />
|
|
794
|
+
<span${isChecked ? ' style="text-decoration: line-through; color: #6b7280;"' : ""}>${escapedContent}</span>
|
|
795
|
+
</div>`;
|
|
796
|
+
}
|
|
797
|
+
return `<div class="flex items-center gap-2 my-2 task-list-item">
|
|
798
|
+
<input type="checkbox" ${isChecked ? "checked" : ""} disabled
|
|
33
799
|
class="form-checkbox h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary" />
|
|
34
|
-
<span${
|
|
35
|
-
</div
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
800
|
+
<span${isChecked ? ' class="line-through text-muted-foreground"' : ""}>${escapedContent}</span>
|
|
801
|
+
</div>`;
|
|
802
|
+
}
|
|
803
|
+
});
|
|
804
|
+
this.addRule({
|
|
805
|
+
type: "image",
|
|
806
|
+
render: (token) => {
|
|
807
|
+
const src = token.attributes?.src || "";
|
|
808
|
+
const alt = token.attributes?.alt || "";
|
|
809
|
+
const title = token.attributes?.title || "";
|
|
810
|
+
const titleAttr = title ? ` title="${escapeHtml(title)}"` : "";
|
|
811
|
+
if (this.config.format === "html") {
|
|
812
|
+
return `<img src="${escapeHtml(src)}" alt="${escapeHtml(alt)}"${titleAttr} style="max-width: 100%; height: auto; border-radius: 8px; margin: 16px 0;" loading="lazy" />`;
|
|
813
|
+
}
|
|
814
|
+
return `<img src="${escapeHtml(src)}" alt="${escapeHtml(alt)}"${titleAttr} class="max-w-full h-auto rounded-lg my-4" loading="lazy" />`;
|
|
815
|
+
}
|
|
816
|
+
});
|
|
817
|
+
this.addRule({
|
|
818
|
+
type: "hr",
|
|
819
|
+
render: () => {
|
|
820
|
+
if (this.config.format === "html") {
|
|
821
|
+
return '<hr style="margin: 24px 0; border: none; border-top: 1px solid #d1d5db;">';
|
|
822
|
+
}
|
|
823
|
+
return '<hr class="my-6 border-t border-border">';
|
|
824
|
+
}
|
|
825
|
+
});
|
|
826
|
+
this.addRule({
|
|
827
|
+
type: "line-break",
|
|
828
|
+
render: () => "<br>"
|
|
829
|
+
});
|
|
830
|
+
this.addRule({
|
|
831
|
+
type: "paragraph-break",
|
|
832
|
+
render: () => "</p><p>"
|
|
833
|
+
});
|
|
834
|
+
this.addRule({
|
|
835
|
+
type: "soft-break",
|
|
836
|
+
render: (token) => token.content || " "
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
};
|
|
840
|
+
|
|
841
|
+
// src/extensions/alert.ts
|
|
842
|
+
var AlertExtension = {
|
|
843
|
+
name: "alert",
|
|
844
|
+
parseRules: [
|
|
845
|
+
{
|
|
846
|
+
name: "alert",
|
|
847
|
+
pattern: /:::(\w+)(?:\s+(.*?))?\s*\n([\s\S]*?)\n:::/,
|
|
848
|
+
render: (match) => {
|
|
849
|
+
return {
|
|
850
|
+
type: "alert",
|
|
851
|
+
content: match[3]?.trim() || "",
|
|
852
|
+
raw: match[0] || "",
|
|
853
|
+
attributes: {
|
|
854
|
+
type: match[1] || "info",
|
|
855
|
+
title: match[2] || ""
|
|
856
|
+
}
|
|
857
|
+
};
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
],
|
|
861
|
+
renderRules: [
|
|
862
|
+
{
|
|
863
|
+
type: "alert",
|
|
864
|
+
render: (token) => {
|
|
865
|
+
const type = token.attributes?.type || "info";
|
|
866
|
+
const title = token.attributes?.title || "";
|
|
867
|
+
const typeConfig = {
|
|
868
|
+
info: {
|
|
869
|
+
icon: "\u2139\uFE0F",
|
|
870
|
+
classes: "bg-blue-500/10 border-blue-500/30 text-blue-600 border-l-blue-500"
|
|
871
|
+
},
|
|
872
|
+
warning: {
|
|
873
|
+
icon: "\u26A0\uFE0F",
|
|
874
|
+
classes: "bg-amber-500/10 border-amber-500/30 text-amber-600 border-l-amber-500"
|
|
875
|
+
},
|
|
876
|
+
error: {
|
|
877
|
+
icon: "\u274C",
|
|
878
|
+
classes: "bg-red-500/10 border-red-500/30 text-red-600 border-l-red-500"
|
|
879
|
+
},
|
|
880
|
+
success: {
|
|
881
|
+
icon: "\u2705",
|
|
882
|
+
classes: "bg-green-500/10 border-green-500/30 text-green-600 border-l-green-500"
|
|
883
|
+
},
|
|
884
|
+
tip: {
|
|
885
|
+
icon: "\u{1F4A1}",
|
|
886
|
+
classes: "bg-purple-500/10 border-purple-500/30 text-purple-600 border-l-purple-500"
|
|
887
|
+
},
|
|
888
|
+
note: {
|
|
889
|
+
icon: "\u{1F4DD}",
|
|
890
|
+
classes: "bg-gray-500/10 border-gray-500/30 text-gray-600 border-l-gray-500"
|
|
891
|
+
}
|
|
892
|
+
};
|
|
893
|
+
const config = typeConfig[type] || typeConfig.info;
|
|
894
|
+
const baseClasses = "border-l-4 p-4 mb-4 rounded-md transition-colors duration-200";
|
|
895
|
+
const classes = `${baseClasses} ${config?.classes}`;
|
|
896
|
+
const titleHtml = title ? `<div class="font-medium mb-2 flex items-center gap-2">
|
|
897
|
+
<span class="text-lg" role="img" aria-label="${type}">${config?.icon}</span>
|
|
898
|
+
<span>${title}</span>
|
|
899
|
+
</div>` : `<div class="font-medium mb-2 flex items-center gap-2">
|
|
900
|
+
<span class="text-lg" role="img" aria-label="${type}">${config?.icon}</span>
|
|
901
|
+
<span>${type.charAt(0).toUpperCase() + type.slice(1)}</span>
|
|
902
|
+
</div>`;
|
|
903
|
+
return `<div class="${classes}" role="alert" aria-live="polite">
|
|
904
|
+
${titleHtml}
|
|
905
|
+
<div class="leading-relaxed">${token.content}</div>
|
|
906
|
+
</div>`;
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
]
|
|
910
|
+
};
|
|
911
|
+
|
|
912
|
+
// src/extensions/button.ts
|
|
913
|
+
var ButtonExtension = {
|
|
914
|
+
name: "button",
|
|
915
|
+
parseRules: [
|
|
916
|
+
{
|
|
917
|
+
name: "button",
|
|
918
|
+
pattern: /\[button:([^\]]+)\]\(([^)]+)\)(?:\{([^}]+)\})?/,
|
|
919
|
+
render: (match) => {
|
|
920
|
+
const text = match[1] || "";
|
|
921
|
+
const href = match[2] || "";
|
|
922
|
+
const optionsString = match[3] || "";
|
|
923
|
+
const options = optionsString.split(",").map((opt) => opt.trim()).filter(Boolean);
|
|
924
|
+
const styleOptions = ["default", "primary", "secondary", "success", "danger", "outline", "ghost"];
|
|
925
|
+
const style = options.find((opt) => styleOptions.includes(opt)) || "primary";
|
|
926
|
+
const sizeOptions = ["sm", "md", "lg"];
|
|
927
|
+
const size = options.find((opt) => sizeOptions.includes(opt)) || "md";
|
|
928
|
+
const disabled = options.includes("disabled");
|
|
929
|
+
const target = options.includes("self") ? "_self" : "_blank";
|
|
930
|
+
return {
|
|
931
|
+
type: "button",
|
|
932
|
+
content: text,
|
|
933
|
+
raw: match[0] || "",
|
|
934
|
+
attributes: {
|
|
935
|
+
href,
|
|
936
|
+
style,
|
|
937
|
+
size,
|
|
938
|
+
disabled: disabled.toString(),
|
|
939
|
+
target
|
|
940
|
+
}
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
],
|
|
945
|
+
renderRules: [
|
|
946
|
+
{
|
|
947
|
+
type: "button",
|
|
948
|
+
render: (token) => {
|
|
949
|
+
const href = token.attributes?.href || "#";
|
|
950
|
+
const style = token.attributes?.style || "primary";
|
|
951
|
+
const size = token.attributes?.size || "md";
|
|
952
|
+
const disabled = token.attributes?.disabled === "true";
|
|
953
|
+
const target = token.attributes?.target || "_blank";
|
|
954
|
+
const text = token.content;
|
|
955
|
+
const classes = buildButtonClasses(style, size);
|
|
956
|
+
const targetAttr = target === "_blank" ? ' target="_blank" rel="noopener noreferrer"' : target === "_self" ? ' target="_self"' : "";
|
|
957
|
+
const disabledAttr = disabled ? ' aria-disabled="true" tabindex="-1"' : "";
|
|
958
|
+
const externalIcon = target === "_blank" && !disabled ? '<svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"/></svg>' : "";
|
|
959
|
+
return `<a href="${href}" class="${classes}"${targetAttr}${disabledAttr}>
|
|
960
|
+
${text}${externalIcon}
|
|
961
|
+
</a>`;
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
]
|
|
965
|
+
};
|
|
966
|
+
function buildButtonClasses(style, size) {
|
|
967
|
+
const base = [
|
|
968
|
+
"inline-flex",
|
|
969
|
+
"items-center",
|
|
970
|
+
"justify-center",
|
|
971
|
+
"font-medium",
|
|
972
|
+
"rounded-lg",
|
|
973
|
+
"transition-colors",
|
|
974
|
+
"focus:outline-none",
|
|
975
|
+
"focus:ring-2",
|
|
976
|
+
"focus:ring-offset-2",
|
|
977
|
+
"disabled:opacity-50",
|
|
978
|
+
"disabled:cursor-not-allowed"
|
|
979
|
+
];
|
|
980
|
+
const sizes = {
|
|
981
|
+
sm: ["px-3", "py-1.5", "text-sm"],
|
|
982
|
+
md: ["px-4", "py-2", "text-base"],
|
|
983
|
+
lg: ["px-6", "py-3", "text-lg"]
|
|
984
|
+
};
|
|
985
|
+
const styles = {
|
|
986
|
+
default: ["bg-slate-600", "text-white", "hover:bg-slate-700", "focus:ring-slate-500"],
|
|
987
|
+
primary: ["bg-blue-600", "text-white", "hover:bg-blue-700", "focus:ring-blue-500"],
|
|
988
|
+
secondary: ["bg-gray-600", "text-white", "hover:bg-gray-700", "focus:ring-gray-500"],
|
|
989
|
+
success: ["bg-green-600", "text-white", "hover:bg-green-700", "focus:ring-green-500"],
|
|
990
|
+
danger: ["bg-red-600", "text-white", "hover:bg-red-700", "focus:ring-red-500"],
|
|
991
|
+
outline: ["border", "border-blue-600", "text-blue-600", "hover:bg-blue-50", "focus:ring-blue-500"],
|
|
992
|
+
ghost: ["text-gray-700", "hover:bg-gray-100", "focus:ring-gray-500"]
|
|
993
|
+
};
|
|
994
|
+
const allClasses = [
|
|
995
|
+
...base,
|
|
996
|
+
...sizes[size] ?? sizes.md ?? [],
|
|
997
|
+
...styles[style] ?? styles.primary ?? []
|
|
998
|
+
];
|
|
999
|
+
return allClasses.join(" ");
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// src/extensions/embed.ts
|
|
1003
|
+
var EmbedExtension = {
|
|
1004
|
+
name: "embed",
|
|
1005
|
+
parseRules: [
|
|
1006
|
+
{
|
|
1007
|
+
name: "embed",
|
|
1008
|
+
pattern: /\[embed:(\w+)\]\(([^)]+)\)(?:\{([^}]+)\})?/,
|
|
1009
|
+
render: (match) => {
|
|
1010
|
+
return {
|
|
1011
|
+
type: "embed",
|
|
1012
|
+
content: match[2] || "",
|
|
1013
|
+
// URL
|
|
1014
|
+
raw: match[0] || "",
|
|
1015
|
+
attributes: {
|
|
1016
|
+
provider: match[1] || "generic",
|
|
1017
|
+
url: match[2] || "",
|
|
1018
|
+
options: match[3] || ""
|
|
1019
|
+
}
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
],
|
|
1024
|
+
renderRules: [
|
|
1025
|
+
{
|
|
1026
|
+
type: "embed",
|
|
1027
|
+
render: (token) => {
|
|
1028
|
+
const provider = token.attributes?.provider || "generic";
|
|
1029
|
+
const url = token.attributes?.url || "";
|
|
1030
|
+
const options = token.attributes?.options || "";
|
|
1031
|
+
return renderEmbed(provider, url, options);
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
]
|
|
1035
|
+
};
|
|
1036
|
+
function renderEmbed(provider, url, options) {
|
|
1037
|
+
const baseClasses = "rounded-lg border bg-card text-card-foreground shadow-sm mb-6 overflow-hidden";
|
|
1038
|
+
const parsedOptions = parseOptions(options);
|
|
1039
|
+
switch (provider.toLowerCase()) {
|
|
1040
|
+
case "youtube":
|
|
1041
|
+
return renderYouTubeEmbed(url, parsedOptions, baseClasses);
|
|
1042
|
+
case "codepen":
|
|
1043
|
+
return renderCodePenEmbed(url, parsedOptions, baseClasses);
|
|
1044
|
+
case "figma":
|
|
1045
|
+
return renderFigmaEmbed(url, parsedOptions, baseClasses);
|
|
1046
|
+
case "twitter":
|
|
1047
|
+
case "tweet":
|
|
1048
|
+
return renderTwitterEmbed(url, parsedOptions, baseClasses);
|
|
1049
|
+
case "github":
|
|
1050
|
+
return renderGitHubEmbed(url, parsedOptions, baseClasses);
|
|
1051
|
+
case "vimeo":
|
|
1052
|
+
return renderVimeoEmbed(url, parsedOptions, baseClasses);
|
|
1053
|
+
case "spotify":
|
|
1054
|
+
return renderSpotifyEmbed(url, parsedOptions, baseClasses);
|
|
1055
|
+
case "codesandbox":
|
|
1056
|
+
return renderCodeSandboxEmbed(url, parsedOptions, baseClasses);
|
|
1057
|
+
default:
|
|
1058
|
+
return renderGenericEmbed(url, parsedOptions, baseClasses);
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
function renderYouTubeEmbed(url, options, classes) {
|
|
1062
|
+
const videoId = extractYouTubeId(url);
|
|
1063
|
+
if (!videoId) {
|
|
1064
|
+
return createErrorEmbed("Invalid YouTube URL", url, classes);
|
|
1065
|
+
}
|
|
1066
|
+
const params = new URLSearchParams();
|
|
1067
|
+
if (options.autoplay === "1") params.set("autoplay", "1");
|
|
1068
|
+
if (options.mute === "1") params.set("mute", "1");
|
|
1069
|
+
if (options.loop === "1") {
|
|
1070
|
+
params.set("loop", "1");
|
|
1071
|
+
params.set("playlist", videoId);
|
|
1072
|
+
}
|
|
1073
|
+
if (options.controls === "0") params.set("controls", "0");
|
|
1074
|
+
if (options.start) params.set("start", options.start);
|
|
1075
|
+
params.set("rel", "0");
|
|
1076
|
+
params.set("modestbranding", "1");
|
|
1077
|
+
const embedUrl = `https://www.youtube.com/embed/${videoId}?${params.toString()}`;
|
|
1078
|
+
return `
|
|
1079
|
+
<div class="${classes}">
|
|
48
1080
|
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
|
|
49
1081
|
<iframe
|
|
50
1082
|
style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 0;"
|
|
51
|
-
src="${
|
|
1083
|
+
src="${embedUrl}"
|
|
52
1084
|
title="YouTube video player"
|
|
53
1085
|
frameborder="0"
|
|
54
1086
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
|
55
1087
|
allowfullscreen>
|
|
56
1088
|
</iframe>
|
|
57
1089
|
</div>
|
|
58
|
-
</div
|
|
59
|
-
|
|
1090
|
+
</div>`;
|
|
1091
|
+
}
|
|
1092
|
+
function renderCodePenEmbed(url, options, classes) {
|
|
1093
|
+
const match = url.match(/codepen\.io\/([^\/]+)\/(?:pen|embed)\/([^\/\?#]+)/);
|
|
1094
|
+
if (!match) {
|
|
1095
|
+
return createErrorEmbed("Invalid CodePen URL", url, classes);
|
|
1096
|
+
}
|
|
1097
|
+
const [, user, penId] = match;
|
|
1098
|
+
const height = options.height || "400";
|
|
1099
|
+
const theme = options.theme === "light" ? "light" : "dark";
|
|
1100
|
+
const defaultTab = options.tab || "result";
|
|
1101
|
+
const embedParams = new URLSearchParams({
|
|
1102
|
+
"default-tab": defaultTab,
|
|
1103
|
+
"theme-id": theme,
|
|
1104
|
+
"editable": "true"
|
|
1105
|
+
});
|
|
1106
|
+
const embedUrl = `https://codepen.io/${user}/embed/${penId}?${embedParams.toString()}`;
|
|
1107
|
+
return `
|
|
1108
|
+
<div class="${classes}">
|
|
60
1109
|
<iframe
|
|
61
|
-
height="${
|
|
1110
|
+
height="${height}"
|
|
62
1111
|
style="width: 100%; border: 0;"
|
|
63
1112
|
scrolling="no"
|
|
64
|
-
title="CodePen Embed - ${
|
|
65
|
-
src="${
|
|
1113
|
+
title="CodePen Embed - ${penId}"
|
|
1114
|
+
src="${embedUrl}"
|
|
66
1115
|
frameborder="0"
|
|
67
1116
|
loading="lazy"
|
|
68
1117
|
allowtransparency="true"
|
|
69
1118
|
allowfullscreen="true">
|
|
70
1119
|
</iframe>
|
|
71
|
-
</div
|
|
72
|
-
|
|
1120
|
+
</div>`;
|
|
1121
|
+
}
|
|
1122
|
+
function renderVimeoEmbed(url, options, classes) {
|
|
1123
|
+
const videoId = extractVimeoId(url);
|
|
1124
|
+
if (!videoId) {
|
|
1125
|
+
return createErrorEmbed("Invalid Vimeo URL", url, classes);
|
|
1126
|
+
}
|
|
1127
|
+
const params = new URLSearchParams();
|
|
1128
|
+
if (options.autoplay === "1") params.set("autoplay", "1");
|
|
1129
|
+
if (options.mute === "1") params.set("muted", "1");
|
|
1130
|
+
if (options.loop === "1") params.set("loop", "1");
|
|
1131
|
+
const embedUrl = `https://player.vimeo.com/video/${videoId}?${params.toString()}`;
|
|
1132
|
+
return `
|
|
1133
|
+
<div class="${classes}">
|
|
73
1134
|
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
|
|
74
1135
|
<iframe
|
|
75
1136
|
style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 0;"
|
|
76
|
-
src="${
|
|
1137
|
+
src="${embedUrl}"
|
|
77
1138
|
title="Vimeo video player"
|
|
78
1139
|
frameborder="0"
|
|
79
1140
|
allow="autoplay; fullscreen; picture-in-picture"
|
|
80
1141
|
allowfullscreen>
|
|
81
1142
|
</iframe>
|
|
82
1143
|
</div>
|
|
83
|
-
</div
|
|
84
|
-
|
|
1144
|
+
</div>`;
|
|
1145
|
+
}
|
|
1146
|
+
function renderSpotifyEmbed(url, options, classes) {
|
|
1147
|
+
const embedUrl = url.replace("open.spotify.com", "open.spotify.com/embed");
|
|
1148
|
+
const height = options.height || "380";
|
|
1149
|
+
return `
|
|
1150
|
+
<div class="${classes}">
|
|
85
1151
|
<iframe
|
|
86
1152
|
style="border-radius: 12px;"
|
|
87
|
-
src="${
|
|
1153
|
+
src="${embedUrl}"
|
|
88
1154
|
width="100%"
|
|
89
|
-
height="${
|
|
1155
|
+
height="${height}"
|
|
90
1156
|
frameborder="0"
|
|
91
1157
|
allowfullscreen=""
|
|
92
1158
|
allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"
|
|
93
1159
|
loading="lazy">
|
|
94
1160
|
</iframe>
|
|
95
|
-
</div
|
|
96
|
-
|
|
1161
|
+
</div>`;
|
|
1162
|
+
}
|
|
1163
|
+
function renderCodeSandboxEmbed(url, options, classes) {
|
|
1164
|
+
let embedUrl = url;
|
|
1165
|
+
if (url.includes("/s/")) {
|
|
1166
|
+
embedUrl = url.replace("/s/", "/embed/");
|
|
1167
|
+
}
|
|
1168
|
+
const height = options.height || "500";
|
|
1169
|
+
const view = options.view || "preview";
|
|
1170
|
+
if (!embedUrl.includes("?")) {
|
|
1171
|
+
embedUrl += `?view=${view}`;
|
|
1172
|
+
}
|
|
1173
|
+
return `
|
|
1174
|
+
<div class="${classes}">
|
|
97
1175
|
<iframe
|
|
98
|
-
src="${
|
|
99
|
-
style="width: 100%; height: ${
|
|
1176
|
+
src="${embedUrl}"
|
|
1177
|
+
style="width: 100%; height: ${height}px; border: 0; border-radius: 4px; overflow: hidden;"
|
|
100
1178
|
title="CodeSandbox Embed"
|
|
101
1179
|
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
|
|
102
1180
|
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts">
|
|
103
1181
|
</iframe>
|
|
104
|
-
</div
|
|
105
|
-
|
|
1182
|
+
</div>`;
|
|
1183
|
+
}
|
|
1184
|
+
function renderFigmaEmbed(url, options, classes) {
|
|
1185
|
+
const embedUrl = `https://www.figma.com/embed?embed_host=share&url=${encodeURIComponent(url)}`;
|
|
1186
|
+
const height = options.height || "450";
|
|
1187
|
+
return `
|
|
1188
|
+
<div class="${classes}">
|
|
106
1189
|
<iframe
|
|
107
1190
|
style="border: none;"
|
|
108
1191
|
width="100%"
|
|
109
|
-
height="${
|
|
110
|
-
src="${
|
|
1192
|
+
height="${height}"
|
|
1193
|
+
src="${embedUrl}"
|
|
111
1194
|
allowfullscreen>
|
|
112
1195
|
</iframe>
|
|
113
|
-
</div
|
|
114
|
-
|
|
1196
|
+
</div>`;
|
|
1197
|
+
}
|
|
1198
|
+
function renderTwitterEmbed(url, _options, classes) {
|
|
1199
|
+
return `
|
|
1200
|
+
<div class="${classes}">
|
|
115
1201
|
<div class="p-4">
|
|
116
1202
|
<div class="flex items-center gap-3 mb-3">
|
|
117
1203
|
<svg class="w-6 h-6 fill-current text-blue-500" viewBox="0 0 24 24">
|
|
@@ -122,7 +1208,7 @@ var h=class{constructor(e){this.rules=[];this.warnings=[];this.config=e||{}}addR
|
|
|
122
1208
|
<div class="text-sm text-muted-foreground">External Link</div>
|
|
123
1209
|
</div>
|
|
124
1210
|
</div>
|
|
125
|
-
<a href="${
|
|
1211
|
+
<a href="${url}" target="_blank"
|
|
126
1212
|
class="inline-flex items-center gap-2 text-primary hover:text-primary/80 font-medium transition-colors">
|
|
127
1213
|
View on Twitter
|
|
128
1214
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
@@ -130,19 +1216,28 @@ var h=class{constructor(e){this.rules=[];this.warnings=[];this.config=e||{}}addR
|
|
|
130
1216
|
</svg>
|
|
131
1217
|
</a>
|
|
132
1218
|
</div>
|
|
133
|
-
</div
|
|
134
|
-
|
|
1219
|
+
</div>`;
|
|
1220
|
+
}
|
|
1221
|
+
function renderGitHubEmbed(url, _options, classes) {
|
|
1222
|
+
const parts = url.replace("https://github.com/", "").split("/");
|
|
1223
|
+
const owner = parts[0];
|
|
1224
|
+
const repo = parts[1];
|
|
1225
|
+
if (!owner || !repo) {
|
|
1226
|
+
return createErrorEmbed("Invalid GitHub URL", url, classes);
|
|
1227
|
+
}
|
|
1228
|
+
return `
|
|
1229
|
+
<div class="${classes}">
|
|
135
1230
|
<div class="p-4">
|
|
136
1231
|
<div class="flex items-center gap-3 mb-3">
|
|
137
1232
|
<svg class="w-6 h-6 fill-current" viewBox="0 0 24 24">
|
|
138
1233
|
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
|
|
139
1234
|
</svg>
|
|
140
1235
|
<div>
|
|
141
|
-
<div class="font-semibold text-foreground text-lg">${
|
|
1236
|
+
<div class="font-semibold text-foreground text-lg">${owner}/${repo}</div>
|
|
142
1237
|
<div class="text-sm text-muted-foreground">GitHub Repository</div>
|
|
143
1238
|
</div>
|
|
144
1239
|
</div>
|
|
145
|
-
<a href="${
|
|
1240
|
+
<a href="${url}" target="_blank"
|
|
146
1241
|
class="inline-flex items-center gap-2 text-primary hover:text-primary/80 font-medium transition-colors">
|
|
147
1242
|
View on GitHub
|
|
148
1243
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
@@ -150,8 +1245,12 @@ var h=class{constructor(e){this.rules=[];this.warnings=[];this.config=e||{}}addR
|
|
|
150
1245
|
</svg>
|
|
151
1246
|
</a>
|
|
152
1247
|
</div>
|
|
153
|
-
</div
|
|
154
|
-
|
|
1248
|
+
</div>`;
|
|
1249
|
+
}
|
|
1250
|
+
function renderGenericEmbed(url, _options, classes) {
|
|
1251
|
+
const domain = extractDomain(url);
|
|
1252
|
+
return `
|
|
1253
|
+
<div class="${classes}">
|
|
155
1254
|
<div class="p-4">
|
|
156
1255
|
<div class="flex items-center gap-3 mb-3">
|
|
157
1256
|
<div class="w-10 h-10 rounded-lg bg-muted flex items-center justify-center">
|
|
@@ -161,27 +1260,236 @@ var h=class{constructor(e){this.rules=[];this.warnings=[];this.config=e||{}}addR
|
|
|
161
1260
|
</div>
|
|
162
1261
|
<div>
|
|
163
1262
|
<div class="font-semibold text-foreground">External Link</div>
|
|
164
|
-
<div class="text-sm text-muted-foreground">${
|
|
1263
|
+
<div class="text-sm text-muted-foreground">${domain}</div>
|
|
165
1264
|
</div>
|
|
166
1265
|
</div>
|
|
167
|
-
<a href="${
|
|
1266
|
+
<a href="${url}" target="_blank"
|
|
168
1267
|
class="inline-flex items-center gap-2 text-primary hover:text-primary/80 font-medium transition-colors break-all">
|
|
169
|
-
${
|
|
1268
|
+
${url}
|
|
170
1269
|
<svg class="w-4 h-4 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
171
1270
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"/>
|
|
172
1271
|
</svg>
|
|
173
1272
|
</a>
|
|
174
1273
|
</div>
|
|
175
|
-
</div
|
|
176
|
-
|
|
1274
|
+
</div>`;
|
|
1275
|
+
}
|
|
1276
|
+
function createErrorEmbed(error, url, classes) {
|
|
1277
|
+
return `
|
|
1278
|
+
<div class="${classes}">
|
|
177
1279
|
<div class="p-4 text-destructive">
|
|
178
1280
|
<div class="font-medium flex items-center gap-2">
|
|
179
1281
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
180
1282
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
|
181
1283
|
</svg>
|
|
182
|
-
${
|
|
1284
|
+
${error}
|
|
183
1285
|
</div>
|
|
184
|
-
<div class="text-sm text-muted-foreground mt-1 break-all">${
|
|
1286
|
+
<div class="text-sm text-muted-foreground mt-1 break-all">${url}</div>
|
|
185
1287
|
</div>
|
|
186
|
-
</div
|
|
1288
|
+
</div>`;
|
|
1289
|
+
}
|
|
1290
|
+
function extractYouTubeId(url) {
|
|
1291
|
+
const patterns = [
|
|
1292
|
+
/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/|youtube\.com\/shorts\/)([^&\n?#]+)/,
|
|
1293
|
+
/youtube\.com\/watch\?.*v=([^&\n?#]+)/
|
|
1294
|
+
];
|
|
1295
|
+
for (const pattern of patterns) {
|
|
1296
|
+
const match = url.match(pattern);
|
|
1297
|
+
if (match) return match[1] || null;
|
|
1298
|
+
}
|
|
1299
|
+
return null;
|
|
1300
|
+
}
|
|
1301
|
+
function extractVimeoId(url) {
|
|
1302
|
+
const match = url.match(/vimeo\.com\/(?:.*\/)?(\d+)/);
|
|
1303
|
+
return match?.[1] ?? null;
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
// src/engine.ts
|
|
1307
|
+
var ChangerawrMarkdown = class {
|
|
1308
|
+
constructor(config) {
|
|
1309
|
+
this.extensions = /* @__PURE__ */ new Map();
|
|
1310
|
+
this.parser = new MarkdownParser(config?.parser);
|
|
1311
|
+
this.renderer = new MarkdownRenderer(config?.renderer);
|
|
1312
|
+
this.registerBuiltInExtensions();
|
|
1313
|
+
if (config?.extensions) {
|
|
1314
|
+
config.extensions.forEach((extension) => {
|
|
1315
|
+
this.registerExtension(extension);
|
|
1316
|
+
});
|
|
1317
|
+
}
|
|
1318
|
+
this.parser.setupDefaultRulesIfEmpty();
|
|
1319
|
+
}
|
|
1320
|
+
/**
|
|
1321
|
+
* Register a custom extension
|
|
1322
|
+
*/
|
|
1323
|
+
registerExtension(extension) {
|
|
1324
|
+
try {
|
|
1325
|
+
this.extensions.set(extension.name, extension);
|
|
1326
|
+
extension.parseRules.forEach((rule) => {
|
|
1327
|
+
this.parser.addRule(rule);
|
|
1328
|
+
});
|
|
1329
|
+
extension.renderRules.forEach((rule) => {
|
|
1330
|
+
this.renderer.addRule(rule);
|
|
1331
|
+
});
|
|
1332
|
+
return {
|
|
1333
|
+
success: true,
|
|
1334
|
+
extensionName: extension.name
|
|
1335
|
+
};
|
|
1336
|
+
} catch (error) {
|
|
1337
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1338
|
+
return {
|
|
1339
|
+
success: false,
|
|
1340
|
+
extensionName: extension.name,
|
|
1341
|
+
error: errorMessage
|
|
1342
|
+
};
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
/**
|
|
1346
|
+
* Unregister an extension
|
|
1347
|
+
*/
|
|
1348
|
+
unregisterExtension(name) {
|
|
1349
|
+
const extension = this.extensions.get(name);
|
|
1350
|
+
if (!extension) {
|
|
1351
|
+
return false;
|
|
1352
|
+
}
|
|
1353
|
+
try {
|
|
1354
|
+
this.extensions.delete(name);
|
|
1355
|
+
this.rebuildParserAndRenderer();
|
|
1356
|
+
return true;
|
|
1357
|
+
} catch {
|
|
1358
|
+
return false;
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
/**
|
|
1362
|
+
* Parse markdown content into tokens
|
|
1363
|
+
*/
|
|
1364
|
+
parse(markdown2) {
|
|
1365
|
+
return this.parser.parse(markdown2);
|
|
1366
|
+
}
|
|
1367
|
+
/**
|
|
1368
|
+
* Render tokens to HTML
|
|
1369
|
+
*/
|
|
1370
|
+
render(tokens) {
|
|
1371
|
+
return this.renderer.render(tokens);
|
|
1372
|
+
}
|
|
1373
|
+
/**
|
|
1374
|
+
* Parse and render markdown to HTML in one step
|
|
1375
|
+
*/
|
|
1376
|
+
toHtml(markdown2) {
|
|
1377
|
+
const tokens = this.parse(markdown2);
|
|
1378
|
+
return this.render(tokens);
|
|
1379
|
+
}
|
|
1380
|
+
/**
|
|
1381
|
+
* Get list of registered extensions
|
|
1382
|
+
*/
|
|
1383
|
+
getExtensions() {
|
|
1384
|
+
return Array.from(this.extensions.keys());
|
|
1385
|
+
}
|
|
1386
|
+
/**
|
|
1387
|
+
* Check if extension is registered
|
|
1388
|
+
*/
|
|
1389
|
+
hasExtension(name) {
|
|
1390
|
+
return this.extensions.has(name);
|
|
1391
|
+
}
|
|
1392
|
+
/**
|
|
1393
|
+
* Get parser warnings
|
|
1394
|
+
*/
|
|
1395
|
+
getWarnings() {
|
|
1396
|
+
return [...this.parser.getWarnings(), ...this.renderer.getWarnings()];
|
|
1397
|
+
}
|
|
1398
|
+
/**
|
|
1399
|
+
* Get debug information from last render
|
|
1400
|
+
*/
|
|
1401
|
+
getDebugInfo() {
|
|
1402
|
+
return {
|
|
1403
|
+
warnings: this.getWarnings(),
|
|
1404
|
+
parseTime: 0,
|
|
1405
|
+
renderTime: 0,
|
|
1406
|
+
tokenCount: 0,
|
|
1407
|
+
iterationCount: 0
|
|
1408
|
+
};
|
|
1409
|
+
}
|
|
1410
|
+
/**
|
|
1411
|
+
* Get performance metrics for the last operation
|
|
1412
|
+
*/
|
|
1413
|
+
getPerformanceMetrics() {
|
|
1414
|
+
return {
|
|
1415
|
+
parseTime: 0,
|
|
1416
|
+
renderTime: 0,
|
|
1417
|
+
totalTime: 0,
|
|
1418
|
+
tokenCount: 0
|
|
1419
|
+
};
|
|
1420
|
+
}
|
|
1421
|
+
/**
|
|
1422
|
+
* Register built-in extensions
|
|
1423
|
+
*/
|
|
1424
|
+
registerBuiltInExtensions() {
|
|
1425
|
+
this.registerExtension(AlertExtension);
|
|
1426
|
+
this.registerExtension(ButtonExtension);
|
|
1427
|
+
this.registerExtension(EmbedExtension);
|
|
1428
|
+
}
|
|
1429
|
+
/**
|
|
1430
|
+
* Rebuild parser and renderer with current extensions
|
|
1431
|
+
*/
|
|
1432
|
+
rebuildParserAndRenderer() {
|
|
1433
|
+
const parserConfig = this.parser.getConfig();
|
|
1434
|
+
const rendererConfig = this.renderer.getConfig();
|
|
1435
|
+
this.parser = new MarkdownParser(parserConfig);
|
|
1436
|
+
this.renderer = new MarkdownRenderer(rendererConfig);
|
|
1437
|
+
const extensionsToRegister = Array.from(this.extensions.values());
|
|
1438
|
+
this.extensions.clear();
|
|
1439
|
+
extensionsToRegister.forEach((extension) => {
|
|
1440
|
+
this.registerExtension(extension);
|
|
1441
|
+
});
|
|
1442
|
+
this.parser.setupDefaultRulesIfEmpty();
|
|
1443
|
+
}
|
|
1444
|
+
};
|
|
1445
|
+
var markdown = new ChangerawrMarkdown();
|
|
1446
|
+
|
|
1447
|
+
// src/standalone.ts
|
|
1448
|
+
function renderCum(markdown2, config) {
|
|
1449
|
+
const engine = new ChangerawrMarkdown(config);
|
|
1450
|
+
return engine.toHtml(markdown2);
|
|
1451
|
+
}
|
|
1452
|
+
function parseCum(markdown2, config) {
|
|
1453
|
+
const engine = new ChangerawrMarkdown(config);
|
|
1454
|
+
return engine.parse(markdown2);
|
|
1455
|
+
}
|
|
1456
|
+
function createCumEngine(config) {
|
|
1457
|
+
return new ChangerawrMarkdown(config);
|
|
1458
|
+
}
|
|
1459
|
+
function renderCumToHtml(markdown2) {
|
|
1460
|
+
return renderCum(markdown2, {
|
|
1461
|
+
renderer: { format: "html" }
|
|
1462
|
+
});
|
|
1463
|
+
}
|
|
1464
|
+
function renderCumToTailwind(markdown2) {
|
|
1465
|
+
return renderCum(markdown2, {
|
|
1466
|
+
renderer: { format: "tailwind" }
|
|
1467
|
+
});
|
|
1468
|
+
}
|
|
1469
|
+
function renderCumToJson(markdown2) {
|
|
1470
|
+
return parseCum(markdown2);
|
|
1471
|
+
}
|
|
1472
|
+
var standalone_default = {
|
|
1473
|
+
renderCum,
|
|
1474
|
+
parseCum,
|
|
1475
|
+
createCumEngine,
|
|
1476
|
+
renderCumToHtml,
|
|
1477
|
+
renderCumToTailwind,
|
|
1478
|
+
renderCumToJson,
|
|
1479
|
+
// Legacy aliases
|
|
1480
|
+
render: renderCum,
|
|
1481
|
+
parse: parseCum,
|
|
1482
|
+
// Include the main class
|
|
1483
|
+
ChangerawrMarkdown
|
|
1484
|
+
};
|
|
1485
|
+
export {
|
|
1486
|
+
ChangerawrMarkdown,
|
|
1487
|
+
createCumEngine,
|
|
1488
|
+
standalone_default as default,
|
|
1489
|
+
parseCum,
|
|
1490
|
+
renderCum,
|
|
1491
|
+
renderCumToHtml,
|
|
1492
|
+
renderCumToJson,
|
|
1493
|
+
renderCumToTailwind
|
|
1494
|
+
};
|
|
187
1495
|
//# sourceMappingURL=standalone.mjs.map
|