@leadertechie/md2html 0.1.0-alpha.16 → 0.1.0-alpha.18

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.js CHANGED
@@ -55,8 +55,9 @@ class ParagraphHandler {
55
55
  handle(token, ctx) {
56
56
  const tokens = token.tokens || [];
57
57
  const hasInlineImage = tokens.some((t) => t.type === "image");
58
+ const hasInlineLink = tokens.some((t) => t.type === "link");
58
59
  const hasInlineHTML = tokens.some((t) => t.type === "html");
59
- if (hasInlineImage || ctx.preserveRawHTML && hasInlineHTML) {
60
+ if (hasInlineImage || hasInlineLink || ctx.preserveRawHTML && hasInlineHTML) {
60
61
  const children = tokens.map((t) => {
61
62
  if (t.type === "image") {
62
63
  return {
@@ -65,6 +66,16 @@ class ParagraphHandler {
65
66
  alt: t.text || ""
66
67
  };
67
68
  }
69
+ if (t.type === "link") {
70
+ return {
71
+ type: "link",
72
+ content: ctx.processSlots(ctx.processInlineFormatting(t.text || "")),
73
+ attributes: {
74
+ href: t.href || "",
75
+ ...t.title ? { title: t.title } : {}
76
+ }
77
+ };
78
+ }
68
79
  if (t.type === "html" && ctx.preserveRawHTML) {
69
80
  const processed = ctx.processRawHTML(t.raw);
70
81
  if (processed.trim()) {
@@ -161,6 +172,24 @@ class HtmlHandler {
161
172
  return { type: "container", content: token.raw };
162
173
  }
163
174
  }
175
+ class LinkHandler {
176
+ constructor() {
177
+ this.type = "link";
178
+ }
179
+ handle(token, ctx) {
180
+ const text = token.text || "";
181
+ const href = token.href || "";
182
+ const title = token.title || "";
183
+ return {
184
+ type: "link",
185
+ content: ctx.processSlots(ctx.processInlineFormatting(text)),
186
+ attributes: {
187
+ href,
188
+ ...title ? { title } : {}
189
+ }
190
+ };
191
+ }
192
+ }
164
193
  class CatchAllHandler {
165
194
  constructor() {
166
195
  this.type = "*";
@@ -181,6 +210,77 @@ class CatchAllHandler {
181
210
  };
182
211
  }
183
212
  }
213
+ class FrontmatterHandler {
214
+ constructor() {
215
+ this.type = "frontmatter";
216
+ }
217
+ handle(token, ctx) {
218
+ const raw = token.raw || "";
219
+ const lines = raw.split("\n");
220
+ const parsed = {};
221
+ let currentKey = null;
222
+ let currentArray = [];
223
+ for (const line of lines) {
224
+ const listMatch = line.match(/^\s+-\s+(.+)$/);
225
+ if (listMatch && currentKey) {
226
+ currentArray.push(listMatch[1].trim());
227
+ continue;
228
+ }
229
+ if (currentKey && currentArray.length > 0) {
230
+ parsed[currentKey] = [...currentArray];
231
+ currentArray = [];
232
+ currentKey = null;
233
+ }
234
+ const keyMatch = line.match(/^(\w[\w_-]*)\s*:\s*(.*)$/);
235
+ if (keyMatch) {
236
+ currentKey = keyMatch[1];
237
+ const val = keyMatch[2].trim();
238
+ if (val === "") {
239
+ continue;
240
+ } else if (val.startsWith("[") && val.endsWith("]")) {
241
+ parsed[currentKey] = val.slice(1, -1).split(",").map((s) => s.trim().replace(/^["']|["']$/g, ""));
242
+ currentKey = null;
243
+ } else {
244
+ parsed[currentKey] = val.replace(/^["']|["']$/g, "");
245
+ currentKey = null;
246
+ }
247
+ }
248
+ }
249
+ if (currentKey && currentArray.length > 0) {
250
+ parsed[currentKey] = [...currentArray];
251
+ }
252
+ if (ctx.metadata) {
253
+ Object.assign(ctx.metadata, parsed);
254
+ }
255
+ return null;
256
+ }
257
+ }
258
+ class ContainerBlockHandler {
259
+ constructor() {
260
+ this.type = "containerBlock";
261
+ }
262
+ handle(token, ctx) {
263
+ const specifier = token.specifier;
264
+ const childTokens = token.tokens;
265
+ if (!specifier) return null;
266
+ const tagMatch = specifier.match(/^(\w+)/);
267
+ const idMatch = specifier.match(/#([\w-]+)/);
268
+ const classMatches = [...specifier.matchAll(/\.([\w-]+)/g)];
269
+ const tag = tagMatch?.[1] || "div";
270
+ const id = idMatch?.[1] || "";
271
+ const classes = classMatches.map((m) => m[1]);
272
+ const children = ctx.parseTokens(childTokens, 0);
273
+ return {
274
+ type: "container",
275
+ children,
276
+ attributes: {
277
+ tag,
278
+ id: id || void 0
279
+ },
280
+ className: classes.length > 0 ? classes.join(" ") : void 0
281
+ };
282
+ }
283
+ }
184
284
  class TokenHandlerRegistry {
185
285
  constructor() {
186
286
  this.handlers = /* @__PURE__ */ new Map();
@@ -192,6 +292,9 @@ class TokenHandlerRegistry {
192
292
  this.register(new HrHandler());
193
293
  this.register(new BlockquoteHandler());
194
294
  this.register(new HtmlHandler());
295
+ this.register(new FrontmatterHandler());
296
+ this.register(new ContainerBlockHandler());
297
+ this.register(new LinkHandler());
195
298
  this.catchAll = new CatchAllHandler();
196
299
  }
197
300
  /** Register a handler. Overrides any existing handler for the same token type. */
@@ -347,6 +450,7 @@ class MarkdownParser {
347
450
  */
348
451
  createContext() {
349
452
  const self = this;
453
+ const metadata = {};
350
454
  return {
351
455
  get preserveRawHTML() {
352
456
  return self.preserveRawHTML;
@@ -364,10 +468,16 @@ class MarkdownParser {
364
468
  parseTokens: (tokens, depth) => self.parseTokens(tokens, depth),
365
469
  reportUnhandled: (type, token) => {
366
470
  self.onUnhandledToken?.(type, token);
367
- }
471
+ },
472
+ metadata
368
473
  };
369
474
  }
370
- parseTokens(tokens, depth = 0) {
475
+ /**
476
+ * Process an array of marked tokens into ContentNodes.
477
+ * When depth === 0 (root), creates a shared context that accumulates metadata.
478
+ * For recursive calls (depth > 0), creates a fresh context for each level.
479
+ */
480
+ parseTokens(tokens, depth = 0, sharedCtx) {
371
481
  if (depth > this.maxRecursionDepth) {
372
482
  const msg = `[md2html] Max recursion depth (${this.maxRecursionDepth}) exceeded, truncating`;
373
483
  if (this.errorRecovery === "warn") {
@@ -376,7 +486,7 @@ class MarkdownParser {
376
486
  return [];
377
487
  }
378
488
  const nodes = [];
379
- const ctx = this.createContext();
489
+ const ctx = sharedCtx || this.createContext();
380
490
  for (const token of tokens) {
381
491
  const typedToken = token;
382
492
  const handler = this.handlerRegistry.get(typedToken.type);
@@ -387,6 +497,82 @@ class MarkdownParser {
387
497
  }
388
498
  return nodes;
389
499
  }
500
+ /**
501
+ * Pre-process markdown: convert `:::tag#id.class` container syntax
502
+ * into HTML comment markers that marked will preserve as html tokens,
503
+ * but won't affect markdown parsing of the inner content.
504
+ *
505
+ * Example:
506
+ * :::section#header
507
+ * # Heading inside container
508
+ * Some text
509
+ * :::
510
+ *
511
+ * Becomes:
512
+ * <!-- md-container:section#header -->
513
+ * # Heading inside container
514
+ * Some text
515
+ * <!-- /md-container -->
516
+ */
517
+ preprocessContainerBlocks(markdown) {
518
+ return markdown.replace(/^:::(?:(\w+(?:[.#][\w-]+)*)\s*)?$/gm, (match, specifier) => {
519
+ if (!specifier) {
520
+ return "<!-- /md-container -->";
521
+ }
522
+ const normalized = specifier.match(/^\w/) ? specifier : `div${specifier}`;
523
+ return `<!-- md-container:${normalized} -->`;
524
+ });
525
+ }
526
+ /**
527
+ * Post-process marked tokens to collapse container block markers
528
+ * into structured containerBlock tokens with proper nesting.
529
+ *
530
+ * This handles nesting depth up to maxRecursionDepth.
531
+ */
532
+ postprocessTokens(tokens) {
533
+ const result = [];
534
+ const stack = [];
535
+ for (const token of tokens) {
536
+ const t = token;
537
+ if (t.type === "html") {
538
+ const raw = t.raw.trim();
539
+ const openMatch = raw.match(/^<!--\s*md-container:\s*(\S+)\s*-->$/);
540
+ const closeMatch = raw.match(/^<!--\s*\/md-container\s*-->$/);
541
+ if (openMatch) {
542
+ const newContainer = {
543
+ specifier: openMatch[1],
544
+ tokens: []
545
+ };
546
+ stack.push(newContainer);
547
+ continue;
548
+ }
549
+ if (closeMatch) {
550
+ if (stack.length === 0) {
551
+ continue;
552
+ }
553
+ const container = stack.pop();
554
+ const processedInner = this.postprocessTokens(container.tokens);
555
+ const containerToken = {
556
+ type: "containerBlock",
557
+ specifier: container.specifier,
558
+ tokens: processedInner
559
+ };
560
+ if (stack.length > 0) {
561
+ stack[stack.length - 1].tokens.push(containerToken);
562
+ } else {
563
+ result.push(containerToken);
564
+ }
565
+ continue;
566
+ }
567
+ }
568
+ if (stack.length > 0) {
569
+ stack[stack.length - 1].tokens.push(token);
570
+ } else {
571
+ result.push(token);
572
+ }
573
+ }
574
+ return result;
575
+ }
390
576
  parse(markdown, options) {
391
577
  const parseOptions = {
392
578
  gfm: options?.gfm ?? true,
@@ -394,10 +580,14 @@ class MarkdownParser {
394
580
  pedantic: options?.pedantic ?? false
395
581
  };
396
582
  try {
397
- const tokens = marked.lexer(markdown, parseOptions);
398
- const content = this.parseTokens(tokens);
583
+ const processed = this.preprocessContainerBlocks(markdown);
584
+ const rawTokens = marked.lexer(processed, parseOptions);
585
+ const tokens = this.postprocessTokens(rawTokens);
586
+ const ctx = this.createContext();
587
+ const content = this.parseTokens(tokens, 0, ctx);
399
588
  return {
400
589
  title: "",
590
+ metadata: { ...ctx.metadata },
401
591
  content
402
592
  };
403
593
  } catch (err) {
@@ -416,6 +606,198 @@ class MarkdownParser {
416
606
  return this.parse(markdown, options).content;
417
607
  }
418
608
  }
609
+ class HeadingRendererStrategy {
610
+ constructor() {
611
+ this.type = "heading";
612
+ }
613
+ render(node, _renderChild, ctx) {
614
+ const level = node.attributes?.level || "2";
615
+ const headingId = ctx.addHeadingIds ? ` id="${ctx.generateHeadingId(node.content)}"` : "";
616
+ const scopeAttr = ctx.getScopeAttr(node);
617
+ if (!ctx.hasClassConfig()) {
618
+ return `<h${level}${headingId}${scopeAttr}>${node.content || ""}</h${level}>`;
619
+ }
620
+ const prefix = ctx.classPrefix;
621
+ const levelClass = level === "1" ? "h1" : level === "2" ? "h2" : level === "3" ? "h3" : level === "4" ? "h4" : level === "5" ? "h5" : "h6";
622
+ const headingClass = prefix ? `${prefix}${levelClass}` : levelClass;
623
+ return `<h${level}${headingId}${scopeAttr} class="${headingClass}">${node.content || ""}</h${level}>`;
624
+ }
625
+ }
626
+ class ParagraphRendererStrategy {
627
+ constructor() {
628
+ this.type = "paragraph";
629
+ }
630
+ render(node, renderChild, ctx) {
631
+ const scopeAttr = ctx.getScopeAttr(node);
632
+ if (node.children) {
633
+ const childrenHtml = node.children.map(renderChild).join("");
634
+ return ctx.hasClassConfig() && ctx.classPrefix ? `<p class="${ctx.classPrefix}paragraph"${scopeAttr}>${childrenHtml}</p>` : `<p${scopeAttr}>${childrenHtml}</p>`;
635
+ }
636
+ return ctx.hasClassConfig() && ctx.classPrefix ? `<p class="${ctx.classPrefix}paragraph"${scopeAttr}>${node.content || ""}</p>` : `<p${scopeAttr}>${node.content || ""}</p>`;
637
+ }
638
+ }
639
+ class ListRendererStrategy {
640
+ constructor() {
641
+ this.type = "list";
642
+ }
643
+ render(node, renderChild, ctx) {
644
+ const tag = node.ordered ? "ol" : "ul";
645
+ const items = node.children?.map(renderChild).join("") || "";
646
+ const scopeAttr = ctx.getScopeAttr(node);
647
+ return ctx.hasClassConfig() && ctx.classPrefix ? `<${tag} class="${ctx.classPrefix}list"${scopeAttr}>${items}</${tag}>` : `<${tag}${scopeAttr}>${items}</${tag}>`;
648
+ }
649
+ }
650
+ class ListItemRendererStrategy {
651
+ constructor() {
652
+ this.type = "list-item";
653
+ }
654
+ render(node, _renderChild, ctx) {
655
+ const scopeAttr = ctx.getScopeAttr(node);
656
+ return ctx.hasClassConfig() && ctx.classPrefix ? `<li class="${ctx.classPrefix}list-item"${scopeAttr}>${node.content || ""}</li>` : `<li${scopeAttr}>${node.content || ""}</li>`;
657
+ }
658
+ }
659
+ class ImageRendererStrategy {
660
+ constructor() {
661
+ this.type = "image";
662
+ }
663
+ render(node, _renderChild, ctx) {
664
+ const src = node.src || node.attributes?.src || "";
665
+ const alt = node.alt || node.attributes?.alt || "";
666
+ const scopeAttr = ctx.getScopeAttr(node);
667
+ let classStr = "";
668
+ if (ctx.hasClassConfig()) {
669
+ const prefix = ctx.classPrefix;
670
+ classStr = prefix ? `${prefix}image` : "image";
671
+ if (node.className) classStr += ` ${node.className}`;
672
+ return `<img src="${src}" alt="${alt}" class="${classStr}"${scopeAttr}>`;
673
+ }
674
+ if (node.className) {
675
+ return `<img src="${src}" alt="${alt}" class="${node.className}"${scopeAttr}>`;
676
+ }
677
+ return `<img src="${src}" alt="${alt}"${scopeAttr}>`;
678
+ }
679
+ }
680
+ class CodeRendererStrategy {
681
+ constructor() {
682
+ this.type = "code";
683
+ }
684
+ render(node, _renderChild, ctx) {
685
+ const scopeAttr = ctx.getScopeAttr(node);
686
+ const lang = node.attributes?.lang || "";
687
+ if (ctx.hasClassConfig()) {
688
+ const prefix = ctx.classPrefix;
689
+ const codeClass = prefix ? `${prefix}code` : "code";
690
+ return `<pre${scopeAttr}><code class="${codeClass} language-${lang}">${node.content || ""}</code></pre>`;
691
+ }
692
+ return `<pre${scopeAttr}><code class="language-${lang}">${node.content || ""}</code></pre>`;
693
+ }
694
+ }
695
+ class ContainerRendererStrategy {
696
+ constructor() {
697
+ this.type = "container";
698
+ }
699
+ render(node, renderChild, ctx) {
700
+ if (node.rawHTML) {
701
+ return node.rawHTML;
702
+ }
703
+ const tag = node.attributes?.tag || "div";
704
+ const children = node.children?.map(renderChild).join("") || "";
705
+ const id = node.attributes?.id;
706
+ const idAttr = id ? ` id="${id}"` : "";
707
+ const scopeAttr = ctx.getScopeAttr(node);
708
+ if (tag === "hr") return "<hr>";
709
+ if (ctx.hasClassConfig()) {
710
+ const containerClass = ctx.getContainerClass(tag);
711
+ const prefix = ctx.classPrefix;
712
+ if (prefix) {
713
+ const classes2 = [prefix + (containerClass || "container")];
714
+ if (node.className) classes2.push(node.className);
715
+ return `<${tag} class="${classes2.join(" ")}"${idAttr}${scopeAttr}>${children}</${tag}>`;
716
+ }
717
+ const classes = [containerClass || "container"];
718
+ if (node.className) classes.push(node.className);
719
+ return `<${tag} class="${classes.join(" ")}"${idAttr}${scopeAttr}>${children}</${tag}>`;
720
+ }
721
+ if (node.className) {
722
+ return `<${tag} class="${node.className}"${idAttr}${scopeAttr}>${children}</${tag}>`;
723
+ }
724
+ return `<${tag}${idAttr}${scopeAttr}>${children}</${tag}>`;
725
+ }
726
+ }
727
+ class StrongRendererStrategy {
728
+ constructor() {
729
+ this.type = "strong";
730
+ }
731
+ render(node, _renderChild, ctx) {
732
+ return `<strong${ctx.getScopeAttr(node)}>${node.content || ""}</strong>`;
733
+ }
734
+ }
735
+ class EmphasisRendererStrategy {
736
+ constructor() {
737
+ this.type = "emphasis";
738
+ }
739
+ render(node, _renderChild, ctx) {
740
+ return `<em${ctx.getScopeAttr(node)}>${node.content || ""}</em>`;
741
+ }
742
+ }
743
+ class LinkRendererStrategy {
744
+ constructor() {
745
+ this.type = "link";
746
+ }
747
+ render(node, _renderChild, ctx) {
748
+ const href = node.attributes?.href || "";
749
+ return `<a href="${href}"${ctx.getScopeAttr(node)}>${node.content || ""}</a>`;
750
+ }
751
+ }
752
+ class TextRendererStrategy {
753
+ constructor() {
754
+ this.type = "text";
755
+ }
756
+ render(node, _renderChild, _ctx) {
757
+ return node.content || "";
758
+ }
759
+ }
760
+ class RendererStrategyRegistry {
761
+ constructor() {
762
+ this.strategies = /* @__PURE__ */ new Map();
763
+ this.register(new HeadingRendererStrategy());
764
+ this.register(new ParagraphRendererStrategy());
765
+ this.register(new ListRendererStrategy());
766
+ this.register(new ListItemRendererStrategy());
767
+ this.register(new ImageRendererStrategy());
768
+ this.register(new CodeRendererStrategy());
769
+ this.register(new ContainerRendererStrategy());
770
+ this.register(new StrongRendererStrategy());
771
+ this.register(new EmphasisRendererStrategy());
772
+ this.register(new LinkRendererStrategy());
773
+ this.register(new TextRendererStrategy());
774
+ this.fallback = new TextRendererStrategy();
775
+ }
776
+ /** Register a strategy for a node type. Overrides any existing strategy. */
777
+ register(strategy) {
778
+ this.strategies.set(strategy.type, strategy);
779
+ }
780
+ /** Unregister a strategy by node type. */
781
+ unregister(type) {
782
+ this.strategies.delete(type);
783
+ }
784
+ /** Get a strategy for the given node type, falling back to catch-all. */
785
+ get(type) {
786
+ return this.strategies.get(type) ?? this.fallback;
787
+ }
788
+ /** Check if a dedicated strategy exists for the given node type. */
789
+ has(type) {
790
+ return this.strategies.has(type);
791
+ }
792
+ /** Get all registered dedicated strategy types. */
793
+ get types() {
794
+ return Array.from(this.strategies.keys());
795
+ }
796
+ /** Replace the fallback strategy. */
797
+ setFallback(strategy) {
798
+ this.fallback = strategy;
799
+ }
800
+ }
419
801
  class HTMLRenderer {
420
802
  constructor(config = {}) {
421
803
  this.config = {
@@ -424,6 +806,11 @@ class HTMLRenderer {
424
806
  addHeadingIds: config.addHeadingIds ?? false,
425
807
  emitScopeAnchors: config.emitScopeAnchors ?? false
426
808
  };
809
+ this.strategyRegistry = new RendererStrategyRegistry();
810
+ }
811
+ /** Access the strategy registry for customization. */
812
+ get strategies() {
813
+ return this.strategyRegistry;
427
814
  }
428
815
  hasClassConfig() {
429
816
  return this.config.classPrefix !== "" || this.config.addHeadingIds;
@@ -450,68 +837,40 @@ class HTMLRenderer {
450
837
  const scopeValue = node.scope || nodeTypeToScope[node.type] || "container";
451
838
  return ` data-md-scope="${scopeValue}"`;
452
839
  }
453
- renderWithClass(tag, content, baseClass, nodeClass, extraAttrs) {
454
- const classAttr = this.hasClassConfig() && baseClass ? ` class="${this.getClass(baseClass, nodeClass)}"` : "";
455
- return `<${tag}${classAttr}${extraAttrs || ""}>${content}</${tag}>`;
840
+ /**
841
+ * Get the CSS class for a container's tag-based rendering.
842
+ * Returns just the tag name since renderWithClass applies the prefix.
843
+ */
844
+ getContainerClass(tag) {
845
+ if (!this.hasClassConfig()) return "";
846
+ return tag;
847
+ }
848
+ buildRenderContext() {
849
+ const self = this;
850
+ return {
851
+ get classPrefix() {
852
+ return self.config.classPrefix;
853
+ },
854
+ get addHeadingIds() {
855
+ return self.config.addHeadingIds;
856
+ },
857
+ get emitScopeAnchors() {
858
+ return self.config.emitScopeAnchors;
859
+ },
860
+ get customCSS() {
861
+ return self.config.customCSS;
862
+ },
863
+ hasClassConfig: () => self.hasClassConfig(),
864
+ getClass: (baseClass, nodeClass) => self.getClass(baseClass, nodeClass),
865
+ getScopeAttr: (node) => self.getScopeAttr(node),
866
+ generateHeadingId: (content) => self.generateHeadingId(content),
867
+ getContainerClass: (tag) => self.getContainerClass(tag)
868
+ };
456
869
  }
457
870
  renderNode(node) {
458
- const scopeAttr = this.getScopeAttr(node);
459
- switch (node.type) {
460
- case "heading":
461
- const level = node.attributes?.level || "2";
462
- const headingId = this.config.addHeadingIds ? ` id="${this.generateHeadingId(node.content)}"` : "";
463
- let headingClass = "";
464
- if (this.hasClassConfig()) {
465
- const prefix = this.config.classPrefix;
466
- const levelClass = level === "1" ? "h1" : level === "2" ? "h2" : level === "3" ? "h3" : level === "4" ? "h4" : level === "5" ? "h5" : "h6";
467
- headingClass = prefix ? `${prefix}${levelClass}` : levelClass;
468
- }
469
- if (!headingClass) {
470
- return `<h${level}${headingId}${scopeAttr}>${node.content || ""}</h${level}>`;
471
- }
472
- return `<h${level}${headingId}${scopeAttr} class="${headingClass}">${node.content || ""}</h${level}>`;
473
- case "paragraph":
474
- if (node.children) {
475
- const childrenHtml = node.children.map((child) => this.renderNode(child)).join("");
476
- return this.renderWithClass("p", childrenHtml, "paragraph", void 0, scopeAttr);
477
- }
478
- return this.renderWithClass("p", node.content || "", "paragraph", void 0, scopeAttr);
479
- case "list":
480
- const tag = node.ordered ? "ol" : "ul";
481
- const items = node.children?.map((child) => this.renderNode(child)).join("") || "";
482
- return this.renderWithClass(tag, items, "list", void 0, scopeAttr);
483
- case "list-item":
484
- return this.renderWithClass("li", node.content || "", "list-item", void 0, scopeAttr);
485
- case "image":
486
- const src = node.src || node.attributes?.src || "";
487
- const alt = node.alt || node.attributes?.alt || "";
488
- const classStr = this.getClass("image", node.className || void 0);
489
- return `<img src="${src}" alt="${alt}"${classStr ? ` class="${classStr}"` : ""}${scopeAttr}>`;
490
- case "code":
491
- const codeClass = this.hasClassConfig() ? ` class="${this.getClass("code")} language-${node.attributes?.lang || ""}"` : ` class="language-${node.attributes?.lang || ""}"`;
492
- return `<pre${scopeAttr}><code${codeClass}>${node.content || ""}</code></pre>`;
493
- case "container":
494
- if (node.attributes?.tag === "hr") return "<hr>";
495
- if (node.attributes?.tag === "blockquote") {
496
- const children = node.children?.map((child) => this.renderNode(child)).join("") || "";
497
- return this.renderWithClass("blockquote", children, "blockquote", void 0, scopeAttr);
498
- }
499
- if (node.rawHTML) {
500
- return node.rawHTML;
501
- }
502
- const containerChildren = node.children?.map((child) => this.renderNode(child)).join("") || "";
503
- return this.renderWithClass("div", containerChildren, "container", node.className || void 0, scopeAttr);
504
- case "strong":
505
- return `<strong${scopeAttr}>${node.content || ""}</strong>`;
506
- case "emphasis":
507
- return `<em${scopeAttr}>${node.content || ""}</em>`;
508
- case "link":
509
- const href = node.attributes?.href || "";
510
- return `<a href="${href}"${scopeAttr}>${node.content || ""}</a>`;
511
- case "text":
512
- default:
513
- return node.content || "";
514
- }
871
+ const ctx = this.buildRenderContext();
872
+ const strategy = this.strategyRegistry.get(node.type);
873
+ return strategy.render(node, (child) => this.renderNode(child), ctx);
515
874
  }
516
875
  renderNodes(nodes) {
517
876
  if (!nodes || nodes.length === 0) {
@@ -606,21 +965,149 @@ class MarkdownPipeline {
606
965
  return this.renderer.getCustomCSS();
607
966
  }
608
967
  }
968
+ function walkTree(root, visitor) {
969
+ return walkNode(root, null, 0, visitor);
970
+ }
971
+ function walkNode(node, parent, depth, visitor) {
972
+ let processed = visitor.enter ? visitor.enter(node, parent, depth) : node;
973
+ if (processed === null) return null;
974
+ if (processed.children && processed.children.length > 0) {
975
+ processed = {
976
+ ...processed,
977
+ children: processed.children.map((child) => walkNode(child, processed, depth + 1, visitor)).filter(Boolean)
978
+ };
979
+ }
980
+ processed = visitor.exit ? visitor.exit(processed, parent, depth) : processed;
981
+ if (processed === null) return null;
982
+ return processed;
983
+ }
984
+ function collectFromTree(root, collector) {
985
+ const results = [];
986
+ collectNode(root, null, 0, collector, results);
987
+ return results;
988
+ }
989
+ function collectNode(node, parent, depth, collector, results) {
990
+ const result = collector(node, parent, depth);
991
+ if (result !== null) {
992
+ results.push(result);
993
+ }
994
+ if (node.children) {
995
+ for (const child of node.children) {
996
+ collectNode(child, node, depth + 1, collector, results);
997
+ }
998
+ }
999
+ }
1000
+ function collectByType(root, type) {
1001
+ return collectFromTree(
1002
+ root,
1003
+ (node) => node.type === type ? node : null
1004
+ );
1005
+ }
1006
+ function collectHeadings(root) {
1007
+ return collectFromTree(root, (node) => {
1008
+ if (node.type === "heading") {
1009
+ return {
1010
+ level: node.attributes?.level || "2",
1011
+ text: node.content || "",
1012
+ id: node.attributes?.id
1013
+ };
1014
+ }
1015
+ return null;
1016
+ });
1017
+ }
1018
+ function collectImages(root) {
1019
+ return collectFromTree(root, (node) => {
1020
+ if (node.type === "image") {
1021
+ return { src: node.src || "", alt: node.alt || "" };
1022
+ }
1023
+ return null;
1024
+ });
1025
+ }
1026
+ class NodeFactory {
1027
+ static heading(content, attributes) {
1028
+ return {
1029
+ type: "heading",
1030
+ content,
1031
+ attributes: { level: attributes?.level || "2", ...attributes }
1032
+ };
1033
+ }
1034
+ static paragraph(contentOrChildren, children) {
1035
+ if (typeof contentOrChildren === "string") {
1036
+ return { type: "paragraph", content: contentOrChildren };
1037
+ }
1038
+ return { type: "paragraph", children: contentOrChildren ?? children };
1039
+ }
1040
+ static list(items, ordered, attributes) {
1041
+ return {
1042
+ type: "list",
1043
+ ordered: ordered ?? false,
1044
+ children: items,
1045
+ ...attributes ? { attributes } : {}
1046
+ };
1047
+ }
1048
+ static listItem(content) {
1049
+ return { type: "list-item", content };
1050
+ }
1051
+ static image(src, alt, className) {
1052
+ return { type: "image", src, alt: alt || "", ...className ? { className } : {} };
1053
+ }
1054
+ static code(content, lang) {
1055
+ return {
1056
+ type: "code",
1057
+ content,
1058
+ attributes: { lang: lang || "" }
1059
+ };
1060
+ }
1061
+ static container(children, config) {
1062
+ const node = { type: "container" };
1063
+ if (children && children.length > 0) node.children = children;
1064
+ if (config?.rawHTML) node.rawHTML = config.rawHTML;
1065
+ if (config?.scope) node.scope = config.scope;
1066
+ const attrs = {};
1067
+ if (config?.tag) attrs.tag = config.tag;
1068
+ if (config?.id) attrs.id = config.id;
1069
+ if (Object.keys(attrs).length > 0) node.attributes = attrs;
1070
+ if (config?.className) node.className = config.className;
1071
+ return node;
1072
+ }
1073
+ static text(content) {
1074
+ return { type: "text", content };
1075
+ }
1076
+ static strong(content) {
1077
+ return { type: "strong", content };
1078
+ }
1079
+ static emphasis(content) {
1080
+ return { type: "emphasis", content };
1081
+ }
1082
+ static link(href, content) {
1083
+ return { type: "link", content, attributes: { href } };
1084
+ }
1085
+ }
609
1086
  export {
610
1087
  BlockquoteHandler,
611
1088
  CatchAllHandler,
612
1089
  CodeHandler,
1090
+ ContainerBlockHandler,
1091
+ FrontmatterHandler,
613
1092
  HTMLRenderer,
614
1093
  HeadingHandler,
615
1094
  HrHandler,
616
1095
  HtmlHandler,
617
1096
  ImageHandler,
1097
+ LinkHandler,
618
1098
  ListHandler,
619
1099
  MarkdownParser,
620
1100
  MarkdownPipeline,
1101
+ NodeFactory,
621
1102
  ParagraphHandler,
1103
+ RendererStrategyRegistry,
622
1104
  TokenHandlerRegistry,
1105
+ collectByType,
1106
+ collectFromTree,
1107
+ collectHeadings,
1108
+ collectImages,
623
1109
  defaultAllowedHTMLTags,
624
- nodeTypeToScope
1110
+ nodeTypeToScope,
1111
+ walkTree
625
1112
  };
626
1113
  //# sourceMappingURL=index.js.map