@leadertechie/md2html 0.1.0-alpha.13 → 0.1.0-alpha.14

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/README.md CHANGED
@@ -10,6 +10,12 @@ A configuration-driven markdown to HTML pipeline that parses markdown to an AST
10
10
  - **Configuration-driven** - No hardcoded paths or content structure
11
11
  - **SSR-ready** - Works in both Node.js and browser environments
12
12
  - **Image path handling** - Configurable prefix and base URL for images
13
+ - **Strategy pattern token handlers** - Extensible handler registry with per-token-type strategies
14
+ - **Catch-all fallback** - Unhandled token types are wrapped in container nodes with `data-unhandled` attributes
15
+ - **CSS `@scope` anchors** - Emit `data-md-scope` attributes for CSS `@scope` targeting
16
+ - **Raw HTML passthrough** - Preserve allowed HTML tags (div, span, img, etc.) with script stripping by default
17
+ - **Slot hooks** - Resolve `[[SLOT_NAME]]` placeholders via callback for personalization
18
+ - **Graceful error recovery** - Configurable `'throw' | 'warn' | 'silent'` error handling modes
13
19
 
14
20
  ## Installation
15
21
 
@@ -61,8 +67,12 @@ const pipeline = new MarkdownPipeline({
61
67
  styleOptions: {
62
68
  classPrefix: 'md-',
63
69
  customCSS: 'body { font-family: system-ui; }',
64
- addHeadingIds: true
65
- }
70
+ addHeadingIds: true,
71
+ emitScopeAnchors: true // v2: emit data-md-scope attributes
72
+ },
73
+ preserveRawHTML: true, // v2: pass through allowed HTML tags
74
+ errorRecovery: 'warn', // v2: graceful error handling
75
+ onSlot: (name) => `[${name}]` // v2: resolve [[SLOT_NAME]] placeholders
66
76
  });
67
77
  ```
68
78
 
@@ -73,6 +83,7 @@ const pipeline = new MarkdownPipeline({
73
83
  | `classPrefix` | string | `''` | Prefix for CSS classes on elements |
74
84
  | `customCSS` | string | `''` | Custom CSS string to inject (use `pipeline.getCustomCSS()` to retrieve) |
75
85
  | `addHeadingIds` | boolean | `false` | Add ID attributes to headings based on their content for anchor links |
86
+ | `emitScopeAnchors` | boolean | `false` | Emit `data-md-scope` attributes for CSS `@scope` targeting (v2) |
76
87
 
77
88
  When `classPrefix` or `addHeadingIds` is set, CSS classes will be added to elements:
78
89
  - Headings get level-specific classes: `md-h1`, `md-h2`, `md-h3`, etc.
@@ -88,6 +99,83 @@ Example output with `classPrefix: 'md-'` and `addHeadingIds: true`:
88
99
  </ul>
89
100
  ```
90
101
 
102
+ ### CSS `@scope` Anchors (v2)
103
+
104
+ When `emitScopeAnchors: true`, every rendered element gets a `data-md-scope` attribute:
105
+
106
+ ```html
107
+ <div data-md-scope="root">
108
+ <h2 data-md-scope="heading" class="md-heading">Title</h2>
109
+ <p data-md-scope="paragraph" class="md-paragraph">Content</p>
110
+ </div>
111
+ ```
112
+
113
+ This enables CSS `@scope` targeting in your stylesheets:
114
+
115
+ ```css
116
+ @layer components {
117
+ @scope ([data-md-scope="root"]) {
118
+ :scope { max-width: 700px; }
119
+ [data-md-scope="heading"] { font-size: clamp(1.5rem, 4vw, 2.5rem); }
120
+ }
121
+ }
122
+ ```
123
+
124
+ ### Raw HTML Passthrough (v2)
125
+
126
+ When `preserveRawHTML: true`, allowed HTML tags pass through the parser:
127
+
128
+ ```typescript
129
+ const pipeline = new MarkdownPipeline({ preserveRawHTML: true });
130
+ const html = pipeline.renderMarkdown('Hello <div class="test">World</div>');
131
+ // Output preserves the <div> with its attributes
132
+ ```
133
+
134
+ **Default allowed tags:** `img`, `style`, `div`, `span`, `section`, `article`, `aside`, `header`, `footer`, `nav`, `main`, `figure`, `figcaption`, `details`, `summary`, `mark`, `time`, `video`, `audio`, `source`, `iframe`, `embed`
135
+
136
+ **Script tags** are stripped by default for security. Opt-in with `allowedHTMLTags: ['script']`.
137
+
138
+ ### Slot Hooks (v2)
139
+
140
+ Resolve `[[SLOT_NAME]]` placeholders for personalization:
141
+
142
+ ```typescript
143
+ const pipeline = new MarkdownPipeline({
144
+ onSlot: (name) => {
145
+ const values = { USER_NAME: 'Alice', COMPANY: 'Acme' };
146
+ return values[name] || `[[${name}]]`;
147
+ }
148
+ });
149
+ const html = pipeline.renderMarkdown('Hello [[USER_NAME]] from [[COMPANY]]!');
150
+ // Output: Hello Alice from Acme!
151
+ ```
152
+
153
+ Custom slot patterns are supported via `slotPattern`:
154
+
155
+ ```typescript
156
+ const pipeline = new MarkdownPipeline({
157
+ slotPattern: /\{\{(.*?)\}\}/g,
158
+ onSlot: (name) => values[name] || `{{${name}}}`
159
+ });
160
+ ```
161
+
162
+ ### Error Recovery (v2)
163
+
164
+ Three error recovery modes for production resilience:
165
+
166
+ ```typescript
167
+ // 'throw' (default) — backward compatible, throws on parse errors
168
+ const strict = new MarkdownPipeline({ errorRecovery: 'throw' });
169
+
170
+ // 'warn' — logs warning, returns partial content as fallback text
171
+ const tolerant = new MarkdownPipeline({ errorRecovery: 'warn' });
172
+
173
+ // 'silent' — silently returns fallback content
174
+ const silent = new MarkdownPipeline({ errorRecovery: 'silent' });
175
+ ```
176
+
177
+ Additional safety with `maxRecursionDepth` (default: 100) to prevent stack overflow on deeply nested content.
178
+
91
179
  ### API
92
180
 
93
181
  | Method | Description |
@@ -99,6 +187,72 @@ Example output with `classPrefix: 'md-'` and `addHeadingIds: true`:
99
187
  | `getCustomCSS()` | Get custom CSS string from style config |
100
188
  | `getConfig()` | Get current pipeline configuration |
101
189
 
190
+ ## Architecture (v2)
191
+
192
+ ### Strategy Pattern Token Handlers
193
+
194
+ The parser uses a **strategy pattern** with a `TokenHandlerRegistry`. Each marked token type has its own handler class:
195
+
196
+ ```
197
+ src/handlers/
198
+ ├── types.ts # TokenHandler interface + ParseContext
199
+ ├── registry.ts # TokenHandlerRegistry with catch-all fallback
200
+ ├── heading-handler.ts # h1-h6
201
+ ├── paragraph-handler.ts # <p> with inline image/HTML support
202
+ ├── list-handler.ts # <ul>/<ol>
203
+ ├── image-handler.ts # <img>
204
+ ├── code-handler.ts # <pre><code>
205
+ ├── hr-handler.ts # <hr>
206
+ ├── blockquote-handler.ts # <blockquote>
207
+ ├── html-handler.ts # raw HTML passthrough
208
+ └── catchall-handler.ts # fallback for unregistered types
209
+ ```
210
+
211
+ **Extending the parser** — register custom handlers without modifying internals:
212
+
213
+ ```typescript
214
+ import { MarkdownParser, TokenHandler } from '@leadertechie/md2html';
215
+
216
+ const parser = new MarkdownParser();
217
+
218
+ // Override heading rendering
219
+ const customHeading: TokenHandler = {
220
+ type: 'heading',
221
+ handle: (token, ctx) => ({
222
+ type: 'container',
223
+ attributes: { tag: 'div', 'data-custom': 'true' },
224
+ children: [{
225
+ type: 'heading',
226
+ content: ctx.processSlots(token.text as string),
227
+ attributes: { level: String(token.depth) }
228
+ }]
229
+ })
230
+ };
231
+ parser.handlers.register(customHeading);
232
+
233
+ // Remove a handler to skip token types
234
+ parser.handlers.unregister('heading');
235
+
236
+ // Replace the catch-all for unregistered token types
237
+ parser.handlers.setCatchAll({
238
+ type: '*',
239
+ handle: (token) => ({
240
+ type: 'text',
241
+ content: `[fallback: ${token.type}]`
242
+ })
243
+ });
244
+ ```
245
+
246
+ **Catch-all handler** — When a token type has no dedicated handler (e.g., `table`, `def`), the `CatchAllHandler` wraps it in a `<div data-unhandled="type">` container so content is never silently lost. The `onUnhandledToken` callback notifies callers:
247
+
248
+ ```typescript
249
+ const parser = new MarkdownParser({
250
+ onUnhandledToken: (type, token) => {
251
+ console.warn(`[md2html] Unhandled token type: ${type}`);
252
+ }
253
+ });
254
+ ```
255
+
102
256
  ## License
103
257
 
104
258
  MIT
