@memberjunction/ng-markdown 5.44.0 → 5.45.1

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.
Files changed (46) hide show
  1. package/dist/__tests__/markdown.service.dom.test.d.ts +2 -0
  2. package/dist/__tests__/markdown.service.dom.test.d.ts.map +1 -0
  3. package/dist/__tests__/markdown.service.dom.test.js +86 -0
  4. package/dist/__tests__/markdown.service.dom.test.js.map +1 -0
  5. package/dist/__tests__/web-dom-utils.test.d.ts +2 -0
  6. package/dist/__tests__/web-dom-utils.test.d.ts.map +1 -0
  7. package/dist/__tests__/web-dom-utils.test.js +160 -0
  8. package/dist/__tests__/web-dom-utils.test.js.map +1 -0
  9. package/dist/lib/components/markdown.component.d.ts +1 -1
  10. package/dist/lib/components/markdown.component.d.ts.map +1 -1
  11. package/dist/lib/components/markdown.component.dom.test.js.map +1 -1
  12. package/dist/lib/components/markdown.component.js +1 -1
  13. package/dist/lib/components/markdown.component.js.map +1 -1
  14. package/dist/lib/extensions/code-copy.extension.d.ts +0 -7
  15. package/dist/lib/extensions/code-copy.extension.d.ts.map +1 -1
  16. package/dist/lib/extensions/code-copy.extension.js +1 -58
  17. package/dist/lib/extensions/code-copy.extension.js.map +1 -1
  18. package/dist/lib/extensions/collapsible-headings.extension.d.ts +5 -67
  19. package/dist/lib/extensions/collapsible-headings.extension.d.ts.map +1 -1
  20. package/dist/lib/extensions/collapsible-headings.extension.js +5 -179
  21. package/dist/lib/extensions/collapsible-headings.extension.js.map +1 -1
  22. package/dist/lib/extensions/svg-renderer.extension.d.ts +5 -28
  23. package/dist/lib/extensions/svg-renderer.extension.d.ts.map +1 -1
  24. package/dist/lib/extensions/svg-renderer.extension.js +5 -72
  25. package/dist/lib/extensions/svg-renderer.extension.js.map +1 -1
  26. package/dist/lib/services/markdown.service.d.ts +27 -43
  27. package/dist/lib/services/markdown.service.d.ts.map +1 -1
  28. package/dist/lib/services/markdown.service.js +56 -235
  29. package/dist/lib/services/markdown.service.js.map +1 -1
  30. package/dist/public-api.d.ts +0 -1
  31. package/dist/public-api.d.ts.map +1 -1
  32. package/dist/public-api.js +6 -3
  33. package/dist/public-api.js.map +1 -1
  34. package/package.json +4 -7
  35. package/dist/__tests__/markdown-types.test.d.ts +0 -2
  36. package/dist/__tests__/markdown-types.test.d.ts.map +0 -1
  37. package/dist/__tests__/markdown-types.test.js +0 -78
  38. package/dist/__tests__/markdown-types.test.js.map +0 -1
  39. package/dist/lib/extensions/html-block-repair.extension.d.ts +0 -20
  40. package/dist/lib/extensions/html-block-repair.extension.d.ts.map +0 -1
  41. package/dist/lib/extensions/html-block-repair.extension.js +0 -43
  42. package/dist/lib/extensions/html-block-repair.extension.js.map +0 -1
  43. package/dist/lib/types/markdown.types.d.ts +0 -194
  44. package/dist/lib/types/markdown.types.d.ts.map +0 -1
  45. package/dist/lib/types/markdown.types.js +0 -25
  46. package/dist/lib/types/markdown.types.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import { MarkdownConfig, HeadingInfo } from '../types/markdown.types';
1
+ import { MarkdownConfig, ResolvedMarkdownConfig, HeadingInfo } from '@memberjunction/markdown-core';
2
2
  import 'prismjs/components/prism-typescript';
3
3
  import 'prismjs/components/prism-javascript';
4
4
  import 'prismjs/components/prism-css';
@@ -14,37 +14,33 @@ import 'prismjs/components/prism-yaml';
14
14
  import 'prismjs/components/prism-markdown';
15
15
  import 'prismjs/components/prism-graphql';
16
16
  import * as i0 from "@angular/core";
17
- type ResolvedMarkdownConfig = Required<Omit<MarkdownConfig, 'autoExpandLevels'>> & {
18
- autoExpandLevels?: number[];
19
- };
20
17
  /**
21
- * Service for parsing and rendering markdown content.
22
- * Uses marked.js with various extensions for syntax highlighting,
23
- * diagrams, alerts, and more.
18
+ * Service for parsing and rendering markdown content on the web.
19
+ *
20
+ * The parsing/configuration work (marked setup, custom extensions, HTML and
21
+ * token output) is owned by the framework-agnostic `@memberjunction/markdown-core`
22
+ * {@link MarkdownEngine}. This Angular service is the web shell around it: it
23
+ * injects a Prism-based highlighter into the engine and keeps the browser-only
24
+ * concerns here — Mermaid rendering, copy buttons, and the DOM-based fixup of
25
+ * HTML that marked miscoded as a code block.
24
26
  */
25
27
  export declare class MarkdownService {
26
- private marked;
28
+ private engine;
27
29
  private mermaidInitialized;
28
30
  private lastMermaidTheme;
29
31
  private currentConfig;
30
- private headingList;
31
- constructor();
32
- /**
33
- * Configure the marked instance with the provided options
34
- */
35
- configureMarked(config: MarkdownConfig): void;
36
32
  /**
37
- * Resolve the effective mermaid theme.
38
- * 'auto' maps to 'dark' or 'default' based on the document's data-theme attribute.
33
+ * Prism-backed highlight function injected into the core engine. The engine
34
+ * itself has no Prism dependency it calls this for the HTML output path.
39
35
  */
40
- private resolveEffectiveMermaidTheme;
36
+ private prismHighlight;
37
+ constructor();
41
38
  /**
42
- * Initialize Mermaid with the current theme configuration.
43
- * Re-initializes when the effective theme changes.
39
+ * Configure the underlying engine with the provided options.
44
40
  */
45
- private initializeMermaid;
41
+ configureMarked(config: MarkdownConfig): void;
46
42
  /**
47
- * Parse markdown to HTML
43
+ * Parse markdown to HTML.
48
44
  * @param markdown The markdown string to parse
49
45
  * @param config Optional config overrides for this parse operation
50
46
  * @returns The rendered HTML string
@@ -54,6 +50,16 @@ export declare class MarkdownService {
54
50
  * Parse markdown asynchronously (useful for large documents)
55
51
  */
56
52
  parseAsync(markdown: string, config?: Partial<MarkdownConfig>): Promise<string>;
53
+ /**
54
+ * Resolve the effective mermaid theme.
55
+ * 'auto' maps to 'dark' or 'default' based on the document's data-theme attribute.
56
+ */
57
+ private resolveEffectiveMermaidTheme;
58
+ /**
59
+ * Initialize Mermaid with the current theme configuration.
60
+ * Re-initializes when the effective theme changes.
61
+ */
62
+ private initializeMermaid;
57
63
  /**
58
64
  * Render Mermaid diagrams in a container element
59
65
  * Call this after the HTML has been inserted into the DOM
@@ -98,10 +104,6 @@ export declare class MarkdownService {
98
104
  * Get list of supported Prism languages
99
105
  */
100
106
  getSupportedLanguages(): string[];
101
- /**
102
- * Escape HTML entities for safe display
103
- */
104
- private escapeHtml;
105
107
  /**
106
108
  * Fix HTML that was incorrectly wrapped in <pre><code> blocks by marked.
107
109
  * This happens when marked interprets inline HTML (especially indented HTML)
@@ -118,25 +120,7 @@ export declare class MarkdownService {
118
120
  * that indicate this is meant to be rendered HTML, not code.
119
121
  */
120
122
  private looksLikeStructuralHtml;
121
- /**
122
- * Normalize indentation in HTML blocks to prevent marked from treating
123
- * indented HTML as code blocks (4 spaces = code block in markdown).
124
- *
125
- * This finds HTML blocks (starting with common block-level tags) and
126
- * removes ALL leading whitespace from lines within those blocks to ensure
127
- * marked doesn't interpret any nested content as code blocks.
128
- */
129
- private normalizeHtmlBlockIndentation;
130
- /**
131
- * Check if a line contains a self-closing tag or opens and closes the same tag
132
- */
133
- private isSelfClosingLine;
134
- /**
135
- * Update the tag stack based on opening/closing tags in the line
136
- */
137
- private updateTagStack;
138
123
  static ɵfac: i0.ɵɵFactoryDeclaration<MarkdownService, never>;
139
124
  static ɵprov: i0.ɵɵInjectableDeclaration<MarkdownService>;
140
125
  }
