@memberjunction/ng-markdown 0.0.1 → 2.126.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +211 -43
- package/dist/lib/components/markdown.component.d.ts +210 -0
- package/dist/lib/components/markdown.component.js +553 -0
- package/dist/lib/components/markdown.component.js.map +1 -0
- package/dist/lib/extensions/code-copy.extension.d.ts +71 -0
- package/dist/lib/extensions/code-copy.extension.js +169 -0
- package/dist/lib/extensions/code-copy.extension.js.map +1 -0
- package/dist/lib/extensions/collapsible-headings.extension.d.ts +86 -0
- package/dist/lib/extensions/collapsible-headings.extension.js +240 -0
- package/dist/lib/extensions/collapsible-headings.extension.js.map +1 -0
- package/dist/lib/extensions/svg-renderer.extension.d.ts +38 -0
- package/dist/lib/extensions/svg-renderer.extension.js +126 -0
- package/dist/lib/extensions/svg-renderer.extension.js.map +1 -0
- package/dist/lib/markdown.module.d.ts +38 -0
- package/dist/lib/markdown.module.js +59 -0
- package/dist/lib/markdown.module.js.map +1 -0
- package/dist/lib/services/markdown.service.d.ts +101 -0
- package/dist/lib/services/markdown.service.js +310 -0
- package/dist/lib/services/markdown.service.js.map +1 -0
- package/dist/lib/types/markdown.types.d.ts +191 -0
- package/dist/lib/types/markdown.types.js +25 -0
- package/dist/lib/types/markdown.types.js.map +1 -0
- package/dist/public-api.d.ts +7 -0
- package/dist/public-api.js +14 -0
- package/dist/public-api.js.map +1 -0
- package/package.json +44 -6
|
@@ -0,0 +1,553 @@
|
|
|
1
|
+
import { Component, Input, Output, EventEmitter, ElementRef, SecurityContext, ViewEncapsulation, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
|
|
2
|
+
import { DomSanitizer } from '@angular/platform-browser';
|
|
3
|
+
import { MarkdownService } from '../services/markdown.service';
|
|
4
|
+
import { DEFAULT_MARKDOWN_CONFIG } from '../types/markdown.types';
|
|
5
|
+
import * as i0 from "@angular/core";
|
|
6
|
+
import * as i1 from "@angular/platform-browser";
|
|
7
|
+
import * as i2 from "../services/markdown.service";
|
|
8
|
+
// Collapsible section toggle is handled inline in setupCollapsibleListeners
|
|
9
|
+
/**
|
|
10
|
+
* Angular component for rendering markdown content.
|
|
11
|
+
*
|
|
12
|
+
* Features:
|
|
13
|
+
* - Prism.js syntax highlighting for code blocks
|
|
14
|
+
* - Mermaid diagram rendering
|
|
15
|
+
* - Copy-to-clipboard for code blocks
|
|
16
|
+
* - Collapsible heading sections
|
|
17
|
+
* - GitHub-style alerts and heading IDs
|
|
18
|
+
*
|
|
19
|
+
* Usage:
|
|
20
|
+
* ```html
|
|
21
|
+
* <mj-markdown [data]="markdownContent"></mj-markdown>
|
|
22
|
+
*
|
|
23
|
+
* <mj-markdown
|
|
24
|
+
* [data]="content"
|
|
25
|
+
* [enableMermaid]="true"
|
|
26
|
+
* [enableCollapsibleHeadings]="true"
|
|
27
|
+
* (rendered)="onRendered($event)">
|
|
28
|
+
* </mj-markdown>
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export class MarkdownComponent {
|
|
32
|
+
/**
|
|
33
|
+
* Public accessor for the component's element reference.
|
|
34
|
+
* Provided for backward compatibility with ngx-markdown API.
|
|
35
|
+
*/
|
|
36
|
+
get element() {
|
|
37
|
+
return this.elementRef;
|
|
38
|
+
}
|
|
39
|
+
constructor(elementRef, sanitizer, markdownService, cdr) {
|
|
40
|
+
this.elementRef = elementRef;
|
|
41
|
+
this.sanitizer = sanitizer;
|
|
42
|
+
this.markdownService = markdownService;
|
|
43
|
+
this.cdr = cdr;
|
|
44
|
+
/**
|
|
45
|
+
* The markdown content to render
|
|
46
|
+
*/
|
|
47
|
+
this.data = '';
|
|
48
|
+
/**
|
|
49
|
+
* Enable syntax highlighting
|
|
50
|
+
*/
|
|
51
|
+
this.enableHighlight = DEFAULT_MARKDOWN_CONFIG.enableHighlight;
|
|
52
|
+
/**
|
|
53
|
+
* Enable Mermaid diagram rendering
|
|
54
|
+
*/
|
|
55
|
+
this.enableMermaid = DEFAULT_MARKDOWN_CONFIG.enableMermaid;
|
|
56
|
+
/**
|
|
57
|
+
* Enable copy button on code blocks
|
|
58
|
+
*/
|
|
59
|
+
this.enableCodeCopy = DEFAULT_MARKDOWN_CONFIG.enableCodeCopy;
|
|
60
|
+
/**
|
|
61
|
+
* Enable collapsible heading sections
|
|
62
|
+
*/
|
|
63
|
+
this.enableCollapsibleHeadings = DEFAULT_MARKDOWN_CONFIG.enableCollapsibleHeadings;
|
|
64
|
+
/**
|
|
65
|
+
* Heading level at which to start collapsing
|
|
66
|
+
*/
|
|
67
|
+
this.collapsibleHeadingLevel = DEFAULT_MARKDOWN_CONFIG.collapsibleHeadingLevel;
|
|
68
|
+
/**
|
|
69
|
+
* Whether collapsible sections should be expanded by default
|
|
70
|
+
*/
|
|
71
|
+
this.collapsibleDefaultExpanded = DEFAULT_MARKDOWN_CONFIG.collapsibleDefaultExpanded;
|
|
72
|
+
/**
|
|
73
|
+
* Enable GitHub-style alerts
|
|
74
|
+
*/
|
|
75
|
+
this.enableAlerts = DEFAULT_MARKDOWN_CONFIG.enableAlerts;
|
|
76
|
+
/**
|
|
77
|
+
* Enable smartypants for typography (curly quotes, em/en dashes, ellipses)
|
|
78
|
+
*/
|
|
79
|
+
this.enableSmartypants = DEFAULT_MARKDOWN_CONFIG.enableSmartypants;
|
|
80
|
+
/**
|
|
81
|
+
* Enable SVG code block rendering
|
|
82
|
+
* When enabled, ```svg code blocks are rendered as actual SVG images
|
|
83
|
+
*/
|
|
84
|
+
this.enableSvgRenderer = DEFAULT_MARKDOWN_CONFIG.enableSvgRenderer;
|
|
85
|
+
/**
|
|
86
|
+
* Enable raw HTML passthrough in markdown content.
|
|
87
|
+
* Scripts and event handlers are still stripped unless enableJavaScript is true.
|
|
88
|
+
*/
|
|
89
|
+
this.enableHtml = DEFAULT_MARKDOWN_CONFIG.enableHtml;
|
|
90
|
+
/**
|
|
91
|
+
* Enable JavaScript in HTML content (<script> tags and on* handlers).
|
|
92
|
+
* WARNING: Major security risk - only enable for fully trusted content.
|
|
93
|
+
*/
|
|
94
|
+
this.enableJavaScript = DEFAULT_MARKDOWN_CONFIG.enableJavaScript;
|
|
95
|
+
/**
|
|
96
|
+
* Enable heading IDs for anchor links
|
|
97
|
+
*/
|
|
98
|
+
this.enableHeadingIds = DEFAULT_MARKDOWN_CONFIG.enableHeadingIds;
|
|
99
|
+
/**
|
|
100
|
+
* Prefix for heading IDs
|
|
101
|
+
*/
|
|
102
|
+
this.headingIdPrefix = DEFAULT_MARKDOWN_CONFIG.headingIdPrefix;
|
|
103
|
+
/**
|
|
104
|
+
* Enable line numbers in code blocks
|
|
105
|
+
*/
|
|
106
|
+
this.enableLineNumbers = DEFAULT_MARKDOWN_CONFIG.enableLineNumbers;
|
|
107
|
+
/**
|
|
108
|
+
* Custom CSS class for the container
|
|
109
|
+
*/
|
|
110
|
+
this.containerClass = '';
|
|
111
|
+
/**
|
|
112
|
+
* Mermaid theme
|
|
113
|
+
*/
|
|
114
|
+
this.mermaidTheme = DEFAULT_MARKDOWN_CONFIG.mermaidTheme;
|
|
115
|
+
/**
|
|
116
|
+
* Whether to sanitize HTML output
|
|
117
|
+
*/
|
|
118
|
+
this.sanitize = DEFAULT_MARKDOWN_CONFIG.sanitize;
|
|
119
|
+
/**
|
|
120
|
+
* Emitted when rendering is complete
|
|
121
|
+
*/
|
|
122
|
+
this.rendered = new EventEmitter();
|
|
123
|
+
/**
|
|
124
|
+
* Emitted when a heading anchor is clicked
|
|
125
|
+
*/
|
|
126
|
+
this.headingClick = new EventEmitter();
|
|
127
|
+
/**
|
|
128
|
+
* Emitted when code is copied to clipboard
|
|
129
|
+
*/
|
|
130
|
+
this.codeCopied = new EventEmitter();
|
|
131
|
+
/**
|
|
132
|
+
* The sanitized HTML content to display
|
|
133
|
+
*/
|
|
134
|
+
this.renderedContent = '';
|
|
135
|
+
this.renderStartTime = 0;
|
|
136
|
+
this.hasMermaid = false;
|
|
137
|
+
this.hasCodeBlocks = false;
|
|
138
|
+
}
|
|
139
|
+
ngOnChanges(changes) {
|
|
140
|
+
// Check if any relevant input changed
|
|
141
|
+
const needsRerender = changes['data'] ||
|
|
142
|
+
changes['enableHighlight'] ||
|
|
143
|
+
changes['enableMermaid'] ||
|
|
144
|
+
changes['enableCodeCopy'] ||
|
|
145
|
+
changes['enableCollapsibleHeadings'] ||
|
|
146
|
+
changes['collapsibleHeadingLevel'] ||
|
|
147
|
+
changes['collapsibleDefaultExpanded'] ||
|
|
148
|
+
changes['autoExpandLevels'] ||
|
|
149
|
+
changes['enableAlerts'] ||
|
|
150
|
+
changes['enableSmartypants'] ||
|
|
151
|
+
changes['enableSvgRenderer'] ||
|
|
152
|
+
changes['enableHtml'] ||
|
|
153
|
+
changes['enableJavaScript'] ||
|
|
154
|
+
changes['enableHeadingIds'] ||
|
|
155
|
+
changes['headingIdPrefix'] ||
|
|
156
|
+
changes['mermaidTheme'] ||
|
|
157
|
+
changes['sanitize'];
|
|
158
|
+
if (needsRerender) {
|
|
159
|
+
this.render();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
ngAfterViewInit() {
|
|
163
|
+
// Initial render if data was provided
|
|
164
|
+
if (this.data) {
|
|
165
|
+
this.postRenderProcessing();
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
ngOnDestroy() {
|
|
169
|
+
// Cleanup any event listeners
|
|
170
|
+
this.cleanupEventListeners();
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Render the markdown content
|
|
174
|
+
*/
|
|
175
|
+
render() {
|
|
176
|
+
if (!this.data) {
|
|
177
|
+
this.renderedContent = '';
|
|
178
|
+
this.cdr.markForCheck();
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
this.renderStartTime = performance.now();
|
|
182
|
+
// Build config from inputs
|
|
183
|
+
const config = {
|
|
184
|
+
enableHighlight: this.enableHighlight,
|
|
185
|
+
enableMermaid: this.enableMermaid,
|
|
186
|
+
enableCodeCopy: this.enableCodeCopy,
|
|
187
|
+
enableCollapsibleHeadings: this.enableCollapsibleHeadings,
|
|
188
|
+
collapsibleHeadingLevel: this.collapsibleHeadingLevel,
|
|
189
|
+
collapsibleDefaultExpanded: this.collapsibleDefaultExpanded,
|
|
190
|
+
autoExpandLevels: this.autoExpandLevels,
|
|
191
|
+
enableAlerts: this.enableAlerts,
|
|
192
|
+
enableSmartypants: this.enableSmartypants,
|
|
193
|
+
enableSvgRenderer: this.enableSvgRenderer,
|
|
194
|
+
enableHtml: this.enableHtml,
|
|
195
|
+
enableJavaScript: this.enableJavaScript,
|
|
196
|
+
enableHeadingIds: this.enableHeadingIds,
|
|
197
|
+
headingIdPrefix: this.headingIdPrefix,
|
|
198
|
+
mermaidTheme: this.mermaidTheme,
|
|
199
|
+
sanitize: this.sanitize
|
|
200
|
+
};
|
|
201
|
+
// Configure service and parse
|
|
202
|
+
this.markdownService.configureMarked(config);
|
|
203
|
+
let html = this.markdownService.parse(this.data);
|
|
204
|
+
// Check for mermaid and code blocks
|
|
205
|
+
this.hasMermaid = html.includes('language-mermaid') || html.includes('class="mermaid"');
|
|
206
|
+
this.hasCodeBlocks = html.includes('<pre>') && html.includes('<code');
|
|
207
|
+
// Sanitize if enabled
|
|
208
|
+
// Note: We bypass Angular's sanitizer when SVG renderer or HTML passthrough is enabled
|
|
209
|
+
// because it strips SVG elements and most HTML layout tags.
|
|
210
|
+
const bypassAngularSanitizer = this.enableSvgRenderer || this.enableHtml;
|
|
211
|
+
if (this.sanitize && !bypassAngularSanitizer) {
|
|
212
|
+
const sanitized = this.sanitizer.sanitize(SecurityContext.HTML, html);
|
|
213
|
+
html = sanitized || '';
|
|
214
|
+
}
|
|
215
|
+
// Strip JavaScript unless explicitly enabled
|
|
216
|
+
// This removes <script> tags and on* event handlers while keeping layout HTML
|
|
217
|
+
if (bypassAngularSanitizer && !this.enableJavaScript) {
|
|
218
|
+
html = this.stripJavaScript(html);
|
|
219
|
+
}
|
|
220
|
+
// Trust the HTML for display
|
|
221
|
+
this.renderedContent = this.sanitizer.bypassSecurityTrustHtml(html);
|
|
222
|
+
this.cdr.markForCheck();
|
|
223
|
+
// Schedule post-render processing for next tick (after DOM update)
|
|
224
|
+
Promise.resolve().then(() => this.postRenderProcessing());
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Process rendered content after DOM update
|
|
228
|
+
* Handles syntax highlighting, mermaid rendering, copy buttons, etc.
|
|
229
|
+
*/
|
|
230
|
+
async postRenderProcessing() {
|
|
231
|
+
const container = this.elementRef.nativeElement.querySelector('.mj-markdown-container');
|
|
232
|
+
if (!container)
|
|
233
|
+
return;
|
|
234
|
+
// Add copy buttons to code blocks
|
|
235
|
+
if (this.enableCodeCopy && this.hasCodeBlocks) {
|
|
236
|
+
this.markdownService.addCodeCopyButtons(container);
|
|
237
|
+
}
|
|
238
|
+
// Initialize collapsible headings
|
|
239
|
+
if (this.enableCollapsibleHeadings) {
|
|
240
|
+
this.markdownService.initializeCollapsibleHeadings(container);
|
|
241
|
+
this.setupCollapsibleListeners(container);
|
|
242
|
+
}
|
|
243
|
+
// Render mermaid diagrams (async)
|
|
244
|
+
if (this.enableMermaid && this.hasMermaid) {
|
|
245
|
+
await this.markdownService.renderMermaid(container);
|
|
246
|
+
}
|
|
247
|
+
// Setup heading click listeners
|
|
248
|
+
if (this.enableHeadingIds) {
|
|
249
|
+
this.setupHeadingClickListeners(container);
|
|
250
|
+
}
|
|
251
|
+
// Setup code copy listeners for custom event emission
|
|
252
|
+
if (this.enableCodeCopy) {
|
|
253
|
+
this.setupCodeCopyListeners(container);
|
|
254
|
+
}
|
|
255
|
+
// Emit rendered event
|
|
256
|
+
const renderTime = performance.now() - this.renderStartTime;
|
|
257
|
+
const headingIds = this.markdownService.getHeadingList();
|
|
258
|
+
this.rendered.emit({
|
|
259
|
+
html: container.innerHTML,
|
|
260
|
+
renderTime,
|
|
261
|
+
hasMermaid: this.hasMermaid,
|
|
262
|
+
hasCodeBlocks: this.hasCodeBlocks,
|
|
263
|
+
headingIds
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Setup collapsible sections by adding toggle buttons and click listeners
|
|
268
|
+
*/
|
|
269
|
+
setupCollapsibleListeners(container) {
|
|
270
|
+
const sections = container.querySelectorAll('.collapsible-section');
|
|
271
|
+
sections.forEach((section) => {
|
|
272
|
+
const wrapper = section.querySelector(':scope > .collapsible-heading-wrapper');
|
|
273
|
+
if (!wrapper)
|
|
274
|
+
return;
|
|
275
|
+
// Check if toggle already exists
|
|
276
|
+
if (wrapper.querySelector('.collapsible-toggle'))
|
|
277
|
+
return;
|
|
278
|
+
const isExpanded = !section.classList.contains('collapsed');
|
|
279
|
+
const hasChildren = section.querySelector('.collapsible-section') !== null;
|
|
280
|
+
// Create toggle button (chevron)
|
|
281
|
+
const toggle = document.createElement('span');
|
|
282
|
+
toggle.className = 'collapsible-toggle';
|
|
283
|
+
toggle.setAttribute('role', 'button');
|
|
284
|
+
toggle.setAttribute('tabindex', '0');
|
|
285
|
+
toggle.setAttribute('aria-expanded', String(isExpanded));
|
|
286
|
+
toggle.innerHTML = `<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
|
287
|
+
<path d="M6 12l4-4-4-4" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
|
288
|
+
</svg>`;
|
|
289
|
+
// Insert toggle before heading
|
|
290
|
+
wrapper.insertBefore(toggle, wrapper.firstChild);
|
|
291
|
+
// Add action buttons container (only if has children)
|
|
292
|
+
if (hasChildren) {
|
|
293
|
+
const actions = this.createActionButtons(section);
|
|
294
|
+
wrapper.appendChild(actions);
|
|
295
|
+
}
|
|
296
|
+
// Make the whole wrapper clickable
|
|
297
|
+
wrapper.style.cursor = 'pointer';
|
|
298
|
+
// Add click listener to wrapper (but not on action buttons)
|
|
299
|
+
wrapper.addEventListener('click', (e) => {
|
|
300
|
+
const target = e.target;
|
|
301
|
+
// Don't toggle if clicking on action buttons
|
|
302
|
+
if (target.closest('.collapsible-actions'))
|
|
303
|
+
return;
|
|
304
|
+
e.preventDefault();
|
|
305
|
+
e.stopPropagation();
|
|
306
|
+
this.toggleSection(section, toggle);
|
|
307
|
+
});
|
|
308
|
+
// Add keyboard support
|
|
309
|
+
wrapper.addEventListener('keydown', (e) => {
|
|
310
|
+
const target = e.target;
|
|
311
|
+
if (target.closest('.collapsible-actions'))
|
|
312
|
+
return;
|
|
313
|
+
const keyEvent = e;
|
|
314
|
+
if (keyEvent.key === 'Enter' || keyEvent.key === ' ') {
|
|
315
|
+
e.preventDefault();
|
|
316
|
+
this.toggleSection(section, toggle);
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Create the expand/collapse all action buttons for sections with children
|
|
323
|
+
*/
|
|
324
|
+
createActionButtons(section) {
|
|
325
|
+
const container = document.createElement('span');
|
|
326
|
+
container.className = 'collapsible-actions';
|
|
327
|
+
// Expand all button
|
|
328
|
+
const expandBtn = document.createElement('button');
|
|
329
|
+
expandBtn.className = 'collapsible-action-btn expand-all';
|
|
330
|
+
expandBtn.setAttribute('type', 'button');
|
|
331
|
+
expandBtn.setAttribute('title', 'Expand all');
|
|
332
|
+
expandBtn.innerHTML = `<svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2">
|
|
333
|
+
<path d="M4 6l4 4 4-4" stroke-linecap="round" stroke-linejoin="round"/>
|
|
334
|
+
<path d="M4 10l4 4 4-4" stroke-linecap="round" stroke-linejoin="round"/>
|
|
335
|
+
</svg>`;
|
|
336
|
+
expandBtn.addEventListener('click', (e) => {
|
|
337
|
+
e.preventDefault();
|
|
338
|
+
e.stopPropagation();
|
|
339
|
+
this.expandDescendants(section);
|
|
340
|
+
// Also expand the section itself if collapsed
|
|
341
|
+
if (section.classList.contains('collapsed')) {
|
|
342
|
+
section.classList.remove('collapsed');
|
|
343
|
+
const toggle = section.querySelector(':scope > .collapsible-heading-wrapper .collapsible-toggle');
|
|
344
|
+
if (toggle)
|
|
345
|
+
toggle.setAttribute('aria-expanded', 'true');
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
// Collapse all button
|
|
349
|
+
const collapseBtn = document.createElement('button');
|
|
350
|
+
collapseBtn.className = 'collapsible-action-btn collapse-all';
|
|
351
|
+
collapseBtn.setAttribute('type', 'button');
|
|
352
|
+
collapseBtn.setAttribute('title', 'Collapse all');
|
|
353
|
+
collapseBtn.innerHTML = `<svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2">
|
|
354
|
+
<path d="M4 10l4-4 4 4" stroke-linecap="round" stroke-linejoin="round"/>
|
|
355
|
+
<path d="M4 14l4-4 4 4" stroke-linecap="round" stroke-linejoin="round"/>
|
|
356
|
+
</svg>`;
|
|
357
|
+
collapseBtn.addEventListener('click', (e) => {
|
|
358
|
+
e.preventDefault();
|
|
359
|
+
e.stopPropagation();
|
|
360
|
+
this.collapseDescendants(section);
|
|
361
|
+
});
|
|
362
|
+
container.appendChild(expandBtn);
|
|
363
|
+
container.appendChild(collapseBtn);
|
|
364
|
+
return container;
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Toggle a collapsible section
|
|
368
|
+
* @param section The section element to toggle
|
|
369
|
+
* @param toggle The toggle button element
|
|
370
|
+
*/
|
|
371
|
+
toggleSection(section, toggle) {
|
|
372
|
+
const isCollapsed = section.classList.contains('collapsed');
|
|
373
|
+
// Toggle the section
|
|
374
|
+
section.classList.toggle('collapsed');
|
|
375
|
+
toggle.setAttribute('aria-expanded', String(isCollapsed));
|
|
376
|
+
// Children retain their state - CSS handles visibility via parent collapse
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Collapse all descendant sections (used by action button)
|
|
380
|
+
*/
|
|
381
|
+
collapseDescendants(section) {
|
|
382
|
+
const descendants = section.querySelectorAll('.collapsible-section');
|
|
383
|
+
descendants.forEach((desc) => {
|
|
384
|
+
desc.classList.add('collapsed');
|
|
385
|
+
const toggle = desc.querySelector(':scope > .collapsible-heading-wrapper .collapsible-toggle');
|
|
386
|
+
if (toggle) {
|
|
387
|
+
toggle.setAttribute('aria-expanded', 'false');
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Expand all descendant sections (used by action button)
|
|
393
|
+
*/
|
|
394
|
+
expandDescendants(section) {
|
|
395
|
+
const descendants = section.querySelectorAll('.collapsible-section');
|
|
396
|
+
descendants.forEach((desc) => {
|
|
397
|
+
desc.classList.remove('collapsed');
|
|
398
|
+
const toggle = desc.querySelector(':scope > .collapsible-heading-wrapper .collapsible-toggle');
|
|
399
|
+
if (toggle) {
|
|
400
|
+
toggle.setAttribute('aria-expanded', 'true');
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Setup click listeners for heading anchors
|
|
406
|
+
*/
|
|
407
|
+
setupHeadingClickListeners(container) {
|
|
408
|
+
const headings = container.querySelectorAll('h1[id], h2[id], h3[id], h4[id], h5[id], h6[id]');
|
|
409
|
+
headings.forEach((heading) => {
|
|
410
|
+
heading.addEventListener('click', () => {
|
|
411
|
+
const id = heading.getAttribute('id') || '';
|
|
412
|
+
const text = heading.textContent || '';
|
|
413
|
+
const level = parseInt(heading.tagName.charAt(1), 10);
|
|
414
|
+
this.headingClick.emit({
|
|
415
|
+
id,
|
|
416
|
+
text,
|
|
417
|
+
level,
|
|
418
|
+
raw: text
|
|
419
|
+
});
|
|
420
|
+
});
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Setup listeners to emit code copy events
|
|
425
|
+
*/
|
|
426
|
+
setupCodeCopyListeners(container) {
|
|
427
|
+
const copyButtons = container.querySelectorAll('.code-copy-btn');
|
|
428
|
+
copyButtons.forEach((button) => {
|
|
429
|
+
button.addEventListener('click', () => {
|
|
430
|
+
const pre = button.closest('pre');
|
|
431
|
+
const code = pre?.querySelector('code');
|
|
432
|
+
if (code) {
|
|
433
|
+
this.codeCopied.emit(code.textContent || '');
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Cleanup event listeners
|
|
440
|
+
*/
|
|
441
|
+
cleanupEventListeners() {
|
|
442
|
+
const container = this.elementRef.nativeElement.querySelector('.mj-markdown-container');
|
|
443
|
+
if (!container)
|
|
444
|
+
return;
|
|
445
|
+
// Clone and replace to remove all listeners
|
|
446
|
+
const clone = container.cloneNode(true);
|
|
447
|
+
container.parentNode?.replaceChild(clone, container);
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Force a re-render of the markdown content
|
|
451
|
+
*/
|
|
452
|
+
refresh() {
|
|
453
|
+
this.render();
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Get the current heading list (for TOC building)
|
|
457
|
+
*/
|
|
458
|
+
getHeadings() {
|
|
459
|
+
return this.markdownService.getHeadingList();
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Scroll to a heading by ID
|
|
463
|
+
*/
|
|
464
|
+
scrollToHeading(headingId) {
|
|
465
|
+
const container = this.elementRef.nativeElement.querySelector('.mj-markdown-container');
|
|
466
|
+
if (!container)
|
|
467
|
+
return;
|
|
468
|
+
const heading = container.querySelector(`#${headingId}`);
|
|
469
|
+
if (heading) {
|
|
470
|
+
heading.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Strip JavaScript from HTML content while preserving layout HTML.
|
|
475
|
+
* Removes <script> tags, on* event handlers, and javascript: URLs.
|
|
476
|
+
*/
|
|
477
|
+
stripJavaScript(html) {
|
|
478
|
+
// Remove <script> tags and their content
|
|
479
|
+
html = html.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
|
|
480
|
+
// Remove on* event handlers (onclick, onload, onerror, etc.)
|
|
481
|
+
html = html.replace(/\s+on\w+\s*=\s*["'][^"']*["']/gi, '');
|
|
482
|
+
html = html.replace(/\s+on\w+\s*=\s*[^\s>]+/gi, '');
|
|
483
|
+
// Remove javascript: URLs from href and src attributes
|
|
484
|
+
html = html.replace(/\s+href\s*=\s*["']javascript:[^"']*["']/gi, '');
|
|
485
|
+
html = html.replace(/\s+src\s*=\s*["']javascript:[^"']*["']/gi, '');
|
|
486
|
+
// Remove data: URLs that could contain scripts (data:text/html, etc.)
|
|
487
|
+
html = html.replace(/\s+src\s*=\s*["']data:text\/html[^"']*["']/gi, '');
|
|
488
|
+
return html;
|
|
489
|
+
}
|
|
490
|
+
static { this.ɵfac = function MarkdownComponent_Factory(t) { return new (t || MarkdownComponent)(i0.ɵɵdirectiveInject(i0.ElementRef), i0.ɵɵdirectiveInject(i1.DomSanitizer), i0.ɵɵdirectiveInject(i2.MarkdownService), i0.ɵɵdirectiveInject(i0.ChangeDetectorRef)); }; }
|
|
491
|
+
static { this.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: MarkdownComponent, selectors: [["mj-markdown"]], inputs: { data: "data", enableHighlight: "enableHighlight", enableMermaid: "enableMermaid", enableCodeCopy: "enableCodeCopy", enableCollapsibleHeadings: "enableCollapsibleHeadings", collapsibleHeadingLevel: "collapsibleHeadingLevel", collapsibleDefaultExpanded: "collapsibleDefaultExpanded", autoExpandLevels: "autoExpandLevels", enableAlerts: "enableAlerts", enableSmartypants: "enableSmartypants", enableSvgRenderer: "enableSvgRenderer", enableHtml: "enableHtml", enableJavaScript: "enableJavaScript", enableHeadingIds: "enableHeadingIds", headingIdPrefix: "headingIdPrefix", enableLineNumbers: "enableLineNumbers", containerClass: "containerClass", mermaidTheme: "mermaidTheme", sanitize: "sanitize" }, outputs: { rendered: "rendered", headingClick: "headingClick", codeCopied: "codeCopied" }, features: [i0.ɵɵNgOnChangesFeature], decls: 1, vars: 3, consts: [[1, "mj-markdown-container", 3, "innerHTML"]], template: function MarkdownComponent_Template(rf, ctx) { if (rf & 1) {
|
|
492
|
+
i0.ɵɵelement(0, "div", 0);
|
|
493
|
+
} if (rf & 2) {
|
|
494
|
+
i0.ɵɵclassMap(ctx.containerClass);
|
|
495
|
+
i0.ɵɵproperty("innerHTML", ctx.renderedContent, i0.ɵɵsanitizeHtml);
|
|
496
|
+
} }, 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: #1f2937;\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: #111827;\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: #6b7280; }\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: #3b82f6;\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 #e5e7eb;\n background: #f9fafb;\n color: #4b5563;\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: #f3f4f6;\n border-radius: 4px;\n color: #be185d;\n}\n\n/* Code blocks */\n.mj-markdown-container pre {\n margin: 1em 0;\n padding: 1em;\n background: #1e1e1e;\n border-radius: 8px;\n overflow-x: auto;\n position: relative;\n}\n\n.mj-markdown-container pre code {\n background: transparent;\n padding: 0;\n color: #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: #9ca3af;\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: 6px;\n color: #9ca3af;\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: #fff;\n}\n\n.mj-markdown-container .code-copy-btn.copied {\n background: rgba(34, 197, 94, 0.2);\n border-color: rgba(34, 197, 94, 0.3);\n color: #22c55e;\n}\n\n.mj-markdown-container .code-copy-btn.error {\n background: rgba(239, 68, 68, 0.2);\n border-color: rgba(239, 68, 68, 0.3);\n color: #ef4444;\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 #e5e7eb;\n text-align: left;\n}\n\n.mj-markdown-container th {\n background: #f9fafb;\n font-weight: 600;\n}\n\n.mj-markdown-container tr:nth-child(even) {\n background: #f9fafb;\n}\n\n/* ============================================\n Horizontal Rule\n ============================================ */\n.mj-markdown-container hr {\n margin: 2em 0;\n border: none;\n border-top: 1px solid #e5e7eb;\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: #eff6ff;\n border-color: #3b82f6;\n}\n\n.mj-markdown-container .markdown-alert-note .markdown-alert-title {\n color: #1d4ed8;\n}\n\n/* Tip */\n.mj-markdown-container .markdown-alert-tip {\n background: #f0fdf4;\n border-color: #22c55e;\n}\n\n.mj-markdown-container .markdown-alert-tip .markdown-alert-title {\n color: #16a34a;\n}\n\n/* Important */\n.mj-markdown-container .markdown-alert-important {\n background: #faf5ff;\n border-color: #a855f7;\n}\n\n.mj-markdown-container .markdown-alert-important .markdown-alert-title {\n color: #9333ea;\n}\n\n/* Warning */\n.mj-markdown-container .markdown-alert-warning {\n background: #fffbeb;\n border-color: #f59e0b;\n}\n\n.mj-markdown-container .markdown-alert-warning .markdown-alert-title {\n color: #d97706;\n}\n\n/* Caution */\n.mj-markdown-container .markdown-alert-caution {\n background: #fef2f2;\n border-color: #ef4444;\n}\n\n.mj-markdown-container .markdown-alert-caution .markdown-alert-title {\n color: #dc2626;\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: #e5e7eb;\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: rgba(0, 0, 0, 0.03);\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: #9ca3af;\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: #6b7280;\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 #3b82f6;\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: #9ca3af;\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: rgba(0, 0, 0, 0.08);\n color: #374151;\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: #059669;\n background-color: rgba(5, 150, 105, 0.1);\n}\n\n/* Collapse all button - double up chevron */\n.mj-markdown-container .collapsible-action-btn.collapse-all:hover {\n color: #dc2626;\n background-color: rgba(220, 38, 38, 0.1);\n}\n\n/* ============================================\n Mermaid Diagrams\n ============================================ */\n.mj-markdown-container .mermaid-diagram {\n margin: 1em 0;\n padding: 1em;\n background: #fff;\n border: 1px solid #e5e7eb;\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: #fef2f2;\n border-color: #fecaca;\n}\n\n.mj-markdown-container .mermaid-error::before {\n content: 'Diagram rendering failed';\n display: block;\n color: #dc2626;\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: #9ca3af;\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: #6b7280;\n user-select: none;\n}\n\n/* ============================================\n Error State\n ============================================ */\n.mj-markdown-container .markdown-error {\n background: #fef2f2;\n color: #dc2626;\n padding: 1em;\n border-radius: 8px;\n border: 1px solid #fecaca;\n white-space: pre-wrap;\n font-family: monospace;\n}\n\n/* ============================================\n Dark Mode Support\n ============================================ */\n@media (prefers-color-scheme: dark) {\n .mj-markdown-container.dark-mode {\n color: #e5e7eb;\n }\n\n .mj-markdown-container.dark-mode h1,\n .mj-markdown-container.dark-mode h2,\n .mj-markdown-container.dark-mode h3,\n .mj-markdown-container.dark-mode h4,\n .mj-markdown-container.dark-mode h5,\n .mj-markdown-container.dark-mode h6 {\n color: #f9fafb;\n }\n\n .mj-markdown-container.dark-mode blockquote {\n background: #1f2937;\n border-color: #374151;\n color: #9ca3af;\n }\n\n .mj-markdown-container.dark-mode :not(pre) > code {\n background: #374151;\n color: #f472b6;\n }\n\n .mj-markdown-container.dark-mode table th {\n background: #1f2937;\n }\n\n .mj-markdown-container.dark-mode table tr:nth-child(even) {\n background: #1f2937;\n }\n\n .mj-markdown-container.dark-mode th,\n .mj-markdown-container.dark-mode td {\n border-color: #374151;\n }\n\n .mj-markdown-container.dark-mode hr {\n border-color: #374151;\n }\n}\n\n/* ============================================\n SVG Rendered Content\n ============================================ */\n.mj-markdown-container .svg-rendered {\n margin: 1em 0;\n padding: 1em;\n background: #ffffff;\n border: 1px solid #e5e7eb;\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 */\n@media (prefers-color-scheme: dark) {\n .mj-markdown-container.dark-mode .svg-rendered {\n background: #1f2937;\n border-color: #374151;\n }\n}\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 .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 }); }
|
|
497
|
+
}
|
|
498
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MarkdownComponent, [{
|
|
499
|
+
type: Component,
|
|
500
|
+
args: [{ selector: 'mj-markdown', template: `
|
|
501
|
+
<div
|
|
502
|
+
class="mj-markdown-container"
|
|
503
|
+
[class]="containerClass"
|
|
504
|
+
[innerHTML]="renderedContent">
|
|
505
|
+
</div>
|
|
506
|
+
`, 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: #1f2937;\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: #111827;\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: #6b7280; }\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: #3b82f6;\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 #e5e7eb;\n background: #f9fafb;\n color: #4b5563;\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: #f3f4f6;\n border-radius: 4px;\n color: #be185d;\n}\n\n/* Code blocks */\n.mj-markdown-container pre {\n margin: 1em 0;\n padding: 1em;\n background: #1e1e1e;\n border-radius: 8px;\n overflow-x: auto;\n position: relative;\n}\n\n.mj-markdown-container pre code {\n background: transparent;\n padding: 0;\n color: #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: #9ca3af;\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: 6px;\n color: #9ca3af;\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: #fff;\n}\n\n.mj-markdown-container .code-copy-btn.copied {\n background: rgba(34, 197, 94, 0.2);\n border-color: rgba(34, 197, 94, 0.3);\n color: #22c55e;\n}\n\n.mj-markdown-container .code-copy-btn.error {\n background: rgba(239, 68, 68, 0.2);\n border-color: rgba(239, 68, 68, 0.3);\n color: #ef4444;\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 #e5e7eb;\n text-align: left;\n}\n\n.mj-markdown-container th {\n background: #f9fafb;\n font-weight: 600;\n}\n\n.mj-markdown-container tr:nth-child(even) {\n background: #f9fafb;\n}\n\n/* ============================================\n Horizontal Rule\n ============================================ */\n.mj-markdown-container hr {\n margin: 2em 0;\n border: none;\n border-top: 1px solid #e5e7eb;\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: #eff6ff;\n border-color: #3b82f6;\n}\n\n.mj-markdown-container .markdown-alert-note .markdown-alert-title {\n color: #1d4ed8;\n}\n\n/* Tip */\n.mj-markdown-container .markdown-alert-tip {\n background: #f0fdf4;\n border-color: #22c55e;\n}\n\n.mj-markdown-container .markdown-alert-tip .markdown-alert-title {\n color: #16a34a;\n}\n\n/* Important */\n.mj-markdown-container .markdown-alert-important {\n background: #faf5ff;\n border-color: #a855f7;\n}\n\n.mj-markdown-container .markdown-alert-important .markdown-alert-title {\n color: #9333ea;\n}\n\n/* Warning */\n.mj-markdown-container .markdown-alert-warning {\n background: #fffbeb;\n border-color: #f59e0b;\n}\n\n.mj-markdown-container .markdown-alert-warning .markdown-alert-title {\n color: #d97706;\n}\n\n/* Caution */\n.mj-markdown-container .markdown-alert-caution {\n background: #fef2f2;\n border-color: #ef4444;\n}\n\n.mj-markdown-container .markdown-alert-caution .markdown-alert-title {\n color: #dc2626;\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: #e5e7eb;\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: rgba(0, 0, 0, 0.03);\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: #9ca3af;\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: #6b7280;\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 #3b82f6;\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: #9ca3af;\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: rgba(0, 0, 0, 0.08);\n color: #374151;\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: #059669;\n background-color: rgba(5, 150, 105, 0.1);\n}\n\n/* Collapse all button - double up chevron */\n.mj-markdown-container .collapsible-action-btn.collapse-all:hover {\n color: #dc2626;\n background-color: rgba(220, 38, 38, 0.1);\n}\n\n/* ============================================\n Mermaid Diagrams\n ============================================ */\n.mj-markdown-container .mermaid-diagram {\n margin: 1em 0;\n padding: 1em;\n background: #fff;\n border: 1px solid #e5e7eb;\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: #fef2f2;\n border-color: #fecaca;\n}\n\n.mj-markdown-container .mermaid-error::before {\n content: 'Diagram rendering failed';\n display: block;\n color: #dc2626;\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: #9ca3af;\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: #6b7280;\n user-select: none;\n}\n\n/* ============================================\n Error State\n ============================================ */\n.mj-markdown-container .markdown-error {\n background: #fef2f2;\n color: #dc2626;\n padding: 1em;\n border-radius: 8px;\n border: 1px solid #fecaca;\n white-space: pre-wrap;\n font-family: monospace;\n}\n\n/* ============================================\n Dark Mode Support\n ============================================ */\n@media (prefers-color-scheme: dark) {\n .mj-markdown-container.dark-mode {\n color: #e5e7eb;\n }\n\n .mj-markdown-container.dark-mode h1,\n .mj-markdown-container.dark-mode h2,\n .mj-markdown-container.dark-mode h3,\n .mj-markdown-container.dark-mode h4,\n .mj-markdown-container.dark-mode h5,\n .mj-markdown-container.dark-mode h6 {\n color: #f9fafb;\n }\n\n .mj-markdown-container.dark-mode blockquote {\n background: #1f2937;\n border-color: #374151;\n color: #9ca3af;\n }\n\n .mj-markdown-container.dark-mode :not(pre) > code {\n background: #374151;\n color: #f472b6;\n }\n\n .mj-markdown-container.dark-mode table th {\n background: #1f2937;\n }\n\n .mj-markdown-container.dark-mode table tr:nth-child(even) {\n background: #1f2937;\n }\n\n .mj-markdown-container.dark-mode th,\n .mj-markdown-container.dark-mode td {\n border-color: #374151;\n }\n\n .mj-markdown-container.dark-mode hr {\n border-color: #374151;\n }\n}\n\n/* ============================================\n SVG Rendered Content\n ============================================ */\n.mj-markdown-container .svg-rendered {\n margin: 1em 0;\n padding: 1em;\n background: #ffffff;\n border: 1px solid #e5e7eb;\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 */\n@media (prefers-color-scheme: dark) {\n .mj-markdown-container.dark-mode .svg-rendered {\n background: #1f2937;\n border-color: #374151;\n }\n}\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 .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"] }]
|
|
507
|
+
}], () => [{ type: i0.ElementRef }, { type: i1.DomSanitizer }, { type: i2.MarkdownService }, { type: i0.ChangeDetectorRef }], { data: [{
|
|
508
|
+
type: Input
|
|
509
|
+
}], enableHighlight: [{
|
|
510
|
+
type: Input
|
|
511
|
+
}], enableMermaid: [{
|
|
512
|
+
type: Input
|
|
513
|
+
}], enableCodeCopy: [{
|
|
514
|
+
type: Input
|
|
515
|
+
}], enableCollapsibleHeadings: [{
|
|
516
|
+
type: Input
|
|
517
|
+
}], collapsibleHeadingLevel: [{
|
|
518
|
+
type: Input
|
|
519
|
+
}], collapsibleDefaultExpanded: [{
|
|
520
|
+
type: Input
|
|
521
|
+
}], autoExpandLevels: [{
|
|
522
|
+
type: Input
|
|
523
|
+
}], enableAlerts: [{
|
|
524
|
+
type: Input
|
|
525
|
+
}], enableSmartypants: [{
|
|
526
|
+
type: Input
|
|
527
|
+
}], enableSvgRenderer: [{
|
|
528
|
+
type: Input
|
|
529
|
+
}], enableHtml: [{
|
|
530
|
+
type: Input
|
|
531
|
+
}], enableJavaScript: [{
|
|
532
|
+
type: Input
|
|
533
|
+
}], enableHeadingIds: [{
|
|
534
|
+
type: Input
|
|
535
|
+
}], headingIdPrefix: [{
|
|
536
|
+
type: Input
|
|
537
|
+
}], enableLineNumbers: [{
|
|
538
|
+
type: Input
|
|
539
|
+
}], containerClass: [{
|
|
540
|
+
type: Input
|
|
541
|
+
}], mermaidTheme: [{
|
|
542
|
+
type: Input
|
|
543
|
+
}], sanitize: [{
|
|
544
|
+
type: Input
|
|
545
|
+
}], rendered: [{
|
|
546
|
+
type: Output
|
|
547
|
+
}], headingClick: [{
|
|
548
|
+
type: Output
|
|
549
|
+
}], codeCopied: [{
|
|
550
|
+
type: Output
|
|
551
|
+
}] }); })();
|
|
552
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(MarkdownComponent, { className: "MarkdownComponent", filePath: "src/lib/components/markdown.component.ts", lineNumber: 61 }); })();
|
|
553
|
+
//# sourceMappingURL=markdown.component.js.map
|