Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
c96c716
feat: Added chat component Angular wrapper
rkaraivanov Sep 29, 2025
feb599b
Merge branch 'master' into rkaraivanov/wc-chat-wrapper
ChronosSF Sep 30, 2025
b1a45c0
chore(*): pushing updated package.lock
ChronosSF Sep 30, 2025
e0939f4
fix: Old templates refs handling when there is a dynamic change
rkaraivanov Sep 30, 2025
23490fb
Merge branch 'rkaraivanov/wc-chat-wrapper' of https://github.com/Igni…
rkaraivanov Sep 30, 2025
91e241c
feat: Added chat template directives
rkaraivanov Oct 3, 2025
fe80cc1
chore(*): Added a dev sample for the Chat component
gedinakova Oct 13, 2025
94ee98a
fix: Markdown render and shiki initialization
rkaraivanov Oct 20, 2025
9d0675e
Merge remote-tracking branch 'origin/master' into rkaraivanov/wc-chat…
rkaraivanov Oct 20, 2025
8fcb12c
chore: Dedupe and regenerate package-lock
rkaraivanov Oct 20, 2025
2ca4602
Merge branch 'master' into rkaraivanov/wc-chat-wrapper
gedinakova Oct 21, 2025
67389ae
fix(CHat): Adjusted suggestions to dev context.
gedinakova Oct 21, 2025
2d6086f
chore(*): Updated package-lock.
gedinakova Oct 21, 2025
80fe475
Merge branch 'master' into rkaraivanov/wc-chat-wrapper
rkaraivanov Oct 22, 2025
f0da68c
Merge remote-tracking branch 'origin/master' into rkaraivanov/wc-chat…
rkaraivanov Oct 23, 2025
a0c1e68
fix(chat): Added ng-content to support underlying slots
rkaraivanov Oct 23, 2025
83a600a
Merge branch 'rkaraivanov/wc-chat-wrapper' of https://github.com/Igni…
rkaraivanov Oct 23, 2025
5bdbb25
Merge remote-tracking branch 'origin/master' into rkaraivanov/wc-chat…
rkaraivanov Oct 30, 2025
945c9a7
fix(*): Added messageReact event
gedinakova Oct 30, 2025
bba26ce
Merge branch 'rkaraivanov/wc-chat-wrapper' of https://github.com/Igni…
gedinakova Oct 30, 2025
99be9cb
Merge branch 'master' into rkaraivanov/wc-chat-wrapper
gedinakova Oct 30, 2025
6e6215b
refactor: Moved markdown pipe into a separate entry point
rkaraivanov Oct 31, 2025
f7465a2
Merge branch 'master' into rkaraivanov/wc-chat-wrapper
rkaraivanov Oct 31, 2025
7c66dbf
Merge branch 'master' into rkaraivanov/wc-chat-wrapper
rkaraivanov Nov 3, 2025
c900ab7
refactor: Chat wrapper improvements
rkaraivanov Nov 7, 2025
6ab561a
fix: Rename extras to chat-extras for clarity
rkaraivanov Nov 11, 2025
aa9836e
Merge remote-tracking branch 'origin/master' into rkaraivanov/wc-chat…
rkaraivanov Nov 11, 2025
cece274
Merge branch 'master' into rkaraivanov/wc-chat-wrapper
ChronosSF Nov 13, 2025
115fbe3
Merge branch 'master' into rkaraivanov/wc-chat-wrapper
ChronosSF Nov 13, 2025
09c5144
Merge branch 'master' into rkaraivanov/wc-chat-wrapper
gedinakova Nov 17, 2025
0250924
Merge branch 'master' of https://github.com/IgniteUI/igniteui-angular…
gedinakova Nov 17, 2025
0ca2138
Merge branch 'rkaraivanov/wc-chat-wrapper' of https://github.com/Igni…
gedinakova Nov 17, 2025
8f48e77
Merge remote-tracking branch 'origin/master' into rkaraivanov/wc-chat…
rkaraivanov Nov 20, 2025
be91457
chore: Regenerate package-lock.json
rkaraivanov Nov 20, 2025
6cbcadd
refactor: Chat component wrapper moved to its own library
rkaraivanov Nov 20, 2025
d750a12
chore: update package-lock.json yet again
rkaraivanov Nov 20, 2025
538eeb8
chore: hecking ng-package convention
rkaraivanov Nov 20, 2025
6c1ec77
chore: path fixes
rkaraivanov Nov 20, 2025
4eefc55
Merge branch 'master' into rkaraivanov/wc-chat-wrapper
ChronosSF Nov 21, 2025
a4d36b8
Merge remote-tracking branch 'origin/master' into rkaraivanov/wc-chat…
rkaraivanov Nov 24, 2025
bafb381
Merge remote-tracking branch 'origin/master' into rkaraivanov/wc-chat…
rkaraivanov Nov 26, 2025
6a1a52c
fix: Removed inline styles from chat component
rkaraivanov Nov 26, 2025
efaa982
refactor: Minor improvements to chat component
rkaraivanov Nov 26, 2025
4a99550
fix: Added igniteui-webcomponents and markdown dependencies
rkaraivanov Nov 27, 2025
ead65cb
Merge remote-tracking branch 'origin/master' into rkaraivanov/wc-chat…
rkaraivanov Nov 27, 2025
3112005
chore: update dependency versions in dependency-handler.ts
rkaraivanov Nov 27, 2025
88f50cb
Merge remote-tracking branch 'origin/master' into rkaraivanov/wc-chat…
damyanpetev Nov 27, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
207 changes: 110 additions & 97 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,17 @@
"@igniteui/material-icons-extended": "^3.1.0",
"@lit-labs/ssr-dom-shim": "^1.3.0",
"@types/source-map": "0.5.2",
"dompurify": "^3.3.0",
"express": "^5.1.0",
"fflate": "^0.8.1",
"igniteui-theming": "^24.0.0",
"igniteui-trial-watermark": "^3.1.0",
"jspdf": "^3.0.4",
"lodash-es": "^4.17.21",
"marked": "^16.4.0",
"marked-shiki": "^1.2.1",
"rxjs": "^7.8.2",
"shiki": "^3.13.0",
"tslib": "^2.3.0",
"zone.js": "~0.15.0"
},
Expand Down Expand Up @@ -122,7 +126,7 @@
"ig-typedoc-theme": "^7.0.0",
"igniteui-dockmanager": "^1.17.0",
"igniteui-sassdoc-theme": "^2.1.0",
"igniteui-webcomponents": "6.2.1",
"igniteui-webcomponents": "^6.3.1",
"jasmine": "^5.6.0",
"jasmine-core": "^5.6.0",
"karma": "^6.4.4",
Expand Down
1 change: 1 addition & 0 deletions projects/igniteui-angular/chat-extras/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './src/public_api';
1 change: 1 addition & 0 deletions projects/igniteui-angular/chat-extras/ng-package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
57 changes: 57 additions & 0 deletions projects/igniteui-angular/chat-extras/src/markdown-pipe.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { DomSanitizer } from '@angular/platform-browser';
import { TestBed } from '@angular/core/testing';
import { IgxChatMarkdownService } from './markdown-service';
import { MarkdownPipe } from './markdown-pipe';
import Spy = jasmine.Spy;

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

