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

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,41 +1,6 @@
1
+ import { d as defaultAllowedHTMLTags, H as HTMLRenderer } from "./lit-renderer-Bp1Q6wYL.js";
2
+ import { L, a, b, c, e, f, g, h, i, j, k, l, m, n, R, o } from "./lit-renderer-Bp1Q6wYL.js";
1
3
  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
4
  class HeadingHandler {
40
5
  constructor() {
41
6
  this.type = "heading";
@@ -55,8 +20,9 @@ class ParagraphHandler {
55
20
  handle(token, ctx) {
56
21
  const tokens = token.tokens || [];
57
22
  const hasInlineImage = tokens.some((t) => t.type === "image");
23
+ const hasInlineLink = tokens.some((t) => t.type === "link");
58
24
  const hasInlineHTML = tokens.some((t) => t.type === "html");
59
- if (hasInlineImage || ctx.preserveRawHTML && hasInlineHTML) {
25
+ if (hasInlineImage || hasInlineLink || ctx.preserveRawHTML && hasInlineHTML) {
60
26
  const children = tokens.map((t) => {
61
27
  if (t.type === "image") {
62
28
  return {
@@ -65,6 +31,16 @@ class ParagraphHandler {
65
31
  alt: t.text || ""
66
32
  };
67
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
+ }
68
44
  if (t.type === "html" && ctx.preserveRawHTML) {
69
45
  const processed = ctx.processRawHTML(t.raw);
70
46
  if (processed.trim()) {
@@ -161,6 +137,24 @@ class HtmlHandler {
161
137
  return { type: "container", content: token.raw };
162
138
  }
163
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
+ }
164
158
  class CatchAllHandler {
165
159
  constructor() {
166
160
  this.type = "*";
@@ -239,7 +233,7 @@ class ContainerBlockHandler {
239
233
  const classMatches = [...specifier.matchAll(/\.([\w-]+)/g)];
240
234
  const tag = tagMatch?.[1] || "div";
241
235
  const id = idMatch?.[1] || "";
242
- const classes = classMatches.map((m) => m[1]);
236
+ const classes = classMatches.map((m2) => m2[1]);
243
237
  const children = ctx.parseTokens(childTokens, 0);
244
238
  return {
245
239
  type: "container",
@@ -265,6 +259,7 @@ class TokenHandlerRegistry {
265
259
  this.register(new HtmlHandler());
266
260
  this.register(new FrontmatterHandler());
267
261
  this.register(new ContainerBlockHandler());
262
+ this.register(new LinkHandler());
268
263
  this.catchAll = new CatchAllHandler();
269
264
  }
270
265
  /** Register a handler. Overrides any existing handler for the same token type. */
@@ -299,6 +294,392 @@ class TokenHandlerRegistry {
299
294
  return this.catchAll;
300
295
  }
301
296
  }
297
+ var h2 = /* @__PURE__ */ ((t) => (t[t.DEBUG = 1] = "DEBUG", t[t.INFO = 9] = "INFO", t[t.WARN = 13] = "WARN", t[t.ERROR = 17] = "ERROR", t))(h2 || {});
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 e2 = new Error().stack;
310
+ if (!e2) return;
311
+ const s = e2.split(`
312
+ `);
313
+ for (let r = 1; r < s.length; r++) {
314
+ const o2 = s[r].trim(), n2 = $(o2);
315
+ if (!n2) continue;
316
+ const { file: l2 } = n2;
317
+ if (!(l2 && E.some((m2) => l2.includes(m2))) && !(l2 && l2.includes("/node_modules/")) && l2)
318
+ return n2;
319
+ }
320
+ }
321
+ function $(t) {
322
+ const e2 = t.match(
323
+ /at\s+(?:(?:async\s+)?(?:(.+?)\s+\()?)?(?:(.+?):(\d+):(\d+)\)?)$/
324
+ );
325
+ if (!e2) return null;
326
+ const s = e2[1] || "<anonymous>", r = e2[2], o2 = parseInt(e2[3], 10), n2 = parseInt(e2[4], 10);
327
+ return r ? { file: r, line: o2, column: n2, functionName: s } : null;
328
+ }
329
+ class g2 {
330
+ constructor(e2 = [], s, r = {}) {
331
+ this.processors = [...e2], this.resource = s ?? { serviceName: "unknown" }, this.baseAttributes = { ...r };
332
+ }
333
+ // ── Public API ───────────────────────────────────────────────────────────
334
+ debug(e2, s) {
335
+ this.emit(h2.DEBUG, e2, void 0, s);
336
+ }
337
+ info(e2, s) {
338
+ this.emit(h2.INFO, e2, void 0, s);
339
+ }
340
+ warn(e2, s) {
341
+ this.emit(h2.WARN, e2, void 0, s);
342
+ }
343
+ error(e2, s, r) {
344
+ this.emit(h2.ERROR, e2, s, r);
345
+ }
346
+ /**
347
+ * Create a child logger with merged base attributes.
348
+ * Returns a NEW Logger — original is immutable.
349
+ */
350
+ withContext(e2) {
351
+ return new g2(this.processors, this.resource, {
352
+ ...this.baseAttributes,
353
+ ...e2
354
+ });
355
+ }
356
+ /**
357
+ * Append a processor to the pipeline.
358
+ * Returns a NEW Logger — original is immutable.
359
+ */
360
+ withProcessor(e2) {
361
+ return new g2(
362
+ [...this.processors, e2],
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((e2) => e2.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((e2) => e2.shutdown()));
379
+ }
380
+ // ── Internal ────────────────────────────────────────────────────────────
381
+ emit(e2, s, r, o2) {
382
+ if (this.processors.length === 0) return;
383
+ const n2 = (/* @__PURE__ */ new Date()).toISOString(), l2 = {
384
+ severityNumber: e2,
385
+ severityText: S[e2],
386
+ body: s,
387
+ timestamp: n2,
388
+ observedTimestamp: n2,
389
+ attributes: { ...this.baseAttributes, ...o2 },
390
+ caller: N(),
391
+ ...r ? { error: r } : {},
392
+ resource: { ...this.resource }
393
+ };
394
+ for (const m2 of this.processors)
395
+ try {
396
+ m2.onEmit(l2);
397
+ } catch (f2) {
398
+ console.error(
399
+ "[telemetry] Processor threw in onEmit:",
400
+ f2
401
+ );
402
+ }
403
+ }
404
+ }
405
+ class T {
406
+ constructor(e2) {
407
+ this.shutdownFlag = false, this.pendingExport = Promise.resolve(), this.adapter = e2;
408
+ }
409
+ onEmit(e2) {
410
+ this.shutdownFlag || (this.pendingExport = this.pendingExport.then(() => this.adapter.export([e2])).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(e2) {
426
+ this.processors = [], this.resource = {
427
+ serviceName: e2?.serviceName ?? "unknown",
428
+ environment: e2?.environment,
429
+ version: e2?.version,
430
+ processName: e2?.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(e2) {
438
+ return this.addProcessor(new T(e2));
439
+ }
440
+ /**
441
+ * Register a custom processor (SimpleLogProcessor, BatchLogProcessor,
442
+ * or your own implementation).
443
+ */
444
+ addProcessor(e2) {
445
+ return this.processors.push(e2), 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(e2, s) {
453
+ const r = {
454
+ ...this.resource,
455
+ ...e2 ? { processName: e2 } : {}
456
+ };
457
+ return new g2(this.processors, r, s);
458
+ }
459
+ /**
460
+ * Force-flush all registered processors.
461
+ */
462
+ async flush() {
463
+ await Promise.all(this.processors.map((e2) => e2.forceFlush()));
464
+ }
465
+ /**
466
+ * Shutdown: flush + release all processor resources.
467
+ */
468
+ async shutdown() {
469
+ await this.flush(), await Promise.all(this.processors.map((e2) => e2.shutdown()));
470
+ }
471
+ }
472
+ function U(t) {
473
+ const e2 = t?.level ?? h2.DEBUG, s = t?.json ?? false;
474
+ return {
475
+ name: "console",
476
+ async export(r) {
477
+ for (const o2 of r)
478
+ o2.severityNumber < e2 || (s ? I(o2) : R2(o2));
479
+ }
480
+ };
481
+ }
482
+ function R2(t) {
483
+ const e2 = F(t.severityNumber), s = t.timestamp, r = t.severityText, o2 = t.caller ? ` (${t.caller.file}:${t.caller.line})` : "", n2 = Object.keys(t.attributes).length > 0;
484
+ !!t.error ? e2(
485
+ `[${s}] [${r}]${o2} ${t.body}`,
486
+ t.attributes,
487
+ t.error
488
+ ) : n2 ? e2(`[${s}] [${r}]${o2} ${t.body}`, t.attributes) : e2(`[${s}] [${r}]${o2} ${t.body}`);
489
+ }
490
+ function I(t) {
491
+ const e2 = {
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 && (e2.error = {
501
+ name: t.error.name,
502
+ message: t.error.message,
503
+ stack: t.error.stack
504
+ }), console.log(JSON.stringify(e2));
505
+ }
506
+ function F(t) {
507
+ switch (t) {
508
+ case h2.ERROR:
509
+ return console.error.bind(console);
510
+ case h2.WARN:
511
+ return console.warn.bind(console);
512
+ case h2.INFO:
513
+ return console.log.bind(console);
514
+ case h2.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: h2.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
+ }
302
683
  const DEFAULT_SLOT_PATTERN = /\[\[(.*?)\]\]/g;
303
684
  class MarkdownParser {
304
685
  constructor(options) {
@@ -307,6 +688,7 @@ class MarkdownParser {
307
688
  this.preserveRawHTML = options?.preserveRawHTML ?? false;
308
689
  this.slotPattern = options?.slotPattern ?? DEFAULT_SLOT_PATTERN;
309
690
  this.onSlot = options?.onSlot;
691
+ this.log = options?.logger ?? getDefaultLogger("md2html");
310
692
  this.errorRecovery = options?.errorRecovery ?? "throw";
311
693
  this.maxRecursionDepth = options?.maxRecursionDepth ?? 100;
312
694
  this.allowedHTMLTags = /* @__PURE__ */ new Set([
@@ -316,11 +698,21 @@ class MarkdownParser {
316
698
  this.allowedAttributes = options?.allowedAttributes ?? {};
317
699
  this.handlerRegistry = new TokenHandlerRegistry();
318
700
  this.onUnhandledToken = options?.onUnhandledToken;
701
+ this.preprocessor = createDefaultPreprocessor();
702
+ this.postprocessor = createDefaultPostprocessor();
319
703
  }
320
704
  /** Access the handler registry for customization. */
321
705
  get handlers() {
322
706
  return this.handlerRegistry;
323
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
+ }
324
716
  processImagePath(src) {
325
717
  if (src.startsWith("http") || src.startsWith("/")) {
326
718
  return src;
@@ -415,31 +807,20 @@ class MarkdownParser {
415
807
  return html;
416
808
  }
417
809
  /**
418
- * Build the ParseContext that is passed to every token handler.
419
- * This is the bridge between the parser's private services and the handlers.
810
+ * Build a ParserServices object that bridges the parser's private methods
811
+ * to the ParseContext factory. This keeps context creation decoupled.
420
812
  */
421
- createContext() {
422
- const self = this;
423
- const metadata = {};
813
+ buildServices() {
424
814
  return {
425
- get preserveRawHTML() {
426
- return self.preserveRawHTML;
427
- },
428
- get errorRecovery() {
429
- return self.errorRecovery;
430
- },
431
- get maxRecursionDepth() {
432
- return self.maxRecursionDepth;
433
- },
434
- processImagePath: (src) => self.processImagePath(src),
435
- processInlineFormatting: (text) => self.processInlineFormatting(text),
436
- processSlots: (text) => self.processSlots(text),
437
- processRawHTML: (html) => self.processRawHTML(html),
438
- parseTokens: (tokens, depth) => self.parseTokens(tokens, depth),
439
- reportUnhandled: (type, token) => {
440
- self.onUnhandledToken?.(type, token);
441
- },
442
- metadata
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
443
824
  };
444
825
  }
445
826
  /**
@@ -451,12 +832,12 @@ class MarkdownParser {
451
832
  if (depth > this.maxRecursionDepth) {
452
833
  const msg = `[md2html] Max recursion depth (${this.maxRecursionDepth}) exceeded, truncating`;
453
834
  if (this.errorRecovery === "warn") {
454
- console.warn(msg);
835
+ this.log.warn(msg);
455
836
  }
456
837
  return [];
457
838
  }
458
839
  const nodes = [];
459
- const ctx = sharedCtx || this.createContext();
840
+ const ctx = sharedCtx || createParseContext(this.buildServices());
460
841
  for (const token of tokens) {
461
842
  const typedToken = token;
462
843
  const handler = this.handlerRegistry.get(typedToken.type);
@@ -467,82 +848,6 @@ class MarkdownParser {
467
848
  }
468
849
  return nodes;
469
850
  }
470
- /**
471
- * Pre-process markdown: convert `:::tag#id.class` container syntax
472
- * into HTML comment markers that marked will preserve as html tokens,
473
- * but won't affect markdown parsing of the inner content.
474
- *
475
- * Example:
476
- * :::section#header
477
- * # Heading inside container
478
- * Some text
479
- * :::
480
- *
481
- * Becomes:
482
- * <!-- md-container:section#header -->
483
- * # Heading inside container
484
- * Some text
485
- * <!-- /md-container -->
486
- */
487
- preprocessContainerBlocks(markdown) {
488
- return markdown.replace(/^:::(?:(\w+(?:[.#][\w-]+)*)\s*)?$/gm, (match, specifier) => {
489
- if (!specifier) {
490
- return "<!-- /md-container -->";
491
- }
492
- const normalized = specifier.match(/^\w/) ? specifier : `div${specifier}`;
493
- return `<!-- md-container:${normalized} -->`;
494
- });
495
- }
496
- /**
497
- * Post-process marked tokens to collapse container block markers
498
- * into structured containerBlock tokens with proper nesting.
499
- *
500
- * This handles nesting depth up to maxRecursionDepth.
501
- */
502
- postprocessTokens(tokens) {
503
- const result = [];
504
- const stack = [];
505
- for (const token of tokens) {
506
- const t = token;
507
- if (t.type === "html") {
508
- const raw = t.raw.trim();
509
- const openMatch = raw.match(/^<!--\s*md-container:\s*(\S+)\s*-->$/);
510
- const closeMatch = raw.match(/^<!--\s*\/md-container\s*-->$/);
511
- if (openMatch) {
512
- const newContainer = {
513
- specifier: openMatch[1],
514
- tokens: []
515
- };
516
- stack.push(newContainer);
517
- continue;
518
- }
519
- if (closeMatch) {
520
- if (stack.length === 0) {
521
- continue;
522
- }
523
- const container = stack.pop();
524
- const processedInner = this.postprocessTokens(container.tokens);
525
- const containerToken = {
526
- type: "containerBlock",
527
- specifier: container.specifier,
528
- tokens: processedInner
529
- };
530
- if (stack.length > 0) {
531
- stack[stack.length - 1].tokens.push(containerToken);
532
- } else {
533
- result.push(containerToken);
534
- }
535
- continue;
536
- }
537
- }
538
- if (stack.length > 0) {
539
- stack[stack.length - 1].tokens.push(token);
540
- } else {
541
- result.push(token);
542
- }
543
- }
544
- return result;
545
- }
546
851
  parse(markdown, options) {
547
852
  const parseOptions = {
548
853
  gfm: options?.gfm ?? true,
@@ -550,10 +855,10 @@ class MarkdownParser {
550
855
  pedantic: options?.pedantic ?? false
551
856
  };
552
857
  try {
553
- const processed = this.preprocessContainerBlocks(markdown);
858
+ const processed = this.preprocessor.process(markdown);
554
859
  const rawTokens = marked.lexer(processed, parseOptions);
555
- const tokens = this.postprocessTokens(rawTokens);
556
- const ctx = this.createContext();
860
+ const tokens = this.postprocessor.process(rawTokens);
861
+ const ctx = createParseContext(this.buildServices());
557
862
  const content = this.parseTokens(tokens, 0, ctx);
558
863
  return {
559
864
  title: "",
@@ -564,7 +869,7 @@ class MarkdownParser {
564
869
  if (this.errorRecovery === "throw") throw err;
565
870
  const msg = `[md2html] Parse error: ${err instanceof Error ? err.message : String(err)}`;
566
871
  if (this.errorRecovery === "warn") {
567
- console.warn(msg);
872
+ this.log.warn(msg);
568
873
  }
569
874
  return {
570
875
  title: "",
@@ -576,296 +881,9 @@ class MarkdownParser {
576
881
  return this.parse(markdown, options).content;
577
882
  }
578
883
  }
579
- class HeadingRendererStrategy {
580
- constructor() {
581
- this.type = "heading";
582
- }
583
- render(node, _renderChild, ctx) {
584
- const level = node.attributes?.level || "2";
585
- const headingId = ctx.addHeadingIds ? ` id="${ctx.generateHeadingId(node.content)}"` : "";
586
- const scopeAttr = ctx.getScopeAttr(node);
587
- if (!ctx.hasClassConfig()) {
588
- return `<h${level}${headingId}${scopeAttr}>${node.content || ""}</h${level}>`;
589
- }
590
- const prefix = ctx.classPrefix;
591
- const levelClass = level === "1" ? "h1" : level === "2" ? "h2" : level === "3" ? "h3" : level === "4" ? "h4" : level === "5" ? "h5" : "h6";
592
- const headingClass = prefix ? `${prefix}${levelClass}` : levelClass;
593
- return `<h${level}${headingId}${scopeAttr} class="${headingClass}">${node.content || ""}</h${level}>`;
594
- }
595
- }
596
- class ParagraphRendererStrategy {
597
- constructor() {
598
- this.type = "paragraph";
599
- }
600
- render(node, renderChild, ctx) {
601
- const scopeAttr = ctx.getScopeAttr(node);
602
- if (node.children) {
603
- const childrenHtml = node.children.map(renderChild).join("");
604
- return ctx.hasClassConfig() && ctx.classPrefix ? `<p class="${ctx.classPrefix}paragraph"${scopeAttr}>${childrenHtml}</p>` : `<p${scopeAttr}>${childrenHtml}</p>`;
605
- }
606
- return ctx.hasClassConfig() && ctx.classPrefix ? `<p class="${ctx.classPrefix}paragraph"${scopeAttr}>${node.content || ""}</p>` : `<p${scopeAttr}>${node.content || ""}</p>`;
607
- }
608
- }
609
- class ListRendererStrategy {
610
- constructor() {
611
- this.type = "list";
612
- }
613
- render(node, renderChild, ctx) {
614
- const tag = node.ordered ? "ol" : "ul";
615
- const items = node.children?.map(renderChild).join("") || "";
616
- const scopeAttr = ctx.getScopeAttr(node);
617
- return ctx.hasClassConfig() && ctx.classPrefix ? `<${tag} class="${ctx.classPrefix}list"${scopeAttr}>${items}</${tag}>` : `<${tag}${scopeAttr}>${items}</${tag}>`;
618
- }
619
- }
620
- class ListItemRendererStrategy {
621
- constructor() {
622
- this.type = "list-item";
623
- }
624
- render(node, _renderChild, ctx) {
625
- const scopeAttr = ctx.getScopeAttr(node);
626
- return ctx.hasClassConfig() && ctx.classPrefix ? `<li class="${ctx.classPrefix}list-item"${scopeAttr}>${node.content || ""}</li>` : `<li${scopeAttr}>${node.content || ""}</li>`;
627
- }
628
- }
629
- class ImageRendererStrategy {
630
- constructor() {
631
- this.type = "image";
632
- }
633
- render(node, _renderChild, ctx) {
634
- const src = node.src || node.attributes?.src || "";
635
- const alt = node.alt || node.attributes?.alt || "";
636
- const scopeAttr = ctx.getScopeAttr(node);
637
- let classStr = "";
638
- if (ctx.hasClassConfig()) {
639
- const prefix = ctx.classPrefix;
640
- classStr = prefix ? `${prefix}image` : "image";
641
- if (node.className) classStr += ` ${node.className}`;
642
- return `<img src="${src}" alt="${alt}" class="${classStr}"${scopeAttr}>`;
643
- }
644
- if (node.className) {
645
- return `<img src="${src}" alt="${alt}" class="${node.className}"${scopeAttr}>`;
646
- }
647
- return `<img src="${src}" alt="${alt}"${scopeAttr}>`;
648
- }
649
- }
650
- class CodeRendererStrategy {
651
- constructor() {
652
- this.type = "code";
653
- }
654
- render(node, _renderChild, ctx) {
655
- const scopeAttr = ctx.getScopeAttr(node);
656
- const lang = node.attributes?.lang || "";
657
- if (ctx.hasClassConfig()) {
658
- const prefix = ctx.classPrefix;
659
- const codeClass = prefix ? `${prefix}code` : "code";
660
- return `<pre${scopeAttr}><code class="${codeClass} language-${lang}">${node.content || ""}</code></pre>`;
661
- }
662
- return `<pre${scopeAttr}><code class="language-${lang}">${node.content || ""}</code></pre>`;
663
- }
664
- }
665
- class ContainerRendererStrategy {
666
- constructor() {
667
- this.type = "container";
668
- }
669
- render(node, renderChild, ctx) {
670
- if (node.rawHTML) {
671
- return node.rawHTML;
672
- }
673
- const tag = node.attributes?.tag || "div";
674
- const children = node.children?.map(renderChild).join("") || "";
675
- const id = node.attributes?.id;
676
- const idAttr = id ? ` id="${id}"` : "";
677
- const scopeAttr = ctx.getScopeAttr(node);
678
- if (tag === "hr") return "<hr>";
679
- if (ctx.hasClassConfig()) {
680
- const containerClass = ctx.getContainerClass(tag);
681
- const prefix = ctx.classPrefix;
682
- if (prefix) {
683
- const classes2 = [prefix + (containerClass || "container")];
684
- if (node.className) classes2.push(node.className);
685
- return `<${tag} class="${classes2.join(" ")}"${idAttr}${scopeAttr}>${children}</${tag}>`;
686
- }
687
- const classes = [containerClass || "container"];
688
- if (node.className) classes.push(node.className);
689
- return `<${tag} class="${classes.join(" ")}"${idAttr}${scopeAttr}>${children}</${tag}>`;
690
- }
691
- if (node.className) {
692
- return `<${tag} class="${node.className}"${idAttr}${scopeAttr}>${children}</${tag}>`;
693
- }
694
- return `<${tag}${idAttr}${scopeAttr}>${children}</${tag}>`;
695
- }
696
- }
697
- class StrongRendererStrategy {
698
- constructor() {
699
- this.type = "strong";
700
- }
701
- render(node, _renderChild, ctx) {
702
- return `<strong${ctx.getScopeAttr(node)}>${node.content || ""}</strong>`;
703
- }
704
- }
705
- class EmphasisRendererStrategy {
706
- constructor() {
707
- this.type = "emphasis";
708
- }
709
- render(node, _renderChild, ctx) {
710
- return `<em${ctx.getScopeAttr(node)}>${node.content || ""}</em>`;
711
- }
712
- }
713
- class LinkRendererStrategy {
714
- constructor() {
715
- this.type = "link";
716
- }
717
- render(node, _renderChild, ctx) {
718
- const href = node.attributes?.href || "";
719
- return `<a href="${href}"${ctx.getScopeAttr(node)}>${node.content || ""}</a>`;
720
- }
721
- }
722
- class TextRendererStrategy {
723
- constructor() {
724
- this.type = "text";
725
- }
726
- render(node, _renderChild, _ctx) {
727
- return node.content || "";
728
- }
729
- }
730
- class RendererStrategyRegistry {
731
- constructor() {
732
- this.strategies = /* @__PURE__ */ new Map();
733
- this.register(new HeadingRendererStrategy());
734
- this.register(new ParagraphRendererStrategy());
735
- this.register(new ListRendererStrategy());
736
- this.register(new ListItemRendererStrategy());
737
- this.register(new ImageRendererStrategy());
738
- this.register(new CodeRendererStrategy());
739
- this.register(new ContainerRendererStrategy());
740
- this.register(new StrongRendererStrategy());
741
- this.register(new EmphasisRendererStrategy());
742
- this.register(new LinkRendererStrategy());
743
- this.register(new TextRendererStrategy());
744
- this.fallback = new TextRendererStrategy();
745
- }
746
- /** Register a strategy for a node type. Overrides any existing strategy. */
747
- register(strategy) {
748
- this.strategies.set(strategy.type, strategy);
749
- }
750
- /** Unregister a strategy by node type. */
751
- unregister(type) {
752
- this.strategies.delete(type);
753
- }
754
- /** Get a strategy for the given node type, falling back to catch-all. */
755
- get(type) {
756
- return this.strategies.get(type) ?? this.fallback;
757
- }
758
- /** Check if a dedicated strategy exists for the given node type. */
759
- has(type) {
760
- return this.strategies.has(type);
761
- }
762
- /** Get all registered dedicated strategy types. */
763
- get types() {
764
- return Array.from(this.strategies.keys());
765
- }
766
- /** Replace the fallback strategy. */
767
- setFallback(strategy) {
768
- this.fallback = strategy;
769
- }
770
- }
771
- class HTMLRenderer {
772
- constructor(config = {}) {
773
- this.config = {
774
- classPrefix: config.classPrefix || "",
775
- customCSS: config.customCSS || "",
776
- addHeadingIds: config.addHeadingIds ?? false,
777
- emitScopeAnchors: config.emitScopeAnchors ?? false
778
- };
779
- this.strategyRegistry = new RendererStrategyRegistry();
780
- }
781
- /** Access the strategy registry for customization. */
782
- get strategies() {
783
- return this.strategyRegistry;
784
- }
785
- hasClassConfig() {
786
- return this.config.classPrefix !== "" || this.config.addHeadingIds;
787
- }
788
- getClass(baseClass, nodeClass) {
789
- if (!this.hasClassConfig()) {
790
- return nodeClass || "";
791
- }
792
- const prefix = this.config.classPrefix;
793
- const classes = [prefix ? `${prefix}${baseClass}` : baseClass];
794
- if (nodeClass) classes.push(nodeClass);
795
- return classes.join(" ");
796
- }
797
- generateHeadingId(content) {
798
- if (!content) return "";
799
- return content.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
800
- }
801
- /**
802
- * Get the scope attribute string for a node type.
803
- * Returns empty string if emitScopeAnchors is disabled.
804
- */
805
- getScopeAttr(node) {
806
- if (!this.config.emitScopeAnchors) return "";
807
- const scopeValue = node.scope || nodeTypeToScope[node.type] || "container";
808
- return ` data-md-scope="${scopeValue}"`;
809
- }
810
- /**
811
- * Get the CSS class for a container's tag-based rendering.
812
- * Returns just the tag name since renderWithClass applies the prefix.
813
- */
814
- getContainerClass(tag) {
815
- if (!this.hasClassConfig()) return "";
816
- return tag;
817
- }
818
- buildRenderContext() {
819
- const self = this;
820
- return {
821
- get classPrefix() {
822
- return self.config.classPrefix;
823
- },
824
- get addHeadingIds() {
825
- return self.config.addHeadingIds;
826
- },
827
- get emitScopeAnchors() {
828
- return self.config.emitScopeAnchors;
829
- },
830
- get customCSS() {
831
- return self.config.customCSS;
832
- },
833
- hasClassConfig: () => self.hasClassConfig(),
834
- getClass: (baseClass, nodeClass) => self.getClass(baseClass, nodeClass),
835
- getScopeAttr: (node) => self.getScopeAttr(node),
836
- generateHeadingId: (content) => self.generateHeadingId(content),
837
- getContainerClass: (tag) => self.getContainerClass(tag)
838
- };
839
- }
840
- renderNode(node) {
841
- const ctx = this.buildRenderContext();
842
- const strategy = this.strategyRegistry.get(node.type);
843
- return strategy.render(node, (child) => this.renderNode(child), ctx);
844
- }
845
- renderNodes(nodes) {
846
- if (!nodes || nodes.length === 0) {
847
- return "";
848
- }
849
- if (this.config.emitScopeAnchors) {
850
- const inner = nodes.map((node) => this.renderNode(node)).join("\n");
851
- return `<div data-md-scope="root">
852
- ${inner}
853
- </div>`;
854
- }
855
- return nodes.map((node) => this.renderNode(node)).join("\n");
856
- }
857
- renderToHTMLString(nodes) {
858
- return this.renderNodes(nodes);
859
- }
860
- render(markdown) {
861
- return markdown;
862
- }
863
- getCustomCSS() {
864
- return this.config.customCSS;
865
- }
866
- }
867
884
  class MarkdownPipeline {
868
885
  constructor(config = {}) {
886
+ this.log = config.logger ?? getDefaultLogger("md2html");
869
887
  this.config = {
870
888
  imagePathPrefix: config.imagePathPrefix || "",
871
889
  imageBaseUrl: config.imageBaseUrl || "",
@@ -911,8 +929,16 @@ class MarkdownPipeline {
911
929
  return this.renderer.renderNodes(nodes);
912
930
  }
913
931
  renderMarkdown(markdown) {
914
- const nodes = this.parse(markdown);
915
- return this.render(nodes);
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
+ }
916
942
  }
917
943
  renderPage(title, nodes, options) {
918
944
  const html = this.render(nodes);
@@ -1057,26 +1083,48 @@ export {
1057
1083
  BlockquoteHandler,
1058
1084
  CatchAllHandler,
1059
1085
  CodeHandler,
1086
+ CompositePreprocessor,
1087
+ CompositeTokenPostprocessor,
1060
1088
  ContainerBlockHandler,
1089
+ ContainerBlockPostprocessor,
1090
+ ContainerBlockPreprocessor,
1061
1091
  FrontmatterHandler,
1062
1092
  HTMLRenderer,
1063
1093
  HeadingHandler,
1064
1094
  HrHandler,
1065
1095
  HtmlHandler,
1066
1096
  ImageHandler,
1097
+ LinkHandler,
1067
1098
  ListHandler,
1099
+ L as LitCodeStrategy,
1100
+ a as LitContainerStrategy,
1101
+ b as LitEmphasisStrategy,
1102
+ c as LitFallbackStrategy,
1103
+ e as LitHeadingStrategy,
1104
+ f as LitImageStrategy,
1105
+ g as LitLinkStrategy,
1106
+ h as LitListItemStrategy,
1107
+ i as LitListStrategy,
1108
+ j as LitParagraphStrategy,
1109
+ k as LitRenderer,
1110
+ l as LitStrategyRegistry,
1111
+ m as LitStrongStrategy,
1112
+ n as LitTextStrategy,
1068
1113
  MarkdownParser,
1069
1114
  MarkdownPipeline,
1070
1115
  NodeFactory,
1071
1116
  ParagraphHandler,
1072
- RendererStrategyRegistry,
1117
+ R as RendererStrategyRegistry,
1073
1118
  TokenHandlerRegistry,
1074
1119
  collectByType,
1075
1120
  collectFromTree,
1076
1121
  collectHeadings,
1077
1122
  collectImages,
1123
+ createDefaultPostprocessor,
1124
+ createDefaultPreprocessor,
1125
+ createParseContext,
1078
1126
  defaultAllowedHTMLTags,
1079
- nodeTypeToScope,
1127
+ o as nodeTypeToScope,
1080
1128
  walkTree
1081
1129
  };
1082
1130
  //# sourceMappingURL=index.js.map