@leadertechie/md2html 0.1.0-alpha.2 → 0.1.0-alpha.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,6 +1,1116 @@
1
- export * from './types';
2
- export * from './parser';
3
- export * from './renderer';
4
- export * from './lit-renderer';
5
- export * from './pipeline';
6
- export { LitRenderer as HTMLRenderer } from './lit-renderer';
1
+ import { d as defaultAllowedHTMLTags, H as HTMLRenderer } from "./renderer-Ba-j0YEq.js";
2
+ import { R, n } from "./renderer-Ba-j0YEq.js";
3
+ import { marked } from "marked";
4
+ class HeadingHandler {
5
+ constructor() {
6
+ this.type = "heading";
7
+ }
8
+ handle(token, ctx) {
9
+ return {
10
+ type: "heading",
11
+ content: ctx.processSlots(token.text),
12
+ attributes: { level: String(token.depth) }
13
+ };
14
+ }
15
+ }
16
+ class ParagraphHandler {
17
+ constructor() {
18
+ this.type = "paragraph";
19
+ }
20
+ handle(token, ctx) {
21
+ const tokens = token.tokens || [];
22
+ const hasInlineImage = tokens.some((t) => t.type === "image");
23
+ const hasInlineLink = tokens.some((t) => t.type === "link");
24
+ const hasInlineHTML = tokens.some((t) => t.type === "html");
25
+ if (hasInlineImage || hasInlineLink || ctx.preserveRawHTML && hasInlineHTML) {
26
+ const children = tokens.map((t) => {
27
+ if (t.type === "image") {
28
+ return {
29
+ type: "image",
30
+ src: ctx.processImagePath(t.href),
31
+ alt: t.text || ""
32
+ };
33
+ }
34
+ if (t.type === "link") {
35
+ return {
36
+ type: "link",
37
+ content: ctx.processSlots(ctx.processInlineFormatting(t.text || "")),
38
+ attributes: {
39
+ href: t.href || "",
40
+ ...t.title ? { title: t.title } : {}
41
+ }
42
+ };
43
+ }
44
+ if (t.type === "html" && ctx.preserveRawHTML) {
45
+ const processed = ctx.processRawHTML(t.raw);
46
+ if (processed.trim()) {
47
+ return { type: "text", content: processed };
48
+ }
49
+ return null;
50
+ }
51
+ return {
52
+ type: "text",
53
+ content: ctx.processSlots(ctx.processInlineFormatting(t.text || ""))
54
+ };
55
+ }).filter(Boolean);
56
+ if (children.length === 0) return null;
57
+ return { type: "paragraph", children };
58
+ }
59
+ return {
60
+ type: "paragraph",
61
+ content: ctx.processSlots(ctx.processInlineFormatting(token.text))
62
+ };
63
+ }
64
+ }
65
+ class ListHandler {
66
+ constructor() {
67
+ this.type = "list";
68
+ }
69
+ handle(token, ctx) {
70
+ return {
71
+ type: "list",
72
+ ordered: token.ordered,
73
+ children: token.items.map((item) => ({
74
+ type: "list-item",
75
+ content: ctx.processSlots(ctx.processInlineFormatting(item.text))
76
+ }))
77
+ };
78
+ }
79
+ }
80
+ class ImageHandler {
81
+ constructor() {
82
+ this.type = "image";
83
+ }
84
+ handle(token, ctx) {
85
+ return {
86
+ type: "image",
87
+ src: ctx.processImagePath(token.href),
88
+ alt: token.title || ""
89
+ };
90
+ }
91
+ }
92
+ class CodeHandler {
93
+ constructor() {
94
+ this.type = "code";
95
+ }
96
+ handle(token, _ctx) {
97
+ return {
98
+ type: "code",
99
+ content: token.text,
100
+ attributes: { lang: token.lang || "" }
101
+ };
102
+ }
103
+ }
104
+ class HrHandler {
105
+ constructor() {
106
+ this.type = "hr";
107
+ }
108
+ handle(_token, _ctx) {
109
+ return { type: "container", attributes: { tag: "hr" } };
110
+ }
111
+ }
112
+ class BlockquoteHandler {
113
+ constructor() {
114
+ this.type = "blockquote";
115
+ }
116
+ handle(token, ctx) {
117
+ return {
118
+ type: "container",
119
+ attributes: { tag: "blockquote" },
120
+ children: ctx.parseTokens(token.tokens || [], ctx.maxRecursionDepth + 1)
121
+ };
122
+ }
123
+ }
124
+ class HtmlHandler {
125
+ constructor() {
126
+ this.type = "html";
127
+ }
128
+ handle(token, ctx) {
129
+ if (ctx.preserveRawHTML) {
130
+ const raw = token.raw;
131
+ const processed = ctx.processRawHTML(raw);
132
+ if (processed.trim()) {
133
+ return { type: "container", rawHTML: processed };
134
+ }
135
+ return null;
136
+ }
137
+ return { type: "container", content: token.raw };
138
+ }
139
+ }
140
+ class LinkHandler {
141
+ constructor() {
142
+ this.type = "link";
143
+ }
144
+ handle(token, ctx) {
145
+ const text = token.text || "";
146
+ const href = token.href || "";
147
+ const title = token.title || "";
148
+ return {
149
+ type: "link",
150
+ content: ctx.processSlots(ctx.processInlineFormatting(text)),
151
+ attributes: {
152
+ href,
153
+ ...title ? { title } : {}
154
+ }
155
+ };
156
+ }
157
+ }
158
+ class CatchAllHandler {
159
+ constructor() {
160
+ this.type = "*";
161
+ }
162
+ handle(token, ctx) {
163
+ const tokenType = token.type;
164
+ ctx.reportUnhandled(tokenType, token);
165
+ const raw = token.raw;
166
+ const text = token.text;
167
+ const content = raw || text || `[unhandled: ${tokenType}]`;
168
+ return {
169
+ type: "container",
170
+ content,
171
+ attributes: {
172
+ "data-unhandled": tokenType,
173
+ tag: "div"
174
+ }
175
+ };
176
+ }
177
+ }
178
+ class FrontmatterHandler {
179
+ constructor() {
180
+ this.type = "frontmatter";
181
+ }
182
+ handle(token, ctx) {
183
+ const raw = token.raw || "";
184
+ const lines = raw.split("\n");
185
+ const parsed = {};
186
+ let currentKey = null;
187
+ let currentArray = [];
188
+ for (const line of lines) {
189
+ const listMatch = line.match(/^\s+-\s+(.+)$/);
190
+ if (listMatch && currentKey) {
191
+ currentArray.push(listMatch[1].trim());
192
+ continue;
193
+ }
194
+ if (currentKey && currentArray.length > 0) {
195
+ parsed[currentKey] = [...currentArray];
196
+ currentArray = [];
197
+ currentKey = null;
198
+ }
199
+ const keyMatch = line.match(/^(\w[\w_-]*)\s*:\s*(.*)$/);
200
+ if (keyMatch) {
201
+ currentKey = keyMatch[1];
202
+ const val = keyMatch[2].trim();
203
+ if (val === "") {
204
+ continue;
205
+ } else if (val.startsWith("[") && val.endsWith("]")) {
206
+ parsed[currentKey] = val.slice(1, -1).split(",").map((s) => s.trim().replace(/^["']|["']$/g, ""));
207
+ currentKey = null;
208
+ } else {
209
+ parsed[currentKey] = val.replace(/^["']|["']$/g, "");
210
+ currentKey = null;
211
+ }
212
+ }
213
+ }
214
+ if (currentKey && currentArray.length > 0) {
215
+ parsed[currentKey] = [...currentArray];
216
+ }
217
+ if (ctx.metadata) {
218
+ Object.assign(ctx.metadata, parsed);
219
+ }
220
+ return null;
221
+ }
222
+ }
223
+ class ContainerBlockHandler {
224
+ constructor() {
225
+ this.type = "containerBlock";
226
+ }
227
+ handle(token, ctx) {
228
+ const specifier = token.specifier;
229
+ const childTokens = token.tokens;
230
+ if (!specifier) return null;
231
+ const tagMatch = specifier.match(/^(\w+)/);
232
+ const idMatch = specifier.match(/#([\w-]+)/);
233
+ const classMatches = [...specifier.matchAll(/\.([\w-]+)/g)];
234
+ const tag = tagMatch?.[1] || "div";
235
+ const id = idMatch?.[1] || "";
236
+ const classes = classMatches.map((m) => m[1]);
237
+ const children = ctx.parseTokens(childTokens, 0);
238
+ return {
239
+ type: "container",
240
+ children,
241
+ attributes: {
242
+ tag,
243
+ id: id || void 0
244
+ },
245
+ className: classes.length > 0 ? classes.join(" ") : void 0
246
+ };
247
+ }
248
+ }
249
+ class TokenHandlerRegistry {
250
+ constructor() {
251
+ this.handlers = /* @__PURE__ */ new Map();
252
+ this.register(new HeadingHandler());
253
+ this.register(new ParagraphHandler());
254
+ this.register(new ListHandler());
255
+ this.register(new ImageHandler());
256
+ this.register(new CodeHandler());
257
+ this.register(new HrHandler());
258
+ this.register(new BlockquoteHandler());
259
+ this.register(new HtmlHandler());
260
+ this.register(new FrontmatterHandler());
261
+ this.register(new ContainerBlockHandler());
262
+ this.register(new LinkHandler());
263
+ this.catchAll = new CatchAllHandler();
264
+ }
265
+ /** Register a handler. Overrides any existing handler for the same token type. */
266
+ register(handler) {
267
+ this.handlers.set(handler.type, handler);
268
+ }
269
+ /** Unregister a handler by token type. */
270
+ unregister(type) {
271
+ this.handlers.delete(type);
272
+ }
273
+ /**
274
+ * Get a handler for the given token type.
275
+ * Falls back to the catch-all handler if no dedicated handler is registered.
276
+ */
277
+ get(type) {
278
+ return this.handlers.get(type) ?? this.catchAll;
279
+ }
280
+ /** Check if a dedicated handler exists for the given token type (excludes catch-all). */
281
+ has(type) {
282
+ return this.handlers.has(type);
283
+ }
284
+ /** Get all registered dedicated handler types. */
285
+ get types() {
286
+ return Array.from(this.handlers.keys());
287
+ }
288
+ /** Replace the catch-all handler with a custom implementation. */
289
+ setCatchAll(handler) {
290
+ this.catchAll = handler;
291
+ }
292
+ /** Get the current catch-all handler. */
293
+ getCatchAll() {
294
+ return this.catchAll;
295
+ }
296
+ }
297
+ var h = /* @__PURE__ */ ((t) => (t[t.DEBUG = 1] = "DEBUG", t[t.INFO = 9] = "INFO", t[t.WARN = 13] = "WARN", t[t.ERROR = 17] = "ERROR", t))(h || {});
298
+ const S = {
299
+ 1: "DEBUG",
300
+ 9: "INFO",
301
+ 13: "WARN",
302
+ 17: "ERROR"
303
+ }, E = [
304
+ "/telemetry/src/logger.ts",
305
+ "/telemetry/src/caller.ts",
306
+ "/telemetry/src/index.ts"
307
+ ];
308
+ function N() {
309
+ const e = new Error().stack;
310
+ if (!e) return;
311
+ const s = e.split(`
312
+ `);
313
+ for (let r = 1; r < s.length; r++) {
314
+ const o = s[r].trim(), n2 = $(o);
315
+ if (!n2) continue;
316
+ const { file: l } = n2;
317
+ if (!(l && E.some((m) => l.includes(m))) && !(l && l.includes("/node_modules/")) && l)
318
+ return n2;
319
+ }
320
+ }
321
+ function $(t) {
322
+ const e = t.match(
323
+ /at\s+(?:(?:async\s+)?(?:(.+?)\s+\()?)?(?:(.+?):(\d+):(\d+)\)?)$/
324
+ );
325
+ if (!e) return null;
326
+ const s = e[1] || "<anonymous>", r = e[2], o = parseInt(e[3], 10), n2 = parseInt(e[4], 10);
327
+ return r ? { file: r, line: o, column: n2, functionName: s } : null;
328
+ }
329
+ class g {
330
+ constructor(e = [], s, r = {}) {
331
+ this.processors = [...e], this.resource = s ?? { serviceName: "unknown" }, this.baseAttributes = { ...r };
332
+ }
333
+ // ── Public API ───────────────────────────────────────────────────────────
334
+ debug(e, s) {
335
+ this.emit(h.DEBUG, e, void 0, s);
336
+ }
337
+ info(e, s) {
338
+ this.emit(h.INFO, e, void 0, s);
339
+ }
340
+ warn(e, s) {
341
+ this.emit(h.WARN, e, void 0, s);
342
+ }
343
+ error(e, s, r) {
344
+ this.emit(h.ERROR, e, s, r);
345
+ }
346
+ /**
347
+ * Create a child logger with merged base attributes.
348
+ * Returns a NEW Logger — original is immutable.
349
+ */
350
+ withContext(e) {
351
+ return new g(this.processors, this.resource, {
352
+ ...this.baseAttributes,
353
+ ...e
354
+ });
355
+ }
356
+ /**
357
+ * Append a processor to the pipeline.
358
+ * Returns a NEW Logger — original is immutable.
359
+ */
360
+ withProcessor(e) {
361
+ return new g(
362
+ [...this.processors, e],
363
+ this.resource,
364
+ this.baseAttributes
365
+ );
366
+ }
367
+ /**
368
+ * Force-flush all pending records in all processors.
369
+ * Await before the end of a CF Worker request (inside ctx.waitUntil).
370
+ */
371
+ async flush() {
372
+ await Promise.all(this.processors.map((e) => e.forceFlush()));
373
+ }
374
+ /**
375
+ * Shutdown: flush + release resources. No logging after shutdown.
376
+ */
377
+ async shutdown() {
378
+ await this.flush(), await Promise.all(this.processors.map((e) => e.shutdown()));
379
+ }
380
+ // ── Internal ────────────────────────────────────────────────────────────
381
+ emit(e, s, r, o) {
382
+ if (this.processors.length === 0) return;
383
+ const n2 = (/* @__PURE__ */ new Date()).toISOString(), l = {
384
+ severityNumber: e,
385
+ severityText: S[e],
386
+ body: s,
387
+ timestamp: n2,
388
+ observedTimestamp: n2,
389
+ attributes: { ...this.baseAttributes, ...o },
390
+ caller: N(),
391
+ ...r ? { error: r } : {},
392
+ resource: { ...this.resource }
393
+ };
394
+ for (const m of this.processors)
395
+ try {
396
+ m.onEmit(l);
397
+ } catch (f) {
398
+ console.error(
399
+ "[telemetry] Processor threw in onEmit:",
400
+ f
401
+ );
402
+ }
403
+ }
404
+ }
405
+ class T {
406
+ constructor(e) {
407
+ this.shutdownFlag = false, this.pendingExport = Promise.resolve(), this.adapter = e;
408
+ }
409
+ onEmit(e) {
410
+ this.shutdownFlag || (this.pendingExport = this.pendingExport.then(() => this.adapter.export([e])).catch((s) => {
411
+ console.error(
412
+ `[telemetry] SimpleLogProcessor: adapter "${this.adapter.name}" export failed:`,
413
+ s
414
+ );
415
+ }));
416
+ }
417
+ async forceFlush() {
418
+ await this.pendingExport;
419
+ }
420
+ async shutdown() {
421
+ this.shutdownFlag = true, await this.forceFlush(), await this.adapter.shutdown?.();
422
+ }
423
+ }
424
+ class M {
425
+ constructor(e) {
426
+ this.processors = [], this.resource = {
427
+ serviceName: e?.serviceName ?? "unknown",
428
+ environment: e?.environment,
429
+ version: e?.version,
430
+ processName: e?.processName
431
+ };
432
+ }
433
+ /**
434
+ * Register an adapter via a SimpleLogProcessor (immediate export).
435
+ * For batching, use addProcessor(new BatchLogProcessor(adapter, opts)).
436
+ */
437
+ addAdapter(e) {
438
+ return this.addProcessor(new T(e));
439
+ }
440
+ /**
441
+ * Register a custom processor (SimpleLogProcessor, BatchLogProcessor,
442
+ * or your own implementation).
443
+ */
444
+ addProcessor(e) {
445
+ return this.processors.push(e), this;
446
+ }
447
+ /**
448
+ * Get a named Logger instance.
449
+ * The logger inherits the provider's resource and processors.
450
+ * Optionally provide initial context attributes.
451
+ */
452
+ getLogger(e, s) {
453
+ const r = {
454
+ ...this.resource,
455
+ ...e ? { processName: e } : {}
456
+ };
457
+ return new g(this.processors, r, s);
458
+ }
459
+ /**
460
+ * Force-flush all registered processors.
461
+ */
462
+ async flush() {
463
+ await Promise.all(this.processors.map((e) => e.forceFlush()));
464
+ }
465
+ /**
466
+ * Shutdown: flush + release all processor resources.
467
+ */
468
+ async shutdown() {
469
+ await this.flush(), await Promise.all(this.processors.map((e) => e.shutdown()));
470
+ }
471
+ }
472
+ function U(t) {
473
+ const e = t?.level ?? h.DEBUG, s = t?.json ?? false;
474
+ return {
475
+ name: "console",
476
+ async export(r) {
477
+ for (const o of r)
478
+ o.severityNumber < e || (s ? I(o) : R2(o));
479
+ }
480
+ };
481
+ }
482
+ function R2(t) {
483
+ const e = F(t.severityNumber), s = t.timestamp, r = t.severityText, o = t.caller ? ` (${t.caller.file}:${t.caller.line})` : "", n2 = Object.keys(t.attributes).length > 0;
484
+ !!t.error ? e(
485
+ `[${s}] [${r}]${o} ${t.body}`,
486
+ t.attributes,
487
+ t.error
488
+ ) : n2 ? e(`[${s}] [${r}]${o} ${t.body}`, t.attributes) : e(`[${s}] [${r}]${o} ${t.body}`);
489
+ }
490
+ function I(t) {
491
+ const e = {
492
+ timestamp: t.timestamp,
493
+ level: t.severityText,
494
+ message: t.body,
495
+ service: t.resource.serviceName,
496
+ environment: t.resource.environment,
497
+ attributes: t.attributes,
498
+ caller: t.caller
499
+ };
500
+ t.error && (e.error = {
501
+ name: t.error.name,
502
+ message: t.error.message,
503
+ stack: t.error.stack
504
+ }), console.log(JSON.stringify(e));
505
+ }
506
+ function F(t) {
507
+ switch (t) {
508
+ case h.ERROR:
509
+ return console.error.bind(console);
510
+ case h.WARN:
511
+ return console.warn.bind(console);
512
+ case h.INFO:
513
+ return console.log.bind(console);
514
+ case h.DEBUG:
515
+ default:
516
+ return console.debug.bind(console);
517
+ }
518
+ }
519
+ const defaultLoggers = /* @__PURE__ */ new Map();
520
+ function getDefaultLogger(serviceName) {
521
+ let log = defaultLoggers.get(serviceName);
522
+ if (!log) {
523
+ const provider = new M({ serviceName });
524
+ provider.addAdapter(U({ level: h.WARN }));
525
+ log = provider.getLogger();
526
+ defaultLoggers.set(serviceName, log);
527
+ }
528
+ return log;
529
+ }
530
+ function createParseContext(services) {
531
+ const metadata = {};
532
+ return {
533
+ get preserveRawHTML() {
534
+ return services.preserveRawHTML;
535
+ },
536
+ get errorRecovery() {
537
+ return services.errorRecovery;
538
+ },
539
+ get maxRecursionDepth() {
540
+ return services.maxRecursionDepth;
541
+ },
542
+ processImagePath: (src) => services.processImagePath(src),
543
+ processInlineFormatting: (text) => services.processInlineFormatting(text),
544
+ processSlots: (text) => services.processSlots(text),
545
+ processRawHTML: (html) => services.processRawHTML(html),
546
+ parseTokens: (tokens, depth) => services.parseTokens(tokens, depth),
547
+ reportUnhandled: (type, token) => {
548
+ services.onUnhandledToken?.(type, token);
549
+ },
550
+ metadata
551
+ };
552
+ }
553
+ class ContainerBlockPreprocessor {
554
+ constructor() {
555
+ this.name = "container-blocks";
556
+ }
557
+ process(markdown) {
558
+ return markdown.replace(/^:::(?:(\w+(?:[.#][\w-]+)*)\s*)?$/gm, (_match, specifier) => {
559
+ if (!specifier) {
560
+ return "<!-- /md-container -->";
561
+ }
562
+ const normalized = specifier.match(/^\w/) ? specifier : `div${specifier}`;
563
+ return `<!-- md-container:${normalized} -->`;
564
+ });
565
+ }
566
+ }
567
+ class CompositePreprocessor {
568
+ constructor(processors) {
569
+ this.name = "composite";
570
+ this.processors = [];
571
+ if (processors) {
572
+ this.processors = [...processors];
573
+ }
574
+ }
575
+ /** Add a preprocessor to the chain. Returns `this` for fluent API. */
576
+ add(processor) {
577
+ this.processors.push(processor);
578
+ return this;
579
+ }
580
+ /** Remove a preprocessor by name. */
581
+ remove(name) {
582
+ this.processors = this.processors.filter((p) => p.name !== name);
583
+ }
584
+ /** Get the list of registered preprocessors. */
585
+ getProcessors() {
586
+ return [...this.processors];
587
+ }
588
+ process(markdown) {
589
+ let result = markdown;
590
+ for (const processor of this.processors) {
591
+ result = processor.process(result);
592
+ }
593
+ return result;
594
+ }
595
+ }
596
+ function createDefaultPreprocessor() {
597
+ return new CompositePreprocessor([
598
+ new ContainerBlockPreprocessor()
599
+ ]);
600
+ }
601
+ class ContainerBlockPostprocessor {
602
+ constructor() {
603
+ this.name = "container-blocks";
604
+ }
605
+ process(tokens) {
606
+ const result = [];
607
+ const stack = [];
608
+ for (const token of tokens) {
609
+ const t = token;
610
+ if (t.type === "html") {
611
+ const raw = t.raw.trim();
612
+ const openMatch = raw.match(/^<!--\s*md-container:\s*(\S+)\s*-->$/);
613
+ const closeMatch = raw.match(/^<!--\s*\/md-container\s*-->$/);
614
+ if (openMatch) {
615
+ stack.push({
616
+ specifier: openMatch[1],
617
+ tokens: []
618
+ });
619
+ continue;
620
+ }
621
+ if (closeMatch) {
622
+ if (stack.length === 0) {
623
+ continue;
624
+ }
625
+ const container = stack.pop();
626
+ const processedInner = this.process(container.tokens);
627
+ const containerToken = {
628
+ type: "containerBlock",
629
+ specifier: container.specifier,
630
+ tokens: processedInner
631
+ };
632
+ if (stack.length > 0) {
633
+ stack[stack.length - 1].tokens.push(containerToken);
634
+ } else {
635
+ result.push(containerToken);
636
+ }
637
+ continue;
638
+ }
639
+ }
640
+ if (stack.length > 0) {
641
+ stack[stack.length - 1].tokens.push(token);
642
+ } else {
643
+ result.push(token);
644
+ }
645
+ }
646
+ return result;
647
+ }
648
+ }
649
+ class CompositeTokenPostprocessor {
650
+ constructor(processors) {
651
+ this.name = "composite";
652
+ this.processors = [];
653
+ if (processors) {
654
+ this.processors = [...processors];
655
+ }
656
+ }
657
+ /** Add a postprocessor to the chain. Returns `this` for fluent API. */
658
+ add(processor) {
659
+ this.processors.push(processor);
660
+ return this;
661
+ }
662
+ /** Remove a postprocessor by name. */
663
+ remove(name) {
664
+ this.processors = this.processors.filter((p) => p.name !== name);
665
+ }
666
+ /** Get the list of registered postprocessors. */
667
+ getProcessors() {
668
+ return [...this.processors];
669
+ }
670
+ process(tokens) {
671
+ let result = tokens;
672
+ for (const processor of this.processors) {
673
+ result = processor.process(result);
674
+ }
675
+ return result;
676
+ }
677
+ }
678
+ function createDefaultPostprocessor() {
679
+ return new CompositeTokenPostprocessor([
680
+ new ContainerBlockPostprocessor()
681
+ ]);
682
+ }
683
+ const DEFAULT_SLOT_PATTERN = /\[\[(.*?)\]\]/g;
684
+ class MarkdownParser {
685
+ constructor(options) {
686
+ this.imagePathPrefix = options?.imagePathPrefix || "";
687
+ this.imageBaseUrl = options?.imageBaseUrl || "";
688
+ this.preserveRawHTML = options?.preserveRawHTML ?? false;
689
+ this.slotPattern = options?.slotPattern ?? DEFAULT_SLOT_PATTERN;
690
+ this.onSlot = options?.onSlot;
691
+ this.log = options?.logger ?? getDefaultLogger("md2html");
692
+ this.errorRecovery = options?.errorRecovery ?? "throw";
693
+ this.maxRecursionDepth = options?.maxRecursionDepth ?? 100;
694
+ this.allowedHTMLTags = /* @__PURE__ */ new Set([
695
+ ...defaultAllowedHTMLTags,
696
+ ...options?.allowedHTMLTags ?? []
697
+ ]);
698
+ this.allowedAttributes = options?.allowedAttributes ?? {};
699
+ this.handlerRegistry = new TokenHandlerRegistry();
700
+ this.onUnhandledToken = options?.onUnhandledToken;
701
+ this.preprocessor = createDefaultPreprocessor();
702
+ this.postprocessor = createDefaultPostprocessor();
703
+ }
704
+ /** Access the handler registry for customization. */
705
+ get handlers() {
706
+ return this.handlerRegistry;
707
+ }
708
+ /** Access the preprocessor chain for customization. */
709
+ get preprocessors() {
710
+ return this.preprocessor;
711
+ }
712
+ /** Access the token postprocessor chain for customization. */
713
+ get postprocessors() {
714
+ return this.postprocessor;
715
+ }
716
+ processImagePath(src) {
717
+ if (src.startsWith("http") || src.startsWith("/")) {
718
+ return src;
719
+ }
720
+ let path = this.imagePathPrefix ? `${this.imagePathPrefix}${src}` : src;
721
+ if (this.imageBaseUrl && !path.startsWith("http")) {
722
+ path = `${this.imageBaseUrl}${path}`;
723
+ }
724
+ return path;
725
+ }
726
+ processInlineFormatting(text) {
727
+ return text.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>").replace(/\*(.+?)\*/g, "<em>$1</em>");
728
+ }
729
+ processSlots(text) {
730
+ if (!this.onSlot) return text;
731
+ return text.replace(this.slotPattern, (match, name) => {
732
+ return this.onSlot(name.trim());
733
+ });
734
+ }
735
+ /**
736
+ * Check if an attribute name is allowed for a given tag.
737
+ * Supports "data-*" wildcard prefix matching.
738
+ */
739
+ isAttributeAllowed(tagName, attrName) {
740
+ const globalAllowed = this.allowedAttributes["*"];
741
+ if (globalAllowed && this.matchesAttributeList(attrName, globalAllowed)) {
742
+ return true;
743
+ }
744
+ const tagAllowed = this.allowedAttributes[tagName];
745
+ if (tagAllowed && this.matchesAttributeList(attrName, tagAllowed)) {
746
+ return true;
747
+ }
748
+ return false;
749
+ }
750
+ /**
751
+ * Check if an attribute name matches a list of allowed patterns.
752
+ * Supports "data-*" wildcard prefix matching.
753
+ */
754
+ matchesAttributeList(attrName, allowed) {
755
+ const lower = attrName.toLowerCase();
756
+ for (const pattern of allowed) {
757
+ if (pattern.endsWith("-*")) {
758
+ const prefix = pattern.slice(0, -1).toLowerCase();
759
+ if (lower.startsWith(prefix)) return true;
760
+ } else if (pattern.toLowerCase() === lower) {
761
+ return true;
762
+ }
763
+ }
764
+ return false;
765
+ }
766
+ /**
767
+ * Filter attributes on an HTML tag, keeping only allowed ones.
768
+ */
769
+ filterTagAttributes(tag) {
770
+ const tagMatch = tag.match(/^<\/?(\w+)/);
771
+ if (!tagMatch) return tag;
772
+ const tagName = tagMatch[1].toLowerCase();
773
+ const hasRules = Object.keys(this.allowedAttributes).length > 0;
774
+ if (!hasRules) return tag;
775
+ const attrRegex = /(\S+)\s*=\s*(?:"([^"]*)"|'([^']*)'|(\S+))/g;
776
+ const selfClosing = tag.endsWith("/>");
777
+ const tagOpen = tagMatch[0];
778
+ const remaining = tag.slice(tagOpen.length);
779
+ let filtered = tagOpen;
780
+ let match;
781
+ while ((match = attrRegex.exec(remaining)) !== null) {
782
+ const attrName = match[1];
783
+ const attrValue = match[2] ?? match[3] ?? match[4] ?? "";
784
+ if (this.isAttributeAllowed(tagName, attrName)) {
785
+ filtered += ` ${attrName}="${attrValue.replace(/"/g, '"')}"`;
786
+ }
787
+ }
788
+ if (selfClosing) {
789
+ filtered += " /";
790
+ }
791
+ filtered += ">";
792
+ return filtered;
793
+ }
794
+ processRawHTML(html) {
795
+ if (!this.allowedHTMLTags.has("script")) {
796
+ html = html.replace(/<script[\s\S]*?<\/script>/gi, "");
797
+ html = html.replace(/<\/?script[^>]*>/gi, "");
798
+ }
799
+ if (!this.allowedHTMLTags.has("style")) {
800
+ html = html.replace(/<style[\s\S]*?<\/style>/gi, "");
801
+ html = html.replace(/<\/?style[^>]*>/gi, "");
802
+ }
803
+ const hasAttrRules = Object.keys(this.allowedAttributes).length > 0;
804
+ if (hasAttrRules) {
805
+ html = html.replace(/<[^>]+>/g, (match) => this.filterTagAttributes(match));
806
+ }
807
+ return html;
808
+ }
809
+ /**
810
+ * Build a ParserServices object that bridges the parser's private methods
811
+ * to the ParseContext factory. This keeps context creation decoupled.
812
+ */
813
+ buildServices() {
814
+ return {
815
+ preserveRawHTML: this.preserveRawHTML,
816
+ errorRecovery: this.errorRecovery,
817
+ maxRecursionDepth: this.maxRecursionDepth,
818
+ processImagePath: (src) => this.processImagePath(src),
819
+ processInlineFormatting: (text) => this.processInlineFormatting(text),
820
+ processSlots: (text) => this.processSlots(text),
821
+ processRawHTML: (html) => this.processRawHTML(html),
822
+ parseTokens: (tokens, depth) => this.parseTokens(tokens, depth),
823
+ onUnhandledToken: this.onUnhandledToken
824
+ };
825
+ }
826
+ /**
827
+ * Process an array of marked tokens into ContentNodes.
828
+ * When depth === 0 (root), creates a shared context that accumulates metadata.
829
+ * For recursive calls (depth > 0), creates a fresh context for each level.
830
+ */
831
+ parseTokens(tokens, depth = 0, sharedCtx) {
832
+ if (depth > this.maxRecursionDepth) {
833
+ const msg = `[md2html] Max recursion depth (${this.maxRecursionDepth}) exceeded, truncating`;
834
+ if (this.errorRecovery === "warn") {
835
+ this.log.warn(msg);
836
+ }
837
+ return [];
838
+ }
839
+ const nodes = [];
840
+ const ctx = sharedCtx || createParseContext(this.buildServices());
841
+ for (const token of tokens) {
842
+ const typedToken = token;
843
+ const handler = this.handlerRegistry.get(typedToken.type);
844
+ const node = handler.handle(typedToken, ctx);
845
+ if (node) {
846
+ nodes.push(node);
847
+ }
848
+ }
849
+ return nodes;
850
+ }
851
+ parse(markdown, options) {
852
+ const parseOptions = {
853
+ gfm: options?.gfm ?? true,
854
+ breaks: options?.breaks ?? false,
855
+ pedantic: options?.pedantic ?? false
856
+ };
857
+ try {
858
+ const processed = this.preprocessor.process(markdown);
859
+ const rawTokens = marked.lexer(processed, parseOptions);
860
+ const tokens = this.postprocessor.process(rawTokens);
861
+ const ctx = createParseContext(this.buildServices());
862
+ const content = this.parseTokens(tokens, 0, ctx);
863
+ return {
864
+ title: "",
865
+ metadata: { ...ctx.metadata },
866
+ content
867
+ };
868
+ } catch (err) {
869
+ if (this.errorRecovery === "throw") throw err;
870
+ const msg = `[md2html] Parse error: ${err instanceof Error ? err.message : String(err)}`;
871
+ if (this.errorRecovery === "warn") {
872
+ this.log.warn(msg);
873
+ }
874
+ return {
875
+ title: "",
876
+ content: [{ type: "text", content: markdown }]
877
+ };
878
+ }
879
+ }
880
+ parseToNodes(markdown, options) {
881
+ return this.parse(markdown, options).content;
882
+ }
883
+ }
884
+ class MarkdownPipeline {
885
+ constructor(config = {}) {
886
+ this.log = config.logger ?? getDefaultLogger("md2html");
887
+ this.config = {
888
+ imagePathPrefix: config.imagePathPrefix || "",
889
+ imageBaseUrl: config.imageBaseUrl || "",
890
+ parseOptions: {
891
+ gfm: config.parseOptions?.gfm ?? true,
892
+ breaks: config.parseOptions?.breaks ?? false,
893
+ pedantic: config.parseOptions?.pedantic ?? false
894
+ },
895
+ styleOptions: {
896
+ classPrefix: config.styleOptions?.classPrefix || "",
897
+ customCSS: config.styleOptions?.customCSS || "",
898
+ addHeadingIds: config.styleOptions?.addHeadingIds ?? false,
899
+ emitScopeAnchors: config.styleOptions?.emitScopeAnchors ?? false
900
+ },
901
+ preserveRawHTML: config.preserveRawHTML ?? false,
902
+ slotPattern: config.slotPattern ?? /\[\[(.*?)\]\]/g,
903
+ onSlot: config.onSlot,
904
+ errorRecovery: config.errorRecovery ?? "throw",
905
+ maxRecursionDepth: config.maxRecursionDepth ?? 100,
906
+ allowedHTMLTags: config.allowedHTMLTags ?? [],
907
+ allowedAttributes: config.allowedAttributes ?? {}
908
+ };
909
+ this.parser = new MarkdownParser({
910
+ imagePathPrefix: this.config.imagePathPrefix,
911
+ imageBaseUrl: this.config.imageBaseUrl,
912
+ preserveRawHTML: this.config.preserveRawHTML,
913
+ slotPattern: this.config.slotPattern,
914
+ onSlot: this.config.onSlot,
915
+ errorRecovery: this.config.errorRecovery,
916
+ maxRecursionDepth: this.config.maxRecursionDepth,
917
+ allowedHTMLTags: this.config.allowedHTMLTags,
918
+ allowedAttributes: this.config.allowedAttributes
919
+ });
920
+ this.renderer = new HTMLRenderer(this.config.styleOptions);
921
+ }
922
+ parse(markdown) {
923
+ return this.parser.parseToNodes(markdown, this.config.parseOptions);
924
+ }
925
+ parseWithMetadata(markdown) {
926
+ return this.parser.parse(markdown, this.config.parseOptions);
927
+ }
928
+ render(nodes) {
929
+ return this.renderer.renderNodes(nodes);
930
+ }
931
+ renderMarkdown(markdown) {
932
+ try {
933
+ const nodes = this.parse(markdown);
934
+ return this.render(nodes);
935
+ } catch (err) {
936
+ this.log.error("Markdown render failed", err, {
937
+ length: markdown.length,
938
+ recovery: this.config.errorRecovery
939
+ });
940
+ throw err;
941
+ }
942
+ }
943
+ renderPage(title, nodes, options) {
944
+ const html = this.render(nodes);
945
+ return `<!DOCTYPE html>
946
+ <html lang="${options?.lang || "en"}">
947
+ <head>
948
+ <meta charset="${options?.charset || "UTF-8"}">
949
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
950
+ <title>${title}</title>
951
+ </head>
952
+ <body>
953
+ ${html}
954
+ </body>
955
+ </html>`;
956
+ }
957
+ getConfig() {
958
+ return { ...this.config };
959
+ }
960
+ getCustomCSS() {
961
+ return this.renderer.getCustomCSS();
962
+ }
963
+ }
964
+ function walkTree(root, visitor) {
965
+ return walkNode(root, null, 0, visitor);
966
+ }
967
+ function walkNode(node, parent, depth, visitor) {
968
+ let processed = visitor.enter ? visitor.enter(node, parent, depth) : node;
969
+ if (processed === null) return null;
970
+ if (processed.children && processed.children.length > 0) {
971
+ processed = {
972
+ ...processed,
973
+ children: processed.children.map((child) => walkNode(child, processed, depth + 1, visitor)).filter(Boolean)
974
+ };
975
+ }
976
+ processed = visitor.exit ? visitor.exit(processed, parent, depth) : processed;
977
+ if (processed === null) return null;
978
+ return processed;
979
+ }
980
+ function collectFromTree(root, collector) {
981
+ const results = [];
982
+ collectNode(root, null, 0, collector, results);
983
+ return results;
984
+ }
985
+ function collectNode(node, parent, depth, collector, results) {
986
+ const result = collector(node, parent, depth);
987
+ if (result !== null) {
988
+ results.push(result);
989
+ }
990
+ if (node.children) {
991
+ for (const child of node.children) {
992
+ collectNode(child, node, depth + 1, collector, results);
993
+ }
994
+ }
995
+ }
996
+ function collectByType(root, type) {
997
+ return collectFromTree(
998
+ root,
999
+ (node) => node.type === type ? node : null
1000
+ );
1001
+ }
1002
+ function collectHeadings(root) {
1003
+ return collectFromTree(root, (node) => {
1004
+ if (node.type === "heading") {
1005
+ return {
1006
+ level: node.attributes?.level || "2",
1007
+ text: node.content || "",
1008
+ id: node.attributes?.id
1009
+ };
1010
+ }
1011
+ return null;
1012
+ });
1013
+ }
1014
+ function collectImages(root) {
1015
+ return collectFromTree(root, (node) => {
1016
+ if (node.type === "image") {
1017
+ return { src: node.src || "", alt: node.alt || "" };
1018
+ }
1019
+ return null;
1020
+ });
1021
+ }
1022
+ class NodeFactory {
1023
+ static heading(content, attributes) {
1024
+ return {
1025
+ type: "heading",
1026
+ content,
1027
+ attributes: { level: attributes?.level || "2", ...attributes }
1028
+ };
1029
+ }
1030
+ static paragraph(contentOrChildren, children) {
1031
+ if (typeof contentOrChildren === "string") {
1032
+ return { type: "paragraph", content: contentOrChildren };
1033
+ }
1034
+ return { type: "paragraph", children: contentOrChildren ?? children };
1035
+ }
1036
+ static list(items, ordered, attributes) {
1037
+ return {
1038
+ type: "list",
1039
+ ordered: ordered ?? false,
1040
+ children: items,
1041
+ ...attributes ? { attributes } : {}
1042
+ };
1043
+ }
1044
+ static listItem(content) {
1045
+ return { type: "list-item", content };
1046
+ }
1047
+ static image(src, alt, className) {
1048
+ return { type: "image", src, alt: alt || "", ...className ? { className } : {} };
1049
+ }
1050
+ static code(content, lang) {
1051
+ return {
1052
+ type: "code",
1053
+ content,
1054
+ attributes: { lang: lang || "" }
1055
+ };
1056
+ }
1057
+ static container(children, config) {
1058
+ const node = { type: "container" };
1059
+ if (children && children.length > 0) node.children = children;
1060
+ if (config?.rawHTML) node.rawHTML = config.rawHTML;
1061
+ if (config?.scope) node.scope = config.scope;
1062
+ const attrs = {};
1063
+ if (config?.tag) attrs.tag = config.tag;
1064
+ if (config?.id) attrs.id = config.id;
1065
+ if (Object.keys(attrs).length > 0) node.attributes = attrs;
1066
+ if (config?.className) node.className = config.className;
1067
+ return node;
1068
+ }
1069
+ static text(content) {
1070
+ return { type: "text", content };
1071
+ }
1072
+ static strong(content) {
1073
+ return { type: "strong", content };
1074
+ }
1075
+ static emphasis(content) {
1076
+ return { type: "emphasis", content };
1077
+ }
1078
+ static link(href, content) {
1079
+ return { type: "link", content, attributes: { href } };
1080
+ }
1081
+ }
1082
+ export {
1083
+ BlockquoteHandler,
1084
+ CatchAllHandler,
1085
+ CodeHandler,
1086
+ CompositePreprocessor,
1087
+ CompositeTokenPostprocessor,
1088
+ ContainerBlockHandler,
1089
+ ContainerBlockPostprocessor,
1090
+ ContainerBlockPreprocessor,
1091
+ FrontmatterHandler,
1092
+ HTMLRenderer,
1093
+ HeadingHandler,
1094
+ HrHandler,
1095
+ HtmlHandler,
1096
+ ImageHandler,
1097
+ LinkHandler,
1098
+ ListHandler,
1099
+ MarkdownParser,
1100
+ MarkdownPipeline,
1101
+ NodeFactory,
1102
+ ParagraphHandler,
1103
+ R as RendererStrategyRegistry,
1104
+ TokenHandlerRegistry,
1105
+ collectByType,
1106
+ collectFromTree,
1107
+ collectHeadings,
1108
+ collectImages,
1109
+ createDefaultPostprocessor,
1110
+ createDefaultPreprocessor,
1111
+ createParseContext,
1112
+ defaultAllowedHTMLTags,
1113
+ n as nodeTypeToScope,
1114
+ walkTree
1115
+ };
1116
+ //# sourceMappingURL=index.js.map