class MockChatMarkdownService {
public async parse(_: string): Promise<string> {
return mockUnsafeHtml;
}
}

describe('MarkdownPipe', () => {
let pipe: MarkdownPipe;
let sanitizer: DomSanitizer;
let bypassSpy: Spy;

beforeEach(() => {
TestBed.configureTestingModule({
providers: [
MarkdownPipe,
{ provide: IgxChatMarkdownService, useClass: MockChatMarkdownService },
],
});

pipe = TestBed.inject(MarkdownPipe);
sanitizer = TestBed.inject(DomSanitizer);
bypassSpy = spyOn(sanitizer, 'bypassSecurityTrustHtml').and.callThrough();
});

it('should be created', () => {
expect(pipe).toBeTruthy();
});

it('should call the service, sanitize content, and return SafeHtml', async () => {
await pipe.transform('some markdown');

expect(bypassSpy).toHaveBeenCalledTimes(1);

const sanitizedString = bypassSpy.calls.mostRecent().args[0];

expect(sanitizedString).not.toContain('onerror');
expect(sanitizedString).toContain('style="color: var(--shiki-fg);"');
});

it('should handle undefined input text', async () => {
await pipe.transform(undefined);
expect(sanitizer.bypassSecurityTrustHtml).toHaveBeenCalled();
});
});
18 changes: 18 additions & 0 deletions projects/igniteui-angular/chat-extras/src/markdown-pipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import DOMPurify from 'dompurify';
import { inject, Pipe, type PipeTransform } from '@angular/core';
import { IgxChatMarkdownService } from './markdown-service';
import { DomSanitizer, type SafeHtml } from '@angular/platform-browser';


