@changerawr/markdown 1.0.5 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -3,39 +3,56 @@ var MarkdownParser = class {
3
3
  constructor(config) {
4
4
  this.rules = [];
5
5
  this.warnings = [];
6
- this.config = config || {};
6
+ this.config = {
7
+ debugMode: false,
8
+ maxIterations: 1e4,
9
+ validateMarkdown: false,
10
+ ...config
11
+ };
7
12
  }
8
13
  addRule(rule) {
9
14
  this.rules.push(rule);
10
15
  this.rules.sort((a, b) => {
11
- if (a.name.includes("alert") || a.name.includes("embed") || a.name.includes("button")) return -1;
12
- if (b.name.includes("alert") || b.name.includes("embed") || b.name.includes("button")) return 1;
13
- if (a.name === "task-list") return -1;
14
- if (b.name === "task-list") return 1;
15
- if (a.name === "list" && b.name === "task-list") return 1;
16
- if (b.name === "list" && a.name === "task-list") return -1;
16
+ const aFeatureExtension = ["alert", "button", "embed"].includes(a.name);
17
+ const bFeatureExtension = ["alert", "button", "embed"].includes(b.name);
18
+ if (aFeatureExtension && !bFeatureExtension) return -1;
19
+ if (!aFeatureExtension && bFeatureExtension) return 1;
20
+ const aCoreExtension = ["text", "heading", "bold", "italic", "code", "codeblock", "link", "image", "list", "task-list", "blockquote", "hr", "paragraph", "line-break"].includes(a.name);
21
+ const bCoreExtension = ["text", "heading", "bold", "italic", "code", "codeblock", "link", "image", "list", "task-list", "blockquote", "hr", "paragraph", "line-break"].includes(b.name);
22
+ if (aCoreExtension && !bCoreExtension) return -1;
23
+ if (!aCoreExtension && bCoreExtension) return 1;
24
+ if (a.name === "image" && b.name === "link") return -1;
25
+ if (a.name === "link" && b.name === "image") return 1;
26
+ if (a.name === "task-item" && b.name === "list-item") return -1;
27
+ if (a.name === "list-item" && b.name === "task-item") return 1;
28
+ if (a.name === "codeblock" && b.name === "code") return -1;
29
+ if (a.name === "code" && b.name === "codeblock") return 1;
30
+ if (a.name === "bold" && b.name === "italic") return -1;
31
+ if (a.name === "italic" && b.name === "bold") return 1;
17
32
  return a.name.localeCompare(b.name);
18
33
  });
19
34
  }
20
- setupDefaultRulesIfEmpty() {
21
- const hasDefaultRules = this.rules.some(
22
- (rule) => !["alert", "button", "embed"].includes(rule.name)
23
- );
24
- if (!hasDefaultRules) {
25
- this.setupDefaultRules();
26
- }
27
- }
28
35
  hasRule(name) {
29
36
  return this.rules.some((rule) => rule.name === name);
30
37
  }
31
38
  parse(markdown3) {
32
39
  this.warnings = [];
33
- 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
+ }
34
51
  const processedMarkdown = this.preprocessMarkdown(markdown3);
35
52
  const tokens = [];
36
53
  let remaining = processedMarkdown;
37
54
  let iterationCount = 0;
38
- const maxIterations = this.config.maxIterations || markdown3.length * 2;
55
+ const maxIterations = this.config.maxIterations || 1e4;
39
56
  while (remaining.length > 0 && iterationCount < maxIterations) {
40
57
  iterationCount++;
41
58
  let matched = false;
@@ -124,9 +141,6 @@ var MarkdownParser = class {
124
141
  clearWarnings() {
125
142
  this.warnings = [];
126
143
  }
127
- getIterationCount() {
128
- return 0;
129
- }
130
144
  preprocessMarkdown(markdown3) {
131
145
  if (this.config.validateMarkdown) {
132
146
  this.validateMarkdown(markdown3);
@@ -181,161 +195,6 @@ var MarkdownParser = class {
181
195
  }
182
196
  return processed;
183
197
  }
184
- setupDefaultRules() {
185
- this.addRule({
186
- name: "heading",
187
- pattern: /^(#{1,6})\s+(.+)$/m,
188
- render: (match) => ({
189
- type: "heading",
190
- content: match[2]?.trim() || "",
191
- raw: match[0] || "",
192
- attributes: {
193
- level: String(match[1]?.length || 1)
194
- }
195
- })
196
- });
197
- this.addRule({
198
- name: "codeblock",
199
- pattern: /```(\w+)?\s*\n([\s\S]*?)\n```/,
200
- render: (match) => ({
201
- type: "codeblock",
202
- content: match[2] || "",
203
- raw: match[0] || "",
204
- attributes: {
205
- language: match[1] || "text"
206
- }
207
- })
208
- });
209
- this.addRule({
210
- name: "hard-break-backslash",
211
- pattern: /\\\s*\n/,
212
- render: (match) => ({
213
- type: "line-break",
214
- content: "",
215
- raw: match[0] || ""
216
- })
217
- });
218
- this.addRule({
219
- name: "hard-break-spaces",
220
- pattern: / +\n/,
221
- render: (match) => ({
222
- type: "line-break",
223
- content: "",
224
- raw: match[0] || ""
225
- })
226
- });
227
- this.addRule({
228
- name: "paragraph-break",
229
- pattern: /\n\s*\n/,
230
- render: (match) => ({
231
- type: "paragraph-break",
232
- content: "",
233
- raw: match[0] || ""
234
- })
235
- });
236
- this.addRule({
237
- name: "bold",
238
- pattern: /\*\*((?:(?!\*\*).)+)\*\*/,
239
- render: (match) => ({
240
- type: "bold",
241
- content: match[1] || "",
242
- raw: match[0] || ""
243
- })
244
- });
245
- this.addRule({
246
- name: "italic",
247
- pattern: /\*((?:(?!\*).)+)\*/,
248
- render: (match) => ({
249
- type: "italic",
250
- content: match[1] || "",
251
- raw: match[0] || ""
252
- })
253
- });
254
- this.addRule({
255
- name: "code",
256
- pattern: /`([^`]+)`/,
257
- render: (match) => ({
258
- type: "code",
259
- content: match[1] || "",
260
- raw: match[0] || ""
261
- })
262
- });
263
- this.addRule({
264
- name: "image",
265
- pattern: /!\[([^\]]*)\]\(([^)]+?)(?:\s+"([^"]+)")?\)/,
266
- render: (match) => ({
267
- type: "image",
268
- content: match[1] || "",
269
- raw: match[0] || "",
270
- attributes: {
271
- alt: match[1] || "",
272
- src: match[2] || "",
273
- title: match[3] || ""
274
- }
275
- })
276
- });
277
- this.addRule({
278
- name: "link",
279
- pattern: /\[([^\]]+)\]\(([^)]+)\)/,
280
- render: (match) => ({
281
- type: "link",
282
- content: match[1] || "",
283
- raw: match[0] || "",
284
- attributes: {
285
- href: match[2] || ""
286
- }
287
- })
288
- });
289
- this.addRule({
290
- name: "task-list",
291
- pattern: /^(\s*)-\s*\[([ xX])\]\s*(.+)$/m,
292
- render: (match) => ({
293
- type: "task-item",
294
- content: match[3] || "",
295
- raw: match[0] || "",
296
- attributes: {
297
- checked: String((match[2] || "").toLowerCase() === "x")
298
- }
299
- })
300
- });
301
- this.addRule({
302
- name: "list",
303
- pattern: /^(\s*)[-*+]\s+(.+)$/m,
304
- render: (match) => ({
305
- type: "list-item",
306
- content: match[2] || "",
307
- raw: match[0] || ""
308
- })
309
- });
310
- this.addRule({
311
- name: "blockquote",
312
- pattern: /^>\s+(.+)$/m,
313
- render: (match) => ({
314
- type: "blockquote",
315
- content: match[1] || "",
316
- raw: match[0] || ""
317
- })
318
- });
319
- this.addRule({
320
- name: "hr",
321
- pattern: /^---$/m,
322
- render: (match) => ({
323
- type: "hr",
324
- content: "",
325
- raw: match[0] || ""
326
- })
327
- });
328
- this.addRule({
329
- name: "soft-break",
330
- pattern: /\n/,
331
- render: (match) => ({
332
- type: "soft-break",
333
- content: " ",
334
- // Convert to space for inline text
335
- raw: match[0] || ""
336
- })
337
- });
338
- }
339
198
  };
340
199
 
341
200
  // src/utils.ts
@@ -654,7 +513,6 @@ var MarkdownRenderer = class {
654
513
  debugMode: false,
655
514
  ...config
656
515
  };
657
- this.setupDefaultRules();
658
516
  }
659
517
  addRule(rule) {
660
518
  this.rules.set(rule.type, rule);
@@ -664,28 +522,20 @@ var MarkdownRenderer = class {
664
522
  }
665
523
  render(tokens) {
666
524
  this.warnings = [];
667
- 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));
668
533
  const combinedHtml = htmlParts.join("");
669
534
  if (this.config.sanitize && !this.config.allowUnsafeHtml) {
670
535
  return sanitizeHtml(combinedHtml);
671
536
  }
672
537
  return combinedHtml;
673
538
  }
674
- getWarnings() {
675
- return [...this.warnings];
676
- }
677
- getConfig() {
678
- return { ...this.config };
679
- }
680
- updateConfig(config) {
681
- this.config = { ...this.config, ...config };
682
- }
683
- setDebugMode(enabled) {
684
- this.config.debugMode = enabled;
685
- }
686
- clearWarnings() {
687
- this.warnings = [];
688
- }
689
539
  renderToken(token) {
690
540
  const rule = this.rules.get(token.type);
691
541
  if (rule) {
@@ -697,9 +547,6 @@ var MarkdownRenderer = class {
697
547
  return this.createErrorBlock(`Render error for ${token.type}: ${errorMessage}`);
698
548
  }
699
549
  }
700
- if (token.type === "text") {
701
- return escapeHtml(token.content || token.raw || "");
702
- }
703
550
  if (this.config.debugMode) {
704
551
  return this.createDebugBlock(token);
705
552
  }
@@ -727,15 +574,234 @@ var MarkdownRenderer = class {
727
574
  <strong>Content:</strong> ${escapeHtml(token.content || token.raw || "")}
728
575
  </div>`;
729
576
  }
730
- setupDefaultRules() {
731
- 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
+ {
732
797
  type: "heading",
733
798
  render: (token) => {
734
799
  const level = parseInt(token.attributes?.level || "1");
735
800
  const text = token.content;
736
801
  const id = generateId(text);
737
802
  const escapedContent = escapeHtml(text);
738
- if (this.config.format === "html") {
803
+ const format = token.attributes?.format || "html";
804
+ if (format === "html") {
739
805
  return `<h${level} id="${id}">${escapedContent}</h${level}>`;
740
806
  }
741
807
  let headingClasses = "group relative flex items-center gap-2";
@@ -762,61 +828,108 @@ var MarkdownRenderer = class {
762
828
  return `<h${level} id="${id}" class="${headingClasses}">
763
829
  ${escapedContent}
764
830
  <a href="#${id}" class="opacity-0 group-hover:opacity-100 text-muted-foreground transition-opacity">
765
- <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
766
- <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"/>
767
- </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>
768
835
  </a>
769
836
  </h${level}>`;
770
837
  }
771
- });
772
- this.addRule({
773
- type: "bold",
774
- render: (token) => {
775
- const content = escapeHtml(token.content);
776
- if (this.config.format === "html") {
777
- return `<strong>${content}</strong>`;
778
- }
779
- return `<strong class="font-bold">${content}</strong>`;
780
- }
781
- });
782
- this.addRule({
783
- type: "italic",
784
- render: (token) => {
785
- const content = escapeHtml(token.content);
786
- if (this.config.format === "html") {
787
- return `<em>${content}</em>`;
788
- }
789
- return `<em class="italic">${content}</em>`;
790
- }
791
- });
792
- this.addRule({
793
- 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",
794
859
  render: (token) => {
795
- const content = escapeHtml(token.content);
796
- if (this.config.format === "html") {
797
- 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;">';
798
863
  }
799
- 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">';
800
865
  }
801
- });
802
- this.addRule({
803
- 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",
804
892
  render: (token) => {
805
- const language = token.attributes?.language || "text";
806
- const escapedCode = escapeHtml(token.content);
807
- if (this.config.format === "html") {
808
- 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" />`;
809
900
  }
810
- 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" />`;
811
902
  }
812
- });
813
- 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
+ {
814
926
  type: "link",
815
927
  render: (token) => {
816
928
  const href = token.attributes?.href || "#";
817
929
  const escapedHref = escapeHtml(href);
818
930
  const escapedText = escapeHtml(token.content);
819
- if (this.config.format === "html") {
931
+ const format = token.attributes?.format || "html";
932
+ if (format === "html") {
820
933
  return `<a href="${escapedHref}" target="_blank" rel="noopener noreferrer">${escapedText}</a>`;
821
934
  }
822
935
  return `<a href="${escapedHref}" class="text-primary hover:underline inline-flex items-center gap-1" target="_blank" rel="noopener noreferrer">
@@ -828,47 +941,55 @@ var MarkdownRenderer = class {
828
941
  </svg>
829
942
  </a>`;
830
943
  }
831
- });
832
- 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
+ {
833
964
  type: "list-item",
834
965
  render: (token) => `<li>${escapeHtml(token.content)}</li>`
835
- });
836
- this.addRule({
837
- type: "blockquote",
838
- render: (token) => {
839
- const content = escapeHtml(token.content);
840
- if (this.config.format === "html") {
841
- return `<blockquote style="border-left: 2px solid #d1d5db; padding: 8px 0 8px 16px; margin: 16px 0; font-style: italic; color: #6b7280;">${content}</blockquote>`;
842
- }
843
- return `<blockquote class="pl-4 py-2 border-l-2 border-border italic text-muted-foreground my-4">${content}</blockquote>`;
844
- }
845
- });
846
- this.addRule({
847
- type: "text",
848
- render: (token) => {
849
- if (!token.content) return "";
850
- return escapeHtml(token.content);
851
- }
852
- });
853
- this.addRule({
854
- type: "paragraph",
855
- render: (token) => {
856
- if (!token.content) return "";
857
- const content = token.content.trim();
858
- if (!content) return "";
859
- const processedContent = content.includes("<br>") ? content : escapeHtml(content);
860
- if (this.config.format === "html") {
861
- 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")
862
981
  }
863
- return `<p class="leading-7 mb-4">${processedContent}</p>`;
864
- }
865
- });
866
- this.addRule({
982
+ })
983
+ }
984
+ ],
985
+ renderRules: [
986
+ {
867
987
  type: "task-item",
868
988
  render: (token) => {
869
989
  const isChecked = token.attributes?.checked === "true";
870
990
  const escapedContent = escapeHtml(token.content);
871
- if (this.config.format === "html") {
991
+ const format = token.attributes?.format || "html";
992
+ if (format === "html") {
872
993
  return `<div style="display: flex; align-items: center; gap: 8px; margin: 8px 0;">
873
994
  <input type="checkbox" ${isChecked ? "checked" : ""} disabled style="margin: 0;" />
874
995
  <span${isChecked ? ' style="text-decoration: line-through; color: #6b7280;"' : ""}>${escapedContent}</span>
@@ -880,44 +1001,65 @@ var MarkdownRenderer = class {
880
1001
  <span${isChecked ? ' class="line-through text-muted-foreground"' : ""}>${escapedContent}</span>
881
1002
  </div>`;
882
1003
  }
883
- });
884
- this.addRule({
885
- 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",
886
1015
  render: (token) => {
887
- const src = token.attributes?.src || "";
888
- const alt = token.attributes?.alt || "";
889
- const title = token.attributes?.title || "";
890
- const titleAttr = title ? ` title="${escapeHtml(title)}"` : "";
891
- if (this.config.format === "html") {
892
- 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>`;
893
1023
  }
894
- 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>`;
895
1025
  }
896
- });
897
- this.addRule({
898
- type: "hr",
899
- render: () => {
900
- if (this.config.format === "html") {
901
- return '<hr style="margin: 24px 0; border: none; border-top: 1px solid #d1d5db;">';
902
- }
903
- 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);
904
1040
  }
905
- });
906
- this.addRule({
907
- type: "line-break",
908
- render: () => "<br>"
909
- });
910
- this.addRule({
911
- type: "paragraph-break",
912
- render: () => "</p><p>"
913
- });
914
- this.addRule({
915
- type: "soft-break",
916
- render: (token) => token.content || " "
917
- });
918
- }
1041
+ }
1042
+ ]
919
1043
  };
920
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
+
921
1063
  // src/extensions/alert.ts
922
1064
  var AlertExtension = {
923
1065
  name: "alert",
@@ -997,26 +1139,19 @@ var ButtonExtension = {
997
1139
  name: "button",
998
1140
  pattern: /\[button:([^\]]+)\]\(([^)]+)\)(?:\{([^}]+)\})?/,
999
1141
  render: (match) => {
1000
- const text = match[1] || "";
1001
- const href = match[2] || "";
1002
- const optionsString = match[3] || "";
1003
- const options = optionsString.split(",").map((opt) => opt.trim()).filter(Boolean);
1004
- const styleOptions = ["default", "primary", "secondary", "success", "danger", "outline", "ghost"];
1005
- const style = options.find((opt) => styleOptions.includes(opt)) || "primary";
1006
- const sizeOptions = ["sm", "md", "lg"];
1007
- const size = options.find((opt) => sizeOptions.includes(opt)) || "md";
1008
- const disabled = options.includes("disabled");
1009
- const target = options.includes("self") ? "_self" : "_blank";
1142
+ const options = match[3] ? match[3].split(",").map((opt) => opt.trim()) : [];
1010
1143
  return {
1011
1144
  type: "button",
1012
- content: text,
1145
+ content: match[1] || "",
1013
1146
  raw: match[0] || "",
1014
1147
  attributes: {
1015
- href,
1016
- style,
1017
- size,
1018
- disabled: disabled.toString(),
1019
- 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"
1020
1155
  }
1021
1156
  };
1022
1157
  }
@@ -1031,53 +1166,92 @@ var ButtonExtension = {
1031
1166
  const size = token.attributes?.size || "md";
1032
1167
  const disabled = token.attributes?.disabled === "true";
1033
1168
  const target = token.attributes?.target || "_blank";
1034
- const text = token.content;
1035
- 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();
1036
1243
  const targetAttr = target === "_blank" ? ' target="_blank" rel="noopener noreferrer"' : target === "_self" ? ' target="_self"' : "";
1037
1244
  const disabledAttr = disabled ? ' aria-disabled="true" tabindex="-1"' : "";
1038
- 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>` : "";
1039
1248
  return `<a href="${href}" class="${classes}"${targetAttr}${disabledAttr}>
1040
- ${text}${externalIcon}
1041
- </a>`;
1249
+ <span class="relative z-10">${token.content}</span>${externalIcon}
1250
+ </a>`;
1042
1251
  }
1043
1252
  }
