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

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.
@@ -571,11 +571,11 @@ export class MarkdownComponent {
571
571
  } if (rf & 2) {
572
572
  i0.ɵɵclassMap(ctx.containerClass);
573
573
  i0.ɵɵproperty("innerHTML", ctx.renderedContent, i0.ɵɵsanitizeHtml);
574
- } }, styles: ["/**\n * MJ Markdown Component Styles\n *\n * These styles apply to rendered markdown content.\n * Using ViewEncapsulation.None so styles penetrate into dynamically generated content.\n */\n\n/* ============================================\n Container\n ============================================ */\n.mj-markdown-container {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;\n font-size: 15px;\n line-height: 1.6;\n color: var(--mj-text-primary);\n word-wrap: break-word;\n}\n\n/* ============================================\n Typography\n ============================================ */\n.mj-markdown-container h1,\n.mj-markdown-container h2,\n.mj-markdown-container h3,\n.mj-markdown-container h4,\n.mj-markdown-container h5,\n.mj-markdown-container h6 {\n margin-top: 1.5em;\n margin-bottom: 0.5em;\n font-weight: 600;\n line-height: 1.25;\n color: var(--mj-text-primary);\n}\n\n.mj-markdown-container h1 { font-size: 1.75em; }\n.mj-markdown-container h2 { font-size: 1.5em; }\n.mj-markdown-container h3 { font-size: 1.25em; }\n.mj-markdown-container h4 { font-size: 1.1em; }\n.mj-markdown-container h5 { font-size: 1em; }\n.mj-markdown-container h6 { font-size: 0.9em; color: var(--mj-text-secondary); }\n\n.mj-markdown-container > h1:first-child,\n.mj-markdown-container > h2:first-child,\n.mj-markdown-container > h3:first-child {\n margin-top: 0;\n}\n\n.mj-markdown-container p {\n margin: 0 0 1em 0;\n}\n\n.mj-markdown-container > p:last-child {\n margin-bottom: 0;\n}\n\n.mj-markdown-container a {\n color: var(--mj-brand-primary);\n text-decoration: none;\n}\n\n.mj-markdown-container a:hover {\n text-decoration: underline;\n}\n\n.mj-markdown-container strong {\n font-weight: 600;\n}\n\n.mj-markdown-container em {\n font-style: italic;\n}\n\n/* ============================================\n Lists\n ============================================ */\n.mj-markdown-container ul,\n.mj-markdown-container ol {\n margin: 0 0 1em 0;\n padding-left: 2em;\n}\n\n.mj-markdown-container li {\n margin-bottom: 0.25em;\n}\n\n.mj-markdown-container li > ul,\n.mj-markdown-container li > ol {\n margin-top: 0.25em;\n margin-bottom: 0;\n}\n\n/* Task lists */\n.mj-markdown-container ul.contains-task-list {\n list-style: none;\n padding-left: 0;\n}\n\n.mj-markdown-container li.task-list-item {\n display: flex;\n align-items: flex-start;\n gap: 0.5em;\n}\n\n.mj-markdown-container li.task-list-item input[type=\"checkbox\"] {\n margin-top: 0.35em;\n}\n\n/* ============================================\n Blockquotes\n ============================================ */\n.mj-markdown-container blockquote {\n margin: 1em 0;\n padding: 0.5em 1em;\n border-left: 4px solid var(--mj-border-default);\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-secondary);\n}\n\n.mj-markdown-container blockquote > p:last-child {\n margin-bottom: 0;\n}\n\n/* ============================================\n Code Blocks\n ============================================ */\n.mj-markdown-container code {\n font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', 'Courier New', monospace;\n font-size: 0.9em;\n}\n\n/* Inline code */\n.mj-markdown-container :not(pre) > code {\n padding: 0.2em 0.4em;\n background: var(--mj-bg-surface-sunken);\n border-radius: 4px;\n color: var(--mj-status-error);\n}\n\n/* Code blocks\n * design-token-exempt fallbacks: --mj-bg-code-block / --mj-text-code resolve to\n * Monaco-style dark editor colors when an app does not override them. Code\n * blocks intentionally use a fixed dark editor aesthetic (same exemption as\n * Monaco/CodeMirror), so the hex fallbacks here are deliberate. */\n.mj-markdown-container pre {\n margin: 1em 0;\n padding: 1em;\n background: var(--mj-bg-code-block, #1e1e1e);\n border-radius: var(--mj-radius-md);\n overflow-x: auto;\n position: relative;\n}\n\n.mj-markdown-container pre code {\n background: transparent;\n padding: 0;\n color: var(--mj-text-code, #d4d4d4);\n font-size: 0.875em;\n line-height: 1.5;\n}\n\n/* ============================================\n Code Toolbar (copy button, language label)\n ============================================ */\n.mj-markdown-container .code-toolbar {\n position: absolute;\n top: 8px;\n right: 8px;\n display: flex;\n align-items: center;\n gap: 8px;\n z-index: 10;\n}\n\n.mj-markdown-container .code-language-label {\n font-size: 11px;\n font-weight: 500;\n color: var(--mj-text-disabled);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n user-select: none;\n}\n\n.mj-markdown-container .code-copy-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 32px;\n height: 32px;\n padding: 0;\n background: rgba(255, 255, 255, 0.1);\n border: 1px solid rgba(255, 255, 255, 0.15);\n border-radius: var(--mj-radius-md);\n color: var(--mj-text-disabled);\n cursor: pointer;\n transition: all 0.2s ease;\n opacity: 0;\n}\n\n.mj-markdown-container pre:hover .code-copy-btn {\n opacity: 1;\n}\n\n.mj-markdown-container .code-copy-btn:hover {\n background: rgba(255, 255, 255, 0.2);\n color: var(--mj-text-inverse);\n}\n\n.mj-markdown-container .code-copy-btn.copied {\n background: color-mix(in srgb, var(--mj-status-success) 20%, transparent);\n border-color: color-mix(in srgb, var(--mj-status-success) 30%, transparent);\n color: var(--mj-status-success);\n}\n\n.mj-markdown-container .code-copy-btn.error {\n background: color-mix(in srgb, var(--mj-status-error) 20%, transparent);\n border-color: color-mix(in srgb, var(--mj-status-error) 30%, transparent);\n color: var(--mj-status-error);\n}\n\n/* ============================================\n Tables\n ============================================ */\n.mj-markdown-container table {\n width: 100%;\n margin: 1em 0;\n border-collapse: collapse;\n border-spacing: 0;\n}\n\n.mj-markdown-container th,\n.mj-markdown-container td {\n padding: 0.5em 1em;\n border: 1px solid var(--mj-border-default);\n text-align: left;\n}\n\n.mj-markdown-container th {\n background: var(--mj-bg-surface-sunken);\n font-weight: 600;\n}\n\n.mj-markdown-container tr:nth-child(even) {\n background: var(--mj-bg-surface-sunken);\n}\n\n/* ============================================\n Horizontal Rule\n ============================================ */\n.mj-markdown-container hr {\n margin: 2em 0;\n border: none;\n border-top: 1px solid var(--mj-border-default);\n}\n\n/* ============================================\n Images\n ============================================ */\n.mj-markdown-container img {\n max-width: 100%;\n height: auto;\n border-radius: 4px;\n}\n\n/* ============================================\n GitHub-style Alerts (marked-alert)\n ============================================ */\n.mj-markdown-container .markdown-alert {\n margin: 1em 0;\n padding: 1em;\n border-radius: 8px;\n border-left: 4px solid;\n}\n\n.mj-markdown-container .markdown-alert-title {\n display: flex;\n align-items: center;\n gap: 8px;\n font-weight: 600;\n margin-bottom: 0.5em;\n}\n\n.mj-markdown-container .markdown-alert > p:last-child {\n margin-bottom: 0;\n}\n\n/* Note */\n.mj-markdown-container .markdown-alert-note {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n border-color: var(--mj-brand-primary);\n}\n\n.mj-markdown-container .markdown-alert-note .markdown-alert-title {\n color: var(--mj-brand-primary);\n}\n\n/* Tip */\n.mj-markdown-container .markdown-alert-tip {\n background: color-mix(in srgb, var(--mj-status-success) 10%, var(--mj-bg-surface));\n border-color: var(--mj-status-success);\n}\n\n.mj-markdown-container .markdown-alert-tip .markdown-alert-title {\n color: var(--mj-status-success);\n}\n\n/* Important */\n.mj-markdown-container .markdown-alert-important {\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface));\n border-color: var(--mj-brand-primary);\n}\n\n.mj-markdown-container .markdown-alert-important .markdown-alert-title {\n color: var(--mj-brand-primary);\n}\n\n/* Warning */\n.mj-markdown-container .markdown-alert-warning {\n background: color-mix(in srgb, var(--mj-status-warning) 10%, var(--mj-bg-surface));\n border-color: var(--mj-status-warning);\n}\n\n.mj-markdown-container .markdown-alert-warning .markdown-alert-title {\n color: var(--mj-status-warning);\n}\n\n/* Caution */\n.mj-markdown-container .markdown-alert-caution {\n background: color-mix(in srgb, var(--mj-status-error) 10%, var(--mj-bg-surface));\n border-color: var(--mj-status-error);\n}\n\n.mj-markdown-container .markdown-alert-caution .markdown-alert-title {\n color: var(--mj-status-error);\n}\n\n/* ============================================\n Collapsible Headings\n ============================================ */\n.mj-markdown-container .collapsible-section {\n margin: 0.75em 0;\n border-left: 2px solid transparent;\n transition: border-color 0.2s ease;\n}\n\n.mj-markdown-container .collapsible-section:hover {\n border-left-color: var(--mj-border-default);\n}\n\n/* Heading wrapper - contains toggle and heading */\n.mj-markdown-container .collapsible-heading-wrapper {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 4px 0;\n border-radius: 4px;\n transition: background-color 0.15s ease;\n user-select: none;\n}\n\n.mj-markdown-container .collapsible-heading-wrapper:hover {\n background-color: color-mix(in srgb, var(--mj-text-primary) 3%, transparent);\n}\n\n/* The heading itself */\n.mj-markdown-container .collapsible-heading {\n margin: 0 !important;\n flex: 1;\n}\n\n/* Toggle button/icon */\n.mj-markdown-container .collapsible-toggle {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 20px;\n flex-shrink: 0;\n color: var(--mj-text-muted);\n transition: transform 0.2s ease, color 0.15s ease;\n}\n\n.mj-markdown-container .collapsible-toggle svg {\n width: 14px;\n height: 14px;\n}\n\n.mj-markdown-container .collapsible-heading-wrapper:hover .collapsible-toggle {\n color: var(--mj-text-secondary);\n}\n\n/* Expanded state - arrow points down */\n.mj-markdown-container .collapsible-section:not(.collapsed) .collapsible-toggle {\n transform: rotate(90deg);\n}\n\n/* Collapsed state - arrow points right (default SVG orientation) */\n.mj-markdown-container .collapsible-section.collapsed .collapsible-toggle {\n transform: rotate(0deg);\n}\n\n/* Content area */\n.mj-markdown-container .collapsible-content {\n padding-left: 26px;\n overflow: hidden;\n max-height: 5000px; /* Large enough for most content */\n opacity: 1;\n transition: max-height 0.3s ease-out, opacity 0.2s ease-out, padding 0.2s ease;\n}\n\n.mj-markdown-container .collapsible-section.collapsed .collapsible-content {\n max-height: 0;\n opacity: 0;\n padding-top: 0;\n padding-bottom: 0;\n visibility: hidden;\n}\n\n/* Nested sections - indent further */\n.mj-markdown-container .collapsible-section .collapsible-section {\n margin-left: 0;\n}\n\n/* Focus styles for accessibility */\n.mj-markdown-container .collapsible-heading-wrapper:focus-within {\n outline: 2px solid var(--mj-brand-primary);\n outline-offset: 2px;\n}\n\n.mj-markdown-container .collapsible-toggle:focus {\n outline: none;\n}\n\n/* Action buttons container - expand/collapse all */\n.mj-markdown-container .collapsible-actions {\n display: flex;\n align-items: center;\n gap: 2px;\n margin-left: auto;\n padding-left: 12px;\n opacity: 0;\n transition: opacity 0.15s ease;\n}\n\n.mj-markdown-container .collapsible-heading-wrapper:hover .collapsible-actions {\n opacity: 1;\n}\n\n/* Individual action buttons */\n.mj-markdown-container .collapsible-action-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 22px;\n height: 22px;\n padding: 0;\n border: none;\n border-radius: 4px;\n background: transparent;\n color: var(--mj-text-muted);\n cursor: pointer;\n transition: background-color 0.15s ease, color 0.15s ease, transform 0.1s ease;\n}\n\n.mj-markdown-container .collapsible-action-btn:hover {\n background-color: color-mix(in srgb, var(--mj-text-primary) 8%, transparent);\n color: var(--mj-text-primary);\n}\n\n.mj-markdown-container .collapsible-action-btn:active {\n transform: scale(0.92);\n}\n\n.mj-markdown-container .collapsible-action-btn svg {\n width: 14px;\n height: 14px;\n}\n\n/* Expand all button - double down chevron */\n.mj-markdown-container .collapsible-action-btn.expand-all:hover {\n color: var(--mj-status-success);\n background-color: color-mix(in srgb, var(--mj-status-success) 10%, transparent);\n}\n\n/* Collapse all button - double up chevron */\n.mj-markdown-container .collapsible-action-btn.collapse-all:hover {\n color: var(--mj-status-error);\n background-color: color-mix(in srgb, var(--mj-status-error) 10%, transparent);\n}\n\n/* ============================================\n Mermaid Diagrams\n ============================================ */\n.mj-markdown-container .mermaid-diagram {\n margin: 1em 0;\n padding: 1em;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n text-align: center;\n overflow-x: auto;\n}\n\n.mj-markdown-container .mermaid-diagram svg {\n max-width: 100%;\n height: auto;\n}\n\n.mj-markdown-container .mermaid-error {\n background: color-mix(in srgb, var(--mj-status-error) 10%, var(--mj-bg-surface));\n border-color: color-mix(in srgb, var(--mj-status-error) 30%, var(--mj-bg-surface));\n}\n\n.mj-markdown-container .mermaid-error::before {\n content: 'Diagram rendering failed';\n display: block;\n color: var(--mj-status-error);\n font-size: 12px;\n margin-bottom: 8px;\n}\n\n/* ============================================\n Heading Anchors\n ============================================ */\n.mj-markdown-container h1[id],\n.mj-markdown-container h2[id],\n.mj-markdown-container h3[id],\n.mj-markdown-container h4[id],\n.mj-markdown-container h5[id],\n.mj-markdown-container h6[id] {\n scroll-margin-top: 1em;\n}\n\n.mj-markdown-container h1[id]:hover,\n.mj-markdown-container h2[id]:hover,\n.mj-markdown-container h3[id]:hover,\n.mj-markdown-container h4[id]:hover,\n.mj-markdown-container h5[id]:hover,\n.mj-markdown-container h6[id]:hover {\n cursor: pointer;\n}\n\n/* Anchor link indicator on hover */\n.mj-markdown-container h1[id]::before,\n.mj-markdown-container h2[id]::before,\n.mj-markdown-container h3[id]::before,\n.mj-markdown-container h4[id]::before,\n.mj-markdown-container h5[id]::before,\n.mj-markdown-container h6[id]::before {\n content: '#';\n position: absolute;\n left: -1.5em;\n color: var(--mj-text-muted);\n opacity: 0;\n transition: opacity 0.2s ease;\n}\n\n.mj-markdown-container h1[id]:hover::before,\n.mj-markdown-container h2[id]:hover::before,\n.mj-markdown-container h3[id]:hover::before,\n.mj-markdown-container h4[id]:hover::before,\n.mj-markdown-container h5[id]:hover::before,\n.mj-markdown-container h6[id]:hover::before {\n opacity: 1;\n}\n\n/* ============================================\n Line Numbers (optional)\n ============================================ */\n.mj-markdown-container pre.line-numbers {\n padding-left: 3.5em;\n counter-reset: line;\n}\n\n.mj-markdown-container pre.line-numbers code {\n display: block;\n}\n\n.mj-markdown-container pre.line-numbers code .line::before {\n counter-increment: line;\n content: counter(line);\n display: inline-block;\n width: 2em;\n margin-left: -3em;\n margin-right: 1em;\n text-align: right;\n color: var(--mj-text-muted);\n user-select: none;\n}\n\n/* ============================================\n Error State\n ============================================ */\n.mj-markdown-container .markdown-error {\n background: color-mix(in srgb, var(--mj-status-error) 10%, var(--mj-bg-surface));\n color: var(--mj-status-error);\n padding: 1em;\n border-radius: 8px;\n border: 1px solid color-mix(in srgb, var(--mj-status-error) 30%, var(--mj-bg-surface));\n white-space: pre-wrap;\n font-family: monospace;\n}\n\n/* Dark Mode Support \u2014 handled automatically by --mj-* semantic tokens */\n\n/* ============================================\n SVG Rendered Content\n ============================================ */\n.mj-markdown-container .svg-rendered {\n margin: 1em 0;\n padding: 1em;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n text-align: center;\n overflow: auto;\n}\n\n.mj-markdown-container .svg-rendered svg {\n max-width: 100%;\n height: auto;\n}\n\n/* Dark mode for SVG rendered content \u2014 handled automatically by --mj-* semantic tokens */\n\n/* ============================================\n Print Styles\n ============================================ */\n@media print {\n .mj-markdown-container .code-toolbar {\n display: none;\n }\n\n .mj-markdown-container .collapsible-section.collapsed .collapsible-content {\n max-height: none;\n opacity: 1;\n visibility: visible;\n }\n\n /* design-token-exempt: print always uses light background + dark text for\n paper readability, regardless of the screen theme the user is rendering from */\n .mj-markdown-container pre {\n background: #f3f4f6 !important;\n color: #1f2937 !important;\n }\n\n .mj-markdown-container pre code {\n color: #1f2937 !important;\n }\n}\n"], encapsulation: 2, changeDetection: 0 }); }
574
+ } }, styles: ["/**\n * MJ Markdown Component Styles\n *\n * These styles apply to rendered markdown content.\n * Using ViewEncapsulation.None so styles penetrate into dynamically generated content.\n */\n\n/* ============================================\n Container\n ============================================ */\n.mj-markdown-container {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;\n font-size: 15px;\n line-height: 1.6;\n color: var(--mj-text-primary);\n word-wrap: break-word;\n}\n\n/* ============================================\n Typography\n ============================================ */\n.mj-markdown-container h1,\n.mj-markdown-container h2,\n.mj-markdown-container h3,\n.mj-markdown-container h4,\n.mj-markdown-container h5,\n.mj-markdown-container h6 {\n margin-top: 1.5em;\n margin-bottom: 0.5em;\n font-weight: 600;\n line-height: 1.25;\n color: var(--mj-text-primary);\n}\n\n.mj-markdown-container h1 { font-size: 1.75em; }\n.mj-markdown-container h2 { font-size: 1.5em; }\n.mj-markdown-container h3 { font-size: 1.25em; }\n.mj-markdown-container h4 { font-size: 1.1em; }\n.mj-markdown-container h5 { font-size: 1em; }\n.mj-markdown-container h6 { font-size: 0.9em; color: var(--mj-text-secondary); }\n\n.mj-markdown-container > h1:first-child,\n.mj-markdown-container > h2:first-child,\n.mj-markdown-container > h3:first-child {\n margin-top: 0;\n}\n\n.mj-markdown-container p {\n margin: 0 0 1em 0;\n}\n\n.mj-markdown-container > p:last-child {\n margin-bottom: 0;\n}\n\n.mj-markdown-container a {\n color: var(--mj-brand-primary);\n text-decoration: none;\n}\n\n.mj-markdown-container a:hover {\n text-decoration: underline;\n}\n\n.mj-markdown-container strong {\n font-weight: 600;\n}\n\n.mj-markdown-container em {\n font-style: italic;\n}\n\n/* ============================================\n Lists\n ============================================ */\n.mj-markdown-container ul,\n.mj-markdown-container ol {\n margin: 0 0 1em 0;\n padding-left: 2em;\n}\n\n.mj-markdown-container li {\n margin-bottom: 0.25em;\n}\n\n.mj-markdown-container li > ul,\n.mj-markdown-container li > ol {\n margin-top: 0.25em;\n margin-bottom: 0;\n}\n\n/* Task lists */\n.mj-markdown-container ul.contains-task-list {\n list-style: none;\n padding-left: 0;\n}\n\n.mj-markdown-container li.task-list-item {\n display: flex;\n align-items: flex-start;\n gap: 0.5em;\n}\n\n.mj-markdown-container li.task-list-item input[type=\"checkbox\"] {\n margin-top: 0.35em;\n}\n\n/* ============================================\n Blockquotes\n ============================================ */\n.mj-markdown-container blockquote {\n margin: 1em 0;\n padding: 0.5em 1em;\n border-left: 4px solid var(--mj-border-default);\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-secondary);\n}\n\n.mj-markdown-container blockquote > p:last-child {\n margin-bottom: 0;\n}\n\n/* ============================================\n Code Blocks\n ============================================ */\n.mj-markdown-container code {\n font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', 'Courier New', monospace;\n font-size: 0.9em;\n}\n\n/* Inline code */\n.mj-markdown-container :not(pre) > code {\n padding: 0.2em 0.4em;\n background: var(--mj-bg-surface-sunken);\n border-radius: 4px;\n color: var(--mj-status-error);\n}\n\n/* Code blocks\n * design-token-exempt fallbacks: --mj-bg-code-block / --mj-text-code resolve to\n * Monaco-style dark editor colors when an app does not override them. Code\n * blocks intentionally use a fixed dark editor aesthetic (same exemption as\n * Monaco/CodeMirror), so the hex fallbacks here are deliberate. */\n.mj-markdown-container pre {\n margin: 1em 0;\n padding: 1em;\n background: var(--mj-bg-code-block, #1e1e1e);\n border-radius: var(--mj-radius-md);\n overflow-x: auto;\n position: relative;\n}\n\n.mj-markdown-container pre code {\n background: transparent;\n padding: 0;\n color: var(--mj-text-code, #d4d4d4);\n font-size: 0.875em;\n line-height: 1.5;\n}\n\n/* ============================================\n Code Toolbar (copy button, language label)\n ============================================ */\n.mj-markdown-container .code-toolbar {\n position: absolute;\n top: 8px;\n right: 8px;\n display: flex;\n align-items: center;\n gap: 8px;\n z-index: 10;\n}\n\n.mj-markdown-container .code-language-label {\n font-size: 11px;\n font-weight: 500;\n color: var(--mj-text-disabled);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n user-select: none;\n}\n\n.mj-markdown-container .code-copy-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 32px;\n height: 32px;\n padding: 0;\n background: rgba(255, 255, 255, 0.1);\n border: 1px solid rgba(255, 255, 255, 0.15);\n border-radius: var(--mj-radius-md);\n color: var(--mj-text-disabled);\n cursor: pointer;\n transition: all 0.2s ease;\n opacity: 0;\n}\n\n.mj-markdown-container pre:hover .code-copy-btn {\n opacity: 1;\n}\n\n.mj-markdown-container .code-copy-btn:hover {\n background: rgba(255, 255, 255, 0.2);\n color: var(--mj-text-inverse);\n}\n\n.mj-markdown-container .code-copy-btn.copied {\n background: color-mix(in srgb, var(--mj-status-success) 20%, transparent);\n border-color: color-mix(in srgb, var(--mj-status-success) 30%, transparent);\n color: var(--mj-status-success);\n}\n\n.mj-markdown-container .code-copy-btn.error {\n background: color-mix(in srgb, var(--mj-status-error) 20%, transparent);\n border-color: color-mix(in srgb, var(--mj-status-error) 30%, transparent);\n color: var(--mj-status-error);\n}\n\n/* ============================================\n Tables\n ============================================ */\n.mj-markdown-container table {\n width: 100%;\n margin: 1em 0;\n border-collapse: collapse;\n border-spacing: 0;\n}\n\n.mj-markdown-container th,\n.mj-markdown-container td {\n padding: 0.5em 1em;\n border: 1px solid var(--mj-border-default);\n text-align: left;\n}\n\n.mj-markdown-container th {\n background: var(--mj-bg-surface-sunken);\n font-weight: 600;\n}\n\n.mj-markdown-container tr:nth-child(even) {\n background: var(--mj-bg-surface-sunken);\n}\n\n/* ============================================\n Horizontal Rule\n ============================================ */\n.mj-markdown-container hr {\n margin: 2em 0;\n border: none;\n border-top: 1px solid var(--mj-border-default);\n}\n\n/* ============================================\n Images\n ============================================ */\n.mj-markdown-container img {\n max-width: 100%;\n height: auto;\n border-radius: 4px;\n}\n\n/* ============================================\n GitHub-style Alerts (marked-alert)\n ============================================ */\n.mj-markdown-container .markdown-alert {\n margin: 1em 0;\n padding: 1em;\n border-radius: 8px;\n border-left: 4px solid;\n}\n\n.mj-markdown-container .markdown-alert-title {\n display: flex;\n align-items: center;\n gap: 8px;\n font-weight: 600;\n margin-bottom: 0.5em;\n}\n\n.mj-markdown-container .markdown-alert > p:last-child {\n margin-bottom: 0;\n}\n\n/* Note */\n.mj-markdown-container .markdown-alert-note {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n border-color: var(--mj-brand-primary);\n}\n\n.mj-markdown-container .markdown-alert-note .markdown-alert-title {\n color: var(--mj-brand-primary);\n}\n\n/* Tip */\n.mj-markdown-container .markdown-alert-tip {\n background: color-mix(in srgb, var(--mj-status-success) 10%, var(--mj-bg-surface));\n border-color: var(--mj-status-success);\n}\n\n.mj-markdown-container .markdown-alert-tip .markdown-alert-title {\n color: var(--mj-status-success);\n}\n\n/* Important */\n.mj-markdown-container .markdown-alert-important {\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface));\n border-color: var(--mj-brand-primary);\n}\n\n.mj-markdown-container .markdown-alert-important .markdown-alert-title {\n color: var(--mj-brand-primary);\n}\n\n/* Warning */\n.mj-markdown-container .markdown-alert-warning {\n background: color-mix(in srgb, var(--mj-status-warning) 10%, var(--mj-bg-surface));\n border-color: var(--mj-status-warning);\n}\n\n.mj-markdown-container .markdown-alert-warning .markdown-alert-title {\n color: var(--mj-status-warning);\n}\n\n/* Caution */\n.mj-markdown-container .markdown-alert-caution {\n background: color-mix(in srgb, var(--mj-status-error) 10%, var(--mj-bg-surface));\n border-color: var(--mj-status-error);\n}\n\n.mj-markdown-container .markdown-alert-caution .markdown-alert-title {\n color: var(--mj-status-error);\n}\n\n/* ============================================\n Collapsible Headings\n ============================================ */\n.mj-markdown-container .collapsible-section {\n margin: 0.75em 0;\n border-left: 2px solid transparent;\n transition: border-color 0.2s ease;\n}\n\n.mj-markdown-container .collapsible-section:hover {\n border-left-color: var(--mj-border-default);\n}\n\n/* Heading wrapper - contains toggle and heading */\n.mj-markdown-container .collapsible-heading-wrapper {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 4px 0;\n border-radius: 4px;\n transition: background-color 0.15s ease;\n user-select: none;\n}\n\n.mj-markdown-container .collapsible-heading-wrapper:hover {\n background-color: color-mix(in srgb, var(--mj-text-primary) 3%, transparent);\n}\n\n/* The heading itself */\n.mj-markdown-container .collapsible-heading {\n margin: 0 !important;\n flex: 1;\n}\n\n/* Toggle button/icon */\n.mj-markdown-container .collapsible-toggle {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 20px;\n flex-shrink: 0;\n color: var(--mj-text-muted);\n transition: transform 0.2s ease, color 0.15s ease;\n}\n\n.mj-markdown-container .collapsible-toggle svg {\n width: 14px;\n height: 14px;\n}\n\n.mj-markdown-container .collapsible-heading-wrapper:hover .collapsible-toggle {\n color: var(--mj-text-secondary);\n}\n\n/* Expanded state - arrow points down */\n.mj-markdown-container .collapsible-section:not(.collapsed) .collapsible-toggle {\n transform: rotate(90deg);\n}\n\n/* Collapsed state - arrow points right (default SVG orientation) */\n.mj-markdown-container .collapsible-section.collapsed .collapsible-toggle {\n transform: rotate(0deg);\n}\n\n/* Content area */\n.mj-markdown-container .collapsible-content {\n padding-left: 26px;\n overflow: hidden;\n max-height: 5000px; /* Large enough for most content */\n opacity: 1;\n transition: max-height 0.3s ease-out, opacity 0.2s ease-out, padding 0.2s ease;\n}\n\n.mj-markdown-container .collapsible-section.collapsed .collapsible-content {\n max-height: 0;\n opacity: 0;\n padding-top: 0;\n padding-bottom: 0;\n visibility: hidden;\n}\n\n/* Nested sections - indent further */\n.mj-markdown-container .collapsible-section .collapsible-section {\n margin-left: 0;\n}\n\n/* Focus styles for accessibility */\n.mj-markdown-container .collapsible-heading-wrapper:focus-within {\n outline: 2px solid var(--mj-brand-primary);\n outline-offset: 2px;\n}\n\n.mj-markdown-container .collapsible-toggle:focus {\n outline: none;\n}\n\n/* Action buttons container - expand/collapse all */\n.mj-markdown-container .collapsible-actions {\n display: flex;\n align-items: center;\n gap: 2px;\n margin-left: auto;\n padding-left: 12px;\n opacity: 0;\n transition: opacity 0.15s ease;\n}\n\n.mj-markdown-container .collapsible-heading-wrapper:hover .collapsible-actions {\n opacity: 1;\n}\n\n/* Individual action buttons */\n.mj-markdown-container .collapsible-action-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 22px;\n height: 22px;\n padding: 0;\n border: none;\n border-radius: 4px;\n background: transparent;\n color: var(--mj-text-muted);\n cursor: pointer;\n transition: background-color 0.15s ease, color 0.15s ease, transform 0.1s ease;\n}\n\n.mj-markdown-container .collapsible-action-btn:hover {\n background-color: color-mix(in srgb, var(--mj-text-primary) 8%, transparent);\n color: var(--mj-text-primary);\n}\n\n.mj-markdown-container .collapsible-action-btn:active {\n transform: scale(0.92);\n}\n\n.mj-markdown-container .collapsible-action-btn svg {\n width: 14px;\n height: 14px;\n}\n\n/* Expand all button - double down chevron */\n.mj-markdown-container .collapsible-action-btn.expand-all:hover {\n color: var(--mj-status-success);\n background-color: color-mix(in srgb, var(--mj-status-success) 10%, transparent);\n}\n\n/* Collapse all button - double up chevron */\n.mj-markdown-container .collapsible-action-btn.collapse-all:hover {\n color: var(--mj-status-error);\n background-color: color-mix(in srgb, var(--mj-status-error) 10%, transparent);\n}\n\n/* ============================================\n Mermaid Diagrams\n ============================================ */\n.mj-markdown-container .mermaid-diagram {\n margin: 1em 0;\n padding: 1em;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n text-align: center;\n overflow-x: auto;\n}\n\n.mj-markdown-container .mermaid-diagram svg {\n max-width: 100%;\n height: auto;\n}\n\n.mj-markdown-container .mermaid-error {\n background: color-mix(in srgb, var(--mj-status-error) 10%, var(--mj-bg-surface));\n border-color: color-mix(in srgb, var(--mj-status-error) 30%, var(--mj-bg-surface));\n}\n\n.mj-markdown-container .mermaid-error::before {\n content: 'Diagram rendering failed';\n display: block;\n color: var(--mj-status-error);\n font-size: 12px;\n margin-bottom: 8px;\n}\n\n/* A specific reason, when we have one \u2014 e.g. the lazily-fetched diagram engine\n never arrived, which is a different problem than bad diagram source. */\n.mj-markdown-container .mermaid-error[data-mermaid-error]::before {\n content: attr(data-mermaid-error);\n}\n\n/* ============================================\n Heading Anchors\n ============================================ */\n.mj-markdown-container h1[id],\n.mj-markdown-container h2[id],\n.mj-markdown-container h3[id],\n.mj-markdown-container h4[id],\n.mj-markdown-container h5[id],\n.mj-markdown-container h6[id] {\n scroll-margin-top: 1em;\n}\n\n.mj-markdown-container h1[id]:hover,\n.mj-markdown-container h2[id]:hover,\n.mj-markdown-container h3[id]:hover,\n.mj-markdown-container h4[id]:hover,\n.mj-markdown-container h5[id]:hover,\n.mj-markdown-container h6[id]:hover {\n cursor: pointer;\n}\n\n/* Anchor link indicator on hover */\n.mj-markdown-container h1[id]::before,\n.mj-markdown-container h2[id]::before,\n.mj-markdown-container h3[id]::before,\n.mj-markdown-container h4[id]::before,\n.mj-markdown-container h5[id]::before,\n.mj-markdown-container h6[id]::before {\n content: '#';\n position: absolute;\n left: -1.5em;\n color: var(--mj-text-muted);\n opacity: 0;\n transition: opacity 0.2s ease;\n}\n\n.mj-markdown-container h1[id]:hover::before,\n.mj-markdown-container h2[id]:hover::before,\n.mj-markdown-container h3[id]:hover::before,\n.mj-markdown-container h4[id]:hover::before,\n.mj-markdown-container h5[id]:hover::before,\n.mj-markdown-container h6[id]:hover::before {\n opacity: 1;\n}\n\n/* ============================================\n Line Numbers (optional)\n ============================================ */\n.mj-markdown-container pre.line-numbers {\n padding-left: 3.5em;\n counter-reset: line;\n}\n\n.mj-markdown-container pre.line-numbers code {\n display: block;\n}\n\n.mj-markdown-container pre.line-numbers code .line::before {\n counter-increment: line;\n content: counter(line);\n display: inline-block;\n width: 2em;\n margin-left: -3em;\n margin-right: 1em;\n text-align: right;\n color: var(--mj-text-muted);\n user-select: none;\n}\n\n/* ============================================\n Error State\n ============================================ */\n.mj-markdown-container .markdown-error {\n background: color-mix(in srgb, var(--mj-status-error) 10%, var(--mj-bg-surface));\n color: var(--mj-status-error);\n padding: 1em;\n border-radius: 8px;\n border: 1px solid color-mix(in srgb, var(--mj-status-error) 30%, var(--mj-bg-surface));\n white-space: pre-wrap;\n font-family: monospace;\n}\n\n/* Dark Mode Support \u2014 handled automatically by --mj-* semantic tokens */\n\n/* ============================================\n SVG Rendered Content\n ============================================ */\n.mj-markdown-container .svg-rendered {\n margin: 1em 0;\n padding: 1em;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n text-align: center;\n overflow: auto;\n}\n\n.mj-markdown-container .svg-rendered svg {\n max-width: 100%;\n height: auto;\n}\n\n/* Dark mode for SVG rendered content \u2014 handled automatically by --mj-* semantic tokens */\n\n/* ============================================\n Print Styles\n ============================================ */\n@media print {\n .mj-markdown-container .code-toolbar {\n display: none;\n }\n\n .mj-markdown-container .collapsible-section.collapsed .collapsible-content {\n max-height: none;\n opacity: 1;\n visibility: visible;\n }\n\n /* design-token-exempt: print always uses light background + dark text for\n paper readability, regardless of the screen theme the user is rendering from */\n .mj-markdown-container pre {\n background: #f3f4f6 !important;\n color: #1f2937 !important;\n }\n\n .mj-markdown-container pre code {\n color: #1f2937 !important;\n }\n}\n"], encapsulation: 2, changeDetection: 0 }); }
575
575
  }