141
- export {};
142
126
  //# sourceMappingURL=markdown.service.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"markdown.service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/markdown.service.ts"],"names":[],"mappings":"AAQA,OAAO,EACL,cAAc,EAEd,WAAW,EAEZ,MAAM,yBAAyB,CAAC;AAOjC,OAAO,qCAAqC,CAAC;AAC7C,OAAO,qCAAqC,CAAC;AAC7C,OAAO,8BAA8B,CAAC;AACtC,OAAO,+BAA+B,CAAC;AACvC,OAAO,+BAA+B,CAAC;AACvC,OAAO,+BAA+B,CAAC;AACvC,OAAO,8BAA8B,CAAC;AACtC,OAAO,iCAAiC,CAAC;AACzC,OAAO,iCAAiC,CAAC;AACzC,OAAO,+BAA+B,CAAC;AACvC,OAAO,iCAAiC,CAAC;AACzC,OAAO,+BAA+B,CAAC;AACvC,OAAO,mCAAmC,CAAC;AAC3C,OAAO,kCAAkC,CAAC;;AAG1C,KAAK,sBAAsB,GAAG,QAAQ,CAAC,IAAI,CAAC,cAAc,EAAE,kBAAkB,CAAC,CAAC,GAAG;IAAE,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAEnH;;;;GAIG;AACH,qBAGa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,kBAAkB,CAAS;IACnC,OAAO,CAAC,gBAAgB,CAAuB;IAC/C,OAAO,CAAC,aAAa,CAA0D;IAC/E,OAAO,CAAC,WAAW,CAAqB;;IAOxC;;OAEG;IACI,eAAe,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI;IAsFpD;;;OAGG;IACH,OAAO,CAAC,4BAA4B;IAUpC;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAmBzB;;;;;OAKG;IACI,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,MAAM;IAoCxE;;OAEG;IACU,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;IAI5F;;;;OAIG;IACU,aAAa,CAAC,SAAS,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC;IA0CpE;;;;OAIG;IACI,aAAa,CAAC,SAAS,EAAE,WAAW,GAAG,IAAI;IAOlD;;;OAGG;IACI,kBAAkB,CAAC,SAAS,EAAE,WAAW,GAAG,IAAI;IAiDvD;;;;OAIG;IACI,6BAA6B,CAAC,UAAU,EAAE,WAAW,GAAG,IAAI;IAKnE;;;OAGG;IACI,cAAc,IAAI,WAAW,EAAE;IAItC;;OAEG;IACI,SAAS,IAAI,sBAAsB;IAI1C;;OAEG;IACI,WAAW,IAAI,IAAI;IAI1B;;OAEG;IACI,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAIjD;;OAEG;IACI,qBAAqB,IAAI,MAAM,EAAE;IAMxC;;OAEG;IACH,OAAO,CAAC,UAAU;IAMlB;;;;;;;;OAQG;IACH,OAAO,CAAC,kBAAkB;IAsE1B;;;;OAIG;IACH,OAAO,CAAC,uBAAuB;IAyB/B;;;;;;;OAOG;IACH,OAAO,CAAC,6BAA6B;IA0DrC;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAWzB;;OAEG;IACH,OAAO,CAAC,cAAc;yCApiBX,eAAe;6CAAf,eAAe;CAskB3B"}
1
+ {"version":3,"file":"markdown.service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/markdown.service.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,cAAc,EAEd,sBAAsB,EACtB,WAAW,EAEZ,MAAM,+BAA+B,CAAC;AAIvC,OAAO,qCAAqC,CAAC;AAC7C,OAAO,qCAAqC,CAAC;AAC7C,OAAO,8BAA8B,CAAC;AACtC,OAAO,+BAA+B,CAAC;AACvC,OAAO,+BAA+B,CAAC;AACvC,OAAO,+BAA+B,CAAC;AACvC,OAAO,8BAA8B,CAAC;AACtC,OAAO,iCAAiC,CAAC;AACzC,OAAO,iCAAiC,CAAC;AACzC,OAAO,+BAA+B,CAAC;AACvC,OAAO,iCAAiC,CAAC;AACzC,OAAO,+BAA+B,CAAC;AACvC,OAAO,mCAAmC,CAAC;AAC3C,OAAO,kCAAkC,CAAC;;AAE1C;;;;;;;;;GASG;AACH,qBAGa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAAwB;IACtC,OAAO,CAAC,kBAAkB,CAAS;IACnC,OAAO,CAAC,gBAAgB,CAAuB;IAC/C,OAAO,CAAC,aAAa,CAA0D;IAE/E;;;OAGG;IACH,OAAO,CAAC,cAAc,CAUpB;;IAMF;;OAEG;IACI,eAAe,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI;IAKpD;;;;;OAKG;IACI,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,MAAM;IAoBxE;;OAEG;IACU,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;IAI5F;;;OAGG;IACH,OAAO,CAAC,4BAA4B;IAUpC;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAmBzB;;;;OAIG;IACU,aAAa,CAAC,SAAS,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC;IA0CpE;;;;OAIG;IACI,aAAa,CAAC,SAAS,EAAE,WAAW,GAAG,IAAI;IAOlD;;;OAGG;IACI,kBAAkB,CAAC,SAAS,EAAE,WAAW,GAAG,IAAI;IAiDvD;;;;OAIG;IACI,6BAA6B,CAAC,UAAU,EAAE,WAAW,GAAG,IAAI;IAKnE;;;OAGG;IACI,cAAc,IAAI,WAAW,EAAE;IAItC;;OAEG;IACI,SAAS,IAAI,sBAAsB;IAI1C;;OAEG;IACI,WAAW,IAAI,IAAI;IAI1B;;OAEG;IACI,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAIjD;;OAEG;IACI,qBAAqB,IAAI,MAAM,EAAE;IAMxC;;;;;;;;OAQG;IACH,OAAO,CAAC,kBAAkB;IAsE1B;;;;OAIG;IACH,OAAO,CAAC,uBAAuB;yCA5VpB,eAAe;6CAAf,eAAe;CAoX3B"}
@@ -1,15 +1,7 @@
1
1
  import { Injectable } from '@angular/core';
2
- import { Marked } from 'marked';
3
- import { markedHighlight } from 'marked-highlight';
4
- import { gfmHeadingId, getHeadingList } from 'marked-gfm-heading-id';
5
- import markedAlert from 'marked-alert';
6
- import { markedSmartypants } from 'marked-smartypants';
7
2
  import Prism from 'prismjs';
8
3
  import mermaid from 'mermaid';
9
- import { DEFAULT_MARKDOWN_CONFIG } from '../types/markdown.types';
10
- import { createCollapsibleHeadingsExtension } from '../extensions/collapsible-headings.extension';
11
- import { createSvgRendererExtension } from '../extensions/svg-renderer.extension';
12
- import { createHtmlBlockRepairExtension } from '../extensions/html-block-repair.extension';
4
+ import { MarkdownEngine, DEFAULT_MARKDOWN_CONFIG } from '@memberjunction/markdown-core';
13
5
  // Import common Prism language components
14
6
  // Additional languages can be imported by the consuming application
15
7
  import 'prismjs/components/prism-typescript';
@@ -28,90 +20,73 @@ import 'prismjs/components/prism-markdown';
28
20
  import 'prismjs/components/prism-graphql';
29
21
  import * as i0 from "@angular/core";
30
22
  /**
31
- * Service for parsing and rendering markdown content.
32
- * Uses marked.js with various extensions for syntax highlighting,
33
- * diagrams, alerts, and more.
23
+ * Service for parsing and rendering markdown content on the web.
24
+ *
25
+ * The parsing/configuration work (marked setup, custom extensions, HTML and
26
+ * token output) is owned by the framework-agnostic `@memberjunction/markdown-core`
27
+ * {@link MarkdownEngine}. This Angular service is the web shell around it: it
28
+ * injects a Prism-based highlighter into the engine and keeps the browser-only
29
+ * concerns here — Mermaid rendering, copy buttons, and the DOM-based fixup of
30
+ * HTML that marked miscoded as a code block.
34
31
  */