1044
1253
  ]
1045
1254
  };
1046
- function buildButtonClasses(style, size) {
1047
- const base = [
1048
- "inline-flex",
1049
- "items-center",
1050
- "justify-center",
1051
- "font-medium",
1052
- "rounded-lg",
1053
- "transition-colors",
1054
- "focus:outline-none",
1055
- "focus:ring-2",
1056
- "focus:ring-offset-2",
1057
- "disabled:opacity-50",
1058
- "disabled:cursor-not-allowed"
1059
- ];
1060
- const sizes = {
1061
- sm: ["px-3", "py-1.5", "text-sm"],
1062
- md: ["px-4", "py-2", "text-base"],
1063
- lg: ["px-6", "py-3", "text-lg"]
1064
- };
1065
- const styles = {
1066
- default: ["bg-slate-600", "text-white", "hover:bg-slate-700", "focus:ring-slate-500"],
1067
- primary: ["bg-blue-600", "text-white", "hover:bg-blue-700", "focus:ring-blue-500"],
1068
- secondary: ["bg-gray-600", "text-white", "hover:bg-gray-700", "focus:ring-gray-500"],
1069
- success: ["bg-green-600", "text-white", "hover:bg-green-700", "focus:ring-green-500"],
1070
- danger: ["bg-red-600", "text-white", "hover:bg-red-700", "focus:ring-red-500"],
1071
- outline: ["border", "border-blue-600", "text-blue-600", "hover:bg-blue-50", "focus:ring-blue-500"],
1072
- ghost: ["text-gray-700", "hover:bg-gray-100", "focus:ring-gray-500"]
1073
- };
1074
- const allClasses = [
1075
- ...base,
1076
- ...sizes[size] ?? sizes.md ?? [],
1077
- ...styles[style] ?? styles.primary ?? []
1078
- ];
1079
- return allClasses.join(" ");
1080
- }
1081
1255
 
