@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.
package/dist/index.mjs CHANGED
@@ -1,43 +1,58 @@
1
- "use client";
2
-
3
1
  // src/parser.ts
4
2
  var MarkdownParser = class {
5
3
  constructor(config) {
6
4
  this.rules = [];
7
5
  this.warnings = [];
8
- this.config = config || {};
6
+ this.config = {
7
+ debugMode: false,
8
+ maxIterations: 1e4,
9
+ validateMarkdown: false,
10
+ ...config
11
+ };
9
12
  }
10
13
  addRule(rule) {
11
14
  this.rules.push(rule);
12
15
  this.rules.sort((a, b) => {
13
- if (a.name.includes("alert") || a.name.includes("embed") || a.name.includes("button")) return -1;
14
- if (b.name.includes("alert") || b.name.includes("embed") || b.name.includes("button")) return 1;
15
- if (a.name === "task-list") return -1;
16
- if (b.name === "task-list") return 1;
17
- if (a.name === "list" && b.name === "task-list") return 1;
18
- if (b.name === "list" && a.name === "task-list") return -1;
16
+ const aFeatureExtension = ["alert", "button", "embed"].includes(a.name);
17
+ const bFeatureExtension = ["alert", "button", "embed"].includes(b.name);
18
+ if (aFeatureExtension && !bFeatureExtension) return -1;
19
+ if (!aFeatureExtension && bFeatureExtension) return 1;
20
+ const aCoreExtension = ["text", "heading", "bold", "italic", "code", "codeblock", "link", "image", "list", "task-list", "blockquote", "hr", "paragraph", "line-break"].includes(a.name);
21
+ const bCoreExtension = ["text", "heading", "bold", "italic", "code", "codeblock", "link", "image", "list", "task-list", "blockquote", "hr", "paragraph", "line-break"].includes(b.name);
22
+ if (aCoreExtension && !bCoreExtension) return -1;
23
+ if (!aCoreExtension && bCoreExtension) return 1;
24
+ if (a.name === "image" && b.name === "link") return -1;
25
+ if (a.name === "link" && b.name === "image") return 1;
26
+ if (a.name === "task-item" && b.name === "list-item") return -1;
27
+ if (a.name === "list-item" && b.name === "task-item") return 1;
28
+ if (a.name === "codeblock" && b.name === "code") return -1;
29
+ if (a.name === "code" && b.name === "codeblock") return 1;
30
+ if (a.name === "bold" && b.name === "italic") return -1;
31
+ if (a.name === "italic" && b.name === "bold") return 1;
19
32
  return a.name.localeCompare(b.name);
20
33
  });
21
34
  }
22
- setupDefaultRulesIfEmpty() {
23
- const hasDefaultRules = this.rules.some(
24
- (rule) => !["alert", "button", "embed"].includes(rule.name)
25
- );
26
- if (!hasDefaultRules) {
27
- this.setupDefaultRules();
28
- }
29
- }
30
35
  hasRule(name) {
31
36
  return this.rules.some((rule) => rule.name === name);
32
37
  }
33
38
  parse(markdown3) {
34
39
  this.warnings = [];
35
- this.setupDefaultRulesIfEmpty();
40
+ if (!markdown3.trim()) {
41
+ return [];
42
+ }
43
+ if (this.rules.length === 0) {
44
+ this.warnings.push("No parse rules registered - consider using CoreExtensions");
45
+ return [{
46
+ type: "text",
47
+ content: markdown3,
48
+ raw: markdown3
49
+ }];
50
+ }
36
51
  const processedMarkdown = this.preprocessMarkdown(markdown3);
37
52
  const tokens = [];
38
53
  let remaining = processedMarkdown;
39
54
  let iterationCount = 0;
40
- const maxIterations = this.config.maxIterations || markdown3.length * 2;
55
+ const maxIterations = this.config.maxIterations || 1e4;
41
56
  while (remaining.length > 0 && iterationCount < maxIterations) {
42
57
  iterationCount++;
43
58
  let matched = false;
@@ -126,9 +141,6 @@ var MarkdownParser = class {
126
141
  clearWarnings() {
127
142
  this.warnings = [];
128
143
  }
129
- getIterationCount() {
130
- return 0;
131
- }
132
144
  preprocessMarkdown(markdown3) {
133
145
  if (this.config.validateMarkdown) {
134
146
  this.validateMarkdown(markdown3);
@@ -183,161 +195,6 @@ var MarkdownParser = class {
183
195
  }
184
196
  return processed;
185
197
  }
186
- setupDefaultRules() {
187
- this.addRule({
188
- name: "heading",
189
- pattern: /^(#{1,6})\s+(.+)$/m,
190
- render: (match) => ({
191
- type: "heading",
192
- content: match[2]?.trim() || "",
193
- raw: match[0] || "",
194
- attributes: {
195
- level: String(match[1]?.length || 1)
196
- }
197
- })
198
- });
199
- this.addRule({
200
- name: "codeblock",
201
- pattern: /```(\w+)?\s*\n([\s\S]*?)\n```/,
202
- render: (match) => ({
203
- type: "codeblock",
204
- content: match[2] || "",
205
- raw: match[0] || "",
206
- attributes: {
207
- language: match[1] || "text"
208
- }
209
- })
210
- });
211
- this.addRule({
212
- name: "hard-break-backslash",
213
- pattern: /\\\s*\n/,
214
- render: (match) => ({
215
- type: "line-break",
216
- content: "",
217
- raw: match[0] || ""
218
- })
219
- });
220
- this.addRule({
221
- name: "hard-break-spaces",
222
- pattern: / +\n/,
223
- render: (match) => ({
224
- type: "line-break",
225
- content: "",
226
- raw: match[0] || ""
227
- })
228
- });
229
- this.addRule({
230
- name: "paragraph-break",
231
- pattern: /\n\s*\n/,
232
- render: (match) => ({
233
- type: "paragraph-break",
234
- content: "",
235
- raw: match[0] || ""
236
- })
237
- });
238
- this.addRule({
239
- name: "bold",
240
- pattern: /\*\*((?:(?!\*\*).)+)\*\*/,
241
- render: (match) => ({
242
- type: "bold",
243
- content: match[1] || "",
244
- raw: match[0] || ""
245
- })
246
- });
247
- this.addRule({
248
- name: "italic",
249
- pattern: /\*((?:(?!\*).)+)\*/,
250
- render: (match) => ({
251
- type: "italic",
252
- content: match[1] || "",
253
- raw: match[0] || ""
254
- })
255
- });
256
- this.addRule({
257
- name: "code",
258
- pattern: /`([^`]+)`/,
259
- render: (match) => ({
260
- type: "code",
261
- content: match[1] || "",
262
- raw: match[0] || ""
263
- })
264
- });
265
- this.addRule({
266
- name: "image",
267
- pattern: /!\[([^\]]*)\]\(([^)]+?)(?:\s+"([^"]+)")?\)/,
268
- render: (match) => ({
269
- type: "image",
270
- content: match[1] || "",
271
- raw: match[0] || "",
272
- attributes: {
273
- alt: match[1] || "",
274
- src: match[2] || "",
275
- title: match[3] || ""
276
- }
277
- })
278
- });
279
- this.addRule({
280
- name: "link",
281
- pattern: /\[([^\]]+)\]\(([^)]+)\)/,
282
- render: (match) => ({
283
- type: "link",
284
- content: match[1] || "",
285
- raw: match[0] || "",
286
- attributes: {
287
- href: match[2] || ""
288
- }
289
- })
290
- });
291
- this.addRule({
292
- name: "task-list",
293
- pattern: /^(\s*)-\s*\[([ xX])\]\s*(.+)$/m,
294
- render: (match) => ({
295
- type: "task-item",
296
- content: match[3] || "",
297
- raw: match[0] || "",
298
- attributes: {
299
- checked: String((match[2] || "").toLowerCase() === "x")
300
- }
301
- })
302
- });
303
- this.addRule({
304
- name: "list",
305
- pattern: /^(\s*)[-*+]\s+(.+)$/m,
306
- render: (match) => ({
307
- type: "list-item",
308
- content: match[2] || "",
309
- raw: match[0] || ""
310
- })
311
- });
312
- this.addRule({
313
- name: "blockquote",
314
- pattern: /^>\s+(.+)$/m,
315
- render: (match) => ({
316
- type: "blockquote",
317
- content: match[1] || "",
318
- raw: match[0] || ""
319
- })
320
- });
321
- this.addRule({
322
- name: "hr",
323
- pattern: /^---$/m,
324
- render: (match) => ({
325
- type: "hr",
326
- content: "",
327
- raw: match[0] || ""
328
- })
329
- });
330
- this.addRule({
331
- name: "soft-break",
332
- pattern: /\n/,
333
- render: (match) => ({
334
- type: "soft-break",
335
- content: " ",
336
- // Convert to space for inline text
337
- raw: match[0] || ""
338
- })
339
- });
340
- }
341
198
  };
342
199
 
343
200
  // src/utils.ts
@@ -656,7 +513,6 @@ var MarkdownRenderer = class {
656
513
  debugMode: false,
657
514
  ...config
658
515
  };
659
- this.setupDefaultRules();
660
516
  }
661
517
  addRule(rule) {
662
518
  this.rules.set(rule.type, rule);
@@ -666,28 +522,20 @@ var MarkdownRenderer = class {
666
522
  }
667
523
  render(tokens) {
668
524
  this.warnings = [];
669
- const htmlParts = tokens.map((token) => this.renderToken(token));
525
+ const tokensWithFormat = tokens.map((token) => ({
526
+ ...token,
527
+ attributes: {
528
+ ...token.attributes,
529
+ format: this.config.format
530
+ }
531
+ }));
532
+ const htmlParts = tokensWithFormat.map((token) => this.renderToken(token));
670
533
  const combinedHtml = htmlParts.join("");
671
534
  if (this.config.sanitize && !this.config.allowUnsafeHtml) {
672
535
  return sanitizeHtml(combinedHtml);
673
536
  }
674
537
  return combinedHtml;
675
538
  }
676
- getWarnings() {
677
- return [...this.warnings];
678
- }
679
- getConfig() {
680
- return { ...this.config };
681
- }
682
- updateConfig(config) {
683
- this.config = { ...this.config, ...config };
684
- }
685
- setDebugMode(enabled) {
686
- this.config.debugMode = enabled;
687
- }
688
- clearWarnings() {
689
- this.warnings = [];
690
- }
691
539
  renderToken(token) {
692
540
  const rule = this.rules.get(token.type);
693
541
  if (rule) {
@@ -699,9 +547,6 @@ var MarkdownRenderer = class {
699
547
  return this.createErrorBlock(`Render error for ${token.type}: ${errorMessage}`);
700
548
  }
701
549
  }
702
- if (token.type === "text") {
703
- return escapeHtml(token.content || token.raw || "");
704
- }
705
550
  if (this.config.debugMode) {
706
551
  return this.createDebugBlock(token);
707
552
  }
@@ -729,15 +574,234 @@ var MarkdownRenderer = class {
729
574
  <strong>Content:</strong> ${escapeHtml(token.content || token.raw || "")}
730
575
  </div>`;
731
576
  }
732
- setupDefaultRules() {
733
- this.addRule({
577
+ getWarnings() {
578
+ return [...this.warnings];
579
+ }
580
+ getConfig() {
581
+ return { ...this.config };
582
+ }
583
+ updateConfig(config) {
584
+ this.config = { ...this.config, ...config };
585
+ }
586
+ setDebugMode(enabled) {
587
+ this.config.debugMode = enabled;
588
+ }
589
+ clearWarnings() {
590
+ this.warnings = [];
591
+ }
592
+ };
593
+
594
+ // src/extensions/core/blockquote.ts
595
+ var BlockquoteExtension = {
596
+ name: "blockquote",
597
+ parseRules: [
598
+ {
599
+ name: "blockquote",
600
+ pattern: /^>\s+(.+)$/m,
601
+ render: (match) => ({
602
+ type: "blockquote",
603
+ content: match[1] || "",
604
+ raw: match[0] || ""
605
+ })
606
+ }
607
+ ],
608
+ renderRules: [
609
+ {
610
+ type: "blockquote",
611
+ render: (token) => {
612
+ const content = escapeHtml(token.content);
613
+ const format = token.attributes?.format || "html";
614
+ if (format === "html") {
615
+ return `<blockquote style="border-left: 2px solid #d1d5db; padding: 8px 0 8px 16px; margin: 16px 0; font-style: italic; color: #6b7280;">${content}</blockquote>`;
616
+ }
617
+ return `<blockquote class="pl-4 py-2 border-l-2 border-border italic text-muted-foreground my-4">${content}</blockquote>`;
618
+ }
619
+ }
620
+ ]
621
+ };
622
+
623
+ // src/extensions/core/breaks.ts
624
+ var LineBreakExtension = {
625
+ name: "line-break",
626
+ parseRules: [
627
+ {
628
+ name: "hard-break-backslash",
629
+ pattern: /\\\s*\n/,
630
+ render: (match) => ({
631
+ type: "line-break",
632
+ content: "",
633
+ raw: match[0] || ""
634
+ })
635
+ },
636
+ {
637
+ name: "hard-break-spaces",
638
+ pattern: / +\n/,
639
+ render: (match) => ({
640
+ type: "line-break",
641
+ content: "",
642
+ raw: match[0] || ""
643
+ })
644
+ }
645
+ ],
646
+ renderRules: [
647
+ {
648
+ type: "line-break",
649
+ render: () => "<br>"
650
+ },
651
+ {
652
+ type: "paragraph-break",
653
+ render: () => "</p><p>"
654
+ },
655
+ {
656
+ type: "soft-break",
657
+ render: (token) => token.content || " "
658
+ }
659
+ ]
660
+ };
661
+
662
+ // src/extensions/core/code.ts
663
+ var InlineCodeExtension = {
664
+ name: "inline-code",
665
+ parseRules: [
666
+ {
667
+ name: "code",
668
+ pattern: /`([^`]+)`/,
669
+ render: (match) => ({
670
+ type: "code",
671
+ content: match[1] || "",
672
+ raw: match[0] || ""
673
+ })
674
+ }
675
+ ],
676
+ renderRules: [
677
+ {
678
+ type: "code",
679
+ render: (token) => {
680
+ const content = escapeHtml(token.content);
681
+ const format = token.attributes?.format;
682
+ if (format === "html") {
683
+ return `<code style="background-color: #f3f4f6; padding: 2px 6px; border-radius: 4px; font-family: monospace; font-size: 0.875rem;">${content}</code>`;
684
+ }
685
+ return `<code class="bg-muted px-1.5 py-0.5 rounded text-sm font-mono">${content}</code>`;
686
+ }
687
+ }
688
+ ]
689
+ };
690
+ var CodeBlockExtension = {
691
+ name: "codeblock",
692
+ parseRules: [
693
+ {
694
+ name: "codeblock",
695
+ pattern: /```(\w+)?\s*\n([\s\S]*?)\n```/,
696
+ render: (match) => ({
697
+ type: "codeblock",
698
+ content: match[2] || "",
699
+ raw: match[0] || "",
700
+ attributes: {
701
+ language: match[1] || "text"
702
+ }
703
+ })
704
+ }
705
+ ],
706
+ renderRules: [
707
+ {
708
+ type: "codeblock",
709
+ render: (token) => {
710
+ const language = token.attributes?.language || "text";
711
+ const escapedCode = escapeHtml(token.content);
712
+ const format = token.attributes?.format || "html";
713
+ if (format === "html") {
714
+ 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>`;
715
+ }
716
+ return `<pre class="bg-muted p-4 rounded-md overflow-x-auto my-4"><code class="language-${escapeHtml(language)}">${escapedCode}</code></pre>`;
717
+ }
718
+ }
719
+ ]
720
+ };
721
+
722
+ // src/extensions/core/emphasis.ts
723
+ var BoldExtension = {
724
+ name: "bold",
725
+ parseRules: [
726
+ {
727
+ name: "bold",
728
+ pattern: /\*\*((?:(?!\*\*).)+)\*\*/,
729
+ render: (match) => ({
730
+ type: "bold",
731
+ content: match[1] || "",
732
+ raw: match[0] || ""
733
+ })
734
+ }
735
+ ],
736
+ renderRules: [
737
+ {
738
+ type: "bold",
739
+ render: (token) => {
740
+ const content = escapeHtml(token.content);
741
+ const format = token.attributes?.format;
742
+ if (format === "html") {
743
+ return `<strong>${content}</strong>`;
744
+ }
745
+ return `<strong class="font-bold">${content}</strong>`;
746
+ }
747
+ }
748
+ ]
749
+ };
750
+ var ItalicExtension = {
751
+ name: "italic",
752
+ parseRules: [
753
+ {
754
+ name: "italic",
755
+ pattern: /\*((?:(?!\*).)+)\*/,
756
+ render: (match) => ({
757
+ type: "italic",
758
+ content: match[1] || "",
759
+ raw: match[0] || ""
760
+ })
761
+ }
762
+ ],
763
+ renderRules: [
764
+ {
765
+ type: "italic",
766
+ render: (token) => {
767
+ const content = escapeHtml(token.content);
768
+ const format = token.attributes?.format;
769
+ if (format === "html") {
770
+ return `<em>${content}</em>`;
771
+ }
772
+ return `<em class="italic">${content}</em>`;
773
+ }
774
+ }
775
+ ]
776
+ };
777
+
778
+ // src/extensions/core/heading.ts
779
+ var HeadingExtension = {
780
+ name: "heading",
781
+ parseRules: [
782
+ {
783
+ name: "heading",
784
+ pattern: /^(#{1,6})\s+(.+)$/m,
785
+ render: (match) => ({
786
+ type: "heading",
787
+ content: match[2]?.trim() || "",
788
+ raw: match[0] || "",
789
+ attributes: {
790
+ level: String(match[1]?.length || 1)
791
+ }
792
+ })
793
+ }
794
+ ],
795
+ renderRules: [
796
+ {
734
797
  type: "heading",
735
798
  render: (token) => {
736
799
  const level = parseInt(token.attributes?.level || "1");
737
800
  const text = token.content;
738
801
  const id = generateId(text);
739
802
  const escapedContent = escapeHtml(text);
740
- if (this.config.format === "html") {
803
+ const format = token.attributes?.format || "html";
804
+ if (format === "html") {
741
805
  return `<h${level} id="${id}">${escapedContent}</h${level}>`;
742
806
  }
743
807
  let headingClasses = "group relative flex items-center gap-2";
@@ -764,61 +828,108 @@ var MarkdownRenderer = class {
764
828
  return `<h${level} id="${id}" class="${headingClasses}">
765
829
  ${escapedContent}
766
830
  <a href="#${id}" class="opacity-0 group-hover:opacity-100 text-muted-foreground transition-opacity">
767
- <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
768
- <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"/>
769
- </svg>
831
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
832
+ <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/>
833
+ <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>
834
+ </svg>
770
835
  </a>
771
836
  </h${level}>`;
772
837
  }
773
- });
774
- this.addRule({
775
- type: "bold",
776
- render: (token) => {
777
- const content = escapeHtml(token.content);
778
- if (this.config.format === "html") {
779
- return `<strong>${content}</strong>`;
780
- }
781
- return `<strong class="font-bold">${content}</strong>`;
782
- }
783
- });
784
- this.addRule({
785
- type: "italic",
786
- render: (token) => {
787
- const content = escapeHtml(token.content);
788
- if (this.config.format === "html") {
789
- return `<em>${content}</em>`;
790
- }
791
- return `<em class="italic">${content}</em>`;
792
- }
793
- });
794
- this.addRule({
795
- type: "code",
838
+ }
839
+ ]
840
+ };
841
+
842
+ // src/extensions/core/hr.ts
843
+ var HorizontalRuleExtension = {
844
+ name: "hr",
845
+ parseRules: [
846
+ {
847
+ name: "hr",
848
+ pattern: /^---$/m,
849
+ render: (match) => ({
850
+ type: "hr",
851
+ content: "",
852
+ raw: match[0] || ""
853
+ })
854
+ }
855
+ ],
856
+ renderRules: [
857
+ {
858
+ type: "hr",
796
859
  render: (token) => {
797
- const content = escapeHtml(token.content);
798
- if (this.config.format === "html") {
799
- return `<code style="background-color: #f3f4f6; padding: 2px 6px; border-radius: 4px; font-family: monospace; font-size: 0.875rem;">${content}</code>`;
860
+ const format = token.attributes?.format;
861
+ if (format === "html") {
862
+ return '<hr style="margin: 24px 0; border: none; border-top: 1px solid #d1d5db;">';
800
863
  }
801
- return `<code class="bg-muted px-1.5 py-0.5 rounded text-sm font-mono">${content}</code>`;
864
+ return '<hr class="my-6 border-t border-border">';
802
865
  }
803
- });
804
- this.addRule({
805
- type: "codeblock",
866
+ }
867
+ ]
868
+ };
869
+
870
+ // src/extensions/core/image.ts
871
+ var ImageExtension = {
872
+ name: "image",
873
+ parseRules: [
874
+ {
875
+ name: "image",
876
+ pattern: /!\[([^\]]*)\]\(([^)]+?)(?:\s+"([^"]+)")?\)/,
877
+ render: (match) => ({
878
+ type: "image",
879
+ content: match[1] || "",
880
+ raw: match[0] || "",
881
+ attributes: {
882
+ alt: match[1] || "",
883
+ src: match[2] || "",
884
+ title: match[3] || ""
885
+ }
886
+ })
887
+ }
888
+ ],
889
+ renderRules: [
890
+ {
891
+ type: "image",
806
892
  render: (token) => {
807
- const language = token.attributes?.language || "text";
808
- const escapedCode = escapeHtml(token.content);
809
- if (this.config.format === "html") {
810
- 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>`;
893
+ const src = token.attributes?.src || "";
894
+ const alt = token.attributes?.alt || "";
895
+ const title = token.attributes?.title || "";
896
+ const titleAttr = title ? ` title="${escapeHtml(title)}"` : "";
897
+ const format = token.attributes?.format || "html";
898
+ if (format === "html") {
899
+ return `<img src="${escapeHtml(src)}" alt="${escapeHtml(alt)}"${titleAttr} style="max-width: 100%; height: auto; border-radius: 8px; margin: 16px 0;" loading="lazy" />`;
811
900
  }
812
- return `<pre class="bg-muted p-4 rounded-md overflow-x-auto my-4"><code class="language-${escapeHtml(language)}">${escapedCode}</code></pre>`;
901
+ return `<img src="${escapeHtml(src)}" alt="${escapeHtml(alt)}"${titleAttr} class="max-w-full h-auto rounded-lg my-4" loading="lazy" />`;
813
902
  }
814
- });
815
- this.addRule({
903
+ }
904
+ ]
905
+ };
906
+
907
+ // src/extensions/core/links.ts
908
+ var LinkExtension = {
909
+ name: "link",
910
+ parseRules: [
911
+ {
912
+ name: "link",
913
+ pattern: /\[(?!(?:button|embed):)([^\]]+)\]\(([^)]+)\)/,
914
+ render: (match) => ({
915
+ type: "link",
916
+ content: match[1] || "",
917
+ raw: match[0] || "",
918
+ attributes: {
919
+ href: match[2] || ""
920
+ }
921
+ })
922
+ }
923
+ ],
924
+ renderRules: [
925
+ {
816
926
  type: "link",
817
927
  render: (token) => {
818
928
  const href = token.attributes?.href || "#";
819
929
  const escapedHref = escapeHtml(href);
820
930
  const escapedText = escapeHtml(token.content);
821
- if (this.config.format === "html") {
931
+ const format = token.attributes?.format || "html";
932
+ if (format === "html") {
822
933
  return `<a href="${escapedHref}" target="_blank" rel="noopener noreferrer">${escapedText}</a>`;
823
934
  }
824
935
  return `<a href="${escapedHref}" class="text-primary hover:underline inline-flex items-center gap-1" target="_blank" rel="noopener noreferrer">
@@ -830,47 +941,55 @@ var MarkdownRenderer = class {
830
941
  </svg>
831
942
  </a>`;
832
943
  }
833
- });
834
- this.addRule({
944
+ }
945
+ ]
946
+ };
947
+
948
+ // src/extensions/core/lists.ts
949
+ var ListExtension = {
950
+ name: "list",
951
+ parseRules: [
952
+ {
953
+ name: "list-item",
954
+ pattern: /^(\s*)[-*+]\s+(.+)$/m,
955
+ render: (match) => ({
956
+ type: "list-item",
957
+ content: match[2] || "",
958
+ raw: match[0] || ""
959
+ })
960
+ }
961
+ ],
962
+ renderRules: [
963
+ {
835
964
  type: "list-item",
836
965
  render: (token) => `<li>${escapeHtml(token.content)}</li>`
837
- });
838
- this.addRule({
839
- type: "blockquote",
840
- render: (token) => {
841
- const content = escapeHtml(token.content);
842
- if (this.config.format === "html") {
843
- return `<blockquote style="border-left: 2px solid #d1d5db; padding: 8px 0 8px 16px; margin: 16px 0; font-style: italic; color: #6b7280;">${content}</blockquote>`;
844
- }
845
- return `<blockquote class="pl-4 py-2 border-l-2 border-border italic text-muted-foreground my-4">${content}</blockquote>`;
846
- }
847
- });
848
- this.addRule({
849
- type: "text",
850
- render: (token) => {
851
- if (!token.content) return "";
852
- return escapeHtml(token.content);
853
- }
854
- });
855
- this.addRule({
856
- type: "paragraph",
857
- render: (token) => {
858
- if (!token.content) return "";
859
- const content = token.content.trim();
860
- if (!content) return "";
861
- const processedContent = content.includes("<br>") ? content : escapeHtml(content);
862
- if (this.config.format === "html") {
863
- return `<p style="line-height: 1.75; margin-bottom: 16px;">${processedContent}</p>`;
966
+ }
967
+ ]
968
+ };
969
+ var TaskListExtension = {
970
+ name: "task-list",
971
+ parseRules: [
972
+ {
973
+ name: "task-item",
974
+ pattern: /^(\s*)-\s*\[([ xX])\]\s*(.+)$/m,
975
+ render: (match) => ({
976
+ type: "task-item",
977
+ content: match[3] || "",
978
+ raw: match[0] || "",
979
+ attributes: {
980
+ checked: String((match[2] || "").toLowerCase() === "x")
864
981
  }
865
- return `<p class="leading-7 mb-4">${processedContent}</p>`;
866
- }
867
- });
868
- this.addRule({
982
+ })
983
+ }
984
+ ],
985
+ renderRules: [
986
+ {
869
987
  type: "task-item",
870
988
  render: (token) => {
871
989
  const isChecked = token.attributes?.checked === "true";
872
990
  const escapedContent = escapeHtml(token.content);
873
- if (this.config.format === "html") {
991
+ const format = token.attributes?.format || "html";
992
+ if (format === "html") {
874
993
  return `<div style="display: flex; align-items: center; gap: 8px; margin: 8px 0;">
875
994
  <input type="checkbox" ${isChecked ? "checked" : ""} disabled style="margin: 0;" />
876
995
  <span${isChecked ? ' style="text-decoration: line-through; color: #6b7280;"' : ""}>${escapedContent}</span>
@@ -882,44 +1001,65 @@ var MarkdownRenderer = class {
882
1001
  <span${isChecked ? ' class="line-through text-muted-foreground"' : ""}>${escapedContent}</span>
883
1002
  </div>`;
884
1003
  }
885
- });
886
- this.addRule({
887
- type: "image",
1004
+ }
1005
+ ]
1006
+ };
1007
+
1008
+ // src/extensions/core/paragraph.ts
1009
+ var ParagraphExtension = {
1010
+ name: "paragraph",
1011
+ parseRules: [],
1012
+ renderRules: [
1013
+ {
1014
+ type: "paragraph",
888
1015
  render: (token) => {
889
- const src = token.attributes?.src || "";
890
- const alt = token.attributes?.alt || "";
891
- const title = token.attributes?.title || "";
892
- const titleAttr = title ? ` title="${escapeHtml(title)}"` : "";
893
- if (this.config.format === "html") {
894
- return `<img src="${escapeHtml(src)}" alt="${escapeHtml(alt)}"${titleAttr} style="max-width: 100%; height: auto; border-radius: 8px; margin: 16px 0;" loading="lazy" />`;
1016
+ if (!token.content) return "";
1017
+ const content = token.content.trim();
1018
+ if (!content) return "";
1019
+ const processedContent = content.includes("<br>") ? content : escapeHtml(content);
1020
+ const format = token.attributes?.format || "html";
1021
+ if (format === "html") {
1022
+ return `<p style="line-height: 1.75; margin-bottom: 16px;">${processedContent}</p>`;
895
1023
  }
896
- return `<img src="${escapeHtml(src)}" alt="${escapeHtml(alt)}"${titleAttr} class="max-w-full h-auto rounded-lg my-4" loading="lazy" />`;
1024
+ return `<p class="leading-7 mb-4">${processedContent}</p>`;
897
1025
  }
898
- });
899
- this.addRule({
900
- type: "hr",
901
- render: () => {
902
- if (this.config.format === "html") {
903
- return '<hr style="margin: 24px 0; border: none; border-top: 1px solid #d1d5db;">';
904
- }
905
- return '<hr class="my-6 border-t border-border">';
1026
+ }
1027
+ ]
1028
+ };
1029
+
1030
+ // src/extensions/core/text.ts
1031
+ var TextExtension = {
1032
+ name: "text",
1033
+ parseRules: [],
1034
+ renderRules: [
1035
+ {
1036
+ type: "text",
1037
+ render: (token) => {
1038
+ if (!token.content) return "";
1039
+ return escapeHtml(token.content);
906
1040
  }
907
- });
908
- this.addRule({
909
- type: "line-break",
910
- render: () => "<br>"
911
- });
912
- this.addRule({
913
- type: "paragraph-break",
914
- render: () => "</p><p>"
915
- });
916
- this.addRule({
917
- type: "soft-break",
918
- render: (token) => token.content || " "
919
- });
920
- }
1041
+ }
1042
+ ]
921
1043
  };
922
1044
 
1045
+ // src/extensions/core/index.ts
1046
+ var CoreExtensions = [
1047
+ TextExtension,
1048
+ HeadingExtension,
1049
+ BoldExtension,
1050
+ ItalicExtension,
1051
+ InlineCodeExtension,
1052
+ CodeBlockExtension,
1053
+ LinkExtension,
1054
+ ImageExtension,
1055
+ ListExtension,
1056
+ TaskListExtension,
1057
+ BlockquoteExtension,
1058
+ HorizontalRuleExtension,
1059
+ ParagraphExtension,
1060
+ LineBreakExtension
1061
+ ];
1062
+
923
1063
  // src/extensions/alert.ts
924
1064
  var AlertExtension = {
925
1065
  name: "alert",
@@ -999,26 +1139,19 @@ var ButtonExtension = {
999
1139
  name: "button",
1000
1140
  pattern: /\[button:([^\]]+)\]\(([^)]+)\)(?:\{([^}]+)\})?/,
1001
1141
  render: (match) => {
1002
- const text = match[1] || "";
1003
- const href = match[2] || "";
1004
- const optionsString = match[3] || "";
1005
- const options = optionsString.split(",").map((opt) => opt.trim()).filter(Boolean);
1006
- const styleOptions = ["default", "primary", "secondary", "success", "danger", "outline", "ghost"];
1007
- const style = options.find((opt) => styleOptions.includes(opt)) || "primary";
1008
- const sizeOptions = ["sm", "md", "lg"];
1009
- const size = options.find((opt) => sizeOptions.includes(opt)) || "md";
1010
- const disabled = options.includes("disabled");
1011
- const target = options.includes("self") ? "_self" : "_blank";
1142
+ const options = match[3] ? match[3].split(",").map((opt) => opt.trim()) : [];
1012
1143
  return {
1013
1144
  type: "button",
1014
- content: text,
1145
+ content: match[1] || "",
1015
1146
  raw: match[0] || "",
1016
1147
  attributes: {
1017
- href,
1018
- style,
1019
- size,
1020
- disabled: disabled.toString(),
1021
- target
1148
+ href: match[2] || "",
1149
+ style: options.find(
1150
+ (opt) => ["default", "primary", "secondary", "success", "danger", "outline", "ghost"].includes(opt)
1151
+ ) || "primary",
1152
+ size: options.find((opt) => ["sm", "md", "lg"].includes(opt)) || "md",
1153
+ disabled: String(options.includes("disabled")),
1154
+ target: options.includes("self") ? "_self" : "_blank"
1022
1155
  }
1023
1156
  };
1024
1157
  }
@@ -1033,53 +1166,92 @@ var ButtonExtension = {
1033
1166
  const size = token.attributes?.size || "md";
1034
1167
  const disabled = token.attributes?.disabled === "true";
1035
1168
  const target = token.attributes?.target || "_blank";
1036
- const text = token.content;
1037
- const classes = buildButtonClasses(style, size);
1169
+ const baseClasses = `
1170
+ inline-flex items-center justify-center font-medium rounded-lg
1171
+ transition-all duration-200 ease-out
1172
+ focus:outline-none focus:ring-2 focus:ring-offset-2
1173
+ disabled:opacity-50 disabled:cursor-not-allowed disabled:pointer-events-none
1174
+ transform hover:scale-[1.02] active:scale-[0.98]
1175
+ shadow-sm hover:shadow-md active:shadow-sm
1176
+ border border-transparent
1177
+ relative overflow-hidden
1178
+ before:absolute before:inset-0 before:rounded-lg
1179
+ before:bg-gradient-to-br before:from-white/20 before:to-transparent
1180
+ before:opacity-0 hover:before:opacity-100 before:transition-opacity before:duration-200
1181
+ `.replace(/\s+/g, " ").trim();
1182
+ const sizeClasses = {
1183
+ sm: "px-3 py-1.5 text-sm gap-1.5",
1184
+ md: "px-4 py-2 text-base gap-2",
1185
+ lg: "px-6 py-3 text-lg gap-2.5"
1186
+ };
1187
+ const styleClasses = {
1188
+ default: `
1189
+ bg-slate-600 text-white border-slate-500
1190
+ hover:bg-slate-700 hover:border-slate-400
1191
+ focus:ring-slate-500
1192
+ shadow-[0_1px_0_0_rgba(255,255,255,0.1)_inset,0_1px_2px_0_rgba(0,0,0,0.1)]
1193
+ hover:shadow-[0_1px_0_0_rgba(255,255,255,0.15)_inset,0_2px_4px_0_rgba(0,0,0,0.15)]
1194
+ `,
1195
+ primary: `
1196
+ bg-blue-600 text-white border-blue-500
1197
+ hover:bg-blue-700 hover:border-blue-400
1198
+ focus:ring-blue-500
1199
+ shadow-[0_1px_0_0_rgba(255,255,255,0.1)_inset,0_1px_2px_0_rgba(0,0,0,0.1)]
1200
+ hover:shadow-[0_1px_0_0_rgba(255,255,255,0.15)_inset,0_2px_4px_0_rgba(0,0,0,0.15)]
1201
+ `,
1202
+ secondary: `
1203
+ bg-gray-600 text-white border-gray-500
1204
+ hover:bg-gray-700 hover:border-gray-400
1205
+ focus:ring-gray-500
1206
+ shadow-[0_1px_0_0_rgba(255,255,255,0.1)_inset,0_1px_2px_0_rgba(0,0,0,0.1)]
1207
+ hover:shadow-[0_1px_0_0_rgba(255,255,255,0.15)_inset,0_2px_4px_0_rgba(0,0,0,0.15)]
1208
+ `,
1209
+ success: `
1210
+ bg-green-600 text-white border-green-500
1211
+ hover:bg-green-700 hover:border-green-400
1212
+ focus:ring-green-500
1213
+ shadow-[0_1px_0_0_rgba(255,255,255,0.1)_inset,0_1px_2px_0_rgba(0,0,0,0.1)]
1214
+ hover:shadow-[0_1px_0_0_rgba(255,255,255,0.15)_inset,0_2px_4px_0_rgba(0,0,0,0.15)]
1215
+ `,
1216
+ danger: `
1217
+ bg-red-600 text-white border-red-500
1218
+ hover:bg-red-700 hover:border-red-400
1219
+ focus:ring-red-500
1220
+ shadow-[0_1px_0_0_rgba(255,255,255,0.1)_inset,0_1px_2px_0_rgba(0,0,0,0.1)]
1221
+ hover:shadow-[0_1px_0_0_rgba(255,255,255,0.15)_inset,0_2px_4px_0_rgba(0,0,0,0.15)]
1222
+ `,
1223
+ outline: `
1224
+ bg-transparent text-blue-600 border-blue-600
1225
+ hover:bg-blue-50 hover:border-blue-700 hover:text-blue-700
1226
+ focus:ring-blue-500
1227
+ shadow-[0_0_0_1px_rgba(59,130,246,0.5)_inset]
1228
+ hover:shadow-[0_0_0_1px_rgba(29,78,216,0.6)_inset,0_1px_2px_0_rgba(0,0,0,0.05)]
1229
+ `,
1230
+ ghost: `
1231
+ bg-transparent text-gray-700 border-transparent
1232
+ hover:bg-gray-100 hover:text-gray-900
1233
+ focus:ring-gray-500
1234
+ shadow-none
1235
+ hover:shadow-[0_1px_2px_0_rgba(0,0,0,0.05)]
1236
+ `
1237
+ };
1238
+ const classes = `
1239
+ ${baseClasses}
1240
+ ${sizeClasses[size] || sizeClasses.md}
1241
+ ${styleClasses[style] || styleClasses.primary}
1242
+ `.replace(/\s+/g, " ").trim();
1038
1243
  const targetAttr = target === "_blank" ? ' target="_blank" rel="noopener noreferrer"' : target === "_self" ? ' target="_self"' : "";
1039
1244
  const disabledAttr = disabled ? ' aria-disabled="true" tabindex="-1"' : "";
1040
- 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>' : "";
1245
+ const externalIcon = target === "_blank" && !disabled ? `<svg class="w-4 h-4 ml-1 opacity-75" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1246
+ <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"/>
1247
+ </svg>` : "";
1041
1248
  return `<a href="${href}" class="${classes}"${targetAttr}${disabledAttr}>
1042
- ${text}${externalIcon}
1043
- </a>`;
1249
+ <span class="relative z-10">${token.content}</span>${externalIcon}
1250
+ </a>`;
1044
1251
  }
1045
1252
  }
1046
1253
  ]
1047
1254
  };
1048
- function buildButtonClasses(style, size) {
1049
- const base = [
1050
- "inline-flex",
1051
- "items-center",
1052
- "justify-center",
1053
- "font-medium",
1054
- "rounded-lg",
1055
- "transition-colors",
1056
- "focus:outline-none",
1057
- "focus:ring-2",
1058
- "focus:ring-offset-2",
1059
- "disabled:opacity-50",
1060
- "disabled:cursor-not-allowed"
1061
- ];
1062
- const sizes = {
1063
- sm: ["px-3", "py-1.5", "text-sm"],
1064
- md: ["px-4", "py-2", "text-base"],
1065
- lg: ["px-6", "py-3", "text-lg"]
1066
- };
1067
- const styles = {
1068
- default: ["bg-slate-600", "text-white", "hover:bg-slate-700", "focus:ring-slate-500"],
1069
- primary: ["bg-blue-600", "text-white", "hover:bg-blue-700", "focus:ring-blue-500"],
1070
- secondary: ["bg-gray-600", "text-white", "hover:bg-gray-700", "focus:ring-gray-500"],
1071
- success: ["bg-green-600", "text-white", "hover:bg-green-700", "focus:ring-green-500"],
1072
- danger: ["bg-red-600", "text-white", "hover:bg-red-700", "focus:ring-red-500"],
1073
- outline: ["border", "border-blue-600", "text-blue-600", "hover:bg-blue-50", "focus:ring-blue-500"],
1074
- ghost: ["text-gray-700", "hover:bg-gray-100", "focus:ring-gray-500"]
1075
- };
1076
- const allClasses = [
1077
- ...base,
1078
- ...sizes[size] ?? sizes.md ?? [],
1079
- ...styles[style] ?? styles.primary ?? []
1080
- ];
1081
- return allClasses.join(" ");
1082
- }
1083
1255
 
1084
1256
  // src/extensions/embed.ts
1085
1257
  var EmbedExtension = {
@@ -1157,8 +1329,7 @@ function renderYouTubeEmbed(url, options, classes) {
1157
1329
  params.set("rel", "0");
1158
1330
  params.set("modestbranding", "1");
1159
1331
  const embedUrl = `https://www.youtube.com/embed/${videoId}?${params.toString()}`;
1160
- return `
1161
- <div class="${classes}">
1332
+ return `<div class="${classes}">
1162
1333
  <div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
1163
1334
  <iframe
1164
1335
  style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 0;"
@@ -1186,8 +1357,7 @@ function renderCodePenEmbed(url, options, classes) {
1186
1357
  "editable": "true"
1187
1358
  });
1188
1359
  const embedUrl = `https://codepen.io/${user}/embed/${penId}?${embedParams.toString()}`;
1189
- return `
1190
- <div class="${classes}">
1360
+ return `<div class="${classes}">
1191
1361
  <iframe
1192
1362
  height="${height}"
1193
1363
  style="width: 100%; border: 0;"
@@ -1211,8 +1381,7 @@ function renderVimeoEmbed(url, options, classes) {
1211
1381
  if (options.mute === "1") params.set("muted", "1");
1212
1382
  if (options.loop === "1") params.set("loop", "1");
1213
1383
  const embedUrl = `https://player.vimeo.com/video/${videoId}?${params.toString()}`;
1214
- return `
1215
- <div class="${classes}">
1384
+ return `<div class="${classes}">
1216
1385
  <div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
1217
1386
  <iframe
1218
1387
  style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 0;"
@@ -1228,8 +1397,7 @@ function renderVimeoEmbed(url, options, classes) {
1228
1397
  function renderSpotifyEmbed(url, options, classes) {
1229
1398
  const embedUrl = url.replace("open.spotify.com", "open.spotify.com/embed");
1230
1399
  const height = options.height || "380";
1231
- return `
1232
- <div class="${classes}">
1400
+ return `<div class="${classes}">
1233
1401
  <iframe
1234
1402
  style="border-radius: 12px;"
1235
1403
  src="${embedUrl}"
@@ -1252,8 +1420,7 @@ function renderCodeSandboxEmbed(url, options, classes) {
1252
1420
  if (!embedUrl.includes("?")) {
1253
1421
  embedUrl += `?view=${view}`;
1254
1422
  }
1255
- return `
1256
- <div class="${classes}">
1423
+ return `<div class="${classes}">
1257
1424
  <iframe
1258
1425
  src="${embedUrl}"
1259
1426
  style="width: 100%; height: ${height}px; border: 0; border-radius: 4px; overflow: hidden;"
@@ -1266,8 +1433,7 @@ function renderCodeSandboxEmbed(url, options, classes) {
1266
1433
  function renderFigmaEmbed(url, options, classes) {
1267
1434
  const embedUrl = `https://www.figma.com/embed?embed_host=share&url=${encodeURIComponent(url)}`;
1268
1435
  const height = options.height || "450";
1269
- return `
1270
- <div class="${classes}">
1436
+ return `<div class="${classes}">
1271
1437
  <iframe
1272
1438
  style="border: none;"
1273
1439
  width="100%"
@@ -1278,8 +1444,7 @@ function renderFigmaEmbed(url, options, classes) {
1278
1444
  </div>`;
1279
1445
  }
1280
1446
  function renderTwitterEmbed(url, _options, classes) {
1281
- return `
1282
- <div class="${classes}">
1447
+ return `<div class="${classes}">
1283
1448
  <div class="p-4">
1284
1449
  <div class="flex items-center gap-3 mb-3">
1285
1450
  <svg class="w-6 h-6 fill-current text-blue-500" viewBox="0 0 24 24">
@@ -1307,8 +1472,7 @@ function renderGitHubEmbed(url, _options, classes) {
1307
1472
  if (!owner || !repo) {
1308
1473
  return createErrorEmbed("Invalid GitHub URL", url, classes);
1309
1474
  }
1310
- return `
1311
- <div class="${classes}">
1475
+ return `<div class="${classes}">
1312
1476
  <div class="p-4">
1313
1477
  <div class="flex items-center gap-3 mb-3">
1314
1478
  <svg class="w-6 h-6 fill-current" viewBox="0 0 24 24">
@@ -1331,8 +1495,7 @@ function renderGitHubEmbed(url, _options, classes) {
1331
1495
  }
1332
1496
  function renderGenericEmbed(url, _options, classes) {
1333
1497
  const domain = extractDomain(url);
1334
- return `
1335
- <div class="${classes}">
1498
+ return `<div class="${classes}">
1336
1499
  <div class="p-4">
1337
1500
  <div class="flex items-center gap-3 mb-3">
1338
1501
  <div class="w-10 h-10 rounded-lg bg-muted flex items-center justify-center">
@@ -1356,8 +1519,7 @@ function renderGenericEmbed(url, _options, classes) {
1356
1519
  </div>`;
1357
1520
  }
1358
1521
  function createErrorEmbed(error, url, classes) {
1359
- return `
1360
- <div class="${classes}">
1522
+ return `<div class="${classes}">
1361
1523
  <div class="p-4 text-destructive">
1362
1524
  <div class="font-medium flex items-center gap-2">
1363
1525
  <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -1391,19 +1553,54 @@ var ChangerawrMarkdown = class {
1391
1553
  this.extensions = /* @__PURE__ */ new Map();
1392
1554
  this.parser = new MarkdownParser(config?.parser);
1393
1555
  this.renderer = new MarkdownRenderer(config?.renderer);
1394
- this.registerBuiltInExtensions();
1556
+ this.registerCoreExtensions();
1557
+ this.registerFeatureExtensions();
1395
1558
  if (config?.extensions) {
1396
1559
  config.extensions.forEach((extension) => {
1397
1560
  this.registerExtension(extension);
1398
1561
  });
1399
1562
  }
1400
- this.parser.setupDefaultRulesIfEmpty();
1401
1563
  }
1402
- /**
1403
- * Register a custom extension
1404
- */
1564
+ registerFeatureExtensions() {
1565
+ this.registerExtension(AlertExtension);
1566
+ this.registerExtension(ButtonExtension);
1567
+ this.registerExtension(EmbedExtension);
1568
+ }
1569
+ registerCoreExtensions() {
1570
+ CoreExtensions.forEach((extension) => {
1571
+ this.registerExtension(extension);
1572
+ });
1573
+ }
1405
1574
  registerExtension(extension) {
1406
1575
  try {
1576
+ if (!extension.name || typeof extension.name !== "string") {
1577
+ throw new Error("Extension must have a valid name");
1578
+ }
1579
+ if (!Array.isArray(extension.parseRules)) {
1580
+ throw new Error("Extension must have parseRules array");
1581
+ }
1582
+ if (!Array.isArray(extension.renderRules)) {
1583
+ throw new Error("Extension must have renderRules array");
1584
+ }
1585
+ extension.parseRules.forEach((rule, index) => {
1586
+ if (!rule.name || typeof rule.name !== "string") {
1587
+ throw new Error(`Parse rule ${index} must have a valid name`);
1588
+ }
1589
+ if (!rule.pattern || !(rule.pattern instanceof RegExp)) {
1590
+ throw new Error(`Parse rule ${index} must have a valid RegExp pattern`);
1591
+ }
1592
+ if (!rule.render || typeof rule.render !== "function") {
1593
+ throw new Error(`Parse rule ${index} must have a valid render function`);
1594
+ }
1595
+ });
1596
+ extension.renderRules.forEach((rule, index) => {
1597
+ if (!rule.type || typeof rule.type !== "string") {
1598
+ throw new Error(`Render rule ${index} must have a valid type`);
1599
+ }
1600
+ if (!rule.render || typeof rule.render !== "function") {
1601
+ throw new Error(`Render rule ${index} must have a valid render function`);
1602
+ }
1603
+ });
1407
1604
  this.extensions.set(extension.name, extension);
1408
1605
  extension.parseRules.forEach((rule) => {
1409
1606
  this.parser.addRule(rule);
@@ -1424,9 +1621,6 @@ var ChangerawrMarkdown = class {
1424
1621
  };
1425
1622
  }
1426
1623
  }
1427
- /**
1428
- * Unregister an extension
1429
- */
1430
1624
  unregisterExtension(name) {
1431
1625
  const extension = this.extensions.get(name);
1432
1626
  if (!extension) {
@@ -1440,46 +1634,25 @@ var ChangerawrMarkdown = class {
1440
1634
  return false;
1441
1635
  }
1442
1636
  }
1443
- /**
1444
- * Parse markdown content into tokens
1445
- */
1446
1637
  parse(markdown3) {
1447
1638
  return this.parser.parse(markdown3);
1448
1639
  }
1449
- /**
1450
- * Render tokens to HTML
1451
- */
1452
1640
  render(tokens) {
1453
1641
  return this.renderer.render(tokens);
1454
1642
  }
1455
- /**
1456
- * Parse and render markdown to HTML in one step
1457
- */
1458
1643
  toHtml(markdown3) {
1459
1644
  const tokens = this.parse(markdown3);
1460
1645
  return this.render(tokens);
1461
1646
  }
1462
- /**
1463
- * Get list of registered extensions
1464
- */
1465
1647
  getExtensions() {
1466
1648
  return Array.from(this.extensions.keys());
1467
1649
  }
1468
- /**
1469
- * Check if extension is registered
1470
- */
1471
1650
  hasExtension(name) {
1472
1651
  return this.extensions.has(name);
1473
1652
  }
1474
- /**
1475
- * Get parser warnings
1476
- */
1477
1653
  getWarnings() {
1478
1654
  return [...this.parser.getWarnings(), ...this.renderer.getWarnings()];
1479
1655
  }
1480
- /**
1481
- * Get debug information from last render
1482
- */
1483
1656
  getDebugInfo() {
1484
1657
  return {
1485
1658
  warnings: this.getWarnings(),
@@ -1489,9 +1662,6 @@ var ChangerawrMarkdown = class {
1489
1662
  iterationCount: 0
1490
1663
  };
1491
1664
  }
1492
- /**
1493
- * Get performance metrics for the last operation
1494
- */
1495
1665
  getPerformanceMetrics() {
1496
1666
  return {
1497
1667
  parseTime: 0,
@@ -1500,28 +1670,29 @@ var ChangerawrMarkdown = class {
1500
1670
  tokenCount: 0
1501
1671
  };
1502
1672
  }
1503
- /**
1504
- * Register built-in extensions
1505
- */
1506
- registerBuiltInExtensions() {
1507
- this.registerExtension(AlertExtension);
1508
- this.registerExtension(ButtonExtension);
1509
- this.registerExtension(EmbedExtension);
1510
- }
1511
- /**
1512
- * Rebuild parser and renderer with current extensions
1513
- */
1514
1673
  rebuildParserAndRenderer() {
1515
1674
  const parserConfig = this.parser.getConfig();
1516
1675
  const rendererConfig = this.renderer.getConfig();
1517
1676
  this.parser = new MarkdownParser(parserConfig);
1518
1677
  this.renderer = new MarkdownRenderer(rendererConfig);
1519
1678
  const extensionsToRegister = Array.from(this.extensions.values());
1520
- this.extensions.clear();
1521
- extensionsToRegister.forEach((extension) => {
1522
- this.registerExtension(extension);
1679
+ const featureExtensions = extensionsToRegister.filter(
1680
+ (ext) => ["alert", "button", "embed"].includes(ext.name)
1681
+ );
1682
+ const coreExtensions = extensionsToRegister.filter(
1683
+ (ext) => ["text", "heading", "bold", "italic", "code", "codeblock", "link", "image", "list", "task-list", "blockquote", "hr", "paragraph", "line-break"].includes(ext.name)
1684
+ );
1685
+ const customExtensions = extensionsToRegister.filter(
1686
+ (ext) => !["alert", "button", "embed", "text", "heading", "bold", "italic", "code", "codeblock", "link", "image", "list", "task-list", "blockquote", "hr", "paragraph", "line-break"].includes(ext.name)
1687
+ );
1688
+ [...featureExtensions, ...coreExtensions, ...customExtensions].forEach((extension) => {
1689
+ extension.parseRules.forEach((rule) => {
1690
+ this.parser.addRule(rule);
1691
+ });
1692
+ extension.renderRules.forEach((rule) => {
1693
+ this.renderer.addRule(rule);
1694
+ });
1523
1695
  });
1524
- this.parser.setupDefaultRulesIfEmpty();
1525
1696
  }
1526
1697
  };
1527
1698
  var markdown = new ChangerawrMarkdown();
@@ -1951,8 +2122,7 @@ function createHTMLEngine(config) {
1951
2122
  renderer: {
1952
2123
  format: "html",
1953
2124
  sanitize: true,
1954
- allowUnsafeHtml: false,
1955
- ...config?.parser
2125
+ allowUnsafeHtml: false
1956
2126
  }
1957
2127
  });
1958
2128
  }
@@ -1962,8 +2132,7 @@ function createTailwindEngine(config) {
1962
2132
  renderer: {
1963
2133
  format: "tailwind",
1964
2134
  sanitize: true,
1965
- allowUnsafeHtml: false,
1966
- ...config?.parser
2135
+ allowUnsafeHtml: false
1967
2136
  }
1968
2137
  });
1969
2138
  }
@@ -1982,21 +2151,18 @@ function createDebugEngine(config) {
1982
2151
  }
1983
2152
  });
1984
2153
  }
1985
- function createMinimalEngine(config) {
1986
- const engine = new ChangerawrMarkdown({
1987
- ...config,
1988
- extensions: []
1989
- // Override to prevent built-in extensions
1990
- });
1991
- if (engine.hasExtension("alert")) {
1992
- engine.unregisterExtension("alert");
1993
- }
1994
- if (engine.hasExtension("button")) {
1995
- engine.unregisterExtension("button");
1996
- }
1997
- if (engine.hasExtension("embed")) {
1998
- engine.unregisterExtension("embed");
1999
- }
2154
+ function createMinimalEngine(extensions = []) {
2155
+ const engine = new ChangerawrMarkdown();
2156
+ const defaultExtensions = engine.getExtensions();
2157
+ defaultExtensions.forEach((ext) => engine.unregisterExtension(ext));
2158
+ extensions.forEach((ext) => engine.registerExtension(ext));
2159
+ return engine;
2160
+ }
2161
+ function createCoreOnlyEngine(config) {
2162
+ const engine = new ChangerawrMarkdown(config);
2163
+ engine.unregisterExtension("alert");
2164
+ engine.unregisterExtension("button");
2165
+ engine.unregisterExtension("embed");
2000
2166
  return engine;
2001
2167
  }
2002
2168
  function createCustomEngine(extensions, config) {
@@ -2020,6 +2186,7 @@ var markdown2 = {
2020
2186
  createTailwindEngine,
2021
2187
  createDebugEngine,
2022
2188
  createMinimalEngine,
2189
+ createCoreOnlyEngine,
2023
2190
  createCustomEngine,
2024
2191
  // Standalone functions
2025
2192
  renderCum,
@@ -2028,6 +2195,24 @@ var markdown2 = {
2028
2195
  ChangerawrMarkdown,
2029
2196
  // Extensions
2030
2197
  extensions: {
2198
+ // All core extensions as a bundle
2199
+ core: CoreExtensions,
2200
+ // Individual core extensions
2201
+ Text: TextExtension,
2202
+ Heading: HeadingExtension,
2203
+ Bold: BoldExtension,
2204
+ Italic: ItalicExtension,
2205
+ InlineCode: InlineCodeExtension,
2206
+ CodeBlock: CodeBlockExtension,
2207
+ Link: LinkExtension,
2208
+ Image: ImageExtension,
2209
+ List: ListExtension,
2210
+ TaskList: TaskListExtension,
2211
+ Blockquote: BlockquoteExtension,
2212
+ HorizontalRule: HorizontalRuleExtension,
2213
+ Paragraph: ParagraphExtension,
2214
+ LineBreak: LineBreakExtension,
2215
+ // Feature extensions
2031
2216
  Alert: AlertExtension,
2032
2217
  Button: ButtonExtension,
2033
2218
  Embed: EmbedExtension
@@ -2088,6 +2273,14 @@ var presets = {
2088
2273
  sanitize: true
2089
2274
  }
2090
2275
  },
2276
+ /**
2277
+ * Core-only preset with just markdown basics
2278
+ */
2279
+ coreOnly: {
2280
+ renderer: {
2281
+ format: "tailwind"
2282
+ }
2283
+ },
2091
2284
  /**
2092
2285
  * Performance preset with minimal processing
2093
2286
  */
@@ -2104,6 +2297,9 @@ var presets = {
2104
2297
  };
2105
2298
  function createEngineWithPreset(presetName, additionalConfig) {
2106
2299
  const preset = presets[presetName];
2300
+ if (presetName === "coreOnly") {
2301
+ return createCoreOnlyEngine(additionalConfig);
2302
+ }
2107
2303
  return new ChangerawrMarkdown({
2108
2304
  ...preset,
2109
2305
  ...additionalConfig
@@ -2111,17 +2307,33 @@ function createEngineWithPreset(presetName, additionalConfig) {
2111
2307
  }
2112
2308
  export {
2113
2309
  AlertExtension,
2310
+ BlockquoteExtension,
2311
+ BoldExtension,
2114
2312
  ButtonExtension,
2115
2313
  ChangerawrMarkdown,
2314
+ CodeBlockExtension,
2315
+ CoreExtensions,
2116
2316
  EmbedExtension,
2317
+ HeadingExtension,
2318
+ HorizontalRuleExtension,
2319
+ ImageExtension,
2320
+ InlineCodeExtension,
2321
+ ItalicExtension,
2322
+ LineBreakExtension,
2323
+ LinkExtension,
2324
+ ListExtension,
2117
2325
  Logger,
2118
2326
  MarkdownParser,
2119
2327
  MarkdownRenderer,
2328
+ ParagraphExtension,
2120
2329
  PerformanceTimer,
2330
+ TaskListExtension,
2331
+ TextExtension,
2121
2332
  astToJSONString,
2122
2333
  astToTokens,
2123
2334
  basicSanitize,
2124
2335
  compareTokens,
2336
+ createCoreOnlyEngine,
2125
2337
  createCumEngine,
2126
2338
  createCustomEngine,
2127
2339
  createDebugEngine,