@changerawr/markdown 1.0.4 → 1.1.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.
@@ -3,39 +3,56 @@ var MarkdownParser = class {
3
3
  constructor(config) {
4
4
  this.rules = [];
5
5
  this.warnings = [];
6
- this.config = config || {};
6
+ this.config = {
7
+ debugMode: false,
8
+ maxIterations: 1e4,
9
+ validateMarkdown: false,
10
+ ...config
11
+ };
7
12
  }
8
13
  addRule(rule) {
9
14
  this.rules.push(rule);
10
15
  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;
16
+ const aFeatureExtension = ["alert", "button", "embed"].includes(a.name);
17
+ const bFeatureExtension = ["alert", "button", "embed"].includes(b.name);
18
+ if (aFeatureExtension && !bFeatureExtension) return -1;
19
+ if (!aFeatureExtension && bFeatureExtension) return 1;
20
+ const aCoreExtension = ["text", "heading", "bold", "italic", "code", "codeblock", "link", "image", "list", "task-list", "blockquote", "hr", "paragraph", "line-break"].includes(a.name);
21
+ const bCoreExtension = ["text", "heading", "bold", "italic", "code", "codeblock", "link", "image", "list", "task-list", "blockquote", "hr", "paragraph", "line-break"].includes(b.name);
22
+ if (aCoreExtension && !bCoreExtension) return -1;
23
+ if (!aCoreExtension && bCoreExtension) return 1;
24
+ if (a.name === "image" && b.name === "link") return -1;
25
+ if (a.name === "link" && b.name === "image") return 1;
26
+ if (a.name === "task-item" && b.name === "list-item") return -1;
27
+ if (a.name === "list-item" && b.name === "task-item") return 1;
28
+ if (a.name === "codeblock" && b.name === "code") return -1;
29
+ if (a.name === "code" && b.name === "codeblock") return 1;
30
+ if (a.name === "bold" && b.name === "italic") return -1;
31
+ if (a.name === "italic" && b.name === "bold") return 1;
17
32
  return a.name.localeCompare(b.name);
18
33
  });
19
34
  }
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
35
  hasRule(name) {
29
36
  return this.rules.some((rule) => rule.name === name);
30
37
  }