@Pipe({ name: 'fromMarkdown' })
export class MarkdownPipe implements PipeTransform {
private _service = inject(IgxChatMarkdownService);
private _sanitizer = inject(DomSanitizer);


public async transform(text?: string): Promise<SafeHtml> {
return this._sanitizer.bypassSecurityTrustHtml(DOMPurify.sanitize(
await this._service.parse(text ?? '')
));
}
}
41 changes: 41 additions & 0 deletions projects/igniteui-angular/chat-extras/src/markdown-service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { TestBed } from '@angular/core/testing';
import { IgxChatMarkdownService } from './markdown-service';

describe('IgxChatMarkdownService', () => {
let service: IgxChatMarkdownService;

beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(IgxChatMarkdownService);
});

it('should be created', () => {
expect(service).toBeTruthy();
});

it('should parse basic markdown to HTML', async () => {
const markdown = '**Hello** *World*';
const expectedHtml = '<p><strong>Hello</strong> <em>World</em></p>\n';

const result = await service.parse(markdown);
expect(result).toBe(expectedHtml);
});

it('should parse a code block with shiki highlighting', async () => {
const markdown = '```typescript\nconst x = 5;\n```';
const result = await service.parse(markdown);

expect(result).toContain('<pre class="shiki shiki-themes github-light github-dark"');
expect(result).toContain('const');
expect(result).toMatch(/--shiki-.*?/);
expect(result).toContain('code');
});

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

const result = await service.parse(markdown);
expect(result).toContain(expectedLink);
});
});
67 changes: 67 additions & 0 deletions projects/igniteui-angular/chat-extras/src/markdown-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { Injectable } from '@angular/core';
import { Marked } from 'marked';
import markedShiki from 'marked-shiki';
import { bundledThemes, createHighlighter } from 'shiki/bundle/web';


const DEFAULT_LANGUAGES = ['javascript', 'typescript', 'html', 'css'];
const DEFAULT_THEMES = {
light: 'github-light',
dark: 'github-dark'
};

@Injectable({ providedIn: 'root' })
export class IgxChatMarkdownService {

private _instance: Marked;
private _isInitialized: Promise<void>;

private _initializeMarked(): void {
this._instance = new Marked({
breaks: true,
gfm: true,
extensions: [
{
name: 'link',
renderer({ href, title, text }) {
return `<a href="${href}" target="_blank" rel="noopener noreferrer" ${title ? `title="${title}"` : ''}>${text}</a>`;
}
}
]
});
}

private async _initializeShiki(): Promise<void> {
const highlighter = await createHighlighter({
langs: DEFAULT_LANGUAGES,
themes: Object.keys(bundledThemes)
});

this._instance.use(
markedShiki({
highlight(code, lang, _) {
try {
return highlighter.codeToHtml(code, {
lang,
themes: DEFAULT_THEMES,
});

} catch {
return `<pre><code>${code}</code></pre>`;
}
}
})
);
}


constructor() {
this._initializeMarked();
this._isInitialized = this._initializeShiki();
}

public async parse(text: string): Promise<string> {
await this._isInitialized;
return await this._instance.parse(text);
}
}
1 change: 1 addition & 0 deletions projects/igniteui-angular/chat-extras/src/public_api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { MarkdownPipe } from './markdown-pipe';
1 change: 1 addition & 0 deletions projects/igniteui-angular/chat/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './src/public_api';
1 change: 1 addition & 0 deletions projects/igniteui-angular/chat/ng-package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
16 changes: 16 additions & 0 deletions projects/igniteui-angular/chat/src/chat.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<igc-chat
[messages]="messages()"
[draftMessage]="draftMessage()"
[options]="_mergedOptions()"
(igcMessageCreated)="messageCreated.emit($event.detail)"
(igcMessageReact)="messageReact.emit($event.detail)"
(igcAttachmentClick)="attachmentClick.emit($event.detail)"
(igcAttachmentDrag)="attachmentDrag.emit()"
(igcAttachmentDrop)="attachmentDrop.emit()"
(igcTypingChange)="typingChange.emit($event.detail)"
(igcInputFocus)="inputFocus.emit()"
(igcInputBlur)="inputBlur.emit()"
(igcInputChange)="inputChange.emit($event.detail)"
>
<ng-content></ng-content>
</igc-chat>
Loading
Loading