@changerawr/markdown 1.0.5 → 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.
@@ -9,39 +9,56 @@ var MarkdownParser = class {
9
9
  constructor(config) {
10
10
  this.rules = [];
11
11
  this.warnings = [];
12
- this.config = config || {};
12
+ this.config = {
13
+ debugMode: false,
14
+ maxIterations: 1e4,
15
+ validateMarkdown: false,
16
+ ...config
17
+ };
13
18
  }
14
19
  addRule(rule) {
15
20
  this.rules.push(rule);
16
21
  this.rules.sort((a, b) => {
17
- if (a.name.includes("alert") || a.name.includes("embed") || a.name.includes("button")) return -1;
18
- if (b.name.includes("alert") || b.name.includes("embed") || b.name.includes("button")) return 1;
19
- if (a.name === "task-list") return -1;
20
- if (b.name === "task-list") return 1;
21
- if (a.name === "list" && b.name === "task-list") return 1;
22
- if (b.name === "list" && a.name === "task-list") return -1;
22
+ const aFeatureExtension = ["alert", "button", "embed"].includes(a.name);
23
+ const bFeatureExtension = ["alert", "button", "embed"].includes(b.name);
24
+ if (aFeatureExtension && !bFeatureExtension) return -1;
25
+ if (!aFeatureExtension && bFeatureExtension) return 1;
26
+ const aCoreExtension = ["text", "heading", "bold", "italic", "code", "codeblock", "link", "image", "list", "task-list", "blockquote", "hr", "paragraph", "line-break"].includes(a.name);
27
+ const bCoreExtension = ["text", "heading", "bold", "italic", "code", "codeblock", "link", "image", "list", "task-list", "blockquote", "hr", "paragraph", "line-break"].includes(b.name);
28
+ if (aCoreExtension && !bCoreExtension) return -1;
29
+ if (!aCoreExtension && bCoreExtension) return 1;
30
+ if (a.name === "image" && b.name === "link") return -1;
31
+ if (a.name === "link" && b.name === "image") return 1;
32
+ if (a.name === "task-item" && b.name === "list-item") return -1;
33
+ if (a.name === "list-item" && b.name === "task-item") return 1;
34
+ if (a.name === "codeblock" && b.name === "code") return -1;
35
+ if (a.name === "code" && b.name === "codeblock") return 1;
36
+ if (a.name === "bold" && b.name === "italic") return -1;
37
+ if (a.name === "italic" && b.name === "bold") return 1;
23
38
  return a.name.localeCompare(b.name);
24
39
  });
25
40
  }
26
- setupDefaultRulesIfEmpty() {
27
- const hasDefaultRules = this.rules.some(
28
- (rule) => !["alert", "button", "embed"].includes(rule.name)
29
- );
30
- if (!hasDefaultRules) {
31
- this.setupDefaultRules();
32
- }
33
- }
34
41
  hasRule(name) {
35
42
  return this.rules.some((rule) => rule.name === name);
36
43
  }
37
44
  parse(markdown2) {
38
45
  this.warnings = [];
39
- this.setupDefaultRulesIfEmpty();
46
+ if (!markdown2.trim()) {
47
+ return [];
48
+ }
49
+ if (this.rules.length === 0) {
50
+ this.warnings.push("No parse rules registered - consider using CoreExtensions");
51
+ return [{
52
+ type: "text",
53
+ content: markdown2,
54
+ raw: markdown2
55
+ }];
56
+ }
40
57
  const processedMarkdown = this.preprocessMarkdown(markdown2);
41
58
  const tokens = [];
42
59
  let remaining = processedMarkdown;
43
60
  let iterationCount = 0;
44
- const maxIterations = this.config.maxIterations || markdown2.length * 2;
61
+ const maxIterations = this.config.maxIterations || 1e4;
45
62
  while (remaining.length > 0 && iterationCount < maxIterations) {
46
63
  iterationCount++;
47
64
  let matched = false;
@@ -130,9 +147,6 @@ var MarkdownParser = class {
130
147
  clearWarnings() {
131
148
  this.warnings = [];
132
149
  }
133
- getIterationCount() {
134
- return 0;
135
- }
136
150
  preprocessMarkdown(markdown2) {
137
151
  if (this.config.validateMarkdown) {
138
152
  this.validateMarkdown(markdown2);
@@ -187,161 +201,6 @@ var MarkdownParser = class {
187
201
  }
188
202
  return processed;
189
203
  }
190
- setupDefaultRules() {
191
- this.addRule({
192
- name: "heading",
193
- pattern: /^(#{1,6})\s+(.+)$/m,
194
- render: (match) => ({
195
- type: "heading",
196
- content: match[2]?.trim() || "",
197
- raw: match[0] || "",
198
- attributes: {
199
- level: String(match[1]?.length || 1)
200
- }
201
- })
202
- });
203
- this.addRule({
204
- name: "codeblock",
205
- pattern: /```(\w+)?\s*\n([\s\S]*?)\n```/,
206
- render: (match) => ({
207
- type: "codeblock",
208
- content: match[2] || "",
209
- raw: match[0] || "",
210
- attributes: {
211
- language: match[1] || "text"
212
- }
213
- })
214
- });
215
- this.addRule({
216
- name: "hard-break-backslash",
217
- pattern: /\\\s*\n/,
218
- render: (match) => ({
219
- type: "line-break",
220
- content: "",
221
- raw: match[0] || ""
222
- })
223
- });
224
- this.addRule({
225
- name: "hard-break-spaces",
226
- pattern: / +\n/,
227
- render: (match) => ({
228
- type: "line-break",
229
- content: "",
230
- raw: match[0] || ""
231
- })
232
- });
233
- this.addRule({
234
- name: "paragraph-break",
235
- pattern: /\n\s*\n/,
236
- render: (match) => ({
237
- type: "paragraph-break",
238
- content: "",
239
- raw: match[0] || ""
240
- })
241
- });
242
- this.addRule({
243
- name: "bold",
244
- pattern: /\*\*((?:(?!\*\*).)+)\*\*/,
245
- render: (match) => ({
246
- type: "bold",
247
- content: match[1] || "",
248
- raw: match[0] || ""
249
- })
250
- });
251
- this.addRule({
252
- name: "italic",
253
- pattern: /\*((?:(?!\*).)+)\*/,
254
- render: (match) => ({
255
- type: "italic",
256
- content: match[1] || "",
257
- raw: match[0] || ""
258
- })
259
- });
260
- this.addRule({
261
- name: "code",
262
- pattern: /`([^`]+)`/,
263
- render: (match) => ({
264
- type: "code",
265
- content: match[1] || "",
266
- raw: match[0] || ""
267
- })
268
- });
269
- this.addRule({
270
- name: "image",
271
- pattern: /!\[([^\]]*)\]\(([^)]+?)(?:\s+"([^"]+)")?\)/,
272
- render: (match) => ({
273
- type: "image",
274
- content: match[1] || "",
275
- raw: match[0] || "",
276
- attributes: {
277
- alt: match[1] || "",
278
- src: match[2] || "",
279
- title: match[3] || ""
280
- }
281
- })
282
- });
283
- this.addRule({
284
- name: "link",
285
- pattern: /\[([^\]]+)\]\(([^)]+)\)/,
286
- render: (match) => ({
287
- type: "link",
288
- content: match[1] || "",
289
- raw: match[0] || "",
290
- attributes: {
291
- href: match[2] || ""
292
- }
293
- })
294
- });
295
- this.addRule({
296
- name: "task-list",
297
- pattern: /^(\s*)-\s*\[([ xX])\]\s*(.+)$/m,
298
- render: (match) => ({
299
- type: "task-item",
300
- content: match[3] || "",
301
- raw: match[0] || "",
302
- attributes: {
303
- checked: String((match[2] || "").toLowerCase() === "x")
304
- }
305
- })
306
- });
307
- this.addRule({
308
- name: "list",
309
- pattern: /^(\s*)[-*+]\s+(.+)$/m,
310
- render: (match) => ({
311
- type: "list-item",
312
- content: match[2] || "",
313
- raw: match[0] || ""
314
- })
315
- });
316
- this.addRule({
317
- name: "blockquote",
318
- pattern: /^>\s+(.+)$/m,
319
- render: (match) => ({
320
- type: "blockquote",
321
- content: match[1] || "",
322
- raw: match[0] || ""
323
- })
324
- });
325
- this.addRule({
326
- name: "hr",
327
- pattern: /^---$/m,
328
- render: (match) => ({
329
- type: "hr",
330
- content: "",
331
- raw: match[0] || ""
332
- })
333
- });
334
- this.addRule({
335
- name: "soft-break",
336
- pattern: /\n/,
337
- render: (match) => ({
338
- type: "soft-break",
339
- content: " ",
340
- // Convert to space for inline text
341
- raw: match[0] || ""
342
- })
343
- });
344
- }
345
204
  };
346
205
 
347
206
  // src/utils.ts
@@ -580,7 +439,6 @@ var MarkdownRenderer = class {
580
439
  debugMode: false,
581
440
  ...config
582
441
  };
583
- this.setupDefaultRules();
584
442
  }
585
443
  addRule(rule) {
586
444
  this.rules.set(rule.type, rule);
@@ -590,28 +448,20 @@ var MarkdownRenderer = class {
590
448
  }
591
449
  render(tokens) {
592
450
  this.warnings = [];
593
- const htmlParts = tokens.map((token) => this.renderToken(token));
451
+ const tokensWithFormat = tokens.map((token) => ({
452
+ ...token,
453
+ attributes: {
454
+ ...token.attributes,
455
+ format: this.config.format
456
+ }
457
+ }));
458
+ const htmlParts = tokensWithFormat.map((token) => this.renderToken(token));
594
459
  const combinedHtml = htmlParts.join("");
595
460
  if (this.config.sanitize && !this.config.allowUnsafeHtml) {
596
461
  return sanitizeHtml(combinedHtml);
597
462
  }
598
463
  return combinedHtml;
599
464
  }
600
- getWarnings() {
601
- return [...this.warnings];
602
- }
603
- getConfig() {
604
- return { ...this.config };
605
- }
606
- updateConfig(config) {
607
- this.config = { ...this.config, ...config };
608
- }
609
- setDebugMode(enabled) {
610
- this.config.debugMode = enabled;
611
- }
612
- clearWarnings() {
613
- this.warnings = [];
614
- }
615
465
  renderToken(token) {
616
466
  const rule = this.rules.get(token.type);
617
467
  if (rule) {
@@ -623,9 +473,6 @@ var MarkdownRenderer = class {
623
473
  return this.createErrorBlock(`Render error for ${token.type}: ${errorMessage}`);
624
474
  }
625
475
  }
626
- if (token.type === "text") {
627
- return escapeHtml(token.content || token.raw || "");
628
- }
629
476
  if (this.config.debugMode) {
630
477
  return this.createDebugBlock(token);
631
478
  }
@@ -648,20 +495,239 @@ var MarkdownRenderer = class {
648
495
  <strong>Content:</strong> ${escapeHtml(token.content || token.raw || "")}
649
496
  </div>`;
650
497
  }
651
- return `<div class="bg-yellow-100 border border-yellow-300 text-yellow-800 p-2 rounded text-sm mb-2">
652
- <strong>Unknown token type:</strong> ${escapeHtml(token.type)}<br>
653
- <strong>Content:</strong> ${escapeHtml(token.content || token.raw || "")}
654
- </div>`;
655
- }
656
- setupDefaultRules() {
657
- this.addRule({
498
+ return `<div class="bg-yellow-100 border border-yellow-300 text-yellow-800 p-2 rounded text-sm mb-2">
499
+ <strong>Unknown token type:</strong> ${escapeHtml(token.type)}<br>
500
+ <strong>Content:</strong> ${escapeHtml(token.content || token.raw || "")}
501
+ </div>`;
502
+ }
503
+ getWarnings() {
504
+ return [...this.warnings];
505
+ }
506
+ getConfig() {
507
+ return { ...this.config };
508
+ }
509
+ updateConfig(config) {
510
+ this.config = { ...this.config, ...config };
511
+ }
512
+ setDebugMode(enabled) {
513
+ this.config.debugMode = enabled;
514
+ }
515
+ clearWarnings() {
516
+ this.warnings = [];
517
+ }
518
+ };
519
+
520
+ // src/extensions/core/blockquote.ts
521
+ var BlockquoteExtension = {
522
+ name: "blockquote",
523
+ parseRules: [
524
+ {
525
+ name: "blockquote",
526
+ pattern: /^>\s+(.+)$/m,
527
+ render: (match) => ({
528
+ type: "blockquote",
529
+ content: match[1] || "",
530
+ raw: match[0] || ""
531
+ })
532
+ }
533
+ ],
534
+ renderRules: [
535
+ {
536
+ type: "blockquote",
537
+ render: (token) => {
538
+ const content = escapeHtml(token.content);
539
+ const format = token.attributes?.format || "html";
540
+ if (format === "html") {
541
+ return `<blockquote style="border-left: 2px solid #d1d5db; padding: 8px 0 8px 16px; margin: 16px 0; font-style: italic; color: #6b7280;">${content}</blockquote>`;
542
+ }
543
+ return `<blockquote class="pl-4 py-2 border-l-2 border-border italic text-muted-foreground my-4">${content}</blockquote>`;
544
+ }
545
+ }
546
+ ]
547
+ };
548
+
549
+ // src/extensions/core/breaks.ts
550
+ var LineBreakExtension = {
551
+ name: "line-break",
552
+ parseRules: [
553
+ {
554
+ name: "hard-break-backslash",
555
+ pattern: /\\\s*\n/,
556
+ render: (match) => ({
557
+ type: "line-break",
558
+ content: "",
559
+ raw: match[0] || ""
560
+ })
561
+ },
562
+ {
563
+ name: "hard-break-spaces",
564
+ pattern: / +\n/,
565
+ render: (match) => ({
566
+ type: "line-break",
567
+ content: "",
568
+ raw: match[0] || ""
569
+ })
570
+ }
571
+ ],
572
+ renderRules: [
573
+ {
574
+ type: "line-break",
575
+ render: () => "<br>"
576
+ },
577
+ {
578
+ type: "paragraph-break",
579
+ render: () => "</p><p>"
580
+ },
581
+ {
582
+ type: "soft-break",
583
+ render: (token) => token.content || " "
584
+ }
585
+ ]
586
+ };
587
+
588
+ // src/extensions/core/code.ts
589
+ var InlineCodeExtension = {
590
+ name: "inline-code",
591
+ parseRules: [
592
+ {
593
+ name: "code",
594
+ pattern: /`([^`]+)`/,
595
+ render: (match) => ({
596
+ type: "code",
597
+ content: match[1] || "",
598
+ raw: match[0] || ""
599
+ })
600
+ }
601
+ ],
602
+ renderRules: [
603
+ {
604
+ type: "code",
605
+ render: (token) => {
606
+ const content = escapeHtml(token.content);
607
+ const format = token.attributes?.format;
608
+ if (format === "html") {
609
+ return `<code style="background-color: #f3f4f6; padding: 2px 6px; border-radius: 4px; font-family: monospace; font-size: 0.875rem;">${content}</code>`;
610
+ }
611
+ return `<code class="bg-muted px-1.5 py-0.5 rounded text-sm font-mono">${content}</code>`;
612
+ }
613
+ }
614
+ ]
615
+ };
616
+ var CodeBlockExtension = {
617
+ name: "codeblock",
618
+ parseRules: [
619
+ {
620
+ name: "codeblock",
621
+ pattern: /```(\w+)?\s*\n([\s\S]*?)\n```/,
622
+ render: (match) => ({
623
+ type: "codeblock",
624
+ content: match[2] || "",
625
+ raw: match[0] || "",
626
+ attributes: {
627
+ language: match[1] || "text"
628
+ }
629
+ })
630
+ }
631
+ ],
632
+ renderRules: [
633
+ {
634
+ type: "codeblock",
635
+ render: (token) => {
636
+ const language = token.attributes?.language || "text";
637
+ const escapedCode = escapeHtml(token.content);
638
+ const format = token.attributes?.format || "html";
639
+ if (format === "html") {
640
+ 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>`;
641
+ }
642
+ return `<pre class="bg-muted p-4 rounded-md overflow-x-auto my-4"><code class="language-${escapeHtml(language)}">${escapedCode}</code></pre>`;
643
+ }
644
+ }
645
+ ]
646
+ };
647
+
648
+ // src/extensions/core/emphasis.ts
649
+ var BoldExtension = {
650
+ name: "bold",
651
+ parseRules: [
652
+ {
653
+ name: "bold",
654
+ pattern: /\*\*((?:(?!\*\*).)+)\*\*/,
655
+ render: (match) => ({
656
+ type: "bold",
657
+ content: match[1] || "",
658
+ raw: match[0] || ""
659
+ })
660
+ }
661
+ ],
662
+ renderRules: [
663
+ {
664
+ type: "bold",
665
+ render: (token) => {
666
+ const content = escapeHtml(token.content);
667
+ const format = token.attributes?.format;
668
+ if (format === "html") {
669
+ return `<strong>${content}</strong>`;
670
+ }
671
+ return `<strong class="font-bold">${content}</strong>`;
672
+ }
673
+ }
674
+ ]
675
+ };
676
+ var ItalicExtension = {
677
+ name: "italic",
678
+ parseRules: [
679
+ {
680
+ name: "italic",
681
+ pattern: /\*((?:(?!\*).)+)\*/,
682
+ render: (match) => ({
683
+ type: "italic",
684
+ content: match[1] || "",
685
+ raw: match[0] || ""
686
+ })
687
+ }
688
+ ],
689
+ renderRules: [
690
+ {
691
+ type: "italic",
692
+ render: (token) => {
693
+ const content = escapeHtml(token.content);
694
+ const format = token.attributes?.format;
695
+ if (format === "html") {
696
+ return `<em>${content}</em>`;
697
+ }
698
+ return `<em class="italic">${content}</em>`;
699
+ }
700
+ }
701
+ ]
702
+ };
703
+
704
+ // src/extensions/core/heading.ts
705
+ var HeadingExtension = {
706
+ name: "heading",
707
+ parseRules: [
708
+ {
709
+ name: "heading",
710
+ pattern: /^(#{1,6})\s+(.+)$/m,
711
+ render: (match) => ({
712
+ type: "heading",
713
+ content: match[2]?.trim() || "",
714
+ raw: match[0] || "",
715
+ attributes: {
716
+ level: String(match[1]?.length || 1)
717
+ }
718
+ })
719
+ }
720
+ ],
721
+ renderRules: [
722
+ {
658
723
  type: "heading",
659
724
  render: (token) => {
660
725
  const level = parseInt(token.attributes?.level || "1");
661
726
  const text = token.content;
662
727
  const id = generateId(text);
663
728
  const escapedContent = escapeHtml(text);
664
- if (this.config.format === "html") {
729
+ const format = token.attributes?.format || "html";
730
+ if (format === "html") {
665
731
  return `<h${level} id="${id}">${escapedContent}</h${level}>`;
666
732
  }
667
733
  let headingClasses = "group relative flex items-center gap-2";
@@ -688,61 +754,108 @@ var MarkdownRenderer = class {
688
754
  return `<h${level} id="${id}" class="${headingClasses}">
689
755
  ${escapedContent}
690
756
  <a href="#${id}" class="opacity-0 group-hover:opacity-100 text-muted-foreground transition-opacity">
691
- <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
692
- <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"/>
693
- </svg>
757
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
758
+ <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/>
759
+ <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>
760
+ </svg>
694
761
  </a>
695
762
  </h${level}>`;
696
763
  }
697
- });
698
- this.addRule({
699
- type: "bold",
764
+ }
765
+ ]
766
+ };
767
+
768
+ // src/extensions/core/hr.ts
769
+ var HorizontalRuleExtension = {
770
+ name: "hr",
771
+ parseRules: [
772
+ {
773
+ name: "hr",
774
+ pattern: /^---$/m,
775
+ render: (match) => ({
776
+ type: "hr",
777
+ content: "",
778
+ raw: match[0] || ""
779
+ })
780
+ }
781
+ ],
782
+ renderRules: [
783
+ {
784
+ type: "hr",
700
785
  render: (token) => {
701
- const content = escapeHtml(token.content);
702
- if (this.config.format === "html") {
703
- return `<strong>${content}</strong>`;
786
+ const format = token.attributes?.format;
787
+ if (format === "html") {
788
+ return '<hr style="margin: 24px 0; border: none; border-top: 1px solid #d1d5db;">';
704
789
  }
705
- return `<strong class="font-bold">${content}</strong>`;
790
+ return '<hr class="my-6 border-t border-border">';
706
791
  }
707
- });
708
- this.addRule({
709
- type: "italic",
710
- render: (token) => {
711
- const content = escapeHtml(token.content);
712
- if (this.config.format === "html") {
713
- return `<em>${content}</em>`;
792
+ }
793
+ ]
794
+ };
795
+
796
+ // src/extensions/core/image.ts
797
+ var ImageExtension = {
798
+ name: "image",
799
+ parseRules: [
800
+ {
801
+ name: "image",
802
+ pattern: /!\[([^\]]*)\]\(([^)]+?)(?:\s+"([^"]+)")?\)/,
803
+ render: (match) => ({
804
+ type: "image",
805
+ content: match[1] || "",
806
+ raw: match[0] || "",
807
+ attributes: {
808
+ alt: match[1] || "",
809
+ src: match[2] || "",
810
+ title: match[3] || ""
714
811
  }
715
- return `<em class="italic">${content}</em>`;
716
- }
717
- });
718
- this.addRule({
719
- type: "code",
812
+ })
813
+ }
814
+ ],
815
+ renderRules: [
816
+ {
817
+ type: "image",
720
818
  render: (token) => {
721
- const content = escapeHtml(token.content);
722
- if (this.config.format === "html") {
723
- return `<code style="background-color: #f3f4f6; padding: 2px 6px; border-radius: 4px; font-family: monospace; font-size: 0.875rem;">${content}</code>`;
819
+ const src = token.attributes?.src || "";
820
+ const alt = token.attributes?.alt || "";
821
+ const title = token.attributes?.title || "";
822
+ const titleAttr = title ? ` title="${escapeHtml(title)}"` : "";
823
+ const format = token.attributes?.format || "html";
824
+ if (format === "html") {
825
+ return `<img src="${escapeHtml(src)}" alt="${escapeHtml(alt)}"${titleAttr} style="max-width: 100%; height: auto; border-radius: 8px; margin: 16px 0;" loading="lazy" />`;
724
826
  }
725
- return `<code class="bg-muted px-1.5 py-0.5 rounded text-sm font-mono">${content}</code>`;
827
+ return `<img src="${escapeHtml(src)}" alt="${escapeHtml(alt)}"${titleAttr} class="max-w-full h-auto rounded-lg my-4" loading="lazy" />`;
726
828
  }
727
- });
728
- this.addRule({
729
- type: "codeblock",
730
- render: (token) => {
731
- const language = token.attributes?.language || "text";
732
- const escapedCode = escapeHtml(token.content);
733
- if (this.config.format === "html") {
734
- 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>`;
829
+ }
830
+ ]
831
+ };
832
+
833
+ // src/extensions/core/links.ts
834
+ var LinkExtension = {
835
+ name: "link",
836
+ parseRules: [
837
+ {
838
+ name: "link",
839
+ pattern: /\[(?!(?:button|embed):)([^\]]+)\]\(([^)]+)\)/,
840
+ render: (match) => ({
841
+ type: "link",
842
+ content: match[1] || "",
843
+ raw: match[0] || "",
844
+ attributes: {
845
+ href: match[2] || ""
735
846
  }
736
- return `<pre class="bg-muted p-4 rounded-md overflow-x-auto my-4"><code class="language-${escapeHtml(language)}">${escapedCode}</code></pre>`;
737
- }
738
- });
739
- this.addRule({
847
+ })
848
+ }
849
+ ],
850
+ renderRules: [
851
+ {
740
852
  type: "link",
741
853
  render: (token) => {
742
854
  const href = token.attributes?.href || "#";
743
855
  const escapedHref = escapeHtml(href);
744
856
  const escapedText = escapeHtml(token.content);
745
- if (this.config.format === "html") {
857
+ const format = token.attributes?.format || "html";
858
+ if (format === "html") {
746
859
  return `<a href="${escapedHref}" target="_blank" rel="noopener noreferrer">${escapedText}</a>`;
747
860
  }
748
861
  return `<a href="${escapedHref}" class="text-primary hover:underline inline-flex items-center gap-1" target="_blank" rel="noopener noreferrer">
@@ -754,47 +867,55 @@ var MarkdownRenderer = class {
754
867
  </svg>
755
868
  </a>`;
756
869
  }
757
- });
758
- this.addRule({
870
+ }
871
+ ]
872
+ };
873
+
874
+ // src/extensions/core/lists.ts
875
+ var ListExtension = {
876
+ name: "list",
877
+ parseRules: [
878
+ {
879
+ name: "list-item",
880
+ pattern: /^(\s*)[-*+]\s+(.+)$/m,
881
+ render: (match) => ({
882
+ type: "list-item",
883
+ content: match[2] || "",
884
+ raw: match[0] || ""
885
+ })
886
+ }
887
+ ],
888
+ renderRules: [
889
+ {
759
890
  type: "list-item",
760
891
  render: (token) => `<li>${escapeHtml(token.content)}</li>`
761
- });
762
- this.addRule({
763
- type: "blockquote",
764
- render: (token) => {
765
- const content = escapeHtml(token.content);
766
- if (this.config.format === "html") {
767
- return `<blockquote style="border-left: 2px solid #d1d5db; padding: 8px 0 8px 16px; margin: 16px 0; font-style: italic; color: #6b7280;">${content}</blockquote>`;
768
- }
769
- return `<blockquote class="pl-4 py-2 border-l-2 border-border italic text-muted-foreground my-4">${content}</blockquote>`;
770
- }
771
- });
772
- this.addRule({
773
- type: "text",
774
- render: (token) => {
775
- if (!token.content) return "";
776
- return escapeHtml(token.content);
777
- }
778
- });
779
- this.addRule({
780
- type: "paragraph",
781
- render: (token) => {
782
- if (!token.content) return "";
783
- const content = token.content.trim();
784
- if (!content) return "";
785
- const processedContent = content.includes("<br>") ? content : escapeHtml(content);
786
- if (this.config.format === "html") {
787
- return `<p style="line-height: 1.75; margin-bottom: 16px;">${processedContent}</p>`;
892
+ }
893
+ ]
894
+ };
895
+ var TaskListExtension = {
896
+ name: "task-list",
897
+ parseRules: [
898
+ {
899
+ name: "task-item",
900
+ pattern: /^(\s*)-\s*\[([ xX])\]\s*(.+)$/m,
901
+ render: (match) => ({
902
+ type: "task-item",
903
+ content: match[3] || "",
904
+ raw: match[0] || "",
905
+ attributes: {
906
+ checked: String((match[2] || "").toLowerCase() === "x")
788
907
  }
789
- return `<p class="leading-7 mb-4">${processedContent}</p>`;
790
- }
791
- });
792
- this.addRule({
908
+ })
909
+ }
910
+ ],
911
+ renderRules: [
912
+ {
793
913
  type: "task-item",
794
914
  render: (token) => {
795
915
  const isChecked = token.attributes?.checked === "true";
796
916
  const escapedContent = escapeHtml(token.content);
797
- if (this.config.format === "html") {
917
+ const format = token.attributes?.format || "html";
918
+ if (format === "html") {
798
919
  return `<div style="display: flex; align-items: center; gap: 8px; margin: 8px 0;">
799
920
  <input type="checkbox" ${isChecked ? "checked" : ""} disabled style="margin: 0;" />
800
921
  <span${isChecked ? ' style="text-decoration: line-through; color: #6b7280;"' : ""}>${escapedContent}</span>
@@ -806,44 +927,65 @@ var MarkdownRenderer = class {
806
927
  <span${isChecked ? ' class="line-through text-muted-foreground"' : ""}>${escapedContent}</span>
807
928
  </div>`;
808
929
  }
809
- });
810
- this.addRule({
811
- type: "image",
930
+ }
931
+ ]
932
+ };
933
+
934
+ // src/extensions/core/paragraph.ts
935
+ var ParagraphExtension = {
936
+ name: "paragraph",
937
+ parseRules: [],
938
+ renderRules: [
939
+ {
940
+ type: "paragraph",
812
941
  render: (token) => {
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
- if (this.config.format === "html") {
818
- return `<img src="${escapeHtml(src)}" alt="${escapeHtml(alt)}"${titleAttr} style="max-width: 100%; height: auto; border-radius: 8px; margin: 16px 0;" loading="lazy" />`;
942
+ if (!token.content) return "";
943
+ const content = token.content.trim();
944
+ if (!content) return "";
945
+ const processedContent = content.includes("<br>") ? content : escapeHtml(content);
946
+ const format = token.attributes?.format || "html";
947
+ if (format === "html") {
948
+ return `<p style="line-height: 1.75; margin-bottom: 16px;">${processedContent}</p>`;
819
949
  }
820
- return `<img src="${escapeHtml(src)}" alt="${escapeHtml(alt)}"${titleAttr} class="max-w-full h-auto rounded-lg my-4" loading="lazy" />`;
950
+ return `<p class="leading-7 mb-4">${processedContent}</p>`;
821
951
  }
822
- });
823
- this.addRule({
824
- type: "hr",
825
- render: () => {
826
- if (this.config.format === "html") {
827
- return '<hr style="margin: 24px 0; border: none; border-top: 1px solid #d1d5db;">';
828
- }
829
- return '<hr class="my-6 border-t border-border">';
952
+ }
953
+ ]
954
+ };
955
+
956
+ // src/extensions/core/text.ts
957
+ var TextExtension = {
958
+ name: "text",
959
+ parseRules: [],
960
+ renderRules: [
961
+ {
962
+ type: "text",
963
+ render: (token) => {
964
+ if (!token.content) return "";
965
+ return escapeHtml(token.content);
830
966
  }
831
- });
832
- this.addRule({
833
- type: "line-break",
834
- render: () => "<br>"
835
- });
836
- this.addRule({
837
- type: "paragraph-break",
838
- render: () => "</p><p>"
839
- });
840
- this.addRule({
841
- type: "soft-break",
842
- render: (token) => token.content || " "
843
- });
844
- }
967
+ }
968
+ ]
845
969
  };
846
970
 
971
+ // src/extensions/core/index.ts
972
+ var CoreExtensions = [
973
+ TextExtension,
974
+ HeadingExtension,
975
+ BoldExtension,
976
+ ItalicExtension,
977
+ InlineCodeExtension,
978
+ CodeBlockExtension,
979
+ LinkExtension,
980
+ ImageExtension,
981
+ ListExtension,
982
+ TaskListExtension,
983
+ BlockquoteExtension,
984
+ HorizontalRuleExtension,
985
+ ParagraphExtension,
986
+ LineBreakExtension
987
+ ];
988
+
847
989
  // src/extensions/alert.ts
848
990
  var AlertExtension = {
849
991
  name: "alert",
@@ -923,26 +1065,19 @@ var ButtonExtension = {
923
1065
  name: "button",
924
1066
  pattern: /\[button:([^\]]+)\]\(([^)]+)\)(?:\{([^}]+)\})?/,
925
1067
  render: (match) => {
926
- const text = match[1] || "";
927
- const href = match[2] || "";
928
- const optionsString = match[3] || "";
929
- const options = optionsString.split(",").map((opt) => opt.trim()).filter(Boolean);
930
- const styleOptions = ["default", "primary", "secondary", "success", "danger", "outline", "ghost"];
931
- const style = options.find((opt) => styleOptions.includes(opt)) || "primary";
932
- const sizeOptions = ["sm", "md", "lg"];
933
- const size = options.find((opt) => sizeOptions.includes(opt)) || "md";
934
- const disabled = options.includes("disabled");
935
- const target = options.includes("self") ? "_self" : "_blank";
1068
+ const options = match[3] ? match[3].split(",").map((opt) => opt.trim()) : [];
936
1069
  return {
937
1070
  type: "button",
938
- content: text,
1071
+ content: match[1] || "",
939
1072
  raw: match[0] || "",
940
1073
  attributes: {
941
- href,
942
- style,
943
- size,
944
- disabled: disabled.toString(),
945
- target
1074
+ href: match[2] || "",
1075
+ style: options.find(
1076
+ (opt) => ["default", "primary", "secondary", "success", "danger", "outline", "ghost"].includes(opt)
1077
+ ) || "primary",
1078
+ size: options.find((opt) => ["sm", "md", "lg"].includes(opt)) || "md",
1079
+ disabled: String(options.includes("disabled")),
1080
+ target: options.includes("self") ? "_self" : "_blank"
946
1081
  }
947
1082
  };
948
1083
  }
@@ -957,53 +1092,92 @@ var ButtonExtension = {
957
1092
  const size = token.attributes?.size || "md";
958
1093
  const disabled = token.attributes?.disabled === "true";
959
1094
  const target = token.attributes?.target || "_blank";
960
- const text = token.content;
961
- const classes = buildButtonClasses(style, size);
1095
+ const baseClasses = `
1096
+ inline-flex items-center justify-center font-medium rounded-lg
1097
+ transition-all duration-200 ease-out
1098
+ focus:outline-none focus:ring-2 focus:ring-offset-2
1099
+ disabled:opacity-50 disabled:cursor-not-allowed disabled:pointer-events-none
1100
+ transform hover:scale-[1.02] active:scale-[0.98]
1101
+ shadow-sm hover:shadow-md active:shadow-sm
1102
+ border border-transparent
1103
+ relative overflow-hidden
1104
+ before:absolute before:inset-0 before:rounded-lg
1105
+ before:bg-gradient-to-br before:from-white/20 before:to-transparent
1106
+ before:opacity-0 hover:before:opacity-100 before:transition-opacity before:duration-200
1107
+ `.replace(/\s+/g, " ").trim();
1108
+ const sizeClasses = {
1109
+ sm: "px-3 py-1.5 text-sm gap-1.5",
1110
+ md: "px-4 py-2 text-base gap-2",
1111
+ lg: "px-6 py-3 text-lg gap-2.5"
1112
+ };
1113
+ const styleClasses = {
1114
+ default: `
1115
+ bg-slate-600 text-white border-slate-500
1116
+ hover:bg-slate-700 hover:border-slate-400
1117
+ focus:ring-slate-500
1118
+ shadow-[0_1px_0_0_rgba(255,255,255,0.1)_inset,0_1px_2px_0_rgba(0,0,0,0.1)]
1119
+ hover:shadow-[0_1px_0_0_rgba(255,255,255,0.15)_inset,0_2px_4px_0_rgba(0,0,0,0.15)]
1120
+ `,
1121
+ primary: `
1122
+ bg-blue-600 text-white border-blue-500
1123
+ hover:bg-blue-700 hover:border-blue-400
1124
+ focus:ring-blue-500
1125
+ shadow-[0_1px_0_0_rgba(255,255,255,0.1)_inset,0_1px_2px_0_rgba(0,0,0,0.1)]
1126
+ hover:shadow-[0_1px_0_0_rgba(255,255,255,0.15)_inset,0_2px_4px_0_rgba(0,0,0,0.15)]
1127
+ `,
1128
+ secondary: `
1129
+ bg-gray-600 text-white border-gray-500
1130
+ hover:bg-gray-700 hover:border-gray-400
1131
+ focus:ring-gray-500
1132
+ shadow-[0_1px_0_0_rgba(255,255,255,0.1)_inset,0_1px_2px_0_rgba(0,0,0,0.1)]
1133
+ hover:shadow-[0_1px_0_0_rgba(255,255,255,0.15)_inset,0_2px_4px_0_rgba(0,0,0,0.15)]
1134
+ `,
1135
+ success: `
1136
+ bg-green-600 text-white border-green-500
1137
+ hover:bg-green-700 hover:border-green-400
1138
+ focus:ring-green-500
1139
+ shadow-[0_1px_0_0_rgba(255,255,255,0.1)_inset,0_1px_2px_0_rgba(0,0,0,0.1)]
1140
+ hover:shadow-[0_1px_0_0_rgba(255,255,255,0.15)_inset,0_2px_4px_0_rgba(0,0,0,0.15)]
1141
+ `,
1142
+ danger: `
1143
+ bg-red-600 text-white border-red-500
1144
+ hover:bg-red-700 hover:border-red-400
1145
+ focus:ring-red-500
1146
+ shadow-[0_1px_0_0_rgba(255,255,255,0.1)_inset,0_1px_2px_0_rgba(0,0,0,0.1)]
1147
+ hover:shadow-[0_1px_0_0_rgba(255,255,255,0.15)_inset,0_2px_4px_0_rgba(0,0,0,0.15)]
1148
+ `,
1149
+ outline: `
1150
+ bg-transparent text-blue-600 border-blue-600
1151
+ hover:bg-blue-50 hover:border-blue-700 hover:text-blue-700
1152
+ focus:ring-blue-500
1153
+ shadow-[0_0_0_1px_rgba(59,130,246,0.5)_inset]
1154
+ hover:shadow-[0_0_0_1px_rgba(29,78,216,0.6)_inset,0_1px_2px_0_rgba(0,0,0,0.05)]
1155
+ `,
1156
+ ghost: `
1157
+ bg-transparent text-gray-700 border-transparent
1158
+ hover:bg-gray-100 hover:text-gray-900
1159
+ focus:ring-gray-500
1160
+ shadow-none
1161
+ hover:shadow-[0_1px_2px_0_rgba(0,0,0,0.05)]
1162
+ `
1163
+ };
1164
+ const classes = `
1165
+ ${baseClasses}
1166
+ ${sizeClasses[size] || sizeClasses.md}
1167
+ ${styleClasses[style] || styleClasses.primary}
1168
+ `.replace(/\s+/g, " ").trim();
962
1169
  const targetAttr = target === "_blank" ? ' target="_blank" rel="noopener noreferrer"' : target === "_self" ? ' target="_self"' : "";
963
1170
  const disabledAttr = disabled ? ' aria-disabled="true" tabindex="-1"' : "";
964
- 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>' : "";
1171
+ const externalIcon = target === "_blank" && !disabled ? `<svg class="w-4 h-4 ml-1 opacity-75" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1172
+ <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"/>
1173
+ </svg>` : "";
965
1174
  return `<a href="${href}" class="${classes}"${targetAttr}${disabledAttr}>
966
- ${text}${externalIcon}
967
- </a>`;
1175
+ <span class="relative z-10">${token.content}</span>${externalIcon}
1176
+ </a>`;
968
1177
  }
969
1178
  }
970
1179
  ]
971
1180
  };
972
- function buildButtonClasses(style, size) {
973
- const base = [
974
- "inline-flex",
975
- "items-center",
976
- "justify-center",
977
- "font-medium",
978
- "rounded-lg",
979
- "transition-colors",
980
- "focus:outline-none",
981
- "focus:ring-2",
982
- "focus:ring-offset-2",
983
- "disabled:opacity-50",
984
- "disabled:cursor-not-allowed"
985
- ];
986
- const sizes = {
987
- sm: ["px-3", "py-1.5", "text-sm"],
988
- md: ["px-4", "py-2", "text-base"],
989
- lg: ["px-6", "py-3", "text-lg"]
990
- };
991
- const styles = {
992
- default: ["bg-slate-600", "text-white", "hover:bg-slate-700", "focus:ring-slate-500"],
993
- primary: ["bg-blue-600", "text-white", "hover:bg-blue-700", "focus:ring-blue-500"],
994
- secondary: ["bg-gray-600", "text-white", "hover:bg-gray-700", "focus:ring-gray-500"],
995
- success: ["bg-green-600", "text-white", "hover:bg-green-700", "focus:ring-green-500"],
996
- danger: ["bg-red-600", "text-white", "hover:bg-red-700", "focus:ring-red-500"],
997
- outline: ["border", "border-blue-600", "text-blue-600", "hover:bg-blue-50", "focus:ring-blue-500"],
998
- ghost: ["text-gray-700", "hover:bg-gray-100", "focus:ring-gray-500"]
999
- };
1000
- const allClasses = [
1001
- ...base,
1002
- ...sizes[size] ?? sizes.md ?? [],
1003
- ...styles[style] ?? styles.primary ?? []
1004
- ];
1005
- return allClasses.join(" ");
1006
- }
1007
1181
 
1008
1182
  // src/extensions/embed.ts
1009
1183
  var EmbedExtension = {
@@ -1081,8 +1255,7 @@ function renderYouTubeEmbed(url, options, classes) {
1081
1255
  params.set("rel", "0");
1082
1256
  params.set("modestbranding", "1");
1083
1257
  const embedUrl = `https://www.youtube.com/embed/${videoId}?${params.toString()}`;
1084
- return `
1085
- <div class="${classes}">
1258
+ return `<div class="${classes}">
1086
1259
  <div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
1087
1260
  <iframe
1088
1261
  style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 0;"
@@ -1110,8 +1283,7 @@ function renderCodePenEmbed(url, options, classes) {
1110
1283
  "editable": "true"
1111
1284
  });
1112
1285
  const embedUrl = `https://codepen.io/${user}/embed/${penId}?${embedParams.toString()}`;
1113
- return `
1114
- <div class="${classes}">
1286
+ return `<div class="${classes}">
1115
1287
  <iframe
1116
1288
  height="${height}"
1117
1289
  style="width: 100%; border: 0;"
@@ -1135,8 +1307,7 @@ function renderVimeoEmbed(url, options, classes) {
1135
1307
  if (options.mute === "1") params.set("muted", "1");
1136
1308
  if (options.loop === "1") params.set("loop", "1");
1137
1309
  const embedUrl = `https://player.vimeo.com/video/${videoId}?${params.toString()}`;
1138
- return `
1139
- <div class="${classes}">
1310
+ return `<div class="${classes}">
1140
1311
  <div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
1141
1312
  <iframe
1142
1313
  style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 0;"
@@ -1152,8 +1323,7 @@ function renderVimeoEmbed(url, options, classes) {
1152
1323
  function renderSpotifyEmbed(url, options, classes) {
1153
1324
  const embedUrl = url.replace("open.spotify.com", "open.spotify.com/embed");
1154
1325
  const height = options.height || "380";
1155
- return `
1156
- <div class="${classes}">
1326
+ return `<div class="${classes}">
1157
1327
  <iframe
1158
1328
  style="border-radius: 12px;"
1159
1329
  src="${embedUrl}"
@@ -1176,8 +1346,7 @@ function renderCodeSandboxEmbed(url, options, classes) {
1176
1346
  if (!embedUrl.includes("?")) {
1177
1347
  embedUrl += `?view=${view}`;
1178
1348
  }
1179
- return `
1180
- <div class="${classes}">
1349
+ return `<div class="${classes}">
1181
1350
  <iframe
1182
1351
  src="${embedUrl}"
1183
1352
  style="width: 100%; height: ${height}px; border: 0; border-radius: 4px; overflow: hidden;"
@@ -1190,8 +1359,7 @@ function renderCodeSandboxEmbed(url, options, classes) {
1190
1359
  function renderFigmaEmbed(url, options, classes) {
1191
1360
  const embedUrl = `https://www.figma.com/embed?embed_host=share&url=${encodeURIComponent(url)}`;
1192
1361
  const height = options.height || "450";
1193
- return `
1194
- <div class="${classes}">
1362
+ return `<div class="${classes}">
1195
1363
  <iframe
1196
1364
  style="border: none;"
1197
1365
  width="100%"
@@ -1202,8 +1370,7 @@ function renderFigmaEmbed(url, options, classes) {
1202
1370
  </div>`;
1203
1371
  }
1204
1372
  function renderTwitterEmbed(url, _options, classes) {
1205
- return `
1206
- <div class="${classes}">
1373
+ return `<div class="${classes}">
1207
1374
  <div class="p-4">
1208
1375
  <div class="flex items-center gap-3 mb-3">
1209
1376
  <svg class="w-6 h-6 fill-current text-blue-500" viewBox="0 0 24 24">
@@ -1231,8 +1398,7 @@ function renderGitHubEmbed(url, _options, classes) {
1231
1398
  if (!owner || !repo) {
1232
1399
  return createErrorEmbed("Invalid GitHub URL", url, classes);
1233
1400
  }
1234
- return `
1235
- <div class="${classes}">
1401
+ return `<div class="${classes}">
1236
1402
  <div class="p-4">
1237
1403
  <div class="flex items-center gap-3 mb-3">
1238
1404
  <svg class="w-6 h-6 fill-current" viewBox="0 0 24 24">
@@ -1255,8 +1421,7 @@ function renderGitHubEmbed(url, _options, classes) {
1255
1421
  }
1256
1422
  function renderGenericEmbed(url, _options, classes) {
1257
1423
  const domain = extractDomain(url);
1258
- return `
1259
- <div class="${classes}">
1424
+ return `<div class="${classes}">
1260
1425
  <div class="p-4">
1261
1426
  <div class="flex items-center gap-3 mb-3">
1262
1427
  <div class="w-10 h-10 rounded-lg bg-muted flex items-center justify-center">
@@ -1280,8 +1445,7 @@ function renderGenericEmbed(url, _options, classes) {
1280
1445
  </div>`;
1281
1446
  }
1282
1447
  function createErrorEmbed(error, url, classes) {
1283
- return `
1284
- <div class="${classes}">
1448
+ return `<div class="${classes}">
1285
1449
  <div class="p-4 text-destructive">
1286
1450
  <div class="font-medium flex items-center gap-2">
1287
1451
  <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -1315,19 +1479,54 @@ var ChangerawrMarkdown = class {
1315
1479
  this.extensions = /* @__PURE__ */ new Map();
1316
1480
  this.parser = new MarkdownParser(config?.parser);
1317
1481
  this.renderer = new MarkdownRenderer(config?.renderer);
1318
- this.registerBuiltInExtensions();
1482
+ this.registerCoreExtensions();
1483
+ this.registerFeatureExtensions();
1319
1484
  if (config?.extensions) {
1320
1485
  config.extensions.forEach((extension) => {
1321
1486
  this.registerExtension(extension);
1322
1487
  });
1323
1488
  }
1324
- this.parser.setupDefaultRulesIfEmpty();
1325
1489
  }
1326
- /**
1327
- * Register a custom extension
1328
- */
1490
+ registerFeatureExtensions() {
1491
+ this.registerExtension(AlertExtension);
1492
+ this.registerExtension(ButtonExtension);
1493
+ this.registerExtension(EmbedExtension);
1494
+ }
1495
+ registerCoreExtensions() {
1496
+ CoreExtensions.forEach((extension) => {
1497
+ this.registerExtension(extension);
1498
+ });
1499
+ }
1329
1500
  registerExtension(extension) {
1330
1501
  try {
1502
+ if (!extension.name || typeof extension.name !== "string") {
1503
+ throw new Error("Extension must have a valid name");
1504
+ }
1505
+ if (!Array.isArray(extension.parseRules)) {
1506
+ throw new Error("Extension must have parseRules array");
1507
+ }
1508
+ if (!Array.isArray(extension.renderRules)) {
1509
+ throw new Error("Extension must have renderRules array");
1510
+ }
1511
+ extension.parseRules.forEach((rule, index) => {
1512
+ if (!rule.name || typeof rule.name !== "string") {
1513
+ throw new Error(`Parse rule ${index} must have a valid name`);
1514
+ }
1515
+ if (!rule.pattern || !(rule.pattern instanceof RegExp)) {
1516
+ throw new Error(`Parse rule ${index} must have a valid RegExp pattern`);
1517
+ }
1518
+ if (!rule.render || typeof rule.render !== "function") {
1519
+ throw new Error(`Parse rule ${index} must have a valid render function`);
1520
+ }
1521
+ });
1522
+ extension.renderRules.forEach((rule, index) => {
1523
+ if (!rule.type || typeof rule.type !== "string") {
1524
+ throw new Error(`Render rule ${index} must have a valid type`);
1525
+ }
1526
+ if (!rule.render || typeof rule.render !== "function") {
1527
+ throw new Error(`Render rule ${index} must have a valid render function`);
1528
+ }
1529
+ });
1331
1530
  this.extensions.set(extension.name, extension);
1332
1531
  extension.parseRules.forEach((rule) => {
1333
1532
  this.parser.addRule(rule);
@@ -1348,9 +1547,6 @@ var ChangerawrMarkdown = class {
1348
1547
  };
1349
1548
  }
1350
1549
  }
1351
- /**
1352
- * Unregister an extension
1353
- */
1354
1550
  unregisterExtension(name) {
1355
1551
  const extension = this.extensions.get(name);
1356
1552
  if (!extension) {
@@ -1364,46 +1560,25 @@ var ChangerawrMarkdown = class {
1364
1560
  return false;
1365
1561
  }
1366
1562
  }
1367
- /**
1368
- * Parse markdown content into tokens
1369
- */
1370
1563
  parse(markdown2) {
1371
1564
  return this.parser.parse(markdown2);
1372
1565
  }
1373
- /**
1374
- * Render tokens to HTML
1375
- */
1376
1566
  render(tokens) {
1377
1567
  return this.renderer.render(tokens);
1378
1568
  }
1379
- /**
1380
- * Parse and render markdown to HTML in one step
1381
- */
1382
1569
  toHtml(markdown2) {
1383
1570
  const tokens = this.parse(markdown2);
1384
1571
  return this.render(tokens);
1385
1572
  }
1386
- /**
1387
- * Get list of registered extensions
1388
- */
1389
1573
  getExtensions() {
1390
1574
  return Array.from(this.extensions.keys());
1391
1575
  }
1392
- /**
1393
- * Check if extension is registered
1394
- */
1395
1576
  hasExtension(name) {
1396
1577
  return this.extensions.has(name);
1397
1578
  }
1398
- /**
1399
- * Get parser warnings
1400
- */
1401
1579
  getWarnings() {
1402
1580
  return [...this.parser.getWarnings(), ...this.renderer.getWarnings()];
1403
1581
  }
1404
- /**
1405
- * Get debug information from last render
1406
- */
1407
1582
  getDebugInfo() {
1408
1583
  return {
1409
1584
  warnings: this.getWarnings(),
@@ -1413,9 +1588,6 @@ var ChangerawrMarkdown = class {
1413
1588
  iterationCount: 0
1414
1589
  };
1415
1590
  }
1416
- /**
1417
- * Get performance metrics for the last operation
1418
- */
1419
1591
  getPerformanceMetrics() {
1420
1592
  return {
1421
1593
  parseTime: 0,
@@ -1424,28 +1596,29 @@ var ChangerawrMarkdown = class {
1424
1596
  tokenCount: 0
1425
1597
  };
1426
1598
  }
1427
- /**
1428
- * Register built-in extensions
1429
- */
1430
- registerBuiltInExtensions() {
1431
- this.registerExtension(AlertExtension);
1432
- this.registerExtension(ButtonExtension);
1433
- this.registerExtension(EmbedExtension);
1434
- }
1435
- /**
1436
- * Rebuild parser and renderer with current extensions
1437
- */
1438
1599
  rebuildParserAndRenderer() {
1439
1600
  const parserConfig = this.parser.getConfig();
1440
1601
  const rendererConfig = this.renderer.getConfig();
1441
1602
  this.parser = new MarkdownParser(parserConfig);
1442
1603
  this.renderer = new MarkdownRenderer(rendererConfig);
1443
1604
  const extensionsToRegister = Array.from(this.extensions.values());
1444
- this.extensions.clear();
1445
- extensionsToRegister.forEach((extension) => {
1446
- this.registerExtension(extension);
1605
+ const featureExtensions = extensionsToRegister.filter(
1606
+ (ext) => ["alert", "button", "embed"].includes(ext.name)
1607
+ );
1608
+ const coreExtensions = extensionsToRegister.filter(
1609
+ (ext) => ["text", "heading", "bold", "italic", "code", "codeblock", "link", "image", "list", "task-list", "blockquote", "hr", "paragraph", "line-break"].includes(ext.name)
1610
+ );
1611
+ const customExtensions = extensionsToRegister.filter(
1612
+ (ext) => !["alert", "button", "embed", "text", "heading", "bold", "italic", "code", "codeblock", "link", "image", "list", "task-list", "blockquote", "hr", "paragraph", "line-break"].includes(ext.name)
1613
+ );
1614
+ [...featureExtensions, ...coreExtensions, ...customExtensions].forEach((extension) => {
1615
+ extension.parseRules.forEach((rule) => {
1616
+ this.parser.addRule(rule);
1617
+ });
1618
+ extension.renderRules.forEach((rule) => {
1619
+ this.renderer.addRule(rule);
1620
+ });
1447
1621
  });
1448
- this.parser.setupDefaultRulesIfEmpty();
1449
1622
  }
1450
1623
  };
1451
1624
  var markdown = new ChangerawrMarkdown();