@leadertechie/md2html 0.1.0-alpha.15 → 0.1.0-alpha.17

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.d.ts CHANGED
@@ -43,6 +43,51 @@ export declare class CodeHandler implements TokenHandler {
43
43
  };
44
44
  }
45
45
 
46
+ /**
47
+ * Built-in visitor: collect all nodes of a specific type.
48
+ */
49
+ export declare function collectByType(root: ContentNode, type: string): ContentNode[];
50
+
51
+ /**
52
+ * Walk a ContentNode tree and collect results from the visitor.
53
+ * The collector function is called for each node and returns a value or null.
54
+ */
55
+ export declare function collectFromTree<T>(root: ContentNode, collector: (node: ContentNode, parent: ContentNode | null, depth: number) => T | null): T[];
56
+
57
+ /**
58
+ * Built-in visitor: collect all headings with their levels.
59
+ */
60
+ export declare function collectHeadings(root: ContentNode): Array<{
61
+ level: string;
62
+ text: string;
63
+ id?: string;
64
+ }>;
65
+
66
+ /**
67
+ * Built-in visitor: collect all images with their src/alt.
68
+ */
69
+ export declare function collectImages(root: ContentNode): Array<{
70
+ src: string;
71
+ alt: string;
72
+ }>;
73
+
74
+ /**
75
+ * ContainerBlockHandler — handles custom :::tag#id.class container blocks.
76
+ *
77
+ * These are produced by the MarkdownParser's preprocessor which converts
78
+ * the ::: syntax into marked tokens before lexing, then reconstructs
79
+ * containerBlock tokens after lexing.
80
+ *
81
+ * The handler parses the specifier (e.g. "section#header.content") into
82
+ * tag name, id, and class(es), then recursively parses the inner tokens
83
+ * as normal markdown content, producing a container node with the
84
+ * specified HTML wrapper.
85
+ */
86
+ export declare class ContainerBlockHandler implements TokenHandler {
87
+ readonly type = "containerBlock";
88
+ handle(token: Record<string, unknown>, ctx: ParseContext): ContentNode | null;
89
+ }
90
+
46
91
  export declare interface ContentNode {
47
92
  type: ContentNodeType;
48
93
  content?: string;
@@ -63,6 +108,22 @@ export declare type ContentNodeType = 'text' | 'heading' | 'paragraph' | 'list'
63
108
  /** Default allowed HTML tags for preserveRawHTML mode */
64
109
  export declare const defaultAllowedHTMLTags: string[];
65
110
 
111
+ /**
112
+ * FrontmatterHandler — consumes YAML-ish frontmatter tokens produced by marked.lexer().
113
+ * Parses the raw YAML and stores key-value pairs onto ctx.metadata.
114
+ * Returns null so no HTML is emitted for frontmatter blocks.
115
+ *
116
+ * Handles both formats:
117
+ * key: value
118
+ * key:
119
+ * - item1
120
+ * - item2
121
+ */
122
+ export declare class FrontmatterHandler implements TokenHandler {
123
+ readonly type = "frontmatter";
124
+ handle(token: Record<string, unknown>, ctx: ParseContext): null;
125
+ }
126
+
66
127
  /**
67
128
  * Handles 'heading' tokens (h1-h6).
68
129
  */
@@ -110,7 +171,10 @@ export declare class HtmlHandler implements TokenHandler {
110
171
 
111
172
  export declare class HTMLRenderer {
112
173
  private config;
174
+ private strategyRegistry;
113
175
  constructor(config?: StyleConfigV2);
176
+ /** Access the strategy registry for customization. */
177
+ get strategies(): RendererStrategyRegistry;
114
178
  private hasClassConfig;
115
179
  private getClass;
116
180
  private generateHeadingId;
@@ -119,7 +183,12 @@ export declare class HTMLRenderer {
119
183
  * Returns empty string if emitScopeAnchors is disabled.
120
184
  */
121
185
  private getScopeAttr;
122
- private renderWithClass;
186
+ /**
187
+ * Get the CSS class for a container's tag-based rendering.
188
+ * Returns just the tag name since renderWithClass applies the prefix.
189
+ */
190
+ private getContainerClass;
191
+ private buildRenderContext;
123
192
  renderNode(node: ContentNode): string;
124
193
  renderNodes(nodes: ContentNode[]): string;
125
194
  renderToHTMLString(nodes: ContentNode[]): string;
@@ -169,6 +238,7 @@ export declare class MarkdownParser {
169
238
  private errorRecovery;
170
239
  private maxRecursionDepth;
171
240
  private allowedHTMLTags;
241
+ private allowedAttributes;
172
242
  private handlerRegistry;
173
243
  private onUnhandledToken?;
174
244
  constructor(options?: ParserOptions);
@@ -177,13 +247,57 @@ export declare class MarkdownParser {
177
247
  private processImagePath;
178
248
  private processInlineFormatting;
179
249
  private processSlots;
250
+ /**
251
+ * Check if an attribute name is allowed for a given tag.
252
+ * Supports "data-*" wildcard prefix matching.
253
+ */
254
+ private isAttributeAllowed;
255
+ /**
256
+ * Check if an attribute name matches a list of allowed patterns.
257
+ * Supports "data-*" wildcard prefix matching.
258
+ */
259
+ private matchesAttributeList;
260
+ /**
261
+ * Filter attributes on an HTML tag, keeping only allowed ones.
262
+ */
263
+ private filterTagAttributes;
180
264
  private processRawHTML;
181
265
  /**
182
266
  * Build the ParseContext that is passed to every token handler.
183
267
  * This is the bridge between the parser's private services and the handlers.
184
268
  */
185
269
  private createContext;
270
+ /**
271
+ * Process an array of marked tokens into ContentNodes.
272
+ * When depth === 0 (root), creates a shared context that accumulates metadata.
273
+ * For recursive calls (depth > 0), creates a fresh context for each level.
274
+ */
186
275
  private parseTokens;
276
+ /**
277
+ * Pre-process markdown: convert `:::tag#id.class` container syntax
278
+ * into HTML comment markers that marked will preserve as html tokens,
279
+ * but won't affect markdown parsing of the inner content.
280
+ *
281
+ * Example:
282
+ * :::section#header
283
+ * # Heading inside container
284
+ * Some text
285
+ * :::
286
+ *
287
+ * Becomes:
288
+ * <!-- md-container:section#header -->
289
+ * # Heading inside container
290
+ * Some text
291
+ * <!-- /md-container -->
292
+ */
293
+ private preprocessContainerBlocks;
294
+ /**
295
+ * Post-process marked tokens to collapse container block markers
296
+ * into structured containerBlock tokens with proper nesting.
297
+ *
298
+ * This handles nesting depth up to maxRecursionDepth.
299
+ */
300
+ private postprocessTokens;
187
301
  parse(markdown: string, options?: ParseOptions): MarkdownContent;
188
302
  parseToNodes(markdown: string, options?: ParseOptions): ContentNode[];
189
303
  }
@@ -205,9 +319,75 @@ export declare class MarkdownPipeline {
205
319
  getCustomCSS(): string;
206
320
  }
207
321
 
322
+ /**
323
+ * ContentNode factory — a builder API for creating ContentNode instances
324
+ * consistently across the codebase. Eliminates scattered object literals
325
+ * and provides type-safe construction with sensible defaults.
326
+ *
327
+ * Usage:
328
+ * NodeFactory.heading('Hello', { level: '1' })
329
+ * NodeFactory.paragraph('Some text')
330
+ * NodeFactory.container({ tag: 'section', id: 'main' }, [children...])
331
+ */
332
+ export declare class NodeFactory {
333
+ static heading(content: string, attributes?: Record<string, unknown>): ContentNode;
334
+ static paragraph(contentOrChildren: string | ContentNode[], children?: ContentNode[]): ContentNode;
335
+ static list(items: ContentNode[], ordered?: boolean, attributes?: Record<string, unknown>): ContentNode;
336
+ static listItem(content: string): ContentNode;
337
+ static image(src: string, alt?: string, className?: string): ContentNode;
338
+ static code(content: string, lang?: string): ContentNode;
339
+ static container(children?: ContentNode[], config?: {
340
+ tag?: string;
341
+ id?: string;
342
+ className?: string;
343
+ rawHTML?: string;
344
+ scope?: string;
345
+ }): ContentNode;
346
+ static text(content: string): ContentNode;
347
+ static strong(content: string): ContentNode;
348
+ static emphasis(content: string): ContentNode;
349
+ static link(href: string, content: string): ContentNode;
350
+ }
351
+
352
+ /**
353
+ * Strategy interface for rendering a specific ContentNode type to HTML.
354
+ * This is the renderer-side equivalent of TokenHandler — each node type
355
+ * gets its own strategy, eliminating the large switch statement.
356
+ *
357
+ * To add support for a new node type:
358
+ * 1. Create a class implementing NodeRendererStrategy
359
+ * 2. Register it with the RendererStrategyRegistry
360
+ */
361
+ export declare interface NodeRendererStrategy {
362
+ /** The node type this strategy handles */
363
+ readonly type: string;
364
+ /** Render a node of this type to an HTML string */
365
+ render(node: ContentNode, renderChild: (child: ContentNode) => string, ctx: RenderContext): string;
366
+ }
367
+
208
368
  /** Maps ContentNodeType to ScopeValue */
209
369
  export declare const nodeTypeToScope: Record<ContentNodeType, ScopeValue>;
210
370
 
371
+ /**
372
+ * Visitor interface for traversing/walking a ContentNode AST.
373
+ *
374
+ * Implement this interface to inspect, collect, or transform nodes.
375
+ * Each method returns a ContentNode or null — returning null removes the node.
376
+ *
377
+ * Use cases:
378
+ * - Collect all images for preloading
379
+ * - Transform link URLs
380
+ * - Validate AST structure
381
+ * - Extract headings for ToC
382
+ * - Inject attributes
383
+ */
384
+ export declare interface NodeVisitor {
385
+ /** Called when entering a node, before visiting its children. */
386
+ enter?(node: ContentNode, parent: ContentNode | null, depth: number): ContentNode | null;
387
+ /** Called after visiting all children of the node. */
388
+ exit?(node: ContentNode, parent: ContentNode | null, depth: number): ContentNode | null;
389
+ }
390
+
211
391
  /**
212
392
  * Handles 'paragraph' tokens, including inline images and raw HTML.
213
393
  */
@@ -238,6 +418,11 @@ export declare interface ParseContext {
238
418
  maxRecursionDepth: number;
239
419
  /** Report an unhandled token type so callers can be notified */
240
420
  reportUnhandled(type: string, token: Record<string, unknown>): void;
421
+ /**
422
+ * Shared metadata store populated by token handlers (e.g. FrontmatterHandler).
423
+ * After parsing, this object contains all frontmatter key-value pairs.
424
+ */
425
+ metadata: Record<string, unknown>;
241
426
  }
242
427
 
243
428
  export declare interface ParseOptions {
@@ -255,8 +440,15 @@ export declare interface ParserOptions {
255
440
  errorRecovery?: 'throw' | 'warn' | 'silent';
256
441
  maxRecursionDepth?: number;
257
442
  allowedHTMLTags?: string[];
443
+ /**
444
+ * Allowed HTML attributes per tag for preserveRawHTML mode.
445
+ * Key "*" applies to all tags. Key "tag" applies to specific tags.
446
+ * Supports "data-*" wildcard prefix matching.
447
+ */
448
+ allowedAttributes?: Record<string, string[]>;
258
449
  /**
259
450
  * Callback invoked when a token type has no dedicated handler.
451
+
260
452
  * The catch-all handler will still produce a container node for the content,
261
453
  * but this callback allows callers to log, warn, or track unhandled types.
262
454
  *
@@ -290,6 +482,56 @@ export declare interface PipelineConfigV2 extends PipelineConfig {
290
482
  maxRecursionDepth?: number;
291
483
  /** Additional allowed HTML tags for preserveRawHTML mode */
292
484
  allowedHTMLTags?: string[];
485
+ /**
486
+ * Allowed HTML attributes per tag for preserveRawHTML mode.
487
+ * Key "*" applies to all tags. Key "tag" applies to specific tags.
488
+ * Supports "data-*" wildcard prefix matching.
489
+ * Example: { "*": ["id", "class"], "script": ["type", "src"] }
490
+ */
491
+ allowedAttributes?: Record<string, string[]>;
492
+ }
493
+
494
+ /**
495
+ * Context passed to every render strategy, providing access to
496
+ * shared rendering services and configuration.
497
+ */
498
+ export declare interface RenderContext {
499
+ classPrefix: string;
500
+ addHeadingIds: boolean;
501
+ emitScopeAnchors: boolean;
502
+ customCSS: string;
503
+ getClass(baseClass: string, nodeClass?: string): string;
504
+ getScopeAttr(node: ContentNode): string;
505
+ generateHeadingId(content?: string): string;
506
+ getContainerClass(tag: string): string;
507
+ hasClassConfig(): boolean;
508
+ }
509
+
510
+ /**
511
+ * Registry of renderer strategies. Maps ContentNode types to their
512
+ * rendering strategy. This is the renderer-side equivalent of
513
+ * TokenHandlerRegistry.
514
+ *
515
+ * The registry uses a two-tier lookup:
516
+ * 1. Check for a dedicated strategy by node type
517
+ * 2. Fall back to the catch-all strategy (registered as '*')
518
+ */
519
+ export declare class RendererStrategyRegistry {
520
+ private strategies;
521
+ private fallback;
522
+ constructor();
523
+ /** Register a strategy for a node type. Overrides any existing strategy. */
524
+ register(strategy: NodeRendererStrategy): void;
525
+ /** Unregister a strategy by node type. */
526
+ unregister(type: string): void;
527
+ /** Get a strategy for the given node type, falling back to catch-all. */
528
+ get(type: string): NodeRendererStrategy;
529
+ /** Check if a dedicated strategy exists for the given node type. */
530
+ has(type: string): boolean;
531
+ /** Get all registered dedicated strategy types. */
532
+ get types(): string[];
533
+ /** Replace the fallback strategy. */
534
+ setFallback(strategy: NodeRendererStrategy): void;
293
535
  }
294
536
 
295
537
  /** Scope hierarchy values for data-md-scope */
@@ -354,4 +596,10 @@ export declare class TokenHandlerRegistry {
354
596
  getCatchAll(): TokenHandler;
355
597
  }
356
598
 
599
+ /**
600
+ * Walk a ContentNode tree, applying a visitor at each node.
601
+ * Returns a new tree (immutable) — does not mutate the original.
602
+ */
603
+ export declare function walkTree(root: ContentNode, visitor: NodeVisitor): ContentNode;
604
+
357
605
  export { }