@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.
@@ -1,5 +1,3 @@
1
- "use client";
2
-
3
1
  // src/react/MarkdownRenderer.tsx
4
2
  import React, { useMemo as useMemo2, useEffect as useEffect2 } from "react";
5
3
 
@@ -11,39 +9,56 @@ var MarkdownParser = class {
11
9
  constructor(config) {
12
10
  this.rules = [];
13
11
  this.warnings = [];
14
- this.config = config || {};
12
+ this.config = {
13
+ debugMode: false,
14
+ maxIterations: 1e4,
15
+ validateMarkdown: false,
16
+ ...config
17
+ };
15
18
  }
16
19
  addRule(rule) {
17
20
  this.rules.push(rule);
18
21
  this.rules.sort((a, b) => {
19
- if (a.name.includes("alert") || a.name.includes("embed") || a.name.includes("button")) return -1;
20
- if (b.name.includes("alert") || b.name.includes("embed") || b.name.includes("button")) return 1;
21
- if (a.name === "task-list") return -1;
22
- if (b.name === "task-list") return 1;
23
- if (a.name === "list" && b.name === "task-list") return 1;
24
- 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;
25
38
  return a.name.localeCompare(b.name);
26
39
  });
27
40
  }
28
- setupDefaultRulesIfEmpty() {
29
- const hasDefaultRules = this.rules.some(
30
- (rule) => !["alert", "button", "embed"].includes(rule.name)
31
- );
32
- if (!hasDefaultRules) {
33
- this.setupDefaultRules();
34
- }
35
- }
36
41
  hasRule(name) {
37
42
  return this.rules.some((rule) => rule.name === name);
38
43
  }
39
44
  parse(markdown2) {
40
45
  this.warnings = [];
41
- 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
+ }
42
57
  const processedMarkdown = this.preprocessMarkdown(markdown2);
43
58
  const tokens = [];
44
59
  let remaining = processedMarkdown;
45
60
  let iterationCount = 0;
46
- const maxIterations = this.config.maxIterations || markdown2.length * 2;
61
+ const maxIterations = this.config.maxIterations || 1e4;
47
62
  while (remaining.length > 0 && iterationCount < maxIterations) {
48
63
  iterationCount++;
49
64
  let matched = false;
@@ -132,9 +147,6 @@ var MarkdownParser = class {
132
147
  clearWarnings() {
133
148
  this.warnings = [];
134
149
  }
135
- getIterationCount() {
136
- return 0;
137
- }
138
150
  preprocessMarkdown(markdown2) {
139
151
  if (this.config.validateMarkdown) {
140
152
  this.validateMarkdown(markdown2);
@@ -189,161 +201,6 @@ var MarkdownParser = class {
189
201
  }
190
202
  return processed;
191
203
  }
192
- setupDefaultRules() {
193
- this.addRule({
194
- name: "heading",
195
- pattern: /^(#{1,6})\s+(.+)$/m,
196
- render: (match) => ({
197
- type: "heading",
198
- content: match[2]?.trim() || "",
199
- raw: match[0] || "",
200
- attributes: {
201
- level: String(match[1]?.length || 1)
202
- }
203
- })
204
- });
205
- this.addRule({
206
- name: "codeblock",
207
- pattern: /```(\w+)?\s*\n([\s\S]*?)\n```/,
208
- render: (match) => ({
209
- type: "codeblock",
210
- content: match[2] || "",
211
- raw: match[0] || "",
212
- attributes: {
213
- language: match[1] || "text"
214
- }
215
- })
216
- });
217
- this.addRule({
218
- name: "hard-break-backslash",
219
- pattern: /\\\s*\n/,
220
- render: (match) => ({
221
- type: "line-break",
222
- content: "",
223
- raw: match[0] || ""
224
- })
225
- });
226
- this.addRule({
227
- name: "hard-break-spaces",
228
- pattern: / +\n/,
229
- render: (match) => ({
230
- type: "line-break",
231
- content: "",
232
- raw: match[0] || ""
233
- })
234
- });
235
- this.addRule({
236
- name: "paragraph-break",
237
- pattern: /\n\s*\n/,
238
- render: (match) => ({
239
- type: "paragraph-break",
240
- content: "",
241
- raw: match[0] || ""
242
- })
243
- });
244
- this.addRule({
245
- name: "bold",
246
- pattern: /\*\*((?:(?!\*\*).)+)\*\*/,
247
- render: (match) => ({
248
- type: "bold",
249
- content: match[1] || "",
250
- raw: match[0] || ""
251
- })
252
- });
253
- this.addRule({
254
- name: "italic",
255
- pattern: /\*((?:(?!\*).)+)\*/,
256
- render: (match) => ({
257
- type: "italic",
258
- content: match[1] || "",
259
- raw: match[0] || ""
260
- })
261
- });
262
- this.addRule({
263
- name: "code",
264
- pattern: /`([^`]+)`/,
265
- render: (match) => ({
266
- type: "code",
267
- content: match[1] || "",
268
- raw: match[0] || ""
269
- })
270
- });
271
- this.addRule({
272
- name: "image",
273
- pattern: /!\[([^\]]*)\]\(([^)]+?)(?:\s+"([^"]+)")?\)/,
274
- render: (match) => ({
275
- type: "image",
276
- content: match[1] || "",
277
- raw: match[0] || "",
278
- attributes: {
279
- alt: match[1] || "",
280
- src: match[2] || "",
281
- title: match[3] || ""
282
- }
283
- })
284
- });
285
- this.addRule({
286
- name: "link",
287
- pattern: /\[([^\]]+)\]\(([^)]+)\)/,
288
- render: (match) => ({
289
- type: "link",
290
- content: match[1] || "",
291
- raw: match[0] || "",
292
- attributes: {
293
- href: match[2] || ""
294
- }
295
- })
296
- });
297
- this.addRule({
298
- name: "task-list",
299
- pattern: /^(\s*)-\s*\[([ xX])\]\s*(.+)$/m,
300
- render: (match) => ({
301
- type: "task-item",
302
- content: match[3] || "",
303
- raw: match[0] || "",
304
- attributes: {
305
- checked: String((match[2] || "").toLowerCase() === "x")
306
- }
307
- })
308
- });
309
- this.addRule({
310
- name: "list",
311
- pattern: /^(\s*)[-*+]\s+(.+)$/m,
312
- render: (match) => ({
313
- type: "list-item",
314
- content: match[2] || "",
315
- raw: match[0] || ""
316
- })
317
- });
318
- this.addRule({
319
- name: "blockquote",
320
- pattern: /^>\s+(.+)$/m,
321
- render: (match) => ({
322
- type: "blockquote",
323
- content: match[1] || "",
324
- raw: match[0] || ""
325
- })
326
- });
327
- this.addRule({
328
- name: "hr",
329
- pattern: /^---$/m,
330
- render: (match) => ({
331
- type: "hr",
332
- content: "",
333
- raw: match[0] || ""
334
- })
335
- });
336
- this.addRule({
337
- name: "soft-break",
338
- pattern: /\n/,
339
- render: (match) => ({
340
- type: "soft-break",
341
- content: " ",
342
- // Convert to space for inline text
343
- raw: match[0] || ""
344
- })
345
- });
346
- }
347
204
  };
348
205
 
349
206
  // src/utils.ts
@@ -582,7 +439,6 @@ var MarkdownRenderer = class {
582
439
  debugMode: false,
583
440
  ...config
584
441
  };
585
- this.setupDefaultRules();
586
442
  }
587
443
  addRule(rule) {
588
444
  this.rules.set(rule.type, rule);
@@ -592,28 +448,20 @@ var MarkdownRenderer = class {
592
448
  }
593
449
  render(tokens) {
594
450
  this.warnings = [];
595
- 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));
596
459
  const combinedHtml = htmlParts.join("");
597
460
  if (this.config.sanitize && !this.config.allowUnsafeHtml) {
598
461
  return sanitizeHtml(combinedHtml);
599
462
  }
600
463
  return combinedHtml;
601
464
  }
602
- getWarnings() {
603
- return [...this.warnings];
604
- }
605
- getConfig() {
606
- return { ...this.config };
607
- }
608
- updateConfig(config) {
609
- this.config = { ...this.config, ...config };
610
- }
611
- setDebugMode(enabled) {
612
- this.config.debugMode = enabled;
613
- }
614
- clearWarnings() {
615
- this.warnings = [];
616
- }
617
465
  renderToken(token) {
618
466
  const rule = this.rules.get(token.type);
619
467
  if (rule) {
@@ -625,9 +473,6 @@ var MarkdownRenderer = class {
625
473
  return this.createErrorBlock(`Render error for ${token.type}: ${errorMessage}`);
626
474
  }
627
475
  }
628
- if (token.type === "text") {
629
- return escapeHtml(token.content || token.raw || "");
630
- }
631
476
  if (this.config.debugMode) {
632
477
  return this.createDebugBlock(token);
633
478
  }
@@ -650,20 +495,239 @@ var MarkdownRenderer = class {
650
495
  <strong>Content:</strong> ${escapeHtml(token.content || token.raw || "")}
651
496
  </div>`;
652
497
  }
653
- return `<div class="bg-yellow-100 border border-yellow-300 text-yellow-800 p-2 rounded text-sm mb-2">
654
- <strong>Unknown token type:</strong> ${escapeHtml(token.type)}<br>
655
- <strong>Content:</strong> ${escapeHtml(token.content || token.raw || "")}
656
- </div>`;
657
- }
658
- setupDefaultRules() {
659
- 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
+ {
660
723
  type: "heading",
661
724
  render: (token) => {
662
725
  const level = parseInt(token.attributes?.level || "1");
663
726
  const text = token.content;
664
727
  const id = generateId(text);
665
728
  const escapedContent = escapeHtml(text);
666
- if (this.config.format === "html") {
729
+ const format = token.attributes?.format || "html";
730
+ if (format === "html") {
667
731
  return `<h${level} id="${id}">${escapedContent}</h${level}>`;
668
732
  }
669
733
  let headingClasses = "group relative flex items-center gap-2";
@@ -690,61 +754,108 @@ var MarkdownRenderer = class {
690
754
  return `<h${level} id="${id}" class="${headingClasses}">
691
755
  ${escapedContent}
692
756
  <a href="#${id}" class="opacity-0 group-hover:opacity-100 text-muted-foreground transition-opacity">
693
- <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
694
- <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"/>
695
- </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>
696
761
  </a>
697
762
  </h${level}>`;
698
763
  }
699
- });
700
- this.addRule({
701
- 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",
702
785
  render: (token) => {
703
- const content = escapeHtml(token.content);
704
- if (this.config.format === "html") {
705
- 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;">';
706
789
  }
707
- return `<strong class="font-bold">${content}</strong>`;
790
+ return '<hr class="my-6 border-t border-border">';
708
791
  }
709
- });
710
- this.addRule({
711
- type: "italic",
712
- render: (token) => {
713
- const content = escapeHtml(token.content);
714
- if (this.config.format === "html") {
715
- 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] || ""
716
811
  }
717
- return `<em class="italic">${content}</em>`;
718
- }
719
- });
720
- this.addRule({
721
- type: "code",
812
+ })
813
+ }
814
+ ],
815
+ renderRules: [
816
+ {
817
+ type: "image",
722
818
  render: (token) => {
723
- const content = escapeHtml(token.content);
724
- if (this.config.format === "html") {
725
- 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" />`;
726
826
  }
727
- 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" />`;
728
828
  }
729
- });
730
- this.addRule({
731
- type: "codeblock",
732
- render: (token) => {
733
- const language = token.attributes?.language || "text";
734
- const escapedCode = escapeHtml(token.content);
735
- if (this.config.format === "html") {
736
- 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] || ""
737
846
  }
738
- return `<pre class="bg-muted p-4 rounded-md overflow-x-auto my-4"><code class="language-${escapeHtml(language)}">${escapedCode}</code></pre>`;
739
- }
740
- });
741
- this.addRule({
847
+ })
848
+ }
849
+ ],
850
+ renderRules: [
851
+ {
742
852
  type: "link",
743
853
  render: (token) => {
744
854
  const href = token.attributes?.href || "#";
745
855
  const escapedHref = escapeHtml(href);
746
856
  const escapedText = escapeHtml(token.content);
747
- if (this.config.format === "html") {
857
+ const format = token.attributes?.format || "html";
858
+ if (format === "html") {
748
859
  return `<a href="${escapedHref}" target="_blank" rel="noopener noreferrer">${escapedText}</a>`;
749
860
  }
750
861
  return `<a href="${escapedHref}" class="text-primary hover:underline inline-flex items-center gap-1" target="_blank" rel="noopener noreferrer">
@@ -756,47 +867,55 @@ var MarkdownRenderer = class {
756
867
  </svg>
757
868
  </a>`;
758
869
  }
759
- });
760
- 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
+ {
761
890
  type: "list-item",
762
891
  render: (token) => `<li>${escapeHtml(token.content)}</li>`
763
- });
764
- this.addRule({
765
- type: "blockquote",
766
- render: (token) => {
767
- const content = escapeHtml(token.content);
768
- if (this.config.format === "html") {
769
- return `<blockquote style="border-left: 2px solid #d1d5db; padding: 8px 0 8px 16px; margin: 16px 0; font-style: italic; color: #6b7280;">${content}</blockquote>`;
770
- }
771
- return `<blockquote class="pl-4 py-2 border-l-2 border-border italic text-muted-foreground my-4">${content}</blockquote>`;
772
- }
773
- });
774
- this.addRule({
775
- type: "text",
776
- render: (token) => {
777
- if (!token.content) return "";
778
- return escapeHtml(token.content);
779
- }
780
- });
781
- this.addRule({
782
- type: "paragraph",
783
- render: (token) => {
784
- if (!token.content) return "";
785
- const content = token.content.trim();
786
- if (!content) return "";
787
- const processedContent = content.includes("<br>") ? content : escapeHtml(content);
788
- if (this.config.format === "html") {
789
- 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")
790
907
  }
791
- return `<p class="leading-7 mb-4">${processedContent}</p>`;
792
- }
793
- });
794
- this.addRule({
908
+ })
909
+ }
910
+ ],
911
+ renderRules: [
912
+ {
795
913
  type: "task-item",
796
914
  render: (token) => {
797
915
  const isChecked = token.attributes?.checked === "true";
798
916
  const escapedContent = escapeHtml(token.content);
799
- if (this.config.format === "html") {
917
+ const format = token.attributes?.format || "html";
918
+ if (format === "html") {
800
919
  return `<div style="display: flex; align-items: center; gap: 8px; margin: 8px 0;">
801
920
  <input type="checkbox" ${isChecked ? "checked" : ""} disabled style="margin: 0;" />
802
921
  <span${isChecked ? ' style="text-decoration: line-through; color: #6b7280;"' : ""}>${escapedContent}</span>
@@ -808,44 +927,65 @@ var MarkdownRenderer = class {
808
927
  <span${isChecked ? ' class="line-through text-muted-foreground"' : ""}>${escapedContent}</span>
809
928
  </div>`;
810
929
  }
811
- });
812
- this.addRule({
813
- 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",
814
941
  render: (token) => {
815
- const src = token.attributes?.src || "";
816
- const alt = token.attributes?.alt || "";
817
- const title = token.attributes?.title || "";
818
- const titleAttr = title ? ` title="${escapeHtml(title)}"` : "";
819
- if (this.config.format === "html") {
820
- 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>`;
821
949
  }
822
- 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>`;
823
951
  }
824
- });
825
- this.addRule({
826
- type: "hr",
827
- render: () => {
828
- if (this.config.format === "html") {
829
- return '<hr style="margin: 24px 0; border: none; border-top: 1px solid #d1d5db;">';
830
- }
831
- 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);
832
966
  }
833
- });
834
- this.addRule({
835
- type: "line-break",
836
- render: () => "<br>"
837
- });
838
- this.addRule({
839
- type: "paragraph-break",
840
- render: () => "</p><p>"
841
- });
842
- this.addRule({
843
- type: "soft-break",
844
- render: (token) => token.content || " "
845
- });
846
- }
967
+ }
968
+ ]
847
969
  };
