@memberjunction/ng-markdown 6.1.4 → 6.2.0-edge.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.
- package/dist/__tests__/web-dom-utils.test.js +36 -36
- package/dist/__tests__/web-dom-utils.test.js.map +1 -1
- package/dist/lib/components/markdown.component.d.ts +133 -21
- package/dist/lib/components/markdown.component.d.ts.map +1 -1
- package/dist/lib/components/markdown.component.js +304 -60
- package/dist/lib/components/markdown.component.js.map +1 -1
- package/dist/lib/extensions/code-copy.extension.d.ts +4 -0
- package/dist/lib/extensions/code-copy.extension.d.ts.map +1 -1
- package/dist/lib/extensions/code-copy.extension.js +10 -2
- package/dist/lib/extensions/code-copy.extension.js.map +1 -1
- package/dist/lib/extensions/collapsible-headings.extension.d.ts +8 -0
- package/dist/lib/extensions/collapsible-headings.extension.d.ts.map +1 -1
- package/dist/lib/extensions/collapsible-headings.extension.js +20 -4
- package/dist/lib/extensions/collapsible-headings.extension.js.map +1 -1
- package/dist/lib/extensions/svg-renderer.extension.d.ts +2 -0
- package/dist/lib/extensions/svg-renderer.extension.d.ts.map +1 -1
- package/dist/lib/extensions/svg-renderer.extension.js +5 -1
- package/dist/lib/extensions/svg-renderer.extension.js.map +1 -1
- package/dist/lib/services/markdown.service.d.ts +26 -0
- package/dist/lib/services/markdown.service.d.ts.map +1 -1
- package/dist/lib/services/markdown.service.js +68 -16
- package/dist/lib/services/markdown.service.js.map +1 -1
- package/package.json +3 -3
|
@@ -116,27 +116,31 @@ export class MarkdownService {
|
|
|
116
116
|
// Return code as-is if language not found or highlighting fails
|
|
117
117
|
return code;
|
|
118
118
|
};
|
|
119
|
-
this.
|
|
119
|
+
this.ConfigureMarked(this.currentConfig);
|
|
120
120
|
}
|
|
121
121
|
/**
|
|
122
122
|
* Configure the underlying engine with the provided options.
|
|
123
123
|
*/
|
|
124
|
-
|
|
124
|
+
ConfigureMarked(config) {
|
|
125
125
|
this.currentConfig = { ...DEFAULT_MARKDOWN_CONFIG, ...config };
|
|
126
126
|
this.engine.configureMarked(this.currentConfig, { highlightFn: this.prismHighlight });
|
|
127
127
|
}
|
|
128
|
+
/** @deprecated Use {@link ConfigureMarked}. */
|
|
129
|
+
configureMarked(config) {
|
|
130
|
+
return this.ConfigureMarked(config);
|
|
131
|
+
}
|
|
128
132
|
/**
|
|
129
133
|
* Parse markdown to HTML.
|
|
130
134
|
* @param markdown The markdown string to parse
|
|
131
135
|
* @param config Optional config overrides for this parse operation
|
|
132
136
|
* @returns The rendered HTML string
|
|
133
137
|
*/
|
|
134
|
-
|
|
138
|
+
Parse(markdown, config) {
|
|
135
139
|
if (!markdown)
|
|
136
140
|
return '';
|
|
137
141
|
// Apply config overrides if provided (re-injects the Prism highlighter)
|
|
138
142
|
if (config) {
|
|
139
|
-
this.
|
|
143
|
+
this.ConfigureMarked({ ...this.currentConfig, ...config });
|
|
140
144
|
}
|
|
141
145
|
let html = this.engine.parseToHtml(markdown);
|
|
142
146
|
// When HTML passthrough is enabled, fix incorrectly code-wrapped HTML.
|
|
@@ -149,28 +153,40 @@ export class MarkdownService {
|
|
|
149
153
|
// widget, anything binding parse() output to innerHTML), so the sanitizer runs here.
|
|
150
154
|
// enableJavaScript is the explicit opt-out.
|
|
151
155
|
if (!this.currentConfig.enableJavaScript) {
|
|
152
|
-
html = this.
|
|
156
|
+
html = this.SanitizeHtml(html);
|
|
153
157
|
}
|
|
154
158
|
return html;
|
|
155
159
|
}
|
|
160
|
+
/** @deprecated Use {@link Parse}. */
|
|
161
|
+
parse(markdown, config) {
|
|
162
|
+
return this.Parse(markdown, config);
|
|
163
|
+
}
|
|
156
164
|
/**
|
|
157
165
|
* Sanitize rendered HTML for binding to innerHTML while preserving layout HTML, inline
|
|
158
166
|
* styles and inline SVG. See the DOMPurify configuration at the top of this file for
|
|
159
167
|
* what is kept and removed. Where no DOM is available to sanitize with, the markup is
|
|
160
168
|
* escaped and rendered as text rather than trusted.
|
|
161
169
|
*/
|
|
162
|
-
|
|
170
|
+
SanitizeHtml(html) {
|
|
163
171
|
const instance = getPurifier();
|
|
164
172
|
if (!instance) {
|
|
165
173
|
return escapeHtml(html);
|
|
166
174
|
}
|
|
167
175
|
return instance.sanitize(html);
|
|
168
176
|
}
|
|
177
|
+
/** @deprecated Use {@link SanitizeHtml}. */
|
|
178
|
+
sanitizeHtml(html) {
|
|
179
|
+
return this.SanitizeHtml(html);
|
|
180
|
+
}
|
|
169
181
|
/**
|
|
170
182
|
* Parse markdown asynchronously (useful for large documents)
|
|
171
183
|
*/
|
|
184
|
+
async ParseAsync(markdown, config) {
|
|
185
|
+
return this.Parse(markdown, config);
|
|
186
|
+
}
|
|
187
|
+
/** @deprecated Use {@link ParseAsync}. */
|
|
172
188
|
async parseAsync(markdown, config) {
|
|
173
|
-
return this.
|
|
189
|
+
return this.ParseAsync(markdown, config);
|
|
174
190
|
}
|
|
175
191
|
/**
|
|
176
192
|
* Resolve the effective mermaid theme.
|
|
@@ -239,7 +255,7 @@ export class MarkdownService {
|
|
|
239
255
|
* Call this after the HTML has been inserted into the DOM
|
|
240
256
|
* @param container The DOM element containing mermaid code blocks
|
|
241
257
|
*/
|
|
242
|
-
async
|
|
258
|
+
async RenderMermaid(container) {
|
|
243
259
|
if (!this.currentConfig.enableMermaid)
|
|
244
260
|
return false;
|
|
245
261
|
// Find all mermaid code blocks BEFORE touching the engine — this is the branch the
|
|
@@ -286,6 +302,10 @@ export class MarkdownService {
|
|
|
286
302
|
}
|
|
287
303
|
return true;
|
|
288
304
|
}
|
|
305
|
+
/** @deprecated Use {@link RenderMermaid}. */
|
|
306
|
+
async renderMermaid(container) {
|
|
307
|
+
return this.RenderMermaid(container);
|
|
308
|
+
}
|
|
289
309
|
/**
|
|
290
310
|
* Mark mermaid blocks as un-renderable, so the failure is VISIBLE rather than a block of
|
|
291
311
|
* raw diagram source the reader can't explain. The `.mermaid-error` class carries the
|
|
@@ -309,17 +329,21 @@ export class MarkdownService {
|
|
|
309
329
|
* Call this after the HTML has been inserted into the DOM
|
|
310
330
|
* @param container The DOM element containing code blocks
|
|
311
331
|
*/
|
|
312
|
-
|
|
332
|
+
HighlightCode(container) {
|
|
313
333
|
if (!this.currentConfig.enableHighlight)
|
|
314
334
|
return;
|
|
315
335
|
// Prism.highlightAllUnder handles finding and highlighting code blocks
|
|
316
336
|
Prism.highlightAllUnder(container);
|
|
317
337
|
}
|
|
338
|
+
/** @deprecated Use {@link HighlightCode}. */
|
|
339
|
+
highlightCode(container) {
|
|
340
|
+
return this.HighlightCode(container);
|
|
341
|
+
}
|
|
318
342
|
/**
|
|
319
343
|
* Add copy buttons to code blocks
|
|
320
344
|
* @param container The DOM element containing code blocks
|
|
321
345
|
*/
|
|
322
|
-
|
|
346
|
+
AddCodeCopyButtons(container) {
|
|
323
347
|
if (!this.currentConfig.enableCodeCopy)
|
|
324
348
|
return;
|
|
325
349
|
const codeBlocks = container.querySelectorAll('pre > code');
|
|
@@ -361,46 +385,74 @@ export class MarkdownService {
|
|
|
361
385
|
pre.appendChild(toolbar);
|
|
362
386
|
});
|
|
363
387
|
}
|
|
388
|
+
/** @deprecated Use {@link AddCodeCopyButtons}. */
|
|
389
|
+
addCodeCopyButtons(container) {
|
|
390
|
+
return this.AddCodeCopyButtons(container);
|
|
391
|
+
}
|
|
364
392
|
/**
|
|
365
393
|
* Initialize collapsible heading functionality
|
|
366
394
|
* This method is a no-op - the component handles event binding
|
|
367
395
|
* @param container The DOM element containing collapsible sections
|
|
368
396
|
*/
|
|
369
|
-
|
|
397
|
+
InitializeCollapsibleHeadings(_container) {
|
|
370
398
|
// Event binding is handled by the component's setupCollapsibleListeners
|
|
371
399
|
// This method exists for API compatibility but does nothing
|
|
372
400
|
}
|
|
401
|
+
/** @deprecated Use {@link InitializeCollapsibleHeadings}. */
|
|
402
|
+
initializeCollapsibleHeadings(_container) {
|
|
403
|
+
return this.InitializeCollapsibleHeadings(_container);
|
|
404
|
+
}
|
|
373
405
|
/**
|
|
374
406
|
* Get the list of headings from the last parsed document
|
|
375
407
|
* Useful for building table of contents
|
|
376
408
|
*/
|
|
377
|
-
|
|
409
|
+
GetHeadingList() {
|
|
378
410
|
return this.engine.getHeadingList();
|
|
379
411
|
}
|
|
412
|
+
/** @deprecated Use {@link GetHeadingList}. */
|
|
413
|
+
getHeadingList() {
|
|
414
|
+
return this.GetHeadingList();
|
|
415
|
+
}
|
|
380
416
|
/**
|
|
381
417
|
* Get the current configuration
|
|
382
418
|
*/
|
|
383
|
-
|
|
419
|
+
GetConfig() {
|
|
384
420
|
return this.engine.getConfig();
|
|
385
421
|
}
|
|
422
|
+
/** @deprecated Use {@link GetConfig}. */
|
|
423
|
+
getConfig() {
|
|
424
|
+
return this.GetConfig();
|
|
425
|
+
}
|
|
386
426
|
/**
|
|
387
427
|
* Reset configuration to defaults
|
|
388
428
|
*/
|
|
429
|
+
ResetConfig() {
|
|
430
|
+
this.ConfigureMarked(DEFAULT_MARKDOWN_CONFIG);
|
|
431
|
+
}
|
|
432
|
+
/** @deprecated Use {@link ResetConfig}. */
|
|
389
433
|
resetConfig() {
|
|
390
|
-
this.
|
|
434
|
+
return this.ResetConfig();
|
|
391
435
|
}
|
|
392
436
|
/**
|
|
393
437
|
* Check if a language is supported by Prism
|
|
394
438
|
*/
|
|
395
|
-
|
|
439
|
+
IsLanguageSupported(lang) {
|
|
396
440
|
return !!Prism.languages[lang];
|
|
397
441
|
}
|
|
442
|
+
/** @deprecated Use {@link IsLanguageSupported}. */
|
|
443
|
+
isLanguageSupported(lang) {
|
|
444
|
+
return this.IsLanguageSupported(lang);
|
|
445
|
+
}
|
|
398
446
|
/**
|
|
399
447
|
* Get list of supported Prism languages
|
|
400
448
|
*/
|
|
401
|
-
|
|
449
|
+
GetSupportedLanguages() {
|
|
402
450
|
return Object.keys(Prism.languages).filter(lang => typeof Prism.languages[lang] === 'object');
|
|
403
451
|
}
|
|
452
|
+
/** @deprecated Use {@link GetSupportedLanguages}. */
|
|
453
|
+
getSupportedLanguages() {
|
|
454
|
+
return this.GetSupportedLanguages();
|
|
455
|
+
}
|
|
404
456
|
/**
|
|
405
457
|
* Fix HTML that was incorrectly wrapped in <pre><code> blocks by marked.
|
|
406
458
|
* This happens when marked interprets inline HTML (especially indented HTML)
|
|
@@ -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';\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 < or > it's probably escaped HTML being shown\n if (content.includes('<') || content.includes('>')) 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 /** @deprecated Use {@link ConfigureMarked}. */\n public configureMarked(config: MarkdownConfig): void {\n return this.ConfigureMarked(config);\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 /** @deprecated Use {@link Parse}. */\n public parse(markdown: string, config?: Partial<MarkdownConfig>): string {\n return this.Parse(markdown, config);\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 /** @deprecated Use {@link SanitizeHtml}. */\n public sanitizeHtml(html: string): string {\n return this.SanitizeHtml(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 /** @deprecated Use {@link ParseAsync}. */\n public async parseAsync(markdown: string, config?: Partial<MarkdownConfig>): Promise<string> {\n return this.ParseAsync(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 /** @deprecated Use {@link RenderMermaid}. */\n public async renderMermaid(container: HTMLElement): Promise<boolean> {\n return this.RenderMermaid(container);\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 /** @deprecated Use {@link HighlightCode}. */\n public highlightCode(container: HTMLElement): void {\n return this.HighlightCode(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 /** @deprecated Use {@link AddCodeCopyButtons}. */\n public addCodeCopyButtons(container: HTMLElement): void {\n return this.AddCodeCopyButtons(container);\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 /** @deprecated Use {@link InitializeCollapsibleHeadings}. */\n public initializeCollapsibleHeadings(_container: HTMLElement): void {\n return this.InitializeCollapsibleHeadings(_container);\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 /** @deprecated Use {@link GetHeadingList}. */\n public getHeadingList(): HeadingInfo[] {\n return this.GetHeadingList();\n }\n\n /**\n * Get the current configuration\n */\n public GetConfig(): ResolvedMarkdownConfig {\n return this.engine.getConfig();\n }\n\n /** @deprecated Use {@link GetConfig}. */\n public getConfig(): ResolvedMarkdownConfig {\n return this.GetConfig();\n }\n\n /**\n * Reset configuration to defaults\n */\n public ResetConfig(): void {\n this.ConfigureMarked(DEFAULT_MARKDOWN_CONFIG);\n }\n\n /** @deprecated Use {@link ResetConfig}. */\n public resetConfig(): void {\n return this.ResetConfig();\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 /** @deprecated Use {@link IsLanguageSupported}. */\n public isLanguageSupported(lang: string): boolean {\n return this.IsLanguageSupported(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 /** @deprecated Use {@link GetSupportedLanguages}. */\n public getSupportedLanguages(): string[] {\n return this.GetSupportedLanguages();\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 < or > it's probably escaped HTML being shown\n if (content.includes('<') || content.includes('>')) 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
|
|
3
|
+
"version": "6.2.0-edge.1",
|
|
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",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"devDependencies": {
|
|
21
21
|
"@angular/compiler": "21.2.22",
|
|
22
22
|
"@angular/compiler-cli": "21.2.22",
|
|
23
|
-
"@memberjunction/ng-test-utils": "6.1
|
|
23
|
+
"@memberjunction/ng-test-utils": "6.2.0-edge.1",
|
|
24
24
|
"jsdom": "^26.1.0",
|
|
25
25
|
"vitest": "^4.0.18"
|
|
26
26
|
},
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"@angular/platform-browser": "^21.2.22"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@memberjunction/markdown-core": "6.1
|
|
33
|
+
"@memberjunction/markdown-core": "6.2.0-edge.1",
|
|
34
34
|
"dompurify": "^3.3.0",
|
|
35
35
|
"mermaid": "^11.12.2",
|
|
36
36
|
"prismjs": "^1.30.0",
|