@changerawr/markdown 1.1.10 → 1.2.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/README.md +22 -13
- package/dist/astro/MarkdownRenderer.astro +31 -0
- package/dist/astro/index.d.mts +213 -0
- package/dist/astro/index.d.ts +213 -0
- package/dist/astro/index.js +2177 -0
- package/dist/astro/index.js.map +1 -0
- package/dist/astro/index.mjs +2135 -0
- package/dist/astro/index.mjs.map +1 -0
- package/dist/css/index.css +395 -33
- package/dist/index.d.mts +32 -1
- package/dist/index.d.ts +32 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/react/index.d.mts +133 -2
- package/dist/react/index.d.ts +133 -2
- package/dist/react/index.js +120 -15
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +116 -13
- package/dist/react/index.mjs.map +1 -1
- package/dist/tailwind/index.d.mts +16 -8
- package/dist/tailwind/index.d.ts +16 -8
- package/dist/tailwind/index.js +41 -35
- package/dist/tailwind/index.js.map +1 -1
- package/dist/tailwind/index.mjs +40 -35
- package/dist/tailwind/index.mjs.map +1 -1
- package/package.json +9 -3
|
@@ -0,0 +1,2177 @@
|
|
|
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/astro/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
ChangerawrMarkdown: () => ChangerawrMarkdown,
|
|
34
|
+
renderMarkdownForAstro: () => renderMarkdownForAstro,
|
|
35
|
+
renderToHTML: () => renderToHTML,
|
|
36
|
+
renderToHTMLWithConfig: () => renderToHTMLWithConfig,
|
|
37
|
+
renderToTailwind: () => renderToTailwind,
|
|
38
|
+
renderToTailwindWithConfig: () => renderToTailwindWithConfig
|
|
39
|
+
});
|
|
40
|
+
module.exports = __toCommonJS(index_exports);
|
|
41
|
+
|
|
42
|
+
// src/parser.ts
|
|
43
|
+
var MarkdownParser = class _MarkdownParser {
|
|
44
|
+
// Cache compiled regexes
|
|
45
|
+
constructor(config) {
|
|
46
|
+
this.rules = [];
|
|
47
|
+
this.warnings = [];
|
|
48
|
+
this.compiledPatterns = /* @__PURE__ */ new Map();
|
|
49
|
+
this.config = {
|
|
50
|
+
debugMode: false,
|
|
51
|
+
maxIterations: 1e4,
|
|
52
|
+
validateMarkdown: false,
|
|
53
|
+
...config
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
addRule(rule) {
|
|
57
|
+
this.rules.push(rule);
|
|
58
|
+
this.compiledPatterns.set(
|
|
59
|
+
rule,
|
|
60
|
+
new RegExp(rule.pattern.source, rule.pattern.flags.replace("g", ""))
|
|
61
|
+
);
|
|
62
|
+
this.rules.sort((a, b) => {
|
|
63
|
+
const aFeatureExtension = ["alert", "button", "embed"].includes(a.name);
|
|
64
|
+
const bFeatureExtension = ["alert", "button", "embed"].includes(b.name);
|
|
65
|
+
if (aFeatureExtension && !bFeatureExtension) return -1;
|
|
66
|
+
if (!aFeatureExtension && bFeatureExtension) return 1;
|
|
67
|
+
const aCoreExtension = ["text", "heading", "bold", "italic", "code", "codeblock", "link", "image", "list", "task-list", "blockquote", "hr", "paragraph", "line-break"].includes(a.name);
|
|
68
|
+
const bCoreExtension = ["text", "heading", "bold", "italic", "code", "codeblock", "link", "image", "list", "task-list", "blockquote", "hr", "paragraph", "line-break"].includes(b.name);
|
|
69
|
+
if (aCoreExtension && !bCoreExtension) return -1;
|
|
70
|
+
if (!aCoreExtension && bCoreExtension) return 1;
|
|
71
|
+
if (a.name === "image" && b.name === "link") return -1;
|
|
72
|
+
if (a.name === "link" && b.name === "image") return 1;
|
|
73
|
+
if (a.name === "task-item" && b.name === "list-item") return -1;
|
|
74
|
+
if (a.name === "list-item" && b.name === "task-item") return 1;
|
|
75
|
+
if (a.name === "codeblock" && b.name === "code") return -1;
|
|
76
|
+
if (a.name === "code" && b.name === "codeblock") return 1;
|
|
77
|
+
if (a.name === "bold" && b.name === "italic") return -1;
|
|
78
|
+
if (a.name === "italic" && b.name === "bold") return 1;
|
|
79
|
+
return a.name.localeCompare(b.name);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
hasRule(name) {
|
|
83
|
+
return this.rules.some((rule) => rule.name === name);
|
|
84
|
+
}
|
|
85
|
+
parse(markdown2) {
|
|
86
|
+
this.warnings = [];
|
|
87
|
+
if (!markdown2.trim()) {
|
|
88
|
+
return [];
|
|
89
|
+
}
|
|
90
|
+
if (this.rules.length === 0) {
|
|
91
|
+
this.warnings.push("No parse rules registered - consider using CoreExtensions");
|
|
92
|
+
return [{
|
|
93
|
+
type: "text",
|
|
94
|
+
content: markdown2,
|
|
95
|
+
raw: markdown2
|
|
96
|
+
}];
|
|
97
|
+
}
|
|
98
|
+
const processedMarkdown = this.preprocessMarkdown(markdown2);
|
|
99
|
+
const tokens = [];
|
|
100
|
+
let remaining = processedMarkdown;
|
|
101
|
+
let iterationCount = 0;
|
|
102
|
+
const maxIterations = this.config.maxIterations || 1e4;
|
|
103
|
+
while (remaining.length > 0 && iterationCount < maxIterations) {
|
|
104
|
+
iterationCount++;
|
|
105
|
+
let matched = false;
|
|
106
|
+
let bestMatch = null;
|
|
107
|
+
let nextBestMatchIndex = null;
|
|
108
|
+
for (const rule of this.rules) {
|
|
109
|
+
try {
|
|
110
|
+
const pattern = this.compiledPatterns.get(rule);
|
|
111
|
+
const match = remaining.match(pattern);
|
|
112
|
+
if (match && match.index !== void 0) {
|
|
113
|
+
if (match.index === 0) {
|
|
114
|
+
bestMatch = { rule, match, priority: 1e3 };
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
if (nextBestMatchIndex === null || match.index < nextBestMatchIndex) {
|
|
118
|
+
nextBestMatchIndex = match.index;
|
|
119
|
+
}
|
|
120
|
+
const priority = 1e3 - match.index;
|
|
121
|
+
if (!bestMatch || priority > bestMatch.priority || priority === bestMatch.priority && match.index < (bestMatch.match.index || 0)) {
|
|
122
|
+
bestMatch = { rule, match, priority };
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
} catch (error) {
|
|
126
|
+
if (this.config.debugMode) {
|
|
127
|
+
console.warn(`Error in rule "${rule.name}":`, error);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (bestMatch && bestMatch.match.index !== void 0) {
|
|
132
|
+
const { rule, match } = bestMatch;
|
|
133
|
+
const matchIndex = match.index;
|
|
134
|
+
if (matchIndex > 0) {
|
|
135
|
+
const textBefore = remaining.slice(0, matchIndex);
|
|
136
|
+
tokens.push({
|
|
137
|
+
type: "text",
|
|
138
|
+
content: textBefore,
|
|
139
|
+
raw: textBefore
|
|
140
|
+
});
|
|
141
|
+
remaining = remaining.slice(matchIndex);
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
const token = rule.render(match);
|
|
146
|
+
tokens.push({
|
|
147
|
+
...token,
|
|
148
|
+
raw: match[0] || ""
|
|
149
|
+
});
|
|
150
|
+
remaining = remaining.slice(match[0]?.length || 0);
|
|
151
|
+
matched = true;
|
|
152
|
+
} catch (error) {
|
|
153
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
154
|
+
this.warnings.push(`Failed to render ${rule.name}: ${errorMessage}`);
|
|
155
|
+
const char = remaining[0];
|
|
156
|
+
if (char) {
|
|
157
|
+
tokens.push({
|
|
158
|
+
type: "text",
|
|
159
|
+
content: char,
|
|
160
|
+
raw: char
|
|
161
|
+
});
|
|
162
|
+
remaining = remaining.slice(1);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (!matched) {
|
|
167
|
+
const chunkSize = nextBestMatchIndex !== null ? nextBestMatchIndex : Math.min(remaining.length, 1e3);
|
|
168
|
+
const textChunk = remaining.slice(0, chunkSize);
|
|
169
|
+
tokens.push({
|
|
170
|
+
type: "text",
|
|
171
|
+
content: textChunk,
|
|
172
|
+
raw: textChunk
|
|
173
|
+
});
|
|
174
|
+
remaining = remaining.slice(chunkSize);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (iterationCount >= maxIterations) {
|
|
178
|
+
this.warnings.push("Parser hit maximum iterations - possible infinite loop detected");
|
|
179
|
+
}
|
|
180
|
+
const processedTokens = this.postProcessTokens(tokens);
|
|
181
|
+
return processedTokens;
|
|
182
|
+
}
|
|
183
|
+
getWarnings() {
|
|
184
|
+
return [...this.warnings];
|
|
185
|
+
}
|
|
186
|
+
getConfig() {
|
|
187
|
+
return { ...this.config };
|
|
188
|
+
}
|
|
189
|
+
updateConfig(config) {
|
|
190
|
+
this.config = { ...this.config, ...config };
|
|
191
|
+
}
|
|
192
|
+
setDebugMode(enabled) {
|
|
193
|
+
this.config.debugMode = enabled;
|
|
194
|
+
}
|
|
195
|
+
clearWarnings() {
|
|
196
|
+
this.warnings = [];
|
|
197
|
+
}
|
|
198
|
+
preprocessMarkdown(markdown2) {
|
|
199
|
+
if (this.config.validateMarkdown) {
|
|
200
|
+
this.validateMarkdown(markdown2);
|
|
201
|
+
}
|
|
202
|
+
return markdown2.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
203
|
+
}
|
|
204
|
+
validateMarkdown(markdown2) {
|
|
205
|
+
const boldMatches = markdown2.match(/\*\*/g);
|
|
206
|
+
if (boldMatches && boldMatches.length % 2 !== 0) {
|
|
207
|
+
this.warnings.push("Unclosed bold markers (**) detected - some bold formatting may not work");
|
|
208
|
+
}
|
|
209
|
+
const italicMatches = markdown2.match(/(?<!\*)\*(?!\*)/g);
|
|
210
|
+
if (italicMatches && italicMatches.length % 2 !== 0) {
|
|
211
|
+
this.warnings.push("Unclosed italic markers (*) detected - some italic formatting may not work");
|
|
212
|
+
}
|
|
213
|
+
const codeBlockMatches = markdown2.match(/```/g);
|
|
214
|
+
if (codeBlockMatches && codeBlockMatches.length % 2 !== 0) {
|
|
215
|
+
this.warnings.push("Unclosed code blocks (```) detected - some code formatting may not work");
|
|
216
|
+
}
|
|
217
|
+
const inlineCodeMatches = markdown2.match(/`/g);
|
|
218
|
+
if (inlineCodeMatches && inlineCodeMatches.length % 2 !== 0) {
|
|
219
|
+
this.warnings.push("Unclosed inline code markers (`) detected - some code formatting may not work");
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
postProcessTokens(tokens) {
|
|
223
|
+
const processed = [];
|
|
224
|
+
let i = 0;
|
|
225
|
+
while (i < tokens.length) {
|
|
226
|
+
const token = tokens[i];
|
|
227
|
+
if (token && token.type === "text") {
|
|
228
|
+
let textContent = token.content || token.raw || "";
|
|
229
|
+
let j = i + 1;
|
|
230
|
+
while (j < tokens.length && tokens[j] && tokens[j].type === "text") {
|
|
231
|
+
const nextToken = tokens[j];
|
|
232
|
+
textContent += nextToken.content || nextToken.raw || "";
|
|
233
|
+
j++;
|
|
234
|
+
}
|
|
235
|
+
if (textContent.trim().length > 0 || textContent.includes("\n")) {
|
|
236
|
+
processed.push({
|
|
237
|
+
type: "text",
|
|
238
|
+
content: textContent,
|
|
239
|
+
raw: textContent
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
i = j;
|
|
243
|
+
} else if (token) {
|
|
244
|
+
const processedToken = this.recursivelyParseBlockContent(token);
|
|
245
|
+
processed.push(processedToken);
|
|
246
|
+
i++;
|
|
247
|
+
} else {
|
|
248
|
+
i++;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return processed;
|
|
252
|
+
}
|
|
253
|
+
recursivelyParseBlockContent(token) {
|
|
254
|
+
const blockTypes = ["alert", "blockquote", "list-item", "ordered-list-item", "task-item"];
|
|
255
|
+
if (blockTypes.includes(token.type) && token.content && token.content.trim()) {
|
|
256
|
+
let children;
|
|
257
|
+
if ((token.type === "list-item" || token.type === "ordered-list-item" || token.type === "task-item") && this.rules.some((r) => r.name === "unordered-list-item" || r.name === "ordered-list-item" || r.name === "task-item")) {
|
|
258
|
+
const parserWithoutListRule = new _MarkdownParser(this.config);
|
|
259
|
+
this.rules.forEach((rule) => {
|
|
260
|
+
if (rule.name !== "unordered-list-item" && rule.name !== "ordered-list-item" && rule.name !== "task-item") {
|
|
261
|
+
parserWithoutListRule.addRule(rule);
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
children = parserWithoutListRule.parse(token.content);
|
|
265
|
+
} else {
|
|
266
|
+
children = this.parse(token.content);
|
|
267
|
+
}
|
|
268
|
+
return {
|
|
269
|
+
...token,
|
|
270
|
+
children
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
return token;
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
// src/utils.ts
|
|
278
|
+
var DOMPurify = {
|
|
279
|
+
sanitize: (html) => html
|
|
280
|
+
};
|
|
281
|
+
if (typeof window !== "undefined") {
|
|
282
|
+
import("dompurify").then((module2) => {
|
|
283
|
+
DOMPurify = module2.default;
|
|
284
|
+
}).catch((err) => {
|
|
285
|
+
console.error("Failed to load DOMPurify", err);
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
var ALLOWED_TAGS = [
|
|
289
|
+
// Standard HTML
|
|
290
|
+
"h1",
|
|
291
|
+
"h2",
|
|
292
|
+
"h3",
|
|
293
|
+
"h4",
|
|
294
|
+
"h5",
|
|
295
|
+
"h6",
|
|
296
|
+
"p",
|
|
297
|
+
"br",
|
|
298
|
+
"strong",
|
|
299
|
+
"em",
|
|
300
|
+
"del",
|
|
301
|
+
"ins",
|
|
302
|
+
"a",
|
|
303
|
+
"img",
|
|
304
|
+
"ul",
|
|
305
|
+
"ol",
|
|
306
|
+
"li",
|
|
307
|
+
"blockquote",
|
|
308
|
+
"pre",
|
|
309
|
+
"code",
|
|
310
|
+
"table",
|
|
311
|
+
"thead",
|
|
312
|
+
"tbody",
|
|
313
|
+
"tr",
|
|
314
|
+
"th",
|
|
315
|
+
"td",
|
|
316
|
+
"div",
|
|
317
|
+
"span",
|
|
318
|
+
"sup",
|
|
319
|
+
"sub",
|
|
320
|
+
"hr",
|
|
321
|
+
"input",
|
|
322
|
+
// Embeds
|
|
323
|
+
"iframe",
|
|
324
|
+
"embed",
|
|
325
|
+
"object",
|
|
326
|
+
"param",
|
|
327
|
+
"video",
|
|
328
|
+
"audio",
|
|
329
|
+
"source",
|
|
330
|
+
// SVG
|
|
331
|
+
"svg",
|
|
332
|
+
"path",
|
|
333
|
+
"polyline",
|
|
334
|
+
"line",
|
|
335
|
+
"circle",
|
|
336
|
+
"rect",
|
|
337
|
+
"g",
|
|
338
|
+
"defs",
|
|
339
|
+
"use",
|
|
340
|
+
// Form elements
|
|
341
|
+
"form",
|
|
342
|
+
"fieldset",
|
|
343
|
+
"legend",
|
|
344
|
+
"label",
|
|
345
|
+
"select",
|
|
346
|
+
"option",
|
|
347
|
+
"textarea",
|
|
348
|
+
"button"
|
|
349
|
+
];
|
|
350
|
+
var ALLOWED_ATTR = [
|
|
351
|
+
// Standard attributes
|
|
352
|
+
"href",
|
|
353
|
+
"title",
|
|
354
|
+
"alt",
|
|
355
|
+
"src",
|
|
356
|
+
"class",
|
|
357
|
+
"id",
|
|
358
|
+
"target",
|
|
359
|
+
"rel",
|
|
360
|
+
"type",
|
|
361
|
+
"checked",
|
|
362
|
+
"disabled",
|
|
363
|
+
"loading",
|
|
364
|
+
"width",
|
|
365
|
+
"height",
|
|
366
|
+
"style",
|
|
367
|
+
"role",
|
|
368
|
+
// Iframe attributes
|
|
369
|
+
"frameborder",
|
|
370
|
+
"allowfullscreen",
|
|
371
|
+
"allow",
|
|
372
|
+
"sandbox",
|
|
373
|
+
"scrolling",
|
|
374
|
+
"allowtransparency",
|
|
375
|
+
"name",
|
|
376
|
+
"seamless",
|
|
377
|
+
"srcdoc",
|
|
378
|
+
// Data attributes (for embeds)
|
|
379
|
+
"data-*",
|
|
380
|
+
// SVG attributes
|
|
381
|
+
"viewBox",
|
|
382
|
+
"fill",
|
|
383
|
+
"stroke",
|
|
384
|
+
"stroke-width",
|
|
385
|
+
"stroke-linecap",
|
|
386
|
+
"stroke-linejoin",
|
|
387
|
+
"d",
|
|
388
|
+
"points",
|
|
389
|
+
"x1",
|
|
390
|
+
"y1",
|
|
391
|
+
"x2",
|
|
392
|
+
"y2",
|
|
393
|
+
"cx",
|
|
394
|
+
"cy",
|
|
395
|
+
"r",
|
|
396
|
+
"rx",
|
|
397
|
+
"ry",
|
|
398
|
+
// Media attributes
|
|
399
|
+
"autoplay",
|
|
400
|
+
"controls",
|
|
401
|
+
"loop",
|
|
402
|
+
"muted",
|
|
403
|
+
"preload",
|
|
404
|
+
"poster",
|
|
405
|
+
// Form attributes
|
|
406
|
+
"value",
|
|
407
|
+
"placeholder",
|
|
408
|
+
"required",
|
|
409
|
+
"readonly",
|
|
410
|
+
"maxlength",
|
|
411
|
+
"minlength",
|
|
412
|
+
"max",
|
|
413
|
+
"min",
|
|
414
|
+
"step",
|
|
415
|
+
"pattern",
|
|
416
|
+
"autocomplete",
|
|
417
|
+
"autofocus"
|
|
418
|
+
];
|
|
419
|
+
function escapeHtml(text) {
|
|
420
|
+
const map = {
|
|
421
|
+
"&": "&",
|
|
422
|
+
"<": "<",
|
|
423
|
+
">": ">",
|
|
424
|
+
'"': """,
|
|
425
|
+
"'": "'"
|
|
426
|
+
};
|
|
427
|
+
return text.replace(/[&<>"']/g, (char) => map[char] || char);
|
|
428
|
+
}
|
|
429
|
+
function generateId(text) {
|
|
430
|
+
return text.toLowerCase().replace(/[^\w\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").trim();
|
|
431
|
+
}
|
|
432
|
+
function sanitizeHtml(html) {
|
|
433
|
+
try {
|
|
434
|
+
if (html.includes("codepen.io/") || html.includes("youtube.com/embed/")) {
|
|
435
|
+
return html;
|
|
436
|
+
}
|
|
437
|
+
if (typeof DOMPurify?.sanitize === "function") {
|
|
438
|
+
const sanitized = DOMPurify.sanitize(html, {
|
|
439
|
+
ALLOWED_TAGS,
|
|
440
|
+
ALLOWED_ATTR,
|
|
441
|
+
ALLOW_DATA_ATTR: true,
|
|
442
|
+
ALLOW_UNKNOWN_PROTOCOLS: false,
|
|
443
|
+
SAFE_FOR_TEMPLATES: false,
|
|
444
|
+
WHOLE_DOCUMENT: false,
|
|
445
|
+
RETURN_DOM: false,
|
|
446
|
+
RETURN_DOM_FRAGMENT: false,
|
|
447
|
+
FORCE_BODY: false,
|
|
448
|
+
SANITIZE_DOM: false,
|
|
449
|
+
SANITIZE_NAMED_PROPS: false,
|
|
450
|
+
FORBID_ATTR: ["onload", "onerror", "onclick", "onmouseover", "onmouseout", "onfocus", "onblur"],
|
|
451
|
+
ADD_TAGS: ["iframe", "embed", "object", "param"],
|
|
452
|
+
ADD_ATTR: [
|
|
453
|
+
"allow",
|
|
454
|
+
"allowfullscreen",
|
|
455
|
+
"frameborder",
|
|
456
|
+
"scrolling",
|
|
457
|
+
"allowtransparency",
|
|
458
|
+
"sandbox",
|
|
459
|
+
"loading",
|
|
460
|
+
"style",
|
|
461
|
+
"title",
|
|
462
|
+
"name",
|
|
463
|
+
"seamless",
|
|
464
|
+
"srcdoc"
|
|
465
|
+
],
|
|
466
|
+
ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp|xxx):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i
|
|
467
|
+
});
|
|
468
|
+
if (sanitized.length < html.length * 0.7) {
|
|
469
|
+
return basicSanitize(html);
|
|
470
|
+
}
|
|
471
|
+
return sanitized;
|
|
472
|
+
}
|
|
473
|
+
return basicSanitize(html);
|
|
474
|
+
} catch (error) {
|
|
475
|
+
console.error("Sanitization failed:", error);
|
|
476
|
+
return basicSanitize(html);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
function basicSanitize(html) {
|
|
480
|
+
return html.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "").replace(/on\w+\s*=\s*"[^"]*"/gi, "").replace(/on\w+\s*=\s*'[^']*'/gi, "").replace(/javascript:/gi, "");
|
|
481
|
+
}
|
|
482
|
+
function extractDomain(url) {
|
|
483
|
+
try {
|
|
484
|
+
return new URL(url).hostname;
|
|
485
|
+
} catch {
|
|
486
|
+
return url;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
function parseOptions(options) {
|
|
490
|
+
const parsed = {};
|
|
491
|
+
if (!options) return parsed;
|
|
492
|
+
options.split(",").forEach((option) => {
|
|
493
|
+
const [key, value] = option.split(":").map((s) => s.trim());
|
|
494
|
+
if (key && value) {
|
|
495
|
+
parsed[key] = value;
|
|
496
|
+
}
|
|
497
|
+
});
|
|
498
|
+
return parsed;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// src/renderer.ts
|
|
502
|
+
var MarkdownRenderer = class {
|
|
503
|
+
constructor(config) {
|
|
504
|
+
this.rules = /* @__PURE__ */ new Map();
|
|
505
|
+
this.warnings = [];
|
|
506
|
+
this.config = {
|
|
507
|
+
format: "tailwind",
|
|
508
|
+
sanitize: true,
|
|
509
|
+
allowUnsafeHtml: false,
|
|
510
|
+
debugMode: false,
|
|
511
|
+
...config
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
addRule(rule) {
|
|
515
|
+
this.rules.set(rule.type, rule);
|
|
516
|
+
}
|
|
517
|
+
hasRule(type) {
|
|
518
|
+
return this.rules.has(type);
|
|
519
|
+
}
|
|
520
|
+
render(tokens) {
|
|
521
|
+
this.warnings = [];
|
|
522
|
+
const tokensWithFormat = tokens.map((token) => ({
|
|
523
|
+
...token,
|
|
524
|
+
attributes: {
|
|
525
|
+
...token.attributes,
|
|
526
|
+
format: this.config.format
|
|
527
|
+
}
|
|
528
|
+
}));
|
|
529
|
+
const groupedTokens = this.groupListItems(tokensWithFormat);
|
|
530
|
+
const htmlParts = groupedTokens.map((token) => this.renderToken(token));
|
|
531
|
+
const combinedHtml = htmlParts.join("");
|
|
532
|
+
if (this.config.sanitize && !this.config.allowUnsafeHtml) {
|
|
533
|
+
return sanitizeHtml(combinedHtml);
|
|
534
|
+
}
|
|
535
|
+
return combinedHtml;
|
|
536
|
+
}
|
|
537
|
+
groupListItems(tokens) {
|
|
538
|
+
const result = [];
|
|
539
|
+
let i = 0;
|
|
540
|
+
while (i < tokens.length) {
|
|
541
|
+
const token = tokens[i];
|
|
542
|
+
const isListItem = token?.type === "list-item" || token?.type === "ordered-list-item" || token?.type === "task-item";
|
|
543
|
+
if (isListItem) {
|
|
544
|
+
const listItems = [];
|
|
545
|
+
const firstItemType = token.type;
|
|
546
|
+
const isOrdered = firstItemType === "ordered-list-item";
|
|
547
|
+
while (i < tokens.length) {
|
|
548
|
+
const item = tokens[i];
|
|
549
|
+
if (!item) break;
|
|
550
|
+
const itemType = item.type;
|
|
551
|
+
const isSameListType = isOrdered && itemType === "ordered-list-item" || !isOrdered && (itemType === "list-item" || itemType === "task-item");
|
|
552
|
+
if (isSameListType) {
|
|
553
|
+
listItems.push(item);
|
|
554
|
+
i++;
|
|
555
|
+
} else {
|
|
556
|
+
break;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
const wrappedList = {
|
|
560
|
+
type: isOrdered ? "ol" : "ul",
|
|
561
|
+
content: "",
|
|
562
|
+
raw: "",
|
|
563
|
+
children: listItems,
|
|
564
|
+
attributes: { format: this.config.format }
|
|
565
|
+
};
|
|
566
|
+
wrappedList._isWrapped = true;
|
|
567
|
+
result.push(wrappedList);
|
|
568
|
+
} else {
|
|
569
|
+
result.push(token);
|
|
570
|
+
i++;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
return result;
|
|
574
|
+
}
|
|
575
|
+
renderToken(token) {
|
|
576
|
+
const rule = this.rules.get(token.type);
|
|
577
|
+
if (rule) {
|
|
578
|
+
try {
|
|
579
|
+
let tokenToRender = token;
|
|
580
|
+
if (token.children && token.children.length > 0) {
|
|
581
|
+
const renderedChildren = token.type === "ul" || token.type === "ol" ? token.children.map((child) => this.renderToken(child)).join("") : this.render(token.children);
|
|
582
|
+
tokenToRender = {
|
|
583
|
+
...token,
|
|
584
|
+
attributes: {
|
|
585
|
+
...token.attributes,
|
|
586
|
+
renderedChildren
|
|
587
|
+
// Extensions can use this instead of re-parsing
|
|
588
|
+
}
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
return rule.render(tokenToRender);
|
|
592
|
+
} catch (error) {
|
|
593
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
594
|
+
this.warnings.push(`Render error for ${token.type}: ${errorMessage}`);
|
|
595
|
+
return this.createErrorBlock(`Render error for ${token.type}: ${errorMessage}`);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
if (this.config.debugMode) {
|
|
599
|
+
return this.createDebugBlock(token);
|
|
600
|
+
}
|
|
601
|
+
return escapeHtml(token.content || token.raw || "");
|
|
602
|
+
}
|
|
603
|
+
createErrorBlock(message) {
|
|
604
|
+
if (this.config.format === "html") {
|
|
605
|
+
return `<div style="background-color: #fee; border: 1px solid #fcc; color: #c66; padding: 8px; border-radius: 4px; margin: 8px 0; font-size: 14px;">
|
|
606
|
+
<strong>Render Error:</strong> ${escapeHtml(message)}
|
|
607
|
+
</div>`;
|
|
608
|
+
}
|
|
609
|
+
return `<div class="bg-red-100 border border-red-300 text-red-800 p-2 rounded text-sm mb-2">
|
|
610
|
+
<strong>Render Error:</strong> ${escapeHtml(message)}
|
|
611
|
+
</div>`;
|
|
612
|
+
}
|
|
613
|
+
createDebugBlock(token) {
|
|
614
|
+
if (this.config.format === "html") {
|
|
615
|
+
return `<div style="background-color: #fffbf0; border: 1px solid #fed; color: #b8860b; padding: 8px; border-radius: 4px; margin: 8px 0; font-size: 14px;">
|
|
616
|
+
<strong>Unknown token type:</strong> ${escapeHtml(token.type)}<br>
|
|
617
|
+
<strong>Content:</strong> ${escapeHtml(token.content || token.raw || "")}
|
|
618
|
+
</div>`;
|
|
619
|
+
}
|
|
620
|
+
return `<div class="bg-yellow-100 border border-yellow-300 text-yellow-800 p-2 rounded text-sm mb-2">
|
|
621
|
+
<strong>Unknown token type:</strong> ${escapeHtml(token.type)}<br>
|
|
622
|
+
<strong>Content:</strong> ${escapeHtml(token.content || token.raw || "")}
|
|
623
|
+
</div>`;
|
|
624
|
+
}
|
|
625
|
+
getWarnings() {
|
|
626
|
+
return [...this.warnings];
|
|
627
|
+
}
|
|
628
|
+
getConfig() {
|
|
629
|
+
return { ...this.config };
|
|
630
|
+
}
|
|
631
|
+
updateConfig(config) {
|
|
632
|
+
this.config = { ...this.config, ...config };
|
|
633
|
+
}
|
|
634
|
+
setDebugMode(enabled) {
|
|
635
|
+
this.config.debugMode = enabled;
|
|
636
|
+
}
|
|
637
|
+
clearWarnings() {
|
|
638
|
+
this.warnings = [];
|
|
639
|
+
}
|
|
640
|
+
};
|
|
641
|
+
|
|
642
|
+
// src/cache.ts
|
|
643
|
+
var LRUCache = class {
|
|
644
|
+
constructor(capacity = 100) {
|
|
645
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
646
|
+
this.hits = 0;
|
|
647
|
+
this.misses = 0;
|
|
648
|
+
this.evictions = 0;
|
|
649
|
+
if (capacity <= 0) {
|
|
650
|
+
throw new Error("Cache capacity must be greater than 0");
|
|
651
|
+
}
|
|
652
|
+
this.capacity = capacity;
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* Get a value from the cache
|
|
656
|
+
*/
|
|
657
|
+
get(key) {
|
|
658
|
+
const entry = this.cache.get(key);
|
|
659
|
+
if (entry) {
|
|
660
|
+
entry.timestamp = Date.now();
|
|
661
|
+
entry.accessCount++;
|
|
662
|
+
this.hits++;
|
|
663
|
+
this.cache.delete(key);
|
|
664
|
+
this.cache.set(key, entry);
|
|
665
|
+
return entry.value;
|
|
666
|
+
}
|
|
667
|
+
this.misses++;
|
|
668
|
+
return void 0;
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* Set a value in the cache
|
|
672
|
+
*/
|
|
673
|
+
set(key, value) {
|
|
674
|
+
if (this.cache.has(key)) {
|
|
675
|
+
this.cache.delete(key);
|
|
676
|
+
} else if (this.cache.size >= this.capacity) {
|
|
677
|
+
this.evictLRU();
|
|
678
|
+
}
|
|
679
|
+
this.cache.set(key, {
|
|
680
|
+
value,
|
|
681
|
+
timestamp: Date.now(),
|
|
682
|
+
accessCount: 0
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* Check if a key exists in the cache
|
|
687
|
+
*/
|
|
688
|
+
has(key) {
|
|
689
|
+
return this.cache.has(key);
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* Delete a specific key from the cache
|
|
693
|
+
*/
|
|
694
|
+
delete(key) {
|
|
695
|
+
return this.cache.delete(key);
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* Clear all entries from the cache
|
|
699
|
+
*/
|
|
700
|
+
clear() {
|
|
701
|
+
this.cache.clear();
|
|
702
|
+
this.hits = 0;
|
|
703
|
+
this.misses = 0;
|
|
704
|
+
this.evictions = 0;
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* Get the current size of the cache
|
|
708
|
+
*/
|
|
709
|
+
get size() {
|
|
710
|
+
return this.cache.size;
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
713
|
+
* Get cache statistics
|
|
714
|
+
*/
|
|
715
|
+
getStats() {
|
|
716
|
+
const totalRequests = this.hits + this.misses;
|
|
717
|
+
return {
|
|
718
|
+
size: this.cache.size,
|
|
719
|
+
capacity: this.capacity,
|
|
720
|
+
hits: this.hits,
|
|
721
|
+
misses: this.misses,
|
|
722
|
+
hitRate: totalRequests > 0 ? this.hits / totalRequests : 0,
|
|
723
|
+
evictions: this.evictions
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* Reset cache statistics
|
|
728
|
+
*/
|
|
729
|
+
resetStats() {
|
|
730
|
+
this.hits = 0;
|
|
731
|
+
this.misses = 0;
|
|
732
|
+
this.evictions = 0;
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Get all keys in the cache
|
|
736
|
+
*/
|
|
737
|
+
keys() {
|
|
738
|
+
return Array.from(this.cache.keys());
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* Get all values in the cache
|
|
742
|
+
*/
|
|
743
|
+
values() {
|
|
744
|
+
return Array.from(this.cache.values()).map((entry) => entry.value);
|
|
745
|
+
}
|
|
746
|
+
/**
|
|
747
|
+
* Update cache capacity and evict if necessary
|
|
748
|
+
*/
|
|
749
|
+
setCapacity(newCapacity) {
|
|
750
|
+
if (newCapacity <= 0) {
|
|
751
|
+
throw new Error("Cache capacity must be greater than 0");
|
|
752
|
+
}
|
|
753
|
+
this.capacity = newCapacity;
|
|
754
|
+
while (this.cache.size > this.capacity) {
|
|
755
|
+
this.evictLRU();
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
/**
|
|
759
|
+
* Evict the least recently used entry
|
|
760
|
+
*/
|
|
761
|
+
evictLRU() {
|
|
762
|
+
const firstKey = this.cache.keys().next().value;
|
|
763
|
+
if (firstKey !== void 0) {
|
|
764
|
+
this.cache.delete(firstKey);
|
|
765
|
+
this.evictions++;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
};
|
|
769
|
+
function hashContent(content) {
|
|
770
|
+
if (content.length > 1e4) {
|
|
771
|
+
const start = content.slice(0, 1e3);
|
|
772
|
+
const middle = content.slice(Math.floor(content.length / 2) - 500, Math.floor(content.length / 2) + 500);
|
|
773
|
+
const end = content.slice(-1e3);
|
|
774
|
+
const sample = content.length + "|" + start + middle + end;
|
|
775
|
+
let hash2 = 2166136261;
|
|
776
|
+
for (let i = 0; i < sample.length; i++) {
|
|
777
|
+
hash2 ^= sample.charCodeAt(i);
|
|
778
|
+
hash2 += (hash2 << 1) + (hash2 << 4) + (hash2 << 7) + (hash2 << 8) + (hash2 << 24);
|
|
779
|
+
}
|
|
780
|
+
return (hash2 >>> 0).toString(36);
|
|
781
|
+
}
|
|
782
|
+
let hash = 2166136261;
|
|
783
|
+
for (let i = 0; i < content.length; i++) {
|
|
784
|
+
hash ^= content.charCodeAt(i);
|
|
785
|
+
hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
|
|
786
|
+
}
|
|
787
|
+
return (hash >>> 0).toString(36);
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
// src/extensions/core/blockquote.ts
|
|
791
|
+
var BlockquoteExtension = {
|
|
792
|
+
name: "blockquote",
|
|
793
|
+
parseRules: [
|
|
794
|
+
{
|
|
795
|
+
name: "blockquote",
|
|
796
|
+
pattern: /^>\s+(.+)$/m,
|
|
797
|
+
render: (match) => ({
|
|
798
|
+
type: "blockquote",
|
|
799
|
+
content: match[1] || "",
|
|
800
|
+
raw: match[0] || ""
|
|
801
|
+
})
|
|
802
|
+
}
|
|
803
|
+
],
|
|
804
|
+
renderRules: [
|
|
805
|
+
{
|
|
806
|
+
type: "blockquote",
|
|
807
|
+
render: (token) => {
|
|
808
|
+
const format = token.attributes?.format || "html";
|
|
809
|
+
const renderedChildren = token.attributes?.renderedChildren;
|
|
810
|
+
const renderMarkdown = token.attributes?.renderMarkdown;
|
|
811
|
+
const content = renderedChildren || (renderMarkdown ? renderMarkdown(token.content) : escapeHtml(token.content));
|
|
812
|
+
if (format === "html") {
|
|
813
|
+
return `<blockquote style="border-left: 2px solid #d1d5db; padding: 8px 0 8px 16px; margin: 16px 0; font-style: italic; color: #6b7280;">${content}</blockquote>`;
|
|
814
|
+
}
|
|
815
|
+
return `<blockquote class="pl-4 py-2 border-l-2 border-border italic text-muted-foreground my-4">${content}</blockquote>`;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
]
|
|
819
|
+
};
|
|
820
|
+
|
|
821
|
+
// src/extensions/core/breaks.ts
|
|
822
|
+
var LineBreakExtension = {
|
|
823
|
+
name: "line-break",
|
|
824
|
+
parseRules: [
|
|
825
|
+
{
|
|
826
|
+
name: "hard-break-backslash",
|
|
827
|
+
pattern: /\\\s*\n/,
|
|
828
|
+
render: (match) => ({
|
|
829
|
+
type: "line-break",
|
|
830
|
+
content: "",
|
|
831
|
+
raw: match[0] || ""
|
|
832
|
+
})
|
|
833
|
+
},
|
|
834
|
+
{
|
|
835
|
+
name: "hard-break-spaces",
|
|
836
|
+
pattern: / +\n/,
|
|
837
|
+
render: (match) => ({
|
|
838
|
+
type: "line-break",
|
|
839
|
+
content: "",
|
|
840
|
+
raw: match[0] || ""
|
|
841
|
+
})
|
|
842
|
+
}
|
|
843
|
+
],
|
|
844
|
+
renderRules: [
|
|
845
|
+
{
|
|
846
|
+
type: "line-break",
|
|
847
|
+
render: () => "<br>"
|
|
848
|
+
},
|
|
849
|
+
{
|
|
850
|
+
type: "paragraph-break",
|
|
851
|
+
render: () => "</p><p>"
|
|
852
|
+
},
|
|
853
|
+
{
|
|
854
|
+
type: "soft-break",
|
|
855
|
+
render: (token) => token.content || " "
|
|
856
|
+
}
|
|
857
|
+
]
|
|
858
|
+
};
|
|
859
|
+
|
|
860
|
+
// src/extensions/core/code.ts
|
|
861
|
+
var InlineCodeExtension = {
|
|
862
|
+
name: "inline-code",
|
|
863
|
+
parseRules: [
|
|
864
|
+
{
|
|
865
|
+
name: "code",
|
|
866
|
+
pattern: /`([^`]+)`/,
|
|
867
|
+
render: (match) => ({
|
|
868
|
+
type: "code",
|
|
869
|
+
content: match[1] || "",
|
|
870
|
+
raw: match[0] || ""
|
|
871
|
+
})
|
|
872
|
+
}
|
|
873
|
+
],
|
|
874
|
+
renderRules: [
|
|
875
|
+
{
|
|
876
|
+
type: "code",
|
|
877
|
+
render: (token) => {
|
|
878
|
+
const content = escapeHtml(token.content);
|
|
879
|
+
const format = token.attributes?.format;
|
|
880
|
+
if (format === "html") {
|
|
881
|
+
return `<code style="background-color: #f3f4f6; padding: 2px 6px; border-radius: 4px; font-family: monospace; font-size: 0.875rem;">${content}</code>`;
|
|
882
|
+
}
|
|
883
|
+
return `<code class="bg-muted px-1.5 py-0.5 rounded text-sm font-mono">${content}</code>`;
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
]
|
|
887
|
+
};
|
|
888
|
+
var CodeBlockExtension = {
|
|
889
|
+
name: "codeblock",
|
|
890
|
+
parseRules: [
|
|
891
|
+
{
|
|
892
|
+
name: "codeblock",
|
|
893
|
+
pattern: /```(\w+)?\s*\n([\s\S]*?)\n```/,
|
|
894
|
+
render: (match) => ({
|
|
895
|
+
type: "codeblock",
|
|
896
|
+
content: match[2] || "",
|
|
897
|
+
raw: match[0] || "",
|
|
898
|
+
attributes: {
|
|
899
|
+
language: match[1] || "text"
|
|
900
|
+
}
|
|
901
|
+
})
|
|
902
|
+
}
|
|
903
|
+
],
|
|
904
|
+
renderRules: [
|
|
905
|
+
{
|
|
906
|
+
type: "codeblock",
|
|
907
|
+
render: (token) => {
|
|
908
|
+
const language = token.attributes?.language || "text";
|
|
909
|
+
const escapedCode = escapeHtml(token.content);
|
|
910
|
+
const format = token.attributes?.format || "html";
|
|
911
|
+
if (format === "html") {
|
|
912
|
+
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>`;
|
|
913
|
+
}
|
|
914
|
+
return `<pre class="bg-muted p-4 rounded-md overflow-x-auto my-4"><code class="language-${escapeHtml(language)}">${escapedCode}</code></pre>`;
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
]
|
|
918
|
+
};
|
|
919
|
+
|
|
920
|
+
// src/extensions/core/emphasis.ts
|
|
921
|
+
var BoldExtension = {
|
|
922
|
+
name: "bold",
|
|
923
|
+
parseRules: [
|
|
924
|
+
{
|
|
925
|
+
name: "bold",
|
|
926
|
+
pattern: /\*\*((?:(?!\*\*).)+)\*\*/,
|
|
927
|
+
render: (match) => ({
|
|
928
|
+
type: "bold",
|
|
929
|
+
content: match[1] || "",
|
|
930
|
+
raw: match[0] || ""
|
|
931
|
+
})
|
|
932
|
+
}
|
|
933
|
+
],
|
|
934
|
+
renderRules: [
|
|
935
|
+
{
|
|
936
|
+
type: "bold",
|
|
937
|
+
render: (token) => {
|
|
938
|
+
const content = escapeHtml(token.content);
|
|
939
|
+
const format = token.attributes?.format;
|
|
940
|
+
if (format === "html") {
|
|
941
|
+
return `<strong>${content}</strong>`;
|
|
942
|
+
}
|
|
943
|
+
return `<strong class="font-bold">${content}</strong>`;
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
]
|
|
947
|
+
};
|
|
948
|
+
var ItalicExtension = {
|
|
949
|
+
name: "italic",
|
|
950
|
+
parseRules: [
|
|
951
|
+
{
|
|
952
|
+
name: "italic",
|
|
953
|
+
pattern: /\*((?:(?!\*).)+)\*/,
|
|
954
|
+
render: (match) => ({
|
|
955
|
+
type: "italic",
|
|
956
|
+
content: match[1] || "",
|
|
957
|
+
raw: match[0] || ""
|
|
958
|
+
})
|
|
959
|
+
}
|
|
960
|
+
],
|
|
961
|
+
renderRules: [
|
|
962
|
+
{
|
|
963
|
+
type: "italic",
|
|
964
|
+
render: (token) => {
|
|
965
|
+
const content = escapeHtml(token.content);
|
|
966
|
+
const format = token.attributes?.format;
|
|
967
|
+
if (format === "html") {
|
|
968
|
+
return `<em>${content}</em>`;
|
|
969
|
+
}
|
|
970
|
+
return `<em class="italic">${content}</em>`;
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
]
|
|
974
|
+
};
|
|
975
|
+
|
|
976
|
+
// src/extensions/core/heading.ts
|
|
977
|
+
var HeadingExtension = {
|
|
978
|
+
name: "heading",
|
|
979
|
+
parseRules: [
|
|
980
|
+
{
|
|
981
|
+
name: "heading",
|
|
982
|
+
pattern: /^(#{1,6})\s+(.+)$/m,
|
|
983
|
+
render: (match) => ({
|
|
984
|
+
type: "heading",
|
|
985
|
+
content: match[2]?.trim() || "",
|
|
986
|
+
raw: match[0] || "",
|
|
987
|
+
attributes: {
|
|
988
|
+
level: String(match[1]?.length || 1)
|
|
989
|
+
}
|
|
990
|
+
})
|
|
991
|
+
}
|
|
992
|
+
],
|
|
993
|
+
renderRules: [
|
|
994
|
+
{
|
|
995
|
+
type: "heading",
|
|
996
|
+
render: (token) => {
|
|
997
|
+
const level = parseInt(token.attributes?.level || "1");
|
|
998
|
+
const text = token.content;
|
|
999
|
+
const id = generateId(text);
|
|
1000
|
+
const escapedContent = escapeHtml(text);
|
|
1001
|
+
const format = token.attributes?.format || "html";
|
|
1002
|
+
if (format === "html") {
|
|
1003
|
+
return `<h${level} id="${id}">${escapedContent}</h${level}>`;
|
|
1004
|
+
}
|
|
1005
|
+
let headingClasses = "group relative flex items-center gap-2";
|
|
1006
|
+
switch (level) {
|
|
1007
|
+
case 1:
|
|
1008
|
+
headingClasses += " text-3xl font-bold mt-8 mb-4";
|
|
1009
|
+
break;
|
|
1010
|
+
case 2:
|
|
1011
|
+
headingClasses += " text-2xl font-semibold mt-6 mb-3";
|
|
1012
|
+
break;
|
|
1013
|
+
case 3:
|
|
1014
|
+
headingClasses += " text-xl font-medium mt-5 mb-3";
|
|
1015
|
+
break;
|
|
1016
|
+
case 4:
|
|
1017
|
+
headingClasses += " text-lg font-medium mt-4 mb-2";
|
|
1018
|
+
break;
|
|
1019
|
+
case 5:
|
|
1020
|
+
headingClasses += " text-base font-medium mt-3 mb-2";
|
|
1021
|
+
break;
|
|
1022
|
+
case 6:
|
|
1023
|
+
headingClasses += " text-sm font-medium mt-3 mb-2";
|
|
1024
|
+
break;
|
|
1025
|
+
}
|
|
1026
|
+
return `<h${level} id="${id}" class="${headingClasses}">
|
|
1027
|
+
${escapedContent}
|
|
1028
|
+
<a href="#${id}" class="opacity-0 group-hover:opacity-100 text-muted-foreground transition-opacity">
|
|
1029
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1030
|
+
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/>
|
|
1031
|
+
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>
|
|
1032
|
+
</svg>
|
|
1033
|
+
</a>
|
|
1034
|
+
</h${level}>`;
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
]
|
|
1038
|
+
};
|
|
1039
|
+
|
|
1040
|
+
// src/extensions/core/hr.ts
|
|
1041
|
+
var HorizontalRuleExtension = {
|
|
1042
|
+
name: "hr",
|
|
1043
|
+
parseRules: [
|
|
1044
|
+
{
|
|
1045
|
+
name: "hr",
|
|
1046
|
+
pattern: /^---$/m,
|
|
1047
|
+
render: (match) => ({
|
|
1048
|
+
type: "hr",
|
|
1049
|
+
content: "",
|
|
1050
|
+
raw: match[0] || ""
|
|
1051
|
+
})
|
|
1052
|
+
}
|
|
1053
|
+
],
|
|
1054
|
+
renderRules: [
|
|
1055
|
+
{
|
|
1056
|
+
type: "hr",
|
|
1057
|
+
render: (token) => {
|
|
1058
|
+
const format = token.attributes?.format;
|
|
1059
|
+
if (format === "html") {
|
|
1060
|
+
return '<hr style="margin: 24px 0; border: none; border-top: 1px solid #d1d5db;">';
|
|
1061
|
+
}
|
|
1062
|
+
return '<hr class="my-6 border-t border-border">';
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
]
|
|
1066
|
+
};
|
|
1067
|
+
|
|
1068
|
+
// src/extensions/core/image.ts
|
|
1069
|
+
var ImageExtension = {
|
|
1070
|
+
name: "image",
|
|
1071
|
+
parseRules: [
|
|
1072
|
+
{
|
|
1073
|
+
name: "image",
|
|
1074
|
+
pattern: /!\[([^\]]*)\]\(([^)]+?)(?:\s+"([^"]+)")?\)/,
|
|
1075
|
+
render: (match) => ({
|
|
1076
|
+
type: "image",
|
|
1077
|
+
content: match[1] || "",
|
|
1078
|
+
raw: match[0] || "",
|
|
1079
|
+
attributes: {
|
|
1080
|
+
alt: match[1] || "",
|
|
1081
|
+
src: match[2] || "",
|
|
1082
|
+
caption: match[3] || ""
|
|
1083
|
+
// Renamed from 'title' to 'caption' for clarity
|
|
1084
|
+
}
|
|
1085
|
+
})
|
|
1086
|
+
}
|
|
1087
|
+
],
|
|
1088
|
+
renderRules: [
|
|
1089
|
+
{
|
|
1090
|
+
type: "image",
|
|
1091
|
+
render: (token) => {
|
|
1092
|
+
const src = token.attributes?.src || "";
|
|
1093
|
+
const alt = token.attributes?.alt || "";
|
|
1094
|
+
const caption = token.attributes?.caption || "";
|
|
1095
|
+
const format = token.attributes?.format || "html";
|
|
1096
|
+
if (caption) {
|
|
1097
|
+
if (format === "html") {
|
|
1098
|
+
return `<figure style="margin: 16px 0; text-align: center;">
|
|
1099
|
+
<img src="${escapeHtml(src)}" alt="${escapeHtml(alt)}" style="max-width: 100%; height: auto; border-radius: 8px;" loading="lazy" />
|
|
1100
|
+
<figcaption style="margin-top: 8px; font-size: 14px; color: #6b7280; font-style: italic;">${escapeHtml(caption)}</figcaption>
|
|
1101
|
+
</figure>`;
|
|
1102
|
+
}
|
|
1103
|
+
return `<figure class="my-4 text-center">
|
|
1104
|
+
<img src="${escapeHtml(src)}" alt="${escapeHtml(alt)}" class="max-w-full h-auto rounded-lg" loading="lazy" />
|
|
1105
|
+
<figcaption class="mt-2 text-sm text-gray-500 italic">${escapeHtml(caption)}</figcaption>
|
|
1106
|
+
</figure>`;
|
|
1107
|
+
}
|
|
1108
|
+
if (format === "html") {
|
|
1109
|
+
return `<img src="${escapeHtml(src)}" alt="${escapeHtml(alt)}" style="max-width: 100%; height: auto; border-radius: 8px; margin: 16px 0;" loading="lazy" />`;
|
|
1110
|
+
}
|
|
1111
|
+
return `<img src="${escapeHtml(src)}" alt="${escapeHtml(alt)}" class="max-w-full h-auto rounded-lg my-4" loading="lazy" />`;
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
]
|
|
1115
|
+
};
|
|
1116
|
+
|
|
1117
|
+
// src/extensions/core/links.ts
|
|
1118
|
+
var LinkExtension = {
|
|
1119
|
+
name: "link",
|
|
1120
|
+
parseRules: [
|
|
1121
|
+
{
|
|
1122
|
+
name: "link",
|
|
1123
|
+
pattern: /\[(?!(?:button|embed):)([^\]]+)\]\(([^)]+)\)/,
|
|
1124
|
+
render: (match) => ({
|
|
1125
|
+
type: "link",
|
|
1126
|
+
content: match[1] || "",
|
|
1127
|
+
raw: match[0] || "",
|
|
1128
|
+
attributes: {
|
|
1129
|
+
href: match[2] || ""
|
|
1130
|
+
}
|
|
1131
|
+
})
|
|
1132
|
+
}
|
|
1133
|
+
],
|
|
1134
|
+
renderRules: [
|
|
1135
|
+
{
|
|
1136
|
+
type: "link",
|
|
1137
|
+
render: (token) => {
|
|
1138
|
+
const href = token.attributes?.href || "#";
|
|
1139
|
+
const escapedHref = escapeHtml(href);
|
|
1140
|
+
const escapedText = escapeHtml(token.content);
|
|
1141
|
+
const format = token.attributes?.format || "html";
|
|
1142
|
+
if (format === "html") {
|
|
1143
|
+
return `<a href="${escapedHref}" target="_blank" rel="noopener noreferrer">${escapedText}</a>`;
|
|
1144
|
+
}
|
|
1145
|
+
return `<a href="${escapedHref}" class="text-primary hover:underline inline-flex items-center gap-1" target="_blank" rel="noopener noreferrer">
|
|
1146
|
+
${escapedText}
|
|
1147
|
+
<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">
|
|
1148
|
+
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>
|
|
1149
|
+
<polyline points="15 3 21 3 21 9"></polyline>
|
|
1150
|
+
<line x1="10" y1="14" x2="21" y2="3"></line>
|
|
1151
|
+
</svg>
|
|
1152
|
+
</a>`;
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
]
|
|
1156
|
+
};
|
|
1157
|
+
|
|
1158
|
+
// src/extensions/core/lists.ts
|
|
1159
|
+
var ListExtension = {
|
|
1160
|
+
name: "list",
|
|
1161
|
+
parseRules: [
|
|
1162
|
+
{
|
|
1163
|
+
name: "unordered-list-item",
|
|
1164
|
+
pattern: /^(\s*)[-*+]\s+(.+)$/m,
|
|
1165
|
+
render: (match) => ({
|
|
1166
|
+
type: "list-item",
|
|
1167
|
+
content: match[2] || "",
|
|
1168
|
+
raw: match[0] || "",
|
|
1169
|
+
attributes: {
|
|
1170
|
+
indent: match[1]?.length || 0,
|
|
1171
|
+
ordered: false,
|
|
1172
|
+
marker: match[1] ? match[0].match(/[-*+]/)?.[0] : "-"
|
|
1173
|
+
}
|
|
1174
|
+
})
|
|
1175
|
+
},
|
|
1176
|
+
{
|
|
1177
|
+
name: "ordered-list-item",
|
|
1178
|
+
pattern: /^(\s*)(\d+)\.\s+(.+)$/m,
|
|
1179
|
+
render: (match) => ({
|
|
1180
|
+
type: "ordered-list-item",
|
|
1181
|
+
content: match[3] || "",
|
|
1182
|
+
raw: match[0] || "",
|
|
1183
|
+
attributes: {
|
|
1184
|
+
indent: match[1]?.length || 0,
|
|
1185
|
+
ordered: true,
|
|
1186
|
+
number: parseInt(match[2] || "1")
|
|
1187
|
+
}
|
|
1188
|
+
})
|
|
1189
|
+
}
|
|
1190
|
+
],
|
|
1191
|
+
renderRules: [
|
|
1192
|
+
{
|
|
1193
|
+
type: "ul",
|
|
1194
|
+
render: (token) => {
|
|
1195
|
+
const format = token.attributes?.format || "tailwind";
|
|
1196
|
+
const content = token.attributes?.renderedChildren || "";
|
|
1197
|
+
if (format === "html") {
|
|
1198
|
+
return `<ul style="margin: 8px 0; padding-left: 24px; list-style: disc;">${content}</ul>`;
|
|
1199
|
+
}
|
|
1200
|
+
return `<ul class="my-2 pl-6 list-disc">${content}</ul>`;
|
|
1201
|
+
}
|
|
1202
|
+
},
|
|
1203
|
+
{
|
|
1204
|
+
type: "ol",
|
|
1205
|
+
render: (token) => {
|
|
1206
|
+
const format = token.attributes?.format || "tailwind";
|
|
1207
|
+
const content = token.attributes?.renderedChildren || "";
|
|
1208
|
+
if (format === "html") {
|
|
1209
|
+
return `<ol style="margin: 8px 0; padding-left: 24px; list-style: decimal;">${content}</ol>`;
|
|
1210
|
+
}
|
|
1211
|
+
return `<ol class="my-2 pl-6 list-decimal">${content}</ol>`;
|
|
1212
|
+
}
|
|
1213
|
+
},
|
|
1214
|
+
{
|
|
1215
|
+
type: "list-item",
|
|
1216
|
+
render: (token) => {
|
|
1217
|
+
const format = token.attributes?.format || "tailwind";
|
|
1218
|
+
const content = token.attributes?.renderedChildren || escapeHtml(token.content);
|
|
1219
|
+
if (format === "html") {
|
|
1220
|
+
return `<li>${content}</li>`;
|
|
1221
|
+
}
|
|
1222
|
+
return `<li>${content}</li>`;
|
|
1223
|
+
}
|
|
1224
|
+
},
|
|
1225
|
+
{
|
|
1226
|
+
type: "ordered-list-item",
|
|
1227
|
+
render: (token) => {
|
|
1228
|
+
const format = token.attributes?.format || "tailwind";
|
|
1229
|
+
const content = token.attributes?.renderedChildren || escapeHtml(token.content);
|
|
1230
|
+
if (format === "html") {
|
|
1231
|
+
return `<li>${content}</li>`;
|
|
1232
|
+
}
|
|
1233
|
+
return `<li>${content}</li>`;
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
]
|
|
1237
|
+
};
|
|
1238
|
+
var TaskListExtension = {
|
|
1239
|
+
name: "task-list",
|
|
1240
|
+
parseRules: [
|
|
1241
|
+
{
|
|
1242
|
+
name: "task-item",
|
|
1243
|
+
pattern: /^(\s*)-\s*\[([ xX])\]\s*(.+)$/m,
|
|
1244
|
+
render: (match) => ({
|
|
1245
|
+
type: "task-item",
|
|
1246
|
+
content: match[3] || "",
|
|
1247
|
+
raw: match[0] || "",
|
|
1248
|
+
attributes: {
|
|
1249
|
+
indent: match[1]?.length || 0,
|
|
1250
|
+
checked: String((match[2] || "").toLowerCase() === "x")
|
|
1251
|
+
}
|
|
1252
|
+
})
|
|
1253
|
+
}
|
|
1254
|
+
],
|
|
1255
|
+
renderRules: [
|
|
1256
|
+
{
|
|
1257
|
+
type: "task-item",
|
|
1258
|
+
render: (token) => {
|
|
1259
|
+
const isChecked = token.attributes?.checked === "true";
|
|
1260
|
+
const content = token.attributes?.renderedChildren || escapeHtml(token.content);
|
|
1261
|
+
const format = token.attributes?.format || "html";
|
|
1262
|
+
const checkmark = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink: 0; margin-top: 2px;"><polyline points="20 6 9 17 4 12"></polyline></svg>`;
|
|
1263
|
+
const checkbox = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink: 0; margin-top: 2px;"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect></svg>`;
|
|
1264
|
+
if (format === "html") {
|
|
1265
|
+
return `<div style="display: flex; align-items: flex-start; gap: 8px; margin: 8px 0;">
|
|
1266
|
+
<div style="color: ${isChecked ? "#10b981" : "#9ca3af"}; margin-top: 2px;">
|
|
1267
|
+
${isChecked ? checkmark : checkbox}
|
|
1268
|
+
</div>
|
|
1269
|
+
<span${isChecked ? ' style="text-decoration: line-through; color: #6b7280;"' : ""}>${content}</span>
|
|
1270
|
+
</div>`;
|
|
1271
|
+
}
|
|
1272
|
+
return `<div class="flex items-start gap-2 my-2 task-list-item">
|
|
1273
|
+
<div class="${isChecked ? "text-green-600" : "text-gray-400"} flex-shrink-0 mt-0.5">
|
|
1274
|
+
${isChecked ? checkmark : checkbox}
|
|
1275
|
+
</div>
|
|
1276
|
+
<span${isChecked ? ' class="line-through text-muted-foreground"' : ""}>${content}</span>
|
|
1277
|
+
</div>`;
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
]
|
|
1281
|
+
};
|
|
1282
|
+
|
|
1283
|
+
// src/extensions/core/paragraph.ts
|
|
1284
|
+
var ParagraphExtension = {
|
|
1285
|
+
name: "paragraph",
|
|
1286
|
+
parseRules: [],
|
|
1287
|
+
renderRules: [
|
|
1288
|
+
{
|
|
1289
|
+
type: "paragraph",
|
|
1290
|
+
render: (token) => {
|
|
1291
|
+
if (!token.content) return "";
|
|
1292
|
+
const content = token.content.trim();
|
|
1293
|
+
if (!content) return "";
|
|
1294
|
+
const processedContent = content.includes("<br>") ? content : escapeHtml(content);
|
|
1295
|
+
const format = token.attributes?.format || "html";
|
|
1296
|
+
if (format === "html") {
|
|
1297
|
+
return `<p style="line-height: 1.75; margin-bottom: 16px;">${processedContent}</p>`;
|
|
1298
|
+
}
|
|
1299
|
+
return `<p class="leading-7 mb-4">${processedContent}</p>`;
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
]
|
|
1303
|
+
};
|
|
1304
|
+
|
|
1305
|
+
// src/extensions/core/text.ts
|
|
1306
|
+
var TextExtension = {
|
|
1307
|
+
name: "text",
|
|
1308
|
+
parseRules: [],
|
|
1309
|
+
renderRules: [
|
|
1310
|
+
{
|
|
1311
|
+
type: "text",
|
|
1312
|
+
render: (token) => {
|
|
1313
|
+
if (!token.content) return "";
|
|
1314
|
+
return escapeHtml(token.content);
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
]
|
|
1318
|
+
};
|
|
1319
|
+
|
|
1320
|
+
// src/extensions/core/strikethrough.ts
|
|
1321
|
+
var StrikethroughExtension = {
|
|
1322
|
+
name: "strikethrough",
|
|
1323
|
+
parseRules: [
|
|
1324
|
+
{
|
|
1325
|
+
name: "strikethrough",
|
|
1326
|
+
pattern: /~~((?:(?!~~).)+)~~/,
|
|
1327
|
+
render: (match) => ({
|
|
1328
|
+
type: "strikethrough",
|
|
1329
|
+
content: match[1] || "",
|
|
1330
|
+
raw: match[0] || ""
|
|
1331
|
+
})
|
|
1332
|
+
}
|
|
1333
|
+
],
|
|
1334
|
+
renderRules: [
|
|
1335
|
+
{
|
|
1336
|
+
type: "strikethrough",
|
|
1337
|
+
render: (token) => {
|
|
1338
|
+
const content = escapeHtml(token.content);
|
|
1339
|
+
const format = token.attributes?.format;
|
|
1340
|
+
if (format === "html") {
|
|
1341
|
+
return `<del style="text-decoration: line-through; color: #6b7280;">${content}</del>`;
|
|
1342
|
+
}
|
|
1343
|
+
return `<del class="line-through text-gray-500">${content}</del>`;
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
]
|
|
1347
|
+
};
|
|
1348
|
+
|
|
1349
|
+
// src/extensions/core/index.ts
|
|
1350
|
+
var CoreExtensions = [
|
|
1351
|
+
TextExtension,
|
|
1352
|
+
HeadingExtension,
|
|
1353
|
+
BoldExtension,
|
|
1354
|
+
ItalicExtension,
|
|
1355
|
+
InlineCodeExtension,
|
|
1356
|
+
CodeBlockExtension,
|
|
1357
|
+
LinkExtension,
|
|
1358
|
+
ImageExtension,
|
|
1359
|
+
ListExtension,
|
|
1360
|
+
TaskListExtension,
|
|
1361
|
+
BlockquoteExtension,
|
|
1362
|
+
HorizontalRuleExtension,
|
|
1363
|
+
StrikethroughExtension,
|
|
1364
|
+
ParagraphExtension,
|
|
1365
|
+
LineBreakExtension
|
|
1366
|
+
];
|
|
1367
|
+
|
|
1368
|
+
// src/extensions/alert.ts
|
|
1369
|
+
var AlertExtension = {
|
|
1370
|
+
name: "alert",
|
|
1371
|
+
parseRules: [
|
|
1372
|
+
{
|
|
1373
|
+
name: "alert",
|
|
1374
|
+
pattern: /:::(\w+)(?: ([^\n]+))?\n([\s\S]*?)\n:::/,
|
|
1375
|
+
render: (match) => {
|
|
1376
|
+
return {
|
|
1377
|
+
type: "alert",
|
|
1378
|
+
content: match[3]?.trim() || "",
|
|
1379
|
+
raw: match[0] || "",
|
|
1380
|
+
attributes: {
|
|
1381
|
+
type: match[1] || "info",
|
|
1382
|
+
title: match[2]?.trim() || ""
|
|
1383
|
+
}
|
|
1384
|
+
};
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
],
|
|
1388
|
+
renderRules: [
|
|
1389
|
+
{
|
|
1390
|
+
type: "alert",
|
|
1391
|
+
render: (token) => {
|
|
1392
|
+
const type = token.attributes?.type || "info";
|
|
1393
|
+
const title = token.attributes?.title || "";
|
|
1394
|
+
const typeConfig = {
|
|
1395
|
+
info: {
|
|
1396
|
+
icon: "\u2139\uFE0F",
|
|
1397
|
+
classes: "bg-blue-500/10 border-blue-500/30 text-blue-600 border-l-blue-500"
|
|
1398
|
+
},
|
|
1399
|
+
warning: {
|
|
1400
|
+
icon: "\u26A0\uFE0F",
|
|
1401
|
+
classes: "bg-amber-500/10 border-amber-500/30 text-amber-600 border-l-amber-500"
|
|
1402
|
+
},
|
|
1403
|
+
error: {
|
|
1404
|
+
icon: "\u274C",
|
|
1405
|
+
classes: "bg-red-500/10 border-red-500/30 text-red-600 border-l-red-500"
|
|
1406
|
+
},
|
|
1407
|
+
success: {
|
|
1408
|
+
icon: "\u2705",
|
|
1409
|
+
classes: "bg-green-500/10 border-green-500/30 text-green-600 border-l-green-500"
|
|
1410
|
+
},
|
|
1411
|
+
tip: {
|
|
1412
|
+
icon: "\u{1F4A1}",
|
|
1413
|
+
classes: "bg-purple-500/10 border-purple-500/30 text-purple-600 border-l-purple-500"
|
|
1414
|
+
},
|
|
1415
|
+
note: {
|
|
1416
|
+
icon: "\u{1F4DD}",
|
|
1417
|
+
classes: "bg-gray-500/10 border-gray-500/30 text-gray-600 border-l-gray-500"
|
|
1418
|
+
}
|
|
1419
|
+
};
|
|
1420
|
+
const config = typeConfig[type] || typeConfig.info;
|
|
1421
|
+
const baseClasses = "border-l-4 p-4 mb-4 rounded-md transition-colors duration-200";
|
|
1422
|
+
const classes = `${baseClasses} ${config?.classes}`;
|
|
1423
|
+
const renderedChildren = token.attributes?.renderedChildren;
|
|
1424
|
+
const renderMarkdown = token.attributes?.renderMarkdown;
|
|
1425
|
+
const renderedContent = renderedChildren || (renderMarkdown ? renderMarkdown(token.content) : token.content);
|
|
1426
|
+
const titleHtml = title ? `<div class="font-medium mb-2 flex items-center gap-2">
|
|
1427
|
+
<span class="text-lg" role="img" aria-label="${type}">${config?.icon}</span>
|
|
1428
|
+
<span>${title}</span>
|
|
1429
|
+
</div>` : `<div class="font-medium mb-2 flex items-center gap-2">
|
|
1430
|
+
<span class="text-lg" role="img" aria-label="${type}">${config?.icon}</span>
|
|
1431
|
+
<span>${type.charAt(0).toUpperCase() + type.slice(1)}</span>
|
|
1432
|
+
</div>`;
|
|
1433
|
+
return `<div class="${classes}" role="alert" aria-live="polite">
|
|
1434
|
+
${titleHtml}
|
|
1435
|
+
<div class="leading-relaxed">${renderedContent}</div>
|
|
1436
|
+
</div>`;
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
]
|
|
1440
|
+
};
|
|
1441
|
+
|
|
1442
|
+
// src/extensions/button.ts
|
|
1443
|
+
var ButtonExtension = {
|
|
1444
|
+
name: "button",
|
|
1445
|
+
parseRules: [
|
|
1446
|
+
{
|
|
1447
|
+
name: "button",
|
|
1448
|
+
pattern: /\[button:([^\]]+)\]\(([^)]+)\)(?:\{([^}]+)\})?/,
|
|
1449
|
+
render: (match) => {
|
|
1450
|
+
const options = match[3] ? match[3].split(",").map((opt) => opt.trim()) : [];
|
|
1451
|
+
return {
|
|
1452
|
+
type: "button",
|
|
1453
|
+
content: match[1] || "",
|
|
1454
|
+
raw: match[0] || "",
|
|
1455
|
+
attributes: {
|
|
1456
|
+
href: match[2] || "",
|
|
1457
|
+
style: options.find(
|
|
1458
|
+
(opt) => ["default", "primary", "secondary", "success", "danger", "outline", "ghost"].includes(opt)
|
|
1459
|
+
) || "primary",
|
|
1460
|
+
size: options.find((opt) => ["sm", "md", "lg"].includes(opt)) || "md",
|
|
1461
|
+
disabled: String(options.includes("disabled")),
|
|
1462
|
+
target: options.includes("self") ? "_self" : "_blank"
|
|
1463
|
+
}
|
|
1464
|
+
};
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
],
|
|
1468
|
+
renderRules: [
|
|
1469
|
+
{
|
|
1470
|
+
type: "button",
|
|
1471
|
+
render: (token) => {
|
|
1472
|
+
const href = token.attributes?.href || "#";
|
|
1473
|
+
const style = token.attributes?.style || "primary";
|
|
1474
|
+
const size = token.attributes?.size || "md";
|
|
1475
|
+
const disabled = token.attributes?.disabled === "true";
|
|
1476
|
+
const target = token.attributes?.target || "_blank";
|
|
1477
|
+
const baseClasses = `
|
|
1478
|
+
inline-flex items-center justify-center font-medium rounded-lg
|
|
1479
|
+
transition-all duration-200 ease-out
|
|
1480
|
+
focus:outline-none focus:ring-2 focus:ring-offset-2
|
|
1481
|
+
disabled:opacity-50 disabled:cursor-not-allowed disabled:pointer-events-none
|
|
1482
|
+
transform hover:scale-[1.02] active:scale-[0.98]
|
|
1483
|
+
shadow-sm hover:shadow-md active:shadow-sm
|
|
1484
|
+
border border-transparent
|
|
1485
|
+
relative overflow-hidden
|
|
1486
|
+
before:absolute before:inset-0 before:rounded-lg
|
|
1487
|
+
before:bg-gradient-to-br before:from-white/20 before:to-transparent
|
|
1488
|
+
before:opacity-0 hover:before:opacity-100 before:transition-opacity before:duration-200
|
|
1489
|
+
`.replace(/\s+/g, " ").trim();
|
|
1490
|
+
const sizeClasses = {
|
|
1491
|
+
sm: "px-3 py-1.5 text-sm gap-1.5",
|
|
1492
|
+
md: "px-4 py-2 text-base gap-2",
|
|
1493
|
+
lg: "px-6 py-3 text-lg gap-2.5"
|
|
1494
|
+
};
|
|
1495
|
+
const styleClasses = {
|
|
1496
|
+
default: `
|
|
1497
|
+
bg-slate-600 text-white border-slate-500
|
|
1498
|
+
hover:bg-slate-700 hover:border-slate-400
|
|
1499
|
+
focus:ring-slate-500
|
|
1500
|
+
shadow-[0_1px_0_0_rgba(255,255,255,0.1)_inset,0_1px_2px_0_rgba(0,0,0,0.1)]
|
|
1501
|
+
hover:shadow-[0_1px_0_0_rgba(255,255,255,0.15)_inset,0_2px_4px_0_rgba(0,0,0,0.15)]
|
|
1502
|
+
`,
|
|
1503
|
+
primary: `
|
|
1504
|
+
bg-blue-600 text-white border-blue-500
|
|
1505
|
+
hover:bg-blue-700 hover:border-blue-400
|
|
1506
|
+
focus:ring-blue-500
|
|
1507
|
+
shadow-[0_1px_0_0_rgba(255,255,255,0.1)_inset,0_1px_2px_0_rgba(0,0,0,0.1)]
|
|
1508
|
+
hover:shadow-[0_1px_0_0_rgba(255,255,255,0.15)_inset,0_2px_4px_0_rgba(0,0,0,0.15)]
|
|
1509
|
+
`,
|
|
1510
|
+
secondary: `
|
|
1511
|
+
bg-gray-600 text-white border-gray-500
|
|
1512
|
+
hover:bg-gray-700 hover:border-gray-400
|
|
1513
|
+
focus:ring-gray-500
|
|
1514
|
+
shadow-[0_1px_0_0_rgba(255,255,255,0.1)_inset,0_1px_2px_0_rgba(0,0,0,0.1)]
|
|
1515
|
+
hover:shadow-[0_1px_0_0_rgba(255,255,255,0.15)_inset,0_2px_4px_0_rgba(0,0,0,0.15)]
|
|
1516
|
+
`,
|
|
1517
|
+
success: `
|
|
1518
|
+
bg-green-600 text-white border-green-500
|
|
1519
|
+
hover:bg-green-700 hover:border-green-400
|
|
1520
|
+
focus:ring-green-500
|
|
1521
|
+
shadow-[0_1px_0_0_rgba(255,255,255,0.1)_inset,0_1px_2px_0_rgba(0,0,0,0.1)]
|
|
1522
|
+
hover:shadow-[0_1px_0_0_rgba(255,255,255,0.15)_inset,0_2px_4px_0_rgba(0,0,0,0.15)]
|
|
1523
|
+
`,
|
|
1524
|
+
danger: `
|
|
1525
|
+
bg-red-600 text-white border-red-500
|
|
1526
|
+
hover:bg-red-700 hover:border-red-400
|
|
1527
|
+
focus:ring-red-500
|
|
1528
|
+
shadow-[0_1px_0_0_rgba(255,255,255,0.1)_inset,0_1px_2px_0_rgba(0,0,0,0.1)]
|
|
1529
|
+
hover:shadow-[0_1px_0_0_rgba(255,255,255,0.15)_inset,0_2px_4px_0_rgba(0,0,0,0.15)]
|
|
1530
|
+
`,
|
|
1531
|
+
outline: `
|
|
1532
|
+
bg-transparent text-blue-600 border-blue-600
|
|
1533
|
+
hover:bg-blue-50 hover:border-blue-700 hover:text-blue-700
|
|
1534
|
+
focus:ring-blue-500
|
|
1535
|
+
shadow-[0_0_0_1px_rgba(59,130,246,0.5)_inset]
|
|
1536
|
+
hover:shadow-[0_0_0_1px_rgba(29,78,216,0.6)_inset,0_1px_2px_0_rgba(0,0,0,0.05)]
|
|
1537
|
+
`,
|
|
1538
|
+
ghost: `
|
|
1539
|
+
bg-transparent text-gray-700 border-transparent
|
|
1540
|
+
hover:bg-gray-100 hover:text-gray-900
|
|
1541
|
+
focus:ring-gray-500
|
|
1542
|
+
shadow-none
|
|
1543
|
+
hover:shadow-[0_1px_2px_0_rgba(0,0,0,0.05)]
|
|
1544
|
+
`
|
|
1545
|
+
};
|
|
1546
|
+
const classes = `
|
|
1547
|
+
${baseClasses}
|
|
1548
|
+
${sizeClasses[size] || sizeClasses.md}
|
|
1549
|
+
${styleClasses[style] || styleClasses.primary}
|
|
1550
|
+
`.replace(/\s+/g, " ").trim();
|
|
1551
|
+
const targetAttr = target === "_blank" ? ' target="_blank" rel="noopener noreferrer"' : target === "_self" ? ' target="_self"' : "";
|
|
1552
|
+
const disabledAttr = disabled ? ' aria-disabled="true" tabindex="-1"' : "";
|
|
1553
|
+
const externalIcon = target === "_blank" && !disabled ? `<svg class="w-4 h-4 ml-1 opacity-75" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
1554
|
+
<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"/>
|
|
1555
|
+
</svg>` : "";
|
|
1556
|
+
return `<a href="${href}" class="${classes}"${targetAttr}${disabledAttr}>
|
|
1557
|
+
<span class="relative z-10">${token.content}</span>${externalIcon}
|
|
1558
|
+
</a>`;
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
]
|
|
1562
|
+
};
|
|
1563
|
+
|
|
1564
|
+
// src/extensions/embed.ts
|
|
1565
|
+
var EmbedExtension = {
|
|
1566
|
+
name: "embed",
|
|
1567
|
+
parseRules: [
|
|
1568
|
+
{
|
|
1569
|
+
name: "embed",
|
|
1570
|
+
pattern: /\[embed:(\w+)\]\(([^)]+)\)(?:\{([^}]+)\})?/,
|
|
1571
|
+
render: (match) => {
|
|
1572
|
+
return {
|
|
1573
|
+
type: "embed",
|
|
1574
|
+
content: match[2] || "",
|
|
1575
|
+
// URL
|
|
1576
|
+
raw: match[0] || "",
|
|
1577
|
+
attributes: {
|
|
1578
|
+
provider: match[1] || "generic",
|
|
1579
|
+
url: match[2] || "",
|
|
1580
|
+
options: match[3] || ""
|
|
1581
|
+
}
|
|
1582
|
+
};
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
],
|
|
1586
|
+
renderRules: [
|
|
1587
|
+
{
|
|
1588
|
+
type: "embed",
|
|
1589
|
+
render: (token) => {
|
|
1590
|
+
const provider = token.attributes?.provider || "generic";
|
|
1591
|
+
const url = token.attributes?.url || "";
|
|
1592
|
+
const options = token.attributes?.options || "";
|
|
1593
|
+
return renderEmbed(provider, url, options);
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
]
|
|
1597
|
+
};
|
|
1598
|
+
function renderEmbed(provider, url, options) {
|
|
1599
|
+
const baseClasses = "rounded-lg border bg-card text-card-foreground shadow-sm mb-6 overflow-hidden";
|
|
1600
|
+
const parsedOptions = parseOptions(options);
|
|
1601
|
+
switch (provider.toLowerCase()) {
|
|
1602
|
+
case "youtube":
|
|
1603
|
+
return renderYouTubeEmbed(url, parsedOptions, baseClasses);
|
|
1604
|
+
case "codepen":
|
|
1605
|
+
return renderCodePenEmbed(url, parsedOptions, baseClasses);
|
|
1606
|
+
case "figma":
|
|
1607
|
+
return renderFigmaEmbed(url, parsedOptions, baseClasses);
|
|
1608
|
+
case "twitter":
|
|
1609
|
+
case "tweet":
|
|
1610
|
+
return renderTwitterEmbed(url, parsedOptions, baseClasses);
|
|
1611
|
+
case "github":
|
|
1612
|
+
return renderGitHubEmbed(url, parsedOptions, baseClasses);
|
|
1613
|
+
case "vimeo":
|
|
1614
|
+
return renderVimeoEmbed(url, parsedOptions, baseClasses);
|
|
1615
|
+
case "spotify":
|
|
1616
|
+
return renderSpotifyEmbed(url, parsedOptions, baseClasses);
|
|
1617
|
+
case "codesandbox":
|
|
1618
|
+
return renderCodeSandboxEmbed(url, parsedOptions, baseClasses);
|
|
1619
|
+
default:
|
|
1620
|
+
return renderGenericEmbed(url, parsedOptions, baseClasses);
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
function renderYouTubeEmbed(url, options, classes) {
|
|
1624
|
+
const videoId = extractYouTubeId(url);
|
|
1625
|
+
if (!videoId) {
|
|
1626
|
+
return createErrorEmbed("Invalid YouTube URL", url, classes);
|
|
1627
|
+
}
|
|
1628
|
+
const params = new URLSearchParams();
|
|
1629
|
+
if (options.autoplay === "1") params.set("autoplay", "1");
|
|
1630
|
+
if (options.mute === "1") params.set("mute", "1");
|
|
1631
|
+
if (options.loop === "1") {
|
|
1632
|
+
params.set("loop", "1");
|
|
1633
|
+
params.set("playlist", videoId);
|
|
1634
|
+
}
|
|
1635
|
+
if (options.controls === "0") params.set("controls", "0");
|
|
1636
|
+
if (options.start) params.set("start", options.start);
|
|
1637
|
+
params.set("rel", "0");
|
|
1638
|
+
params.set("modestbranding", "1");
|
|
1639
|
+
const embedUrl = `https://www.youtube.com/embed/${videoId}?${params.toString()}`;
|
|
1640
|
+
return `<div class="${classes}">
|
|
1641
|
+
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
|
|
1642
|
+
<iframe
|
|
1643
|
+
style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 0;"
|
|
1644
|
+
src="${embedUrl}"
|
|
1645
|
+
title="YouTube video player"
|
|
1646
|
+
frameborder="0"
|
|
1647
|
+
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
|
1648
|
+
allowfullscreen>
|
|
1649
|
+
</iframe>
|
|
1650
|
+
</div>
|
|
1651
|
+
</div>`;
|
|
1652
|
+
}
|
|
1653
|
+
function renderCodePenEmbed(url, options, classes) {
|
|
1654
|
+
const match = url.match(/codepen\.io\/([^\/]+)\/(?:pen|embed)\/([^\/\?#]+)/);
|
|
1655
|
+
if (!match) {
|
|
1656
|
+
return createErrorEmbed("Invalid CodePen URL", url, classes);
|
|
1657
|
+
}
|
|
1658
|
+
const [, user, penId] = match;
|
|
1659
|
+
const height = options.height || "400";
|
|
1660
|
+
const theme = options.theme === "light" ? "light" : "dark";
|
|
1661
|
+
const defaultTab = options.tab || "result";
|
|
1662
|
+
const embedParams = new URLSearchParams({
|
|
1663
|
+
"default-tab": defaultTab,
|
|
1664
|
+
"theme-id": theme,
|
|
1665
|
+
"editable": "true"
|
|
1666
|
+
});
|
|
1667
|
+
const embedUrl = `https://codepen.io/${user}/embed/${penId}?${embedParams.toString()}`;
|
|
1668
|
+
return `<div class="${classes}">
|
|
1669
|
+
<iframe
|
|
1670
|
+
height="${height}"
|
|
1671
|
+
style="width: 100%; border: 0;"
|
|
1672
|
+
scrolling="no"
|
|
1673
|
+
title="CodePen Embed - ${penId}"
|
|
1674
|
+
src="${embedUrl}"
|
|
1675
|
+
frameborder="0"
|
|
1676
|
+
loading="lazy"
|
|
1677
|
+
allowtransparency="true"
|
|
1678
|
+
allowfullscreen="true">
|
|
1679
|
+
</iframe>
|
|
1680
|
+
</div>`;
|
|
1681
|
+
}
|
|
1682
|
+
function renderVimeoEmbed(url, options, classes) {
|
|
1683
|
+
const videoId = extractVimeoId(url);
|
|
1684
|
+
if (!videoId) {
|
|
1685
|
+
return createErrorEmbed("Invalid Vimeo URL", url, classes);
|
|
1686
|
+
}
|
|
1687
|
+
const params = new URLSearchParams();
|
|
1688
|
+
if (options.autoplay === "1") params.set("autoplay", "1");
|
|
1689
|
+
if (options.mute === "1") params.set("muted", "1");
|
|
1690
|
+
if (options.loop === "1") params.set("loop", "1");
|
|
1691
|
+
const embedUrl = `https://player.vimeo.com/video/${videoId}?${params.toString()}`;
|
|
1692
|
+
return `<div class="${classes}">
|
|
1693
|
+
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
|
|
1694
|
+
<iframe
|
|
1695
|
+
style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 0;"
|
|
1696
|
+
src="${embedUrl}"
|
|
1697
|
+
title="Vimeo video player"
|
|
1698
|
+
frameborder="0"
|
|
1699
|
+
allow="autoplay; fullscreen; picture-in-picture"
|
|
1700
|
+
allowfullscreen>
|
|
1701
|
+
</iframe>
|
|
1702
|
+
</div>
|
|
1703
|
+
</div>`;
|
|
1704
|
+
}
|
|
1705
|
+
function renderSpotifyEmbed(url, options, classes) {
|
|
1706
|
+
const embedUrl = url.replace("open.spotify.com", "open.spotify.com/embed");
|
|
1707
|
+
const height = options.height || "380";
|
|
1708
|
+
return `<div class="${classes}">
|
|
1709
|
+
<iframe
|
|
1710
|
+
style="border-radius: 12px;"
|
|
1711
|
+
src="${embedUrl}"
|
|
1712
|
+
width="100%"
|
|
1713
|
+
height="${height}"
|
|
1714
|
+
frameborder="0"
|
|
1715
|
+
allowfullscreen=""
|
|
1716
|
+
allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"
|
|
1717
|
+
loading="lazy">
|
|
1718
|
+
</iframe>
|
|
1719
|
+
</div>`;
|
|
1720
|
+
}
|
|
1721
|
+
function renderCodeSandboxEmbed(url, options, classes) {
|
|
1722
|
+
let embedUrl = url;
|
|
1723
|
+
if (url.includes("/s/")) {
|
|
1724
|
+
embedUrl = url.replace("/s/", "/embed/");
|
|
1725
|
+
}
|
|
1726
|
+
const height = options.height || "500";
|
|
1727
|
+
const view = options.view || "preview";
|
|
1728
|
+
if (!embedUrl.includes("?")) {
|
|
1729
|
+
embedUrl += `?view=${view}`;
|
|
1730
|
+
}
|
|
1731
|
+
return `<div class="${classes}">
|
|
1732
|
+
<iframe
|
|
1733
|
+
src="${embedUrl}"
|
|
1734
|
+
style="width: 100%; height: ${height}px; border: 0; border-radius: 4px; overflow: hidden;"
|
|
1735
|
+
title="CodeSandbox Embed"
|
|
1736
|
+
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
|
|
1737
|
+
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts">
|
|
1738
|
+
</iframe>
|
|
1739
|
+
</div>`;
|
|
1740
|
+
}
|
|
1741
|
+
function renderFigmaEmbed(url, options, classes) {
|
|
1742
|
+
const embedUrl = `https://www.figma.com/embed?embed_host=share&url=${encodeURIComponent(url)}`;
|
|
1743
|
+
const height = options.height || "450";
|
|
1744
|
+
return `<div class="${classes}">
|
|
1745
|
+
<iframe
|
|
1746
|
+
style="border: none;"
|
|
1747
|
+
width="100%"
|
|
1748
|
+
height="${height}"
|
|
1749
|
+
src="${embedUrl}"
|
|
1750
|
+
allowfullscreen>
|
|
1751
|
+
</iframe>
|
|
1752
|
+
</div>`;
|
|
1753
|
+
}
|
|
1754
|
+
function renderTwitterEmbed(url, _options, classes) {
|
|
1755
|
+
return `<div class="${classes}">
|
|
1756
|
+
<div class="p-4">
|
|
1757
|
+
<div class="flex items-center gap-3 mb-3">
|
|
1758
|
+
<svg class="w-6 h-6 fill-current text-blue-500" viewBox="0 0 24 24">
|
|
1759
|
+
<path d="M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723c-.951.555-2.005.959-3.127 1.184a4.92 4.92 0 00-8.384 4.482C7.69 8.095 4.067 6.13 1.64 3.162a4.822 4.822 0 00-.666 2.475c0 1.71.87 3.213 2.188 4.096a4.904 4.904 0 01-2.228-.616v.06a4.923 4.923 0 003.946 4.827 4.996 4.996 0 01-2.212.085 4.936 4.936 0 004.604 3.417 9.867 9.867 0 01-6.102 2.105c-.39 0-.779-.023-1.17-.067a13.995 13.995 0 007.557 2.209c9.053 0 13.998-7.496 13.998-13.985 0-.21 0-.42-.015-.63A9.935 9.935 0 0024 4.59z"/>
|
|
1760
|
+
</svg>
|
|
1761
|
+
<div>
|
|
1762
|
+
<div class="font-semibold text-foreground">Twitter Post</div>
|
|
1763
|
+
<div class="text-sm text-muted-foreground">External Link</div>
|
|
1764
|
+
</div>
|
|
1765
|
+
</div>
|
|
1766
|
+
<a href="${url}" target="_blank"
|
|
1767
|
+
class="inline-flex items-center gap-2 text-primary hover:text-primary/80 font-medium transition-colors">
|
|
1768
|
+
View on Twitter
|
|
1769
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
1770
|
+
<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"/>
|
|
1771
|
+
</svg>
|
|
1772
|
+
</a>
|
|
1773
|
+
</div>
|
|
1774
|
+
</div>`;
|
|
1775
|
+
}
|
|
1776
|
+
function renderGitHubEmbed(url, _options, classes) {
|
|
1777
|
+
const parts = url.replace("https://github.com/", "").split("/");
|
|
1778
|
+
const owner = parts[0];
|
|
1779
|
+
const repo = parts[1];
|
|
1780
|
+
if (!owner || !repo) {
|
|
1781
|
+
return createErrorEmbed("Invalid GitHub URL", url, classes);
|
|
1782
|
+
}
|
|
1783
|
+
return `<div class="${classes}">
|
|
1784
|
+
<div class="p-4">
|
|
1785
|
+
<div class="flex items-center gap-3 mb-3">
|
|
1786
|
+
<svg class="w-6 h-6 fill-current" viewBox="0 0 24 24">
|
|
1787
|
+
<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"/>
|
|
1788
|
+
</svg>
|
|
1789
|
+
<div>
|
|
1790
|
+
<div class="font-semibold text-foreground text-lg">${owner}/${repo}</div>
|
|
1791
|
+
<div class="text-sm text-muted-foreground">GitHub Repository</div>
|
|
1792
|
+
</div>
|
|
1793
|
+
</div>
|
|
1794
|
+
<a href="${url}" target="_blank"
|
|
1795
|
+
class="inline-flex items-center gap-2 text-primary hover:text-primary/80 font-medium transition-colors">
|
|
1796
|
+
View on GitHub
|
|
1797
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
1798
|
+
<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"/>
|
|
1799
|
+
</svg>
|
|
1800
|
+
</a>
|
|
1801
|
+
</div>
|
|
1802
|
+
</div>`;
|
|
1803
|
+
}
|
|
1804
|
+
function renderGenericEmbed(url, _options, classes) {
|
|
1805
|
+
const domain = extractDomain(url);
|
|
1806
|
+
return `<div class="${classes}">
|
|
1807
|
+
<div class="p-4">
|
|
1808
|
+
<div class="flex items-center gap-3 mb-3">
|
|
1809
|
+
<div class="w-10 h-10 rounded-lg bg-muted flex items-center justify-center">
|
|
1810
|
+
<svg class="w-5 h-5 text-muted-foreground" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
1811
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"/>
|
|
1812
|
+
</svg>
|
|
1813
|
+
</div>
|
|
1814
|
+
<div>
|
|
1815
|
+
<div class="font-semibold text-foreground">External Link</div>
|
|
1816
|
+
<div class="text-sm text-muted-foreground">${domain}</div>
|
|
1817
|
+
</div>
|
|
1818
|
+
</div>
|
|
1819
|
+
<a href="${url}" target="_blank"
|
|
1820
|
+
class="inline-flex items-center gap-2 text-primary hover:text-primary/80 font-medium transition-colors break-all">
|
|
1821
|
+
${url}
|
|
1822
|
+
<svg class="w-4 h-4 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
1823
|
+
<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"/>
|
|
1824
|
+
</svg>
|
|
1825
|
+
</a>
|
|
1826
|
+
</div>
|
|
1827
|
+
</div>`;
|
|
1828
|
+
}
|
|
1829
|
+
function createErrorEmbed(error, url, classes) {
|
|
1830
|
+
return `<div class="${classes}">
|
|
1831
|
+
<div class="p-4 text-destructive">
|
|
1832
|
+
<div class="font-medium flex items-center gap-2">
|
|
1833
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
1834
|
+
<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"/>
|
|
1835
|
+
</svg>
|
|
1836
|
+
${error}
|
|
1837
|
+
</div>
|
|
1838
|
+
<div class="text-sm text-muted-foreground mt-1 break-all">${url}</div>
|
|
1839
|
+
</div>
|
|
1840
|
+
</div>`;
|
|
1841
|
+
}
|
|
1842
|
+
function extractYouTubeId(url) {
|
|
1843
|
+
const patterns = [
|
|
1844
|
+
/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/|youtube\.com\/shorts\/)([^&\n?#]+)/,
|
|
1845
|
+
/youtube\.com\/watch\?.*v=([^&\n?#]+)/
|
|
1846
|
+
];
|
|
1847
|
+
for (const pattern of patterns) {
|
|
1848
|
+
const match = url.match(pattern);
|
|
1849
|
+
if (match) return match[1] || null;
|
|
1850
|
+
}
|
|
1851
|
+
return null;
|
|
1852
|
+
}
|
|
1853
|
+
function extractVimeoId(url) {
|
|
1854
|
+
const match = url.match(/vimeo\.com\/(?:.*\/)?(\d+)/);
|
|
1855
|
+
return match?.[1] ?? null;
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
// src/engine.ts
|
|
1859
|
+
var ChangerawrMarkdown = class {
|
|
1860
|
+
constructor(config) {
|
|
1861
|
+
this.extensions = /* @__PURE__ */ new Map();
|
|
1862
|
+
this.parseTime = 0;
|
|
1863
|
+
this.renderTime = 0;
|
|
1864
|
+
this.lastTokenCount = 0;
|
|
1865
|
+
this.parser = new MarkdownParser(config?.parser);
|
|
1866
|
+
this.renderer = new MarkdownRenderer(config?.renderer);
|
|
1867
|
+
this.parseCache = new LRUCache(100);
|
|
1868
|
+
this.renderCache = new LRUCache(100);
|
|
1869
|
+
this.registerCoreExtensions();
|
|
1870
|
+
this.registerFeatureExtensions();
|
|
1871
|
+
if (config?.extensions) {
|
|
1872
|
+
config.extensions.forEach((extension) => {
|
|
1873
|
+
this.registerExtension(extension);
|
|
1874
|
+
});
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
registerFeatureExtensions() {
|
|
1878
|
+
this.registerExtension(AlertExtension);
|
|
1879
|
+
this.registerExtension(ButtonExtension);
|
|
1880
|
+
this.registerExtension(EmbedExtension);
|
|
1881
|
+
}
|
|
1882
|
+
registerCoreExtensions() {
|
|
1883
|
+
CoreExtensions.forEach((extension) => {
|
|
1884
|
+
this.registerExtension(extension);
|
|
1885
|
+
});
|
|
1886
|
+
}
|
|
1887
|
+
registerExtension(extension) {
|
|
1888
|
+
try {
|
|
1889
|
+
if (!extension.name || typeof extension.name !== "string") {
|
|
1890
|
+
throw new Error("Extension must have a valid name");
|
|
1891
|
+
}
|
|
1892
|
+
if (!Array.isArray(extension.parseRules)) {
|
|
1893
|
+
throw new Error("Extension must have parseRules array");
|
|
1894
|
+
}
|
|
1895
|
+
if (!Array.isArray(extension.renderRules)) {
|
|
1896
|
+
throw new Error("Extension must have renderRules array");
|
|
1897
|
+
}
|
|
1898
|
+
extension.parseRules.forEach((rule, index) => {
|
|
1899
|
+
if (!rule.name || typeof rule.name !== "string") {
|
|
1900
|
+
throw new Error(`Parse rule ${index} must have a valid name`);
|
|
1901
|
+
}
|
|
1902
|
+
if (!rule.pattern || !(rule.pattern instanceof RegExp)) {
|
|
1903
|
+
throw new Error(`Parse rule ${index} must have a valid RegExp pattern`);
|
|
1904
|
+
}
|
|
1905
|
+
if (!rule.render || typeof rule.render !== "function") {
|
|
1906
|
+
throw new Error(`Parse rule ${index} must have a valid render function`);
|
|
1907
|
+
}
|
|
1908
|
+
});
|
|
1909
|
+
extension.renderRules.forEach((rule, index) => {
|
|
1910
|
+
if (!rule.type || typeof rule.type !== "string") {
|
|
1911
|
+
throw new Error(`Render rule ${index} must have a valid type`);
|
|
1912
|
+
}
|
|
1913
|
+
if (!rule.render || typeof rule.render !== "function") {
|
|
1914
|
+
throw new Error(`Render rule ${index} must have a valid render function`);
|
|
1915
|
+
}
|
|
1916
|
+
});
|
|
1917
|
+
this.extensions.set(extension.name, extension);
|
|
1918
|
+
extension.parseRules.forEach((rule) => {
|
|
1919
|
+
this.parser.addRule(rule);
|
|
1920
|
+
});
|
|
1921
|
+
extension.renderRules.forEach((rule) => {
|
|
1922
|
+
this.renderer.addRule(rule);
|
|
1923
|
+
});
|
|
1924
|
+
return {
|
|
1925
|
+
success: true,
|
|
1926
|
+
extensionName: extension.name
|
|
1927
|
+
};
|
|
1928
|
+
} catch (error) {
|
|
1929
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1930
|
+
return {
|
|
1931
|
+
success: false,
|
|
1932
|
+
extensionName: extension.name,
|
|
1933
|
+
error: errorMessage
|
|
1934
|
+
};
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
unregisterExtension(name) {
|
|
1938
|
+
const extension = this.extensions.get(name);
|
|
1939
|
+
if (!extension) {
|
|
1940
|
+
return false;
|
|
1941
|
+
}
|
|
1942
|
+
try {
|
|
1943
|
+
this.extensions.delete(name);
|
|
1944
|
+
this.rebuildParserAndRenderer();
|
|
1945
|
+
return true;
|
|
1946
|
+
} catch {
|
|
1947
|
+
return false;
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
parse(markdown2) {
|
|
1951
|
+
const cacheKey = hashContent(markdown2);
|
|
1952
|
+
const cached = this.parseCache.get(cacheKey);
|
|
1953
|
+
if (cached) {
|
|
1954
|
+
this.lastTokenCount = cached.length;
|
|
1955
|
+
return cached;
|
|
1956
|
+
}
|
|
1957
|
+
const startTime = performance.now();
|
|
1958
|
+
const tokens = this.parser.parse(markdown2);
|
|
1959
|
+
this.parseTime = performance.now() - startTime;
|
|
1960
|
+
this.lastTokenCount = tokens.length;
|
|
1961
|
+
this.parseCache.set(cacheKey, tokens);
|
|
1962
|
+
return tokens;
|
|
1963
|
+
}
|
|
1964
|
+
render(tokens, cacheKey) {
|
|
1965
|
+
if (cacheKey) {
|
|
1966
|
+
const cached = this.renderCache.get(cacheKey);
|
|
1967
|
+
if (cached) {
|
|
1968
|
+
return cached;
|
|
1969
|
+
}
|
|
1970
|
+
}
|
|
1971
|
+
const startTime = performance.now();
|
|
1972
|
+
const html = this.renderer.render(tokens);
|
|
1973
|
+
this.renderTime = performance.now() - startTime;
|
|
1974
|
+
if (cacheKey) {
|
|
1975
|
+
this.renderCache.set(cacheKey, html);
|
|
1976
|
+
}
|
|
1977
|
+
return html;
|
|
1978
|
+
}
|
|
1979
|
+
toHtml(markdown2) {
|
|
1980
|
+
const cacheKey = hashContent(markdown2);
|
|
1981
|
+
const cachedHtml = this.renderCache.get(cacheKey);
|
|
1982
|
+
if (cachedHtml) {
|
|
1983
|
+
return cachedHtml;
|
|
1984
|
+
}
|
|
1985
|
+
const tokens = this.parse(markdown2);
|
|
1986
|
+
return this.render(tokens, cacheKey);
|
|
1987
|
+
}
|
|
1988
|
+
/**
|
|
1989
|
+
* Render markdown with performance metrics
|
|
1990
|
+
*/
|
|
1991
|
+
toHtmlWithMetrics(markdown2) {
|
|
1992
|
+
const startTotal = performance.now();
|
|
1993
|
+
const parseCacheKey = hashContent(markdown2);
|
|
1994
|
+
const parseCacheHit = this.parseCache.has(parseCacheKey);
|
|
1995
|
+
const html = this.toHtml(markdown2);
|
|
1996
|
+
const totalTime = performance.now() - startTotal;
|
|
1997
|
+
const metrics = {
|
|
1998
|
+
inputSize: markdown2.length,
|
|
1999
|
+
parseTime: this.parseTime,
|
|
2000
|
+
renderTime: this.renderTime,
|
|
2001
|
+
totalTime,
|
|
2002
|
+
tokenCount: this.lastTokenCount,
|
|
2003
|
+
cacheHit: parseCacheHit
|
|
2004
|
+
};
|
|
2005
|
+
return { html, metrics };
|
|
2006
|
+
}
|
|
2007
|
+
/**
|
|
2008
|
+
* Stream-render large documents in chunks for better performance
|
|
2009
|
+
*/
|
|
2010
|
+
async toHtmlStreamed(markdown2, options = {}) {
|
|
2011
|
+
const chunkSize = options.chunkSize || 50;
|
|
2012
|
+
const tokens = this.parse(markdown2);
|
|
2013
|
+
const totalTokens = tokens.length;
|
|
2014
|
+
const chunks = [];
|
|
2015
|
+
for (let i = 0; i < tokens.length; i += chunkSize) {
|
|
2016
|
+
const chunkTokens = tokens.slice(i, Math.min(i + chunkSize, tokens.length));
|
|
2017
|
+
const chunkHtml = this.render(chunkTokens);
|
|
2018
|
+
chunks.push(chunkHtml);
|
|
2019
|
+
if (options.onChunk) {
|
|
2020
|
+
options.onChunk({
|
|
2021
|
+
html: chunkHtml,
|
|
2022
|
+
progress: Math.min(i + chunkSize, tokens.length) / totalTokens
|
|
2023
|
+
});
|
|
2024
|
+
}
|
|
2025
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
2026
|
+
}
|
|
2027
|
+
return chunks.join("");
|
|
2028
|
+
}
|
|
2029
|
+
getExtensions() {
|
|
2030
|
+
return Array.from(this.extensions.keys());
|
|
2031
|
+
}
|
|
2032
|
+
hasExtension(name) {
|
|
2033
|
+
return this.extensions.has(name);
|
|
2034
|
+
}
|
|
2035
|
+
getWarnings() {
|
|
2036
|
+
return [...this.parser.getWarnings(), ...this.renderer.getWarnings()];
|
|
2037
|
+
}
|
|
2038
|
+
getDebugInfo() {
|
|
2039
|
+
return {
|
|
2040
|
+
warnings: this.getWarnings(),
|
|
2041
|
+
parseTime: this.parseTime,
|
|
2042
|
+
renderTime: this.renderTime,
|
|
2043
|
+
tokenCount: this.lastTokenCount,
|
|
2044
|
+
iterationCount: 0
|
|
2045
|
+
};
|
|
2046
|
+
}
|
|
2047
|
+
getPerformanceMetrics() {
|
|
2048
|
+
return {
|
|
2049
|
+
parseTime: this.parseTime,
|
|
2050
|
+
renderTime: this.renderTime,
|
|
2051
|
+
totalTime: this.parseTime + this.renderTime,
|
|
2052
|
+
tokenCount: this.lastTokenCount
|
|
2053
|
+
};
|
|
2054
|
+
}
|
|
2055
|
+
/**
|
|
2056
|
+
* Get cache statistics
|
|
2057
|
+
*/
|
|
2058
|
+
getCacheStats() {
|
|
2059
|
+
return {
|
|
2060
|
+
parse: this.parseCache.getStats(),
|
|
2061
|
+
render: this.renderCache.getStats()
|
|
2062
|
+
};
|
|
2063
|
+
}
|
|
2064
|
+
/**
|
|
2065
|
+
* Clear all caches
|
|
2066
|
+
*/
|
|
2067
|
+
clearCaches() {
|
|
2068
|
+
this.parseCache.clear();
|
|
2069
|
+
this.renderCache.clear();
|
|
2070
|
+
}
|
|
2071
|
+
/**
|
|
2072
|
+
* Update cache capacity
|
|
2073
|
+
*/
|
|
2074
|
+
setCacheSize(size) {
|
|
2075
|
+
this.parseCache.setCapacity(size);
|
|
2076
|
+
this.renderCache.setCapacity(size);
|
|
2077
|
+
}
|
|
2078
|
+
rebuildParserAndRenderer() {
|
|
2079
|
+
const parserConfig = this.parser.getConfig();
|
|
2080
|
+
const rendererConfig = this.renderer.getConfig();
|
|
2081
|
+
this.parser = new MarkdownParser(parserConfig);
|
|
2082
|
+
this.renderer = new MarkdownRenderer(rendererConfig);
|
|
2083
|
+
this.parseCache.clear();
|
|
2084
|
+
this.renderCache.clear();
|
|
2085
|
+
const extensionsToRegister = Array.from(this.extensions.values());
|
|
2086
|
+
const featureExtensions = extensionsToRegister.filter(
|
|
2087
|
+
(ext) => ["alert", "button", "embed"].includes(ext.name)
|
|
2088
|
+
);
|
|
2089
|
+
const coreExtensions = extensionsToRegister.filter(
|
|
2090
|
+
(ext) => ["text", "heading", "bold", "italic", "code", "codeblock", "link", "image", "list", "task-list", "blockquote", "hr", "paragraph", "line-break"].includes(ext.name)
|
|
2091
|
+
);
|
|
2092
|
+
const customExtensions = extensionsToRegister.filter(
|
|
2093
|
+
(ext) => !["alert", "button", "embed", "text", "heading", "bold", "italic", "code", "codeblock", "link", "image", "list", "task-list", "blockquote", "hr", "paragraph", "line-break"].includes(ext.name)
|
|
2094
|
+
);
|
|
2095
|
+
[...featureExtensions, ...coreExtensions, ...customExtensions].forEach((extension) => {
|
|
2096
|
+
extension.parseRules.forEach((rule) => {
|
|
2097
|
+
this.parser.addRule(rule);
|
|
2098
|
+
});
|
|
2099
|
+
extension.renderRules.forEach((rule) => {
|
|
2100
|
+
this.renderer.addRule(rule);
|
|
2101
|
+
});
|
|
2102
|
+
});
|
|
2103
|
+
}
|
|
2104
|
+
};
|
|
2105
|
+
var markdown = new ChangerawrMarkdown();
|
|
2106
|
+
|
|
2107
|
+
// src/outputs/html.ts
|
|
2108
|
+
function renderToHTML(markdown2, config) {
|
|
2109
|
+
const engine = new ChangerawrMarkdown({
|
|
2110
|
+
...config,
|
|
2111
|
+
renderer: {
|
|
2112
|
+
format: "html",
|
|
2113
|
+
sanitize: true,
|
|
2114
|
+
allowUnsafeHtml: false
|
|
2115
|
+
}
|
|
2116
|
+
});
|
|
2117
|
+
return engine.toHtml(markdown2);
|
|
2118
|
+
}
|
|
2119
|
+
function renderToHTMLWithConfig(markdown2, rendererConfig) {
|
|
2120
|
+
const engine = new ChangerawrMarkdown({
|
|
2121
|
+
renderer: {
|
|
2122
|
+
format: "html",
|
|
2123
|
+
...rendererConfig
|
|
2124
|
+
}
|
|
2125
|
+
});
|
|
2126
|
+
return engine.toHtml(markdown2);
|
|
2127
|
+
}
|
|
2128
|
+
|
|
2129
|
+
// src/outputs/tailwind.ts
|
|
2130
|
+
function renderToTailwind(markdown2, config) {
|
|
2131
|
+
const engine = new ChangerawrMarkdown({
|
|
2132
|
+
...config,
|
|
2133
|
+
renderer: {
|
|
2134
|
+
format: "tailwind",
|
|
2135
|
+
sanitize: true,
|
|
2136
|
+
allowUnsafeHtml: false
|
|
2137
|
+
}
|
|
2138
|
+
});
|
|
2139
|
+
return engine.toHtml(markdown2);
|
|
2140
|
+
}
|
|
2141
|
+
function renderToTailwindWithConfig(markdown2, rendererConfig) {
|
|
2142
|
+
const engine = new ChangerawrMarkdown({
|
|
2143
|
+
renderer: {
|
|
2144
|
+
format: "tailwind",
|
|
2145
|
+
...rendererConfig
|
|
2146
|
+
}
|
|
2147
|
+
});
|
|
2148
|
+
return engine.toHtml(markdown2);
|
|
2149
|
+
}
|
|
2150
|
+
|
|
2151
|
+
// src/astro/index.ts
|
|
2152
|
+
function renderMarkdownForAstro(content, options = {}) {
|
|
2153
|
+
const { format = "html", config, extensions } = options;
|
|
2154
|
+
const engineConfig = {
|
|
2155
|
+
renderer: {
|
|
2156
|
+
sanitize: true,
|
|
2157
|
+
...config?.renderer ?? {},
|
|
2158
|
+
format
|
|
2159
|
+
}
|
|
2160
|
+
};
|
|
2161
|
+
if (config?.parser) engineConfig.parser = config.parser;
|
|
2162
|
+
const engine = new ChangerawrMarkdown(engineConfig);
|
|
2163
|
+
if (extensions) {
|
|
2164
|
+
extensions.forEach((ext) => engine.registerExtension(ext));
|
|
2165
|
+
}
|
|
2166
|
+
return engine.toHtml(content);
|
|
2167
|
+
}
|
|
2168
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
2169
|
+
0 && (module.exports = {
|
|
2170
|
+
ChangerawrMarkdown,
|
|
2171
|
+
renderMarkdownForAstro,
|
|
2172
|
+
renderToHTML,
|
|
2173
|
+
renderToHTMLWithConfig,
|
|
2174
|
+
renderToTailwind,
|
|
2175
|
+
renderToTailwindWithConfig
|
|
2176
|
+
});
|
|
2177
|
+
//# sourceMappingURL=index.js.map
|