848
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
+
849
989
  // src/extensions/alert.ts
850
990
  var AlertExtension = {
851
991
  name: "alert",
@@ -925,26 +1065,19 @@ var ButtonExtension = {
925
1065
  name: "button",
926
1066
  pattern: /\[button:([^\]]+)\]\(([^)]+)\)(?:\{([^}]+)\})?/,
927
1067
  render: (match) => {
928
- const text = match[1] || "";
929
- const href = match[2] || "";
930
- const optionsString = match[3] || "";
931
- const options = optionsString.split(",").map((opt) => opt.trim()).filter(Boolean);
932
- const styleOptions = ["default", "primary", "secondary", "success", "danger", "outline", "ghost"];
933
- const style = options.find((opt) => styleOptions.includes(opt)) || "primary";
934
- const sizeOptions = ["sm", "md", "lg"];
935
- const size = options.find((opt) => sizeOptions.includes(opt)) || "md";
936
- const disabled = options.includes("disabled");
937
- const target = options.includes("self") ? "_self" : "_blank";
1068
+ const options = match[3] ? match[3].split(",").map((opt) => opt.trim()) : [];
938
1069
  return {
939
1070
  type: "button",
940
- content: text,
1071
+ content: match[1] || "",
941
1072
  raw: match[0] || "",
942
1073
  attributes: {
943
- href,
944
- style,
945
- size,
946
- disabled: disabled.toString(),
947
- 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"
948
1081
  }
949
1082
  };
950
1083
  }