1082
1256
  // src/extensions/embed.ts
1083
1257
  var EmbedExtension = {
@@ -1155,8 +1329,7 @@ function renderYouTubeEmbed(url, options, classes) {
1155
1329
  params.set("rel", "0");
1156
1330
  params.set("modestbranding", "1");
1157
1331
  const embedUrl = `https://www.youtube.com/embed/${videoId}?${params.toString()}`;
1158
- return `
1159
- <div class="${classes}">
1332
+ return `<div class="${classes}">
1160
1333
  <div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
1161
1334
  <iframe
1162
1335
  style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 0;"
@@ -1184,8 +1357,7 @@ function renderCodePenEmbed(url, options, classes) {
1184
1357
  "editable": "true"
1185
1358
  });
1186
1359
  const embedUrl = `https://codepen.io/${user}/embed/${penId}?${embedParams.toString()}`;
1187
- return `
1188
- <div class="${classes}">
1360
+ return `<div class="${classes}">
1189
1361
  <iframe
1190
1362
  height="${height}"
1191
1363
  style="width: 100%; border: 0;"
@@ -1209,8 +1381,7 @@ function renderVimeoEmbed(url, options, classes) {
1209
1381
  if (options.mute === "1") params.set("muted", "1");
1210
1382
  if (options.loop === "1") params.set("loop", "1");
1211
1383
  const embedUrl = `https://player.vimeo.com/video/${videoId}?${params.toString()}`;
1212
- return `
1213
- <div class="${classes}">
1384
+ return `<div class="${classes}">
1214
1385
  <div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
1215
1386
  <iframe
1216
1387
  style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 0;"
@@ -1226,8 +1397,7 @@ function renderVimeoEmbed(url, options, classes) {
1226
1397
  function renderSpotifyEmbed(url, options, classes) {
1227
1398
  const embedUrl = url.replace("open.spotify.com", "open.spotify.com/embed");
1228
1399
  const height = options.height || "380";
1229
- return `
1230
- <div class="${classes}">
1400
+ return `<div class="${classes}">
1231
1401
  <iframe
1232
1402
  style="border-radius: 12px;"
1233
1403
  src="${embedUrl}"
@@ -1250,8 +1420,7 @@ function renderCodeSandboxEmbed(url, options, classes) {
1250
1420
  if (!embedUrl.includes("?")) {
1251
1421
  embedUrl += `?view=${view}`;
1252
1422
  }
1253
- return `
1254
- <div class="${classes}">
1423
+ return `<div class="${classes}">
1255
1424
  <iframe
1256
1425
  src="${embedUrl}"
1257
1426
  style="width: 100%; height: ${height}px; border: 0; border-radius: 4px; overflow: hidden;"
@@ -1264,8 +1433,7 @@ function renderCodeSandboxEmbed(url, options, classes) {
1264
1433
  function renderFigmaEmbed(url, options, classes) {
1265
1434
  const embedUrl = `https://www.figma.com/embed?embed_host=share&url=${encodeURIComponent(url)}`;
1266
1435
  const height = options.height || "450";
1267
- return `
1268
- <div class="${classes}">
1436
+ return `<div class="${classes}">
1269
1437
  <iframe
1270
1438
  style="border: none;"
1271
1439
  width="100%"
@@ -1276,8 +1444,7 @@ function renderFigmaEmbed(url, options, classes) {
1276
1444
  </div>`;
1277
1445
  }
1278
1446
  function renderTwitterEmbed(url, _options, classes) {
1279
- return `
1280
- <div class="${classes}">
1447
+ return `<div class="${classes}">
1281
1448
  <div class="p-4">
1282
1449
  <div class="flex items-center gap-3 mb-3">
1283
1450
  <svg class="w-6 h-6 fill-current text-blue-500" viewBox="0 0 24 24">
@@ -1305,8 +1472,7 @@ function renderGitHubEmbed(url, _options, classes) {
1305
1472
  if (!owner || !repo) {
1306
1473
  return createErrorEmbed("Invalid GitHub URL", url, classes);
1307
1474
  }
1308
- return `
1309
- <div class="${classes}">
1475
+ return `<div class="${classes}">
1310
1476
  <div class="p-4">
1311
1477
  <div class="flex items-center gap-3 mb-3">
1312
1478
  <svg class="w-6 h-6 fill-current" viewBox="0 0 24 24">
@@ -1329,8 +1495,7 @@ function renderGitHubEmbed(url, _options, classes) {
1329
1495
  }
1330
1496
  function renderGenericEmbed(url, _options, classes) {
1331
1497
  const domain = extractDomain(url);
1332
- return `
1333
- <div class="${classes}">
1498
+ return `<div class="${classes}">
1334
1499
  <div class="p-4">
1335
1500
  <div class="flex items-center gap-3 mb-3">
1336
1501
  <div class="w-10 h-10 rounded-lg bg-muted flex items-center justify-center">
@@ -1354,8 +1519,7 @@ function renderGenericEmbed(url, _options, classes) {
1354
1519
  </div>`;
1355
1520
  }
1356
1521
  function createErrorEmbed(error, url, classes) {
1357
- return `
1358
- <div class="${classes}">
1522
+ return `<div class="${classes}">
1359
1523
  <div class="p-4 text-destructive">
1360
1524
  <div class="font-medium flex items-center gap-2">
1361
1525
  <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -1389,19 +1553,54 @@ var ChangerawrMarkdown = class {
1389
1553
  this.extensions = /* @__PURE__ */ new Map();
1390
1554
  this.parser = new MarkdownParser(config?.parser);
1391
1555
  this.renderer = new MarkdownRenderer(config?.renderer);
1392
- this.registerBuiltInExtensions();
1556
+ this.registerCoreExtensions();
1557
+ this.registerFeatureExtensions();
1393
1558
  if (config?.extensions) {
1394
1559
  config.extensions.forEach((extension) => {
1395
1560
  this.registerExtension(extension);
1396
1561
  });
1397
1562
  }
1398
- this.parser.setupDefaultRulesIfEmpty();
1399
1563
  }
1400
- /**
1401
- * Register a custom extension
1402
- */
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
+ }
1403
1574
  registerExtension(extension) {
1404
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
+ });
1405
1604
  this.extensions.set(extension.name, extension);
1406
1605
  extension.parseRules.forEach((rule) => {
1407
1606
  this.parser.addRule(rule);
@@ -1422,9 +1621,6 @@ var ChangerawrMarkdown = class {
1422
1621
  };
1423
1622
  }
1424
1623
  }
1425
- /**
1426
- * Unregister an extension
1427
- */
1428
1624
  unregisterExtension(name) {
1429
1625
  const extension = this.extensions.get(name);
1430
1626
  if (!extension) {
@@ -1438,46 +1634,25 @@ var ChangerawrMarkdown = class {
1438
1634
  return false;
1439
1635
  }
1440
1636
  }
1441
- /**
1442
- * Parse markdown content into tokens
1443
- */
1444
1637
  parse(markdown3) {
1445
1638
  return this.parser.parse(markdown3);
1446
1639
  }
1447
- /**
1448
- * Render tokens to HTML
1449
- */
1450
1640
  render(tokens) {
1451
1641
  return this.renderer.render(tokens);
1452
1642
  }
1453
- /**
1454
- * Parse and render markdown to HTML in one step
1455
- */
1456
1643
  toHtml(markdown3) {
1457
1644
  const tokens = this.parse(markdown3);
1458
1645
  return this.render(tokens);
1459
1646
  }
1460
- /**
1461
- * Get list of registered extensions
1462
- */
1463
1647
  getExtensions() {
1464
1648
  return Array.from(this.extensions.keys());
1465
1649
  }
1466
- /**
1467
- * Check if extension is registered
1468
- */
1469
1650
  hasExtension(name) {
1470
1651
  return this.extensions.has(name);
1471
1652
  }
1472
- /**
1473
- * Get parser warnings
1474
- */
1475
1653
  getWarnings() {
1476
1654
  return [...this.parser.getWarnings(), ...this.renderer.getWarnings()];
1477
1655
  }
1478
- /**
1479
- * Get debug information from last render
1480
- */
1481
1656
  getDebugInfo() {
1482
1657
  return {
1483
1658
  warnings: this.getWarnings(),
@@ -1487,9 +1662,6 @@ var ChangerawrMarkdown = class {
1487
1662
  iterationCount: 0
1488
1663
  };
1489
1664
  }
1490
- /**
1491
- * Get performance metrics for the last operation
1492
- */
1493
1665
  getPerformanceMetrics() {
1494
1666
  return {
1495
1667
  parseTime: 0,
@@ -1498,28 +1670,29 @@ var ChangerawrMarkdown = class {
1498
1670
  tokenCount: 0
1499
1671
  };
1500
1672
  }
1501
- /**
1502
- * Register built-in extensions
1503
- */
1504
- registerBuiltInExtensions() {
1505
- this.registerExtension(AlertExtension);
1506
- this.registerExtension(ButtonExtension);
1507
- this.registerExtension(EmbedExtension);
1508
- }
1509
- /**
1510
- * Rebuild parser and renderer with current extensions
1511
- */
1512
1673
  rebuildParserAndRenderer() {
1513
1674
  const parserConfig = this.parser.getConfig();
1514
1675
  const rendererConfig = this.renderer.getConfig();
1515
1676
  this.parser = new MarkdownParser(parserConfig);
1516
1677
  this.renderer = new MarkdownRenderer(rendererConfig);
1517
1678
  const extensionsToRegister = Array.from(this.extensions.values());
1518
- this.extensions.clear();
1519
- extensionsToRegister.forEach((extension) => {
1520
- 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
+ });
1521
1695
  });
1522
- this.parser.setupDefaultRulesIfEmpty();
1523
1696
  }
1524
1697
  };
1525
1698
  var markdown = new ChangerawrMarkdown();
@@ -1949,8 +2122,7 @@ function createHTMLEngine(config) {
1949
2122
  renderer: {
1950
2123
  format: "html",
1951
2124
  sanitize: true,
1952
- allowUnsafeHtml: false,
1953
- ...config?.parser
2125
+ allowUnsafeHtml: false
1954
2126
  }
1955
2127
  });
1956
2128
  }
@@ -1960,8 +2132,7 @@ function createTailwindEngine(config) {
1960
2132
  renderer: {
1961
2133
  format: "tailwind",
1962
2134
  sanitize: true,
1963
- allowUnsafeHtml: false,
1964
- ...config?.parser
2135
+ allowUnsafeHtml: false
1965
2136
  }
1966
2137
  });
1967
2138
  }
@@ -1980,21 +2151,18 @@ function createDebugEngine(config) {
1980
2151
  }
1981
2152
  });
