Skip to content

Commit b450cc0

Browse files
authored
refactor: Refactored markdown renderer setup in MarkdownService (#16738)
1 parent 531c7f9 commit b450cc0

10 files changed

Lines changed: 5829 additions & 6862 deletions

File tree

package-lock.json

Lines changed: 5797 additions & 6761 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@
128128
"igniteui-dockmanager": "^1.17.0",
129129
"igniteui-i18n-resources": "^1.0.2",
130130
"igniteui-sassdoc-theme": "^2.1.0",
131-
"igniteui-webcomponents": "^6.4.0",
131+
"igniteui-webcomponents": "^6.5.0",
132132
"jasmine": "^5.6.0",
133133
"jasmine-core": "^5.6.0",
134134
"karma": "^6.4.4",

projects/igniteui-angular/chat-extras/src/markdown-pipe.spec.ts

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,15 @@ import { IgxChatMarkdownService } from './markdown-service';
44
import { MarkdownPipe } from './markdown-pipe';
55
import Spy = jasmine.Spy;
66

7-
// Mock the Service: We only care that the pipe calls the service and gets an HTML string.
8-
// We provide a *known* unsafe HTML string to ensure sanitization is working.
9-
const mockUnsafeHtml = `
7+
// Mock the Service: We trust the service to provide safe HTML from Shiki.
8+
const mockSafeHtml = `
109
<pre class="shiki" style="color: var(--shiki-fg);"><code><span style="color: #FF0000;">unsafe</span></code></pre>
11-
<img src="x" onerror="alert(1)">
10+
<img src="x">
1211
`;
1312

1413
class MockChatMarkdownService {
1514
public async parse(_: string): Promise<string> {
16-
return mockUnsafeHtml;
15+
return mockSafeHtml;
1716
}
1817
}
1918

@@ -39,15 +38,22 @@ describe('MarkdownPipe', () => {
3938
expect(pipe).toBeTruthy();
4039
});
4140

42-
it('should call the service, sanitize content, and return SafeHtml', async () => {
41+
it('should call the service and return SafeHtml with styles preserved', async () => {
4342
await pipe.transform('some markdown');
4443

4544
expect(bypassSpy).toHaveBeenCalledTimes(1);
4645

47-
const sanitizedString = bypassSpy.calls.mostRecent().args[0];
46+
const htmlString = bypassSpy.calls.mostRecent().args[0];
4847

49-
expect(sanitizedString).not.toContain('onerror');
50-
expect(sanitizedString).toContain('style="color: var(--shiki-fg);"');
48+
expect(htmlString).toContain('style="color: var(--shiki-fg);"');
49+
expect(htmlString).toContain('<pre class="shiki"');
50+
});
51+
52+
it('should trust the service to provide safe HTML', async () => {
53+
const result = await pipe.transform('# Test');
54+
55+
expect(bypassSpy).toHaveBeenCalledWith(mockSafeHtml);
56+
expect(result).toBeTruthy();
5157
});
5258

5359
it('should handle undefined input text', async () => {

projects/igniteui-angular/chat-extras/src/markdown-pipe.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import DOMPurify from 'dompurify';
21
import { inject, Pipe, type PipeTransform } from '@angular/core';
32
import { IgxChatMarkdownService } from './markdown-service';
43
import { DomSanitizer, type SafeHtml } from '@angular/platform-browser';
@@ -11,8 +10,8 @@ export class MarkdownPipe implements PipeTransform {
1110

1211

1312
public async transform(text?: string): Promise<SafeHtml> {
14-
return this._sanitizer.bypassSecurityTrustHtml(DOMPurify.sanitize(
13+
return this._sanitizer.bypassSecurityTrustHtml(
1514
await this._service.parse(text ?? '')
16-
));
15+
);
1716
}
1817
}

projects/igniteui-angular/chat-extras/src/markdown-service.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ describe('IgxChatMarkdownService', () => {
3131
expect(result).toContain('code');
3232
});
3333

34-
it('should apply custom link extension with target="_blank"', async () => {
34+
it('should apply custom link extension', async () => {
3535
const markdown = '[Infragistics](https://www.infragistics.com)';
36-
const expectedLink = '<p><a href="https://www.infragistics.com" target="_blank" rel="noopener noreferrer" >Infragistics</a></p>';
36+
const expectedLink = '<p><a href="https://www.infragistics.com" rel="noopener noreferrer">Infragistics</a></p>';
3737

3838
const result = await service.parse(markdown);
3939
expect(result).toContain(expectedLink);
Lines changed: 9 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,21 @@
11
import { Injectable } from '@angular/core';
2-
import { Marked } from 'marked';
3-
import markedShiki from 'marked-shiki';
4-
import { bundledThemes, createHighlighter } from 'shiki/bundle/web';
2+
import { setupMarkdownRenderer, type MarkdownRenderer } from 'igniteui-webcomponents/extras';
53

64

7-
const DEFAULT_LANGUAGES = ['javascript', 'typescript', 'html', 'css'];
8-
const DEFAULT_THEMES = {
9-
light: 'github-light',
10-
dark: 'github-dark'
11-
};
12-
135
@Injectable({ providedIn: 'root' })
146
export class IgxChatMarkdownService {
157

16-
private _instance: Marked;
17-
private _isInitialized: Promise<void>;
18-
19-
private _initializeMarked(): void {
20-
this._instance = new Marked({
21-
breaks: true,
22-
gfm: true,
23-
extensions: [
24-
{
25-
name: 'link',
26-
renderer({ href, title, text }) {
27-
return `<a href="${href}" target="_blank" rel="noopener noreferrer" ${title ? `title="${title}"` : ''}>${text}</a>`;
28-
}
29-
}
30-
]
31-
});
32-
}
33-
34-
private async _initializeShiki(): Promise<void> {
35-
const highlighter = await createHighlighter({
36-
langs: DEFAULT_LANGUAGES,
37-
themes: Object.keys(bundledThemes)
38-
});
39-
40-
this._instance.use(
41-
markedShiki({
42-
highlight(code, lang, _) {
43-
try {
44-
return highlighter.codeToHtml(code, {
45-
lang,
46-
themes: DEFAULT_THEMES,
47-
});
48-
49-
} catch {
50-
return `<pre><code>${code}</code></pre>`;
51-
}
52-
}
53-
})
54-
);
55-
}
56-
8+
private _renderer: MarkdownRenderer | null = null;
579

58-
constructor() {
59-
this._initializeMarked();
60-
this._isInitialized = this._initializeShiki();
10+
private async _getRenderer(): Promise<MarkdownRenderer> {
11+
if (!this._renderer) {
12+
this._renderer = await setupMarkdownRenderer();
13+
}
14+
return this._renderer;
6115
}
6216

6317
public async parse(text: string): Promise<string> {
64-
await this._isInitialized;
65-
return await this._instance.parse(text);
18+
const renderer = await this._getRenderer();
19+
return await renderer.parse(text);
6620
}
6721
}

projects/igniteui-angular/ng-package.json

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,6 @@
1515
"@igniteui/material-icons-extended",
1616
"igniteui-theming",
1717
"igniteui-i18n-core",
18-
"igniteui-webcomponents",
19-
"dompurify",
20-
"marked",
21-
"marked-shiki",
22-
"shiki"
18+
"igniteui-webcomponents"
2319
]
2420
}

projects/igniteui-angular/ng-package.prod.json

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,6 @@
1414
"@igniteui/material-icons-extended",
1515
"igniteui-theming",
1616
"igniteui-i18n-core",
17-
"igniteui-webcomponents",
18-
"dompurify",
19-
"marked",
20-
"marked-shiki",
21-
"shiki"
17+
"igniteui-webcomponents"
2218
]
2319
}

projects/igniteui-angular/package.json

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,7 @@
8686
"@angular/forms": "21",
8787
"hammerjs": "^2.0.8",
8888
"@types/hammerjs": "^2.0.46",
89-
"igniteui-webcomponents": "^6.3.0",
90-
"dompurify": "^3.2.0",
91-
"marked": ">=16.3.0",
92-
"marked-shiki": "^1.2.0",
93-
"shiki": "^3.12.0"
89+
"igniteui-webcomponents": "^6.5.0"
9490
},
9591
"peerDependenciesMeta": {
9692
"hammerjs": {
@@ -101,18 +97,6 @@
10197
},
10298
"igniteui-webcomponents": {
10399
"optional": true
104-
},
105-
"dompurify": {
106-
"optional": true
107-
},
108-
"marked": {
109-
"optional": true
110-
},
111-
"marked-shiki": {
112-
"optional": true
113-
},
114-
"shiki": {
115-
"optional": true
116100
}
117101
},
118102
"igxDevDependencies": {

projects/igniteui-angular/schematics/utils/dependency-handler.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,6 @@ export const DEPENDENCIES_MAP: PackageEntry[] = [
2929
{ name: 'igniteui-i18n-core', target: PackageTarget.REGULAR },
3030
{ name: 'igniteui-theming', target: PackageTarget.NONE },
3131
{ name: 'igniteui-webcomponents', target: PackageTarget.NONE },
32-
{ name: 'dompurify', target: PackageTarget.NONE },
33-
{ name: 'marked', target: PackageTarget.NONE },
34-
{ name: 'marked-shiki', target: PackageTarget.NONE },
35-
{ name: 'shiki', target: PackageTarget.NONE },
3632
// peerDependencies
3733
{ name: '@angular/forms', target: PackageTarget.NONE },
3834
{ name: '@angular/common', target: PackageTarget.NONE },

0 commit comments

Comments
 (0)