@@ -959,53 +1092,92 @@ var ButtonExtension = {
959
1092
  const size = token.attributes?.size || "md";
960
1093
  const disabled = token.attributes?.disabled === "true";
961
1094
  const target = token.attributes?.target || "_blank";
962
- const text = token.content;
963
- 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();
964
1169
  const targetAttr = target === "_blank" ? ' target="_blank" rel="noopener noreferrer"' : target === "_self" ? ' target="_self"' : "";
965
1170
  const disabledAttr = disabled ? ' aria-disabled="true" tabindex="-1"' : "";
966
- 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>` : "";
967
1174
  return `<a href="${href}" class="${classes}"${targetAttr}${disabledAttr}>
968
- ${text}${externalIcon}
969
- </a>`;
1175
+ <span class="relative z-10">${token.content}</span>${externalIcon}
1176
+ </a>`;
970
1177
  }
971
1178
  }
972
1179
  ]
973
1180
  };
974
- function buildButtonClasses(style, size) {
975
- const base = [
976
- "inline-flex",
977
- "items-center",
978
- "justify-center",
979
- "font-medium",
980
- "rounded-lg",
981
- "transition-colors",
982
- "focus:outline-none",
983
- "focus:ring-2",
984
- "focus:ring-offset-2",
985
- "disabled:opacity-50",
986
- "disabled:cursor-not-allowed"
987
- ];
988
- const sizes = {
989
- sm: ["px-3", "py-1.5", "text-sm"],
990
- md: ["px-4", "py-2", "text-base"],
991
- lg: ["px-6", "py-3", "text-lg"]
992
- };
993
- const styles = {
994
- default: ["bg-slate-600", "text-white", "hover:bg-slate-700", "focus:ring-slate-500"],
995
- primary: ["bg-blue-600", "text-white", "hover:bg-blue-700", "focus:ring-blue-500"],
996
- secondary: ["bg-gray-600", "text-white", "hover:bg-gray-700", "focus:ring-gray-500"],
997
- success: ["bg-green-600", "text-white", "hover:bg-green-700", "focus:ring-green-500"],
998
- danger: ["bg-red-600", "text-white", "hover:bg-red-700", "focus:ring-red-500"],
999
- outline: ["border", "border-blue-600", "text-blue-600", "hover:bg-blue-50", "focus:ring-blue-500"],
1000
- ghost: ["text-gray-700", "hover:bg-gray-100", "focus:ring-gray-500"]
1001
- };
1002
- const allClasses = [
1003
- ...base,
1004
- ...sizes[size] ?? sizes.md ?? [],
1005
- ...styles[style] ?? styles.primary ?? []
1006
- ];
1007
- return allClasses.join(" ");
1008
- }
1009
1181
 
