@memberjunction/ng-markdown 6.1.0-edge.5 → 6.1.0-edge.7

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.
@@ -1,6 +1,8 @@
1
1
  import { Injectable } from '@angular/core';
2
2
  import Prism from 'prismjs';
3
3
  import { MarkdownEngine, DEFAULT_MARKDOWN_CONFIG } from '@memberjunction/markdown-core';
4
+ import DOMPurify from 'dompurify';
5
+ import { escapeHtml } from '@memberjunction/markdown-core';
4
6
  // Import common Prism language components
5
7
  // Additional languages can be imported by the consuming application
6
8
  import 'prismjs/components/prism-typescript';
@@ -28,6 +30,66 @@ import * as i0 from "@angular/core";
28
30
  * concerns here — Mermaid rendering, copy buttons, and the DOM-based fixup of
29
31
  * HTML that marked miscoded as a code block.
30
32
  */
33
+ /**
34
+ * The DOMPurify instance used for rendered HTML, created once per window and configured
35
+ * once. A private instance rather than the shared default so the hooks below cannot leak
36
+ * into other packages' DOMPurify usage, and so the configuration is parsed once instead
37
+ * of on every render.
38
+ *
39
+ * Profile: HTML + SVG + SVG filters, which keeps structural markup, inline styles, data
40
+ * attributes and inline vector graphics and removes the script vectors. Deviations from
41
+ * DOMPurify's defaults, each deliberate:
42
+ * - FORCE_BODY: a document that starts with `<style>` keeps it (otherwise the parser hoists
43
+ * a leading style element into <head> and it is dropped); agent-authored mockups start
44
+ * that way.
45
+ * - SANITIZE_DOM off: DOMPurify would strip any id or name that collides with a document
46
+ * or form property (`title`, `name`, `location`, ...), which are exactly the slugs
47
+ * heading ids produce. This app does not read globals off `window` by element id, so
48
+ * DOM clobbering has no effect here, and heading anchors matter.
49
+ * - `target` on links is kept, with `rel="noopener noreferrer"` enforced when it is set.
50
+ * - `<use>` is allowed only with a same-document reference (`#id`); external references
51
+ * are removed. SMIL `<animate>`/`<set>` stay disallowed: they can rewrite `href`.
52
+ */
53
+ let purifier = null;
54
+ function getPurifier() {
55
+ if (purifier)
56
+ return purifier;
57
+ if (typeof window === 'undefined')
58
+ return null;
59
+ const instance = DOMPurify(window);
60
+ if (!instance.isSupported)
61
+ return null;
62
+ instance.setConfig({
63
+ USE_PROFILES: { html: true, svg: true, svgFilters: true },
64
+ ADD_TAGS: ['use'],
65
+ ADD_ATTR: ['target'],
66
+ FORCE_BODY: true,
67
+ SANITIZE_DOM: false,
68
+ });
69
+ instance.addHook('uponSanitizeAttribute', (node, data) => {
70
+ const name = data.attrName.toLowerCase();
71
+ if (node.nodeName.toLowerCase() === 'use' && (name === 'href' || name === 'xlink:href')) {
72
+ // Same-document sprite references only. Anything else can load an external document.
73
+ data.keepAttr = data.attrValue.trim().startsWith('#');
74
+ }
75
+ });
76
+ instance.addHook('afterSanitizeAttributes', (node) => {
77
+ const tag = node.nodeName.toLowerCase();
78
+ if (tag === 'a' && node.hasAttribute('target')) {
79
+ node.setAttribute('rel', 'noopener noreferrer');
80
+ }
81
+ if (tag === 'use') {
82
+ // The attribute hook above has already dropped any non-fragment reference; a <use>
83
+ // with nothing left to reference is removed rather than left as an empty element.
84
+ const ref = node.getAttribute('href') ?? node.getAttribute('xlink:href');
85
+ if (!ref || !ref.trim().startsWith('#')) {
86
+ node.parentNode?.removeChild(node);
87
+ }
88
+ }
89
+ });
90
+ purifier = instance;
91
+ return purifier;
92
+ }
31
93
  export class MarkdownService {
32
94
  constructor() {
33
95
  this.engine = new MarkdownEngine();
@@ -83,8 +145,27 @@ export class MarkdownService {
83
145
  if (this.currentConfig.enableHtml) {
84
146
  html = this.unwrapMiscodedHtml(html);
85
147
  }
148
+ // The service is the one place every consumer passes through (the component, the chat
149
+ // widget, anything binding parse() output to innerHTML), so the sanitizer runs here.
150
+ // enableJavaScript is the explicit opt-out.
151
+ if (!this.currentConfig.enableJavaScript) {
152
+ html = this.sanitizeHtml(html);
153
+ }
86
154
  return html;
87
155
  }
156
+ /**
157
+ * Sanitize rendered HTML for binding to innerHTML while preserving layout HTML, inline
158
+ * styles and inline SVG. See the DOMPurify configuration at the top of this file for
159
+ * what is kept and removed. Where no DOM is available to sanitize with, the markup is
160
+ * escaped and rendered as text rather than trusted.
161
+ */
162
+ sanitizeHtml(html) {
163
+ const instance = getPurifier();
164
+ if (!instance) {
165
+ return escapeHtml(html);
166
+ }
167
+ return instance.sanitize(html);
168
+ }
88
169
  /**
89
170
  * Parse markdown asynchronously (useful for large documents)
90
171
  */
@@ -367,16 +448,15 @@ export class MarkdownService {
367
448
  (testDoc.body.innerHTML.trim().length > 0 &&
368
449
  testDoc.body.innerHTML.includes('<'));
369
450
  if (hasStructure) {
370
- // Replace the <pre> with the actual HTML content
371
- const wrapper = document.createElement('div');
372
- wrapper.className = 'unwrapped-html';
373
- wrapper.innerHTML = content;
374
- // Move all children from wrapper to replace pre
375
- const fragment = document.createDocumentFragment();
376
- while (wrapper.firstChild) {
377
- fragment.appendChild(wrapper.firstChild);
378
- }
379
- pre.parentNode?.replaceChild(fragment, pre);
451
+ // Replace the <pre> with the actual HTML content. The nodes are built inside a
452
+ // <template> of the inert DOMParser document, never on the live `document`:
453
+ // setting innerHTML on a live element fires image error handlers at parse time,
454
+ // before the sanitizer in parse() has seen the markup. A <template> also parses
455
+ // in body context, so a mockup that starts with <style> keeps it (a full-document
456
+ // parse would hoist it into <head> and drop it).
457
+ const template = doc.createElement('template');
458
+ template.innerHTML = content;
459
+ pre.replaceWith(template.content);
380
460
  modified = true;
381
461
  }
382
462
  }
@@ -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 Prism from 'prismjs';\n// TYPE-ONLY — erased at compile time, so it costs nothing at runtime. The mermaid engine\n// itself is fetched on demand; see `loadMermaid()`.\nimport type { 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 /** The mermaid engine, once its lazy chunk has landed. Null until a diagram is actually rendered. */\n private mermaidEngine: Mermaid | null = null;\n /** In-flight load, so N diagrams in one document share ONE chunk fetch. */\n private mermaidLoad: Promise<Mermaid> | null = null;\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 * Fetch the mermaid engine, on first use only.\n *\n * MEASURED BUNDLE DEFERRAL — this is deliberately a dynamic `import()`, NOT a top-level\n * one, and must not be \"tidied\" back into a static import. `mermaid` and the parsing /\n * graph engines it pulls with it (`@mermaid-js/parser`, `langium`, `chevrotain`,\n * `cytoscape`, `katex`, `d3`) were ~0.95 MB raw of every host's initial download even\n * when no document ever contained a diagram. That is a rounding error for an in-app\n * host and the whole cost for a CDN-loaded embedding of the realtime session overlay\n * (MJ #3882). Documents with no diagram now never fetch the chunk at all.\n *\n * The in-flight promise is cached so N diagrams in one document share one fetch; a\n * FAILED load clears it, so a later render can retry. Retries are bounded by\n * user-driven render calls — there is no loop here.\n */\n private async loadMermaid(): Promise<Mermaid> {\n if (this.mermaidEngine) {\n return this.mermaidEngine;\n }\n\n this.mermaidLoad ??= import('mermaid').then((module) => module.default);\n\n try {\n this.mermaidEngine = await this.mermaidLoad;\n return this.mermaidEngine;\n } catch (error) {\n this.mermaidLoad = null;\n throw error;\n }\n }\n\n /**\n * Initialize Mermaid with the current theme configuration.\n * Re-initializes when the effective theme changes.\n * @param mermaid The lazily-loaded mermaid engine (see {@link loadMermaid})\n */\n private initializeMermaid(mermaid: Mermaid): 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 // Find all mermaid code blocks BEFORE touching the engine — this is the branch the\n // deferral hangs off: no diagrams in this document means no chunk fetch at all.\n const mermaidBlocks = container.querySelectorAll('pre > code.language-mermaid, .mermaid');\n\n if (mermaidBlocks.length === 0) return false;\n\n let mermaid: Mermaid;\n try {\n mermaid = await this.loadMermaid();\n } catch (error) {\n // The chunk could not be fetched (offline, blocked host, bad deploy). Say so on\n // every block we were going to render — an empty panel would look like the diagram\n // simply wasn't there.\n console.error('Failed to load the Mermaid diagram engine:', error);\n this.markMermaidBlocksFailed(mermaidBlocks, 'Diagram engine failed to load');\n return false;\n }\n\n this.initializeMermaid(mermaid);\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 // Show it failed on this block — the source stays visible underneath.\n this.markMermaidBlocksFailed([block], 'Diagram rendering failed');\n }\n }\n\n return true;\n }\n\n /**\n * Mark mermaid blocks as un-renderable, so the failure is VISIBLE rather than a block of\n * raw diagram source the reader can't explain. The `.mermaid-error` class carries the\n * error styling and a default caption; `data-mermaid-error` overrides that caption with\n * the specific reason (see `.mermaid-error[data-mermaid-error]::before` in the component\n * stylesheet).\n *\n * @param blocks The mermaid code blocks (or `.mermaid` elements) that could not be rendered\n * @param message Short reason shown above the un-rendered source\n */\n private markMermaidBlocksFailed(blocks: ArrayLike<Element>, message: string): void {\n for (let i = 0; i < blocks.length; i++) {\n const block = blocks[i];\n const target = block.tagName === 'CODE' ? block.parentElement : block;\n target?.classList.add('mermaid-error');\n target?.setAttribute('data-mermaid-error', message);\n }\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"]}
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';\n// TYPE-ONLY — erased at compile time, so it costs nothing at runtime. The mermaid engine\n// itself is fetched on demand; see `loadMermaid()`.\nimport type { Mermaid } from 'mermaid';\nimport {\n MarkdownEngine,\n MarkdownConfig,\n DEFAULT_MARKDOWN_CONFIG,\n ResolvedMarkdownConfig,\n HeadingInfo,\n HighlightFunction\n} from '@memberjunction/markdown-core';\nimport DOMPurify from 'dompurify';\nimport { escapeHtml } 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/**\n * The DOMPurify instance used for rendered HTML, created once per window and configured\n * once. A private instance rather than the shared default so the hooks below cannot leak\n * into other packages' DOMPurify usage, and so the configuration is parsed once instead\n * of on every render.\n *\n * Profile: HTML + SVG + SVG filters, which keeps structural markup, inline styles, data\n * attributes and inline vector graphics and removes the script vectors. Deviations from\n * DOMPurify's defaults, each deliberate:\n * - FORCE_BODY: a document that starts with `<style>` keeps it (otherwise the parser hoists\n * a leading style element into <head> and it is dropped); agent-authored mockups start\n * that way.\n * - SANITIZE_DOM off: DOMPurify would strip any id or name that collides with a document\n * or form property (`title`, `name`, `location`, ...), which are exactly the slugs\n * heading ids produce. This app does not read globals off `window` by element id, so\n * DOM clobbering has no effect here, and heading anchors matter.\n * - `target` on links is kept, with `rel=\"noopener noreferrer\"` enforced when it is set.\n * - `<use>` is allowed only with a same-document reference (`#id`); external references\n * are removed. SMIL `<animate>`/`<set>` stay disallowed: they can rewrite `href`.\n */\nlet purifier: ReturnType<typeof DOMPurify> | null = null;\n\nfunction getPurifier(): ReturnType<typeof DOMPurify> | null {\n if (purifier) return purifier;\n if (typeof window === 'undefined') return null;\n const instance = DOMPurify(window);\n if (!instance.isSupported) return null;\n instance.setConfig({\n USE_PROFILES: { html: true, svg: true, svgFilters: true },\n ADD_TAGS: ['use'],\n ADD_ATTR: ['target'],\n FORCE_BODY: true,\n SANITIZE_DOM: false,\n });\n instance.addHook('uponSanitizeAttribute', (node, data) => {\n const name = data.attrName.toLowerCase();\n if (node.nodeName.toLowerCase() === 'use' && (name === 'href' || name === 'xlink:href')) {\n // Same-document sprite references only. Anything else can load an external document.\n data.keepAttr = data.attrValue.trim().startsWith('#');\n }\n });\n instance.addHook('afterSanitizeAttributes', (node) => {\n const tag = node.nodeName.toLowerCase();\n if (tag === 'a' && node.hasAttribute('target')) {\n node.setAttribute('rel', 'noopener noreferrer');\n }\n if (tag === 'use') {\n // The attribute hook above has already dropped any non-fragment reference; a <use>\n // with nothing left to reference is removed rather than left as an empty element.\n const ref = node.getAttribute('href') ?? node.getAttribute('xlink:href');\n if (!ref || !ref.trim().startsWith('#')) {\n node.parentNode?.removeChild(node);\n }\n }\n });\n purifier = instance;\n return purifier;\n}\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 /** The mermaid engine, once its lazy chunk has landed. Null until a diagram is actually rendered. */\n private mermaidEngine: Mermaid | null = null;\n /** In-flight load, so N diagrams in one document share ONE chunk fetch. */\n private mermaidLoad: Promise<Mermaid> | null = null;\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 // The service is the one place every consumer passes through (the component, the chat\n // widget, anything binding parse() output to innerHTML), so the sanitizer runs here.\n // enableJavaScript is the explicit opt-out.\n if (!this.currentConfig.enableJavaScript) {\n html = this.sanitizeHtml(html);\n }\n\n return html;\n }\n\n /**\n * Sanitize rendered HTML for binding to innerHTML while preserving layout HTML, inline\n * styles and inline SVG. See the DOMPurify configuration at the top of this file for\n * what is kept and removed. Where no DOM is available to sanitize with, the markup is\n * escaped and rendered as text rather than trusted.\n */\n public sanitizeHtml(html: string): string {\n const instance = getPurifier();\n if (!instance) {\n return escapeHtml(html);\n }\n return instance.sanitize(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 * Fetch the mermaid engine, on first use only.\n *\n * MEASURED BUNDLE DEFERRAL — this is deliberately a dynamic `import()`, NOT a top-level\n * one, and must not be \"tidied\" back into a static import. `mermaid` and the parsing /\n * graph engines it pulls with it (`@mermaid-js/parser`, `langium`, `chevrotain`,\n * `cytoscape`, `katex`, `d3`) were ~0.95 MB raw of every host's initial download even\n * when no document ever contained a diagram. That is a rounding error for an in-app\n * host and the whole cost for a CDN-loaded embedding of the realtime session overlay\n * (MJ #3882). Documents with no diagram now never fetch the chunk at all.\n *\n * The in-flight promise is cached so N diagrams in one document share one fetch; a\n * FAILED load clears it, so a later render can retry. Retries are bounded by\n * user-driven render calls — there is no loop here.\n */\n private async loadMermaid(): Promise<Mermaid> {\n if (this.mermaidEngine) {\n return this.mermaidEngine;\n }\n\n this.mermaidLoad ??= import('mermaid').then((module) => module.default);\n\n try {\n this.mermaidEngine = await this.mermaidLoad;\n return this.mermaidEngine;\n } catch (error) {\n this.mermaidLoad = null;\n throw error;\n }\n }\n\n /**\n * Initialize Mermaid with the current theme configuration.\n * Re-initializes when the effective theme changes.\n * @param mermaid The lazily-loaded mermaid engine (see {@link loadMermaid})\n */\n private initializeMermaid(mermaid: Mermaid): 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 // Find all mermaid code blocks BEFORE touching the engine — this is the branch the\n // deferral hangs off: no diagrams in this document means no chunk fetch at all.\n const mermaidBlocks = container.querySelectorAll('pre > code.language-mermaid, .mermaid');\n\n if (mermaidBlocks.length === 0) return false;\n\n let mermaid: Mermaid;\n try {\n mermaid = await this.loadMermaid();\n } catch (error) {\n // The chunk could not be fetched (offline, blocked host, bad deploy). Say so on\n // every block we were going to render — an empty panel would look like the diagram\n // simply wasn't there.\n console.error('Failed to load the Mermaid diagram engine:', error);\n this.markMermaidBlocksFailed(mermaidBlocks, 'Diagram engine failed to load');\n return false;\n }\n\n this.initializeMermaid(mermaid);\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 // Show it failed on this block — the source stays visible underneath.\n this.markMermaidBlocksFailed([block], 'Diagram rendering failed');\n }\n }\n\n return true;\n }\n\n /**\n * Mark mermaid blocks as un-renderable, so the failure is VISIBLE rather than a block of\n * raw diagram source the reader can't explain. The `.mermaid-error` class carries the\n * error styling and a default caption; `data-mermaid-error` overrides that caption with\n * the specific reason (see `.mermaid-error[data-mermaid-error]::before` in the component\n * stylesheet).\n *\n * @param blocks The mermaid code blocks (or `.mermaid` elements) that could not be rendered\n * @param message Short reason shown above the un-rendered source\n */\n private markMermaidBlocksFailed(blocks: ArrayLike<Element>, message: string): void {\n for (let i = 0; i < blocks.length; i++) {\n const block = blocks[i];\n const target = block.tagName === 'CODE' ? block.parentElement : block;\n target?.classList.add('mermaid-error');\n target?.setAttribute('data-mermaid-error', message);\n }\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. The nodes are built inside a\n // <template> of the inert DOMParser document, never on the live `document`:\n // setting innerHTML on a live element fires image error handlers at parse time,\n // before the sanitizer in parse() has seen the markup. A <template> also parses\n // in body context, so a mockup that starts with <style> keeps it (a full-document\n // parse would hoist it into <head> and drop it).\n const template = doc.createElement('template');\n template.innerHTML = content;\n pre.replaceWith(template.content);\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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memberjunction/ng-markdown",
3
- "version": "6.1.0-edge.5",
3
+ "version": "6.1.0-edge.7",
4
4
  "description": "MemberJunction: Lightweight Angular markdown component with Prism.js highlighting, Mermaid diagrams, and extensible features",
5
5
  "main": "./dist/public-api.js",
6
6
  "typings": "./dist/public-api.d.ts",
@@ -18,21 +18,22 @@
18
18
  "author": "MemberJunction",
19
19
  "license": "BUSL-1.1",
20
20
  "devDependencies": {
21
- "@angular/compiler": "21.1.3",
22
- "@angular/compiler-cli": "21.1.3",
23
- "@memberjunction/ng-test-utils": "6.1.0-edge.5",
21
+ "@angular/compiler": "21.2.22",
22
+ "@angular/compiler-cli": "21.2.22",
23
+ "@memberjunction/ng-test-utils": "6.1.0-edge.7",
24
24
  "jsdom": "^26.1.0",
25
25
  "vitest": "^4.0.18"
26
26
  },
27
27
  "peerDependencies": {
28
- "@angular/common": "^21.1.3",
29
- "@angular/core": "^21.1.3",
30
- "@angular/platform-browser": "^21.1.3"
28
+ "@angular/common": "^21.2.22",
29
+ "@angular/core": "^21.2.22",
30
+ "@angular/platform-browser": "^21.2.22"
31
31
  },
32
32
  "dependencies": {
33
- "@memberjunction/markdown-core": "6.1.0-edge.5",
34
- "prismjs": "^1.30.0",
33
+ "@memberjunction/markdown-core": "6.1.0-edge.7",
34
+ "dompurify": "^3.3.0",
35
35
  "mermaid": "^11.12.2",
36
+ "prismjs": "^1.30.0",
36
37
  "tslib": "^2.8.1"
37
38
  },
38
39
  "sideEffects": true,