576
576
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MarkdownComponent, [{
577
577
  type: Component,
578
- args: [{ selector: 'mj-markdown', standalone: false, template: ` <div class="mj-markdown-container" [class]="containerClass" [innerHTML]="renderedContent"></div> `, encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, styles: ["/**\n * MJ Markdown Component Styles\n *\n * These styles apply to rendered markdown content.\n * Using ViewEncapsulation.None so styles penetrate into dynamically generated content.\n */\n\n/* ============================================\n Container\n ============================================ */\n.mj-markdown-container {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;\n font-size: 15px;\n line-height: 1.6;\n color: var(--mj-text-primary);\n word-wrap: break-word;\n}\n\n/* ============================================\n Typography\n ============================================ */\n.mj-markdown-container h1,\n.mj-markdown-container h2,\n.mj-markdown-container h3,\n.mj-markdown-container h4,\n.mj-markdown-container h5,\n.mj-markdown-container h6 {\n margin-top: 1.5em;\n margin-bottom: 0.5em;\n font-weight: 600;\n line-height: 1.25;\n color: var(--mj-text-primary);\n}\n\n.mj-markdown-container h1 { font-size: 1.75em; }\n.mj-markdown-container h2 { font-size: 1.5em; }\n.mj-markdown-container h3 { font-size: 1.25em; }\n.mj-markdown-container h4 { font-size: 1.1em; }\n.mj-markdown-container h5 { font-size: 1em; }\n.mj-markdown-container h6 { font-size: 0.9em; color: var(--mj-text-secondary); }\n\n.mj-markdown-container > h1:first-child,\n.mj-markdown-container > h2:first-child,\n.mj-markdown-container > h3:first-child {\n margin-top: 0;\n}\n\n.mj-markdown-container p {\n margin: 0 0 1em 0;\n}\n\n.mj-markdown-container > p:last-child {\n margin-bottom: 0;\n}\n\n.mj-markdown-container a {\n color: var(--mj-brand-primary);\n text-decoration: none;\n}\n\n.mj-markdown-container a:hover {\n text-decoration: underline;\n}\n\n.mj-markdown-container strong {\n font-weight: 600;\n}\n\n.mj-markdown-container em {\n font-style: italic;\n}\n\n/* ============================================\n Lists\n ============================================ */\n.mj-markdown-container ul,\n.mj-markdown-container ol {\n margin: 0 0 1em 0;\n padding-left: 2em;\n}\n\n.mj-markdown-container li {\n margin-bottom: 0.25em;\n}\n\n.mj-markdown-container li > ul,\n.mj-markdown-container li > ol {\n margin-top: 0.25em;\n margin-bottom: 0;\n}\n\n/* Task lists */\n.mj-markdown-container ul.contains-task-list {\n list-style: none;\n padding-left: 0;\n}\n\n.mj-markdown-container li.task-list-item {\n display: flex;\n align-items: flex-start;\n gap: 0.5em;\n}\n\n.mj-markdown-container li.task-list-item input[type=\"checkbox\"] {\n margin-top: 0.35em;\n}\n\n/* ============================================\n Blockquotes\n ============================================ */\n.mj-markdown-container blockquote {\n margin: 1em 0;\n padding: 0.5em 1em;\n border-left: 4px solid var(--mj-border-default);\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-secondary);\n}\n\n.mj-markdown-container blockquote > p:last-child {\n margin-bottom: 0;\n}\n\n/* ============================================\n Code Blocks\n ============================================ */\n.mj-markdown-container code {\n font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', 'Courier New', monospace;\n font-size: 0.9em;\n}\n\n/* Inline code */\n.mj-markdown-container :not(pre) > code {\n padding: 0.2em 0.4em;\n background: var(--mj-bg-surface-sunken);\n border-radius: 4px;\n color: var(--mj-status-error);\n}\n\n/* Code blocks\n * design-token-exempt fallbacks: --mj-bg-code-block / --mj-text-code resolve to\n * Monaco-style dark editor colors when an app does not override them. Code\n * blocks intentionally use a fixed dark editor aesthetic (same exemption as\n * Monaco/CodeMirror), so the hex fallbacks here are deliberate. */\n.mj-markdown-container pre {\n margin: 1em 0;\n padding: 1em;\n background: var(--mj-bg-code-block, #1e1e1e);\n border-radius: var(--mj-radius-md);\n overflow-x: auto;\n position: relative;\n}\n\n.mj-markdown-container pre code {\n background: transparent;\n padding: 0;\n color: var(--mj-text-code, #d4d4d4);\n font-size: 0.875em;\n line-height: 1.5;\n}\n\n/* ============================================\n Code Toolbar (copy button, language label)\n ============================================ */\n.mj-markdown-container .code-toolbar {\n position: absolute;\n top: 8px;\n right: 8px;\n display: flex;\n align-items: center;\n gap: 8px;\n z-index: 10;\n}\n\n.mj-markdown-container .code-language-label {\n font-size: 11px;\n font-weight: 500;\n color: var(--mj-text-disabled);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n user-select: none;\n}\n\n.mj-markdown-container .code-copy-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 32px;\n height: 32px;\n padding: 0;\n background: rgba(255, 255, 255, 0.1);\n border: 1px solid rgba(255, 255, 255, 0.15);\n border-radius: var(--mj-radius-md);\n color: var(--mj-text-disabled);\n cursor: pointer;\n transition: all 0.2s ease;\n opacity: 0;\n}\n\n.mj-markdown-container pre:hover .code-copy-btn {\n opacity: 1;\n}\n\n.mj-markdown-container .code-copy-btn:hover {\n background: rgba(255, 255, 255, 0.2);\n color: var(--mj-text-inverse);\n}\n\n.mj-markdown-container .code-copy-btn.copied {\n background: color-mix(in srgb, var(--mj-status-success) 20%, transparent);\n border-color: color-mix(in srgb, var(--mj-status-success) 30%, transparent);\n color: var(--mj-status-success);\n}\n\n.mj-markdown-container .code-copy-btn.error {\n background: color-mix(in srgb, var(--mj-status-error) 20%, transparent);\n border-color: color-mix(in srgb, var(--mj-status-error) 30%, transparent);\n color: var(--mj-status-error);\n}\n\n/* ============================================\n Tables\n ============================================ */\n.mj-markdown-container table {\n width: 100%;\n margin: 1em 0;\n border-collapse: collapse;\n border-spacing: 0;\n}\n\n.mj-markdown-container th,\n.mj-markdown-container td {\n padding: 0.5em 1em;\n border: 1px solid var(--mj-border-default);\n text-align: left;\n}\n\n.mj-markdown-container th {\n background: var(--mj-bg-surface-sunken);\n font-weight: 600;\n}\n\n.mj-markdown-container tr:nth-child(even) {\n background: var(--mj-bg-surface-sunken);\n}\n\n/* ============================================\n Horizontal Rule\n ============================================ */\n.mj-markdown-container hr {\n margin: 2em 0;\n border: none;\n border-top: 1px solid var(--mj-border-default);\n}\n\n/* ============================================\n Images\n ============================================ */\n.mj-markdown-container img {\n max-width: 100%;\n height: auto;\n border-radius: 4px;\n}\n\n/* ============================================\n GitHub-style Alerts (marked-alert)\n ============================================ */\n.mj-markdown-container .markdown-alert {\n margin: 1em 0;\n padding: 1em;\n border-radius: 8px;\n border-left: 4px solid;\n}\n\n.mj-markdown-container .markdown-alert-title {\n display: flex;\n align-items: center;\n gap: 8px;\n font-weight: 600;\n margin-bottom: 0.5em;\n}\n\n.mj-markdown-container .markdown-alert > p:last-child {\n margin-bottom: 0;\n}\n\n/* Note */\n.mj-markdown-container .markdown-alert-note {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n border-color: var(--mj-brand-primary);\n}\n\n.mj-markdown-container .markdown-alert-note .markdown-alert-title {\n color: var(--mj-brand-primary);\n}\n\n/* Tip */\n.mj-markdown-container .markdown-alert-tip {\n background: color-mix(in srgb, var(--mj-status-success) 10%, var(--mj-bg-surface));\n border-color: var(--mj-status-success);\n}\n\n.mj-markdown-container .markdown-alert-tip .markdown-alert-title {\n color: var(--mj-status-success);\n}\n\n/* Important */\n.mj-markdown-container .markdown-alert-important {\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface));\n border-color: var(--mj-brand-primary);\n}\n\n.mj-markdown-container .markdown-alert-important .markdown-alert-title {\n color: var(--mj-brand-primary);\n}\n\n/* Warning */\n.mj-markdown-container .markdown-alert-warning {\n background: color-mix(in srgb, var(--mj-status-warning) 10%, var(--mj-bg-surface));\n border-color: var(--mj-status-warning);\n}\n\n.mj-markdown-container .markdown-alert-warning .markdown-alert-title {\n color: var(--mj-status-warning);\n}\n\n/* Caution */\n.mj-markdown-container .markdown-alert-caution {\n background: color-mix(in srgb, var(--mj-status-error) 10%, var(--mj-bg-surface));\n border-color: var(--mj-status-error);\n}\n\n.mj-markdown-container .markdown-alert-caution .markdown-alert-title {\n color: var(--mj-status-error);\n}\n\n/* ============================================\n Collapsible Headings\n ============================================ */\n.mj-markdown-container .collapsible-section {\n margin: 0.75em 0;\n border-left: 2px solid transparent;\n transition: border-color 0.2s ease;\n}\n\n.mj-markdown-container .collapsible-section:hover {\n border-left-color: var(--mj-border-default);\n}\n\n/* Heading wrapper - contains toggle and heading */\n.mj-markdown-container .collapsible-heading-wrapper {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 4px 0;\n border-radius: 4px;\n transition: background-color 0.15s ease;\n user-select: none;\n}\n\n.mj-markdown-container .collapsible-heading-wrapper:hover {\n background-color: color-mix(in srgb, var(--mj-text-primary) 3%, transparent);\n}\n\n/* The heading itself */\n.mj-markdown-container .collapsible-heading {\n margin: 0 !important;\n flex: 1;\n}\n\n/* Toggle button/icon */\n.mj-markdown-container .collapsible-toggle {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 20px;\n flex-shrink: 0;\n color: var(--mj-text-muted);\n transition: transform 0.2s ease, color 0.15s ease;\n}\n\n.mj-markdown-container .collapsible-toggle svg {\n width: 14px;\n height: 14px;\n}\n\n.mj-markdown-container .collapsible-heading-wrapper:hover .collapsible-toggle {\n color: var(--mj-text-secondary);\n}\n\n/* Expanded state - arrow points down */\n.mj-markdown-container .collapsible-section:not(.collapsed) .collapsible-toggle {\n transform: rotate(90deg);\n}\n\n/* Collapsed state - arrow points right (default SVG orientation) */\n.mj-markdown-container .collapsible-section.collapsed .collapsible-toggle {\n transform: rotate(0deg);\n}\n\n/* Content area */\n.mj-markdown-container .collapsible-content {\n padding-left: 26px;\n overflow: hidden;\n max-height: 5000px; /* Large enough for most content */\n opacity: 1;\n transition: max-height 0.3s ease-out, opacity 0.2s ease-out, padding 0.2s ease;\n}\n\n.mj-markdown-container .collapsible-section.collapsed .collapsible-content {\n max-height: 0;\n opacity: 0;\n padding-top: 0;\n padding-bottom: 0;\n visibility: hidden;\n}\n\n/* Nested sections - indent further */\n.mj-markdown-container .collapsible-section .collapsible-section {\n margin-left: 0;\n}\n\n/* Focus styles for accessibility */\n.mj-markdown-container .collapsible-heading-wrapper:focus-within {\n outline: 2px solid var(--mj-brand-primary);\n outline-offset: 2px;\n}\n\n.mj-markdown-container .collapsible-toggle:focus {\n outline: none;\n}\n\n/* Action buttons container - expand/collapse all */\n.mj-markdown-container .collapsible-actions {\n display: flex;\n align-items: center;\n gap: 2px;\n margin-left: auto;\n padding-left: 12px;\n opacity: 0;\n transition: opacity 0.15s ease;\n}\n\n.mj-markdown-container .collapsible-heading-wrapper:hover .collapsible-actions {\n opacity: 1;\n}\n\n/* Individual action buttons */\n.mj-markdown-container .collapsible-action-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 22px;\n height: 22px;\n padding: 0;\n border: none;\n border-radius: 4px;\n background: transparent;\n color: var(--mj-text-muted);\n cursor: pointer;\n transition: background-color 0.15s ease, color 0.15s ease, transform 0.1s ease;\n}\n\n.mj-markdown-container .collapsible-action-btn:hover {\n background-color: color-mix(in srgb, var(--mj-text-primary) 8%, transparent);\n color: var(--mj-text-primary);\n}\n\n.mj-markdown-container .collapsible-action-btn:active {\n transform: scale(0.92);\n}\n\n.mj-markdown-container .collapsible-action-btn svg {\n width: 14px;\n height: 14px;\n}\n\n/* Expand all button - double down chevron */\n.mj-markdown-container .collapsible-action-btn.expand-all:hover {\n color: var(--mj-status-success);\n background-color: color-mix(in srgb, var(--mj-status-success) 10%, transparent);\n}\n\n/* Collapse all button - double up chevron */\n.mj-markdown-container .collapsible-action-btn.collapse-all:hover {\n color: var(--mj-status-error);\n background-color: color-mix(in srgb, var(--mj-status-error) 10%, transparent);\n}\n\n/* ============================================\n Mermaid Diagrams\n ============================================ */\n.mj-markdown-container .mermaid-diagram {\n margin: 1em 0;\n padding: 1em;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n text-align: center;\n overflow-x: auto;\n}\n\n.mj-markdown-container .mermaid-diagram svg {\n max-width: 100%;\n height: auto;\n}\n\n.mj-markdown-container .mermaid-error {\n background: color-mix(in srgb, var(--mj-status-error) 10%, var(--mj-bg-surface));\n border-color: color-mix(in srgb, var(--mj-status-error) 30%, var(--mj-bg-surface));\n}\n\n.mj-markdown-container .mermaid-error::before {\n content: 'Diagram rendering failed';\n display: block;\n color: var(--mj-status-error);\n font-size: 12px;\n margin-bottom: 8px;\n}\n\n/* ============================================\n Heading Anchors\n ============================================ */\n.mj-markdown-container h1[id],\n.mj-markdown-container h2[id],\n.mj-markdown-container h3[id],\n.mj-markdown-container h4[id],\n.mj-markdown-container h5[id],\n.mj-markdown-container h6[id] {\n scroll-margin-top: 1em;\n}\n\n.mj-markdown-container h1[id]:hover,\n.mj-markdown-container h2[id]:hover,\n.mj-markdown-container h3[id]:hover,\n.mj-markdown-container h4[id]:hover,\n.mj-markdown-container h5[id]:hover,\n.mj-markdown-container h6[id]:hover {\n cursor: pointer;\n}\n\n/* Anchor link indicator on hover */\n.mj-markdown-container h1[id]::before,\n.mj-markdown-container h2[id]::before,\n.mj-markdown-container h3[id]::before,\n.mj-markdown-container h4[id]::before,\n.mj-markdown-container h5[id]::before,\n.mj-markdown-container h6[id]::before {\n content: '#';\n position: absolute;\n left: -1.5em;\n color: var(--mj-text-muted);\n opacity: 0;\n transition: opacity 0.2s ease;\n}\n\n.mj-markdown-container h1[id]:hover::before,\n.mj-markdown-container h2[id]:hover::before,\n.mj-markdown-container h3[id]:hover::before,\n.mj-markdown-container h4[id]:hover::before,\n.mj-markdown-container h5[id]:hover::before,\n.mj-markdown-container h6[id]:hover::before {\n opacity: 1;\n}\n\n/* ============================================\n Line Numbers (optional)\n ============================================ */\n.mj-markdown-container pre.line-numbers {\n padding-left: 3.5em;\n counter-reset: line;\n}\n\n.mj-markdown-container pre.line-numbers code {\n display: block;\n}\n\n.mj-markdown-container pre.line-numbers code .line::before {\n counter-increment: line;\n content: counter(line);\n display: inline-block;\n width: 2em;\n margin-left: -3em;\n margin-right: 1em;\n text-align: right;\n color: var(--mj-text-muted);\n user-select: none;\n}\n\n/* ============================================\n Error State\n ============================================ */\n.mj-markdown-container .markdown-error {\n background: color-mix(in srgb, var(--mj-status-error) 10%, var(--mj-bg-surface));\n color: var(--mj-status-error);\n padding: 1em;\n border-radius: 8px;\n border: 1px solid color-mix(in srgb, var(--mj-status-error) 30%, var(--mj-bg-surface));\n white-space: pre-wrap;\n font-family: monospace;\n}\n\n/* Dark Mode Support \u2014 handled automatically by --mj-* semantic tokens */\n\n/* ============================================\n SVG Rendered Content\n ============================================ */\n.mj-markdown-container .svg-rendered {\n margin: 1em 0;\n padding: 1em;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n text-align: center;\n overflow: auto;\n}\n\n.mj-markdown-container .svg-rendered svg {\n max-width: 100%;\n height: auto;\n}\n\n/* Dark mode for SVG rendered content \u2014 handled automatically by --mj-* semantic tokens */\n\n/* ============================================\n Print Styles\n ============================================ */\n@media print {\n .mj-markdown-container .code-toolbar {\n display: none;\n }\n\n .mj-markdown-container .collapsible-section.collapsed .collapsible-content {\n max-height: none;\n opacity: 1;\n visibility: visible;\n }\n\n /* design-token-exempt: print always uses light background + dark text for\n paper readability, regardless of the screen theme the user is rendering from */\n .mj-markdown-container pre {\n background: #f3f4f6 !important;\n color: #1f2937 !important;\n }\n\n .mj-markdown-container pre code {\n color: #1f2937 !important;\n }\n}\n"] }]
578
+ args: [{ selector: 'mj-markdown', standalone: false, template: ` <div class="mj-markdown-container" [class]="containerClass" [innerHTML]="renderedContent"></div> `, encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, styles: ["/**\n * MJ Markdown Component Styles\n *\n * These styles apply to rendered markdown content.\n * Using ViewEncapsulation.None so styles penetrate into dynamically generated content.\n */\n\n/* ============================================\n Container\n ============================================ */\n.mj-markdown-container {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;\n font-size: 15px;\n line-height: 1.6;\n color: var(--mj-text-primary);\n word-wrap: break-word;\n}\n\n/* ============================================\n Typography\n ============================================ */\n.mj-markdown-container h1,\n.mj-markdown-container h2,\n.mj-markdown-container h3,\n.mj-markdown-container h4,\n.mj-markdown-container h5,\n.mj-markdown-container h6 {\n margin-top: 1.5em;\n margin-bottom: 0.5em;\n font-weight: 600;\n line-height: 1.25;\n color: var(--mj-text-primary);\n}\n\n.mj-markdown-container h1 { font-size: 1.75em; }\n.mj-markdown-container h2 { font-size: 1.5em; }\n.mj-markdown-container h3 { font-size: 1.25em; }\n.mj-markdown-container h4 { font-size: 1.1em; }\n.mj-markdown-container h5 { font-size: 1em; }\n.mj-markdown-container h6 { font-size: 0.9em; color: var(--mj-text-secondary); }\n\n.mj-markdown-container > h1:first-child,\n.mj-markdown-container > h2:first-child,\n.mj-markdown-container > h3:first-child {\n margin-top: 0;\n}\n\n.mj-markdown-container p {\n margin: 0 0 1em 0;\n}\n\n.mj-markdown-container > p:last-child {\n margin-bottom: 0;\n}\n\n.mj-markdown-container a {\n color: var(--mj-brand-primary);\n text-decoration: none;\n}\n\n.mj-markdown-container a:hover {\n text-decoration: underline;\n}\n\n.mj-markdown-container strong {\n font-weight: 600;\n}\n\n.mj-markdown-container em {\n font-style: italic;\n}\n\n/* ============================================\n Lists\n ============================================ */\n.mj-markdown-container ul,\n.mj-markdown-container ol {\n margin: 0 0 1em 0;\n padding-left: 2em;\n}\n\n.mj-markdown-container li {\n margin-bottom: 0.25em;\n}\n\n.mj-markdown-container li > ul,\n.mj-markdown-container li > ol {\n margin-top: 0.25em;\n margin-bottom: 0;\n}\n\n/* Task lists */\n.mj-markdown-container ul.contains-task-list {\n list-style: none;\n padding-left: 0;\n}\n\n.mj-markdown-container li.task-list-item {\n display: flex;\n align-items: flex-start;\n gap: 0.5em;\n}\n\n.mj-markdown-container li.task-list-item input[type=\"checkbox\"] {\n margin-top: 0.35em;\n}\n\n/* ============================================\n Blockquotes\n ============================================ */\n.mj-markdown-container blockquote {\n margin: 1em 0;\n padding: 0.5em 1em;\n border-left: 4px solid var(--mj-border-default);\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-secondary);\n}\n\n.mj-markdown-container blockquote > p:last-child {\n margin-bottom: 0;\n}\n\n/* ============================================\n Code Blocks\n ============================================ */\n.mj-markdown-container code {\n font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', 'Courier New', monospace;\n font-size: 0.9em;\n}\n\n/* Inline code */\n.mj-markdown-container :not(pre) > code {\n padding: 0.2em 0.4em;\n background: var(--mj-bg-surface-sunken);\n border-radius: 4px;\n color: var(--mj-status-error);\n}\n\n/* Code blocks\n * design-token-exempt fallbacks: --mj-bg-code-block / --mj-text-code resolve to\n * Monaco-style dark editor colors when an app does not override them. Code\n * blocks intentionally use a fixed dark editor aesthetic (same exemption as\n * Monaco/CodeMirror), so the hex fallbacks here are deliberate. */\n.mj-markdown-container pre {\n margin: 1em 0;\n padding: 1em;\n background: var(--mj-bg-code-block, #1e1e1e);\n border-radius: var(--mj-radius-md);\n overflow-x: auto;\n position: relative;\n}\n\n.mj-markdown-container pre code {\n background: transparent;\n padding: 0;\n color: var(--mj-text-code, #d4d4d4);\n font-size: 0.875em;\n line-height: 1.5;\n}\n\n/* ============================================\n Code Toolbar (copy button, language label)\n ============================================ */\n.mj-markdown-container .code-toolbar {\n position: absolute;\n top: 8px;\n right: 8px;\n display: flex;\n align-items: center;\n gap: 8px;\n z-index: 10;\n}\n\n.mj-markdown-container .code-language-label {\n font-size: 11px;\n font-weight: 500;\n color: var(--mj-text-disabled);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n user-select: none;\n}\n\n.mj-markdown-container .code-copy-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 32px;\n height: 32px;\n padding: 0;\n background: rgba(255, 255, 255, 0.1);\n border: 1px solid rgba(255, 255, 255, 0.15);\n border-radius: var(--mj-radius-md);\n color: var(--mj-text-disabled);\n cursor: pointer;\n transition: all 0.2s ease;\n opacity: 0;\n}\n\n.mj-markdown-container pre:hover .code-copy-btn {\n opacity: 1;\n}\n\n.mj-markdown-container .code-copy-btn:hover {\n background: rgba(255, 255, 255, 0.2);\n color: var(--mj-text-inverse);\n}\n\n.mj-markdown-container .code-copy-btn.copied {\n background: color-mix(in srgb, var(--mj-status-success) 20%, transparent);\n border-color: color-mix(in srgb, var(--mj-status-success) 30%, transparent);\n color: var(--mj-status-success);\n}\n\n.mj-markdown-container .code-copy-btn.error {\n background: color-mix(in srgb, var(--mj-status-error) 20%, transparent);\n border-color: color-mix(in srgb, var(--mj-status-error) 30%, transparent);\n color: var(--mj-status-error);\n}\n\n/* ============================================\n Tables\n ============================================ */\n.mj-markdown-container table {\n width: 100%;\n margin: 1em 0;\n border-collapse: collapse;\n border-spacing: 0;\n}\n\n.mj-markdown-container th,\n.mj-markdown-container td {\n padding: 0.5em 1em;\n border: 1px solid var(--mj-border-default);\n text-align: left;\n}\n\n.mj-markdown-container th {\n background: var(--mj-bg-surface-sunken);\n font-weight: 600;\n}\n\n.mj-markdown-container tr:nth-child(even) {\n background: var(--mj-bg-surface-sunken);\n}\n\n/* ============================================\n Horizontal Rule\n ============================================ */\n.mj-markdown-container hr {\n margin: 2em 0;\n border: none;\n border-top: 1px solid var(--mj-border-default);\n}\n\n/* ============================================\n Images\n ============================================ */\n.mj-markdown-container img {\n max-width: 100%;\n height: auto;\n border-radius: 4px;\n}\n\n/* ============================================\n GitHub-style Alerts (marked-alert)\n ============================================ */\n.mj-markdown-container .markdown-alert {\n margin: 1em 0;\n padding: 1em;\n border-radius: 8px;\n border-left: 4px solid;\n}\n\n.mj-markdown-container .markdown-alert-title {\n display: flex;\n align-items: center;\n gap: 8px;\n font-weight: 600;\n margin-bottom: 0.5em;\n}\n\n.mj-markdown-container .markdown-alert > p:last-child {\n margin-bottom: 0;\n}\n\n/* Note */\n.mj-markdown-container .markdown-alert-note {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n border-color: var(--mj-brand-primary);\n}\n\n.mj-markdown-container .markdown-alert-note .markdown-alert-title {\n color: var(--mj-brand-primary);\n}\n\n/* Tip */\n.mj-markdown-container .markdown-alert-tip {\n background: color-mix(in srgb, var(--mj-status-success) 10%, var(--mj-bg-surface));\n border-color: var(--mj-status-success);\n}\n\n.mj-markdown-container .markdown-alert-tip .markdown-alert-title {\n color: var(--mj-status-success);\n}\n\n/* Important */\n.mj-markdown-container .markdown-alert-important {\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface));\n border-color: var(--mj-brand-primary);\n}\n\n.mj-markdown-container .markdown-alert-important .markdown-alert-title {\n color: var(--mj-brand-primary);\n}\n\n/* Warning */\n.mj-markdown-container .markdown-alert-warning {\n background: color-mix(in srgb, var(--mj-status-warning) 10%, var(--mj-bg-surface));\n border-color: var(--mj-status-warning);\n}\n\n.mj-markdown-container .markdown-alert-warning .markdown-alert-title {\n color: var(--mj-status-warning);\n}\n\n/* Caution */\n.mj-markdown-container .markdown-alert-caution {\n background: color-mix(in srgb, var(--mj-status-error) 10%, var(--mj-bg-surface));\n border-color: var(--mj-status-error);\n}\n\n.mj-markdown-container .markdown-alert-caution .markdown-alert-title {\n color: var(--mj-status-error);\n}\n\n/* ============================================\n Collapsible Headings\n ============================================ */\n.mj-markdown-container .collapsible-section {\n margin: 0.75em 0;\n border-left: 2px solid transparent;\n transition: border-color 0.2s ease;\n}\n\n.mj-markdown-container .collapsible-section:hover {\n border-left-color: var(--mj-border-default);\n}\n\n/* Heading wrapper - contains toggle and heading */\n.mj-markdown-container .collapsible-heading-wrapper {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 4px 0;\n border-radius: 4px;\n transition: background-color 0.15s ease;\n user-select: none;\n}\n\n.mj-markdown-container .collapsible-heading-wrapper:hover {\n background-color: color-mix(in srgb, var(--mj-text-primary) 3%, transparent);\n}\n\n/* The heading itself */\n.mj-markdown-container .collapsible-heading {\n margin: 0 !important;\n flex: 1;\n}\n\n/* Toggle button/icon */\n.mj-markdown-container .collapsible-toggle {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 20px;\n flex-shrink: 0;\n color: var(--mj-text-muted);\n transition: transform 0.2s ease, color 0.15s ease;\n}\n\n.mj-markdown-container .collapsible-toggle svg {\n width: 14px;\n height: 14px;\n}\n\n.mj-markdown-container .collapsible-heading-wrapper:hover .collapsible-toggle {\n color: var(--mj-text-secondary);\n}\n\n/* Expanded state - arrow points down */\n.mj-markdown-container .collapsible-section:not(.collapsed) .collapsible-toggle {\n transform: rotate(90deg);\n}\n\n/* Collapsed state - arrow points right (default SVG orientation) */\n.mj-markdown-container .collapsible-section.collapsed .collapsible-toggle {\n transform: rotate(0deg);\n}\n\n/* Content area */\n.mj-markdown-container .collapsible-content {\n padding-left: 26px;\n overflow: hidden;\n max-height: 5000px; /* Large enough for most content */\n opacity: 1;\n transition: max-height 0.3s ease-out, opacity 0.2s ease-out, padding 0.2s ease;\n}\n\n.mj-markdown-container .collapsible-section.collapsed .collapsible-content {\n max-height: 0;\n opacity: 0;\n padding-top: 0;\n padding-bottom: 0;\n visibility: hidden;\n}\n\n/* Nested sections - indent further */\n.mj-markdown-container .collapsible-section .collapsible-section {\n margin-left: 0;\n}\n\n/* Focus styles for accessibility */\n.mj-markdown-container .collapsible-heading-wrapper:focus-within {\n outline: 2px solid var(--mj-brand-primary);\n outline-offset: 2px;\n}\n\n.mj-markdown-container .collapsible-toggle:focus {\n outline: none;\n}\n\n/* Action buttons container - expand/collapse all */\n.mj-markdown-container .collapsible-actions {\n display: flex;\n align-items: center;\n gap: 2px;\n margin-left: auto;\n padding-left: 12px;\n opacity: 0;\n transition: opacity 0.15s ease;\n}\n\n.mj-markdown-container .collapsible-heading-wrapper:hover .collapsible-actions {\n opacity: 1;\n}\n\n/* Individual action buttons */\n.mj-markdown-container .collapsible-action-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 22px;\n height: 22px;\n padding: 0;\n border: none;\n border-radius: 4px;\n background: transparent;\n color: var(--mj-text-muted);\n cursor: pointer;\n transition: background-color 0.15s ease, color 0.15s ease, transform 0.1s ease;\n}\n\n.mj-markdown-container .collapsible-action-btn:hover {\n background-color: color-mix(in srgb, var(--mj-text-primary) 8%, transparent);\n color: var(--mj-text-primary);\n}\n\n.mj-markdown-container .collapsible-action-btn:active {\n transform: scale(0.92);\n}\n\n.mj-markdown-container .collapsible-action-btn svg {\n width: 14px;\n height: 14px;\n}\n\n/* Expand all button - double down chevron */\n.mj-markdown-container .collapsible-action-btn.expand-all:hover {\n color: var(--mj-status-success);\n background-color: color-mix(in srgb, var(--mj-status-success) 10%, transparent);\n}\n\n/* Collapse all button - double up chevron */\n.mj-markdown-container .collapsible-action-btn.collapse-all:hover {\n color: var(--mj-status-error);\n background-color: color-mix(in srgb, var(--mj-status-error) 10%, transparent);\n}\n\n/* ============================================\n Mermaid Diagrams\n ============================================ */\n.mj-markdown-container .mermaid-diagram {\n margin: 1em 0;\n padding: 1em;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n text-align: center;\n overflow-x: auto;\n}\n\n.mj-markdown-container .mermaid-diagram svg {\n max-width: 100%;\n height: auto;\n}\n\n.mj-markdown-container .mermaid-error {\n background: color-mix(in srgb, var(--mj-status-error) 10%, var(--mj-bg-surface));\n border-color: color-mix(in srgb, var(--mj-status-error) 30%, var(--mj-bg-surface));\n}\n\n.mj-markdown-container .mermaid-error::before {\n content: 'Diagram rendering failed';\n display: block;\n color: var(--mj-status-error);\n font-size: 12px;\n margin-bottom: 8px;\n}\n\n/* A specific reason, when we have one \u2014 e.g. the lazily-fetched diagram engine\n never arrived, which is a different problem than bad diagram source. */\n.mj-markdown-container .mermaid-error[data-mermaid-error]::before {\n content: attr(data-mermaid-error);\n}\n\n/* ============================================\n Heading Anchors\n ============================================ */\n.mj-markdown-container h1[id],\n.mj-markdown-container h2[id],\n.mj-markdown-container h3[id],\n.mj-markdown-container h4[id],\n.mj-markdown-container h5[id],\n.mj-markdown-container h6[id] {\n scroll-margin-top: 1em;\n}\n\n.mj-markdown-container h1[id]:hover,\n.mj-markdown-container h2[id]:hover,\n.mj-markdown-container h3[id]:hover,\n.mj-markdown-container h4[id]:hover,\n.mj-markdown-container h5[id]:hover,\n.mj-markdown-container h6[id]:hover {\n cursor: pointer;\n}\n\n/* Anchor link indicator on hover */\n.mj-markdown-container h1[id]::before,\n.mj-markdown-container h2[id]::before,\n.mj-markdown-container h3[id]::before,\n.mj-markdown-container h4[id]::before,\n.mj-markdown-container h5[id]::before,\n.mj-markdown-container h6[id]::before {\n content: '#';\n position: absolute;\n left: -1.5em;\n color: var(--mj-text-muted);\n opacity: 0;\n transition: opacity 0.2s ease;\n}\n\n.mj-markdown-container h1[id]:hover::before,\n.mj-markdown-container h2[id]:hover::before,\n.mj-markdown-container h3[id]:hover::before,\n.mj-markdown-container h4[id]:hover::before,\n.mj-markdown-container h5[id]:hover::before,\n.mj-markdown-container h6[id]:hover::before {\n opacity: 1;\n}\n\n/* ============================================\n Line Numbers (optional)\n ============================================ */\n.mj-markdown-container pre.line-numbers {\n padding-left: 3.5em;\n counter-reset: line;\n}\n\n.mj-markdown-container pre.line-numbers code {\n display: block;\n}\n\n.mj-markdown-container pre.line-numbers code .line::before {\n counter-increment: line;\n content: counter(line);\n display: inline-block;\n width: 2em;\n margin-left: -3em;\n margin-right: 1em;\n text-align: right;\n color: var(--mj-text-muted);\n user-select: none;\n}\n\n/* ============================================\n Error State\n ============================================ */\n.mj-markdown-container .markdown-error {\n background: color-mix(in srgb, var(--mj-status-error) 10%, var(--mj-bg-surface));\n color: var(--mj-status-error);\n padding: 1em;\n border-radius: 8px;\n border: 1px solid color-mix(in srgb, var(--mj-status-error) 30%, var(--mj-bg-surface));\n white-space: pre-wrap;\n font-family: monospace;\n}\n\n/* Dark Mode Support \u2014 handled automatically by --mj-* semantic tokens */\n\n/* ============================================\n SVG Rendered Content\n ============================================ */\n.mj-markdown-container .svg-rendered {\n margin: 1em 0;\n padding: 1em;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n text-align: center;\n overflow: auto;\n}\n\n.mj-markdown-container .svg-rendered svg {\n max-width: 100%;\n height: auto;\n}\n\n/* Dark mode for SVG rendered content \u2014 handled automatically by --mj-* semantic tokens */\n\n/* ============================================\n Print Styles\n ============================================ */\n@media print {\n .mj-markdown-container .code-toolbar {\n display: none;\n }\n\n .mj-markdown-container .collapsible-section.collapsed .collapsible-content {\n max-height: none;\n opacity: 1;\n visibility: visible;\n }\n\n /* design-token-exempt: print always uses light background + dark text for\n paper readability, regardless of the screen theme the user is rendering from */\n .mj-markdown-container pre {\n background: #f3f4f6 !important;\n color: #1f2937 !important;\n }\n\n .mj-markdown-container pre code {\n color: #1f2937 !important;\n }\n}\n"] }]
579
579
  }], () => [{ type: i0.ElementRef }, { type: i1.DomSanitizer }, { type: i2.MarkdownService }, { type: i0.ChangeDetectorRef }], { data: [{
580
580
  type: Input
581
581
  }], enableHighlight: [{
@@ -29,6 +29,10 @@ export declare class MarkdownService {
29
29
  private mermaidInitialized;
30
30
  private lastMermaidTheme;
31
31
  private currentConfig;
32
+ /** The mermaid engine, once its lazy chunk has landed. Null until a diagram is actually rendered. */
33
+ private mermaidEngine;
34
+ /** In-flight load, so N diagrams in one document share ONE chunk fetch. */
35
+ private mermaidLoad;
32
36
  /**
33
37
  * Prism-backed highlight function injected into the core engine. The engine
34
38
  * itself has no Prism dependency — it calls this for the HTML output path.
@@ -55,9 +59,26 @@ export declare class MarkdownService {
55
59
  * 'auto' maps to 'dark' or 'default' based on the document's data-theme attribute.
56
60
  */
57
61
  private resolveEffectiveMermaidTheme;
62
+ /**
63
+ * Fetch the mermaid engine, on first use only.
64
+ *
65
+ * MEASURED BUNDLE DEFERRAL — this is deliberately a dynamic `import()`, NOT a top-level
66
+ * one, and must not be "tidied" back into a static import. `mermaid` and the parsing /
67
+ * graph engines it pulls with it (`@mermaid-js/parser`, `langium`, `chevrotain`,
68
+ * `cytoscape`, `katex`, `d3`) were ~0.95 MB raw of every host's initial download even
69
+ * when no document ever contained a diagram. That is a rounding error for an in-app
70
+ * host and the whole cost for a CDN-loaded embedding of the realtime session overlay
71
+ * (MJ #3882). Documents with no diagram now never fetch the chunk at all.
72
+ *
73
+ * The in-flight promise is cached so N diagrams in one document share one fetch; a
74
+ * FAILED load clears it, so a later render can retry. Retries are bounded by
75
+ * user-driven render calls — there is no loop here.
76
+ */
77
+ private loadMermaid;
58
78
  /**
59
79
  * Initialize Mermaid with the current theme configuration.
60
80
  * Re-initializes when the effective theme changes.
81
+ * @param mermaid The lazily-loaded mermaid engine (see {@link loadMermaid})
61
82
  */
62
83
  private initializeMermaid;
63
84
  /**
@@ -66,6 +87,17 @@ export declare class MarkdownService {
66
87
  * @param container The DOM element containing mermaid code blocks
67
88
  */
68
89
  renderMermaid(container: HTMLElement): Promise<boolean>;
90
+ /**
91
+ * Mark mermaid blocks as un-renderable, so the failure is VISIBLE rather than a block of
92
+ * raw diagram source the reader can't explain. The `.mermaid-error` class carries the
93
+ * error styling and a default caption; `data-mermaid-error` overrides that caption with
94
+ * the specific reason (see `.mermaid-error[data-mermaid-error]::before` in the component
95
+ * stylesheet).
96
+ *
97
+ * @param blocks The mermaid code blocks (or `.mermaid` elements) that could not be rendered
98
+ * @param message Short reason shown above the un-rendered source
99
+ */
100
+ private markMermaidBlocksFailed;
69
101
  /**
70
102
  * Highlight code blocks with Prism
71
103
  * Call this after the HTML has been inserted into the DOM
@@ -1 +1 @@
1
- {"version":3,"file":"markdown.service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/markdown.service.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,cAAc,EAEd,sBAAsB,EACtB,WAAW,EAEZ,MAAM,+BAA+B,CAAC;AAIvC,OAAO,qCAAqC,CAAC;AAC7C,OAAO,qCAAqC,CAAC;AAC7C,OAAO,8BAA8B,CAAC;AACtC,OAAO,+BAA+B,CAAC;AACvC,OAAO,+BAA+B,CAAC;AACvC,OAAO,+BAA+B,CAAC;AACvC,OAAO,8BAA8B,CAAC;AACtC,OAAO,iCAAiC,CAAC;AACzC,OAAO,iCAAiC,CAAC;AACzC,OAAO,+BAA+B,CAAC;AACvC,OAAO,iCAAiC,CAAC;AACzC,OAAO,+BAA+B,CAAC;AACvC,OAAO,mCAAmC,CAAC;AAC3C,OAAO,kCAAkC,CAAC;;AAE1C;;;;;;;;;GASG;AACH,qBAGa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAAwB;IACtC,OAAO,CAAC,kBAAkB,CAAS;IACnC,OAAO,CAAC,gBAAgB,CAAuB;IAC/C,OAAO,CAAC,aAAa,CAA0D;IAE/E;;;OAGG;IACH,OAAO,CAAC,cAAc,CAUpB;;IAMF;;OAEG;IACI,eAAe,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI;IAKpD;;;;;OAKG;IACI,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,MAAM;IAoBxE;;OAEG;IACU,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;IAI5F;;;OAGG;IACH,OAAO,CAAC,4BAA4B;IAUpC;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAmBzB;;;;OAIG;IACU,aAAa,CAAC,SAAS,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC;IA0CpE;;;;OAIG;IACI,aAAa,CAAC,SAAS,EAAE,WAAW,GAAG,IAAI;IAOlD;;;OAGG;IACI,kBAAkB,CAAC,SAAS,EAAE,WAAW,GAAG,IAAI;IAiDvD;;;;OAIG;IACI,6BAA6B,CAAC,UAAU,EAAE,WAAW,GAAG,IAAI;IAKnE;;;OAGG;IACI,cAAc,IAAI,WAAW,EAAE;IAItC;;OAEG;IACI,SAAS,IAAI,sBAAsB;IAI1C;;OAEG;IACI,WAAW,IAAI,IAAI;IAI1B;;OAEG;IACI,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAIjD;;OAEG;IACI,qBAAqB,IAAI,MAAM,EAAE;IAMxC;;;;;;;;OAQG;IACH,OAAO,CAAC,kBAAkB;IAsE1B;;;;OAIG;IACH,OAAO,CAAC,uBAAuB;yCA5VpB,eAAe;6CAAf,eAAe;CAoX3B"}
1
+ {"version":3,"file":"markdown.service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/markdown.service.ts"],"names":[],"mappings":"AAKA,OAAO,EAEL,cAAc,EAEd,sBAAsB,EACtB,WAAW,EAEZ,MAAM,+BAA+B,CAAC;AAIvC,OAAO,qCAAqC,CAAC;AAC7C,OAAO,qCAAqC,CAAC;AAC7C,OAAO,8BAA8B,CAAC;AACtC,OAAO,+BAA+B,CAAC;AACvC,OAAO,+BAA+B,CAAC;AACvC,OAAO,+BAA+B,CAAC;AACvC,OAAO,8BAA8B,CAAC;AACtC,OAAO,iCAAiC,CAAC;AACzC,OAAO,iCAAiC,CAAC;AACzC,OAAO,+BAA+B,CAAC;AACvC,OAAO,iCAAiC,CAAC;AACzC,OAAO,+BAA+B,CAAC;AACvC,OAAO,mCAAmC,CAAC;AAC3C,OAAO,kCAAkC,CAAC;;AAE1C;;;;;;;;;GASG;AACH,qBAGa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAAwB;IACtC,OAAO,CAAC,kBAAkB,CAAS;IACnC,OAAO,CAAC,gBAAgB,CAAuB;IAC/C,OAAO,CAAC,aAAa,CAA0D;IAC/E,qGAAqG;IACrG,OAAO,CAAC,aAAa,CAAwB;IAC7C,2EAA2E;IAC3E,OAAO,CAAC,WAAW,CAAiC;IAEpD;;;OAGG;IACH,OAAO,CAAC,cAAc,CAUpB;;IAMF;;OAEG;IACI,eAAe,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI;IAKpD;;;;;OAKG;IACI,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,MAAM;IAoBxE;;OAEG;IACU,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;IAI5F;;;OAGG;IACH,OAAO,CAAC,4BAA4B;IAUpC;;;;;;;;;;;;;;OAcG;YACW,WAAW;IAgBzB;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;IAmBzB;;;;OAIG;IACU,aAAa,CAAC,SAAS,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC;IAsDpE;;;;;;;;;OASG;IACH,OAAO,CAAC,uBAAuB;IAS/B;;;;OAIG;IACI,aAAa,CAAC,SAAS,EAAE,WAAW,GAAG,IAAI;IAOlD;;;OAGG;IACI,kBAAkB,CAAC,SAAS,EAAE,WAAW,GAAG,IAAI;IAiDvD;;;;OAIG;IACI,6BAA6B,CAAC,UAAU,EAAE,WAAW,GAAG,IAAI;IAKnE;;;OAGG;IACI,cAAc,IAAI,WAAW,EAAE;IAItC;;OAEG;IACI,SAAS,IAAI,sBAAsB;IAI1C;;OAEG;IACI,WAAW,IAAI,IAAI;IAI1B;;OAEG;IACI,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAIjD;;OAEG;IACI,qBAAqB,IAAI,MAAM,EAAE;IAMxC;;;;;;;;OAQG;IACH,OAAO,CAAC,kBAAkB;IAsE1B;;;;OAIG;IACH,OAAO,CAAC,uBAAuB;yCA/ZpB,eAAe;6CAAf,eAAe;CAub3B"}
@@ -82,5 +82,49 @@ describe('MarkdownService (DOM)', () => {
82
82
  expect(service.isLanguageSupported('definitely-not-a-language')).toBe(false);
83
83
  });
84
84
  });
85
+ describe('renderMermaid', () => {
86
+ /** Stub the private lazy loader — the point of these specs is WHEN it is called, not what it returns. */
87
+ const stubLoader = (impl) => {
88
+ service.loadMermaid = impl;
89
+ };
90
+ const containerWith = (html) => {
91
+ const container = document.createElement('div');
92
+ container.innerHTML = html;
93
+ return container;
94
+ };
95
+ it('never loads the diagram engine when the document has no diagram', async () => {
96
+ let loads = 0;
97
+ stubLoader(async () => {
98
+ loads++;
99
+ throw new Error('the engine must not be fetched for a diagram-free document');
100
+ });
101
+ const result = await service.renderMermaid(containerWith('<p>no diagrams here</p>'));
102
+ expect(result).toBe(false);
103
+ expect(loads).toBe(0);
104
+ });
105
+ it('shows a visible failure on every block when the lazy chunk cannot be loaded', async () => {
106
+ stubLoader(() => Promise.reject(new Error('chunk fetch failed')));
107
+ const container = containerWith('<pre><code class="language-mermaid">graph TD; A-->B;</code></pre>' +
108
+ '<div class="mermaid">graph TD; C-->D;</div>');
109
+ const result = await service.renderMermaid(container);
110
+ expect(result).toBe(false);
111
+ const failed = container.querySelectorAll('.mermaid-error');
112
+ expect(failed.length).toBe(2);
113
+ for (const element of Array.from(failed)) {
114
+ expect(element.getAttribute('data-mermaid-error')).toBe('Diagram engine failed to load');
115
+ }
116
+ });
117
+ it('does nothing at all when mermaid is disabled by config', async () => {
118
+ let loads = 0;
119
+ stubLoader(async () => {
120
+ loads++;
121
+ throw new Error('the engine must not be fetched when mermaid is disabled');
122
+ });
123
+ service.configureMarked({ enableMermaid: false });
124
+ const result = await service.renderMermaid(containerWith('<pre><code class="language-mermaid">graph TD; A-->B;</code></pre>'));
125
+ expect(result).toBe(false);
126
+ expect(loads).toBe(0);
127
+ });
128
+ });
85
129
  });