package/dist/index.d.ts CHANGED
@@ -1,3 +1,48 @@
1
+ /**
2
+ * Handles 'blockquote' tokens.
3
+ */
4
+ export declare class BlockquoteHandler implements TokenHandler {
5
+ readonly type = "blockquote";
6
+ handle(token: Record<string, unknown>, ctx: ParseContext): {
7
+ type: "container";
8
+ attributes: {
9
+ tag: string;
10
+ };
11
+ children: ContentNode[];
12
+ };
13
+ }
14
+
15
+ /**
16
+ * Catch-all handler for any token type that doesn't have a dedicated handler.
17
+ * Wraps the raw token content in a container node so content is never silently lost.
18
+ * Reports the unhandled type via the context's reportUnhandled callback.
19
+ */
20
+ export declare class CatchAllHandler implements TokenHandler {
21
+ readonly type = "*";
22
+ handle(token: Record<string, unknown>, ctx: ParseContext): {
23
+ type: "container";
24
+ content: string;
25
+ attributes: {
26
+ 'data-unhandled': string;
27
+ tag: string;
28
+ };
29
+ };
30
+ }
31
+
32
+ /**
33
+ * Handles 'code' tokens (fenced code blocks).
34
+ */
35
+ export declare class CodeHandler implements TokenHandler {
36
+ readonly type = "code";
37
+ handle(token: Record<string, unknown>, _ctx: ParseContext): {
38
+ type: "code";
39
+ content: string;
40
+ attributes: {
41
+ lang: string;
42
+ };
43
+ };
44
+ }
45
+
1
46
  export declare interface ContentNode {
2
47
  type: ContentNodeType;
3
48
  content?: string;
@@ -7,16 +52,73 @@ export declare interface ContentNode {
7
52
  src?: string;
8
53
  alt?: string;
9
54
  ordered?: boolean;
55
+ /** Raw HTML content for passthrough mode */
56
+ rawHTML?: string;
57
+ /** Scope anchor value for data-md-scope */
58
+ scope?: string;
59
+ }
60
+
61
+ export declare type ContentNodeType = 'text' | 'heading' | 'paragraph' | 'list' | 'list-item' | 'image' | 'code' | 'container' | 'strong' | 'emphasis' | 'link';
62
+
63
+ /** Default allowed HTML tags for preserveRawHTML mode */
64
+ export declare const defaultAllowedHTMLTags: string[];
65
+
66
+ /**
67
+ * Handles 'heading' tokens (h1-h6).
68
+ */
69
+ export declare class HeadingHandler implements TokenHandler {
70
+ readonly type = "heading";
71
+ handle(token: Record<string, unknown>, ctx: ParseContext): {
72
+ type: "heading";
73
+ content: string;
74
+ attributes: {
75
+ level: string;
76
+ };
77
+ };
10
78
  }
11
79
 
12
- export declare type ContentNodeType = 'text' | 'heading' | 'paragraph' | 'list' | 'list-item' | 'image' | 'code' | 'container' | 'strong' | 'emphasis';
80
+ /**
81
+ * Handles 'hr' tokens (horizontal rules).
82
+ */
83
+ export declare class HrHandler implements TokenHandler {
84
+ readonly type = "hr";
85
+ handle(_token: Record<string, unknown>, _ctx: ParseContext): {
86
+ type: "container";
87
+ attributes: {
88
+ tag: string;
89
+ };
90
+ };
91
+ }
92
+
93
+ /**
94
+ * Handles 'html' tokens (inline or block-level raw HTML).
95
+ * In preserveRawHTML mode, passes through allowed HTML tags.
96
+ * Otherwise, stores the raw content as a container node.
97
+ */
98
+ export declare class HtmlHandler implements TokenHandler {
99
+ readonly type = "html";
100
+ handle(token: Record<string, unknown>, ctx: ParseContext): {
101
+ type: "container";
102
+ rawHTML: string;
103
+ content?: undefined;
104
+ } | {
105
+ type: "container";
106
+ content: string;
107
+ rawHTML?: undefined;
108
+ } | null;
109
+ }
13
110
 
14
111
  export declare class HTMLRenderer {
15
112
  private config;
16
- constructor(config?: StyleConfig);
113
+ constructor(config?: StyleConfigV2);
17
114
  private hasClassConfig;
18
115
  private getClass;
19
116
  private generateHeadingId;
117
+ /**
118
+ * Get the scope attribute string for a node type.
119
+ * Returns empty string if emitScopeAnchors is disabled.
120
+ */
121
+ private getScopeAttr;
20
122
  private renderWithClass;
21
123
  renderNode(node: ContentNode): string;
22
124
  renderNodes(nodes: ContentNode[]): string;
@@ -25,6 +127,33 @@ export declare class HTMLRenderer {
25
127
  getCustomCSS(): string;
26
128
  }
27
129
 
130
+ /**
131
+ * Handles standalone 'image' tokens.
132
+ */
133
+ export declare class ImageHandler implements TokenHandler {
134
+ readonly type = "image";
135
+ handle(token: Record<string, unknown>, ctx: ParseContext): {
136
+ type: "image";
137
+ src: string;
138
+ alt: string;
139
+ };
140
+ }
141
+
142
+ /**
143
+ * Handles 'list' tokens (ordered and unordered).
144
+ */
145
+ export declare class ListHandler implements TokenHandler {
146
+ readonly type = "list";
147
+ handle(token: Record<string, unknown>, ctx: ParseContext): {
148
+ type: "list";
149
+ ordered: boolean;
150
+ children: {
151
+ type: "list-item";
152
+ content: string;
153
+ }[];
154
+ };
155
+ }
156
+
28
157
  export declare interface MarkdownContent {
29
158
  title: string;
30
159
  metadata?: Record<string, unknown>;
@@ -34,14 +163,27 @@ export declare interface MarkdownContent {
34
163
  export declare class MarkdownParser {
35
164
  private imagePathPrefix;
36
165
  private imageBaseUrl;
37
- constructor(options?: {
38
- imagePathPrefix?: string;
39
- imageBaseUrl?: string;
40
- });
166
+ private preserveRawHTML;
167
+ private slotPattern;
168
+ private onSlot;
169
+ private errorRecovery;
170
+ private maxRecursionDepth;
171
+ private allowedHTMLTags;
172
+ private handlerRegistry;
173
+ private onUnhandledToken?;
174
+ constructor(options?: ParserOptions);
175
+ /** Access the handler registry for customization. */
176
+ get handlers(): TokenHandlerRegistry;
41
177
  private processImagePath;
42
178
  private processInlineFormatting;
179
+ private processSlots;
180
+ private processRawHTML;
181
+ /**
182
+ * Build the ParseContext that is passed to every token handler.
183
+ * This is the bridge between the parser's private services and the handlers.
184
+ */
185
+ private createContext;
43
186
  private parseTokens;
44
- private parseToken;
45
187
  parse(markdown: string, options?: ParseOptions): MarkdownContent;
46
188
  parseToNodes(markdown: string, options?: ParseOptions): ContentNode[];
47
189
  }
@@ -50,7 +192,7 @@ export declare class MarkdownPipeline {
50
192
  private parser;
51
193
  private renderer;
52
194
  private config;
53
- constructor(config?: PipelineConfig);
195
+ constructor(config?: PipelineConfigV2);
54
196
  parse(markdown: string): ContentNode[];
55
197
  parseWithMetadata(markdown: string): MarkdownContent;
56
198
  render(nodes: ContentNode[]): string;
@@ -59,16 +201,71 @@ export declare class MarkdownPipeline {
59
201
  lang?: string;
60
202
  charset?: string;
61
203
  }): string;
62
- getConfig(): Readonly<Required<PipelineConfig>>;
204
+ getConfig(): Readonly<PipelineConfigV2>;
63
205
  getCustomCSS(): string;
64
206
  }
65
207
 
208
+ /** Maps ContentNodeType to ScopeValue */
209
+ export declare const nodeTypeToScope: Record<ContentNodeType, ScopeValue>;
210
+
211
+ /**
212
+ * Handles 'paragraph' tokens, including inline images and raw HTML.
213
+ */
214
+ export declare class ParagraphHandler implements TokenHandler {
215
+ readonly type = "paragraph";
216
+ handle(token: Record<string, unknown>, ctx: ParseContext): {
217
+ type: "paragraph";
218
+ children: ContentNode[];
219
+ content?: undefined;
220
+ } | {
221
+ type: "paragraph";
222
+ content: string;
223
+ children?: undefined;
224
+ } | null;
225
+ }
226
+
227
+ /**
228
+ * Context passed to every token handler, giving access to parser services.
229
+ */
230
+ export declare interface ParseContext {
231
+ processImagePath(src: string): string;
232
+ processInlineFormatting(text: string): string;
233
+ processSlots(text: string): string;
234
+ processRawHTML(html: string): string;
235
+ parseTokens(tokens: unknown[], depth: number): ContentNode[];
236
+ preserveRawHTML: boolean;
237
+ errorRecovery: 'throw' | 'warn' | 'silent';
238
+ maxRecursionDepth: number;
239
+ /** Report an unhandled token type so callers can be notified */
240
+ reportUnhandled(type: string, token: Record<string, unknown>): void;
241
+ }
242
+
66
243
  export declare interface ParseOptions {
67
244
  gfm?: boolean;
68
245
  breaks?: boolean;
69
246
  pedantic?: boolean;
70
247
  }
71
248
 
249
+ export declare interface ParserOptions {
250
+ imagePathPrefix?: string;
251
+ imageBaseUrl?: string;
252
+ preserveRawHTML?: boolean;
253
+ slotPattern?: RegExp;
254
+ onSlot?: (name: string) => string;
255
+ errorRecovery?: 'throw' | 'warn' | 'silent';
256
+ maxRecursionDepth?: number;
257
+ allowedHTMLTags?: string[];
258
+ /**
259
+ * Callback invoked when a token type has no dedicated handler.
260
+ * The catch-all handler will still produce a container node for the content,
261
+ * but this callback allows callers to log, warn, or track unhandled types.
262
+ *
263
+ * @param type - The unhandled token type name (e.g. 'table', 'def')
264
+ * @param token - The raw marked token
265
+ */
266
+ onUnhandledToken?: (type: string, token: Record<string, unknown>) => void;
267
+ }
268
+
72
269
  export declare interface PipelineConfig {
73
270
  imagePathPrefix?: string;
74
271
  imageBaseUrl?: string;
@@ -76,10 +273,85 @@ export declare interface PipelineConfig {
76
273
  styleOptions?: StyleConfig;
77
274
  }
78
275
 
276
+ /**
277
+ * v2: Extended PipelineConfig with raw HTML passthrough, slot hooks, and error recovery.
278
+ */
279
+ export declare interface PipelineConfigV2 extends PipelineConfig {
280
+ styleOptions?: StyleConfigV2;
281
+ /** Preserve raw HTML tags in markdown (img, style, div, span, etc.) (default: false) */
282
+ preserveRawHTML?: boolean;
283
+ /** Regex pattern for slot placeholders like [[SLOT_NAME]] (default: /\[\[(.*?)\]\]/g) */
284
+ slotPattern?: RegExp;
285
+ /** Callback to resolve slot values. Called with the slot name, returns replacement string. */
286
+ onSlot?: (name: string) => string;
287
+ /** Error recovery mode (default: 'throw' — backward compatible) */
288
+ errorRecovery?: 'throw' | 'warn' | 'silent';
289
+ /** Max recursion depth to prevent stack overflow (default: 100) */
290
+ maxRecursionDepth?: number;
291
+ /** Additional allowed HTML tags for preserveRawHTML mode */
292
+ allowedHTMLTags?: string[];
293
+ }
294
+
295
+ /** Scope hierarchy values for data-md-scope */
296
+ export declare type ScopeValue = 'root' | 'heading' | 'paragraph' | 'list' | 'list-item' | 'image' | 'code' | 'strong' | 'emphasis' | 'link' | 'container';
297
+
79
298
  export declare interface StyleConfig {
80
299
  classPrefix?: string;
81
300
  customCSS?: string;
82
301
  addHeadingIds?: boolean;
83
302
  }
84
303
 
304
+ /**
305
+ * v2: Extended StyleConfig with scope anchor support.
306
+ */
307
+ export declare interface StyleConfigV2 extends StyleConfig {
308
+ /** Emit data-md-scope attributes for CSS @scope anchoring (default: false) */
309
+ emitScopeAnchors?: boolean;
310
+ }
311
+
312
+ /**
313
+ * A token handler knows how to convert a single marked token into a ContentNode.
314
+ * Handlers are registered by token type name in the handler registry.
315
+ */
316
+ export declare interface TokenHandler {
317
+ /** The marked token type this handler processes (e.g. 'heading', 'paragraph') */
318
+ readonly type: string;
319
+ /** Convert a marked token to a ContentNode (or null to skip) */
320
+ handle(token: Record<string, unknown>, ctx: ParseContext): ContentNode | null;
321
+ }
322
+
323
+ /**
324
+ * Registry of token handlers. Handlers can be added/overridden externally
325
+ * to extend the parser without modifying its internals.
326
+ *
327
+ * The registry uses a two-tier lookup:
328
+ * 1. First, check for a dedicated handler by token type name
329
+ * 2. If none found, fall back to the catch-all handler (registered as '*')
330
+ *
331
+ * The catch-all handler ensures no content is silently lost — unhandled
332
+ * token types are wrapped in a container node with `data-unhandled` attribute.
333
+ */
334
+ export declare class TokenHandlerRegistry {
335
+ private handlers;
336
+ private catchAll;
337
+ constructor();
338
+ /** Register a handler. Overrides any existing handler for the same token type. */
339
+ register(handler: TokenHandler): void;
340
+ /** Unregister a handler by token type. */
341
+ unregister(type: string): void;
342
+ /**
343
+ * Get a handler for the given token type.
344
+ * Falls back to the catch-all handler if no dedicated handler is registered.
345
+ */
346
+ get(type: string): TokenHandler;
347
+ /** Check if a dedicated handler exists for the given token type (excludes catch-all). */
348
+ has(type: string): boolean;
349
+ /** Get all registered dedicated handler types. */
350
+ get types(): string[];
351
+ /** Replace the catch-all handler with a custom implementation. */
352
+ setCatchAll(handler: TokenHandler): void;
353
+ /** Get the current catch-all handler. */
354
+ getCatchAll(): TokenHandler;
355
+ }
356
+
85
357
  export { }
package/dist/index.js CHANGED
@@ -1,8 +1,251 @@
1
1
  import { marked } from "marked";
2
+ const defaultAllowedHTMLTags = [
3
+ "img",
4
+ "style",
5
+ "div",
6
+ "span",
7
+ "section",
8
+ "article",
9
+ "aside",
10
+ "header",
11
+ "footer",
12
+ "nav",
13
+ "main",
14
+ "figure",
15
+ "figcaption",
16
+ "details",
17
+ "summary",
18
+ "mark",
19
+ "time",
20
+ "video",
21
+ "audio",
22
+ "source",
23
+ "iframe",
24
+ "embed"
25
+ ];
26
+ const nodeTypeToScope = {
27
+ "text": "root",
28
+ "heading": "heading",
29
+ "paragraph": "paragraph",
30
+ "list": "list",
31
+ "list-item": "list-item",
32
+ "image": "image",
33
+ "code": "code",
34
+ "container": "container",
35
+ "strong": "strong",
36
+ "emphasis": "emphasis",
37
+ "link": "link"
38
+ };
39
+ class HeadingHandler {
40
+ constructor() {
41
+ this.type = "heading";
42
+ }
43
+ handle(token, ctx) {
44
+ return {
45
+ type: "heading",
46
+ content: ctx.processSlots(token.text),
47
+ attributes: { level: String(token.depth) }
48
+ };
49
+ }
50
+ }
51
+ class ParagraphHandler {
52
+ constructor() {
53
+ this.type = "paragraph";
54
+ }
55
+ handle(token, ctx) {
56
+ const tokens = token.tokens || [];
57
+ const hasInlineImage = tokens.some((t) => t.type === "image");
58
+ const hasInlineHTML = tokens.some((t) => t.type === "html");
59
+ if (hasInlineImage || ctx.preserveRawHTML && hasInlineHTML) {
60
+ const children = tokens.map((t) => {
61
+ if (t.type === "image") {
62
+ return {
63
+ type: "image",
64
+ src: ctx.processImagePath(t.href),
65
+ alt: t.text || ""
66
+ };
67
+ }
68
+ if (t.type === "html" && ctx.preserveRawHTML) {
69
+ const processed = ctx.processRawHTML(t.raw);
70
+ if (processed.trim()) {
71
+ return { type: "text", content: processed };
72
+ }
73
+ return null;
74
+ }
75
+ return {
76
+ type: "text",
77
+ content: ctx.processSlots(ctx.processInlineFormatting(t.text || ""))
78
+ };
79
+ }).filter(Boolean);
80
+ if (children.length === 0) return null;
81
+ return { type: "paragraph", children };
82
+ }
83
+ return {
84
+ type: "paragraph",
85
+ content: ctx.processSlots(ctx.processInlineFormatting(token.text))
86
+ };
87
+ }
88
+ }
89
+ class ListHandler {
90
+ constructor() {
91
+ this.type = "list";
92
+ }
93
+ handle(token, ctx) {
94
+ return {
95
+ type: "list",
96
+ ordered: token.ordered,
97
+ children: token.items.map((item) => ({
98
+ type: "list-item",
99
+ content: ctx.processSlots(ctx.processInlineFormatting(item.text))
100
+ }))
101
+ };
102
+ }
103
+ }
104
+ class ImageHandler {
105
+ constructor() {
106
+ this.type = "image";
107
+ }
108
+ handle(token, ctx) {
109
+ return {
110
+ type: "image",
111
+ src: ctx.processImagePath(token.href),
112
+ alt: token.title || ""
113
+ };
114
+ }
115
+ }
116
+ class CodeHandler {
117
+ constructor() {
118
+ this.type = "code";
119
+ }
120
+ handle(token, _ctx) {
121
+ return {
122
+ type: "code",
123
+ content: token.text,
124
+ attributes: { lang: token.lang || "" }
125
+ };
126
+ }
127
+ }
128
+ class HrHandler {
129
+ constructor() {
130
+ this.type = "hr";
131
+ }
132
+ handle(_token, _ctx) {
133
+ return { type: "container", attributes: { tag: "hr" } };
134
+ }
135
+ }
136
+ class BlockquoteHandler {
137
+ constructor() {
138
+ this.type = "blockquote";
139
+ }
140
+ handle(token, ctx) {
141
+ return {
142
+ type: "container",
143
+ attributes: { tag: "blockquote" },
144
+ children: ctx.parseTokens(token.tokens || [], ctx.maxRecursionDepth + 1)
145
+ };
146
+ }
147
+ }
148
+ class HtmlHandler {
149
+ constructor() {
150
+ this.type = "html";
151
+ }
152
+ handle(token, ctx) {
153
+ if (ctx.preserveRawHTML) {
154
+ const raw = token.raw;
155
+ const processed = ctx.processRawHTML(raw);
156
+ if (processed.trim()) {
157
+ return { type: "container", rawHTML: processed };
158
+ }
159
+ return null;
160
+ }
161
+ return { type: "container", content: token.raw };
162
+ }
163
+ }
164
+ class CatchAllHandler {
165
+ constructor() {
166
+ this.type = "*";
167
+ }
168
+ handle(token, ctx) {
169
+ const tokenType = token.type;
170
+ ctx.reportUnhandled(tokenType, token);
171
+ const raw = token.raw;
172
+ const text = token.text;
173
+ const content = raw || text || `[unhandled: ${tokenType}]`;
174
+ return {
175
+ type: "container",
176
+ content,
177
+ attributes: {
178
+ "data-unhandled": tokenType,
179
+ tag: "div"
180
+ }
181
+ };
182
+ }
183
+ }
184
+ class TokenHandlerRegistry {
185
+ constructor() {
186
+ this.handlers = /* @__PURE__ */ new Map();
187
+ this.register(new HeadingHandler());
188
+ this.register(new ParagraphHandler());
189
+ this.register(new ListHandler());
190
+ this.register(new ImageHandler());
191
+ this.register(new CodeHandler());
192
+ this.register(new HrHandler());
193
+ this.register(new BlockquoteHandler());
194
+ this.register(new HtmlHandler());
195
+ this.catchAll = new CatchAllHandler();
196
+ }
197
+ /** Register a handler. Overrides any existing handler for the same token type. */
198
+ register(handler) {
199
+ this.handlers.set(handler.type, handler);
200
+ }
201
+ /** Unregister a handler by token type. */
202
+ unregister(type) {
203
+ this.handlers.delete(type);
204
+ }
205
+ /**
206
+ * Get a handler for the given token type.
207
+ * Falls back to the catch-all handler if no dedicated handler is registered.
208
+ */
209
+ get(type) {
210
+ return this.handlers.get(type) ?? this.catchAll;
211
+ }
212
+ /** Check if a dedicated handler exists for the given token type (excludes catch-all). */
213
+ has(type) {
214
+ return this.handlers.has(type);
215
+ }
216
+ /** Get all registered dedicated handler types. */
217
+ get types() {
218
+ return Array.from(this.handlers.keys());
219
+ }
220
+ /** Replace the catch-all handler with a custom implementation. */
221
+ setCatchAll(handler) {
222
+ this.catchAll = handler;
223
+ }
224
+ /** Get the current catch-all handler. */
225
+ getCatchAll() {
226
+ return this.catchAll;
227
+ }
228
+ }
229
+ const DEFAULT_SLOT_PATTERN = /\[\[(.*?)\]\]/g;
2
230
  class MarkdownParser {
3
231
  constructor(options) {
4
232
  this.imagePathPrefix = options?.imagePathPrefix || "";
5
233
  this.imageBaseUrl = options?.imageBaseUrl || "";
234
+ this.preserveRawHTML = options?.preserveRawHTML ?? false;
235
+ this.slotPattern = options?.slotPattern ?? DEFAULT_SLOT_PATTERN;
236
+ this.onSlot = options?.onSlot;
237
+ this.errorRecovery = options?.errorRecovery ?? "throw";
238
+ this.maxRecursionDepth = options?.maxRecursionDepth ?? 100;
239
+ this.allowedHTMLTags = /* @__PURE__ */ new Set([
240
+ ...defaultAllowedHTMLTags,
241
+ ...options?.allowedHTMLTags ?? []
242
+ ]);
243
+ this.handlerRegistry = new TokenHandlerRegistry();
244
+ this.onUnhandledToken = options?.onUnhandledToken;
245
+ }
246
+ /** Access the handler registry for customization. */
247
+ get handlers() {
248
+ return this.handlerRegistry;
6
249
  }
7
250
  processImagePath(src) {
8
251
  if (src.startsWith("http") || src.startsWith("/")) {
@@ -17,97 +260,93 @@ class MarkdownParser {
17
260
  processInlineFormatting(text) {
18
261
  return text.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>").replace(/\*(.+?)\*/g, "<em>$1</em>");
19
262
  }
20
- parseTokens(tokens) {
263
+ processSlots(text) {
264
+ if (!this.onSlot) return text;
265
+ return text.replace(this.slotPattern, (match, name) => {
266
+ return this.onSlot(name.trim());
267
+ });
268
+ }
269
+ processRawHTML(html) {
270
+ if (!this.allowedHTMLTags.has("script")) {
271
+ html = html.replace(/<script[\s\S]*?<\/script>/gi, "");
272
+ html = html.replace(/<\/?script[^>]*>/gi, "");
273
+ }
274
+ if (!this.allowedHTMLTags.has("style")) {
275
+ html = html.replace(/<style[\s\S]*?<\/style>/gi, "");
276
+ html = html.replace(/<\/?style[^>]*>/gi, "");
277
+ }
278
+ return html;
279
+ }
280
+ /**
281
+ * Build the ParseContext that is passed to every token handler.
282
+ * This is the bridge between the parser's private services and the handlers.
283
+ */
284
+ createContext() {
285
+ const self = this;
286
+ return {
287
+ get preserveRawHTML() {
288
+ return self.preserveRawHTML;
289
+ },
290
+ get errorRecovery() {
291
+ return self.errorRecovery;
292
+ },
293
+ get maxRecursionDepth() {
294
+ return self.maxRecursionDepth;
295
+ },
296
+ processImagePath: (src) => self.processImagePath(src),
297
+ processInlineFormatting: (text) => self.processInlineFormatting(text),
298
+ processSlots: (text) => self.processSlots(text),
299
+ processRawHTML: (html) => self.processRawHTML(html),
300
+ parseTokens: (tokens, depth) => self.parseTokens(tokens, depth),
301
+ reportUnhandled: (type, token) => {
302
+ self.onUnhandledToken?.(type, token);
303
+ }
304
+ };
305
+ }
306
+ parseTokens(tokens, depth = 0) {
307
+ if (depth > this.maxRecursionDepth) {
308
+ const msg = `[md2html] Max recursion depth (${this.maxRecursionDepth}) exceeded, truncating`;
309
+ if (this.errorRecovery === "warn") {
310
+ console.warn(msg);
311
+ }
312
+ return [];
313
+ }
21
314
  const nodes = [];
315
+ const ctx = this.createContext();
22
316
  for (const token of tokens) {
23
- const node = this.parseToken(token);
317
+ const typedToken = token;
318
+ const handler = this.handlerRegistry.get(typedToken.type);
319
+ const node = handler.handle(typedToken, ctx);
24
320
  if (node) {
25
321
  nodes.push(node);
26
322
  }
27
323
  }
28
324
  return nodes;
29
325
  }
30
- parseToken(token) {
31
- switch (token.type) {
32
- case "heading":
33
- return {
34
- type: "heading",
35
- content: token.text,
36
- attributes: { level: String(token.depth) }
37
- };
38
- case "paragraph":
39
- const tokens = token.tokens || [];
40
- const hasInlineImage = tokens.some((t) => t.type === "image");
41
- if (hasInlineImage) {
42
- const children = tokens.map((t) => {
43
- if (t.type === "image") {
44
- return {
45
- type: "image",
46
- src: this.processImagePath(t.href),
47
- alt: t.text || ""
48
- };
49
- }
50
- return {
51
- type: "text",
52
- content: this.processInlineFormatting(t.text || "")
53
- };
54
- });
55
- return {
56
- type: "paragraph",
57
- children
58
- };
59
- }
60
- return {
61
- type: "paragraph",
62
- content: this.processInlineFormatting(token.text)
63
- };
64
- case "list":
65
- return {
66
- type: "list",
67
- ordered: token.ordered,
68
- children: token.items.map((item) => ({
69
- type: "list-item",
70
- content: this.processInlineFormatting(item.text)
71
- }))
72
- };
73
- case "image":
74
- return {
75
- type: "image",
76
- src: this.processImagePath(token.href),
77
- alt: token.title || ""
78
- };
79
- case "code":
80
- return {
81
- type: "code",
82
- content: token.text,
83
- attributes: { lang: token.lang || "" }
84
- };
85
- case "hr":
86
- return { type: "container", attributes: { tag: "hr" } };
87
- case "blockquote":
88
- return {
89
- type: "container",
90
- attributes: { tag: "blockquote" },
91
- children: this.parseTokens(token.tokens || [])
92
- };
93
- case "html":
94
- return { type: "container", content: token.raw };
95
- default:
96
- return null;
97
- }
98
- }
99
326
  parse(markdown, options) {
100
327
  const parseOptions = {
101
328
  gfm: options?.gfm ?? true,
102
329
  breaks: options?.breaks ?? false,
103
330
  pedantic: options?.pedantic ?? false
104
331
  };
105
- const tokens = marked.lexer(markdown, parseOptions);
106
- const content = this.parseTokens(tokens);
107
- return {
108
- title: "",
109
- content
110
- };
332
+ try {
333
+ const tokens = marked.lexer(markdown, parseOptions);
334
+ const content = this.parseTokens(tokens);
335
+ return {
336
+ title: "",
337
+ content
338
+ };
339
+ } catch (err) {
340
+ if (this.errorRecovery === "throw") throw err;
341
+ const msg = `[md2html] Parse error: ${err instanceof Error ? err.message : String(err)}`;
342
+ if (this.errorRecovery === "warn") {
343
+ console.warn(msg);
344
+ }
345
+ return {
346
+ title: "",
347
+ content: [{ type: "text", content: markdown }]
348
+ };
349
+ }
111
350
  }
112
351
  parseToNodes(markdown, options) {
113
352
  return this.parse(markdown, options).content;
@@ -118,7 +357,8 @@ class HTMLRenderer {
118
357
  this.config = {
119
358
  classPrefix: config.classPrefix || "",
120
359
  customCSS: config.customCSS || "",
121
- addHeadingIds: config.addHeadingIds ?? false
360
+ addHeadingIds: config.addHeadingIds ?? false,
361
+ emitScopeAnchors: config.emitScopeAnchors ?? false
122
362
  };
123
363
  }
124
364
  hasClassConfig() {
@@ -137,11 +377,21 @@ class HTMLRenderer {
137
377
  if (!content) return "";
138
378
  return content.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
139
379
  }
380
+ /**
381
+ * Get the scope attribute string for a node type.
382
+ * Returns empty string if emitScopeAnchors is disabled.
383
+ */
384
+ getScopeAttr(node) {
385
+ if (!this.config.emitScopeAnchors) return "";
386
+ const scopeValue = node.scope || nodeTypeToScope[node.type] || "container";
387
+ return ` data-md-scope="${scopeValue}"`;
388
+ }
140
389
  renderWithClass(tag, content, baseClass, nodeClass, extraAttrs) {
141
390
  const classAttr = this.hasClassConfig() && baseClass ? ` class="${this.getClass(baseClass, nodeClass)}"` : "";
142
391
  return `<${tag}${classAttr}${extraAttrs || ""}>${content}</${tag}>`;
143
392
  }
144
393
  renderNode(node) {
394
+ const scopeAttr = this.getScopeAttr(node);
145
395
  switch (node.type) {
146
396
  case "heading":
147
397
  const level = node.attributes?.level || "2";
@@ -153,41 +403,47 @@ class HTMLRenderer {
153
403
  headingClass = prefix ? `${prefix}${levelClass}` : levelClass;
154
404
  }
155
405
  if (!headingClass) {
156
- return `<h${level}${headingId}>${node.content || ""}</h${level}>`;
406
+ return `<h${level}${headingId}${scopeAttr}>${node.content || ""}</h${level}>`;
157
407
  }
158
- return `<h${level}${headingId} class="${headingClass}">${node.content || ""}</h${level}>`;
408
+ return `<h${level}${headingId}${scopeAttr} class="${headingClass}">${node.content || ""}</h${level}>`;
159
409
  case "paragraph":
160
410
  if (node.children) {
161
411
  const childrenHtml = node.children.map((child) => this.renderNode(child)).join("");
162
- return this.renderWithClass("p", childrenHtml, "paragraph");
412
+ return this.renderWithClass("p", childrenHtml, "paragraph", void 0, scopeAttr);
163
413
  }
164
- return this.renderWithClass("p", node.content || "", "paragraph");
414
+ return this.renderWithClass("p", node.content || "", "paragraph", void 0, scopeAttr);
165
415
  case "list":
166
416
  const tag = node.ordered ? "ol" : "ul";
167
417
  const items = node.children?.map((child) => this.renderNode(child)).join("") || "";
168
- return this.renderWithClass(tag, items, "list");
418
+ return this.renderWithClass(tag, items, "list", void 0, scopeAttr);
169
419
  case "list-item":
170
- return this.renderWithClass("li", node.content || "", "list-item");
420
+ return this.renderWithClass("li", node.content || "", "list-item", void 0, scopeAttr);
171
421
  case "image":
172
422
  const src = node.src || node.attributes?.src || "";
173
423
  const alt = node.alt || node.attributes?.alt || "";
174
424
  const classStr = this.getClass("image", node.className || void 0);
175
- return `<img src="${src}" alt="${alt}"${classStr ? ` class="${classStr}"` : ""}>`;
425
+ return `<img src="${src}" alt="${alt}"${classStr ? ` class="${classStr}"` : ""}${scopeAttr}>`;
176
426
  case "code":
177
427
  const codeClass = this.hasClassConfig() ? ` class="${this.getClass("code")} language-${node.attributes?.lang || ""}"` : ` class="language-${node.attributes?.lang || ""}"`;
178
- return `<pre><code${codeClass}>${node.content || ""}</code></pre>`;
428
+ return `<pre${scopeAttr}><code${codeClass}>${node.content || ""}</code></pre>`;
179
429
  case "container":
180
430
  if (node.attributes?.tag === "hr") return "<hr>";
181
431
  if (node.attributes?.tag === "blockquote") {
182
432
  const children = node.children?.map((child) => this.renderNode(child)).join("") || "";
183
- return this.renderWithClass("blockquote", children, "blockquote");
433
+ return this.renderWithClass("blockquote", children, "blockquote", void 0, scopeAttr);
434
+ }
435
+ if (node.rawHTML) {
436
+ return node.rawHTML;
184
437
  }
185
438
  const containerChildren = node.children?.map((child) => this.renderNode(child)).join("") || "";
186
- return this.renderWithClass("div", containerChildren, "container", node.className || void 0);
439
+ return this.renderWithClass("div", containerChildren, "container", node.className || void 0, scopeAttr);
187
440
  case "strong":
188
- return `<strong>${node.content || ""}</strong>`;
441
+ return `<strong${scopeAttr}>${node.content || ""}</strong>`;
189
442
  case "emphasis":
190
- return `<em>${node.content || ""}</em>`;
443
+ return `<em${scopeAttr}>${node.content || ""}</em>`;
444
+ case "link":
445
+ const href = node.attributes?.href || "";
446
+ return `<a href="${href}"${scopeAttr}>${node.content || ""}</a>`;
191
447
  case "text":
192
448
  default:
193
449
  return node.content || "";
@@ -197,6 +453,12 @@ class HTMLRenderer {
197
453
  if (!nodes || nodes.length === 0) {
198
454
  return "";
199
455
  }
456
+ if (this.config.emitScopeAnchors) {
457
+ const inner = nodes.map((node) => this.renderNode(node)).join("\n");
458
+ return `<div data-md-scope="root">
459
+ ${inner}
460
+ </div>`;
461
+ }
200
462
  return nodes.map((node) => this.renderNode(node)).join("\n");
201
463
  }
202
464
  renderToHTMLString(nodes) {
@@ -222,12 +484,25 @@ class MarkdownPipeline {
222
484
  styleOptions: {
223
485
  classPrefix: config.styleOptions?.classPrefix || "",
224
486
  customCSS: config.styleOptions?.customCSS || "",
225
- addHeadingIds: config.styleOptions?.addHeadingIds ?? false
226
- }
487
+ addHeadingIds: config.styleOptions?.addHeadingIds ?? false,
488
+ emitScopeAnchors: config.styleOptions?.emitScopeAnchors ?? false
489
+ },
490
+ preserveRawHTML: config.preserveRawHTML ?? false,
491
+ slotPattern: config.slotPattern ?? /\[\[(.*?)\]\]/g,
492
+ onSlot: config.onSlot,
493
+ errorRecovery: config.errorRecovery ?? "throw",
494
+ maxRecursionDepth: config.maxRecursionDepth ?? 100,
495
+ allowedHTMLTags: config.allowedHTMLTags ?? []
227
496
  };
228
497
  this.parser = new MarkdownParser({
229
498
  imagePathPrefix: this.config.imagePathPrefix,
230
- imageBaseUrl: this.config.imageBaseUrl
499
+ imageBaseUrl: this.config.imageBaseUrl,
500
+ preserveRawHTML: this.config.preserveRawHTML,
501
+ slotPattern: this.config.slotPattern,
502
+ onSlot: this.config.onSlot,
503
+ errorRecovery: this.config.errorRecovery,
504
+ maxRecursionDepth: this.config.maxRecursionDepth,
505
+ allowedHTMLTags: this.config.allowedHTMLTags
231
506
  });
232
507
  this.renderer = new HTMLRenderer(this.config.styleOptions);
233
508
  }
@@ -266,8 +541,20 @@ class MarkdownPipeline {
266
541
  }
267
542
  }
268
543
  export {
544
+ BlockquoteHandler,
545
+ CatchAllHandler,
546
+ CodeHandler,
269
547
  HTMLRenderer,
548
+ HeadingHandler,
549
+ HrHandler,
550
+ HtmlHandler,
551
+ ImageHandler,
552
+ ListHandler,
270
553
  MarkdownParser,
271
- MarkdownPipeline
554
+ MarkdownPipeline,
555
+ ParagraphHandler,
556
+ TokenHandlerRegistry,
557
+ defaultAllowedHTMLTags,
558
+ nodeTypeToScope
272
559
  };
273
560
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/parser.ts","../src/renderer.ts","../src/pipeline.ts"],"sourcesContent":["import { marked } from 'marked';\nimport { ContentNode, MarkdownContent, ParseOptions } from './types';\n\nexport class MarkdownParser {\n private imagePathPrefix: string;\n private imageBaseUrl: string;\n\n constructor(options?: { imagePathPrefix?: string; imageBaseUrl?: string }) {\n this.imagePathPrefix = options?.imagePathPrefix || '';\n this.imageBaseUrl = options?.imageBaseUrl || '';\n }\n\n private processImagePath(src: string): string {\n if (src.startsWith('http') || src.startsWith('/')) {\n return src;\n }\n let path = this.imagePathPrefix ? `${this.imagePathPrefix}${src}` : src;\n if (this.imageBaseUrl && !path.startsWith('http')) {\n path = `${this.imageBaseUrl}${path}`;\n }\n return path;\n }\n\n private processInlineFormatting(text: string): string {\n return text\n .replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>')\n .replace(/\\*(.+?)\\*/g, '<em>$1</em>');\n }\n\n private parseTokens(tokens: unknown[]): ContentNode[] {\n const nodes: ContentNode[] = [];\n \n for (const token of tokens) {\n const node = this.parseToken(token as Record<string, unknown>);\n if (node) {\n nodes.push(node);\n }\n }\n \n return nodes;\n }\n\n private parseToken(token: Record<string, unknown>): ContentNode | null {\n switch (token.type) {\n case 'heading':\n return {\n type: 'heading',\n content: token.text as string,\n attributes: { level: String(token.depth) }\n };\n \n case 'paragraph':\n const tokens = (token.tokens as Array<Record<string, unknown>>) || [];\n const hasInlineImage = tokens.some(t => t.type === 'image');\n \n if (hasInlineImage) {\n const children = tokens.map(t => {\n if (t.type === 'image') {\n return {\n type: 'image' as const,\n src: this.processImagePath(t.href as string),\n alt: t.text as string || ''\n };\n }\n return {\n type: 'text' as const,\n content: this.processInlineFormatting(t.text as string || '')\n };\n });\n return {\n type: 'paragraph',\n children\n };\n }\n \n return {\n type: 'paragraph',\n content: this.processInlineFormatting(token.text as string)\n };\n \n case 'list':\n return {\n type: 'list',\n ordered: token.ordered as boolean,\n children: (token.items as Array<Record<string, unknown>>).map((item) => ({\n type: 'list-item',\n content: this.processInlineFormatting(item.text as string)\n }))\n };\n \n case 'image':\n return {\n type: 'image',\n src: this.processImagePath(token.href as string),\n alt: token.title as string || ''\n };\n\n case 'code':\n return {\n type: 'code',\n content: token.text as string,\n attributes: { lang: token.lang as string || '' }\n };\n \n case 'hr':\n return { type: 'container', attributes: { tag: 'hr' } };\n \n case 'blockquote':\n return {\n type: 'container',\n attributes: { tag: 'blockquote' },\n children: this.parseTokens((token as Record<string, unknown>).tokens as unknown[] || [])\n };\n \n case 'html':\n return { type: 'container', content: token.raw as string };\n \n default:\n return null;\n }\n }\n\n parse(markdown: string, options?: ParseOptions): MarkdownContent {\n const parseOptions = {\n gfm: options?.gfm ?? true,\n breaks: options?.breaks ?? false,\n pedantic: options?.pedantic ?? false\n };\n \n const tokens = marked.lexer(markdown, parseOptions as Parameters<typeof marked.lexer>[1]);\n const content = this.parseTokens(tokens);\n \n return {\n title: '',\n content\n };\n }\n\n parseToNodes(markdown: string, options?: ParseOptions): ContentNode[] {\n return this.parse(markdown, options).content;\n }\n}\n","import { ContentNode, StyleConfig } from './types.js';\n\nexport class HTMLRenderer {\n private config: Required<StyleConfig>;\n\n constructor(config: StyleConfig = {}) {\n this.config = {\n classPrefix: config.classPrefix || '',\n customCSS: config.customCSS || '',\n addHeadingIds: config.addHeadingIds ?? false\n };\n }\n\n private hasClassConfig(): boolean {\n return this.config.classPrefix !== '' || this.config.addHeadingIds;\n }\n\n private getClass(baseClass: string, nodeClass?: string): string {\n if (!this.hasClassConfig()) {\n return nodeClass || '';\n }\n const prefix = this.config.classPrefix;\n const classes = [prefix ? `${prefix}${baseClass}` : baseClass];\n if (nodeClass) classes.push(nodeClass);\n return classes.join(' ');\n }\n\n private generateHeadingId(content?: string): string {\n if (!content) return '';\n return content\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/(^-|-$)/g, '');\n }\n\n private renderWithClass(tag: string, content: string, baseClass?: string, nodeClass?: string, extraAttrs?: string): string {\n const classAttr = this.hasClassConfig() && baseClass \n ? ` class=\"${this.getClass(baseClass, nodeClass)}\"` \n : '';\n return `<${tag}${classAttr}${extraAttrs || ''}>${content}</${tag}>`;\n }\n\n renderNode(node: ContentNode): string {\n switch (node.type) {\n case 'heading':\n const level = node.attributes?.level || '2';\n const headingId = this.config.addHeadingIds \n ? ` id=\"${this.generateHeadingId(node.content)}\"` \n : '';\n let headingClass = '';\n if (this.hasClassConfig()) {\n const prefix = this.config.classPrefix;\n const levelClass = level === '1' ? 'h1' : level === '2' ? 'h2' : level === '3' ? 'h3' : level === '4' ? 'h4' : level === '5' ? 'h5' : 'h6';\n headingClass = prefix ? `${prefix}${levelClass}` : levelClass;\n }\n if (!headingClass) {\n return `<h${level}${headingId}>${node.content || ''}</h${level}>`;\n }\n return `<h${level}${headingId} class=\"${headingClass}\">${node.content || ''}</h${level}>`;\n \n case 'paragraph':\n if (node.children) {\n const childrenHtml = node.children.map(child => this.renderNode(child)).join('');\n return this.renderWithClass('p', childrenHtml, 'paragraph');\n }\n return this.renderWithClass('p', node.content || '', 'paragraph');\n \n case 'list':\n const tag = node.ordered ? 'ol' : 'ul';\n const items = node.children?.map(child => this.renderNode(child)).join('') || '';\n return this.renderWithClass(tag, items, 'list');\n \n case 'list-item':\n return this.renderWithClass('li', node.content || '', 'list-item');\n \n case 'image':\n const src = node.src || node.attributes?.src || '';\n const alt = node.alt || node.attributes?.alt || '';\n const classStr = this.getClass('image', node.className || undefined);\n return `<img src=\"${src}\" alt=\"${alt}\"${classStr ? ` class=\"${classStr}\"` : ''}>`;\n \n case 'code':\n const codeClass = this.hasClassConfig() \n ? ` class=\"${this.getClass('code')} language-${node.attributes?.lang || ''}\"` \n : ` class=\"language-${node.attributes?.lang || ''}\"`;\n return `<pre><code${codeClass}>${node.content || ''}</code></pre>`;\n \n case 'container':\n if (node.attributes?.tag === 'hr') return '<hr>';\n if (node.attributes?.tag === 'blockquote') {\n const children = node.children?.map(child => this.renderNode(child)).join('') || '';\n return this.renderWithClass('blockquote', children, 'blockquote');\n }\n const containerChildren = node.children?.map(child => this.renderNode(child)).join('') || '';\n return this.renderWithClass('div', containerChildren, 'container', node.className || undefined);\n \n case 'strong':\n return `<strong>${node.content || ''}</strong>`;\n \n case 'emphasis':\n return `<em>${node.content || ''}</em>`;\n \n case 'text':\n default:\n return node.content || '';\n }\n }\n\n renderNodes(nodes: ContentNode[]): string {\n if (!nodes || nodes.length === 0) {\n return '';\n }\n return nodes.map(node => this.renderNode(node)).join('\\n');\n }\n\n renderToHTMLString(nodes: ContentNode[]): string {\n return this.renderNodes(nodes);\n }\n\n render(markdown: string): string {\n return markdown;\n }\n\n getCustomCSS(): string {\n return this.config.customCSS;\n }\n}\n","import { MarkdownParser } from './parser.js';\nimport { HTMLRenderer } from './renderer.js';\nimport { ContentNode, MarkdownContent, PipelineConfig, StyleConfig } from './types.js';\n\nexport class MarkdownPipeline {\n private parser: MarkdownParser;\n private renderer: HTMLRenderer;\n private config: Required<PipelineConfig>;\n\n constructor(config: PipelineConfig = {}) {\n this.config = {\n imagePathPrefix: config.imagePathPrefix || '',\n imageBaseUrl: config.imageBaseUrl || '',\n parseOptions: {\n gfm: config.parseOptions?.gfm ?? true,\n breaks: config.parseOptions?.breaks ?? false,\n pedantic: config.parseOptions?.pedantic ?? false\n },\n styleOptions: {\n classPrefix: config.styleOptions?.classPrefix || '',\n customCSS: config.styleOptions?.customCSS || '',\n addHeadingIds: config.styleOptions?.addHeadingIds ?? false\n }\n };\n\n this.parser = new MarkdownParser({\n imagePathPrefix: this.config.imagePathPrefix,\n imageBaseUrl: this.config.imageBaseUrl\n });\n this.renderer = new HTMLRenderer(this.config.styleOptions);\n }\n\n parse(markdown: string): ContentNode[] {\n return this.parser.parseToNodes(markdown, this.config.parseOptions);\n }\n\n parseWithMetadata(markdown: string): MarkdownContent {\n return this.parser.parse(markdown, this.config.parseOptions);\n }\n\n render(nodes: ContentNode[]): string {\n return this.renderer.renderNodes(nodes);\n }\n\n renderMarkdown(markdown: string): string {\n const nodes = this.parse(markdown);\n return this.render(nodes);\n }\n\n renderPage(title: string, nodes: ContentNode[], options?: {\n lang?: string;\n charset?: string;\n }): string {\n const html = this.render(nodes);\n return `<!DOCTYPE html>\n<html lang=\"${options?.lang || 'en'}\">\n<head>\n <meta charset=\"${options?.charset || 'UTF-8'}\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>${title}</title>\n</head>\n<body>\n ${html}\n</body>\n</html>`;\n }\n\n getConfig(): Readonly<Required<PipelineConfig>> {\n return { ...this.config };\n }\n\n getCustomCSS(): string {\n return this.renderer.getCustomCSS();\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../src/types.ts","../src/handlers/heading-handler.ts","../src/handlers/paragraph-handler.ts","../src/handlers/list-handler.ts","../src/handlers/image-handler.ts","../src/handlers/code-handler.ts","../src/handlers/hr-handler.ts","../src/handlers/blockquote-handler.ts","../src/handlers/html-handler.ts","../src/handlers/catchall-handler.ts","../src/handlers/registry.ts","../src/parser.ts","../src/renderer.ts","../src/pipeline.ts"],"sourcesContent":["export type ContentNodeType = \n | 'text' \n | 'heading' \n | 'paragraph' \n | 'list' \n | 'list-item' \n | 'image' \n | 'code' \n | 'container'\n | 'strong'\n | 'emphasis'\n | 'link';\n\nexport interface ContentNode {\n type: ContentNodeType;\n content?: string;\n children?: ContentNode[];\n attributes?: Record<string, unknown>;\n className?: string;\n src?: string;\n alt?: string;\n ordered?: boolean;\n /** Raw HTML content for passthrough mode */\n rawHTML?: string;\n /** Scope anchor value for data-md-scope */\n scope?: string;\n}\n\nexport interface MarkdownContent {\n title: string;\n metadata?: Record<string, unknown>;\n content: ContentNode[];\n}\n\nexport interface ParseOptions {\n gfm?: boolean;\n breaks?: boolean;\n pedantic?: boolean;\n}\n\nexport interface StyleConfig {\n classPrefix?: string;\n customCSS?: string;\n addHeadingIds?: boolean;\n}\n\n/**\n * v2: Extended StyleConfig with scope anchor support.\n */\nexport interface StyleConfigV2 extends StyleConfig {\n /** Emit data-md-scope attributes for CSS @scope anchoring (default: false) */\n emitScopeAnchors?: boolean;\n}\n\nexport interface PipelineConfig {\n imagePathPrefix?: string;\n imageBaseUrl?: string;\n parseOptions?: ParseOptions;\n styleOptions?: StyleConfig;\n}\n\n/**\n * v2: Extended PipelineConfig with raw HTML passthrough, slot hooks, and error recovery.\n */\nexport interface PipelineConfigV2 extends PipelineConfig {\n styleOptions?: StyleConfigV2;\n /** Preserve raw HTML tags in markdown (img, style, div, span, etc.) (default: false) */\n preserveRawHTML?: boolean;\n /** Regex pattern for slot placeholders like [[SLOT_NAME]] (default: /\\[\\[(.*?)\\]\\]/g) */\n slotPattern?: RegExp;\n /** Callback to resolve slot values. Called with the slot name, returns replacement string. */\n onSlot?: (name: string) => string;\n /** Error recovery mode (default: 'throw' — backward compatible) */\n errorRecovery?: 'throw' | 'warn' | 'silent';\n /** Max recursion depth to prevent stack overflow (default: 100) */\n maxRecursionDepth?: number;\n /** Additional allowed HTML tags for preserveRawHTML mode */\n allowedHTMLTags?: string[];\n}\n\n/** Default allowed HTML tags for preserveRawHTML mode */\nexport const defaultAllowedHTMLTags = [\n 'img', 'style', 'div', 'span', 'section', 'article',\n 'aside', 'header', 'footer', 'nav', 'main', 'figure',\n 'figcaption', 'details', 'summary', 'mark', 'time',\n 'video', 'audio', 'source', 'iframe', 'embed'\n];\n\n/** Scope hierarchy values for data-md-scope */\nexport type ScopeValue =\n | 'root'\n | 'heading'\n | 'paragraph'\n | 'list'\n | 'list-item'\n | 'image'\n | 'code'\n | 'strong'\n | 'emphasis'\n | 'link'\n | 'container';\n\n/** Maps ContentNodeType to ScopeValue */\nexport const nodeTypeToScope: Record<ContentNodeType, ScopeValue> = {\n 'text': 'root',\n 'heading': 'heading',\n 'paragraph': 'paragraph',\n 'list': 'list',\n 'list-item': 'list-item',\n 'image': 'image',\n 'code': 'code',\n 'container': 'container',\n 'strong': 'strong',\n 'emphasis': 'emphasis',\n 'link': 'link',\n};\n","import { TokenHandler, ParseContext } from './types.js';\n\n/**\n * Handles 'heading' tokens (h1-h6).\n */\nexport class HeadingHandler implements TokenHandler {\n readonly type = 'heading';\n\n handle(token: Record<string, unknown>, ctx: ParseContext) {\n return {\n type: 'heading' as const,\n content: ctx.processSlots(token.text as string),\n attributes: { level: String(token.depth) }\n };\n }\n}\n","import { ContentNode } from '../types.js';\nimport { TokenHandler, ParseContext } from './types.js';\n\n/**\n * Handles 'paragraph' tokens, including inline images and raw HTML.\n */\nexport class ParagraphHandler implements TokenHandler {\n readonly type = 'paragraph';\n\n handle(token: Record<string, unknown>, ctx: ParseContext) {\n const tokens = (token.tokens as Array<Record<string, unknown>>) || [];\n const hasInlineImage = tokens.some(t => t.type === 'image');\n const hasInlineHTML = tokens.some(t => t.type === 'html');\n\n if (hasInlineImage || (ctx.preserveRawHTML && hasInlineHTML)) {\n const children = tokens.map(t => {\n if (t.type === 'image') {\n return {\n type: 'image' as const,\n src: ctx.processImagePath(t.href as string),\n alt: t.text as string || ''\n };\n }\n if (t.type === 'html' && ctx.preserveRawHTML) {\n const processed = ctx.processRawHTML(t.raw as string);\n if (processed.trim()) {\n return { type: 'text' as const, content: processed };\n }\n return null;\n }\n return {\n type: 'text' as const,\n content: ctx.processSlots(ctx.processInlineFormatting(t.text as string || ''))\n };\n }).filter(Boolean) as ContentNode[];\n\n if (children.length === 0) return null;\n\n return { type: 'paragraph' as const, children };\n }\n\n return {\n type: 'paragraph' as const,\n content: ctx.processSlots(ctx.processInlineFormatting(token.text as string))\n };\n }\n}\n","import { TokenHandler, ParseContext } from './types.js';\n\n/**\n * Handles 'list' tokens (ordered and unordered).\n */\nexport class ListHandler implements TokenHandler {\n readonly type = 'list';\n\n handle(token: Record<string, unknown>, ctx: ParseContext) {\n return {\n type: 'list' as const,\n ordered: token.ordered as boolean,\n children: (token.items as Array<Record<string, unknown>>).map((item) => ({\n type: 'list-item' as const,\n content: ctx.processSlots(ctx.processInlineFormatting(item.text as string))\n }))\n };\n }\n}\n","import { TokenHandler, ParseContext } from './types.js';\n\n/**\n * Handles standalone 'image' tokens.\n */\nexport class ImageHandler implements TokenHandler {\n readonly type = 'image';\n\n handle(token: Record<string, unknown>, ctx: ParseContext) {\n return {\n type: 'image' as const,\n src: ctx.processImagePath(token.href as string),\n alt: token.title as string || ''\n };\n }\n}\n","import { TokenHandler, ParseContext } from './types.js';\n\n/**\n * Handles 'code' tokens (fenced code blocks).\n */\nexport class CodeHandler implements TokenHandler {\n readonly type = 'code';\n\n handle(token: Record<string, unknown>, _ctx: ParseContext) {\n return {\n type: 'code' as const,\n content: token.text as string,\n attributes: { lang: token.lang as string || '' }\n };\n }\n}\n","import { TokenHandler, ParseContext } from './types.js';\n\n/**\n * Handles 'hr' tokens (horizontal rules).\n */\nexport class HrHandler implements TokenHandler {\n readonly type = 'hr';\n\n handle(_token: Record<string, unknown>, _ctx: ParseContext) {\n return { type: 'container' as const, attributes: { tag: 'hr' } };\n }\n}\n","import { TokenHandler, ParseContext } from './types.js';\n\n/**\n * Handles 'blockquote' tokens.\n */\nexport class BlockquoteHandler implements TokenHandler {\n readonly type = 'blockquote';\n\n handle(token: Record<string, unknown>, ctx: ParseContext) {\n return {\n type: 'container' as const,\n attributes: { tag: 'blockquote' },\n children: ctx.parseTokens((token.tokens as unknown[]) || [], ctx.maxRecursionDepth + 1)\n };\n }\n}\n","import { TokenHandler, ParseContext } from './types.js';\n\n/**\n * Handles 'html' tokens (inline or block-level raw HTML).\n * In preserveRawHTML mode, passes through allowed HTML tags.\n * Otherwise, stores the raw content as a container node.\n */\nexport class HtmlHandler implements TokenHandler {\n readonly type = 'html';\n\n handle(token: Record<string, unknown>, ctx: ParseContext) {\n if (ctx.preserveRawHTML) {\n const raw = token.raw as string;\n const processed = ctx.processRawHTML(raw);\n if (processed.trim()) {\n return { type: 'container' as const, rawHTML: processed };\n }\n return null;\n }\n return { type: 'container' as const, content: token.raw as string };\n }\n}\n","import { TokenHandler, ParseContext } from './types.js';\n\n/**\n * Catch-all handler for any token type that doesn't have a dedicated handler.\n * Wraps the raw token content in a container node so content is never silently lost.\n * Reports the unhandled type via the context's reportUnhandled callback.\n */\nexport class CatchAllHandler implements TokenHandler {\n readonly type = '*';\n\n handle(token: Record<string, unknown>, ctx: ParseContext) {\n const tokenType = token.type as string;\n\n // Report the unhandled token so callers can be notified\n ctx.reportUnhandled(tokenType, token);\n\n // Try to extract meaningful content from the raw token\n const raw = token.raw as string | undefined;\n const text = token.text as string | undefined;\n const content = raw || text || `[unhandled: ${tokenType}]`;\n\n return {\n type: 'container' as const,\n content,\n attributes: {\n 'data-unhandled': tokenType,\n tag: 'div'\n }\n };\n }\n}\n","import type { TokenHandler } from './types.js';\nimport { HeadingHandler } from './heading-handler.js';\nimport { ParagraphHandler } from './paragraph-handler.js';\nimport { ListHandler } from './list-handler.js';\nimport { ImageHandler } from './image-handler.js';\nimport { CodeHandler } from './code-handler.js';\nimport { HrHandler } from './hr-handler.js';\nimport { BlockquoteHandler } from './blockquote-handler.js';\nimport { HtmlHandler } from './html-handler.js';\nimport { CatchAllHandler } from './catchall-handler.js';\n\n/**\n * Registry of token handlers. Handlers can be added/overridden externally\n * to extend the parser without modifying its internals.\n *\n * The registry uses a two-tier lookup:\n * 1. First, check for a dedicated handler by token type name\n * 2. If none found, fall back to the catch-all handler (registered as '*')\n *\n * The catch-all handler ensures no content is silently lost — unhandled\n * token types are wrapped in a container node with `data-unhandled` attribute.\n */\nexport class TokenHandlerRegistry {\n private handlers = new Map<string, TokenHandler>();\n private catchAll: TokenHandler;\n\n constructor() {\n // Register all built-in handlers\n this.register(new HeadingHandler());\n this.register(new ParagraphHandler());\n this.register(new ListHandler());\n this.register(new ImageHandler());\n this.register(new CodeHandler());\n this.register(new HrHandler());\n this.register(new BlockquoteHandler());\n this.register(new HtmlHandler());\n\n // Catch-all handler for any unregistered token types\n this.catchAll = new CatchAllHandler();\n }\n\n /** Register a handler. Overrides any existing handler for the same token type. */\n register(handler: TokenHandler): void {\n this.handlers.set(handler.type, handler);\n }\n\n /** Unregister a handler by token type. */\n unregister(type: string): void {\n this.handlers.delete(type);\n }\n\n /**\n * Get a handler for the given token type.\n * Falls back to the catch-all handler if no dedicated handler is registered.\n */\n get(type: string): TokenHandler {\n return this.handlers.get(type) ?? this.catchAll;\n }\n\n /** Check if a dedicated handler exists for the given token type (excludes catch-all). */\n has(type: string): boolean {\n return this.handlers.has(type);\n }\n\n /** Get all registered dedicated handler types. */\n get types(): string[] {\n return Array.from(this.handlers.keys());\n }\n\n /** Replace the catch-all handler with a custom implementation. */\n setCatchAll(handler: TokenHandler): void {\n this.catchAll = handler;\n }\n\n /** Get the current catch-all handler. */\n getCatchAll(): TokenHandler {\n return this.catchAll;\n }\n}\n","import { marked } from 'marked';\nimport { ContentNode, MarkdownContent, ParseOptions, defaultAllowedHTMLTags } from './types.js';\nimport { TokenHandlerRegistry, ParseContext } from './handlers/index.js';\n\nexport interface ParserOptions {\n imagePathPrefix?: string;\n imageBaseUrl?: string;\n preserveRawHTML?: boolean;\n slotPattern?: RegExp;\n onSlot?: (name: string) => string;\n errorRecovery?: 'throw' | 'warn' | 'silent';\n maxRecursionDepth?: number;\n allowedHTMLTags?: string[];\n /**\n * Callback invoked when a token type has no dedicated handler.\n * The catch-all handler will still produce a container node for the content,\n * but this callback allows callers to log, warn, or track unhandled types.\n *\n * @param type - The unhandled token type name (e.g. 'table', 'def')\n * @param token - The raw marked token\n */\n onUnhandledToken?: (type: string, token: Record<string, unknown>) => void;\n}\n\nconst DEFAULT_SLOT_PATTERN = /\\[\\[(.*?)\\]\\]/g;\n\n// Re-export handler types for convenience\nexport { TokenHandlerRegistry } from './handlers/index.js';\nexport type { TokenHandler, ParseContext } from './handlers/index.js';\n\n// ─── MarkdownParser ───────────────────────────────────────────────────────────\n\nexport class MarkdownParser {\n private imagePathPrefix: string;\n private imageBaseUrl: string;\n private preserveRawHTML: boolean;\n private slotPattern: RegExp;\n private onSlot: ((name: string) => string) | undefined;\n private errorRecovery: 'throw' | 'warn' | 'silent';\n private maxRecursionDepth: number;\n private allowedHTMLTags: Set<string>;\n private handlerRegistry: TokenHandlerRegistry;\n private onUnhandledToken?: (type: string, token: Record<string, unknown>) => void;\n\n constructor(options?: ParserOptions) {\n this.imagePathPrefix = options?.imagePathPrefix || '';\n this.imageBaseUrl = options?.imageBaseUrl || '';\n this.preserveRawHTML = options?.preserveRawHTML ?? false;\n this.slotPattern = options?.slotPattern ?? DEFAULT_SLOT_PATTERN;\n this.onSlot = options?.onSlot;\n this.errorRecovery = options?.errorRecovery ?? 'throw';\n this.maxRecursionDepth = options?.maxRecursionDepth ?? 100;\n this.allowedHTMLTags = new Set([\n ...defaultAllowedHTMLTags,\n ...(options?.allowedHTMLTags ?? [])\n ]);\n this.handlerRegistry = new TokenHandlerRegistry();\n this.onUnhandledToken = options?.onUnhandledToken;\n }\n\n /** Access the handler registry for customization. */\n get handlers(): TokenHandlerRegistry {\n return this.handlerRegistry;\n }\n\n private processImagePath(src: string): string {\n if (src.startsWith('http') || src.startsWith('/')) {\n return src;\n }\n let path = this.imagePathPrefix ? `${this.imagePathPrefix}${src}` : src;\n if (this.imageBaseUrl && !path.startsWith('http')) {\n path = `${this.imageBaseUrl}${path}`;\n }\n return path;\n }\n\n private processInlineFormatting(text: string): string {\n return text\n .replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>')\n .replace(/\\*(.+?)\\*/g, '<em>$1</em>');\n }\n\n private processSlots(text: string): string {\n if (!this.onSlot) return text;\n return text.replace(this.slotPattern, (match, name: string) => {\n return this.onSlot!(name.trim());\n });\n }\n\n private processRawHTML(html: string): string {\n if (!this.allowedHTMLTags.has('script')) {\n html = html.replace(/<script[\\s\\S]*?<\\/script>/gi, '');\n html = html.replace(/<\\/?script[^>]*>/gi, '');\n }\n if (!this.allowedHTMLTags.has('style')) {\n html = html.replace(/<style[\\s\\S]*?<\\/style>/gi, '');\n html = html.replace(/<\\/?style[^>]*>/gi, '');\n }\n return html;\n }\n\n /**\n * Build the ParseContext that is passed to every token handler.\n * This is the bridge between the parser's private services and the handlers.\n */\n private createContext(): ParseContext {\n const self = this;\n return {\n get preserveRawHTML() { return self.preserveRawHTML; },\n get errorRecovery() { return self.errorRecovery; },\n get maxRecursionDepth() { return self.maxRecursionDepth; },\n processImagePath: (src: string) => self.processImagePath(src),\n processInlineFormatting: (text: string) => self.processInlineFormatting(text),\n processSlots: (text: string) => self.processSlots(text),\n processRawHTML: (html: string) => self.processRawHTML(html),\n parseTokens: (tokens: unknown[], depth: number) => self.parseTokens(tokens, depth),\n reportUnhandled: (type: string, token: Record<string, unknown>) => {\n self.onUnhandledToken?.(type, token);\n }\n };\n }\n\n private parseTokens(tokens: unknown[], depth: number = 0): ContentNode[] {\n if (depth > this.maxRecursionDepth) {\n const msg = `[md2html] Max recursion depth (${this.maxRecursionDepth}) exceeded, truncating`;\n if (this.errorRecovery === 'warn') {\n console.warn(msg);\n }\n return [];\n }\n\n const nodes: ContentNode[] = [];\n const ctx = this.createContext();\n\n for (const token of tokens) {\n const typedToken = token as Record<string, unknown>;\n // The registry automatically falls back to the catch-all handler\n const handler = this.handlerRegistry.get(typedToken.type as string);\n const node = handler.handle(typedToken, ctx);\n if (node) {\n nodes.push(node);\n }\n }\n\n return nodes;\n }\n\n parse(markdown: string, options?: ParseOptions): MarkdownContent {\n const parseOptions = {\n gfm: options?.gfm ?? true,\n breaks: options?.breaks ?? false,\n pedantic: options?.pedantic ?? false\n };\n\n try {\n const tokens = marked.lexer(markdown, parseOptions as Parameters<typeof marked.lexer>[1]);\n const content = this.parseTokens(tokens);\n\n return {\n title: '',\n content\n };\n } catch (err) {\n if (this.errorRecovery === 'throw') throw err;\n\n const msg = `[md2html] Parse error: ${err instanceof Error ? err.message : String(err)}`;\n if (this.errorRecovery === 'warn') {\n console.warn(msg);\n }\n\n return {\n title: '',\n content: [{ type: 'text', content: markdown }]\n };\n }\n }\n\n parseToNodes(markdown: string, options?: ParseOptions): ContentNode[] {\n return this.parse(markdown, options).content;\n }\n}\n","import { ContentNode, StyleConfig, StyleConfigV2, nodeTypeToScope } from './types.js';\n\nexport class HTMLRenderer {\n private config: Required<StyleConfigV2>;\n\n constructor(config: StyleConfigV2 = {}) {\n this.config = {\n classPrefix: config.classPrefix || '',\n customCSS: config.customCSS || '',\n addHeadingIds: config.addHeadingIds ?? false,\n emitScopeAnchors: config.emitScopeAnchors ?? false\n };\n }\n\n private hasClassConfig(): boolean {\n return this.config.classPrefix !== '' || this.config.addHeadingIds;\n }\n\n private getClass(baseClass: string, nodeClass?: string): string {\n if (!this.hasClassConfig()) {\n return nodeClass || '';\n }\n const prefix = this.config.classPrefix;\n const classes = [prefix ? `${prefix}${baseClass}` : baseClass];\n if (nodeClass) classes.push(nodeClass);\n return classes.join(' ');\n }\n\n private generateHeadingId(content?: string): string {\n if (!content) return '';\n return content\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/(^-|-$)/g, '');\n }\n\n /**\n * Get the scope attribute string for a node type.\n * Returns empty string if emitScopeAnchors is disabled.\n */\n private getScopeAttr(node: ContentNode): string {\n if (!this.config.emitScopeAnchors) return '';\n const scopeValue = node.scope || nodeTypeToScope[node.type] || 'container';\n return ` data-md-scope=\"${scopeValue}\"`;\n }\n\n private renderWithClass(tag: string, content: string, baseClass?: string, nodeClass?: string, extraAttrs?: string): string {\n const classAttr = this.hasClassConfig() && baseClass \n ? ` class=\"${this.getClass(baseClass, nodeClass)}\"` \n : '';\n return `<${tag}${classAttr}${extraAttrs || ''}>${content}</${tag}>`;\n }\n\n renderNode(node: ContentNode): string {\n const scopeAttr = this.getScopeAttr(node);\n\n switch (node.type) {\n case 'heading':\n const level = node.attributes?.level || '2';\n const headingId = this.config.addHeadingIds \n ? ` id=\"${this.generateHeadingId(node.content)}\"` \n : '';\n let headingClass = '';\n if (this.hasClassConfig()) {\n const prefix = this.config.classPrefix;\n const levelClass = level === '1' ? 'h1' : level === '2' ? 'h2' : level === '3' ? 'h3' : level === '4' ? 'h4' : level === '5' ? 'h5' : 'h6';\n headingClass = prefix ? `${prefix}${levelClass}` : levelClass;\n }\n if (!headingClass) {\n return `<h${level}${headingId}${scopeAttr}>${node.content || ''}</h${level}>`;\n }\n return `<h${level}${headingId}${scopeAttr} class=\"${headingClass}\">${node.content || ''}</h${level}>`;\n \n case 'paragraph':\n if (node.children) {\n const childrenHtml = node.children.map(child => this.renderNode(child)).join('');\n return this.renderWithClass('p', childrenHtml, 'paragraph', undefined, scopeAttr);\n }\n return this.renderWithClass('p', node.content || '', 'paragraph', undefined, scopeAttr);\n \n case 'list':\n const tag = node.ordered ? 'ol' : 'ul';\n const items = node.children?.map(child => this.renderNode(child)).join('') || '';\n return this.renderWithClass(tag, items, 'list', undefined, scopeAttr);\n \n case 'list-item':\n return this.renderWithClass('li', node.content || '', 'list-item', undefined, scopeAttr);\n \n case 'image':\n const src = node.src || node.attributes?.src || '';\n const alt = node.alt || node.attributes?.alt || '';\n const classStr = this.getClass('image', node.className || undefined);\n return `<img src=\"${src}\" alt=\"${alt}\"${classStr ? ` class=\"${classStr}\"` : ''}${scopeAttr}>`;\n \n case 'code':\n const codeClass = this.hasClassConfig() \n ? ` class=\"${this.getClass('code')} language-${node.attributes?.lang || ''}\"` \n : ` class=\"language-${node.attributes?.lang || ''}\"`;\n return `<pre${scopeAttr}><code${codeClass}>${node.content || ''}</code></pre>`;\n \n case 'container':\n if (node.attributes?.tag === 'hr') return '<hr>';\n if (node.attributes?.tag === 'blockquote') {\n const children = node.children?.map(child => this.renderNode(child)).join('') || '';\n return this.renderWithClass('blockquote', children, 'blockquote', undefined, scopeAttr);\n }\n // If node has rawHTML, emit it directly\n if (node.rawHTML) {\n return node.rawHTML;\n }\n const containerChildren = node.children?.map(child => this.renderNode(child)).join('') || '';\n return this.renderWithClass('div', containerChildren, 'container', node.className || undefined, scopeAttr);\n \n case 'strong':\n return `<strong${scopeAttr}>${node.content || ''}</strong>`;\n \n case 'emphasis':\n return `<em${scopeAttr}>${node.content || ''}</em>`;\n \n case 'link':\n const href = node.attributes?.href || '';\n return `<a href=\"${href}\"${scopeAttr}>${node.content || ''}</a>`;\n \n case 'text':\n default:\n return node.content || '';\n }\n }\n\n renderNodes(nodes: ContentNode[]): string {\n if (!nodes || nodes.length === 0) {\n return '';\n }\n // Wrap in scope root if emitScopeAnchors is enabled\n if (this.config.emitScopeAnchors) {\n const inner = nodes.map(node => this.renderNode(node)).join('\\n');\n return `<div data-md-scope=\"root\">\\n${inner}\\n</div>`;\n }\n return nodes.map(node => this.renderNode(node)).join('\\n');\n }\n\n renderToHTMLString(nodes: ContentNode[]): string {\n return this.renderNodes(nodes);\n }\n\n render(markdown: string): string {\n return markdown;\n }\n\n getCustomCSS(): string {\n return this.config.customCSS;\n }\n}\n","import { MarkdownParser } from './parser.js';\nimport { HTMLRenderer } from './renderer.js';\nimport { ContentNode, MarkdownContent, PipelineConfigV2 } from './types.js';\n\ntype NormalizedPipelineConfig = Required<Omit<PipelineConfigV2, 'onSlot' | 'slotPattern'>> & {\n onSlot?: (name: string) => string;\n slotPattern: RegExp;\n};\n\nexport class MarkdownPipeline {\n private parser: MarkdownParser;\n private renderer: HTMLRenderer;\n private config: NormalizedPipelineConfig;\n\n constructor(config: PipelineConfigV2 = {}) {\n this.config = {\n imagePathPrefix: config.imagePathPrefix || '',\n imageBaseUrl: config.imageBaseUrl || '',\n parseOptions: {\n gfm: config.parseOptions?.gfm ?? true,\n breaks: config.parseOptions?.breaks ?? false,\n pedantic: config.parseOptions?.pedantic ?? false\n },\n styleOptions: {\n classPrefix: config.styleOptions?.classPrefix || '',\n customCSS: config.styleOptions?.customCSS || '',\n addHeadingIds: config.styleOptions?.addHeadingIds ?? false,\n emitScopeAnchors: config.styleOptions?.emitScopeAnchors ?? false\n },\n preserveRawHTML: config.preserveRawHTML ?? false,\n slotPattern: config.slotPattern ?? /\\[\\[(.*?)\\]\\]/g,\n onSlot: config.onSlot,\n errorRecovery: config.errorRecovery ?? 'throw',\n maxRecursionDepth: config.maxRecursionDepth ?? 100,\n allowedHTMLTags: config.allowedHTMLTags ?? []\n };\n\n this.parser = new MarkdownParser({\n imagePathPrefix: this.config.imagePathPrefix,\n imageBaseUrl: this.config.imageBaseUrl,\n preserveRawHTML: this.config.preserveRawHTML,\n slotPattern: this.config.slotPattern,\n onSlot: this.config.onSlot,\n errorRecovery: this.config.errorRecovery,\n maxRecursionDepth: this.config.maxRecursionDepth,\n allowedHTMLTags: this.config.allowedHTMLTags\n });\n this.renderer = new HTMLRenderer(this.config.styleOptions);\n }\n\n parse(markdown: string): ContentNode[] {\n return this.parser.parseToNodes(markdown, this.config.parseOptions);\n }\n\n parseWithMetadata(markdown: string): MarkdownContent {\n return this.parser.parse(markdown, this.config.parseOptions);\n }\n\n render(nodes: ContentNode[]): string {\n return this.renderer.renderNodes(nodes);\n }\n\n renderMarkdown(markdown: string): string {\n const nodes = this.parse(markdown);\n return this.render(nodes);\n }\n\n renderPage(title: string, nodes: ContentNode[], options?: {\n lang?: string;\n charset?: string;\n }): string {\n const html = this.render(nodes);\n return `<!DOCTYPE html>\n<html lang=\"${options?.lang || 'en'}\">\n<head>\n <meta charset=\"${options?.charset || 'UTF-8'}\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>${title}</title>\n</head>\n<body>\n ${html}\n</body>\n</html>`;\n }\n\n getConfig(): Readonly<PipelineConfigV2> {\n return { ...this.config };\n }\n\n getCustomCSS(): string {\n return this.renderer.getCustomCSS();\n }\n}\n"],"names":[],"mappings":"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"}
@@ -9,9 +9,13 @@ declare interface ContentNode {
9
9
  src?: string;
10
10
  alt?: string;
11
11
  ordered?: boolean;
12
+ /** Raw HTML content for passthrough mode */
13
+ rawHTML?: string;
14
+ /** Scope anchor value for data-md-scope */
15
+ scope?: string;
12
16
  }
13
17
 
14
- declare type ContentNodeType = 'text' | 'heading' | 'paragraph' | 'list' | 'list-item' | 'image' | 'code' | 'container' | 'strong' | 'emphasis';
18
+ declare type ContentNodeType = 'text' | 'heading' | 'paragraph' | 'list' | 'list-item' | 'image' | 'code' | 'container' | 'strong' | 'emphasis' | 'link';
15
19
 
16
20
  export declare class LitRenderer {
17
21
  private renderTextNode;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leadertechie/md2html",
3
- "version": "0.1.0-alpha.13",
3
+ "version": "0.1.0-alpha.14",
4
4
  "description": "Markdown to HTML pipeline - parse markdown to AST, render to HTML or Lit templates",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",