@memberjunction/ng-markdown 5.44.0 → 5.45.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/dist/__tests__/markdown.service.dom.test.d.ts +2 -0
- package/dist/__tests__/markdown.service.dom.test.d.ts.map +1 -0
- package/dist/__tests__/markdown.service.dom.test.js +86 -0
- package/dist/__tests__/markdown.service.dom.test.js.map +1 -0
- package/dist/__tests__/web-dom-utils.test.d.ts +2 -0
- package/dist/__tests__/web-dom-utils.test.d.ts.map +1 -0
- package/dist/__tests__/web-dom-utils.test.js +160 -0
- package/dist/__tests__/web-dom-utils.test.js.map +1 -0
- package/dist/lib/components/markdown.component.d.ts +1 -1
- package/dist/lib/components/markdown.component.d.ts.map +1 -1
- package/dist/lib/components/markdown.component.dom.test.js.map +1 -1
- package/dist/lib/components/markdown.component.js +1 -1
- package/dist/lib/components/markdown.component.js.map +1 -1
- package/dist/lib/extensions/code-copy.extension.d.ts +0 -7
- package/dist/lib/extensions/code-copy.extension.d.ts.map +1 -1
- package/dist/lib/extensions/code-copy.extension.js +1 -58
- package/dist/lib/extensions/code-copy.extension.js.map +1 -1
- package/dist/lib/extensions/collapsible-headings.extension.d.ts +5 -67
- package/dist/lib/extensions/collapsible-headings.extension.d.ts.map +1 -1
- package/dist/lib/extensions/collapsible-headings.extension.js +5 -179
- package/dist/lib/extensions/collapsible-headings.extension.js.map +1 -1
- package/dist/lib/extensions/svg-renderer.extension.d.ts +5 -28
- package/dist/lib/extensions/svg-renderer.extension.d.ts.map +1 -1
- package/dist/lib/extensions/svg-renderer.extension.js +5 -72
- package/dist/lib/extensions/svg-renderer.extension.js.map +1 -1
- package/dist/lib/services/markdown.service.d.ts +27 -43
- package/dist/lib/services/markdown.service.d.ts.map +1 -1
- package/dist/lib/services/markdown.service.js +56 -235
- package/dist/lib/services/markdown.service.js.map +1 -1
- package/dist/public-api.d.ts +0 -1
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +6 -3
- package/dist/public-api.js.map +1 -1
- package/package.json +4 -7
- package/dist/__tests__/markdown-types.test.d.ts +0 -2
- package/dist/__tests__/markdown-types.test.d.ts.map +0 -1
- package/dist/__tests__/markdown-types.test.js +0 -78
- package/dist/__tests__/markdown-types.test.js.map +0 -1
- package/dist/lib/extensions/html-block-repair.extension.d.ts +0 -20
- package/dist/lib/extensions/html-block-repair.extension.d.ts.map +0 -1
- package/dist/lib/extensions/html-block-repair.extension.js +0 -43
- package/dist/lib/extensions/html-block-repair.extension.js.map +0 -1
- package/dist/lib/types/markdown.types.d.ts +0 -194
- package/dist/lib/types/markdown.types.d.ts.map +0 -1
- package/dist/lib/types/markdown.types.js +0 -25
- package/dist/lib/types/markdown.types.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.service.dom.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/markdown.service.dom.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { MarkdownService } from '../lib/services/markdown.service';
|
|
3
|
+
describe('MarkdownService (DOM)', () => {
|
|
4
|
+
let service;
|
|
5
|
+
let unwrap;
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
service = new MarkdownService();
|
|
8
|
+
unwrap = (html) => service.unwrapMiscodedHtml(html);
|
|
9
|
+
});
|
|
10
|
+
describe('parse', () => {
|
|
11
|
+
it('returns empty string for empty input', () => {
|
|
12
|
+
expect(service.parse('')).toBe('');
|
|
13
|
+
});
|
|
14
|
+
it('renders basic prose to a paragraph', () => {
|
|
15
|
+
expect(service.parse('hello world')).toContain('<p>');
|
|
16
|
+
});
|
|
17
|
+
it('accepts per-call config overrides without throwing', () => {
|
|
18
|
+
expect(() => service.parse('# Title', { enableHtml: true })).not.toThrow();
|
|
19
|
+
});
|
|
20
|
+
it('parseAsync resolves to the same output as parse', async () => {
|
|
21
|
+
const md = '**bold**';
|
|
22
|
+
expect(await service.parseAsync(md)).toBe(service.parse(md));
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
describe('unwrapMiscodedHtml', () => {
|
|
26
|
+
it('is a no-op when there are no <pre> blocks', () => {
|
|
27
|
+
const html = '<p>just a paragraph</p>';
|
|
28
|
+
expect(unwrap(html)).toBe(html);
|
|
29
|
+
});
|
|
30
|
+
it('returns empty input unchanged', () => {
|
|
31
|
+
expect(unwrap('')).toBe('');
|
|
32
|
+
});
|
|
33
|
+
it('skips content containing <svg (DOMParser would mangle SVG namespaces)', () => {
|
|
34
|
+
const html = '<pre><code><svg><rect/></svg></code></pre>';
|
|
35
|
+
// Left untouched precisely because it contains "<svg".
|
|
36
|
+
expect(unwrap(html)).toBe(html);
|
|
37
|
+
});
|
|
38
|
+
it('preserves an intentional code block that carries a language class', () => {
|
|
39
|
+
const html = '<pre><code class="language-html"><div><span>x</span></div></code></pre>';
|
|
40
|
+
const out = unwrap(html);
|
|
41
|
+
// Language-classed code is an intentional example → NOT unwrapped.
|
|
42
|
+
expect(out).toContain('language-html');
|
|
43
|
+
expect(out).toContain('<pre>');
|
|
44
|
+
});
|
|
45
|
+
it('unwraps structural HTML that marked miscoded into a <pre><code> block', () => {
|
|
46
|
+
// textContent decodes to <div><span>x</span></div> — multi-tag structural HTML.
|
|
47
|
+
const html = '<pre><code><div><span>x</span></div></code></pre>';
|
|
48
|
+
const out = unwrap(html);
|
|
49
|
+
expect(out).toContain('<div>');
|
|
50
|
+
expect(out).toContain('<span>x</span>');
|
|
51
|
+
// The escaped code-block form must be gone.
|
|
52
|
+
expect(out).not.toContain('<div>');
|
|
53
|
+
expect(out).not.toContain('<pre>');
|
|
54
|
+
});
|
|
55
|
+
it('does not unwrap a single self-closing tag (needs 2+ tags to qualify)', () => {
|
|
56
|
+
const html = '<pre><code><br></code></pre>';
|
|
57
|
+
// tagCount < 2 → looksLikeStructuralHtml returns false → left as-is.
|
|
58
|
+
expect(unwrap(html)).toBe(html);
|
|
59
|
+
});
|
|
60
|
+
it('does not unwrap an empty code block', () => {
|
|
61
|
+
const html = '<pre><code></code></pre>';
|
|
62
|
+
expect(unwrap(html)).toBe(html);
|
|
63
|
+
});
|
|
64
|
+
it('does not unwrap plain non-HTML code content', () => {
|
|
65
|
+
const html = '<pre><code>const x = 1; // not html</code></pre>';
|
|
66
|
+
expect(unwrap(html)).toBe(html);
|
|
67
|
+
});
|
|
68
|
+
it('does not throw on malformed markup', () => {
|
|
69
|
+
expect(() => unwrap('<pre><code><div><span>unclosed')).not.toThrow();
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
describe('config accessors', () => {
|
|
73
|
+
it('resetConfig restores enableHtml to the default (false)', () => {
|
|
74
|
+
service.configureMarked({ enableHtml: true });
|
|
75
|
+
expect(service.getConfig().enableHtml).toBe(true);
|
|
76
|
+
service.resetConfig();
|
|
77
|
+
expect(service.getConfig().enableHtml).toBe(false);
|
|
78
|
+
});
|
|
79
|
+
it('isLanguageSupported reflects Prism registration', () => {
|
|
80
|
+
// typescript is imported by the service; a made-up language is not.
|
|
81
|
+
expect(service.isLanguageSupported('typescript')).toBe(true);
|
|
82
|
+
expect(service.isLanguageSupported('definitely-not-a-language')).toBe(false);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
//# sourceMappingURL=markdown.service.dom.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.service.dom.test.js","sourceRoot":"","sources":["../../src/__tests__/markdown.service.dom.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AAgBnE,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;IACrC,IAAI,OAAwB,CAAC;IAC7B,IAAI,MAAgC,CAAC;IAErC,UAAU,CAAC,GAAG,EAAE;QACd,OAAO,GAAG,IAAI,eAAe,EAAE,CAAC;QAChC,MAAM,GAAG,CAAC,IAAY,EAAE,EAAE,CAAE,OAAqC,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC;IAC7F,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,OAAO,EAAE,GAAG,EAAE;QACrB,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;YAC9C,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACrC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;YAC5C,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QACxD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;YAC5D,MAAM,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,SAAS,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;QAC7E,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,iDAAiD,EAAE,KAAK,IAAI,EAAE;YAC/D,MAAM,EAAE,GAAG,UAAU,CAAC;YACtB,MAAM,CAAC,MAAM,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;QAC/D,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,oBAAoB,EAAE,GAAG,EAAE;QAClC,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;YACnD,MAAM,IAAI,GAAG,yBAAyB,CAAC;YACvC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;YACvC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC9B,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,uEAAuE,EAAE,GAAG,EAAE;YAC/E,MAAM,IAAI,GAAG,8DAA8D,CAAC;YAC5E,uDAAuD;YACvD,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,mEAAmE,EAAE,GAAG,EAAE;YAC3E,MAAM,IAAI,GAAG,iGAAiG,CAAC;YAC/G,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;YACzB,mEAAmE;YACnE,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,eAAe,CAAC,CAAC;YACvC,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QACjC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,uEAAuE,EAAE,GAAG,EAAE;YAC/E,gFAAgF;YAChF,MAAM,IAAI,GAAG,2EAA2E,CAAC;YACzF,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;YACzB,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;YAC/B,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,gBAAgB,CAAC,CAAC;YACxC,4CAA4C;YAC5C,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC;YACzC,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QACrC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,sEAAsE,EAAE,GAAG,EAAE;YAC9E,MAAM,IAAI,GAAG,oCAAoC,CAAC;YAClD,qEAAqE;YACrE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;YAC7C,MAAM,IAAI,GAAG,0BAA0B,CAAC;YACxC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,6CAA6C,EAAE,GAAG,EAAE;YACrD,MAAM,IAAI,GAAG,kDAAkD,CAAC;YAChE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;YAC5C,MAAM,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,4CAA4C,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;QACnF,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;QAChC,EAAE,CAAC,wDAAwD,EAAE,GAAG,EAAE;YAChE,OAAO,CAAC,eAAe,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;YAC9C,MAAM,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAClD,OAAO,CAAC,WAAW,EAAE,CAAC;YACtB,MAAM,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACrD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;YACzD,oEAAoE;YACpE,MAAM,CAAC,OAAO,CAAC,mBAAmB,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC7D,MAAM,CAAC,OAAO,CAAC,mBAAmB,CAAC,2BAA2B,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC/E,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC","sourcesContent":["import { describe, it, expect, beforeEach } from 'vitest';\nimport { MarkdownService } from '../lib/services/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><svg><rect/></svg></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\"><div><span>x</span></div></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><div><span>x</span></div></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('<div>');\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><br></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><div><span>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"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web-dom-utils.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/web-dom-utils.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { sanitizeSvgContent } from '../lib/extensions/svg-renderer.extension';
|
|
3
|
+
import { addCopyButtonsToCodeBlocks, removeCopyButtonsFromCodeBlocks, } from '../lib/extensions/code-copy.extension';
|
|
4
|
+
import { toggleCollapsibleSection, expandAllSections, collapseAllSections, expandToHeading, } from '../lib/extensions/collapsible-headings.extension';
|
|
5
|
+
function container(html) {
|
|
6
|
+
const el = document.createElement('div');
|
|
7
|
+
el.innerHTML = html;
|
|
8
|
+
document.body.appendChild(el);
|
|
9
|
+
return el;
|
|
10
|
+
}
|
|
11
|
+
// Prevent fixtures (and their duplicate ids) from leaking across tests.
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
document.body.innerHTML = '';
|
|
14
|
+
});
|
|
15
|
+
describe('sanitizeSvgContent', () => {
|
|
16
|
+
it('removes <script> elements', () => {
|
|
17
|
+
const el = container('<svg><script>alert(1)</script><circle/></svg>');
|
|
18
|
+
sanitizeSvgContent(el);
|
|
19
|
+
expect(el.querySelector('script')).toBeNull();
|
|
20
|
+
expect(el.querySelector('circle')).not.toBeNull();
|
|
21
|
+
});
|
|
22
|
+
it('strips inline event-handler attributes', () => {
|
|
23
|
+
const el = container('<svg><rect onclick="hack()" onload="x()"/></svg>');
|
|
24
|
+
sanitizeSvgContent(el);
|
|
25
|
+
const rect = el.querySelector('rect');
|
|
26
|
+
expect(rect?.hasAttribute('onclick')).toBe(false);
|
|
27
|
+
expect(rect?.hasAttribute('onload')).toBe(false);
|
|
28
|
+
});
|
|
29
|
+
it('removes javascript: hrefs', () => {
|
|
30
|
+
const el = container('<svg><a href="javascript:evil()">x</a></svg>');
|
|
31
|
+
sanitizeSvgContent(el);
|
|
32
|
+
expect(el.querySelector('a')?.hasAttribute('href')).toBe(false);
|
|
33
|
+
});
|
|
34
|
+
it('removes foreignObject and external <use>', () => {
|
|
35
|
+
const el = container('<svg><foreignObject></foreignObject><use href="https://evil.test/x"/></svg>');
|
|
36
|
+
sanitizeSvgContent(el);
|
|
37
|
+
expect(el.querySelector('foreignObject')).toBeNull();
|
|
38
|
+
expect(el.querySelector('use')).toBeNull();
|
|
39
|
+
});
|
|
40
|
+
it('strips javascript: from xlink:href', () => {
|
|
41
|
+
const el = container('<svg><a xlink:href="javascript:evil()">x</a></svg>');
|
|
42
|
+
sanitizeSvgContent(el);
|
|
43
|
+
expect(el.querySelector('a')?.hasAttribute('xlink:href')).toBe(false);
|
|
44
|
+
});
|
|
45
|
+
it('keeps a local <use> reference (only external ones are removed)', () => {
|
|
46
|
+
const el = container('<svg><use href="#icon"/></svg>');
|
|
47
|
+
sanitizeSvgContent(el);
|
|
48
|
+
expect(el.querySelector('use')).not.toBeNull();
|
|
49
|
+
});
|
|
50
|
+
it('is a no-op on a container with no dangerous content', () => {
|
|
51
|
+
const el = container('<svg><circle cx="1" cy="1" r="1"/></svg>');
|
|
52
|
+
sanitizeSvgContent(el);
|
|
53
|
+
expect(el.querySelector('circle')).not.toBeNull();
|
|
54
|
+
});
|
|
55
|
+
it('does not throw on an empty container', () => {
|
|
56
|
+
const el = container('');
|
|
57
|
+
expect(() => sanitizeSvgContent(el)).not.toThrow();
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
describe('addCopyButtonsToCodeBlocks / removeCopyButtonsFromCodeBlocks', () => {
|
|
61
|
+
it('adds a copy button with a formatted language label', () => {
|
|
62
|
+
const el = container('<pre><code class="language-ts">const x = 1;</code></pre>');
|
|
63
|
+
addCopyButtonsToCodeBlocks(el);
|
|
64
|
+
expect(el.querySelector('.code-copy-btn')).not.toBeNull();
|
|
65
|
+
expect(el.querySelector('.code-language-label')?.textContent).toBe('TypeScript');
|
|
66
|
+
});
|
|
67
|
+
it('does not double-add a toolbar', () => {
|
|
68
|
+
const el = container('<pre><code class="language-js">a</code></pre>');
|
|
69
|
+
addCopyButtonsToCodeBlocks(el);
|
|
70
|
+
addCopyButtonsToCodeBlocks(el);
|
|
71
|
+
expect(el.querySelectorAll('.code-toolbar')).toHaveLength(1);
|
|
72
|
+
});
|
|
73
|
+
it('removes toolbars on cleanup', () => {
|
|
74
|
+
const el = container('<pre><code class="language-js">a</code></pre>');
|
|
75
|
+
addCopyButtonsToCodeBlocks(el);
|
|
76
|
+
removeCopyButtonsFromCodeBlocks(el);
|
|
77
|
+
expect(el.querySelector('.code-toolbar')).toBeNull();
|
|
78
|
+
});
|
|
79
|
+
it('adds a copy button but no language label for a code block without a language class', () => {
|
|
80
|
+
const el = container('<pre><code>plain code</code></pre>');
|
|
81
|
+
addCopyButtonsToCodeBlocks(el);
|
|
82
|
+
expect(el.querySelector('.code-copy-btn')).not.toBeNull();
|
|
83
|
+
expect(el.querySelector('.code-language-label')).toBeNull();
|
|
84
|
+
});
|
|
85
|
+
it('omits the language label when showLanguageLabel is false', () => {
|
|
86
|
+
const el = container('<pre><code class="language-ts">a</code></pre>');
|
|
87
|
+
addCopyButtonsToCodeBlocks(el, { showLanguageLabel: false });
|
|
88
|
+
expect(el.querySelector('.code-copy-btn')).not.toBeNull();
|
|
89
|
+
expect(el.querySelector('.code-language-label')).toBeNull();
|
|
90
|
+
});
|
|
91
|
+
it('honors a custom button/toolbar class and can remove it again', () => {
|
|
92
|
+
const el = container('<pre><code class="language-js">a</code></pre>');
|
|
93
|
+
addCopyButtonsToCodeBlocks(el, { toolbarClass: 'my-bar', buttonClass: 'my-btn' });
|
|
94
|
+
expect(el.querySelector('.my-btn')).not.toBeNull();
|
|
95
|
+
removeCopyButtonsFromCodeBlocks(el, 'my-bar');
|
|
96
|
+
expect(el.querySelector('.my-bar')).toBeNull();
|
|
97
|
+
});
|
|
98
|
+
it('is a no-op on a container with no code blocks', () => {
|
|
99
|
+
const el = container('<p>no code here</p>');
|
|
100
|
+
expect(() => addCopyButtonsToCodeBlocks(el)).not.toThrow();
|
|
101
|
+
expect(el.querySelector('.code-toolbar')).toBeNull();
|
|
102
|
+
});
|
|
103
|
+
it('removeCopyButtonsFromCodeBlocks is safe when there are no toolbars', () => {
|
|
104
|
+
const el = container('<pre><code>a</code></pre>');
|
|
105
|
+
expect(() => removeCopyButtonsFromCodeBlocks(el)).not.toThrow();
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
describe('collapsible DOM helpers', () => {
|
|
109
|
+
let el;
|
|
110
|
+
beforeEach(() => {
|
|
111
|
+
el = container('<div class="collapsible-section" data-level="2">' +
|
|
112
|
+
'<div class="collapsible-heading-wrapper"><button class="collapsible-toggle" aria-expanded="true"></button><h2 id="parent">Parent</h2></div>' +
|
|
113
|
+
'<div class="collapsible-content">' +
|
|
114
|
+
'<div class="collapsible-section collapsed" data-level="3">' +
|
|
115
|
+
'<div class="collapsible-heading-wrapper"><button class="collapsible-toggle" aria-expanded="false"></button><h3 id="child">Child</h3></div>' +
|
|
116
|
+
'<div class="collapsible-content"><p>c</p></div>' +
|
|
117
|
+
'</div>' +
|
|
118
|
+
'</div>' +
|
|
119
|
+
'</div>');
|
|
120
|
+
});
|
|
121
|
+
it('toggleCollapsibleSection flips the collapsed class and aria-expanded', () => {
|
|
122
|
+
const parent = el.querySelector('.collapsible-section');
|
|
123
|
+
toggleCollapsibleSection(parent);
|
|
124
|
+
expect(parent.classList.contains('collapsed')).toBe(true);
|
|
125
|
+
expect(parent.querySelector('.collapsible-toggle')?.getAttribute('aria-expanded')).toBe('false');
|
|
126
|
+
});
|
|
127
|
+
it('expandAllSections clears every collapsed section', () => {
|
|
128
|
+
expandAllSections(el);
|
|
129
|
+
expect(el.querySelectorAll('.collapsible-section.collapsed')).toHaveLength(0);
|
|
130
|
+
});
|
|
131
|
+
it('collapseAllSections collapses every section', () => {
|
|
132
|
+
collapseAllSections(el);
|
|
133
|
+
expect(el.querySelectorAll('.collapsible-section:not(.collapsed)')).toHaveLength(0);
|
|
134
|
+
});
|
|
135
|
+
it('expandToHeading reveals ancestor sections of a heading', () => {
|
|
136
|
+
// child section starts collapsed; expanding to #child should clear it
|
|
137
|
+
expandToHeading(el, 'child');
|
|
138
|
+
const child = el.querySelector('[data-level="3"]');
|
|
139
|
+
expect(child.classList.contains('collapsed')).toBe(false);
|
|
140
|
+
});
|
|
141
|
+
it('toggleCollapsibleSection expands a section that starts collapsed', () => {
|
|
142
|
+
const child = el.querySelector('[data-level="3"]');
|
|
143
|
+
expect(child.classList.contains('collapsed')).toBe(true);
|
|
144
|
+
toggleCollapsibleSection(child);
|
|
145
|
+
expect(child.classList.contains('collapsed')).toBe(false);
|
|
146
|
+
expect(child.querySelector('.collapsible-toggle')?.getAttribute('aria-expanded')).toBe('true');
|
|
147
|
+
});
|
|
148
|
+
it('expandToHeading is a no-op for an unknown heading id (no throw)', () => {
|
|
149
|
+
const child = el.querySelector('[data-level="3"]');
|
|
150
|
+
expect(() => expandToHeading(el, 'does-not-exist')).not.toThrow();
|
|
151
|
+
// The child that was collapsed stays collapsed — nothing was touched.
|
|
152
|
+
expect(child.classList.contains('collapsed')).toBe(true);
|
|
153
|
+
});
|
|
154
|
+
it('expand/collapse helpers are safe on a container with no sections', () => {
|
|
155
|
+
const empty = container('<p>nothing collapsible</p>');
|
|
156
|
+
expect(() => expandAllSections(empty)).not.toThrow();
|
|
157
|
+
expect(() => collapseAllSections(empty)).not.toThrow();
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
//# sourceMappingURL=web-dom-utils.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web-dom-utils.test.js","sourceRoot":"","sources":["../../src/__tests__/web-dom-utils.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, it, expect, beforeEach, afterEach } from 'vitest';\nimport { sanitizeSvgContent } from '../lib/extensions/svg-renderer.extension';\nimport {\n addCopyButtonsToCodeBlocks,\n removeCopyButtonsFromCodeBlocks,\n} from '../lib/extensions/code-copy.extension';\nimport {\n toggleCollapsibleSection,\n expandAllSections,\n collapseAllSections,\n expandToHeading,\n} from '../lib/extensions/collapsible-headings.extension';\n\nfunction container(html: string): HTMLElement {\n const el = document.createElement('div');\n el.innerHTML = html;\n document.body.appendChild(el);\n return el;\n}\n\n// Prevent fixtures (and their duplicate ids) from leaking across tests.\nafterEach(() => {\n document.body.innerHTML = '';\n});\n\ndescribe('sanitizeSvgContent', () => {\n it('removes <script> elements', () => {\n const el = container('<svg><script>alert(1)</script><circle/></svg>');\n sanitizeSvgContent(el);\n expect(el.querySelector('script')).toBeNull();\n expect(el.querySelector('circle')).not.toBeNull();\n });\n\n it('strips inline event-handler attributes', () => {\n const el = container('<svg><rect onclick=\"hack()\" onload=\"x()\"/></svg>');\n sanitizeSvgContent(el);\n const rect = el.querySelector('rect');\n expect(rect?.hasAttribute('onclick')).toBe(false);\n expect(rect?.hasAttribute('onload')).toBe(false);\n });\n\n it('removes javascript: hrefs', () => {\n const el = container('<svg><a href=\"javascript:evil()\">x</a></svg>');\n sanitizeSvgContent(el);\n expect(el.querySelector('a')?.hasAttribute('href')).toBe(false);\n });\n\n it('removes foreignObject and external <use>', () => {\n const el = container('<svg><foreignObject></foreignObject><use href=\"https://evil.test/x\"/></svg>');\n sanitizeSvgContent(el);\n expect(el.querySelector('foreignObject')).toBeNull();\n expect(el.querySelector('use')).toBeNull();\n });\n\n it('strips javascript: from xlink:href', () => {\n const el = container('<svg><a xlink:href=\"javascript:evil()\">x</a></svg>');\n sanitizeSvgContent(el);\n expect(el.querySelector('a')?.hasAttribute('xlink:href')).toBe(false);\n });\n\n it('keeps a local <use> reference (only external ones are removed)', () => {\n const el = container('<svg><use href=\"#icon\"/></svg>');\n sanitizeSvgContent(el);\n expect(el.querySelector('use')).not.toBeNull();\n });\n\n it('is a no-op on a container with no dangerous content', () => {\n const el = container('<svg><circle cx=\"1\" cy=\"1\" r=\"1\"/></svg>');\n sanitizeSvgContent(el);\n expect(el.querySelector('circle')).not.toBeNull();\n });\n\n it('does not throw on an empty container', () => {\n const el = container('');\n expect(() => sanitizeSvgContent(el)).not.toThrow();\n });\n});\n\ndescribe('addCopyButtonsToCodeBlocks / removeCopyButtonsFromCodeBlocks', () => {\n it('adds a copy button with a formatted language label', () => {\n const el = container('<pre><code class=\"language-ts\">const x = 1;</code></pre>');\n addCopyButtonsToCodeBlocks(el);\n expect(el.querySelector('.code-copy-btn')).not.toBeNull();\n expect(el.querySelector('.code-language-label')?.textContent).toBe('TypeScript');\n });\n\n it('does not double-add a toolbar', () => {\n const el = container('<pre><code class=\"language-js\">a</code></pre>');\n addCopyButtonsToCodeBlocks(el);\n addCopyButtonsToCodeBlocks(el);\n expect(el.querySelectorAll('.code-toolbar')).toHaveLength(1);\n });\n\n it('removes toolbars on cleanup', () => {\n const el = container('<pre><code class=\"language-js\">a</code></pre>');\n addCopyButtonsToCodeBlocks(el);\n removeCopyButtonsFromCodeBlocks(el);\n expect(el.querySelector('.code-toolbar')).toBeNull();\n });\n\n it('adds a copy button but no language label for a code block without a language class', () => {\n const el = container('<pre><code>plain code</code></pre>');\n addCopyButtonsToCodeBlocks(el);\n expect(el.querySelector('.code-copy-btn')).not.toBeNull();\n expect(el.querySelector('.code-language-label')).toBeNull();\n });\n\n it('omits the language label when showLanguageLabel is false', () => {\n const el = container('<pre><code class=\"language-ts\">a</code></pre>');\n addCopyButtonsToCodeBlocks(el, { showLanguageLabel: false });\n expect(el.querySelector('.code-copy-btn')).not.toBeNull();\n expect(el.querySelector('.code-language-label')).toBeNull();\n });\n\n it('honors a custom button/toolbar class and can remove it again', () => {\n const el = container('<pre><code class=\"language-js\">a</code></pre>');\n addCopyButtonsToCodeBlocks(el, { toolbarClass: 'my-bar', buttonClass: 'my-btn' });\n expect(el.querySelector('.my-btn')).not.toBeNull();\n removeCopyButtonsFromCodeBlocks(el, 'my-bar');\n expect(el.querySelector('.my-bar')).toBeNull();\n });\n\n it('is a no-op on a container with no code blocks', () => {\n const el = container('<p>no code here</p>');\n expect(() => addCopyButtonsToCodeBlocks(el)).not.toThrow();\n expect(el.querySelector('.code-toolbar')).toBeNull();\n });\n\n it('removeCopyButtonsFromCodeBlocks is safe when there are no toolbars', () => {\n const el = container('<pre><code>a</code></pre>');\n expect(() => removeCopyButtonsFromCodeBlocks(el)).not.toThrow();\n });\n});\n\ndescribe('collapsible DOM helpers', () => {\n let el: HTMLElement;\n\n beforeEach(() => {\n el = container(\n '<div class=\"collapsible-section\" data-level=\"2\">' +\n '<div class=\"collapsible-heading-wrapper\"><button class=\"collapsible-toggle\" aria-expanded=\"true\"></button><h2 id=\"parent\">Parent</h2></div>' +\n '<div class=\"collapsible-content\">' +\n '<div class=\"collapsible-section collapsed\" data-level=\"3\">' +\n '<div class=\"collapsible-heading-wrapper\"><button class=\"collapsible-toggle\" aria-expanded=\"false\"></button><h3 id=\"child\">Child</h3></div>' +\n '<div class=\"collapsible-content\"><p>c</p></div>' +\n '</div>' +\n '</div>' +\n '</div>'\n );\n });\n\n it('toggleCollapsibleSection flips the collapsed class and aria-expanded', () => {\n const parent = el.querySelector('.collapsible-section') as HTMLElement;\n toggleCollapsibleSection(parent);\n expect(parent.classList.contains('collapsed')).toBe(true);\n expect(parent.querySelector('.collapsible-toggle')?.getAttribute('aria-expanded')).toBe('false');\n });\n\n it('expandAllSections clears every collapsed section', () => {\n expandAllSections(el);\n expect(el.querySelectorAll('.collapsible-section.collapsed')).toHaveLength(0);\n });\n\n it('collapseAllSections collapses every section', () => {\n collapseAllSections(el);\n expect(el.querySelectorAll('.collapsible-section:not(.collapsed)')).toHaveLength(0);\n });\n\n it('expandToHeading reveals ancestor sections of a heading', () => {\n // child section starts collapsed; expanding to #child should clear it\n expandToHeading(el, 'child');\n const child = el.querySelector('[data-level=\"3\"]') as HTMLElement;\n expect(child.classList.contains('collapsed')).toBe(false);\n });\n\n it('toggleCollapsibleSection expands a section that starts collapsed', () => {\n const child = el.querySelector('[data-level=\"3\"]') as HTMLElement;\n expect(child.classList.contains('collapsed')).toBe(true);\n toggleCollapsibleSection(child);\n expect(child.classList.contains('collapsed')).toBe(false);\n expect(child.querySelector('.collapsible-toggle')?.getAttribute('aria-expanded')).toBe('true');\n });\n\n it('expandToHeading is a no-op for an unknown heading id (no throw)', () => {\n const child = el.querySelector('[data-level=\"3\"]') as HTMLElement;\n expect(() => expandToHeading(el, 'does-not-exist')).not.toThrow();\n // The child that was collapsed stays collapsed — nothing was touched.\n expect(child.classList.contains('collapsed')).toBe(true);\n });\n\n it('expand/collapse helpers are safe on a container with no sections', () => {\n const empty = container('<p>nothing collapsible</p>');\n expect(() => expandAllSections(empty)).not.toThrow();\n expect(() => collapseAllSections(empty)).not.toThrow();\n });\n});\n"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { EventEmitter, ElementRef, OnChanges, OnDestroy, SimpleChanges, ChangeDetectorRef, AfterViewInit } from '@angular/core';
|
|
2
2
|
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
|
|
3
3
|
import { MarkdownService } from '../services/markdown.service';
|
|
4
|
-
import { MarkdownRenderEvent, HeadingInfo } from '
|
|
4
|
+
import { MarkdownRenderEvent, HeadingInfo } from '@memberjunction/markdown-core';
|
|
5
5
|
import * as i0 from "@angular/core";
|
|
6
6
|
/**
|
|
7
7
|
* Angular component for rendering markdown content.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"markdown.component.d.ts","sourceRoot":"","sources":["../../../src/lib/components/markdown.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,YAAY,EACZ,UAAU,EACV,SAAS,EACT,SAAS,EACT,aAAa,EAIb,iBAAiB,EACjB,aAAa,EACd,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAC/D,OAAO,EAA2C,mBAAmB,EAAE,WAAW,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"markdown.component.d.ts","sourceRoot":"","sources":["../../../src/lib/components/markdown.component.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"markdown.component.dom.test.js","sourceRoot":"","sources":["../../../src/lib/components/markdown.component.dom.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, it, expect } from 'vitest';\nimport { ComponentFixture } from '@angular/core/testing';\nimport { CommonModule } from '@angular/common';\nimport { renderComponentFixture, query, capture } from '@memberjunction/ng-test-utils';\nimport { MarkdownComponent } from './markdown.component';\nimport { MarkdownRenderEvent } from '
|
|
1
|
+
{"version":3,"file":"markdown.component.dom.test.js","sourceRoot":"","sources":["../../../src/lib/components/markdown.component.dom.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, it, expect } from 'vitest';\nimport { ComponentFixture } from '@angular/core/testing';\nimport { CommonModule } from '@angular/common';\nimport { renderComponentFixture, query, capture } from '@memberjunction/ng-test-utils';\nimport { MarkdownComponent } from './markdown.component';\nimport { MarkdownRenderEvent } from '@memberjunction/markdown-core';\n\n/**\n * DOM-level spec for <mj-markdown>. The component is module-declared\n * (standalone:false), so we render it via `declarations` + `imports: [CommonModule]`\n * and configure it through `@Input`s.\n *\n * It uses the real (providedIn:'root') MarkdownService, which parses markdown\n * synchronously via marked.js. We assert on the rendered output that lands in the\n * `[innerHTML]`-bound container, plus the container-class binding and the `rendered`\n * @Output. Mermaid/Prism async post-processing is NOT asserted here (mermaid needs a\n * real browser; that's a live-test concern) — we only verify the deterministic,\n * media-free template surface.\n */\ndescribe('MarkdownComponent (DOM)', () => {\n function render(inputs: Record<string, unknown>): ComponentFixture<MarkdownComponent> {\n return renderComponentFixture(MarkdownComponent, {\n imports: [CommonModule],\n declarations: [MarkdownComponent],\n inputs,\n });\n }\n\n const container = (f: ComponentFixture<MarkdownComponent>): HTMLElement => f.nativeElement.querySelector('.mj-markdown-container') as HTMLElement;\n\n it('renders the static container element', () => {\n const f = render({ data: '' });\n expect(query(f, '.mj-markdown-container')).not.toBeNull();\n });\n\n it('renders markdown content into the innerHTML-bound container', () => {\n const f = render({ data: '# Hello World' });\n const c = container(f);\n const heading = c.querySelector('h1');\n expect(heading).not.toBeNull();\n expect(heading!.textContent).toContain('Hello World');\n });\n\n it('renders inline markdown formatting (bold) as the corresponding HTML element', () => {\n const f = render({ data: 'This is **strong** text' });\n expect(container(f).querySelector('strong')?.textContent).toBe('strong');\n });\n\n it('renders a fenced code block as a <pre><code> structure', () => {\n const f = render({ data: '```\\nconst x = 1;\\n```' });\n const c = container(f);\n expect(c.querySelector('pre')).not.toBeNull();\n expect(c.querySelector('pre code')).not.toBeNull();\n });\n\n it('renders an empty container when data is empty', () => {\n const f = render({ data: '' });\n // No data => renderedContent stays '' => no rendered child markup.\n expect(container(f).children.length).toBe(0);\n });\n\n it('applies the containerClass input to the container element', () => {\n const f = render({ data: '# X', containerClass: 'my-custom-class' });\n expect(container(f).classList.contains('my-custom-class')).toBe(true);\n });\n\n it('emits the rendered @Output with hasCodeBlocks=true for code content', async () => {\n const f = render({ data: '' });\n const events = capture<MarkdownRenderEvent>(f.componentInstance.rendered);\n\n // Setting data after first render drives ngOnChanges -> render() -> postRenderProcessing\n // is scheduled on a microtask. Use setInput (zoneless-correct), then flush the microtask.\n f.componentRef.setInput('data', '```\\nconst x = 1;\\n```');\n f.detectChanges();\n await Promise.resolve();\n await Promise.resolve();\n\n expect(events.length).toBeGreaterThanOrEqual(1);\n const last = events[events.length - 1];\n expect(last.hasCodeBlocks).toBe(true);\n expect(last.hasMermaid).toBe(false);\n });\n\n it('emits the rendered @Output with hasCodeBlocks=false for prose-only content', async () => {\n const f = render({ data: '' });\n const events = capture<MarkdownRenderEvent>(f.componentInstance.rendered);\n\n f.componentRef.setInput('data', 'Just some plain prose.');\n f.detectChanges();\n await Promise.resolve();\n await Promise.resolve();\n\n expect(events.length).toBeGreaterThanOrEqual(1);\n expect(events[events.length - 1].hasCodeBlocks).toBe(false);\n });\n});\n"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Component, Input, Output, EventEmitter, ElementRef, SecurityContext, ViewEncapsulation, ChangeDetectionStrategy, ChangeDetectorRef, } from '@angular/core';
|
|
2
2
|
import { DomSanitizer } from '@angular/platform-browser';
|
|
3
3
|
import { MarkdownService } from '../services/markdown.service';
|
|
4
|
-
import { DEFAULT_MARKDOWN_CONFIG } from '
|
|
4
|
+
import { DEFAULT_MARKDOWN_CONFIG } from '@memberjunction/markdown-core';
|
|
5
5
|
import * as i0 from "@angular/core";
|
|
6
6
|
import * as i1 from "@angular/platform-browser";
|
|
7
7
|
import * as i2 from "../services/markdown.service";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"markdown.component.js","sourceRoot":"","sources":["../../../src/lib/components/markdown.component.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Component,\n Input,\n Output,\n EventEmitter,\n ElementRef,\n OnChanges,\n OnDestroy,\n SimpleChanges,\n SecurityContext,\n ViewEncapsulation,\n ChangeDetectionStrategy,\n ChangeDetectorRef,\n AfterViewInit,\n} from '@angular/core';\nimport { DomSanitizer, SafeHtml } from '@angular/platform-browser';\nimport { MarkdownService } from '../services/markdown.service';\nimport { MarkdownConfig, DEFAULT_MARKDOWN_CONFIG, MarkdownRenderEvent, HeadingInfo } from '../types/markdown.types';\n// Collapsible section toggle is handled inline in setupCollapsibleListeners\n\n/**\n * Angular component for rendering markdown content.\n *\n * Features:\n * - Prism.js syntax highlighting for code blocks\n * - Mermaid diagram rendering\n * - Copy-to-clipboard for code blocks\n * - Collapsible heading sections\n * - GitHub-style alerts and heading IDs\n *\n * Usage:\n * ```html\n * <mj-markdown [data]=\"markdownContent\"></mj-markdown>\n *\n * <mj-markdown\n * [data]=\"content\"\n * [enableMermaid]=\"true\"\n * [enableCollapsibleHeadings]=\"true\"\n * (rendered)=\"onRendered($event)\">\n * </mj-markdown>\n * ```\n */\n@Component({\n selector: 'mj-markdown',\n standalone: false,\n template: ` <div class=\"mj-markdown-container\" [class]=\"containerClass\" [innerHTML]=\"renderedContent\"></div> `,\n styleUrls: ['./markdown.component.css'],\n encapsulation: ViewEncapsulation.None, // Allow styles to penetrate into rendered content\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class MarkdownComponent implements OnChanges, AfterViewInit, OnDestroy {\n /**\n * The markdown content to render\n */\n @Input() data: string = '';\n\n /**\n * Enable syntax highlighting\n */\n @Input() enableHighlight: boolean = DEFAULT_MARKDOWN_CONFIG.enableHighlight;\n\n /**\n * Enable Mermaid diagram rendering\n */\n @Input() enableMermaid: boolean = DEFAULT_MARKDOWN_CONFIG.enableMermaid;\n\n /**\n * Enable copy button on code blocks\n */\n @Input() enableCodeCopy: boolean = DEFAULT_MARKDOWN_CONFIG.enableCodeCopy;\n\n /**\n * Enable collapsible heading sections\n */\n @Input() enableCollapsibleHeadings: boolean = DEFAULT_MARKDOWN_CONFIG.enableCollapsibleHeadings;\n\n /**\n * Heading level at which to start collapsing\n */\n @Input() collapsibleHeadingLevel: 1 | 2 | 3 | 4 | 5 | 6 = DEFAULT_MARKDOWN_CONFIG.collapsibleHeadingLevel;\n\n /**\n * Whether collapsible sections should be expanded by default\n */\n @Input() collapsibleDefaultExpanded: boolean = DEFAULT_MARKDOWN_CONFIG.collapsibleDefaultExpanded;\n\n /**\n * Specify which heading levels should start expanded.\n * Array of heading levels (2-6) that should be expanded by default.\n * Takes precedence over collapsibleDefaultExpanded for specified levels.\n *\n * Examples:\n * - [2] = Only h2 expanded, h3-h6 collapsed\n * - [2, 3] = h2 and h3 expanded, h4-h6 collapsed\n * - [] = All collapsed\n * - undefined = Uses collapsibleDefaultExpanded for all levels\n */\n @Input() autoExpandLevels?: number[];\n\n /**\n * Enable GitHub-style alerts\n */\n @Input() enableAlerts: boolean = DEFAULT_MARKDOWN_CONFIG.enableAlerts;\n\n /**\n * Enable smartypants for typography (curly quotes, em/en dashes, ellipses)\n */\n @Input() enableSmartypants: boolean = DEFAULT_MARKDOWN_CONFIG.enableSmartypants;\n\n /**\n * Enable SVG code block rendering\n * When enabled, ```svg code blocks are rendered as actual SVG images\n */\n @Input() enableSvgRenderer: boolean = DEFAULT_MARKDOWN_CONFIG.enableSvgRenderer;\n\n /**\n * Enable raw HTML passthrough in markdown content.\n * Scripts and event handlers are still stripped unless enableJavaScript is true.\n */\n @Input() enableHtml: boolean = DEFAULT_MARKDOWN_CONFIG.enableHtml;\n\n /**\n * Enable JavaScript in HTML content (<script> tags and on* handlers).\n * WARNING: Major security risk - only enable for fully trusted content.\n */\n @Input() enableJavaScript: boolean = DEFAULT_MARKDOWN_CONFIG.enableJavaScript;\n\n /**\n * Enable heading IDs for anchor links\n */\n @Input() enableHeadingIds: boolean = DEFAULT_MARKDOWN_CONFIG.enableHeadingIds;\n\n /**\n * Prefix for heading IDs\n */\n @Input() headingIdPrefix: string = DEFAULT_MARKDOWN_CONFIG.headingIdPrefix;\n\n /**\n * Enable line numbers in code blocks\n */\n @Input() enableLineNumbers: boolean = DEFAULT_MARKDOWN_CONFIG.enableLineNumbers;\n\n /**\n * Custom CSS class for the container\n */\n @Input() containerClass: string = '';\n\n /**\n * Mermaid theme.\n * 'auto' (default) detects light/dark from the document's data-theme attribute.\n */\n @Input() mermaidTheme: 'auto' | 'default' | 'dark' | 'forest' | 'neutral' | 'base' = DEFAULT_MARKDOWN_CONFIG.mermaidTheme;\n\n /**\n * Whether to sanitize HTML output\n */\n @Input() sanitize: boolean = DEFAULT_MARKDOWN_CONFIG.sanitize;\n\n /**\n * Emitted when rendering is complete\n */\n @Output() rendered = new EventEmitter<MarkdownRenderEvent>();\n\n /**\n * Emitted when a heading anchor is clicked\n */\n @Output() headingClick = new EventEmitter<HeadingInfo>();\n\n /**\n * Emitted when code is copied to clipboard\n */\n @Output() codeCopied = new EventEmitter<string>();\n\n /**\n * The sanitized HTML content to display\n */\n public renderedContent: SafeHtml = '';\n\n /**\n * Public accessor for the component's element reference.\n * Provided for backward compatibility with ngx-markdown API.\n */\n public get element(): ElementRef<HTMLElement> {\n return this.elementRef;\n }\n\n private renderStartTime: number = 0;\n private hasMermaid: boolean = false;\n private hasCodeBlocks: boolean = false;\n private themeObserver: MutationObserver | null = null;\n\n constructor(\n private elementRef: ElementRef<HTMLElement>,\n private sanitizer: DomSanitizer,\n private markdownService: MarkdownService,\n private cdr: ChangeDetectorRef,\n ) {}\n\n ngOnChanges(changes: SimpleChanges): void {\n // Check if any relevant input changed\n const needsRerender =\n changes['data'] ||\n changes['enableHighlight'] ||\n changes['enableMermaid'] ||\n changes['enableCodeCopy'] ||\n changes['enableCollapsibleHeadings'] ||\n changes['collapsibleHeadingLevel'] ||\n changes['collapsibleDefaultExpanded'] ||\n changes['autoExpandLevels'] ||\n changes['enableAlerts'] ||\n changes['enableSmartypants'] ||\n changes['enableSvgRenderer'] ||\n changes['enableHtml'] ||\n changes['enableJavaScript'] ||\n changes['enableHeadingIds'] ||\n changes['headingIdPrefix'] ||\n changes['mermaidTheme'] ||\n changes['sanitize'];\n\n if (needsRerender) {\n this.render();\n }\n }\n\n ngAfterViewInit(): void {\n // Initial render if data was provided\n if (this.data) {\n this.postRenderProcessing();\n }\n\n this.setupThemeObserver();\n }\n\n ngOnDestroy(): void {\n this.themeObserver?.disconnect();\n this.themeObserver = null;\n this.cleanupEventListeners();\n }\n\n /**\n * Render the markdown content\n */\n private render(): void {\n if (!this.data) {\n this.renderedContent = '';\n this.cdr.markForCheck();\n return;\n }\n\n this.renderStartTime = performance.now();\n\n // Build config from inputs\n const config: MarkdownConfig = {\n enableHighlight: this.enableHighlight,\n enableMermaid: this.enableMermaid,\n enableCodeCopy: this.enableCodeCopy,\n enableCollapsibleHeadings: this.enableCollapsibleHeadings,\n collapsibleHeadingLevel: this.collapsibleHeadingLevel,\n collapsibleDefaultExpanded: this.collapsibleDefaultExpanded,\n autoExpandLevels: this.autoExpandLevels,\n enableAlerts: this.enableAlerts,\n enableSmartypants: this.enableSmartypants,\n enableSvgRenderer: this.enableSvgRenderer,\n enableHtml: this.enableHtml,\n enableJavaScript: this.enableJavaScript,\n enableHeadingIds: this.enableHeadingIds,\n headingIdPrefix: this.headingIdPrefix,\n mermaidTheme: this.mermaidTheme,\n sanitize: this.sanitize,\n };\n\n // Configure service and parse\n this.markdownService.configureMarked(config);\n let html = this.markdownService.parse(this.data);\n\n // Check for mermaid and code blocks\n this.hasMermaid = html.includes('language-mermaid') || html.includes('class=\"mermaid\"');\n this.hasCodeBlocks = html.includes('<pre>') && html.includes('<code');\n\n // Sanitize if enabled\n // Note: We bypass Angular's sanitizer when SVG renderer or HTML passthrough is enabled\n // because it strips SVG elements and most HTML layout tags.\n const bypassAngularSanitizer = this.enableSvgRenderer || this.enableHtml;\n if (this.sanitize && !bypassAngularSanitizer) {\n const sanitized = this.sanitizer.sanitize(SecurityContext.HTML, html);\n html = sanitized || '';\n }\n\n // Strip JavaScript unless explicitly enabled\n // This removes <script> tags and on* event handlers while keeping layout HTML\n if (bypassAngularSanitizer && !this.enableJavaScript) {\n html = this.stripJavaScript(html);\n }\n\n // Trust the HTML for display\n this.renderedContent = this.sanitizer.bypassSecurityTrustHtml(html);\n this.cdr.markForCheck();\n\n // Schedule post-render processing for next tick (after DOM update)\n Promise.resolve().then(() => this.postRenderProcessing());\n }\n\n /**\n * Process rendered content after DOM update\n * Handles syntax highlighting, mermaid rendering, copy buttons, etc.\n */\n private async postRenderProcessing(): Promise<void> {\n const container = this.elementRef.nativeElement.querySelector('.mj-markdown-container');\n if (!container) return;\n\n // Add copy buttons to code blocks\n if (this.enableCodeCopy && this.hasCodeBlocks) {\n this.markdownService.addCodeCopyButtons(container as HTMLElement);\n }\n\n // Initialize collapsible headings\n if (this.enableCollapsibleHeadings) {\n this.markdownService.initializeCollapsibleHeadings(container as HTMLElement);\n this.setupCollapsibleListeners(container as HTMLElement);\n }\n\n // Render mermaid diagrams (async)\n if (this.enableMermaid && this.hasMermaid) {\n await this.markdownService.renderMermaid(container as HTMLElement);\n }\n\n // Setup heading click listeners\n if (this.enableHeadingIds) {\n this.setupHeadingClickListeners(container as HTMLElement);\n }\n\n // Setup code copy listeners for custom event emission\n if (this.enableCodeCopy) {\n this.setupCodeCopyListeners(container as HTMLElement);\n }\n\n // Emit rendered event\n const renderTime = performance.now() - this.renderStartTime;\n const headingIds = this.markdownService.getHeadingList();\n\n this.rendered.emit({\n html: (container as HTMLElement).innerHTML,\n renderTime,\n hasMermaid: this.hasMermaid,\n hasCodeBlocks: this.hasCodeBlocks,\n headingIds,\n });\n }\n\n /**\n * Setup collapsible sections by adding toggle buttons and click listeners\n */\n private setupCollapsibleListeners(container: HTMLElement): void {\n const sections = container.querySelectorAll('.collapsible-section');\n\n sections.forEach((section) => {\n const wrapper = section.querySelector(':scope > .collapsible-heading-wrapper') as HTMLElement | null;\n if (!wrapper) return;\n\n // Check if toggle already exists\n if (wrapper.querySelector('.collapsible-toggle')) return;\n\n const isExpanded = !section.classList.contains('collapsed');\n const hasChildren = section.querySelector('.collapsible-section') !== null;\n\n // Create toggle button (chevron)\n const toggle = document.createElement('span');\n toggle.className = 'collapsible-toggle';\n toggle.setAttribute('role', 'button');\n toggle.setAttribute('tabindex', '0');\n toggle.setAttribute('aria-expanded', String(isExpanded));\n toggle.innerHTML = `<svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"currentColor\">\n <path d=\"M6 12l4-4-4-4\" stroke=\"currentColor\" stroke-width=\"2\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>`;\n\n // Insert toggle before heading\n wrapper.insertBefore(toggle, wrapper.firstChild);\n\n // Add action buttons container (only if has children)\n if (hasChildren) {\n const actions = this.createActionButtons(section as HTMLElement);\n wrapper.appendChild(actions);\n }\n\n // Make the whole wrapper clickable\n wrapper.style.cursor = 'pointer';\n\n // Add click listener to wrapper (but not on action buttons)\n wrapper.addEventListener('click', (e: Event) => {\n const target = e.target as HTMLElement;\n // Don't toggle if clicking on action buttons\n if (target.closest('.collapsible-actions')) return;\n\n e.preventDefault();\n e.stopPropagation();\n this.toggleSection(section as HTMLElement, toggle);\n });\n\n // Add keyboard support\n wrapper.addEventListener('keydown', (e: Event) => {\n const target = e.target as HTMLElement;\n if (target.closest('.collapsible-actions')) return;\n\n const keyEvent = e as KeyboardEvent;\n if (keyEvent.key === 'Enter' || keyEvent.key === ' ') {\n e.preventDefault();\n this.toggleSection(section as HTMLElement, toggle);\n }\n });\n });\n }\n\n /**\n * Create the expand/collapse all action buttons for sections with children\n */\n private createActionButtons(section: HTMLElement): HTMLElement {\n const container = document.createElement('span');\n container.className = 'collapsible-actions';\n\n // Expand all button\n const expandBtn = document.createElement('button');\n expandBtn.className = 'collapsible-action-btn expand-all';\n expandBtn.setAttribute('type', 'button');\n expandBtn.setAttribute('title', 'Expand all');\n expandBtn.innerHTML = `<svg width=\"14\" height=\"14\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M4 6l4 4 4-4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n <path d=\"M4 10l4 4 4-4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>`;\n\n expandBtn.addEventListener('click', (e: Event) => {\n e.preventDefault();\n e.stopPropagation();\n this.expandDescendants(section);\n // Also expand the section itself if collapsed\n if (section.classList.contains('collapsed')) {\n section.classList.remove('collapsed');\n const toggle = section.querySelector(':scope > .collapsible-heading-wrapper .collapsible-toggle');\n if (toggle) toggle.setAttribute('aria-expanded', 'true');\n }\n });\n\n // Collapse all button\n const collapseBtn = document.createElement('button');\n collapseBtn.className = 'collapsible-action-btn collapse-all';\n collapseBtn.setAttribute('type', 'button');\n collapseBtn.setAttribute('title', 'Collapse all');\n collapseBtn.innerHTML = `<svg width=\"14\" height=\"14\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M4 10l4-4 4 4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n <path d=\"M4 14l4-4 4 4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>`;\n\n collapseBtn.addEventListener('click', (e: Event) => {\n e.preventDefault();\n e.stopPropagation();\n this.collapseDescendants(section);\n });\n\n container.appendChild(expandBtn);\n container.appendChild(collapseBtn);\n\n return container;\n }\n\n /**\n * Toggle a collapsible section\n * @param section The section element to toggle\n * @param toggle The toggle button element\n */\n private toggleSection(section: HTMLElement, toggle: HTMLElement): void {\n const isCollapsed = section.classList.contains('collapsed');\n\n // Toggle the section\n section.classList.toggle('collapsed');\n toggle.setAttribute('aria-expanded', String(isCollapsed));\n // Children retain their state - CSS handles visibility via parent collapse\n }\n\n /**\n * Collapse all descendant sections (used by action button)\n */\n private collapseDescendants(section: HTMLElement): void {\n const descendants = section.querySelectorAll('.collapsible-section');\n descendants.forEach((desc) => {\n desc.classList.add('collapsed');\n const toggle = desc.querySelector(':scope > .collapsible-heading-wrapper .collapsible-toggle');\n if (toggle) {\n toggle.setAttribute('aria-expanded', 'false');\n }\n });\n }\n\n /**\n * Expand all descendant sections (used by action button)\n */\n private expandDescendants(section: HTMLElement): void {\n const descendants = section.querySelectorAll('.collapsible-section');\n descendants.forEach((desc) => {\n desc.classList.remove('collapsed');\n const toggle = desc.querySelector(':scope > .collapsible-heading-wrapper .collapsible-toggle');\n if (toggle) {\n toggle.setAttribute('aria-expanded', 'true');\n }\n });\n }\n\n /**\n * Setup click listeners for heading anchors\n */\n private setupHeadingClickListeners(container: HTMLElement): void {\n const headings = container.querySelectorAll('h1[id], h2[id], h3[id], h4[id], h5[id], h6[id]');\n\n headings.forEach((heading) => {\n heading.addEventListener('click', () => {\n const id = heading.getAttribute('id') || '';\n const text = heading.textContent || '';\n const level = parseInt(heading.tagName.charAt(1), 10);\n\n this.headingClick.emit({\n id,\n text,\n level,\n raw: text,\n });\n });\n });\n }\n\n /**\n * Setup listeners to emit code copy events\n */\n private setupCodeCopyListeners(container: HTMLElement): void {\n const copyButtons = container.querySelectorAll('.code-copy-btn');\n\n copyButtons.forEach((button) => {\n button.addEventListener('click', () => {\n const pre = button.closest('pre');\n const code = pre?.querySelector('code');\n if (code) {\n this.codeCopied.emit(code.textContent || '');\n }\n });\n });\n }\n\n /**\n * Watch the document's data-theme attribute for changes.\n * When the app theme switches (light ↔ dark) and mermaidTheme is 'auto',\n * re-render so mermaid diagrams pick up the new theme.\n */\n private setupThemeObserver(): void {\n if (typeof MutationObserver === 'undefined') return;\n\n this.themeObserver = new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n if (mutation.type === 'attributes' && mutation.attributeName === 'data-theme') {\n this.onThemeAttributeChanged();\n break;\n }\n }\n });\n\n this.themeObserver.observe(document.documentElement, {\n attributes: true,\n attributeFilter: ['data-theme'],\n });\n }\n\n /**\n * Called when data-theme changes on the document root.\n * Triggers a full re-render when mermaid auto-theming is active.\n */\n private onThemeAttributeChanged(): void {\n if (this.mermaidTheme === 'auto' && this.enableMermaid && this.hasMermaid && this.data) {\n this.render();\n }\n }\n\n /**\n * Cleanup event listeners\n */\n private cleanupEventListeners(): void {\n const container = this.elementRef.nativeElement.querySelector('.mj-markdown-container');\n if (!container) return;\n\n // Clone and replace to remove all listeners\n const clone = container.cloneNode(true);\n container.parentNode?.replaceChild(clone, container);\n }\n\n /**\n * Force a re-render of the markdown content\n */\n public refresh(): void {\n this.render();\n }\n\n /**\n * Get the current heading list (for TOC building)\n */\n public getHeadings(): HeadingInfo[] {\n return this.markdownService.getHeadingList();\n }\n\n /**\n * Scroll to a heading by ID\n */\n public scrollToHeading(headingId: string): void {\n const container = this.elementRef.nativeElement.querySelector('.mj-markdown-container');\n if (!container) return;\n\n const heading = container.querySelector(`#${headingId}`);\n if (heading) {\n heading.scrollIntoView({ behavior: 'smooth', block: 'start' });\n }\n }\n\n /**\n * Strip JavaScript from HTML content while preserving layout HTML.\n * Removes <script> tags, on* event handlers, and javascript: URLs.\n */\n private stripJavaScript(html: string): string {\n if (typeof DOMParser === 'undefined') {\n // Fallback for environments without DOMParser\n return this.fallbackStripJavaScript(html);\n }\n\n try {\n const parser = new DOMParser();\n const doc = parser.parseFromString('<body>' + html + '</body>', 'text/html');\n\n const cleanNode = (node: Element) => {\n const tagName = node.tagName.toLowerCase();\n\n // Remove unsafe elements completely\n if (tagName === 'script' || tagName === 'iframe' || tagName === 'object' || tagName === 'embed' || tagName === 'base') {\n node.parentNode?.removeChild(node);\n return;\n }\n\n if (node.attributes) {\n const attrs = Array.from(node.attributes);\n for (const attr of attrs) {\n const name = attr.name.toLowerCase();\n // Remove whitespace and control chars from value to prevent bypasses like \"java\\nscript:\"\n // eslint-disable-next-line no-control-regex\n const valueStr = attr.value.toLowerCase().replace(/[\\s\\x00-\\x20]/g, '');\n\n if (\n name.startsWith('on') ||\n ((name === 'href' || name === 'xlink:href' || name === 'src' || name === 'action' || name === 'formaction') &&\n (valueStr.startsWith('javascript:') || valueStr.startsWith('vbscript:') || valueStr.startsWith('data:text/html')))\n ) {\n node.removeAttribute(attr.name);\n }\n }\n }\n\n // Recursively clean children (Array.from prevents issues with live collections when removing nodes)\n Array.from(node.children).forEach(cleanNode);\n };\n\n Array.from(doc.body.children).forEach(cleanNode);\n return doc.body.innerHTML;\n } catch (e) {\n // If parsing fails completely, fall back to aggressive regex\n return this.fallbackStripJavaScript(html);\n }\n }\n\n /**\n * Fallback regex-based sanitization when DOMParser is unavailable.\n * Note: This is less robust than DOM parsing and should only be a fallback.\n */\n private fallbackStripJavaScript(html: string): string {\n // Remove <script> tags and their content\n html = html.replace(/<script\\b[^<]*(?:(?!<\\/script>)<[^<]*)*<\\/script>/gi, '');\n\n // Remove on* event handlers (onclick, onload, onerror, etc.)\n html = html.replace(/\\s+on\\w+\\s*=\\s*[\"'][^\"']*[\"']/gi, '');\n html = html.replace(/\\s+on\\w+\\s*=\\s*[^\\s>]+/gi, '');\n\n // Remove javascript: URLs from href and src attributes\n html = html.replace(/\\s+href\\s*=\\s*[\"']?javascript:[^\"'>\\s]*[\"']?/gi, '');\n html = html.replace(/\\s+src\\s*=\\s*[\"']?javascript:[^\"'>\\s]*[\"']?/gi, '');\n\n // Remove data: URLs that could contain scripts (data:text/html, etc.)\n html = html.replace(/\\s+src\\s*=\\s*[\"']?data:text\\/html[^\"'>\\s]*[\"']?/gi, '');\n\n return html;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"markdown.component.js","sourceRoot":"","sources":["../../../src/lib/components/markdown.component.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Component,\n Input,\n Output,\n EventEmitter,\n ElementRef,\n OnChanges,\n OnDestroy,\n SimpleChanges,\n SecurityContext,\n ViewEncapsulation,\n ChangeDetectionStrategy,\n ChangeDetectorRef,\n AfterViewInit,\n} from '@angular/core';\nimport { DomSanitizer, SafeHtml } from '@angular/platform-browser';\nimport { MarkdownService } from '../services/markdown.service';\nimport { MarkdownConfig, DEFAULT_MARKDOWN_CONFIG, MarkdownRenderEvent, HeadingInfo } from '@memberjunction/markdown-core';\n// Collapsible section toggle is handled inline in setupCollapsibleListeners\n\n/**\n * Angular component for rendering markdown content.\n *\n * Features:\n * - Prism.js syntax highlighting for code blocks\n * - Mermaid diagram rendering\n * - Copy-to-clipboard for code blocks\n * - Collapsible heading sections\n * - GitHub-style alerts and heading IDs\n *\n * Usage:\n * ```html\n * <mj-markdown [data]=\"markdownContent\"></mj-markdown>\n *\n * <mj-markdown\n * [data]=\"content\"\n * [enableMermaid]=\"true\"\n * [enableCollapsibleHeadings]=\"true\"\n * (rendered)=\"onRendered($event)\">\n * </mj-markdown>\n * ```\n */\n@Component({\n selector: 'mj-markdown',\n standalone: false,\n template: ` <div class=\"mj-markdown-container\" [class]=\"containerClass\" [innerHTML]=\"renderedContent\"></div> `,\n styleUrls: ['./markdown.component.css'],\n encapsulation: ViewEncapsulation.None, // Allow styles to penetrate into rendered content\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class MarkdownComponent implements OnChanges, AfterViewInit, OnDestroy {\n /**\n * The markdown content to render\n */\n @Input() data: string = '';\n\n /**\n * Enable syntax highlighting\n */\n @Input() enableHighlight: boolean = DEFAULT_MARKDOWN_CONFIG.enableHighlight;\n\n /**\n * Enable Mermaid diagram rendering\n */\n @Input() enableMermaid: boolean = DEFAULT_MARKDOWN_CONFIG.enableMermaid;\n\n /**\n * Enable copy button on code blocks\n */\n @Input() enableCodeCopy: boolean = DEFAULT_MARKDOWN_CONFIG.enableCodeCopy;\n\n /**\n * Enable collapsible heading sections\n */\n @Input() enableCollapsibleHeadings: boolean = DEFAULT_MARKDOWN_CONFIG.enableCollapsibleHeadings;\n\n /**\n * Heading level at which to start collapsing\n */\n @Input() collapsibleHeadingLevel: 1 | 2 | 3 | 4 | 5 | 6 = DEFAULT_MARKDOWN_CONFIG.collapsibleHeadingLevel;\n\n /**\n * Whether collapsible sections should be expanded by default\n */\n @Input() collapsibleDefaultExpanded: boolean = DEFAULT_MARKDOWN_CONFIG.collapsibleDefaultExpanded;\n\n /**\n * Specify which heading levels should start expanded.\n * Array of heading levels (2-6) that should be expanded by default.\n * Takes precedence over collapsibleDefaultExpanded for specified levels.\n *\n * Examples:\n * - [2] = Only h2 expanded, h3-h6 collapsed\n * - [2, 3] = h2 and h3 expanded, h4-h6 collapsed\n * - [] = All collapsed\n * - undefined = Uses collapsibleDefaultExpanded for all levels\n */\n @Input() autoExpandLevels?: number[];\n\n /**\n * Enable GitHub-style alerts\n */\n @Input() enableAlerts: boolean = DEFAULT_MARKDOWN_CONFIG.enableAlerts;\n\n /**\n * Enable smartypants for typography (curly quotes, em/en dashes, ellipses)\n */\n @Input() enableSmartypants: boolean = DEFAULT_MARKDOWN_CONFIG.enableSmartypants;\n\n /**\n * Enable SVG code block rendering\n * When enabled, ```svg code blocks are rendered as actual SVG images\n */\n @Input() enableSvgRenderer: boolean = DEFAULT_MARKDOWN_CONFIG.enableSvgRenderer;\n\n /**\n * Enable raw HTML passthrough in markdown content.\n * Scripts and event handlers are still stripped unless enableJavaScript is true.\n */\n @Input() enableHtml: boolean = DEFAULT_MARKDOWN_CONFIG.enableHtml;\n\n /**\n * Enable JavaScript in HTML content (<script> tags and on* handlers).\n * WARNING: Major security risk - only enable for fully trusted content.\n */\n @Input() enableJavaScript: boolean = DEFAULT_MARKDOWN_CONFIG.enableJavaScript;\n\n /**\n * Enable heading IDs for anchor links\n */\n @Input() enableHeadingIds: boolean = DEFAULT_MARKDOWN_CONFIG.enableHeadingIds;\n\n /**\n * Prefix for heading IDs\n */\n @Input() headingIdPrefix: string = DEFAULT_MARKDOWN_CONFIG.headingIdPrefix;\n\n /**\n * Enable line numbers in code blocks\n */\n @Input() enableLineNumbers: boolean = DEFAULT_MARKDOWN_CONFIG.enableLineNumbers;\n\n /**\n * Custom CSS class for the container\n */\n @Input() containerClass: string = '';\n\n /**\n * Mermaid theme.\n * 'auto' (default) detects light/dark from the document's data-theme attribute.\n */\n @Input() mermaidTheme: 'auto' | 'default' | 'dark' | 'forest' | 'neutral' | 'base' = DEFAULT_MARKDOWN_CONFIG.mermaidTheme;\n\n /**\n * Whether to sanitize HTML output\n */\n @Input() sanitize: boolean = DEFAULT_MARKDOWN_CONFIG.sanitize;\n\n /**\n * Emitted when rendering is complete\n */\n @Output() rendered = new EventEmitter<MarkdownRenderEvent>();\n\n /**\n * Emitted when a heading anchor is clicked\n */\n @Output() headingClick = new EventEmitter<HeadingInfo>();\n\n /**\n * Emitted when code is copied to clipboard\n */\n @Output() codeCopied = new EventEmitter<string>();\n\n /**\n * The sanitized HTML content to display\n */\n public renderedContent: SafeHtml = '';\n\n /**\n * Public accessor for the component's element reference.\n * Provided for backward compatibility with ngx-markdown API.\n */\n public get element(): ElementRef<HTMLElement> {\n return this.elementRef;\n }\n\n private renderStartTime: number = 0;\n private hasMermaid: boolean = false;\n private hasCodeBlocks: boolean = false;\n private themeObserver: MutationObserver | null = null;\n\n constructor(\n private elementRef: ElementRef<HTMLElement>,\n private sanitizer: DomSanitizer,\n private markdownService: MarkdownService,\n private cdr: ChangeDetectorRef,\n ) {}\n\n ngOnChanges(changes: SimpleChanges): void {\n // Check if any relevant input changed\n const needsRerender =\n changes['data'] ||\n changes['enableHighlight'] ||\n changes['enableMermaid'] ||\n changes['enableCodeCopy'] ||\n changes['enableCollapsibleHeadings'] ||\n changes['collapsibleHeadingLevel'] ||\n changes['collapsibleDefaultExpanded'] ||\n changes['autoExpandLevels'] ||\n changes['enableAlerts'] ||\n changes['enableSmartypants'] ||\n changes['enableSvgRenderer'] ||\n changes['enableHtml'] ||\n changes['enableJavaScript'] ||\n changes['enableHeadingIds'] ||\n changes['headingIdPrefix'] ||\n changes['mermaidTheme'] ||\n changes['sanitize'];\n\n if (needsRerender) {\n this.render();\n }\n }\n\n ngAfterViewInit(): void {\n // Initial render if data was provided\n if (this.data) {\n this.postRenderProcessing();\n }\n\n this.setupThemeObserver();\n }\n\n ngOnDestroy(): void {\n this.themeObserver?.disconnect();\n this.themeObserver = null;\n this.cleanupEventListeners();\n }\n\n /**\n * Render the markdown content\n */\n private render(): void {\n if (!this.data) {\n this.renderedContent = '';\n this.cdr.markForCheck();\n return;\n }\n\n this.renderStartTime = performance.now();\n\n // Build config from inputs\n const config: MarkdownConfig = {\n enableHighlight: this.enableHighlight,\n enableMermaid: this.enableMermaid,\n enableCodeCopy: this.enableCodeCopy,\n enableCollapsibleHeadings: this.enableCollapsibleHeadings,\n collapsibleHeadingLevel: this.collapsibleHeadingLevel,\n collapsibleDefaultExpanded: this.collapsibleDefaultExpanded,\n autoExpandLevels: this.autoExpandLevels,\n enableAlerts: this.enableAlerts,\n enableSmartypants: this.enableSmartypants,\n enableSvgRenderer: this.enableSvgRenderer,\n enableHtml: this.enableHtml,\n enableJavaScript: this.enableJavaScript,\n enableHeadingIds: this.enableHeadingIds,\n headingIdPrefix: this.headingIdPrefix,\n mermaidTheme: this.mermaidTheme,\n sanitize: this.sanitize,\n };\n\n // Configure service and parse\n this.markdownService.configureMarked(config);\n let html = this.markdownService.parse(this.data);\n\n // Check for mermaid and code blocks\n this.hasMermaid = html.includes('language-mermaid') || html.includes('class=\"mermaid\"');\n this.hasCodeBlocks = html.includes('<pre>') && html.includes('<code');\n\n // Sanitize if enabled\n // Note: We bypass Angular's sanitizer when SVG renderer or HTML passthrough is enabled\n // because it strips SVG elements and most HTML layout tags.\n const bypassAngularSanitizer = this.enableSvgRenderer || this.enableHtml;\n if (this.sanitize && !bypassAngularSanitizer) {\n const sanitized = this.sanitizer.sanitize(SecurityContext.HTML, html);\n html = sanitized || '';\n }\n\n // Strip JavaScript unless explicitly enabled\n // This removes <script> tags and on* event handlers while keeping layout HTML\n if (bypassAngularSanitizer && !this.enableJavaScript) {\n html = this.stripJavaScript(html);\n }\n\n // Trust the HTML for display\n this.renderedContent = this.sanitizer.bypassSecurityTrustHtml(html);\n this.cdr.markForCheck();\n\n // Schedule post-render processing for next tick (after DOM update)\n Promise.resolve().then(() => this.postRenderProcessing());\n }\n\n /**\n * Process rendered content after DOM update\n * Handles syntax highlighting, mermaid rendering, copy buttons, etc.\n */\n private async postRenderProcessing(): Promise<void> {\n const container = this.elementRef.nativeElement.querySelector('.mj-markdown-container');\n if (!container) return;\n\n // Add copy buttons to code blocks\n if (this.enableCodeCopy && this.hasCodeBlocks) {\n this.markdownService.addCodeCopyButtons(container as HTMLElement);\n }\n\n // Initialize collapsible headings\n if (this.enableCollapsibleHeadings) {\n this.markdownService.initializeCollapsibleHeadings(container as HTMLElement);\n this.setupCollapsibleListeners(container as HTMLElement);\n }\n\n // Render mermaid diagrams (async)\n if (this.enableMermaid && this.hasMermaid) {\n await this.markdownService.renderMermaid(container as HTMLElement);\n }\n\n // Setup heading click listeners\n if (this.enableHeadingIds) {\n this.setupHeadingClickListeners(container as HTMLElement);\n }\n\n // Setup code copy listeners for custom event emission\n if (this.enableCodeCopy) {\n this.setupCodeCopyListeners(container as HTMLElement);\n }\n\n // Emit rendered event\n const renderTime = performance.now() - this.renderStartTime;\n const headingIds = this.markdownService.getHeadingList();\n\n this.rendered.emit({\n html: (container as HTMLElement).innerHTML,\n renderTime,\n hasMermaid: this.hasMermaid,\n hasCodeBlocks: this.hasCodeBlocks,\n headingIds,\n });\n }\n\n /**\n * Setup collapsible sections by adding toggle buttons and click listeners\n */\n private setupCollapsibleListeners(container: HTMLElement): void {\n const sections = container.querySelectorAll('.collapsible-section');\n\n sections.forEach((section) => {\n const wrapper = section.querySelector(':scope > .collapsible-heading-wrapper') as HTMLElement | null;\n if (!wrapper) return;\n\n // Check if toggle already exists\n if (wrapper.querySelector('.collapsible-toggle')) return;\n\n const isExpanded = !section.classList.contains('collapsed');\n const hasChildren = section.querySelector('.collapsible-section') !== null;\n\n // Create toggle button (chevron)\n const toggle = document.createElement('span');\n toggle.className = 'collapsible-toggle';\n toggle.setAttribute('role', 'button');\n toggle.setAttribute('tabindex', '0');\n toggle.setAttribute('aria-expanded', String(isExpanded));\n toggle.innerHTML = `<svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"currentColor\">\n <path d=\"M6 12l4-4-4-4\" stroke=\"currentColor\" stroke-width=\"2\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>`;\n\n // Insert toggle before heading\n wrapper.insertBefore(toggle, wrapper.firstChild);\n\n // Add action buttons container (only if has children)\n if (hasChildren) {\n const actions = this.createActionButtons(section as HTMLElement);\n wrapper.appendChild(actions);\n }\n\n // Make the whole wrapper clickable\n wrapper.style.cursor = 'pointer';\n\n // Add click listener to wrapper (but not on action buttons)\n wrapper.addEventListener('click', (e: Event) => {\n const target = e.target as HTMLElement;\n // Don't toggle if clicking on action buttons\n if (target.closest('.collapsible-actions')) return;\n\n e.preventDefault();\n e.stopPropagation();\n this.toggleSection(section as HTMLElement, toggle);\n });\n\n // Add keyboard support\n wrapper.addEventListener('keydown', (e: Event) => {\n const target = e.target as HTMLElement;\n if (target.closest('.collapsible-actions')) return;\n\n const keyEvent = e as KeyboardEvent;\n if (keyEvent.key === 'Enter' || keyEvent.key === ' ') {\n e.preventDefault();\n this.toggleSection(section as HTMLElement, toggle);\n }\n });\n });\n }\n\n /**\n * Create the expand/collapse all action buttons for sections with children\n */\n private createActionButtons(section: HTMLElement): HTMLElement {\n const container = document.createElement('span');\n container.className = 'collapsible-actions';\n\n // Expand all button\n const expandBtn = document.createElement('button');\n expandBtn.className = 'collapsible-action-btn expand-all';\n expandBtn.setAttribute('type', 'button');\n expandBtn.setAttribute('title', 'Expand all');\n expandBtn.innerHTML = `<svg width=\"14\" height=\"14\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M4 6l4 4 4-4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n <path d=\"M4 10l4 4 4-4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>`;\n\n expandBtn.addEventListener('click', (e: Event) => {\n e.preventDefault();\n e.stopPropagation();\n this.expandDescendants(section);\n // Also expand the section itself if collapsed\n if (section.classList.contains('collapsed')) {\n section.classList.remove('collapsed');\n const toggle = section.querySelector(':scope > .collapsible-heading-wrapper .collapsible-toggle');\n if (toggle) toggle.setAttribute('aria-expanded', 'true');\n }\n });\n\n // Collapse all button\n const collapseBtn = document.createElement('button');\n collapseBtn.className = 'collapsible-action-btn collapse-all';\n collapseBtn.setAttribute('type', 'button');\n collapseBtn.setAttribute('title', 'Collapse all');\n collapseBtn.innerHTML = `<svg width=\"14\" height=\"14\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M4 10l4-4 4 4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n <path d=\"M4 14l4-4 4 4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>`;\n\n collapseBtn.addEventListener('click', (e: Event) => {\n e.preventDefault();\n e.stopPropagation();\n this.collapseDescendants(section);\n });\n\n container.appendChild(expandBtn);\n container.appendChild(collapseBtn);\n\n return container;\n }\n\n /**\n * Toggle a collapsible section\n * @param section The section element to toggle\n * @param toggle The toggle button element\n */\n private toggleSection(section: HTMLElement, toggle: HTMLElement): void {\n const isCollapsed = section.classList.contains('collapsed');\n\n // Toggle the section\n section.classList.toggle('collapsed');\n toggle.setAttribute('aria-expanded', String(isCollapsed));\n // Children retain their state - CSS handles visibility via parent collapse\n }\n\n /**\n * Collapse all descendant sections (used by action button)\n */\n private collapseDescendants(section: HTMLElement): void {\n const descendants = section.querySelectorAll('.collapsible-section');\n descendants.forEach((desc) => {\n desc.classList.add('collapsed');\n const toggle = desc.querySelector(':scope > .collapsible-heading-wrapper .collapsible-toggle');\n if (toggle) {\n toggle.setAttribute('aria-expanded', 'false');\n }\n });\n }\n\n /**\n * Expand all descendant sections (used by action button)\n */\n private expandDescendants(section: HTMLElement): void {\n const descendants = section.querySelectorAll('.collapsible-section');\n descendants.forEach((desc) => {\n desc.classList.remove('collapsed');\n const toggle = desc.querySelector(':scope > .collapsible-heading-wrapper .collapsible-toggle');\n if (toggle) {\n toggle.setAttribute('aria-expanded', 'true');\n }\n });\n }\n\n /**\n * Setup click listeners for heading anchors\n */\n private setupHeadingClickListeners(container: HTMLElement): void {\n const headings = container.querySelectorAll('h1[id], h2[id], h3[id], h4[id], h5[id], h6[id]');\n\n headings.forEach((heading) => {\n heading.addEventListener('click', () => {\n const id = heading.getAttribute('id') || '';\n const text = heading.textContent || '';\n const level = parseInt(heading.tagName.charAt(1), 10);\n\n this.headingClick.emit({\n id,\n text,\n level,\n raw: text,\n });\n });\n });\n }\n\n /**\n * Setup listeners to emit code copy events\n */\n private setupCodeCopyListeners(container: HTMLElement): void {\n const copyButtons = container.querySelectorAll('.code-copy-btn');\n\n copyButtons.forEach((button) => {\n button.addEventListener('click', () => {\n const pre = button.closest('pre');\n const code = pre?.querySelector('code');\n if (code) {\n this.codeCopied.emit(code.textContent || '');\n }\n });\n });\n }\n\n /**\n * Watch the document's data-theme attribute for changes.\n * When the app theme switches (light ↔ dark) and mermaidTheme is 'auto',\n * re-render so mermaid diagrams pick up the new theme.\n */\n private setupThemeObserver(): void {\n if (typeof MutationObserver === 'undefined') return;\n\n this.themeObserver = new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n if (mutation.type === 'attributes' && mutation.attributeName === 'data-theme') {\n this.onThemeAttributeChanged();\n break;\n }\n }\n });\n\n this.themeObserver.observe(document.documentElement, {\n attributes: true,\n attributeFilter: ['data-theme'],\n });\n }\n\n /**\n * Called when data-theme changes on the document root.\n * Triggers a full re-render when mermaid auto-theming is active.\n */\n private onThemeAttributeChanged(): void {\n if (this.mermaidTheme === 'auto' && this.enableMermaid && this.hasMermaid && this.data) {\n this.render();\n }\n }\n\n /**\n * Cleanup event listeners\n */\n private cleanupEventListeners(): void {\n const container = this.elementRef.nativeElement.querySelector('.mj-markdown-container');\n if (!container) return;\n\n // Clone and replace to remove all listeners\n const clone = container.cloneNode(true);\n container.parentNode?.replaceChild(clone, container);\n }\n\n /**\n * Force a re-render of the markdown content\n */\n public refresh(): void {\n this.render();\n }\n\n /**\n * Get the current heading list (for TOC building)\n */\n public getHeadings(): HeadingInfo[] {\n return this.markdownService.getHeadingList();\n }\n\n /**\n * Scroll to a heading by ID\n */\n public scrollToHeading(headingId: string): void {\n const container = this.elementRef.nativeElement.querySelector('.mj-markdown-container');\n if (!container) return;\n\n const heading = container.querySelector(`#${headingId}`);\n if (heading) {\n heading.scrollIntoView({ behavior: 'smooth', block: 'start' });\n }\n }\n\n /**\n * Strip JavaScript from HTML content while preserving layout HTML.\n * Removes <script> tags, on* event handlers, and javascript: URLs.\n */\n private stripJavaScript(html: string): string {\n if (typeof DOMParser === 'undefined') {\n // Fallback for environments without DOMParser\n return this.fallbackStripJavaScript(html);\n }\n\n try {\n const parser = new DOMParser();\n const doc = parser.parseFromString('<body>' + html + '</body>', 'text/html');\n\n const cleanNode = (node: Element) => {\n const tagName = node.tagName.toLowerCase();\n\n // Remove unsafe elements completely\n if (tagName === 'script' || tagName === 'iframe' || tagName === 'object' || tagName === 'embed' || tagName === 'base') {\n node.parentNode?.removeChild(node);\n return;\n }\n\n if (node.attributes) {\n const attrs = Array.from(node.attributes);\n for (const attr of attrs) {\n const name = attr.name.toLowerCase();\n // Remove whitespace and control chars from value to prevent bypasses like \"java\\nscript:\"\n // eslint-disable-next-line no-control-regex\n const valueStr = attr.value.toLowerCase().replace(/[\\s\\x00-\\x20]/g, '');\n\n if (\n name.startsWith('on') ||\n ((name === 'href' || name === 'xlink:href' || name === 'src' || name === 'action' || name === 'formaction') &&\n (valueStr.startsWith('javascript:') || valueStr.startsWith('vbscript:') || valueStr.startsWith('data:text/html')))\n ) {\n node.removeAttribute(attr.name);\n }\n }\n }\n\n // Recursively clean children (Array.from prevents issues with live collections when removing nodes)\n Array.from(node.children).forEach(cleanNode);\n };\n\n Array.from(doc.body.children).forEach(cleanNode);\n return doc.body.innerHTML;\n } catch (e) {\n // If parsing fails completely, fall back to aggressive regex\n return this.fallbackStripJavaScript(html);\n }\n }\n\n /**\n * Fallback regex-based sanitization when DOMParser is unavailable.\n * Note: This is less robust than DOM parsing and should only be a fallback.\n */\n private fallbackStripJavaScript(html: string): string {\n // Remove <script> tags and their content\n html = html.replace(/<script\\b[^<]*(?:(?!<\\/script>)<[^<]*)*<\\/script>/gi, '');\n\n // Remove on* event handlers (onclick, onload, onerror, etc.)\n html = html.replace(/\\s+on\\w+\\s*=\\s*[\"'][^\"']*[\"']/gi, '');\n html = html.replace(/\\s+on\\w+\\s*=\\s*[^\\s>]+/gi, '');\n\n // Remove javascript: URLs from href and src attributes\n html = html.replace(/\\s+href\\s*=\\s*[\"']?javascript:[^\"'>\\s]*[\"']?/gi, '');\n html = html.replace(/\\s+src\\s*=\\s*[\"']?javascript:[^\"'>\\s]*[\"']?/gi, '');\n\n // Remove data: URLs that could contain scripts (data:text/html, etc.)\n html = html.replace(/\\s+src\\s*=\\s*[\"']?data:text\\/html[^\"'>\\s]*[\"']?/gi, '');\n\n return html;\n }\n}\n"]}
|