86
130
  //# sourceMappingURL=markdown.service.dom.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"markdown.service.dom.test.js","sourceRoot":"","sources":["../../../src/lib/services/markdown.service.dom.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, it, expect, beforeEach } from 'vitest';\nimport { MarkdownService } from './markdown.service';\n\n/**\n * jsdom specs for the web shell around @memberjunction/markdown-core.\n *\n * These focus on the browser-only concerns that stay in the Angular service:\n * the `parse()` entry points and the DOM-based `unwrapMiscodedHtml()` fixup.\n * The framework-agnostic parsing itself is covered in @memberjunction/markdown-core.\n *\n * `unwrapMiscodedHtml` is private, so we reach it through a typed cast — the same\n * pattern the markdown-core engine spec uses to poke the internal marked instance.\n * This keeps the assertions deterministic (exact HTML in, exact behavior out)\n * rather than depending on which fixtures marked happens to miscode.\n */\ntype UnwrapAccessor = { unwrapMiscodedHtml(html: string): string };\n\ndescribe('MarkdownService (DOM)', () => {\n let service: MarkdownService;\n let unwrap: (html: string) => string;\n\n beforeEach(() => {\n service = new MarkdownService();\n unwrap = (html: string) => (service as unknown as UnwrapAccessor).unwrapMiscodedHtml(html);\n });\n\n describe('parse', () => {\n it('returns empty string for empty input', () => {\n expect(service.parse('')).toBe('');\n });\n\n it('renders basic prose to a paragraph', () => {\n expect(service.parse('hello world')).toContain('<p>');\n });\n\n it('accepts per-call config overrides without throwing', () => {\n expect(() => service.parse('# Title', { enableHtml: true })).not.toThrow();\n });\n\n it('parseAsync resolves to the same output as parse', async () => {\n const md = '**bold**';\n expect(await service.parseAsync(md)).toBe(service.parse(md));\n });\n });\n\n describe('unwrapMiscodedHtml', () => {\n it('is a no-op when there are no <pre> blocks', () => {\n const html = '<p>just a paragraph</p>';\n expect(unwrap(html)).toBe(html);\n });\n\n it('returns empty input unchanged', () => {\n expect(unwrap('')).toBe('');\n });\n\n it('skips content containing <svg (DOMParser would mangle SVG namespaces)', () => {\n const html = '<pre><code>&lt;svg&gt;&lt;rect/&gt;&lt;/svg&gt;</code></pre>';\n // Left untouched precisely because it contains \"<svg\".\n expect(unwrap(html)).toBe(html);\n });\n\n it('preserves an intentional code block that carries a language class', () => {\n const html = '<pre><code class=\"language-html\">&lt;div&gt;&lt;span&gt;x&lt;/span&gt;&lt;/div&gt;</code></pre>';\n const out = unwrap(html);\n // Language-classed code is an intentional example → NOT unwrapped.\n expect(out).toContain('language-html');\n expect(out).toContain('<pre>');\n });\n\n it('unwraps structural HTML that marked miscoded into a <pre><code> block', () => {\n // textContent decodes to <div><span>x</span></div> — multi-tag structural HTML.\n const html = '<pre><code>&lt;div&gt;&lt;span&gt;x&lt;/span&gt;&lt;/div&gt;</code></pre>';\n const out = unwrap(html);\n expect(out).toContain('<div>');\n expect(out).toContain('<span>x</span>');\n // The escaped code-block form must be gone.\n expect(out).not.toContain('&lt;div&gt;');\n expect(out).not.toContain('<pre>');\n });\n\n it('does not unwrap a single self-closing tag (needs 2+ tags to qualify)', () => {\n const html = '<pre><code>&lt;br&gt;</code></pre>';\n // tagCount < 2 → looksLikeStructuralHtml returns false → left as-is.\n expect(unwrap(html)).toBe(html);\n });\n\n it('does not unwrap an empty code block', () => {\n const html = '<pre><code></code></pre>';\n expect(unwrap(html)).toBe(html);\n });\n\n it('does not unwrap plain non-HTML code content', () => {\n const html = '<pre><code>const x = 1; // not html</code></pre>';\n expect(unwrap(html)).toBe(html);\n });\n\n it('does not throw on malformed markup', () => {\n expect(() => unwrap('<pre><code>&lt;div&gt;&lt;span&gt;unclosed')).not.toThrow();\n });\n });\n\n describe('config accessors', () => {\n it('resetConfig restores enableHtml to the default (false)', () => {\n service.configureMarked({ enableHtml: true });\n expect(service.getConfig().enableHtml).toBe(true);\n service.resetConfig();\n expect(service.getConfig().enableHtml).toBe(false);\n });\n\n it('isLanguageSupported reflects Prism registration', () => {\n // typescript is imported by the service; a made-up language is not.\n expect(service.isLanguageSupported('typescript')).toBe(true);\n expect(service.isLanguageSupported('definitely-not-a-language')).toBe(false);\n });\n });\n});\n"]}