31
38
  parse(markdown2) {
32
39
  this.warnings = [];
33
- this.setupDefaultRulesIfEmpty();
40
+ if (!markdown2.trim()) {
41
+ return [];
42
+ }
43
+ if (this.rules.length === 0) {
44
+ this.warnings.push("No parse rules registered - consider using CoreExtensions");
45
+ return [{
46
+ type: "text",
47
+ content: markdown2,
48
+ raw: markdown2
49
+ }];
50
+ }
34
51
  const processedMarkdown = this.preprocessMarkdown(markdown2);
35
52
  const tokens = [];
36
53
  let remaining = processedMarkdown;
37
54
  let iterationCount = 0;
38
- const maxIterations = this.config.maxIterations || markdown2.length * 2;
55
+ const maxIterations = this.config.maxIterations || 1e4;
39
56
  while (remaining.length > 0 && iterationCount < maxIterations) {
40
57
  iterationCount++;
41
58
  let matched = false;
@@ -124,9 +141,6 @@ var MarkdownParser = class {
124
141
  clearWarnings() {
125
142
  this.warnings = [];
126
143
  }
127
- getIterationCount() {
128
- return 0;
129
- }
130
144
  preprocessMarkdown(markdown2) {
131
145
  if (this.config.validateMarkdown) {
132
146
  this.validateMarkdown(markdown2);
@@ -181,161 +195,6 @@ var MarkdownParser = class {
181
195
  }
182
196
  return processed;
183
197
  }
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
198
  };
340
199
 
341
200
  // src/utils.ts
@@ -574,7 +433,6 @@ var MarkdownRenderer = class {
574
433
  debugMode: false,
575
434
  ...config
576
435
  };
577
- this.setupDefaultRules();
578
436
  }
579
437
  addRule(rule) {
580
438
  this.rules.set(rule.type, rule);
@@ -584,28 +442,20 @@ var MarkdownRenderer = class {
584
442
  }
585
443
  render(tokens) {
586
444
  this.warnings = [];
587
- const htmlParts = tokens.map((token) => this.renderToken(token));
445
+ const tokensWithFormat = tokens.map((token) => ({
446
+ ...token,
447
+ attributes: {
448
+ ...token.attributes,
449
+ format: this.config.format
450
+ }
451
+ }));
452
+ const htmlParts = tokensWithFormat.map((token) => this.renderToken(token));
588
453
  const combinedHtml = htmlParts.join("");
589
454
  if (this.config.sanitize && !this.config.allowUnsafeHtml) {
590
455
  return sanitizeHtml(combinedHtml);
591
456
  }
592
457
  return combinedHtml;
593
458
  }
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
459
  renderToken(token) {
610
460
  const rule = this.rules.get(token.type);
611
461
  if (rule) {
@@ -617,9 +467,6 @@ var MarkdownRenderer = class {
617
467
  return this.createErrorBlock(`Render error for ${token.type}: ${errorMessage}`);
618
468
  }
619
469
  }
620
- if (token.type === "text") {
621
- return escapeHtml(token.content || token.raw || "");
622
- }
623
470
  if (this.config.debugMode) {
624
471
  return this.createDebugBlock(token);
625
472
  }
@@ -642,20 +489,239 @@ var MarkdownRenderer = class {
642
489
  <strong>Content:</strong> ${escapeHtml(token.content || token.raw || "")}
643
490
  </div>`;
644
491
  }
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({
492
+ return `<div class="bg-yellow-100 border border-yellow-300 text-yellow-800 p-2 rounded text-sm mb-2">
493
+ <strong>Unknown token type:</strong> ${escapeHtml(token.type)}<br>
494
+ <strong>Content:</strong> ${escapeHtml(token.content || token.raw || "")}
495
+ </div>`;
496
+ }
497
+ getWarnings() {
498
+ return [...this.warnings];
499
+ }
500
+ getConfig() {
501
+ return { ...this.config };
502
+ }
503
+ updateConfig(config) {
504
+ this.config = { ...this.config, ...config };
505
+ }
506
+ setDebugMode(enabled) {
507
+ this.config.debugMode = enabled;
508
+ }
509
+ clearWarnings() {
510
+ this.warnings = [];
511
+ }
512
+ };
513
+
514
+ // src/extensions/core/blockquote.ts
515
+ var BlockquoteExtension = {
516
+ name: "blockquote",
517
+ parseRules: [
518
+ {
519
+ name: "blockquote",
520
+ pattern: /^>\s+(.+)$/m,
521
+ render: (match) => ({
522
+ type: "blockquote",
523
+ content: match[1] || "",
524
+ raw: match[0] || ""
525
+ })
526
+ }
527
+ ],
528
+ renderRules: [
529
+ {
530
+ type: "blockquote",
531
+ render: (token) => {
532
+ const content = escapeHtml(token.content);
533
+ const format = token.attributes?.format || "html";
534
+ if (format === "html") {
535
+ return `<blockquote style="border-left: 2px solid #d1d5db; padding: 8px 0 8px 16px; margin: 16px 0; font-style: italic; color: #6b7280;">${content}</blockquote>`;
536
+ }
537
+ return `<blockquote class="pl-4 py-2 border-l-2 border-border italic text-muted-foreground my-4">${content}</blockquote>`;
538
+ }
539
+ }
540
+ ]
541
+ };
542
+
543
+ // src/extensions/core/breaks.ts
544
+ var LineBreakExtension = {
545
+ name: "line-break",
546
+ parseRules: [
547
+ {
548
+ name: "hard-break-backslash",
549
+ pattern: /\\\s*\n/,
550
+ render: (match) => ({
551
+ type: "line-break",
552
+ content: "",
553
+ raw: match[0] || ""
554
+ })
555
+ },
556
+ {
557
+ name: "hard-break-spaces",
558
+ pattern: / +\n/,
559
+ render: (match) => ({
560
+ type: "line-break",
561
+ content: "",
562
+ raw: match[0] || ""
563
+ })
564
+ }
565
+ ],
566
+ renderRules: [
567
+ {
568
+ type: "line-break",
569
+ render: () => "<br>"
570
+ },
571
+ {
572
+ type: "paragraph-break",
573
+ render: () => "</p><p>"
574
+ },
575
+ {
576
+ type: "soft-break",
577
+ render: (token) => token.content || " "
578
+ }
579
+ ]
580
+ };
581
+
582
+ // src/extensions/core/code.ts
583
+ var InlineCodeExtension = {
584
+ name: "inline-code",
585
+ parseRules: [
586
+ {
587
+ name: "code",
588
+ pattern: /`([^`]+)`/,
589
+ render: (match) => ({
590
+ type: "code",
591
+ content: match[1] || "",
592
+ raw: match[0] || ""
593
+ })
594
+ }
595
+ ],
596
+ renderRules: [
597
+ {
598
+ type: "code",
599
+ render: (token) => {
600
+ const content = escapeHtml(token.content);
601
+ const format = token.attributes?.format;
602
+ if (format === "html") {
603
+ return `<code style="background-color: #f3f4f6; padding: 2px 6px; border-radius: 4px; font-family: monospace; font-size: 0.875rem;">${content}</code>`;
604
+ }
605
+ return `<code class="bg-muted px-1.5 py-0.5 rounded text-sm font-mono">${content}</code>`;
606
+ }
607
+ }
608
+ ]
609
+ };
610
+ var CodeBlockExtension = {
611
+ name: "codeblock",
612
+ parseRules: [
613
+ {
614
+ name: "codeblock",
615
+ pattern: /```(\w+)?\s*\n([\s\S]*?)\n```/,
616
+ render: (match) => ({
617
+ type: "codeblock",
618
+ content: match[2] || "",
619
+ raw: match[0] || "",
620
+ attributes: {
621
+ language: match[1] || "text"
622
+ }
623
+ })
624
+ }
625
+ ],
626
+ renderRules: [
627
+ {
628
+ type: "codeblock",
629
+ render: (token) => {
630
+ const language = token.attributes?.language || "text";
631
+ const escapedCode = escapeHtml(token.content);
632
+ const format = token.attributes?.format || "html";
633
+ if (format === "html") {
634
+ 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>`;
635
+ }
636
+ return `<pre class="bg-muted p-4 rounded-md overflow-x-auto my-4"><code class="language-${escapeHtml(language)}">${escapedCode}</code></pre>`;
637
+ }
638
+ }
639
+ ]
640
+ };
641
+
642
+ // src/extensions/core/emphasis.ts
643
+ var BoldExtension = {
644
+ name: "bold",
645
+ parseRules: [
646
+ {
647
+ name: "bold",
648
+ pattern: /\*\*((?:(?!\*\*).)+)\*\*/,
649
+ render: (match) => ({
650
+ type: "bold",
651
+ content: match[1] || "",
652
+ raw: match[0] || ""
653
+ })
654
+ }
655
+ ],
656
+ renderRules: [
657
+ {
658
+ type: "bold",
659
+ render: (token) => {
660
+ const content = escapeHtml(token.content);
661
+ const format = token.attributes?.format;
662
+ if (format === "html") {
663
+ return `<strong>${content}</strong>`;
664
+ }
665
+ return `<strong class="font-bold">${content}</strong>`;
666
+ }
667
+ }
668
+ ]
669
+ };
670
+ var ItalicExtension = {
671
+ name: "italic",
672
+ parseRules: [
673
+ {
674
+ name: "italic",
675
+ pattern: /\*((?:(?!\*).)+)\*/,
676
+ render: (match) => ({
677
+ type: "italic",
678
+ content: match[1] || "",
679
+ raw: match[0] || ""
680
+ })
681
+ }
682
+ ],
683
+ renderRules: [
684
+ {
685
+ type: "italic",
686
+ render: (token) => {
687
+ const content = escapeHtml(token.content);
688
+ const format = token.attributes?.format;
689
+ if (format === "html") {
690
+ return `<em>${content}</em>`;
691
+ }
692
+ return `<em class="italic">${content}</em>`;
693
+ }
694
+ }
695
+ ]
696
+ };
697
+
698
+ // src/extensions/core/heading.ts
699
+ var HeadingExtension = {
700
+ name: "heading",
701
+ parseRules: [
702
+ {
703
+ name: "heading",
704
+ pattern: /^(#{1,6})\s+(.+)$/m,
705
+ render: (match) => ({
706
+ type: "heading",
707
+ content: match[2]?.trim() || "",
708
+ raw: match[0] || "",
709
+ attributes: {
710
+ level: String(match[1]?.length || 1)
711
+ }
712
+ })
713
+ }
714
+ ],
715
+ renderRules: [
716
+ {
652
717
  type: "heading",
653
718
  render: (token) => {
654
719
  const level = parseInt(token.attributes?.level || "1");
655
720
  const text = token.content;
656
721
  const id = generateId(text);
657
722
  const escapedContent = escapeHtml(text);
658
- if (this.config.format === "html") {
723
+ const format = token.attributes?.format || "html";
724
+ if (format === "html") {
659
725
  return `<h${level} id="${id}">${escapedContent}</h${level}>`;
660
726
  }
661
727
  let headingClasses = "group relative flex items-center gap-2";
@@ -682,61 +748,108 @@ var MarkdownRenderer = class {
682
748
  return `<h${level} id="${id}" class="${headingClasses}">
683
749
  ${escapedContent}
684
750
  <a href="#${id}" class="opacity-0 group-hover:opacity-100 text-muted-foreground transition-opacity">
685
- <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
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"/>
687
- </svg>
751
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
752
+ <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/>
753
+ <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>
754
+ </svg>
688
755
  </a>
689
756
  </h${level}>`;
690
757
  }
691
- });
692
- this.addRule({
693
- type: "bold",
758
+ }
759
+ ]
760
+ };
761
+
762
+ // src/extensions/core/hr.ts
763
+ var HorizontalRuleExtension = {
764
+ name: "hr",
765
+ parseRules: [
766
+ {
767
+ name: "hr",
768
+ pattern: /^---$/m,
769
+ render: (match) => ({
770
+ type: "hr",
771
+ content: "",
772
+ raw: match[0] || ""
773
+ })
774
+ }
775
+ ],
776
+ renderRules: [
777
+ {
778
+ type: "hr",
694
779
  render: (token) => {
695
- const content = escapeHtml(token.content);
696
- if (this.config.format === "html") {
697
- return `<strong>${content}</strong>`;
780
+ const format = token.attributes?.format;
781
+ if (format === "html") {
782
+ return '<hr style="margin: 24px 0; border: none; border-top: 1px solid #d1d5db;">';
698
783
  }
699
- return `<strong class="font-bold">${content}</strong>`;
784
+ return '<hr class="my-6 border-t border-border">';
700
785
  }
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>`;
786
+ }
787
+ ]
788
+ };
789
+
790
+ // src/extensions/core/image.ts
791
+ var ImageExtension = {
792
+ name: "image",
793
+ parseRules: [
794
+ {
795
+ name: "image",
796
+ pattern: /!\[([^\]]*)\]\(([^)]+?)(?:\s+"([^"]+)")?\)/,
797
+ render: (match) => ({
798
+ type: "image",
799
+ content: match[1] || "",
800
+ raw: match[0] || "",
801
+ attributes: {
802
+ alt: match[1] || "",
803
+ src: match[2] || "",
804
+ title: match[3] || ""
708
805
  }
709
- return `<em class="italic">${content}</em>`;
710
- }
711
- });
712
- this.addRule({
713
- type: "code",
806
+ })
807
+ }
808
+ ],
809
+ renderRules: [
810
+ {
811
+ type: "image",
714
812
  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>`;
813
+ const src = token.attributes?.src || "";
814
+ const alt = token.attributes?.alt || "";
815
+ const title = token.attributes?.title || "";
816
+ const titleAttr = title ? ` title="${escapeHtml(title)}"` : "";
817
+ const format = token.attributes?.format || "html";
818
+ if (format === "html") {
819
+ return `<img src="${escapeHtml(src)}" alt="${escapeHtml(alt)}"${titleAttr} style="max-width: 100%; height: auto; border-radius: 8px; margin: 16px 0;" loading="lazy" />`;
718
820
  }
719
- return `<code class="bg-muted px-1.5 py-0.5 rounded text-sm font-mono">${content}</code>`;
821
+ return `<img src="${escapeHtml(src)}" alt="${escapeHtml(alt)}"${titleAttr} class="max-w-full h-auto rounded-lg my-4" loading="lazy" />`;
720
822
  }
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>`;
823
+ }
824
+ ]
825
+ };
826
+
827
+ // src/extensions/core/links.ts
828
+ var LinkExtension = {
829
+ name: "link",
830
+ parseRules: [
831
+ {
832
+ name: "link",
833
+ pattern: /\[(?!(?:button|embed):)([^\]]+)\]\(([^)]+)\)/,
834
+ render: (match) => ({
835
+ type: "link",
836
+ content: match[1] || "",
837
+ raw: match[0] || "",
838
+ attributes: {
839
+ href: match[2] || ""
729
840
  }
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({
841
+ })
842
+ }
843
+ ],
844
+ renderRules: [
845
+ {
734
846
  type: "link",
735
847
  render: (token) => {
736
848
  const href = token.attributes?.href || "#";
737
849
  const escapedHref = escapeHtml(href);
738
850
  const escapedText = escapeHtml(token.content);
739
- if (this.config.format === "html") {
851
+ const format = token.attributes?.format || "html";
852
+ if (format === "html") {
740
853
  return `<a href="${escapedHref}" target="_blank" rel="noopener noreferrer">${escapedText}</a>`;
741
854
  }
742
855
  return `<a href="${escapedHref}" class="text-primary hover:underline inline-flex items-center gap-1" target="_blank" rel="noopener noreferrer">
@@ -748,47 +861,55 @@ var MarkdownRenderer = class {
748
861
  </svg>
749
862
  </a>`;
750
863
  }
751
- });
752
- this.addRule({
864
+ }
865
+ ]
866
+ };
867
+
868
+ // src/extensions/core/lists.ts
869
+ var ListExtension = {
870
+ name: "list",
871
+ parseRules: [
872
+ {
873
+ name: "list-item",
874
+ pattern: /^(\s*)[-*+]\s+(.+)$/m,
875
+ render: (match) => ({
876
+ type: "list-item",
877
+ content: match[2] || "",
878
+ raw: match[0] || ""
879
+ })
880
+ }
881
+ ],
882
+ renderRules: [
883
+ {
753
884
  type: "list-item",
754
885
  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>`;
886
+ }
887
+ ]
888
+ };
889
+ var TaskListExtension = {
890
+ name: "task-list",
891
+ parseRules: [
892
+ {
893
+ name: "task-item",
894
+ pattern: /^(\s*)-\s*\[([ xX])\]\s*(.+)$/m,
895
+ render: (match) => ({
896
+ type: "task-item",
897
+ content: match[3] || "",
898
+ raw: match[0] || "",
899
+ attributes: {
900
+ checked: String((match[2] || "").toLowerCase() === "x")
782
901
  }
783
- return `<p class="leading-7 mb-4">${processedContent}</p>`;
784
- }
785
- });
786
- this.addRule({
902
+ })
903
+ }
904
+ ],
905
+ renderRules: [
906
+ {
787
907
  type: "task-item",
788
908
  render: (token) => {
789
909
  const isChecked = token.attributes?.checked === "true";
790
910
  const escapedContent = escapeHtml(token.content);
791
- if (this.config.format === "html") {
911
+ const format = token.attributes?.format || "html";
912
+ if (format === "html") {
792
913
  return `<div style="display: flex; align-items: center; gap: 8px; margin: 8px 0;">
793
914
  <input type="checkbox" ${isChecked ? "checked" : ""} disabled style="margin: 0;" />
794
915
  <span${isChecked ? ' style="text-decoration: line-through; color: #6b7280;"' : ""}>${escapedContent}</span>
@@ -800,44 +921,65 @@ var MarkdownRenderer = class {
800
921
  <span${isChecked ? ' class="line-through text-muted-foreground"' : ""}>${escapedContent}</span>
801
922
  </div>`;
802
923
  }
803
- });
804
- this.addRule({
805
- type: "image",
924
+ }
925
+ ]
926
+ };
927
+
928
+ // src/extensions/core/paragraph.ts
929
+ var ParagraphExtension = {
930
+ name: "paragraph",
931
+ parseRules: [],
932
+ renderRules: [
933
+ {
934
+ type: "paragraph",
806
935
  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" />`;
936
+ if (!token.content) return "";
937
+ const content = token.content.trim();
938
+ if (!content) return "";
939
+ const processedContent = content.includes("<br>") ? content : escapeHtml(content);
940
+ const format = token.attributes?.format || "html";
941
+ if (format === "html") {
942
+ return `<p style="line-height: 1.75; margin-bottom: 16px;">${processedContent}</p>`;
813
943
  }
814
- return `<img src="${escapeHtml(src)}" alt="${escapeHtml(alt)}"${titleAttr} class="max-w-full h-auto rounded-lg my-4" loading="lazy" />`;
944
+ return `<p class="leading-7 mb-4">${processedContent}</p>`;
815
945
  }
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">';
946
+ }
947
+ ]
948
+ };
949
+
950
+ // src/extensions/core/text.ts
951
+ var TextExtension = {
952
+ name: "text",
953
+ parseRules: [],
954
+ renderRules: [
955
+ {
956
+ type: "text",
957
+ render: (token) => {
958
+ if (!token.content) return "";
959
+ return escapeHtml(token.content);
824
960
  }
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
- }
961
+ }
962
+ ]
839
963
  };
840
964
 
965
+ // src/extensions/core/index.ts
966
+ var CoreExtensions = [
967
+ TextExtension,
968
+ HeadingExtension,
969
+ BoldExtension,
970
+ ItalicExtension,
971
+ InlineCodeExtension,
972
+ CodeBlockExtension,
973
+ LinkExtension,
974
+ ImageExtension,
975
+ ListExtension,
976
+ TaskListExtension,
977
+ BlockquoteExtension,
978
+ HorizontalRuleExtension,
979
+ ParagraphExtension,
980
+ LineBreakExtension
981
+ ];
982
+
841
983
  // src/extensions/alert.ts
842
984
  var AlertExtension = {
843
985
  name: "alert",
@@ -917,26 +1059,19 @@ var ButtonExtension = {
917
1059
  name: "button",
918
1060
  pattern: /\[button:([^\]]+)\]\(([^)]+)\)(?:\{([^}]+)\})?/,
919
1061
  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";
1062
+ const options = match[3] ? match[3].split(",").map((opt) => opt.trim()) : [];
930
1063
  return {
931
1064
  type: "button",
932
- content: text,
1065
+ content: match[1] || "",
933
1066
  raw: match[0] || "",
934
1067
  attributes: {
935
- href,
936
- style,
937
- size,
938
- disabled: disabled.toString(),
939
- target
1068
+ href: match[2] || "",
1069
+ style: options.find(
1070
+ (opt) => ["default", "primary", "secondary", "success", "danger", "outline", "ghost"].includes(opt)
1071
+ ) || "primary",
1072
+ size: options.find((opt) => ["sm", "md", "lg"].includes(opt)) || "md",
1073
+ disabled: String(options.includes("disabled")),
1074
+ target: options.includes("self") ? "_self" : "_blank"
940
1075
  }
941
1076
  };
942
1077
  }
@@ -951,53 +1086,92 @@ var ButtonExtension = {
951
1086
  const size = token.attributes?.size || "md";
952
1087
  const disabled = token.attributes?.disabled === "true";
953
1088
  const target = token.attributes?.target || "_blank";
954
- const text = token.content;
955
- const classes = buildButtonClasses(style, size);
1089
+ const baseClasses = `
1090
+ inline-flex items-center justify-center font-medium rounded-lg
1091
+ transition-all duration-200 ease-out
1092
+ focus:outline-none focus:ring-2 focus:ring-offset-2
1093
+ disabled:opacity-50 disabled:cursor-not-allowed disabled:pointer-events-none
1094
+ transform hover:scale-[1.02] active:scale-[0.98]
1095
+ shadow-sm hover:shadow-md active:shadow-sm
1096
+ border border-transparent
1097
+ relative overflow-hidden
1098
+ before:absolute before:inset-0 before:rounded-lg
1099
+ before:bg-gradient-to-br before:from-white/20 before:to-transparent
1100
+ before:opacity-0 hover:before:opacity-100 before:transition-opacity before:duration-200
1101
+ `.replace(/\s+/g, " ").trim();
1102
+ const sizeClasses = {
1103
+ sm: "px-3 py-1.5 text-sm gap-1.5",
1104
+ md: "px-4 py-2 text-base gap-2",
1105
+ lg: "px-6 py-3 text-lg gap-2.5"
1106
+ };
1107
+ const styleClasses = {
1108
+ default: `
1109
+ bg-slate-600 text-white border-slate-500
1110
+ hover:bg-slate-700 hover:border-slate-400
1111
+ focus:ring-slate-500
1112
+ shadow-[0_1px_0_0_rgba(255,255,255,0.1)_inset,0_1px_2px_0_rgba(0,0,0,0.1)]
1113
+ hover:shadow-[0_1px_0_0_rgba(255,255,255,0.15)_inset,0_2px_4px_0_rgba(0,0,0,0.15)]
1114
+ `,
1115
+ primary: `
1116
+ bg-blue-600 text-white border-blue-500
1117
+ hover:bg-blue-700 hover:border-blue-400
1118
+ focus:ring-blue-500
1119
+ shadow-[0_1px_0_0_rgba(255,255,255,0.1)_inset,0_1px_2px_0_rgba(0,0,0,0.1)]
1120
+ hover:shadow-[0_1px_0_0_rgba(255,255,255,0.15)_inset,0_2px_4px_0_rgba(0,0,0,0.15)]
1121
+ `,
1122
+ secondary: `
1123
+ bg-gray-600 text-white border-gray-500
1124
+ hover:bg-gray-700 hover:border-gray-400
1125
+ focus:ring-gray-500
1126
+ shadow-[0_1px_0_0_rgba(255,255,255,0.1)_inset,0_1px_2px_0_rgba(0,0,0,0.1)]
1127
+ hover:shadow-[0_1px_0_0_rgba(255,255,255,0.15)_inset,0_2px_4px_0_rgba(0,0,0,0.15)]
1128
+ `,
1129
+ success: `
1130
+ bg-green-600 text-white border-green-500
1131
+ hover:bg-green-700 hover:border-green-400
1132
+ focus:ring-green-500
1133
+ shadow-[0_1px_0_0_rgba(255,255,255,0.1)_inset,0_1px_2px_0_rgba(0,0,0,0.1)]
1134
+ hover:shadow-[0_1px_0_0_rgba(255,255,255,0.15)_inset,0_2px_4px_0_rgba(0,0,0,0.15)]
1135
+ `,
1136
+ danger: `
1137
+ bg-red-600 text-white border-red-500
1138
+ hover:bg-red-700 hover:border-red-400
1139
+ focus:ring-red-500
1140
+ shadow-[0_1px_0_0_rgba(255,255,255,0.1)_inset,0_1px_2px_0_rgba(0,0,0,0.1)]
1141
+ hover:shadow-[0_1px_0_0_rgba(255,255,255,0.15)_inset,0_2px_4px_0_rgba(0,0,0,0.15)]
1142
+ `,
1143
+ outline: `
1144
+ bg-transparent text-blue-600 border-blue-600
1145
+ hover:bg-blue-50 hover:border-blue-700 hover:text-blue-700
1146
+ focus:ring-blue-500
1147
+ shadow-[0_0_0_1px_rgba(59,130,246,0.5)_inset]
1148
+ hover:shadow-[0_0_0_1px_rgba(29,78,216,0.6)_inset,0_1px_2px_0_rgba(0,0,0,0.05)]
1149
+ `,
1150
+ ghost: `
1151
+ bg-transparent text-gray-700 border-transparent
1152
+ hover:bg-gray-100 hover:text-gray-900
1153
+ focus:ring-gray-500
1154
+ shadow-none
1155
+ hover:shadow-[0_1px_2px_0_rgba(0,0,0,0.05)]
1156
+ `
1157
+ };
1158
+ const classes = `
1159
+ ${baseClasses}
1160
+ ${sizeClasses[size] || sizeClasses.md}
1161
+ ${styleClasses[style] || styleClasses.primary}
1162
+ `.replace(/\s+/g, " ").trim();
956
1163
  const targetAttr = target === "_blank" ? ' target="_blank" rel="noopener noreferrer"' : target === "_self" ? ' target="_self"' : "";
957
1164
  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>' : "";
1165
+ const externalIcon = target === "_blank" && !disabled ? `<svg class="w-4 h-4 ml-1 opacity-75" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1166
+ <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"/>
1167
+ </svg>` : "";
959
1168
  return `<a href="${href}" class="${classes}"${targetAttr}${disabledAttr}>
960
- ${text}${externalIcon}
961
- </a>`;
1169
+ <span class="relative z-10">${token.content}</span>${externalIcon}
1170
+ </a>`;
962
1171
  }
963
1172
  }
964
1173
  ]
965
1174
  };
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
1175
 
1002
1176
  // src/extensions/embed.ts
1003
1177
  var EmbedExtension = {
@@ -1075,8 +1249,7 @@ function renderYouTubeEmbed(url, options, classes) {
1075
1249
  params.set("rel", "0");
1076
1250
  params.set("modestbranding", "1");
1077
1251
  const embedUrl = `https://www.youtube.com/embed/${videoId}?${params.toString()}`;
1078
- return `
1079
- <div class="${classes}">
1252
+ return `<div class="${classes}">
1080
1253
  <div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
1081
1254
  <iframe
1082
1255
  style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 0;"
@@ -1104,8 +1277,7 @@ function renderCodePenEmbed(url, options, classes) {
1104
1277
  "editable": "true"
1105
1278
  });
1106
1279
  const embedUrl = `https://codepen.io/${user}/embed/${penId}?${embedParams.toString()}`;
1107
- return `
1108
- <div class="${classes}">
1280
+ return `<div class="${classes}">
1109
1281
  <iframe
1110
1282
  height="${height}"
1111
1283
  style="width: 100%; border: 0;"
@@ -1129,8 +1301,7 @@ function renderVimeoEmbed(url, options, classes) {
1129
1301
  if (options.mute === "1") params.set("muted", "1");
1130
1302
  if (options.loop === "1") params.set("loop", "1");
1131
1303
  const embedUrl = `https://player.vimeo.com/video/${videoId}?${params.toString()}`;
1132
- return `
1133
- <div class="${classes}">
1304
+ return `<div class="${classes}">
1134
1305
  <div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
1135
1306
  <iframe
1136
1307
  style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 0;"
@@ -1146,8 +1317,7 @@ function renderVimeoEmbed(url, options, classes) {
1146
1317
  function renderSpotifyEmbed(url, options, classes) {
1147
1318
  const embedUrl = url.replace("open.spotify.com", "open.spotify.com/embed");
1148
1319
  const height = options.height || "380";
1149
- return `
1150
- <div class="${classes}">
1320
+ return `<div class="${classes}">
1151
1321
  <iframe
1152
1322
  style="border-radius: 12px;"
1153
1323
  src="${embedUrl}"
@@ -1170,8 +1340,7 @@ function renderCodeSandboxEmbed(url, options, classes) {
1170
1340
  if (!embedUrl.includes("?")) {
1171
1341
  embedUrl += `?view=${view}`;
1172
1342
  }
1173
- return `
1174
- <div class="${classes}">
1343
+ return `<div class="${classes}">
1175
1344
  <iframe
1176
1345
  src="${embedUrl}"
1177
1346
  style="width: 100%; height: ${height}px; border: 0; border-radius: 4px; overflow: hidden;"
@@ -1184,8 +1353,7 @@ function renderCodeSandboxEmbed(url, options, classes) {
1184
1353
  function renderFigmaEmbed(url, options, classes) {
1185
1354
  const embedUrl = `https://www.figma.com/embed?embed_host=share&url=${encodeURIComponent(url)}`;
1186
1355
  const height = options.height || "450";
1187
- return `
1188
- <div class="${classes}">
1356
+ return `<div class="${classes}">
1189
1357
  <iframe
1190
1358
  style="border: none;"
1191
1359
  width="100%"
@@ -1196,8 +1364,7 @@ function renderFigmaEmbed(url, options, classes) {
1196
1364
  </div>`;
1197
1365
  }
1198
1366
  function renderTwitterEmbed(url, _options, classes) {
1199
- return `
1200
- <div class="${classes}">
1367
+ return `<div class="${classes}">
1201
1368
  <div class="p-4">
1202
1369
  <div class="flex items-center gap-3 mb-3">
1203
1370
  <svg class="w-6 h-6 fill-current text-blue-500" viewBox="0 0 24 24">
@@ -1225,8 +1392,7 @@ function renderGitHubEmbed(url, _options, classes) {
1225
1392
  if (!owner || !repo) {
1226
1393
  return createErrorEmbed("Invalid GitHub URL", url, classes);
1227
1394
  }
1228
- return `
1229
- <div class="${classes}">
1395
+ return `<div class="${classes}">
1230
1396
  <div class="p-4">
1231
1397
  <div class="flex items-center gap-3 mb-3">
1232
1398
  <svg class="w-6 h-6 fill-current" viewBox="0 0 24 24">
@@ -1249,8 +1415,7 @@ function renderGitHubEmbed(url, _options, classes) {
1249
1415
  }
1250
1416
  function renderGenericEmbed(url, _options, classes) {
1251
1417
  const domain = extractDomain(url);
1252
- return `
1253
- <div class="${classes}">
1418
+ return `<div class="${classes}">
1254
1419
  <div class="p-4">
1255
1420
  <div class="flex items-center gap-3 mb-3">
1256
1421
  <div class="w-10 h-10 rounded-lg bg-muted flex items-center justify-center">
@@ -1274,8 +1439,7 @@ function renderGenericEmbed(url, _options, classes) {
1274
1439
  </div>`;
1275
1440
  }
1276
1441
  function createErrorEmbed(error, url, classes) {
1277
- return `
1278
- <div class="${classes}">
1442
+ return `<div class="${classes}">
1279
1443
  <div class="p-4 text-destructive">
1280
1444
  <div class="font-medium flex items-center gap-2">
1281
1445
  <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -1309,19 +1473,54 @@ var ChangerawrMarkdown = class {
1309
1473
  this.extensions = /* @__PURE__ */ new Map();
1310
1474
  this.parser = new MarkdownParser(config?.parser);
1311
1475
  this.renderer = new MarkdownRenderer(config?.renderer);
1312
- this.registerBuiltInExtensions();
1476
+ this.registerCoreExtensions();
1477
+ this.registerFeatureExtensions();
1313
1478
  if (config?.extensions) {
1314
1479
  config.extensions.forEach((extension) => {
1315
1480
  this.registerExtension(extension);
1316
1481
  });
1317
1482
  }
1318
- this.parser.setupDefaultRulesIfEmpty();
1319
1483
  }
1320
- /**
1321
- * Register a custom extension
1322
- */
1484
+ registerFeatureExtensions() {
1485
+ this.registerExtension(AlertExtension);
1486
+ this.registerExtension(ButtonExtension);
1487
+ this.registerExtension(EmbedExtension);
1488
+ }
1489
+ registerCoreExtensions() {
1490
+ CoreExtensions.forEach((extension) => {
1491
+ this.registerExtension(extension);
1492
+ });
1493
+ }
1323
1494
  registerExtension(extension) {
1324
1495
  try {
1496
+ if (!extension.name || typeof extension.name !== "string") {
1497
+ throw new Error("Extension must have a valid name");
1498
+ }
1499
+ if (!Array.isArray(extension.parseRules)) {
1500
+ throw new Error("Extension must have parseRules array");
1501
+ }
1502
+ if (!Array.isArray(extension.renderRules)) {
1503
+ throw new Error("Extension must have renderRules array");
1504
+ }
1505
+ extension.parseRules.forEach((rule, index) => {
1506
+ if (!rule.name || typeof rule.name !== "string") {
1507
+ throw new Error(`Parse rule ${index} must have a valid name`);
1508
+ }
1509
+ if (!rule.pattern || !(rule.pattern instanceof RegExp)) {
1510
+ throw new Error(`Parse rule ${index} must have a valid RegExp pattern`);
1511
+ }
1512
+ if (!rule.render || typeof rule.render !== "function") {
1513
+ throw new Error(`Parse rule ${index} must have a valid render function`);
1514
+ }
1515
+ });
1516
+ extension.renderRules.forEach((rule, index) => {
1517
+ if (!rule.type || typeof rule.type !== "string") {
1518
+ throw new Error(`Render rule ${index} must have a valid type`);
1519
+ }
1520
+ if (!rule.render || typeof rule.render !== "function") {
1521
+ throw new Error(`Render rule ${index} must have a valid render function`);
1522
+ }
1523
+ });
1325
1524
  this.extensions.set(extension.name, extension);
1326
1525
  extension.parseRules.forEach((rule) => {
1327
1526
  this.parser.addRule(rule);
@@ -1342,9 +1541,6 @@ var ChangerawrMarkdown = class {
1342
1541
  };
1343
1542
  }
1344
1543
  }
1345
- /**
1346
- * Unregister an extension
1347
- */
1348
1544
  unregisterExtension(name) {
1349
1545
  const extension = this.extensions.get(name);
1350
1546
  if (!extension) {
@@ -1358,46 +1554,25 @@ var ChangerawrMarkdown = class {
1358
1554
  return false;
1359
1555
  }
1360
1556
  }
1361
- /**
1362
- * Parse markdown content into tokens
1363
- */
1364
1557
  parse(markdown2) {
1365
1558
  return this.parser.parse(markdown2);
1366
1559
  }
1367
- /**
1368
- * Render tokens to HTML
1369
- */
1370
1560
  render(tokens) {
1371
1561
  return this.renderer.render(tokens);
1372
1562
  }
1373
- /**
1374
- * Parse and render markdown to HTML in one step
1375
- */
1376
1563
  toHtml(markdown2) {
1377
1564
  const tokens = this.parse(markdown2);
1378
1565
  return this.render(tokens);
1379
1566
  }
1380
- /**
1381
- * Get list of registered extensions
1382
- */
1383
1567
  getExtensions() {
1384
1568
  return Array.from(this.extensions.keys());
1385
1569
  }
1386
- /**
1387
- * Check if extension is registered
1388
- */
1389
1570
  hasExtension(name) {
1390
1571
  return this.extensions.has(name);
1391
1572
  }
1392
- /**
1393
- * Get parser warnings
1394
- */
1395
1573
  getWarnings() {
1396
1574
  return [...this.parser.getWarnings(), ...this.renderer.getWarnings()];
1397
1575
  }
1398
- /**
1399
- * Get debug information from last render
1400
- */
1401
1576
  getDebugInfo() {
1402
1577
  return {
1403
1578
  warnings: this.getWarnings(),
@@ -1407,9 +1582,6 @@ var ChangerawrMarkdown = class {
1407
1582
  iterationCount: 0
1408
1583
  };
1409
1584
  }
1410
- /**
1411
- * Get performance metrics for the last operation
1412
- */
1413
1585
  getPerformanceMetrics() {
1414
1586
  return {
1415
1587
  parseTime: 0,
@@ -1418,28 +1590,29 @@ var ChangerawrMarkdown = class {
1418
1590
  tokenCount: 0
1419
1591
  };
1420
1592
  }
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
1593
  rebuildParserAndRenderer() {
1433
1594
  const parserConfig = this.parser.getConfig();
1434
1595
  const rendererConfig = this.renderer.getConfig();
1435
1596
  this.parser = new MarkdownParser(parserConfig);
1436
1597
  this.renderer = new MarkdownRenderer(rendererConfig);
1437
1598
  const extensionsToRegister = Array.from(this.extensions.values());
1438
- this.extensions.clear();
1439
- extensionsToRegister.forEach((extension) => {
1440
- this.registerExtension(extension);
1599
+ const featureExtensions = extensionsToRegister.filter(
1600
+ (ext) => ["alert", "button", "embed"].includes(ext.name)
1601
+ );
1602
+ const coreExtensions = extensionsToRegister.filter(
1603
+ (ext) => ["text", "heading", "bold", "italic", "code", "codeblock", "link", "image", "list", "task-list", "blockquote", "hr", "paragraph", "line-break"].includes(ext.name)
1604
+ );
1605
+ const customExtensions = extensionsToRegister.filter(
1606
+ (ext) => !["alert", "button", "embed", "text", "heading", "bold", "italic", "code", "codeblock", "link", "image", "list", "task-list", "blockquote", "hr", "paragraph", "line-break"].includes(ext.name)
1607
+ );
1608
+ [...featureExtensions, ...coreExtensions, ...customExtensions].forEach((extension) => {
1609
+ extension.parseRules.forEach((rule) => {
1610
+ this.parser.addRule(rule);
1611
+ });
1612
+ extension.renderRules.forEach((rule) => {
1613
+ this.renderer.addRule(rule);
1614
+ });
1441
1615
  });
1442
- this.parser.setupDefaultRulesIfEmpty();
1443
1616
  }
1444
1617
  };
1445
1618
  var markdown = new ChangerawrMarkdown();