35
32
  export class MarkdownService {
36
33
  constructor() {
34
+ this.engine = new MarkdownEngine();
37
35
  this.mermaidInitialized = false;
38
36
  this.lastMermaidTheme = null;
39
37
  this.currentConfig = { ...DEFAULT_MARKDOWN_CONFIG };
40
- this.headingList = [];
41
- this.marked = new Marked();
38
+ /**
39
+ * Prism-backed highlight function injected into the core engine. The engine
40
+ * itself has no Prism dependency — it calls this for the HTML output path.
41
+ */
42
+ this.prismHighlight = (code, lang) => {
43
+ if (lang && Prism.languages[lang]) {
44
+ try {
45
+ return Prism.highlight(code, Prism.languages[lang], lang);
46
+ }
47
+ catch (e) {
48
+ console.warn(`Prism highlighting failed for language: ${lang}`, e);
49
+ }
50
+ }
51
+ // Return code as-is if language not found or highlighting fails
52
+ return code;
53
+ };
42
54
  this.configureMarked(this.currentConfig);
43
55
  }
44
56
  /**
45
- * Configure the marked instance with the provided options
57
+ * Configure the underlying engine with the provided options.
46
58
  */
47
59
  configureMarked(config) {
48
60
  this.currentConfig = { ...DEFAULT_MARKDOWN_CONFIG, ...config };
49
- // Create a fresh Marked instance
50
- this.marked = new Marked();
51
- // Configure base options
52
- this.marked.setOptions({
53
- gfm: true,
54
- breaks: true
55
- });
56
- // Apply extensions based on config
57
- const extensions = [];
58
- // Repair HTML blocks split by a blank line (e.g. PRD mockups) so embedded
59
- // raw HTML renders instead of showing as an escaped code block. Always on -
60
- // precisely scoped to misparsed HTML, leaves prose and fenced code untouched.
61
- extensions.push(createHtmlBlockRepairExtension());
62
- // SVG code block renderer - MUST be before syntax highlighting
63
- // so it can intercept svg blocks before Prism processes them
64
- if (this.currentConfig.enableSvgRenderer) {
65
- extensions.push(createSvgRendererExtension());
66
- }
67
- // Syntax highlighting with Prism
68
- if (this.currentConfig.enableHighlight) {
69
- extensions.push(markedHighlight({
70
- langPrefix: 'language-',
71
- highlight: (code, lang) => {
72
- // Skip SVG blocks - they're handled by the SVG renderer
73
- if (lang === 'svg' && this.currentConfig.enableSvgRenderer) {
74
- return code;
75
- }
76
- if (lang && Prism.languages[lang]) {
77
- try {
78
- return Prism.highlight(code, Prism.languages[lang], lang);
79
- }
80
- catch (e) {
81
- console.warn(`Prism highlighting failed for language: ${lang}`, e);
82
- }
83
- }
84
- // Return code as-is if language not found or highlighting fails
85
- return code;
86
- }
87
- }));
88
- }
89
- // GitHub-style heading IDs
90
- if (this.currentConfig.enableHeadingIds) {
91
- extensions.push(gfmHeadingId({
92
- prefix: this.currentConfig.headingIdPrefix
93
- }));
94
- }
95
- // GitHub-style alerts
96
- if (this.currentConfig.enableAlerts) {
97
- extensions.push(markedAlert());
98
- }
99
- // Collapsible headings (custom extension)
100
- if (this.currentConfig.enableCollapsibleHeadings) {
101
- extensions.push(createCollapsibleHeadingsExtension({
102
- startLevel: this.currentConfig.collapsibleHeadingLevel,
103
- defaultExpanded: this.currentConfig.collapsibleDefaultExpanded,
104
- autoExpandLevels: this.currentConfig.autoExpandLevels
105
- }));
106
- }
107
- // Smartypants for typography (curly quotes, em/en dashes, ellipses)
108
- if (this.currentConfig.enableSmartypants) {
109
- extensions.push(markedSmartypants());
61
+ this.engine.configureMarked(this.currentConfig, { highlightFn: this.prismHighlight });
62
+ }
63
+ /**
64
+ * Parse markdown to HTML.
65
+ * @param markdown The markdown string to parse
66
+ * @param config Optional config overrides for this parse operation
67
+ * @returns The rendered HTML string
68
+ */
69
+ parse(markdown, config) {
70
+ if (!markdown)
71
+ return '';
72
+ // Apply config overrides if provided (re-injects the Prism highlighter)
73
+ if (config) {
74
+ this.configureMarked({ ...this.currentConfig, ...config });
110
75
  }
111
- // Apply all extensions
112
- if (extensions.length > 0) {
113
- this.marked.use(...extensions);
76
+ let html = this.engine.parseToHtml(markdown);
77
+ // When HTML passthrough is enabled, fix incorrectly code-wrapped HTML.
78
+ // marked sometimes wraps inline HTML in <pre><code> blocks. This is a
79
+ // DOM-based fixup so it stays in the web service, not the core engine.
80
+ if (this.currentConfig.enableHtml) {
81
+ html = this.unwrapMiscodedHtml(html);
114
82
  }
83
+ return html;
84
+ }
85
+ /**
86
+ * Parse markdown asynchronously (useful for large documents)
87
+ */
88
+ async parseAsync(markdown, config) {
89
+ return this.parse(markdown, config);
115
90
  }
116
91
  /**
117
92
  * Resolve the effective mermaid theme.
@@ -145,49 +120,6 @@ export class MarkdownService {
145
120
  this.mermaidInitialized = true;
146
121
  this.lastMermaidTheme = effectiveTheme;
147
122
  }
148
- /**
149
- * Parse markdown to HTML
150
- * @param markdown The markdown string to parse
151
- * @param config Optional config overrides for this parse operation
152
- * @returns The rendered HTML string
153
- */
154
- parse(markdown, config) {
155
- if (!markdown)
156
- return '';
157
- // Apply config overrides if provided
158
- if (config) {
159
- this.configureMarked({ ...this.currentConfig, ...config });
160
- }
161
- try {
162
- // Preprocess markdown to fix indentation in HTML blocks
163
- // This prevents marked from treating indented HTML as code blocks
164
- let processedMarkdown = markdown;
165
- if (this.currentConfig.enableHtml) {
166
- processedMarkdown = this.normalizeHtmlBlockIndentation(markdown);
167
- }
168
- let html = this.marked.parse(processedMarkdown);
169
- // Capture heading list after parsing
170
- if (this.currentConfig.enableHeadingIds) {
171
- this.headingList = getHeadingList();
172
- }
173
- // When HTML passthrough is enabled, fix incorrectly code-wrapped HTML
174
- // marked sometimes wraps inline HTML in <pre><code> blocks
175
- if (this.currentConfig.enableHtml) {
176
- html = this.unwrapMiscodedHtml(html);
177
- }
178
- return html;
179
- }
180
- catch (error) {
181
- console.error('Markdown parsing error:', error);
182
- return `<pre class="markdown-error">${this.escapeHtml(markdown)}</pre>`;
183
- }
184
- }
185
- /**
186
- * Parse markdown asynchronously (useful for large documents)
187
- */
188
- async parseAsync(markdown, config) {
189
- return this.parse(markdown, config);
190
- }
191
123
  /**
192
124
  * Render Mermaid diagrams in a container element
193
125
  * Call this after the HTML has been inserted into the DOM
@@ -299,13 +231,13 @@ export class MarkdownService {
299
231
  * Useful for building table of contents
300
232
  */
301
233
  getHeadingList() {
302
- return this.headingList;
234
+ return this.engine.getHeadingList();
303
235
  }
304
236
  /**
305
237
  * Get the current configuration
306
238
  */
307
239
  getConfig() {
308
- return { ...this.currentConfig };
240
+ return this.engine.getConfig();
309
241
  }
310
242
  /**
311
243
  * Reset configuration to defaults
@@ -325,14 +257,6 @@ export class MarkdownService {
325
257
  getSupportedLanguages() {
326
258
  return Object.keys(Prism.languages).filter(lang => typeof Prism.languages[lang] === 'object');
327
259
  }
328
- /**
329
- * Escape HTML entities for safe display
330
- */
331
- escapeHtml(text) {
332
- const div = document.createElement('div');
333
- div.textContent = text;
334
- return div.innerHTML;
335
- }
336
260
  /**
337
261
  * Fix HTML that was incorrectly wrapped in <pre><code> blocks by marked.
338
262
  * This happens when marked interprets inline HTML (especially indented HTML)
@@ -431,109 +355,6 @@ export class MarkdownService {
431
355
  return false;
432
356
  return true;
433
357
  }
434
- /**
435
- * Normalize indentation in HTML blocks to prevent marked from treating
436
- * indented HTML as code blocks (4 spaces = code block in markdown).
437
- *
438
- * This finds HTML blocks (starting with common block-level tags) and
439
- * removes ALL leading whitespace from lines within those blocks to ensure
440
- * marked doesn't interpret any nested content as code blocks.
441
- */
442
- normalizeHtmlBlockIndentation(markdown) {
443
- // Match HTML blocks that start with common block-level tags
444
- // These tags indicate structural HTML that should be rendered, not code
445
- const htmlBlockTags = [
446
- 'div', 'table', 'thead', 'tbody', 'tr', 'td', 'th',
447
- 'ul', 'ol', 'li', 'p', 'section', 'article', 'header',
448
- 'footer', 'nav', 'main', 'aside', 'form', 'svg', 'figure'
449
- ];
450
- const tagPattern = htmlBlockTags.join('|');
451
- // Match opening tag at start of line (possibly with leading whitespace)
452
- const htmlBlockStartRegex = new RegExp(`^[ \\t]*<(${tagPattern})\\b`, 'i');
453
- const lines = markdown.split('\n');
454
- const result = [];
455
- let inHtmlBlock = false;
456
- let tagStack = [];
457
- for (const line of lines) {
458
- const trimmedLine = line.trimStart();
459
- if (!inHtmlBlock) {
460
- // Check if this line starts an HTML block
461
- const match = trimmedLine.match(htmlBlockStartRegex);
462
- if (match) {
463
- inHtmlBlock = true;
464
- const tag = match[1].toLowerCase();
465
- // Push to stack if it's not a self-closing tag on this line
466
- if (!this.isSelfClosingLine(trimmedLine, tag)) {
467
- tagStack.push(tag);
468
- }
469
- // Remove leading indentation
470
- result.push(trimmedLine);
471
- continue;
472
- }
473
- result.push(line);
474
- }
475
- else {
476
- // We're inside an HTML block - remove ALL leading whitespace
477
- // to prevent any nested content from being treated as code blocks
478
- // Track tag stack for proper nesting
479
- this.updateTagStack(trimmedLine, tagStack, htmlBlockTags);
480
- // Remove leading whitespace from this line
481
- result.push(trimmedLine);
482
- // Check if we've closed all HTML blocks
483
- if (tagStack.length === 0) {
484
- inHtmlBlock = false;
485
- }
486
- }
487
- }
488
- return result.join('\n');
489
- }
490
- /**
491
- * Check if a line contains a self-closing tag or opens and closes the same tag
492
- */
493
- isSelfClosingLine(line, tag) {
494
- // Check for self-closing syntax: <tag ... />
495
- if (new RegExp(`<${tag}[^>]*/>`, 'i').test(line)) {
496
- return true;
497
- }
498
- // Check if tag opens and closes on same line: <tag>...</tag>
499
- const openCount = (line.match(new RegExp(`<${tag}\\b`, 'gi')) || []).length;
500
- const closeCount = (line.match(new RegExp(`</${tag}>`, 'gi')) || []).length;
501
- return openCount > 0 && openCount === closeCount;
502
- }
503
- /**
504
- * Update the tag stack based on opening/closing tags in the line
505
- */
506
- updateTagStack(line, tagStack, validTags) {
507
- // Find all opening tags
508
- const openTagRegex = /<(\w+)\b[^>]*(?<!\/)>/gi;
509
- const closeTagRegex = /<\/(\w+)>/gi;
510
- let match;
511
- // Process closing tags first (they might close tags opened earlier)
512
- while ((match = closeTagRegex.exec(line)) !== null) {
513
- const tag = match[1].toLowerCase();
514
- const idx = tagStack.lastIndexOf(tag);
515
- if (idx !== -1) {
516
- tagStack.splice(idx, 1);
517
- }
518
- }
519
- // Process opening tags
520
- while ((match = openTagRegex.exec(line)) !== null) {
521
- const tag = match[1].toLowerCase();
522
- // Only track block-level tags we care about
523
- if (validTags.includes(tag)) {
524
- // Don't add if it's self-closing or closed on same line
525
- if (!this.isSelfClosingLine(line, tag)) {
526
- // Check if there's a closing tag for this specific opening
527
- const closeRegex = new RegExp(`</${tag}>`, 'gi');
528
- const opens = (line.match(new RegExp(`<${tag}\\b`, 'gi')) || []).length;
529
- const closes = (line.match(closeRegex) || []).length;
530
- if (opens > closes) {
531
- tagStack.push(tag);
532
- }
533
- }
534
- }
535
- }
536
- }
537
358
  static { this.ɵfac = function MarkdownService_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || MarkdownService)(); }; }
538
359
  static { this.ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: MarkdownService, factory: MarkdownService.ɵfac, providedIn: 'root' }); }
