@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 +156 -2
- package/dist/index.d.ts +281 -9
- package/dist/index.js +381 -94
- package/dist/index.js.map +1 -1
- package/dist/lit-renderer.d.ts +5 -1
- package/package.json +1 -1
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
|
-
|
|
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?:
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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?:
|
|
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<
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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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"}
|
package/dist/lit-renderer.d.ts
CHANGED
|
@@ -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