1010
1182
  // src/extensions/embed.ts
1011
1183
  var EmbedExtension = {
@@ -1083,8 +1255,7 @@ function renderYouTubeEmbed(url, options, classes) {
1083
1255
  params.set("rel", "0");
1084
1256
  params.set("modestbranding", "1");
1085
1257
  const embedUrl = `https://www.youtube.com/embed/${videoId}?${params.toString()}`;
1086
- return `
1087
- <div class="${classes}">
1258
+ return `<div class="${classes}">
1088
1259
  <div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
1089
1260
  <iframe
1090
1261
  style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 0;"
@@ -1112,8 +1283,7 @@ function renderCodePenEmbed(url, options, classes) {
1112
1283
  "editable": "true"
1113
1284
  });
1114
1285
  const embedUrl = `https://codepen.io/${user}/embed/${penId}?${embedParams.toString()}`;
1115
- return `
1116
- <div class="${classes}">
1286
+ return `<div class="${classes}">
1117
1287
  <iframe
1118
1288
  height="${height}"
1119
1289
  style="width: 100%; border: 0;"
@@ -1137,8 +1307,7 @@ function renderVimeoEmbed(url, options, classes) {
1137
1307
  if (options.mute === "1") params.set("muted", "1");
1138
1308
  if (options.loop === "1") params.set("loop", "1");
1139
1309
  const embedUrl = `https://player.vimeo.com/video/${videoId}?${params.toString()}`;
1140
- return `
1141
- <div class="${classes}">
1310
+ return `<div class="${classes}">
1142
1311
  <div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
1143
1312
  <iframe
1144
1313
  style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 0;"
@@ -1154,8 +1323,7 @@ function renderVimeoEmbed(url, options, classes) {
1154
1323
  function renderSpotifyEmbed(url, options, classes) {
1155
1324
  const embedUrl = url.replace("open.spotify.com", "open.spotify.com/embed");
1156
1325
  const height = options.height || "380";
1157
- return `
1158
- <div class="${classes}">
1326
+ return `<div class="${classes}">
1159
1327
  <iframe
1160
1328
  style="border-radius: 12px;"
1161
1329
  src="${embedUrl}"
@@ -1178,8 +1346,7 @@ function renderCodeSandboxEmbed(url, options, classes) {
1178
1346
  if (!embedUrl.includes("?")) {
1179
1347
  embedUrl += `?view=${view}`;
1180
1348
  }
1181
- return `
1182
- <div class="${classes}">
1349
+ return `<div class="${classes}">
1183
1350
  <iframe
1184
1351
  src="${embedUrl}"
1185
1352
  style="width: 100%; height: ${height}px; border: 0; border-radius: 4px; overflow: hidden;"
@@ -1192,8 +1359,7 @@ function renderCodeSandboxEmbed(url, options, classes) {
1192
1359
  function renderFigmaEmbed(url, options, classes) {
1193
1360
  const embedUrl = `https://www.figma.com/embed?embed_host=share&url=${encodeURIComponent(url)}`;
1194
1361
  const height = options.height || "450";
1195
- return `
1196
- <div class="${classes}">
1362
+ return `<div class="${classes}">
1197
1363
  <iframe
1198
1364
  style="border: none;"
1199
1365
  width="100%"
@@ -1204,8 +1370,7 @@ function renderFigmaEmbed(url, options, classes) {
1204
1370
  </div>`;
1205
1371
  }
1206
1372
  function renderTwitterEmbed(url, _options, classes) {
1207
- return `
1208
- <div class="${classes}">
1373
+ return `<div class="${classes}">
1209
1374
  <div class="p-4">
1210
1375
  <div class="flex items-center gap-3 mb-3">
1211
1376
  <svg class="w-6 h-6 fill-current text-blue-500" viewBox="0 0 24 24">
@@ -1233,8 +1398,7 @@ function renderGitHubEmbed(url, _options, classes) {
1233
1398
  if (!owner || !repo) {
1234
1399
  return createErrorEmbed("Invalid GitHub URL", url, classes);
1235
1400
  }
1236
- return `
1237
- <div class="${classes}">
1401
+ return `<div class="${classes}">
1238
1402
  <div class="p-4">
1239
1403
  <div class="flex items-center gap-3 mb-3">
1240
1404
  <svg class="w-6 h-6 fill-current" viewBox="0 0 24 24">
@@ -1257,8 +1421,7 @@ function renderGitHubEmbed(url, _options, classes) {
1257
1421
  }
1258
1422
  function renderGenericEmbed(url, _options, classes) {
1259
1423
  const domain = extractDomain(url);
1260
- return `
1261
- <div class="${classes}">
1424
+ return `<div class="${classes}">
1262
1425
  <div class="p-4">
1263
1426
  <div class="flex items-center gap-3 mb-3">
1264
1427
  <div class="w-10 h-10 rounded-lg bg-muted flex items-center justify-center">
@@ -1282,8 +1445,7 @@ function renderGenericEmbed(url, _options, classes) {
1282
1445
  </div>`;
1283
1446
  }
1284
1447
  function createErrorEmbed(error, url, classes) {
1285
- return `
1286
- <div class="${classes}">
1448
+ return `<div class="${classes}">
1287
1449
  <div class="p-4 text-destructive">
1288
1450
  <div class="font-medium flex items-center gap-2">
1289
1451
  <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -1317,19 +1479,54 @@ var ChangerawrMarkdown = class {
1317
1479
  this.extensions = /* @__PURE__ */ new Map();
1318
1480
  this.parser = new MarkdownParser(config?.parser);
1319
1481
  this.renderer = new MarkdownRenderer(config?.renderer);
1320
- this.registerBuiltInExtensions();
1482
+ this.registerCoreExtensions();
1483
+ this.registerFeatureExtensions();
1321
1484
  if (config?.extensions) {
1322
1485
  config.extensions.forEach((extension) => {
1323
1486
  this.registerExtension(extension);
1324
1487
  });
1325
1488
  }
1326
- this.parser.setupDefaultRulesIfEmpty();
1327
1489
  }
1328
- /**
1329
- * Register a custom extension
1330
- */
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
+ }
1331
1500
  registerExtension(extension) {
1332
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
+ });
1333
1530
  this.extensions.set(extension.name, extension);
1334
1531
  extension.parseRules.forEach((rule) => {
1335
1532
  this.parser.addRule(rule);
@@ -1350,9 +1547,6 @@ var ChangerawrMarkdown = class {
1350
1547
  };
1351
1548
  }
1352
1549
  }
1353
- /**
1354
- * Unregister an extension
1355
- */
1356
1550
  unregisterExtension(name) {
1357
1551
  const extension = this.extensions.get(name);
1358
1552
  if (!extension) {
@@ -1366,46 +1560,25 @@ var ChangerawrMarkdown = class {
1366
1560
  return false;
1367
1561
  }
1368
1562
  }
1369
- /**
1370
- * Parse markdown content into tokens
1371
- */
1372
1563
  parse(markdown2) {
1373
1564
  return this.parser.parse(markdown2);
1374
1565
  }
1375
- /**
1376
- * Render tokens to HTML
1377
- */
1378
1566
  render(tokens) {
1379
1567
  return this.renderer.render(tokens);
1380
1568
  }
1381
- /**
1382
- * Parse and render markdown to HTML in one step
1383
- */
1384
1569
  toHtml(markdown2) {
1385
1570
  const tokens = this.parse(markdown2);
1386
1571
  return this.render(tokens);
1387
1572
  }
1388
- /**
1389
- * Get list of registered extensions
1390
- */
1391
1573
  getExtensions() {
1392
1574
  return Array.from(this.extensions.keys());
1393
1575
  }
1394
- /**
1395
- * Check if extension is registered
1396
- */
1397
1576
  hasExtension(name) {
1398
1577
  return this.extensions.has(name);
1399
1578
  }
1400
- /**
1401
- * Get parser warnings
1402
- */
1403
1579
  getWarnings() {
1404
1580
  return [...this.parser.getWarnings(), ...this.renderer.getWarnings()];
1405
1581
  }
1406
- /**
1407
- * Get debug information from last render
1408
- */
1409
1582
  getDebugInfo() {
1410
1583
  return {
1411
1584
  warnings: this.getWarnings(),
@@ -1415,9 +1588,6 @@ var ChangerawrMarkdown = class {
1415
1588
  iterationCount: 0
1416
1589
  };
1417
1590
  }
1418
- /**
1419
- * Get performance metrics for the last operation
1420
- */
1421
1591
  getPerformanceMetrics() {
1422
1592
  return {
1423
1593
  parseTime: 0,
@@ -1426,28 +1596,29 @@ var ChangerawrMarkdown = class {
1426
1596
  tokenCount: 0
1427
1597
  };
1428
1598
  }
1429
- /**
1430
- * Register built-in extensions
1431
- */
1432
- registerBuiltInExtensions() {
1433
- this.registerExtension(AlertExtension);
1434
- this.registerExtension(ButtonExtension);
1435
- this.registerExtension(EmbedExtension);
1436
- }
1437
- /**
1438
- * Rebuild parser and renderer with current extensions
1439
- */
1440
1599
  rebuildParserAndRenderer() {
1441
1600
  const parserConfig = this.parser.getConfig();
1442
1601
  const rendererConfig = this.renderer.getConfig();
1443
1602
  this.parser = new MarkdownParser(parserConfig);
1444
1603
  this.renderer = new MarkdownRenderer(rendererConfig);
1445
1604
  const extensionsToRegister = Array.from(this.extensions.values());
1446
- this.extensions.clear();
1447
- extensionsToRegister.forEach((extension) => {
1448
- 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
+ });
1449
1621
  });
1450
- this.parser.setupDefaultRulesIfEmpty();
1451
1622
  }
1452
1623
  };
1453
1624
  var markdown = new ChangerawrMarkdown();