1
+ {"version":3,"file":"markdown.service.dom.test.js","sourceRoot":"","sources":["../../../src/lib/services/markdown.service.dom.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, it, expect, beforeEach } from 'vitest';\nimport type { Mermaid } from 'mermaid';\nimport { MarkdownService } from './markdown.service';\n\n/**\n * jsdom specs for the web shell around @memberjunction/markdown-core.\n *\n * These focus on the browser-only concerns that stay in the Angular service:\n * the `parse()` entry points and the DOM-based `unwrapMiscodedHtml()` fixup.\n * The framework-agnostic parsing itself is covered in @memberjunction/markdown-core.\n *\n * `unwrapMiscodedHtml` is private, so we reach it through a typed cast — the same\n * pattern the markdown-core engine spec uses to poke the internal marked instance.\n * This keeps the assertions deterministic (exact HTML in, exact behavior out)\n * rather than depending on which fixtures marked happens to miscode.\n */\ntype UnwrapAccessor = { unwrapMiscodedHtml(html: string): string };\n\n/** Same private-member reach-in, for the lazily-loaded mermaid engine (see `loadMermaid`). */\ntype MermaidLoaderAccessor = { loadMermaid(): Promise<Mermaid> };\n\ndescribe('MarkdownService (DOM)', () => {\n let service: MarkdownService;\n let unwrap: (html: string) => string;\n\n beforeEach(() => {\n service = new MarkdownService();\n unwrap = (html: string) => (service as unknown as UnwrapAccessor).unwrapMiscodedHtml(html);\n });\n\n describe('parse', () => {\n it('returns empty string for empty input', () => {\n expect(service.parse('')).toBe('');\n });\n\n it('renders basic prose to a paragraph', () => {\n expect(service.parse('hello world')).toContain('<p>');\n });\n\n it('accepts per-call config overrides without throwing', () => {\n expect(() => service.parse('# Title', { enableHtml: true })).not.toThrow();\n });\n\n it('parseAsync resolves to the same output as parse', async () => {\n const md = '**bold**';\n expect(await service.parseAsync(md)).toBe(service.parse(md));\n });\n });\n\n describe('unwrapMiscodedHtml', () => {\n it('is a no-op when there are no <pre> blocks', () => {\n const html = '<p>just a paragraph</p>';\n expect(unwrap(html)).toBe(html);\n });\n\n it('returns empty input unchanged', () => {\n expect(unwrap('')).toBe('');\n });\n\n it('skips content containing <svg (DOMParser would mangle SVG namespaces)', () => {\n const html = '<pre><code>&lt;svg&gt;&lt;rect/&gt;&lt;/svg&gt;</code></pre>';\n // Left untouched precisely because it contains \"<svg\".\n expect(unwrap(html)).toBe(html);\n });\n\n it('preserves an intentional code block that carries a language class', () => {\n const html = '<pre><code class=\"language-html\">&lt;div&gt;&lt;span&gt;x&lt;/span&gt;&lt;/div&gt;</code></pre>';\n const out = unwrap(html);\n // Language-classed code is an intentional example → NOT unwrapped.\n expect(out).toContain('language-html');\n expect(out).toContain('<pre>');\n });\n\n it('unwraps structural HTML that marked miscoded into a <pre><code> block', () => {\n // textContent decodes to <div><span>x</span></div> — multi-tag structural HTML.\n const html = '<pre><code>&lt;div&gt;&lt;span&gt;x&lt;/span&gt;&lt;/div&gt;</code></pre>';\n const out = unwrap(html);\n expect(out).toContain('<div>');\n expect(out).toContain('<span>x</span>');\n // The escaped code-block form must be gone.\n expect(out).not.toContain('&lt;div&gt;');\n expect(out).not.toContain('<pre>');\n });\n\n it('does not unwrap a single self-closing tag (needs 2+ tags to qualify)', () => {\n const html = '<pre><code>&lt;br&gt;</code></pre>';\n // tagCount < 2 → looksLikeStructuralHtml returns false → left as-is.\n expect(unwrap(html)).toBe(html);\n });\n\n it('does not unwrap an empty code block', () => {\n const html = '<pre><code></code></pre>';\n expect(unwrap(html)).toBe(html);\n });\n\n it('does not unwrap plain non-HTML code content', () => {\n const html = '<pre><code>const x = 1; // not html</code></pre>';\n expect(unwrap(html)).toBe(html);\n });\n\n it('does not throw on malformed markup', () => {\n expect(() => unwrap('<pre><code>&lt;div&gt;&lt;span&gt;unclosed')).not.toThrow();\n });\n });\n\n describe('config accessors', () => {\n it('resetConfig restores enableHtml to the default (false)', () => {\n service.configureMarked({ enableHtml: true });\n expect(service.getConfig().enableHtml).toBe(true);\n service.resetConfig();\n expect(service.getConfig().enableHtml).toBe(false);\n });\n\n it('isLanguageSupported reflects Prism registration', () => {\n // typescript is imported by the service; a made-up language is not.\n expect(service.isLanguageSupported('typescript')).toBe(true);\n expect(service.isLanguageSupported('definitely-not-a-language')).toBe(false);\n });\n });\n\n describe('renderMermaid', () => {\n /** Stub the private lazy loader — the point of these specs is WHEN it is called, not what it returns. */\n const stubLoader = (impl: () => Promise<Mermaid>): void => {\n (service as unknown as MermaidLoaderAccessor).loadMermaid = impl;\n };\n\n const containerWith = (html: string): HTMLElement => {\n const container = document.createElement('div');\n container.innerHTML = html;\n return container;\n };\n\n it('never loads the diagram engine when the document has no diagram', async () => {\n let loads = 0;\n stubLoader(async () => {\n loads++;\n throw new Error('the engine must not be fetched for a diagram-free document');\n });\n\n const result = await service.renderMermaid(containerWith('<p>no diagrams here</p>'));\n\n expect(result).toBe(false);\n expect(loads).toBe(0);\n });\n\n it('shows a visible failure on every block when the lazy chunk cannot be loaded', async () => {\n stubLoader(() => Promise.reject(new Error('chunk fetch failed')));\n const container = containerWith(\n '<pre><code class=\"language-mermaid\">graph TD; A-->B;</code></pre>' +\n '<div class=\"mermaid\">graph TD; C-->D;</div>',\n );\n\n const result = await service.renderMermaid(container);\n\n expect(result).toBe(false);\n const failed = container.querySelectorAll('.mermaid-error');\n expect(failed.length).toBe(2);\n for (const element of Array.from(failed)) {\n expect(element.getAttribute('data-mermaid-error')).toBe('Diagram engine failed to load');\n }\n });\n\n it('does nothing at all when mermaid is disabled by config', async () => {\n let loads = 0;\n stubLoader(async () => {\n loads++;\n throw new Error('the engine must not be fetched when mermaid is disabled');\n });\n service.configureMarked({ enableMermaid: false });\n\n const result = await service.renderMermaid(\n containerWith('<pre><code class=\"language-mermaid\">graph TD; A-->B;</code></pre>'),\n );\n\n expect(result).toBe(false);\n expect(loads).toBe(0);\n });\n });\n});\n"]}
@@ -1,6 +1,5 @@
1
1
  import { Injectable } from '@angular/core';