539
360
  }
@@ -1 +1 @@
1
- {"version":3,"file":"markdown.service.js","sourceRoot":"","sources":["../../../src/lib/services/markdown.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { Injectable } from '@angular/core';\nimport { Marked } from 'marked';\nimport { markedHighlight } from 'marked-highlight';\nimport { gfmHeadingId, getHeadingList } from 'marked-gfm-heading-id';\nimport markedAlert from 'marked-alert';\nimport { markedSmartypants } from 'marked-smartypants';\nimport Prism from 'prismjs';\nimport mermaid from 'mermaid';\nimport {\n MarkdownConfig,\n DEFAULT_MARKDOWN_CONFIG,\n HeadingInfo,\n MarkdownRenderEvent\n} from '../types/markdown.types';\nimport { createCollapsibleHeadingsExtension } from '../extensions/collapsible-headings.extension';\nimport { createSvgRendererExtension } from '../extensions/svg-renderer.extension';\nimport { createHtmlBlockRepairExtension } from '../extensions/html-block-repair.extension';\n\n// Import common Prism language components\n// Additional languages can be imported by the consuming application\nimport 'prismjs/components/prism-typescript';\nimport 'prismjs/components/prism-javascript';\nimport 'prismjs/components/prism-css';\nimport 'prismjs/components/prism-scss';\nimport 'prismjs/components/prism-json';\nimport 'prismjs/components/prism-bash';\nimport 'prismjs/components/prism-sql';\nimport 'prismjs/components/prism-python';\nimport 'prismjs/components/prism-csharp';\nimport 'prismjs/components/prism-java';\nimport 'prismjs/components/prism-markup';\nimport 'prismjs/components/prism-yaml';\nimport 'prismjs/components/prism-markdown';\nimport 'prismjs/components/prism-graphql';\n\n// Type for config with optional autoExpandLevels\ntype ResolvedMarkdownConfig = Required<Omit<MarkdownConfig, 'autoExpandLevels'>> & { autoExpandLevels?: number[] };\n\n/**\n * Service for parsing and rendering markdown content.\n * Uses marked.js with various extensions for syntax highlighting,\n * diagrams, alerts, and more.\n */\n@Injectable({\n providedIn: 'root'\n})\nexport class MarkdownService {\n private marked: Marked;\n private mermaidInitialized = false;\n private lastMermaidTheme: string | null = null;\n private currentConfig: ResolvedMarkdownConfig = { ...DEFAULT_MARKDOWN_CONFIG };\n private headingList: HeadingInfo[] = [];\n\n constructor() {\n this.marked = new Marked();\n this.configureMarked(this.currentConfig);\n }\n\n /**\n * Configure the marked instance with the provided options\n */\n public configureMarked(config: MarkdownConfig): void {\n this.currentConfig = { ...DEFAULT_MARKDOWN_CONFIG, ...config };\n\n // Create a fresh Marked instance\n this.marked = new Marked();\n\n // Configure base options\n this.marked.setOptions({\n gfm: true,\n breaks: true\n });\n\n // Apply extensions based on config\n const extensions: any[] = [];\n\n // Repair HTML blocks split by a blank line (e.g. PRD mockups) so embedded\n // raw HTML renders instead of showing as an escaped code block. Always on -\n // precisely scoped to misparsed HTML, leaves prose and fenced code untouched.\n extensions.push(createHtmlBlockRepairExtension());\n\n // SVG code block renderer - MUST be before syntax highlighting\n // so it can intercept svg blocks before Prism processes them\n if (this.currentConfig.enableSvgRenderer) {\n extensions.push(createSvgRendererExtension());\n }\n\n // Syntax highlighting with Prism\n if (this.currentConfig.enableHighlight) {\n extensions.push(\n markedHighlight({\n langPrefix: 'language-',\n highlight: (code: string, lang: string) => {\n // Skip SVG blocks - they're handled by the SVG renderer\n if (lang === 'svg' && this.currentConfig.enableSvgRenderer) {\n return code;\n }\n if (lang && Prism.languages[lang]) {\n try {\n return Prism.highlight(code, Prism.languages[lang], lang);\n } catch (e) {\n console.warn(`Prism highlighting failed for language: ${lang}`, e);\n }\n }\n // Return code as-is if language not found or highlighting fails\n return code;\n }\n })\n );\n }\n\n // GitHub-style heading IDs\n if (this.currentConfig.enableHeadingIds) {\n extensions.push(\n gfmHeadingId({\n prefix: this.currentConfig.headingIdPrefix\n })\n );\n }\n\n // GitHub-style alerts\n if (this.currentConfig.enableAlerts) {\n extensions.push(markedAlert());\n }\n\n // Collapsible headings (custom extension)\n if (this.currentConfig.enableCollapsibleHeadings) {\n extensions.push(\n createCollapsibleHeadingsExtension({\n startLevel: this.currentConfig.collapsibleHeadingLevel,\n defaultExpanded: this.currentConfig.collapsibleDefaultExpanded,\n autoExpandLevels: this.currentConfig.autoExpandLevels\n })\n );\n }\n\n // Smartypants for typography (curly quotes, em/en dashes, ellipses)\n if (this.currentConfig.enableSmartypants) {\n extensions.push(markedSmartypants());\n }\n\n // Apply all extensions\n if (extensions.length > 0) {\n this.marked.use(...extensions);\n }\n }\n\n /**\n * Resolve the effective mermaid theme.\n * 'auto' maps to 'dark' or 'default' based on the document's data-theme attribute.\n */\n private resolveEffectiveMermaidTheme(): 'default' | 'dark' | 'forest' | 'neutral' | 'base' {\n const configTheme = this.currentConfig.mermaidTheme;\n if (configTheme !== 'auto') {\n return configTheme;\n }\n const isDark = typeof document !== 'undefined'\n && document.documentElement.getAttribute('data-theme') === 'dark';\n return isDark ? 'dark' : 'default';\n }\n\n /**\n * Initialize Mermaid with the current theme configuration.\n * Re-initializes when the effective theme changes.\n */\n private initializeMermaid(): void {\n const effectiveTheme = this.resolveEffectiveMermaidTheme();\n\n if (this.mermaidInitialized && this.lastMermaidTheme === effectiveTheme) {\n return;\n }\n\n mermaid.initialize({\n startOnLoad: false,\n theme: effectiveTheme,\n securityLevel: 'loose',\n fontFamily: 'inherit',\n suppressErrorRendering: true // Suppress visual error diagrams - errors go to console only\n });\n\n this.mermaidInitialized = true;\n this.lastMermaidTheme = effectiveTheme;\n }\n\n /**\n * Parse markdown to HTML\n * @param markdown The markdown string to parse\n * @param config Optional config overrides for this parse operation\n * @returns The rendered HTML string\n */\n public parse(markdown: string, config?: Partial<MarkdownConfig>): string {\n if (!markdown) return '';\n\n // Apply config overrides if provided\n if (config) {\n this.configureMarked({ ...this.currentConfig, ...config });\n }\n\n try {\n // Preprocess markdown to fix indentation in HTML blocks\n // This prevents marked from treating indented HTML as code blocks\n let processedMarkdown = markdown;\n if (this.currentConfig.enableHtml) {\n processedMarkdown = this.normalizeHtmlBlockIndentation(markdown);\n }\n\n let html = this.marked.parse(processedMarkdown) as string;\n\n // Capture heading list after parsing\n if (this.currentConfig.enableHeadingIds) {\n this.headingList = getHeadingList() as HeadingInfo[];\n }\n\n // When HTML passthrough is enabled, fix incorrectly code-wrapped HTML\n // marked sometimes wraps inline HTML in <pre><code> blocks\n if (this.currentConfig.enableHtml) {\n html = this.unwrapMiscodedHtml(html);\n }\n\n return html;\n } catch (error) {\n console.error('Markdown parsing error:', error);\n return `<pre class=\"markdown-error\">${this.escapeHtml(markdown)}</pre>`;\n }\n }\n\n /**\n * Parse markdown asynchronously (useful for large documents)\n */\n public async parseAsync(markdown: string, config?: Partial<MarkdownConfig>): Promise<string> {\n return this.parse(markdown, config);\n }\n\n /**\n * Render Mermaid diagrams in a container element\n * Call this after the HTML has been inserted into the DOM\n * @param container The DOM element containing mermaid code blocks\n */\n public async renderMermaid(container: HTMLElement): Promise<boolean> {\n if (!this.currentConfig.enableMermaid) return false;\n\n this.initializeMermaid();\n\n // Find all mermaid code blocks\n const mermaidBlocks = container.querySelectorAll('pre > code.language-mermaid, .mermaid');\n\n if (mermaidBlocks.length === 0) return false;\n\n for (let i = 0; i < mermaidBlocks.length; i++) {\n const block = mermaidBlocks[i];\n const code = block.textContent || '';\n\n if (!code.trim()) continue;\n\n try {\n // Create a unique ID for this diagram\n const id = `mermaid-${Date.now()}-${i}`;\n\n // Render the diagram\n const { svg } = await mermaid.render(id, code);\n\n // Replace the code block with the rendered SVG\n const wrapper = document.createElement('div');\n wrapper.className = 'mermaid-diagram';\n wrapper.innerHTML = svg;\n\n // Replace the pre element (parent of code) or the mermaid element itself\n const elementToReplace = block.tagName === 'CODE' ? block.parentElement : block;\n elementToReplace?.parentNode?.replaceChild(wrapper, elementToReplace);\n } catch (error) {\n console.warn('Mermaid rendering failed:', error);\n // Add error class to show it failed\n const parent = block.tagName === 'CODE' ? block.parentElement : block;\n parent?.classList.add('mermaid-error');\n }\n }\n\n return true;\n }\n\n /**\n * Highlight code blocks with Prism\n * Call this after the HTML has been inserted into the DOM\n * @param container The DOM element containing code blocks\n */\n public highlightCode(container: HTMLElement): void {\n if (!this.currentConfig.enableHighlight) return;\n\n // Prism.highlightAllUnder handles finding and highlighting code blocks\n Prism.highlightAllUnder(container);\n }\n\n /**\n * Add copy buttons to code blocks\n * @param container The DOM element containing code blocks\n */\n public addCodeCopyButtons(container: HTMLElement): void {\n if (!this.currentConfig.enableCodeCopy) return;\n\n const codeBlocks = container.querySelectorAll('pre > code');\n\n codeBlocks.forEach((codeBlock) => {\n const pre = codeBlock.parentElement;\n if (!pre || pre.querySelector('.code-copy-btn')) return; // Already has button\n\n // Create copy button\n const button = document.createElement('button');\n button.className = 'code-copy-btn';\n button.innerHTML = '<i class=\"fas fa-copy\"></i>';\n button.title = 'Copy code';\n button.type = 'button';\n\n button.addEventListener('click', async () => {\n const code = codeBlock.textContent || '';\n\n try {\n await navigator.clipboard.writeText(code);\n button.innerHTML = '<i class=\"fas fa-check\"></i>';\n button.classList.add('copied');\n\n setTimeout(() => {\n button.innerHTML = '<i class=\"fas fa-copy\"></i>';\n button.classList.remove('copied');\n }, 2000);\n } catch (err) {\n console.error('Failed to copy code:', err);\n button.innerHTML = '<i class=\"fas fa-times\"></i>';\n\n setTimeout(() => {\n button.innerHTML = '<i class=\"fas fa-copy\"></i>';\n }, 2000);\n }\n });\n\n // Add toolbar wrapper\n const toolbar = document.createElement('div');\n toolbar.className = 'code-toolbar';\n toolbar.appendChild(button);\n\n // Make pre position relative for absolute positioning of toolbar\n pre.style.position = 'relative';\n pre.appendChild(toolbar);\n });\n }\n\n /**\n * Initialize collapsible heading functionality\n * This method is a no-op - the component handles event binding\n * @param container The DOM element containing collapsible sections\n */\n public initializeCollapsibleHeadings(_container: HTMLElement): void {\n // Event binding is handled by the component's setupCollapsibleListeners\n // This method exists for API compatibility but does nothing\n }\n\n /**\n * Get the list of headings from the last parsed document\n * Useful for building table of contents\n */\n public getHeadingList(): HeadingInfo[] {\n return this.headingList;\n }\n\n /**\n * Get the current configuration\n */\n public getConfig(): ResolvedMarkdownConfig {\n return { ...this.currentConfig };\n }\n\n /**\n * Reset configuration to defaults\n */\n public resetConfig(): void {\n this.configureMarked(DEFAULT_MARKDOWN_CONFIG);\n }\n\n /**\n * Check if a language is supported by Prism\n */\n public isLanguageSupported(lang: string): boolean {\n return !!Prism.languages[lang];\n }\n\n /**\n * Get list of supported Prism languages\n */\n public getSupportedLanguages(): string[] {\n return Object.keys(Prism.languages).filter(\n lang => typeof Prism.languages[lang] === 'object'\n );\n }\n\n /**\n * Escape HTML entities for safe display\n */\n private escapeHtml(text: string): string {\n const div = document.createElement('div');\n div.textContent = text;\n return div.innerHTML;\n }\n\n /**\n * Fix HTML that was incorrectly wrapped in <pre><code> blocks by marked.\n * This happens when marked interprets inline HTML (especially indented HTML)\n * as code blocks. We detect this by checking if the code block content\n * looks like valid HTML structure rather than actual code.\n *\n * Only processes code blocks WITHOUT a language class (e.g., language-javascript)\n * to avoid unwrapping intentional code examples.\n */\n private unwrapMiscodedHtml(html: string): string {\n // Quick check - if no pre tags, nothing to do\n if (!html.includes('<pre>')) {\n return html;\n }\n\n // Skip if SVG is present - DOMParser mangles SVG elements like <rect>\n // when parsing as 'text/html' due to namespace issues\n if (html.includes('<svg')) {\n return html;\n }\n\n try {\n const parser = new DOMParser();\n const doc = parser.parseFromString(`<div>${html}</div>`, 'text/html');\n const container = doc.body.firstChild as HTMLElement;\n\n if (!container) return html;\n\n // Find all pre > code elements WITHOUT a language class\n // Code blocks with language classes (language-javascript, etc.) are intentional\n const preElements = container.querySelectorAll('pre');\n let modified = false;\n\n for (const pre of Array.from(preElements)) {\n const code = pre.querySelector('code');\n if (!code) continue;\n\n // Skip if code has a language class - it's intentional code\n const hasLanguageClass = code.className && /language-\\w+/.test(code.className);\n if (hasLanguageClass) continue;\n\n // Get the text content (this is HTML-decoded by the browser)\n const content = code.textContent?.trim() || '';\n\n // Check if this looks like HTML that was incorrectly wrapped\n if (this.looksLikeStructuralHtml(content)) {\n // Verify it parses as valid HTML with actual elements\n const testDoc = parser.parseFromString(content, 'text/html');\n const hasStructure = testDoc.body.children.length > 0 ||\n (testDoc.body.innerHTML.trim().length > 0 &&\n testDoc.body.innerHTML.includes('<'));\n\n if (hasStructure) {\n // Replace the <pre> with the actual HTML content\n const wrapper = document.createElement('div');\n wrapper.className = 'unwrapped-html';\n wrapper.innerHTML = content;\n\n // Move all children from wrapper to replace pre\n const fragment = document.createDocumentFragment();\n while (wrapper.firstChild) {\n fragment.appendChild(wrapper.firstChild);\n }\n pre.parentNode?.replaceChild(fragment, pre);\n modified = true;\n }\n }\n }\n\n if (modified) {\n return container.innerHTML;\n }\n } catch (error) {\n console.warn('Error in unwrapMiscodedHtml:', error);\n }\n\n return html;\n }\n\n /**\n * Check if content looks like structural HTML that was incorrectly\n * wrapped in a code block. We look for common HTML element patterns\n * that indicate this is meant to be rendered HTML, not code.\n */\n private looksLikeStructuralHtml(content: string): boolean {\n // Must start with < to be HTML\n if (!content.startsWith('<')) return false;\n\n // Must end with > (closing tag)\n if (!content.endsWith('>')) return false;\n\n // Check for common structural HTML tags that indicate layout HTML\n // These are tags that would typically appear in a UI mockup/prototype\n const structuralTagPattern = /<(div|span|table|tr|td|th|thead|tbody|p|ul|ol|li|section|article|header|footer|nav|main|aside|form|input|button|label|select|option|textarea|h[1-6]|img|a|strong|em|b|i|br|hr)\\b/i;\n\n if (!structuralTagPattern.test(content)) return false;\n\n // Additional check: should have multiple tags or nested structure\n // Single self-closing tags like <br> or <img> shouldn't trigger unwrapping\n const tagCount = (content.match(/<\\w+/g) || []).length;\n if (tagCount < 2) return false;\n\n // Check it's not just showing HTML as an example (common in docs)\n // If content has lots of &lt; or &gt; it's probably escaped HTML being shown\n if (content.includes('&lt;') || content.includes('&gt;')) return false;\n\n return true;\n }\n\n /**\n * Normalize indentation in HTML blocks to prevent marked from treating\n * indented HTML as code blocks (4 spaces = code block in markdown).\n *\n * This finds HTML blocks (starting with common block-level tags) and\n * removes ALL leading whitespace from lines within those blocks to ensure\n * marked doesn't interpret any nested content as code blocks.\n */\n private normalizeHtmlBlockIndentation(markdown: string): string {\n // Match HTML blocks that start with common block-level tags\n // These tags indicate structural HTML that should be rendered, not code\n const htmlBlockTags = [\n 'div', 'table', 'thead', 'tbody', 'tr', 'td', 'th',\n 'ul', 'ol', 'li', 'p', 'section', 'article', 'header',\n 'footer', 'nav', 'main', 'aside', 'form', 'svg', 'figure'\n ];\n\n const tagPattern = htmlBlockTags.join('|');\n // Match opening tag at start of line (possibly with leading whitespace)\n const htmlBlockStartRegex = new RegExp(`^[ \\\\t]*<(${tagPattern})\\\\b`, 'i');\n\n const lines = markdown.split('\\n');\n const result: string[] = [];\n let inHtmlBlock = false;\n let tagStack: string[] = [];\n\n for (const line of lines) {\n const trimmedLine = line.trimStart();\n\n if (!inHtmlBlock) {\n // Check if this line starts an HTML block\n const match = trimmedLine.match(htmlBlockStartRegex);\n if (match) {\n inHtmlBlock = true;\n const tag = match[1].toLowerCase();\n\n // Push to stack if it's not a self-closing tag on this line\n if (!this.isSelfClosingLine(trimmedLine, tag)) {\n tagStack.push(tag);\n }\n\n // Remove leading indentation\n result.push(trimmedLine);\n continue;\n }\n result.push(line);\n } else {\n // We're inside an HTML block - remove ALL leading whitespace\n // to prevent any nested content from being treated as code blocks\n\n // Track tag stack for proper nesting\n this.updateTagStack(trimmedLine, tagStack, htmlBlockTags);\n\n // Remove leading whitespace from this line\n result.push(trimmedLine);\n\n // Check if we've closed all HTML blocks\n if (tagStack.length === 0) {\n inHtmlBlock = false;\n }\n }\n }\n\n return result.join('\\n');\n }\n\n /**\n * Check if a line contains a self-closing tag or opens and closes the same tag\n */\n private isSelfClosingLine(line: string, tag: string): boolean {\n // Check for self-closing syntax: <tag ... />\n if (new RegExp(`<${tag}[^>]*/>`,'i').test(line)) {\n return true;\n }\n // Check if tag opens and closes on same line: <tag>...</tag>\n const openCount = (line.match(new RegExp(`<${tag}\\\\b`, 'gi')) || []).length;\n const closeCount = (line.match(new RegExp(`</${tag}>`, 'gi')) || []).length;\n return openCount > 0 && openCount === closeCount;\n }\n\n /**\n * Update the tag stack based on opening/closing tags in the line\n */\n private updateTagStack(line: string, tagStack: string[], validTags: string[]): void {\n // Find all opening tags\n const openTagRegex = /<(\\w+)\\b[^>]*(?<!\\/)>/gi;\n const closeTagRegex = /<\\/(\\w+)>/gi;\n\n let match;\n\n // Process closing tags first (they might close tags opened earlier)\n while ((match = closeTagRegex.exec(line)) !== null) {\n const tag = match[1].toLowerCase();\n const idx = tagStack.lastIndexOf(tag);\n if (idx !== -1) {\n tagStack.splice(idx, 1);\n }\n }\n\n // Process opening tags\n while ((match = openTagRegex.exec(line)) !== null) {\n const tag = match[1].toLowerCase();\n // Only track block-level tags we care about\n if (validTags.includes(tag)) {\n // Don't add if it's self-closing or closed on same line\n if (!this.isSelfClosingLine(line, tag)) {\n // Check if there's a closing tag for this specific opening\n const closeRegex = new RegExp(`</${tag}>`, 'gi');\n const opens = (line.match(new RegExp(`<${tag}\\\\b`, 'gi')) || []).length;\n const closes = (line.match(closeRegex) || []).length;\n if (opens > closes) {\n tagStack.push(tag);\n }\n }\n }\n }\n }\n}\n"]}
1
+ {"version":3,"file":"markdown.service.js","sourceRoot":"","sources":["../../../src/lib/services/markdown.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { Injectable } from '@angular/core';\nimport Prism from 'prismjs';\nimport mermaid from 'mermaid';\nimport {\n MarkdownEngine,\n MarkdownConfig,\n DEFAULT_MARKDOWN_CONFIG,\n ResolvedMarkdownConfig,\n HeadingInfo,\n HighlightFunction\n} from '@memberjunction/markdown-core';\n\n// Import common Prism language components\n// Additional languages can be imported by the consuming application\nimport 'prismjs/components/prism-typescript';\nimport 'prismjs/components/prism-javascript';\nimport 'prismjs/components/prism-css';\nimport 'prismjs/components/prism-scss';\nimport 'prismjs/components/prism-json';\nimport 'prismjs/components/prism-bash';\nimport 'prismjs/components/prism-sql';\nimport 'prismjs/components/prism-python';\nimport 'prismjs/components/prism-csharp';\nimport 'prismjs/components/prism-java';\nimport 'prismjs/components/prism-markup';\nimport 'prismjs/components/prism-yaml';\nimport 'prismjs/components/prism-markdown';\nimport 'prismjs/components/prism-graphql';\n\n/**\n * Service for parsing and rendering markdown content on the web.\n *\n * The parsing/configuration work (marked setup, custom extensions, HTML and\n * token output) is owned by the framework-agnostic `@memberjunction/markdown-core`\n * {@link MarkdownEngine}. This Angular service is the web shell around it: it\n * injects a Prism-based highlighter into the engine and keeps the browser-only\n * concerns here — Mermaid rendering, copy buttons, and the DOM-based fixup of\n * HTML that marked miscoded as a code block.\n */\n@Injectable({\n providedIn: 'root'\n})\nexport class MarkdownService {\n private engine = new MarkdownEngine();\n private mermaidInitialized = false;\n private lastMermaidTheme: string | null = null;\n private currentConfig: ResolvedMarkdownConfig = { ...DEFAULT_MARKDOWN_CONFIG };\n\n /**\n * Prism-backed highlight function injected into the core engine. The engine\n * itself has no Prism dependency — it calls this for the HTML output path.\n */\n private prismHighlight: HighlightFunction = (code: string, lang: string): string => {\n if (lang && Prism.languages[lang]) {\n try {\n return Prism.highlight(code, Prism.languages[lang], lang);\n } catch (e) {\n console.warn(`Prism highlighting failed for language: ${lang}`, e);\n }\n }\n // Return code as-is if language not found or highlighting fails\n return code;\n };\n\n constructor() {\n this.configureMarked(this.currentConfig);\n }\n\n /**\n * Configure the underlying engine with the provided options.\n */\n public configureMarked(config: MarkdownConfig): void {\n this.currentConfig = { ...DEFAULT_MARKDOWN_CONFIG, ...config };\n this.engine.configureMarked(this.currentConfig, { highlightFn: this.prismHighlight });\n }\n\n /**\n * Parse markdown to HTML.\n * @param markdown The markdown string to parse\n * @param config Optional config overrides for this parse operation\n * @returns The rendered HTML string\n */\n public parse(markdown: string, config?: Partial<MarkdownConfig>): string {\n if (!markdown) return '';\n\n // Apply config overrides if provided (re-injects the Prism highlighter)\n if (config) {\n this.configureMarked({ ...this.currentConfig, ...config });\n }\n\n let html = this.engine.parseToHtml(markdown);\n\n // When HTML passthrough is enabled, fix incorrectly code-wrapped HTML.\n // marked sometimes wraps inline HTML in <pre><code> blocks. This is a\n // DOM-based fixup so it stays in the web service, not the core engine.\n if (this.currentConfig.enableHtml) {\n html = this.unwrapMiscodedHtml(html);\n }\n\n return html;\n }\n\n /**\n * Parse markdown asynchronously (useful for large documents)\n */\n public async parseAsync(markdown: string, config?: Partial<MarkdownConfig>): Promise<string> {\n return this.parse(markdown, config);\n }\n\n /**\n * Resolve the effective mermaid theme.\n * 'auto' maps to 'dark' or 'default' based on the document's data-theme attribute.\n */\n private resolveEffectiveMermaidTheme(): 'default' | 'dark' | 'forest' | 'neutral' | 'base' {\n const configTheme = this.currentConfig.mermaidTheme;\n if (configTheme !== 'auto') {\n return configTheme;\n }\n const isDark = typeof document !== 'undefined'\n && document.documentElement.getAttribute('data-theme') === 'dark';\n return isDark ? 'dark' : 'default';\n }\n\n /**\n * Initialize Mermaid with the current theme configuration.\n * Re-initializes when the effective theme changes.\n */\n private initializeMermaid(): void {\n const effectiveTheme = this.resolveEffectiveMermaidTheme();\n\n if (this.mermaidInitialized && this.lastMermaidTheme === effectiveTheme) {\n return;\n }\n\n mermaid.initialize({\n startOnLoad: false,\n theme: effectiveTheme,\n securityLevel: 'loose',\n fontFamily: 'inherit',\n suppressErrorRendering: true // Suppress visual error diagrams - errors go to console only\n });\n\n this.mermaidInitialized = true;\n this.lastMermaidTheme = effectiveTheme;\n }\n\n /**\n * Render Mermaid diagrams in a container element\n * Call this after the HTML has been inserted into the DOM\n * @param container The DOM element containing mermaid code blocks\n */\n public async renderMermaid(container: HTMLElement): Promise<boolean> {\n if (!this.currentConfig.enableMermaid) return false;\n\n this.initializeMermaid();\n\n // Find all mermaid code blocks\n const mermaidBlocks = container.querySelectorAll('pre > code.language-mermaid, .mermaid');\n\n if (mermaidBlocks.length === 0) return false;\n\n for (let i = 0; i < mermaidBlocks.length; i++) {\n const block = mermaidBlocks[i];\n const code = block.textContent || '';\n\n if (!code.trim()) continue;\n\n try {\n // Create a unique ID for this diagram\n const id = `mermaid-${Date.now()}-${i}`;\n\n // Render the diagram\n const { svg } = await mermaid.render(id, code);\n\n // Replace the code block with the rendered SVG\n const wrapper = document.createElement('div');\n wrapper.className = 'mermaid-diagram';\n wrapper.innerHTML = svg;\n\n // Replace the pre element (parent of code) or the mermaid element itself\n const elementToReplace = block.tagName === 'CODE' ? block.parentElement : block;\n elementToReplace?.parentNode?.replaceChild(wrapper, elementToReplace);\n } catch (error) {\n console.warn('Mermaid rendering failed:', error);\n // Add error class to show it failed\n const parent = block.tagName === 'CODE' ? block.parentElement : block;\n parent?.classList.add('mermaid-error');\n }\n }\n\n return true;\n }\n\n /**\n * Highlight code blocks with Prism\n * Call this after the HTML has been inserted into the DOM\n * @param container The DOM element containing code blocks\n */\n public highlightCode(container: HTMLElement): void {\n if (!this.currentConfig.enableHighlight) return;\n\n // Prism.highlightAllUnder handles finding and highlighting code blocks\n Prism.highlightAllUnder(container);\n }\n\n /**\n * Add copy buttons to code blocks\n * @param container The DOM element containing code blocks\n */\n public addCodeCopyButtons(container: HTMLElement): void {\n if (!this.currentConfig.enableCodeCopy) return;\n\n const codeBlocks = container.querySelectorAll('pre > code');\n\n codeBlocks.forEach((codeBlock) => {\n const pre = codeBlock.parentElement;\n if (!pre || pre.querySelector('.code-copy-btn')) return; // Already has button\n\n // Create copy button\n const button = document.createElement('button');\n button.className = 'code-copy-btn';\n button.innerHTML = '<i class=\"fas fa-copy\"></i>';\n button.title = 'Copy code';\n button.type = 'button';\n\n button.addEventListener('click', async () => {\n const code = codeBlock.textContent || '';\n\n try {\n await navigator.clipboard.writeText(code);\n button.innerHTML = '<i class=\"fas fa-check\"></i>';\n button.classList.add('copied');\n\n setTimeout(() => {\n button.innerHTML = '<i class=\"fas fa-copy\"></i>';\n button.classList.remove('copied');\n }, 2000);\n } catch (err) {\n console.error('Failed to copy code:', err);\n button.innerHTML = '<i class=\"fas fa-times\"></i>';\n\n setTimeout(() => {\n button.innerHTML = '<i class=\"fas fa-copy\"></i>';\n }, 2000);\n }\n });\n\n // Add toolbar wrapper\n const toolbar = document.createElement('div');\n toolbar.className = 'code-toolbar';\n toolbar.appendChild(button);\n\n // Make pre position relative for absolute positioning of toolbar\n pre.style.position = 'relative';\n pre.appendChild(toolbar);\n });\n }\n\n /**\n * Initialize collapsible heading functionality\n * This method is a no-op - the component handles event binding\n * @param container The DOM element containing collapsible sections\n */\n public initializeCollapsibleHeadings(_container: HTMLElement): void {\n // Event binding is handled by the component's setupCollapsibleListeners\n // This method exists for API compatibility but does nothing\n }\n\n /**\n * Get the list of headings from the last parsed document\n * Useful for building table of contents\n */\n public getHeadingList(): HeadingInfo[] {\n return this.engine.getHeadingList();\n }\n\n /**\n * Get the current configuration\n */\n public getConfig(): ResolvedMarkdownConfig {\n return this.engine.getConfig();\n }\n\n /**\n * Reset configuration to defaults\n */\n public resetConfig(): void {\n this.configureMarked(DEFAULT_MARKDOWN_CONFIG);\n }\n\n /**\n * Check if a language is supported by Prism\n */\n public isLanguageSupported(lang: string): boolean {\n return !!Prism.languages[lang];\n }\n\n /**\n * Get list of supported Prism languages\n */\n public getSupportedLanguages(): string[] {\n return Object.keys(Prism.languages).filter(\n lang => typeof Prism.languages[lang] === 'object'\n );\n }\n\n /**\n * Fix HTML that was incorrectly wrapped in <pre><code> blocks by marked.\n * This happens when marked interprets inline HTML (especially indented HTML)\n * as code blocks. We detect this by checking if the code block content\n * looks like valid HTML structure rather than actual code.\n *\n * Only processes code blocks WITHOUT a language class (e.g., language-javascript)\n * to avoid unwrapping intentional code examples.\n */\n private unwrapMiscodedHtml(html: string): string {\n // Quick check - if no pre tags, nothing to do\n if (!html.includes('<pre>')) {\n return html;\n }\n\n // Skip if SVG is present - DOMParser mangles SVG elements like <rect>\n // when parsing as 'text/html' due to namespace issues\n if (html.includes('<svg')) {\n return html;\n }\n\n try {\n const parser = new DOMParser();\n const doc = parser.parseFromString(`<div>${html}</div>`, 'text/html');\n const container = doc.body.firstChild as HTMLElement;\n\n if (!container) return html;\n\n // Find all pre > code elements WITHOUT a language class\n // Code blocks with language classes (language-javascript, etc.) are intentional\n const preElements = container.querySelectorAll('pre');\n let modified = false;\n\n for (const pre of Array.from(preElements)) {\n const code = pre.querySelector('code');\n if (!code) continue;\n\n // Skip if code has a language class - it's intentional code\n const hasLanguageClass = code.className && /language-\\w+/.test(code.className);\n if (hasLanguageClass) continue;\n\n // Get the text content (this is HTML-decoded by the browser)\n const content = code.textContent?.trim() || '';\n\n // Check if this looks like HTML that was incorrectly wrapped\n if (this.looksLikeStructuralHtml(content)) {\n // Verify it parses as valid HTML with actual elements\n const testDoc = parser.parseFromString(content, 'text/html');\n const hasStructure = testDoc.body.children.length > 0 ||\n (testDoc.body.innerHTML.trim().length > 0 &&\n testDoc.body.innerHTML.includes('<'));\n\n if (hasStructure) {\n // Replace the <pre> with the actual HTML content\n const wrapper = document.createElement('div');\n wrapper.className = 'unwrapped-html';\n wrapper.innerHTML = content;\n\n // Move all children from wrapper to replace pre\n const fragment = document.createDocumentFragment();\n while (wrapper.firstChild) {\n fragment.appendChild(wrapper.firstChild);\n }\n pre.parentNode?.replaceChild(fragment, pre);\n modified = true;\n }\n }\n }\n\n if (modified) {\n return container.innerHTML;\n }\n } catch (error) {\n console.warn('Error in unwrapMiscodedHtml:', error);\n }\n\n return html;\n }\n\n /**\n * Check if content looks like structural HTML that was incorrectly\n * wrapped in a code block. We look for common HTML element patterns\n * that indicate this is meant to be rendered HTML, not code.\n */\n private looksLikeStructuralHtml(content: string): boolean {\n // Must start with < to be HTML\n if (!content.startsWith('<')) return false;\n\n // Must end with > (closing tag)\n if (!content.endsWith('>')) return false;\n\n // Check for common structural HTML tags that indicate layout HTML\n // These are tags that would typically appear in a UI mockup/prototype\n const structuralTagPattern = /<(div|span|table|tr|td|th|thead|tbody|p|ul|ol|li|section|article|header|footer|nav|main|aside|form|input|button|label|select|option|textarea|h[1-6]|img|a|strong|em|b|i|br|hr)\\b/i;\n\n if (!structuralTagPattern.test(content)) return false;\n\n // Additional check: should have multiple tags or nested structure\n // Single self-closing tags like <br> or <img> shouldn't trigger unwrapping\n const tagCount = (content.match(/<\\w+/g) || []).length;\n if (tagCount < 2) return false;\n\n // Check it's not just showing HTML as an example (common in docs)\n // If content has lots of &lt; or &gt; it's probably escaped HTML being shown\n if (content.includes('&lt;') || content.includes('&gt;')) return false;\n\n return true;\n }\n}\n"]}
@@ -4,5 +4,4 @@ export * from './lib/services/markdown.service';
4
4
  export * from './lib/extensions/collapsible-headings.extension';
5
5
  export * from './lib/extensions/code-copy.extension';
6
6
  export * from './lib/extensions/svg-renderer.extension';
7
- export * from './lib/types/markdown.types';
8
7
  //# sourceMappingURL=public-api.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"public-api.d.ts","sourceRoot":"","sources":["../src/public-api.ts"],"names":[],"mappings":"AAGA,cAAc,uBAAuB,CAAC;AAGtC,cAAc,qCAAqC,CAAC;AAGpD,cAAc,iCAAiC,CAAC;AAGhD,cAAc,iDAAiD,CAAC;AAChE,cAAc,sCAAsC,CAAC;AACrD,cAAc,yCAAyC,CAAC;AAGxD,cAAc,4BAA4B,CAAC"}
1
+ {"version":3,"file":"public-api.d.ts","sourceRoot":"","sources":["../src/public-api.ts"],"names":[],"mappings":"AAQA,cAAc,uBAAuB,CAAC;AAGtC,cAAc,qCAAqC,CAAC;AAGpD,cAAc,iCAAiC,CAAC;AAGhD,cAAc,iDAAiD,CAAC;AAChE,cAAc,sCAAsC,CAAC;AACrD,cAAc,yCAAyC,CAAC"}