1982
2153
  }
1983
- function createMinimalEngine(config) {
1984
- const engine = new ChangerawrMarkdown({
1985
- ...config,
1986
- extensions: []
1987
- // Override to prevent built-in extensions
1988
- });
1989
- if (engine.hasExtension("alert")) {
1990
- engine.unregisterExtension("alert");
1991
- }
1992
- if (engine.hasExtension("button")) {
1993
- engine.unregisterExtension("button");
1994
- }
1995
- if (engine.hasExtension("embed")) {
1996
- engine.unregisterExtension("embed");
1997
- }
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");
1998
2166
  return engine;
1999
2167
  }
2000
2168
  function createCustomEngine(extensions, config) {
@@ -2018,6 +2186,7 @@ var markdown2 = {
2018
2186
  createTailwindEngine,
2019
2187
  createDebugEngine,
2020
2188
  createMinimalEngine,
2189
+ createCoreOnlyEngine,
2021
2190
  createCustomEngine,
2022
2191
  // Standalone functions
2023
2192
  renderCum,
@@ -2026,6 +2195,24 @@ var markdown2 = {
2026
2195
  ChangerawrMarkdown,
2027
2196
  // Extensions
2028
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
2029
2216
  Alert: AlertExtension,
2030
2217
  Button: ButtonExtension,
2031
2218
  Embed: EmbedExtension
@@ -2086,6 +2273,14 @@ var presets = {
2086
2273
  sanitize: true
2087
2274
  }
2088
2275
  },
2276
+ /**
2277
+ * Core-only preset with just markdown basics
2278
+ */
2279
+ coreOnly: {
2280
+ renderer: {
2281
+ format: "tailwind"
2282
+ }
2283
+ },
2089
2284
  /**
2090
2285
  * Performance preset with minimal processing
2091
2286
  */
@@ -2102,6 +2297,9 @@ var presets = {
2102
2297
  };
2103
2298
  function createEngineWithPreset(presetName, additionalConfig) {
2104
2299
  const preset = presets[presetName];
2300
+ if (presetName === "coreOnly") {
2301
+ return createCoreOnlyEngine(additionalConfig);
2302
+ }
2105
2303
  return new ChangerawrMarkdown({
2106
2304
  ...preset,
2107
2305
  ...additionalConfig
@@ -2109,17 +2307,33 @@ function createEngineWithPreset(presetName, additionalConfig) {
2109
2307
  }
2110
2308
  export {
2111
2309
  AlertExtension,
2310
+ BlockquoteExtension,
2311
+ BoldExtension,
2112
2312
  ButtonExtension,
2113
2313
  ChangerawrMarkdown,
2314
+ CodeBlockExtension,
2315
+ CoreExtensions,
2114
2316
  EmbedExtension,
2317
+ HeadingExtension,
2318
+ HorizontalRuleExtension,
2319
+ ImageExtension,
2320
+ InlineCodeExtension,
2321
+ ItalicExtension,
2322
+ LineBreakExtension,
2323
+ LinkExtension,
2324
+ ListExtension,
2115
2325
  Logger,
2116
2326
  MarkdownParser,
2117
2327
  MarkdownRenderer,
2328
+ ParagraphExtension,
2118
2329
  PerformanceTimer,
2330
+ TaskListExtension,
2331
+ TextExtension,
2119
2332
  astToJSONString,
2120
2333
  astToTokens,
2121
2334
  basicSanitize,
2122
2335
  compareTokens,
2336
+ createCoreOnlyEngine,
2123
2337
  createCumEngine,
2124
2338
  createCustomEngine,
2125
2339
  createDebugEngine,