2
2
  import Prism from 'prismjs';
3
- import mermaid from 'mermaid';
4
3
  import { MarkdownEngine, DEFAULT_MARKDOWN_CONFIG } from '@memberjunction/markdown-core';
5
4
  // Import common Prism language components
6
5
  // Additional languages can be imported by the consuming application
@@ -35,6 +34,10 @@ export class MarkdownService {
35
34
  this.mermaidInitialized = false;
36
35
  this.lastMermaidTheme = null;
37
36
  this.currentConfig = { ...DEFAULT_MARKDOWN_CONFIG };
37
+ /** The mermaid engine, once its lazy chunk has landed. Null until a diagram is actually rendered. */
38
+ this.mermaidEngine = null;
39
+ /** In-flight load, so N diagrams in one document share ONE chunk fetch. */
40
+ this.mermaidLoad = null;
38
41
  /**
39
42
  * Prism-backed highlight function injected into the core engine. The engine
40
43
  * itself has no Prism dependency — it calls this for the HTML output path.
@@ -101,11 +104,41 @@ export class MarkdownService {
101
104
  && document.documentElement.getAttribute('data-theme') === 'dark';
102
105
  return isDark ? 'dark' : 'default';
103
106
  }
107
+ /**
108
+ * Fetch the mermaid engine, on first use only.
109
+ *
110
+ * MEASURED BUNDLE DEFERRAL — this is deliberately a dynamic `import()`, NOT a top-level
111
+ * one, and must not be "tidied" back into a static import. `mermaid` and the parsing /
112
+ * graph engines it pulls with it (`@mermaid-js/parser`, `langium`, `chevrotain`,
113
+ * `cytoscape`, `katex`, `d3`) were ~0.95 MB raw of every host's initial download even
114
+ * when no document ever contained a diagram. That is a rounding error for an in-app
115
+ * host and the whole cost for a CDN-loaded embedding of the realtime session overlay
116
+ * (MJ #3882). Documents with no diagram now never fetch the chunk at all.
117
+ *
118
+ * The in-flight promise is cached so N diagrams in one document share one fetch; a
119
+ * FAILED load clears it, so a later render can retry. Retries are bounded by
120
+ * user-driven render calls — there is no loop here.
121
+ */
122
+ async loadMermaid() {
123
+ if (this.mermaidEngine) {
124
+ return this.mermaidEngine;
125
+ }
126
+ this.mermaidLoad ??= import('mermaid').then((module) => module.default);
127
+ try {
128
+ this.mermaidEngine = await this.mermaidLoad;
129
+ return this.mermaidEngine;
130
+ }
131
+ catch (error) {
132
+ this.mermaidLoad = null;
133
+ throw error;
134
+ }
135
+ }
104
136
  /**
105
137
  * Initialize Mermaid with the current theme configuration.
106
138
  * Re-initializes when the effective theme changes.
139
+ * @param mermaid The lazily-loaded mermaid engine (see {@link loadMermaid})
107
140
  */
108
- initializeMermaid() {
141
+ initializeMermaid(mermaid) {
109
142
  const effectiveTheme = this.resolveEffectiveMermaidTheme();
110
143
  if (this.mermaidInitialized && this.lastMermaidTheme === effectiveTheme) {
111
144
  return;
@@ -128,11 +161,24 @@ export class MarkdownService {
128
161
  async renderMermaid(container) {
129
162
  if (!this.currentConfig.enableMermaid)
130
163
  return false;
131
- this.initializeMermaid();
132
- // Find all mermaid code blocks
164
+ // Find all mermaid code blocks BEFORE touching the engine — this is the branch the
165
+ // deferral hangs off: no diagrams in this document means no chunk fetch at all.
133
166
  const mermaidBlocks = container.querySelectorAll('pre > code.language-mermaid, .mermaid');
134
167
  if (mermaidBlocks.length === 0)
135
168
  return false;
169
+ let mermaid;
170
+ try {
171
+ mermaid = await this.loadMermaid();
172
+ }
173
+ catch (error) {
174
+ // The chunk could not be fetched (offline, blocked host, bad deploy). Say so on
175
+ // every block we were going to render — an empty panel would look like the diagram
176
+ // simply wasn't there.
177
+ console.error('Failed to load the Mermaid diagram engine:', error);
178
+ this.markMermaidBlocksFailed(mermaidBlocks, 'Diagram engine failed to load');
179
+ return false;
180
+ }
181
+ this.initializeMermaid(mermaid);
136
182
  for (let i = 0; i < mermaidBlocks.length; i++) {
137
183
  const block = mermaidBlocks[i];
138
184
  const code = block.textContent || '';
@@ -153,13 +199,30 @@ export class MarkdownService {
153
199
  }
154
200
  catch (error) {
155
201
  console.warn('Mermaid rendering failed:', error);
156
- // Add error class to show it failed
157
- const parent = block.tagName === 'CODE' ? block.parentElement : block;
158
- parent?.classList.add('mermaid-error');
202
+ // Show it failed on this block — the source stays visible underneath.
203
+ this.markMermaidBlocksFailed([block], 'Diagram rendering failed');
159
204
  }
160
205
  }
161
206
  return true;
162
207
  }
208
+ /**
209
+ * Mark mermaid blocks as un-renderable, so the failure is VISIBLE rather than a block of
210
+ * raw diagram source the reader can't explain. The `.mermaid-error` class carries the
211
+ * error styling and a default caption; `data-mermaid-error` overrides that caption with
212
+ * the specific reason (see `.mermaid-error[data-mermaid-error]::before` in the component
213
+ * stylesheet).
214
+ *
215
+ * @param blocks The mermaid code blocks (or `.mermaid` elements) that could not be rendered
216
+ * @param message Short reason shown above the un-rendered source
217
+ */
218
+ markMermaidBlocksFailed(blocks, message) {
219
+ for (let i = 0; i < blocks.length; i++) {
220
+ const block = blocks[i];
221
+ const target = block.tagName === 'CODE' ? block.parentElement : block;
222
+ target?.classList.add('mermaid-error');
223
+ target?.setAttribute('data-mermaid-error', message);
224
+ }
225
+ }
163
226
  /**
164
227
  * Highlight code blocks with Prism
165
228
  * Call this after the HTML has been inserted into the DOM
@@ -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';\nimport mermaid from 'mermaid';\nimport {\n MarkdownEngine,\n MarkdownConfig,\n DEFAULT_MARKDOWN_CONFIG,\n ResolvedMarkdownConfig,\n HeadingInfo,\n HighlightFunction\n} from '@memberjunction/markdown-core';\n\n// Import common Prism language components\n// Additional languages can be imported by the consuming application\nimport 'prismjs/components/prism-typescript';\nimport 'prismjs/components/prism-javascript';\nimport 'prismjs/components/prism-css';\nimport 'prismjs/components/prism-scss';\nimport 'prismjs/components/prism-json';\nimport 'prismjs/components/prism-bash';\nimport 'prismjs/components/prism-sql';\nimport 'prismjs/components/prism-python';\nimport 'prismjs/components/prism-csharp';\nimport 'prismjs/components/prism-java';\nimport 'prismjs/components/prism-markup';\nimport 'prismjs/components/prism-yaml';\nimport 'prismjs/components/prism-markdown';\nimport 'prismjs/components/prism-graphql';\n\n/**\n * Service for parsing and rendering markdown content on the web.\n *\n * The parsing/configuration work (marked setup, custom extensions, HTML and\n * token output) is owned by the framework-agnostic `@memberjunction/markdown-core`\n * {@link MarkdownEngine}. This Angular service is the web shell around it: it\n * injects a Prism-based highlighter into the engine and keeps the browser-only\n * concerns here — Mermaid rendering, copy buttons, and the DOM-based fixup of\n * HTML that marked miscoded as a code block.\n */\n@Injectable({\n providedIn: 'root'\n})\nexport class MarkdownService {\n private engine = new MarkdownEngine();\n private mermaidInitialized = false;\n private lastMermaidTheme: string | null = null;\n private currentConfig: ResolvedMarkdownConfig = { ...DEFAULT_MARKDOWN_CONFIG };\n\n /**\n * Prism-backed highlight function injected into the core engine. The engine\n * itself has no Prism dependency — it calls this for the HTML output path.\n */\n private prismHighlight: HighlightFunction = (code: string, lang: string): string => {\n if (lang && Prism.languages[lang]) {\n try {\n return Prism.highlight(code, Prism.languages[lang], lang);\n } catch (e) {\n console.warn(`Prism highlighting failed for language: ${lang}`, e);\n }\n }\n // Return code as-is if language not found or highlighting fails\n return code;\n };\n\n constructor() {\n this.configureMarked(this.currentConfig);\n }\n\n /**\n * Configure the underlying engine with the provided options.\n */\n public configureMarked(config: MarkdownConfig): void {\n this.currentConfig = { ...DEFAULT_MARKDOWN_CONFIG, ...config };\n this.engine.configureMarked(this.currentConfig, { highlightFn: this.prismHighlight });\n }\n\n /**\n * Parse markdown to HTML.\n * @param markdown The markdown string to parse\n * @param config Optional config overrides for this parse operation\n * @returns The rendered HTML string\n */\n public parse(markdown: string, config?: Partial<MarkdownConfig>): string {\n if (!markdown) return '';\n\n // Apply config overrides if provided (re-injects the Prism highlighter)\n if (config) {\n this.configureMarked({ ...this.currentConfig, ...config });\n }\n\n let html = this.engine.parseToHtml(markdown);\n\n // When HTML passthrough is enabled, fix incorrectly code-wrapped HTML.\n // marked sometimes wraps inline HTML in <pre><code> blocks. This is a\n // DOM-based fixup so it stays in the web service, not the core engine.\n if (this.currentConfig.enableHtml) {\n html = this.unwrapMiscodedHtml(html);\n }\n\n return html;\n }\n\n /**\n * Parse markdown asynchronously (useful for large documents)\n */\n public async parseAsync(markdown: string, config?: Partial<MarkdownConfig>): Promise<string> {\n return this.parse(markdown, config);\n }\n\n /**\n * Resolve the effective mermaid theme.\n * 'auto' maps to 'dark' or 'default' based on the document's data-theme attribute.\n */\n private resolveEffectiveMermaidTheme(): 'default' | 'dark' | 'forest' | 'neutral' | 'base' {\n const configTheme = this.currentConfig.mermaidTheme;\n if (configTheme !== 'auto') {\n return configTheme;\n }\n const isDark = typeof document !== 'undefined'\n && document.documentElement.getAttribute('data-theme') === 'dark';\n return isDark ? 'dark' : 'default';\n }\n\n /**\n * Initialize Mermaid with the current theme configuration.\n * Re-initializes when the effective theme changes.\n */\n private initializeMermaid(): void {\n const effectiveTheme = this.resolveEffectiveMermaidTheme();\n\n if (this.mermaidInitialized && this.lastMermaidTheme === effectiveTheme) {\n return;\n }\n\n mermaid.initialize({\n startOnLoad: false,\n theme: effectiveTheme,\n securityLevel: 'loose',\n fontFamily: 'inherit',\n suppressErrorRendering: true // Suppress visual error diagrams - errors go to console only\n });\n\n this.mermaidInitialized = true;\n this.lastMermaidTheme = effectiveTheme;\n }\n\n /**\n * Render Mermaid diagrams in a container element\n * Call this after the HTML has been inserted into the DOM\n * @param container The DOM element containing mermaid code blocks\n */\n public async renderMermaid(container: HTMLElement): Promise<boolean> {\n if (!this.currentConfig.enableMermaid) return false;\n\n this.initializeMermaid();\n\n // Find all mermaid code blocks\n const mermaidBlocks = container.querySelectorAll('pre > code.language-mermaid, .mermaid');\n\n if (mermaidBlocks.length === 0) return false;\n\n for (let i = 0; i < mermaidBlocks.length; i++) {\n const block = mermaidBlocks[i];\n const code = block.textContent || '';\n\n if (!code.trim()) continue;\n\n try {\n // Create a unique ID for this diagram\n const id = `mermaid-${Date.now()}-${i}`;\n\n // Render the diagram\n const { svg } = await mermaid.render(id, code);\n\n // Replace the code block with the rendered SVG\n const wrapper = document.createElement('div');\n wrapper.className = 'mermaid-diagram';\n wrapper.innerHTML = svg;\n\n // Replace the pre element (parent of code) or the mermaid element itself\n const elementToReplace = block.tagName === 'CODE' ? block.parentElement : block;\n elementToReplace?.parentNode?.replaceChild(wrapper, elementToReplace);\n } catch (error) {\n console.warn('Mermaid rendering failed:', error);\n // Add error class to show it failed\n const parent = block.tagName === 'CODE' ? block.parentElement : block;\n parent?.classList.add('mermaid-error');\n }\n }\n\n return true;\n }\n\n /**\n * Highlight code blocks with Prism\n * Call this after the HTML has been inserted into the DOM\n * @param container The DOM element containing code blocks\n */\n public highlightCode(container: HTMLElement): void {\n if (!this.currentConfig.enableHighlight) return;\n\n // Prism.highlightAllUnder handles finding and highlighting code blocks\n Prism.highlightAllUnder(container);\n }\n\n /**\n * Add copy buttons to code blocks\n * @param container The DOM element containing code blocks\n */\n public addCodeCopyButtons(container: HTMLElement): void {\n if (!this.currentConfig.enableCodeCopy) return;\n\n const codeBlocks = container.querySelectorAll('pre > code');\n\n codeBlocks.forEach((codeBlock) => {\n const pre = codeBlock.parentElement;\n if (!pre || pre.querySelector('.code-copy-btn')) return; // Already has button\n\n // Create copy button\n const button = document.createElement('button');\n button.className = 'code-copy-btn';\n button.innerHTML = '<i class=\"fas fa-copy\"></i>';\n button.title = 'Copy code';\n button.type = 'button';\n\n button.addEventListener('click', async () => {\n const code = codeBlock.textContent || '';\n\n try {\n await navigator.clipboard.writeText(code);\n button.innerHTML = '<i class=\"fas fa-check\"></i>';\n button.classList.add('copied');\n\n setTimeout(() => {\n button.innerHTML = '<i class=\"fas fa-copy\"></i>';\n button.classList.remove('copied');\n }, 2000);\n } catch (err) {\n console.error('Failed to copy code:', err);\n button.innerHTML = '<i class=\"fas fa-times\"></i>';\n\n setTimeout(() => {\n button.innerHTML = '<i class=\"fas fa-copy\"></i>';\n }, 2000);\n }\n });\n\n // Add toolbar wrapper\n const toolbar = document.createElement('div');\n toolbar.className = 'code-toolbar';\n toolbar.appendChild(button);\n\n // Make pre position relative for absolute positioning of toolbar\n pre.style.position = 'relative';\n pre.appendChild(toolbar);\n });\n }\n\n /**\n * Initialize collapsible heading functionality\n * This method is a no-op - the component handles event binding\n * @param container The DOM element containing collapsible sections\n */\n public initializeCollapsibleHeadings(_container: HTMLElement): void {\n // Event binding is handled by the component's setupCollapsibleListeners\n // This method exists for API compatibility but does nothing\n }\n\n /**\n * Get the list of headings from the last parsed document\n * Useful for building table of contents\n */\n public getHeadingList(): HeadingInfo[] {\n return this.engine.getHeadingList();\n }\n\n /**\n * Get the current configuration\n */\n public getConfig(): ResolvedMarkdownConfig {\n return this.engine.getConfig();\n }\n\n /**\n * Reset configuration to defaults\n */\n public resetConfig(): void {\n this.configureMarked(DEFAULT_MARKDOWN_CONFIG);\n }\n\n /**\n * Check if a language is supported by Prism\n */\n public isLanguageSupported(lang: string): boolean {\n return !!Prism.languages[lang];\n }\n\n /**\n * Get list of supported Prism languages\n */\n public getSupportedLanguages(): string[] {\n return Object.keys(Prism.languages).filter(\n lang => typeof Prism.languages[lang] === 'object'\n );\n }\n\n /**\n * Fix HTML that was incorrectly wrapped in <pre><code> blocks by marked.\n * This happens when marked interprets inline HTML (especially indented HTML)\n * as code blocks. We detect this by checking if the code block content\n * looks like valid HTML structure rather than actual code.\n *\n * Only processes code blocks WITHOUT a language class (e.g., language-javascript)\n * to avoid unwrapping intentional code examples.\n */\n private unwrapMiscodedHtml(html: string): string {\n // Quick check - if no pre tags, nothing to do\n if (!html.includes('<pre>')) {\n return html;\n }\n\n // Skip if SVG is present - DOMParser mangles SVG elements like <rect>\n // when parsing as 'text/html' due to namespace issues\n if (html.includes('<svg')) {\n return html;\n }\n\n try {\n const parser = new DOMParser();\n const doc = parser.parseFromString(`<div>${html}</div>`, 'text/html');\n const container = doc.body.firstChild as HTMLElement;\n\n if (!container) return html;\n\n // Find all pre > code elements WITHOUT a language class\n // Code blocks with language classes (language-javascript, etc.) are intentional\n const preElements = container.querySelectorAll('pre');\n let modified = false;\n\n for (const pre of Array.from(preElements)) {\n const code = pre.querySelector('code');\n if (!code) continue;\n\n // Skip if code has a language class - it's intentional code\n const hasLanguageClass = code.className && /language-\\w+/.test(code.className);\n if (hasLanguageClass) continue;\n\n // Get the text content (this is HTML-decoded by the browser)\n const content = code.textContent?.trim() || '';\n\n // Check if this looks like HTML that was incorrectly wrapped\n if (this.looksLikeStructuralHtml(content)) {\n // Verify it parses as valid HTML with actual elements\n const testDoc = parser.parseFromString(content, 'text/html');\n const hasStructure = testDoc.body.children.length > 0 ||\n (testDoc.body.innerHTML.trim().length > 0 &&\n testDoc.body.innerHTML.includes('<'));\n\n if (hasStructure) {\n // Replace the <pre> with the actual HTML content\n const wrapper = document.createElement('div');\n wrapper.className = 'unwrapped-html';\n wrapper.innerHTML = content;\n\n // Move all children from wrapper to replace pre\n const fragment = document.createDocumentFragment();\n while (wrapper.firstChild) {\n fragment.appendChild(wrapper.firstChild);\n }\n pre.parentNode?.replaceChild(fragment, pre);\n modified = true;\n }\n }\n }\n\n if (modified) {\n return container.innerHTML;\n }\n } catch (error) {\n console.warn('Error in unwrapMiscodedHtml:', error);\n }\n\n return html;\n }\n\n /**\n * Check if content looks like structural HTML that was incorrectly\n * wrapped in a code block. We look for common HTML element patterns\n * that indicate this is meant to be rendered HTML, not code.\n */\n private looksLikeStructuralHtml(content: string): boolean {\n // Must start with < to be HTML\n if (!content.startsWith('<')) return false;\n\n // Must end with > (closing tag)\n if (!content.endsWith('>')) return false;\n\n // Check for common structural HTML tags that indicate layout HTML\n // These are tags that would typically appear in a UI mockup/prototype\n const structuralTagPattern = /<(div|span|table|tr|td|th|thead|tbody|p|ul|ol|li|section|article|header|footer|nav|main|aside|form|input|button|label|select|option|textarea|h[1-6]|img|a|strong|em|b|i|br|hr)\\b/i;\n\n if (!structuralTagPattern.test(content)) return false;\n\n // Additional check: should have multiple tags or nested structure\n // Single self-closing tags like <br> or <img> shouldn't trigger unwrapping\n const tagCount = (content.match(/<\\w+/g) || []).length;\n if (tagCount < 2) return false;\n\n // Check it's not just showing HTML as an example (common in docs)\n // If content has lots of &lt; or &gt; it's probably escaped HTML being shown\n if (content.includes('&lt;') || content.includes('&gt;')) return false;\n\n return true;\n }\n}\n"]}
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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memberjunction/ng-markdown",
3
- "version": "6.1.0-edge.3",
3
+ "version": "6.1.0-edge.5",
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.1.3",
22
22
  "@angular/compiler-cli": "21.1.3",
23
- "@memberjunction/ng-test-utils": "6.1.0-edge.3",
23
+ "@memberjunction/ng-test-utils": "6.1.0-edge.5",
24
24
  "jsdom": "^26.1.0",
25
25
  "vitest": "^4.0.18"
26
26
  },
@@ -30,7 +30,7 @@
30
30
  "@angular/platform-browser": "^21.1.3"
31
31
  },
32
32
  "dependencies": {
33
- "@memberjunction/markdown-core": "6.1.0-edge.3",
33
+ "@memberjunction/markdown-core": "6.1.0-edge.5",
34
34
  "prismjs": "^1.30.0",
35
35
  "mermaid": "^11.12.2",
36
36
  "tslib": "^2.8.1"