diff --git a/package-lock.json b/package-lock.json
index dbfffb32..a976f80e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -21,7 +21,7 @@
"@ctrl/ngx-emoji-mart": "^8.2.0",
"@floating-ui/dom": "^1.6.3",
"@ngx-translate/core": "^14.0.0",
- "@stream-io/stream-chat-css": "5.3.0",
+ "@stream-io/stream-chat-css": "5.6.1",
"@stream-io/transliterate": "^1.5.2",
"angular-mentions": "1.4.0",
"dayjs": "^1.11.10",
@@ -5427,9 +5427,10 @@
}
},
"node_modules/@stream-io/stream-chat-css": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/@stream-io/stream-chat-css/-/stream-chat-css-5.3.0.tgz",
- "integrity": "sha512-3jdDc8Q8zacLs25MmXhKv8hq8GnQ8v5E8o5i+oDOHotnRsXvDFQTixcJi7TC4heOQcVCqLoy5dOfdg9IZuyObw=="
+ "version": "5.6.1",
+ "resolved": "https://registry.npmjs.org/@stream-io/stream-chat-css/-/stream-chat-css-5.6.1.tgz",
+ "integrity": "sha512-Z5QPoy8koPA2+PTLTRHkhORLLdFs4aTmpdFVJC5Jh5b+wbsGhjbNUddAn2wi2Y6XXm27SA9Vc9iVUdnS6PNgMQ==",
+ "license": "MIT"
},
"node_modules/@stream-io/transliterate": {
"version": "1.5.2",
@@ -27794,9 +27795,9 @@
"dev": true
},
"@stream-io/stream-chat-css": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/@stream-io/stream-chat-css/-/stream-chat-css-5.3.0.tgz",
- "integrity": "sha512-3jdDc8Q8zacLs25MmXhKv8hq8GnQ8v5E8o5i+oDOHotnRsXvDFQTixcJi7TC4heOQcVCqLoy5dOfdg9IZuyObw=="
+ "version": "5.6.1",
+ "resolved": "https://registry.npmjs.org/@stream-io/stream-chat-css/-/stream-chat-css-5.6.1.tgz",
+ "integrity": "sha512-Z5QPoy8koPA2+PTLTRHkhORLLdFs4aTmpdFVJC5Jh5b+wbsGhjbNUddAn2wi2Y6XXm27SA9Vc9iVUdnS6PNgMQ=="
},
"@stream-io/transliterate": {
"version": "1.5.2",
diff --git a/package.json b/package.json
index b9de4600..236e7635 100644
--- a/package.json
+++ b/package.json
@@ -112,7 +112,7 @@
"@ctrl/ngx-emoji-mart": "^8.2.0",
"@floating-ui/dom": "^1.6.3",
"@ngx-translate/core": "^14.0.0",
- "@stream-io/stream-chat-css": "5.3.0",
+ "@stream-io/stream-chat-css": "5.6.1",
"@stream-io/transliterate": "^1.5.2",
"angular-mentions": "1.4.0",
"dayjs": "^1.11.10",
diff --git a/projects/customizations-example/src/app/app.component.html b/projects/customizations-example/src/app/app.component.html
index bbd6eab2..71c21500 100644
--- a/projects/customizations-example/src/app/app.component.html
+++ b/projects/customizations-example/src/app/app.component.html
@@ -262,3 +262,16 @@
Latest message text: {{ latestMessageText }}
Latest message id: {{ latestMessage?.id }}
+
+
+
+
diff --git a/projects/customizations-example/src/app/app.component.ts b/projects/customizations-example/src/app/app.component.ts
index 260ac4ab..670ce649 100644
--- a/projects/customizations-example/src/app/app.component.ts
+++ b/projects/customizations-example/src/app/app.component.ts
@@ -36,6 +36,7 @@ import {
MessageActionsService,
ChannelPreviewInfoContext,
MessageReactionsSelectorComponent,
+ MessageTextContext,
} from 'stream-chat-angular';
import { environment } from '../environments/environment';
@@ -95,6 +96,8 @@ export class AppComponent implements AfterViewInit {
private emptyMainMessageListTemplate!: TemplateRef;
@ViewChild('emptyThreadMessageList')
private emptyThreadMessageListTemplate!: TemplateRef;
+ @ViewChild('messageText')
+ messageTextTemplate!: TemplateRef;
constructor(
private chatService: ChatClientService,
@@ -189,6 +192,9 @@ export class AppComponent implements AfterViewInit {
this.customTemplatesService.emptyThreadMessageListPlaceholder$.next(
this.emptyThreadMessageListTemplate
);
+ this.customTemplatesService.messageTextTemplate$.next(
+ this.messageTextTemplate
+ );
}
inviteClicked(channel: Channel) {
diff --git a/projects/customizations-example/src/app/app.module.ts b/projects/customizations-example/src/app/app.module.ts
index b75a0407..ea6553f1 100644
--- a/projects/customizations-example/src/app/app.module.ts
+++ b/projects/customizations-example/src/app/app.module.ts
@@ -12,6 +12,7 @@ import { PickerModule } from '@ctrl/ngx-emoji-mart';
import { MessageActionComponent } from './message-action/message-action.component';
import { ThreadHeaderComponent } from './thread-header/thread-header.component';
import { IconComponent } from './icon/icon.component';
+import { MessageTextComponent } from './message-text/message-text.component';
@NgModule({
declarations: [
@@ -20,6 +21,7 @@ import { IconComponent } from './icon/icon.component';
MessageActionComponent,
ThreadHeaderComponent,
IconComponent,
+ MessageTextComponent,
],
imports: [
BrowserModule,
diff --git a/projects/customizations-example/src/app/message-text/message-text.component.html b/projects/customizations-example/src/app/message-text/message-text.component.html
new file mode 100644
index 00000000..2ea04512
--- /dev/null
+++ b/projects/customizations-example/src/app/message-text/message-text.component.html
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/projects/customizations-example/src/app/message-text/message-text.component.scss b/projects/customizations-example/src/app/message-text/message-text.component.scss
new file mode 100644
index 00000000..5fbf9df5
--- /dev/null
+++ b/projects/customizations-example/src/app/message-text/message-text.component.scss
@@ -0,0 +1,19 @@
+.message-text {
+ overflow: hidden;
+ display: -webkit-box;
+ -webkit-line-clamp: 5;
+ line-clamp: 5;
+ -webkit-box-orient: vertical;
+}
+
+.message-text.message-text-expanded {
+ display: block;
+}
+
+button {
+ display: none;
+
+ &.visible {
+ display: block;
+ }
+}
diff --git a/projects/customizations-example/src/app/message-text/message-text.component.ts b/projects/customizations-example/src/app/message-text/message-text.component.ts
new file mode 100644
index 00000000..e2406fb0
--- /dev/null
+++ b/projects/customizations-example/src/app/message-text/message-text.component.ts
@@ -0,0 +1,18 @@
+import { Component, Input } from '@angular/core';
+import { MessageResponseBase } from 'stream-chat';
+import { DefaultStreamChatGenerics, StreamMessage } from 'stream-chat-angular';
+
+@Component({
+ selector: 'app-message-text',
+ templateUrl: './message-text.component.html',
+ styleUrls: ['./message-text.component.scss'],
+})
+export class MessageTextComponent {
+ @Input() message:
+ | StreamMessage
+ | undefined
+ | MessageResponseBase;
+ @Input() isQuoted: boolean = false;
+ @Input() shouldTranslate: boolean = false;
+ isExpanded = false;
+}
diff --git a/projects/stream-chat-angular/src/lib/custom-templates.service.ts b/projects/stream-chat-angular/src/lib/custom-templates.service.ts
index b71e024d..18661998 100644
--- a/projects/stream-chat-angular/src/lib/custom-templates.service.ts
+++ b/projects/stream-chat-angular/src/lib/custom-templates.service.ts
@@ -26,6 +26,7 @@ import {
MessageContext,
MessageReactionsContext,
MessageReactionsSelectorContext,
+ MessageTextContext,
ModalContext,
NotificationContext,
ReadStatusContext,
@@ -43,7 +44,7 @@ import {
*
* For code examples to the different customizations see our [customizations example application](https://github.com/GetStream/stream-chat-angular/tree/master/projects/customizations-example), specifically the [AppComponent](https://github.com/GetStream/stream-chat-angular/tree/master/projects/customizations-example/src/app) (see [README](https://github.com/GetStream/stream-chat-angular/blob/master/README.md#customization-examples) for instructions on how to start the application).
*
- * You can find the type definitions of the context that is provided for each template [on GitHub](https://github.com/GetStream/stream-chat-angular/blob/master/projects/stream-chat-angu)
+ * You can find the type definitions of the context that is provided for each template [on GitHub](https://github.com/GetStream/stream-chat-angular/blob/master/projects/stream-chat-angular/src/lib/types.ts)
*/
@Injectable({
providedIn: 'root',
@@ -358,6 +359,12 @@ export class CustomTemplatesService<
customMessageMetadataInsideBubbleTemplate$ = new BehaviorSubject<
TemplateRef | undefined
>(undefined);
+ /**
+ * Template to display the text content inside the [message component](/chat/docs/sdk/angular/components/MessageComponent/). The default component is [stream-message-text](/chat/docs/sdk/angular/components/MessageTextComponent/)
+ */
+ messageTextTemplate$ = new BehaviorSubject<
+ TemplateRef | undefined
+ >(undefined);
constructor() {}
}
diff --git a/projects/stream-chat-angular/src/lib/message-input/message-input.component.html b/projects/stream-chat-angular/src/lib/message-input/message-input.component.html
index 784efc98..29d5296f 100644
--- a/projects/stream-chat-angular/src/lib/message-input/message-input.component.html
+++ b/projects/stream-chat-angular/src/lib/message-input/message-input.component.html
@@ -91,15 +91,28 @@
[attachments]="quotedMessageAttachments"
[messageId]="quotedMessage.id"
>
-
+
+
+
+
+
+
{
let nativeElement: HTMLElement;
@@ -131,6 +132,7 @@ describe('MessageInputComponent', () => {
AutocompleteTextareaComponent,
AttachmentListComponent,
AttachmentPreviewListComponent,
+ MessageTextComponent,
],
providers: [
{
@@ -783,10 +785,15 @@ describe('MessageInputComponent', () => {
expect(avatar.location).toBe('quoted-message-sender');
expect(avatar.user).toBe(message.user!);
expect(attachments.attachments).toEqual([{ id: '1' }]);
- expect(
- nativeElement.querySelector('[data-testid="quoted-message-text"]')
- ?.innerHTML
- ).toContain(message.text);
+
+ const textComponent = fixture.debugElement
+ .query(By.css(quotedMessageContainerSelector))
+ .query(By.directive(MessageTextComponent))
+ .componentInstance as MessageTextComponent;
+
+ expect(textComponent.message).toBe(message);
+ expect(textComponent.isQuoted).toBe(true);
+ expect(textComponent.shouldTranslate).toBe(true);
mockMessageToQuote$.next(undefined);
fixture.detectChanges();
diff --git a/projects/stream-chat-angular/src/lib/message-input/message-input.component.ts b/projects/stream-chat-angular/src/lib/message-input/message-input.component.ts
index f248720e..e17fcb05 100644
--- a/projects/stream-chat-angular/src/lib/message-input/message-input.component.ts
+++ b/projects/stream-chat-angular/src/lib/message-input/message-input.component.ts
@@ -34,6 +34,7 @@ import {
CustomAttachmentUploadContext,
DefaultStreamChatGenerics,
EmojiPickerContext,
+ MessageTextContext,
StreamMessage,
} from '../types';
import { MessageInputConfigService } from './message-input-config.service';
@@ -163,7 +164,7 @@ export class MessageInputComponent
private componentFactoryResolver: ComponentFactoryResolver,
private cdRef: ChangeDetectorRef,
private emojiInputService: EmojiInputService,
- private customTemplatesService: CustomTemplatesService,
+ readonly customTemplatesService: CustomTemplatesService,
private messageActionsService: MessageActionsService,
public readonly voiceRecorderService: VoiceRecorderService,
@Optional() public audioRecorder?: AudioRecorderService
@@ -494,6 +495,14 @@ export class MessageInputComponent
};
}
+ getQuotedMessageTextContext(): MessageTextContext {
+ return {
+ message: this.quotedMessage,
+ isQuoted: true,
+ shouldTranslate: true,
+ };
+ }
+
async startVoiceRecording() {
await this.audioRecorder?.start();
if (this.audioRecorder?.isRecording) {
diff --git a/projects/stream-chat-angular/src/lib/message-text/message-text.component.html b/projects/stream-chat-angular/src/lib/message-text/message-text.component.html
new file mode 100644
index 00000000..8c16c56c
--- /dev/null
+++ b/projects/stream-chat-angular/src/lib/message-text/message-text.component.html
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+ {{ content }}
+
+
+
+
+
+
+
+ {{ messageText || "" }}
+
+
+
+
diff --git a/projects/stream-chat-angular/src/lib/message-text/message-text.component.spec.ts b/projects/stream-chat-angular/src/lib/message-text/message-text.component.spec.ts
new file mode 100644
index 00000000..803990ee
--- /dev/null
+++ b/projects/stream-chat-angular/src/lib/message-text/message-text.component.spec.ts
@@ -0,0 +1,336 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { MessageTextComponent } from './message-text.component';
+import { StreamMessage } from '../types';
+import { SimpleChange } from '@angular/core';
+import { MessageService } from '../message.service';
+import { generateMockMessages } from '../mocks';
+
+describe('MessageTextComponent', () => {
+ let component: MessageTextComponent;
+ let fixture: ComponentFixture;
+ let nativeElement: HTMLElement;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [MessageTextComponent],
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(MessageTextComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ nativeElement = fixture.nativeElement;
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+
+ it('should display text', () => {
+ component.message = {
+ text: 'Hi',
+ } as StreamMessage;
+ component.ngOnChanges({ message: {} as SimpleChange });
+ fixture.detectChanges();
+
+ expect(
+ nativeElement.querySelector('[data-testid="text"]')?.textContent
+ ).toContain('Hi');
+ });
+
+ it('should add class to quoted message text', () => {
+ component.message = {
+ text: 'Hi',
+ translation: 'Szia',
+ } as StreamMessage;
+ component.isQuoted = true;
+ component.ngOnChanges({ isQuoted: {} as SimpleChange });
+ fixture.detectChanges();
+
+ expect(
+ nativeElement.querySelector('.str-chat__quoted-message-text-value')
+ ).not.toBeNull();
+
+ component.isQuoted = false;
+ component.ngOnChanges({ isQuoted: {} as SimpleChange });
+ fixture.detectChanges();
+
+ expect(
+ nativeElement.querySelector('.str-chat__quoted-message-text-value')
+ ).toBeNull();
+ });
+
+ it('should translate, if #shouldTranslate is true', () => {
+ component.message = {
+ text: 'Hi',
+ translation: 'Szia',
+ } as StreamMessage;
+ component.shouldTranslate = true;
+ component.ngOnChanges({ message: {} as SimpleChange });
+ fixture.detectChanges();
+
+ expect(
+ nativeElement.querySelector('[data-testid="text"]')?.textContent
+ ).toContain('Szia');
+ });
+
+ it('should fallback to original text, if #shouldTranslate is true but no translation is provided', () => {
+ component.message = {
+ text: 'Hi',
+ translation: '',
+ } as StreamMessage;
+ component.shouldTranslate = true;
+ component.ngOnChanges({ message: {} as SimpleChange });
+ fixture.detectChanges();
+
+ expect(
+ nativeElement.querySelector('[data-testid="text"]')?.textContent
+ ).toContain('Hi');
+ });
+
+ it(`shouldn't display empty text`, () => {
+ component.message = {
+ text: '',
+ } as StreamMessage;
+ component.ngOnChanges({ message: {} as SimpleChange });
+ fixture.detectChanges();
+
+ expect(nativeElement.querySelector('[data-testid="text"]')).toBeNull();
+ });
+
+ it('should create message parts', () => {
+ component.message = {
+ text: '',
+ } as StreamMessage;
+ component.ngOnChanges({ message: {} as SimpleChange });
+
+ expect(component.messageTextParts).toEqual(undefined);
+ expect(component.messageText).toEqual('');
+
+ component.message = {
+ text: 'This is a message without user mentions',
+ } as StreamMessage;
+ component.ngOnChanges({ message: {} as SimpleChange });
+
+ expect(component.messageTextParts).toEqual(undefined);
+ expect(component.messageText).toEqual(
+ 'This is a message without user mentions'
+ );
+
+ component.message = {
+ text: 'This is just an email, not a mention test@test.com',
+ } as StreamMessage;
+ component.ngOnChanges({ message: {} as SimpleChange });
+
+ expect(component.messageTextParts).toEqual(undefined);
+ expect(component.messageText).toEqual(
+ 'This is just an email, not a mention test@test.com'
+ );
+
+ component.message = {
+ text: 'This is just an email, not a mention test@test.com',
+ } as StreamMessage;
+ component.ngOnChanges({ message: {} as SimpleChange });
+
+ expect(component.messageTextParts).toEqual(undefined);
+ expect(component.messageText).toEqual(
+ 'This is just an email, not a mention test@test.com'
+ );
+
+ component.message = {
+ text: 'Hello @Jack',
+ mentioned_users: [{ id: 'jack', name: 'Jack' }],
+ } as StreamMessage;
+ component.ngOnChanges({ message: {} as SimpleChange });
+
+ expect(component.messageTextParts).toEqual([
+ { content: 'Hello ', type: 'text' },
+ {
+ content: '@Jack',
+ type: 'mention',
+ user: { id: 'jack', name: 'Jack' },
+ },
+ ]);
+
+ component.message = {
+ text: 'Hello @Jack, how are you?',
+ mentioned_users: [{ id: 'jack', name: 'Jack' }],
+ } as StreamMessage;
+ component.ngOnChanges({ message: {} as SimpleChange });
+
+ expect(component.messageTextParts).toEqual([
+ { content: 'Hello ', type: 'text' },
+ {
+ content: '@Jack',
+ type: 'mention',
+ user: { id: 'jack', name: 'Jack' },
+ },
+ { content: ', how are you?', type: 'text' },
+ ]);
+
+ component.message = {
+ text: 'Hello @Jack and @Lucie, how are you?',
+ mentioned_users: [
+ { id: 'id2334', name: 'Jack' },
+ { id: 'id3444', name: 'Lucie' },
+ ],
+ } as StreamMessage;
+ component.ngOnChanges({ message: {} as SimpleChange });
+
+ expect(component.messageTextParts).toEqual([
+ { content: 'Hello ', type: 'text' },
+ {
+ content: '@Jack',
+ type: 'mention',
+ user: { id: 'id2334', name: 'Jack' },
+ },
+ { content: ' and ', type: 'text' },
+ {
+ content: '@Lucie',
+ type: 'mention',
+ user: { id: 'id3444', name: 'Lucie' },
+ },
+ { content: ', how are you?', type: 'text' },
+ ]);
+
+ component.message = {
+ text: 'https://getstream.io/ this is the link @sara',
+ mentioned_users: [{ id: 'sara' }],
+ } as StreamMessage;
+ component.ngOnChanges({ message: {} as SimpleChange });
+
+ expect(component.messageTextParts).toEqual([
+ {
+ content:
+ 'https://getstream.io/ this is the link ',
+ type: 'text',
+ },
+ { content: '@sara', type: 'mention', user: { id: 'sara' } },
+ ]);
+
+ component.message = {
+ text: `This is a message with lots of emojis: 😂😜😂😂, there are compound emojis as well 👨👩👧`,
+ html: `This is a message with lots of emojis: 😂😜😂😂, there are compound emojis as well 👨👩👧`,
+ mentioned_users: [],
+ } as any as StreamMessage;
+ component.ngOnChanges({ message: {} as SimpleChange });
+
+ const content = component.messageTextParts![0].content;
+
+ expect(content).toContain('😂');
+ expect(content).toContain('😜');
+ expect(content).toContain('👨👩👧');
+ });
+
+ it('should add class to emojis in Chrome', () => {
+ const chrome = (window as typeof window & { chrome: Object }).chrome;
+ (window as typeof window & { chrome: Object }).chrome =
+ 'the component now will think that this is a chrome browser';
+ component.message = {
+ text: 'This message contains an emoji 🥑',
+ html: 'This message contains an emoji 🥑',
+ } as any as StreamMessage;
+ component.ngOnChanges({ message: {} as SimpleChange });
+
+ expect(component.messageTextParts![0].content).toContain(
+ 'class="str-chat__emoji-display-fix"'
+ );
+
+ component.message = {
+ text: '@sara what do you think about 🥑s? ',
+ html: '@sara what do you think about 🥑s? ',
+ mentioned_users: [{ id: 'sara' }],
+ } as StreamMessage;
+ component.ngOnChanges({ message: {} as SimpleChange });
+
+ expect(component.messageTextParts![2].content).toContain(
+ 'class="str-chat__emoji-display-fix"'
+ );
+
+ // Simulate a browser that isn't Google Chrome
+ (window as typeof window & { chrome: Object | undefined }).chrome =
+ undefined;
+
+ component.ngOnChanges({ message: {} as SimpleChange });
+
+ expect(component.messageTextParts![0].content).not.toContain(
+ 'class="str-chat__emoji-display-fix"'
+ );
+
+ // Revert changes to the window object
+ (window as typeof window & { chrome: Object }).chrome = chrome;
+ });
+
+ it('should replace URL links inside text content', () => {
+ component.message = {
+ html: 'This is a message with a link https://getstream.io/
\n',
+ text: 'This is a message with a link https://getstream.io/',
+ } as any as StreamMessage;
+ component.ngOnChanges({ message: {} as SimpleChange });
+
+ expect(component.messageTextParts![0].content).toContain(
+ ' https://getstream.io/'
+ );
+
+ component.message.html = undefined;
+ component.ngOnChanges({ message: {} as SimpleChange });
+
+ expect(component.messageTextParts![0].content).toContain(
+ 'https://getstream.io/'
+ );
+
+ component.message.text = 'This is a message with a link google.com';
+ component.ngOnChanges({ message: {} as SimpleChange });
+
+ expect(component.messageTextParts![0].content).toContain(
+ 'google.com'
+ );
+
+ component.message.text = 'This is a message with a link www.google.com';
+ component.ngOnChanges({ message: {} as SimpleChange });
+
+ expect(component.messageTextParts![0].content).toContain(
+ 'www.google.com'
+ );
+
+ component.message.text =
+ 'This is a message with a link file:///C:/Users/YourName/Documents/example.txt';
+ component.ngOnChanges({ message: {} as SimpleChange });
+
+ expect(component.messageTextParts![0].content).toContain(
+ 'file:///C:/Users/YourName/Documents/example.txt'
+ );
+ });
+
+ it('should replace URL links inside text content - custom link renderer', () => {
+ const service = TestBed.inject(MessageService);
+ service.customLinkRenderer = (url) =>
+ `${url}`;
+ component.message = {
+ html: 'This is a message with a link https://getstream.io/
\n',
+ text: 'This is a message with a link https://getstream.io/',
+ } as any as StreamMessage;
+ component.ngOnChanges({ message: {} as SimpleChange });
+
+ expect(component.messageTextParts![0].content).toContain(
+ ' https://getstream.io/'
+ );
+ });
+
+ it('should respect #displayAs setting', () => {
+ component.message = generateMockMessages()[0];
+ fixture.detectChanges();
+
+ expect(nativeElement.querySelector('[data-testid="html-content"]')).toBe(
+ null
+ );
+
+ component.displayAs = 'html';
+ component.ngOnChanges({ message: {} as SimpleChange });
+ fixture.detectChanges();
+
+ expect(
+ nativeElement.querySelector('[data-testid="html-content"]')
+ ).not.toBe(null);
+ });
+});
diff --git a/projects/stream-chat-angular/src/lib/message-text/message-text.component.ts b/projects/stream-chat-angular/src/lib/message-text/message-text.component.ts
new file mode 100644
index 00000000..e6e862c4
--- /dev/null
+++ b/projects/stream-chat-angular/src/lib/message-text/message-text.component.ts
@@ -0,0 +1,172 @@
+import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
+import {
+ DefaultStreamChatGenerics,
+ MentionTemplateContext,
+ StreamMessage,
+} from '../types';
+import { MessageResponseBase, UserResponse } from 'stream-chat';
+import emojiRegex from 'emoji-regex';
+import { MessageService } from '../message.service';
+import { CustomTemplatesService } from '../custom-templates.service';
+
+type MessagePart = {
+ content: string;
+ type: 'text' | 'mention';
+ user?: UserResponse;
+};
+
+/**
+ * The `MessageTextComponent` displays the text content of a message.
+ */
+@Component({
+ selector: 'stream-message-text',
+ templateUrl: './message-text.component.html',
+ styles: [],
+})
+export class MessageTextComponent implements OnChanges {
+ /**
+ * The message which text should be displayed
+ */
+ @Input() message:
+ | StreamMessage
+ | undefined
+ | MessageResponseBase;
+ /**
+ * `true` if the component displayes a message quote
+ */
+ @Input() isQuoted: boolean = false;
+ /**
+ * `true` if the
+ */
+ @Input() shouldTranslate: boolean = false;
+ messageTextParts: MessagePart[] | undefined = [];
+ messageText?: string;
+ displayAs: 'text' | 'html';
+ private readonly urlRegexp =
+ /(?:(?:https?|ftp|file):\/\/|www\.|ftp\.|(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,})(?![^\s]*@[^\s]*)(?:[^\s()<>]+|\([\w\d]+\))*(? {
+ const mention = `@${user.name || user.id}`;
+ const precedingText = text.substring(0, text.indexOf(mention));
+ let formattedPrecedingText = this.fixEmojiDisplay(precedingText);
+ formattedPrecedingText = this.wrapLinksWithAnchorTag(
+ formattedPrecedingText
+ );
+ this.messageTextParts!.push({
+ content: formattedPrecedingText,
+ type: 'text',
+ });
+ this.messageTextParts!.push({
+ content: mention,
+ type: 'mention',
+ user,
+ });
+ text = text.replace(precedingText + mention, '');
+ });
+ if (text) {
+ text = this.fixEmojiDisplay(text);
+ text = this.wrapLinksWithAnchorTag(text);
+ this.messageTextParts.push({ content: text, type: 'text' });
+ }
+ }
+ }
+
+ private getMessageContent() {
+ const originalContent = this.message?.text;
+ if (this.shouldTranslate) {
+ const translation = this.message?.translation;
+ return translation || originalContent;
+ } else {
+ return originalContent;
+ }
+ }
+
+ private fixEmojiDisplay(content: string) {
+ // Wrap emojis in span to display emojis correctly in Chrome https://bugs.chromium.org/p/chromium/issues/detail?id=596223
+ // Based on this: https://stackoverflow.com/questions/4565112/javascript-how-to-find-out-if-the-user-browser-is-chrome
+ /* eslint-disable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any */
+ const isChrome =
+ !!(window as any).chrome && typeof (window as any).opr === 'undefined';
+ /* eslint-enable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any */
+ content = content.replace(
+ this.emojiRegexp,
+ (match) =>
+ `${match}`
+ );
+
+ return content;
+ }
+
+ private wrapLinksWithAnchorTag(content: string) {
+ if (this.displayAs === 'html') {
+ return content;
+ }
+ content = content.replace(this.urlRegexp, (match) => {
+ if (this.messageService.customLinkRenderer) {
+ return this.messageService.customLinkRenderer(match);
+ } else {
+ let href = match;
+ if (
+ !href.startsWith('http') &&
+ !href.startsWith('ftp') &&
+ !href.startsWith('file')
+ ) {
+ href = `https://${match}`;
+ }
+ return `${match}`;
+ }
+ });
+
+ return content;
+ }
+}
diff --git a/projects/stream-chat-angular/src/lib/message/message.component.html b/projects/stream-chat-angular/src/lib/message/message.component.html
index da296210..27aa9adb 100644
--- a/projects/stream-chat-angular/src/lib/message/message.component.html
+++ b/projects/stream-chat-angular/src/lib/message/message.component.html
@@ -213,44 +213,26 @@
) | translate
}}
-
-
-
-
-
-
-
-
- {{
- content
- }}
-
-
-
-
-
-
-
- {{ messageText || "" }}
-
-
-
-
-
+
+
+
+
-
+
+
+
+
+
+
diff --git a/projects/stream-chat-angular/src/lib/message/message.component.spec.ts b/projects/stream-chat-angular/src/lib/message/message.component.spec.ts
index 61af282c..f9ea11b9 100644
--- a/projects/stream-chat-angular/src/lib/message/message.component.spec.ts
+++ b/projects/stream-chat-angular/src/lib/message/message.component.spec.ts
@@ -14,7 +14,7 @@ import { ChatClientService } from '../chat-client.service';
import { IconComponent } from '../icon/icon.component';
import { MessageActionsBoxComponent } from '../message-actions-box/message-actions-box.component';
import { By } from '@angular/platform-browser';
-import { generateMockMessages, mockCurrentUser, mockMessage } from '../mocks';
+import { mockCurrentUser, mockMessage } from '../mocks';
import { AttachmentListComponent } from '../attachment-list/attachment-list.component';
import { MessageReactionsComponent } from '../message-reactions/message-reactions.component';
import { TranslateModule } from '@ngx-translate/core';
@@ -23,8 +23,8 @@ import { ChangeDetectionStrategy, SimpleChange } from '@angular/core';
import { AvatarPlaceholderComponent } from '../avatar-placeholder/avatar-placeholder.component';
import { BehaviorSubject, of } from 'rxjs';
import { MessageActionsService } from '../message-actions.service';
-import { MessageService } from '../message.service';
import { NgxFloatUiModule } from 'ngx-float-ui';
+import { MessageTextComponent } from '../message-text/message-text.component';
describe('MessageComponent', () => {
let component: MessageComponent;
@@ -39,7 +39,7 @@ describe('MessageComponent', () => {
let queryDeliveredIndicator: () => HTMLElement | null;
let queryReadIndicator: () => HTMLElement | null;
let queryAvatar: () => AvatarPlaceholderComponent;
- let queryText: () => HTMLElement | null;
+ let queryMessageTextComponent: (testId: string) => MessageTextComponent;
let queryMessageActionsBoxComponent: () =>
| MessageActionsBoxComponent
| undefined;
@@ -85,6 +85,7 @@ describe('MessageComponent', () => {
AttachmentListComponent,
MessageReactionsComponent,
AvatarPlaceholderComponent,
+ MessageTextComponent,
],
providers: [
{
@@ -130,7 +131,9 @@ describe('MessageComponent', () => {
queryAvatar = () =>
fixture.debugElement.query(By.css('[data-testid="avatar"]'))
?.componentInstance as AvatarPlaceholderComponent;
- queryText = () => nativeElement.querySelector('[data-testid="text"]');
+ queryMessageTextComponent = (testId: string) =>
+ fixture.debugElement.query(By.css(`[data-testid="${testId}"]`))
+ ?.componentInstance as MessageTextComponent;
queryMessageInner = () =>
nativeElement.querySelector('[data-testid="inner-message"]');
queryLoadingIndicator = () =>
@@ -304,7 +307,13 @@ describe('MessageComponent', () => {
});
it('should display text message', () => {
- expect(queryText()?.textContent).toContain(message.text);
+ const messageTextComponent = queryMessageTextComponent('text');
+
+ expect(messageTextComponent.message).toBe(component.message);
+ expect(messageTextComponent.shouldTranslate).toBe(
+ component.displayedMessageTextContent === 'translation'
+ );
+ expect(messageTextComponent.isQuoted).toBe(false);
});
describe('should display error message', () => {
@@ -680,14 +689,6 @@ describe('MessageComponent', () => {
);
});
- it(`shouldn't display empty text`, () => {
- component.message = { ...component.message!, ...{ text: '' } };
- component.ngOnChanges({ message: {} as SimpleChange });
- fixture.detectChanges();
-
- expect(queryText()).toBeNull();
- });
-
it('should resend message, if sending is failed', () => {
component.message = { ...component.message!, status: 'failed' };
component.ngOnChanges({ message: {} as SimpleChange });
@@ -750,210 +751,6 @@ describe('MessageComponent', () => {
expect(systemMessage?.innerHTML).toContain(message.text);
});
- it('should create message parts', () => {
- component.message = {
- text: '',
- } as StreamMessage;
- component.ngOnChanges({ message: {} as SimpleChange });
-
- expect(component.messageTextParts).toEqual(undefined);
- expect(component.messageText).toEqual('');
-
- component.message = {
- text: 'This is a message without user mentions',
- } as StreamMessage;
- component.ngOnChanges({ message: {} as SimpleChange });
-
- expect(component.messageTextParts).toEqual(undefined);
- expect(component.messageText).toEqual(
- 'This is a message without user mentions'
- );
-
- component.message = {
- text: 'This is just an email, not a mention test@test.com',
- } as StreamMessage;
- component.ngOnChanges({ message: {} as SimpleChange });
-
- expect(component.messageTextParts).toEqual(undefined);
- expect(component.messageText).toEqual(
- 'This is just an email, not a mention test@test.com'
- );
-
- component.message = {
- text: 'This is just an email, not a mention test@test.com',
- } as StreamMessage;
- component.ngOnChanges({ message: {} as SimpleChange });
-
- expect(component.messageTextParts).toEqual(undefined);
- expect(component.messageText).toEqual(
- 'This is just an email, not a mention test@test.com'
- );
-
- component.message = {
- text: 'Hello @Jack',
- mentioned_users: [{ id: 'jack', name: 'Jack' }],
- } as StreamMessage;
- component.ngOnChanges({ message: {} as SimpleChange });
-
- expect(component.messageTextParts).toEqual([
- { content: 'Hello ', type: 'text' },
- {
- content: '@Jack',
- type: 'mention',
- user: { id: 'jack', name: 'Jack' },
- },
- ]);
-
- component.message = {
- text: 'Hello @Jack, how are you?',
- mentioned_users: [{ id: 'jack', name: 'Jack' }],
- } as StreamMessage;
- component.ngOnChanges({ message: {} as SimpleChange });
-
- expect(component.messageTextParts).toEqual([
- { content: 'Hello ', type: 'text' },
- {
- content: '@Jack',
- type: 'mention',
- user: { id: 'jack', name: 'Jack' },
- },
- { content: ', how are you?', type: 'text' },
- ]);
-
- component.message = {
- text: 'Hello @Jack and @Lucie, how are you?',
- mentioned_users: [
- { id: 'id2334', name: 'Jack' },
- { id: 'id3444', name: 'Lucie' },
- ],
- } as StreamMessage;
- component.ngOnChanges({ message: {} as SimpleChange });
-
- expect(component.messageTextParts).toEqual([
- { content: 'Hello ', type: 'text' },
- {
- content: '@Jack',
- type: 'mention',
- user: { id: 'id2334', name: 'Jack' },
- },
- { content: ' and ', type: 'text' },
- {
- content: '@Lucie',
- type: 'mention',
- user: { id: 'id3444', name: 'Lucie' },
- },
- { content: ', how are you?', type: 'text' },
- ]);
-
- component.message = {
- text: 'https://getstream.io/ this is the link @sara',
- mentioned_users: [{ id: 'sara' }],
- } as StreamMessage;
- component.ngOnChanges({ message: {} as SimpleChange });
-
- expect(component.messageTextParts).toEqual([
- {
- content:
- 'https://getstream.io/ this is the link ',
- type: 'text',
- },
- { content: '@sara', type: 'mention', user: { id: 'sara' } },
- ]);
-
- component.message = {
- text: `This is a message with lots of emojis: 😂😜😂😂, there are compound emojis as well 👨👩👧`,
- html: `This is a message with lots of emojis: 😂😜😂😂, there are compound emojis as well 👨👩👧`,
- mentioned_users: [],
- } as any as StreamMessage;
- component.ngOnChanges({ message: {} as SimpleChange });
-
- const content = component.messageTextParts![0].content;
-
- expect(content).toContain('😂');
- expect(content).toContain('😜');
- expect(content).toContain('👨👩👧');
- });
-
- it('should add class to emojis in Chrome', () => {
- const chrome = (window as typeof window & { chrome: Object }).chrome;
- (window as typeof window & { chrome: Object }).chrome =
- 'the component now will think that this is a chrome browser';
- component.message = {
- text: 'This message contains an emoji 🥑',
- html: 'This message contains an emoji 🥑',
- } as any as StreamMessage;
- component.ngOnChanges({ message: {} as SimpleChange });
-
- expect(component.messageTextParts![0].content).toContain(
- 'class="str-chat__emoji-display-fix"'
- );
-
- component.message = {
- text: '@sara what do you think about 🥑s? ',
- html: '@sara what do you think about 🥑s? ',
- mentioned_users: [{ id: 'sara' }],
- } as StreamMessage;
- component.ngOnChanges({ message: {} as SimpleChange });
-
- expect(component.messageTextParts![2].content).toContain(
- 'class="str-chat__emoji-display-fix"'
- );
-
- // Simulate a browser that isn't Google Chrome
- (window as typeof window & { chrome: Object | undefined }).chrome =
- undefined;
-
- component.ngOnChanges({ message: {} as SimpleChange });
-
- expect(component.messageTextParts![0].content).not.toContain(
- 'class="str-chat__emoji-display-fix"'
- );
-
- // Revert changes to the window object
- (window as typeof window & { chrome: Object }).chrome = chrome;
- });
-
- it('should replace URL links inside text content', () => {
- component.message = {
- html: 'This is a message with a link https://getstream.io/
\n',
- text: 'This is a message with a link https://getstream.io/',
- } as any as StreamMessage;
- component.ngOnChanges({ message: {} as SimpleChange });
-
- expect(component.messageTextParts![0].content).toContain(
- ' https://getstream.io/'
- );
-
- component.message.html = undefined;
- component.ngOnChanges({ message: {} as SimpleChange });
-
- expect(component.messageTextParts![0].content).toContain(
- 'https://getstream.io/'
- );
-
- component.message.text = 'This is a message with a link google.com';
- component.ngOnChanges({ message: {} as SimpleChange });
-
- expect(component.messageTextParts![0].content).toContain(
- 'google.com'
- );
-
- component.message.text = 'This is a message with a link www.google.com';
- component.ngOnChanges({ message: {} as SimpleChange });
-
- expect(component.messageTextParts![0].content).toContain(
- 'www.google.com'
- );
-
- component.message.text =
- 'This is a message with a link file:///C:/Users/YourName/Documents/example.txt';
- component.ngOnChanges({ message: {} as SimpleChange });
-
- expect(component.messageTextParts![0].content).toContain(
- 'file:///C:/Users/YourName/Documents/example.txt'
- );
- });
-
it('should display reply count for parent messages', () => {
expect(queryReplyCountButton()).toBeNull();
@@ -1165,80 +962,44 @@ describe('MessageComponent', () => {
);
});
- it(`should display message translation if exists`, () => {
- component.message!.user!.id += 'not';
- component.message!.html = 'How are you?
';
- component.message!.translation = 'Hogy vagy?';
- component.ngOnChanges({ message: {} as SimpleChange });
- fixture.detectChanges();
-
- expect(queryText()?.innerHTML).toContain('Hogy vagy?');
- });
-
it('should display translation notifce and user should be able to change between translation and original', () => {
- component.message!.user!.id = component.message!.user!.id + 'not';
- component.message!.text = 'How are you?';
- component.message!.html = 'How are you?
';
component.message!.translation = 'Hogy vagy?';
component.ngOnChanges({ message: {} as SimpleChange });
fixture.detectChanges();
expect(queryTranslationNotice()).not.toBeNull();
expect(querySeeTranslationButton()).toBeNull();
+ expect(queryMessageTextComponent('text').shouldTranslate).toBeTrue();
+ expect(
+ queryMessageTextComponent('quoted-message-text').shouldTranslate
+ ).toBeTrue();
querySeeOriginalButton()?.click();
fixture.detectChanges();
- expect(queryText()?.innerHTML).toContain('How are you?');
+ expect(queryMessageTextComponent('text').shouldTranslate).toBeFalse();
+ expect(
+ queryMessageTextComponent('quoted-message-text').shouldTranslate
+ ).toBeFalse();
expect(querySeeOriginalButton()).toBeNull();
querySeeTranslationButton()?.click();
fixture.detectChanges();
- expect(queryText()?.innerHTML).toContain('Hogy vagy?');
+ expect(queryMessageTextComponent('text').shouldTranslate).toBeTrue();
+ expect(
+ queryMessageTextComponent('quoted-message-text').shouldTranslate
+ ).toBeTrue();
});
it(`shouldn't display translation notice if message isn't translated`, () => {
- component.message!.html = 'How are you?
';
- component.message!.user = currentUser;
component.message!.translation = undefined;
component.ngOnChanges({ message: {} as SimpleChange });
fixture.detectChanges();
expect(queryTranslationNotice()).toBeNull();
});
-
- it('should respect #displayAs setting', () => {
- component.message = generateMockMessages()[0];
- fixture.detectChanges();
-
- expect(nativeElement.querySelector('[data-testid="html-content"]')).toBe(
- null
- );
-
- component.displayAs = 'html';
- fixture.detectChanges();
-
- expect(
- nativeElement.querySelector('[data-testid="html-content"]')
- ).not.toBe(null);
- });
- });
-
- it('should replace URL links inside text content - custom link renderer', () => {
- const service = TestBed.inject(MessageService);
- service.customLinkRenderer = (url) =>
- `${url}`;
- component.message = {
- html: 'This is a message with a link https://getstream.io/
\n',
- text: 'This is a message with a link https://getstream.io/',
- } as any as StreamMessage;
- component.ngOnChanges({ message: {} as SimpleChange });
-
- expect(component.messageTextParts![0].content).toContain(
- ' https://getstream.io/'
- );
});
it(`shouldn't display edited flag if message wasn't edited`, () => {
diff --git a/projects/stream-chat-angular/src/lib/message/message.component.ts b/projects/stream-chat-angular/src/lib/message/message.component.ts
index f832dabc..7d10dfbe 100644
--- a/projects/stream-chat-angular/src/lib/message/message.component.ts
+++ b/projects/stream-chat-angular/src/lib/message/message.component.ts
@@ -17,7 +17,6 @@ import { ChannelService } from '../channel.service';
import { ChatClientService } from '../chat-client.service';
import {
AttachmentListContext,
- MentionTemplateContext,
MessageActionsBoxContext,
MessageReactionsContext,
DefaultStreamChatGenerics,
@@ -27,13 +26,12 @@ import {
ReadStatusContext,
SystemMessageContext,
CustomMetadataContext,
+ MessageTextContext,
} from '../types';
-import emojiRegex from 'emoji-regex';
import { Observable, Subscription, take } from 'rxjs';
import { CustomTemplatesService } from '../custom-templates.service';
import { listUsers } from '../list-users';
import { DateParserService } from '../date-parser.service';
-import { MessageService } from '../message.service';
import { MessageActionsService } from '../message-actions.service';
import {
NgxFloatUiContentComponent,
@@ -41,12 +39,6 @@ import {
} from 'ngx-float-ui';
import { TranslateService } from '@ngx-translate/core';
-type MessagePart = {
- content: string;
- type: 'text' | 'mention';
- user?: UserResponse;
-};
-
/**
* The `Message` component displays a message with additional information such as sender and date, and enables [interaction with the message (i.e. edit or react)](/chat/docs/sdk/angular/concepts/message-interactions/).
*/
@@ -86,15 +78,12 @@ export class MessageComponent
canReceiveReadEvents: boolean | undefined;
canReactToMessage: boolean | undefined;
isEditedFlagOpened = false;
- messageTextParts: MessagePart[] | undefined = [];
- messageText?: string;
shouldDisplayTranslationNotice = false;
displayedMessageTextContent: 'original' | 'translation' = 'original';
imageAttachmentModalState: 'opened' | 'closed' = 'closed';
shouldDisplayThreadLink = false;
isSentByCurrentUser = false;
readByText = '';
- displayAs: 'text' | 'html';
lastReadUser: UserResponse | undefined = undefined;
isOnlyReadByMe = false;
isReadByMultipleUsers = false;
@@ -114,9 +103,6 @@ export class MessageComponent
private subscriptions: Subscription[] = [];
private isViewInited = false;
private userId?: string;
- private readonly urlRegexp =
- /(?:(?:https?|ftp|file):\/\/|www\.|ftp\.|(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,})(?![^\s]*@[^\s]*)(?:[^\s()<>]+|\([\w\d]+\))*(? {
- const mention = `@${user.name || user.id}`;
- const precedingText = text.substring(0, text.indexOf(mention));
- let formattedPrecedingText = this.fixEmojiDisplay(precedingText);
- formattedPrecedingText = this.wrapLinksWithAnchorTag(
- formattedPrecedingText
- );
- this.messageTextParts!.push({
- content: formattedPrecedingText,
- type: 'text',
- });
- this.messageTextParts!.push({
- content: mention,
- type: 'mention',
- user,
- });
- text = text.replace(precedingText + mention, '');
- });
- if (text) {
- text = this.fixEmojiDisplay(text);
- text = this.wrapLinksWithAnchorTag(text);
- this.messageTextParts.push({ content: text, type: 'text' });
- }
- }
- }
-
- private getMessageContent(shouldTranslate: boolean) {
- const originalContent = this.message?.text;
- if (shouldTranslate) {
- const translation = this.message?.translation;
- if (translation) {
- this.shouldDisplayTranslationNotice = true;
- this.displayedMessageTextContent = 'translation';
- }
- return translation || originalContent;
- } else {
- this.displayedMessageTextContent = 'original';
- return originalContent;
- }
- }
-
- private fixEmojiDisplay(content: string) {
- // Wrap emojis in span to display emojis correctly in Chrome https://bugs.chromium.org/p/chromium/issues/detail?id=596223
- // Based on this: https://stackoverflow.com/questions/4565112/javascript-how-to-find-out-if-the-user-browser-is-chrome
- /* eslint-disable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any */
- const isChrome =
- !!(window as any).chrome && typeof (window as any).opr === 'undefined';
- /* eslint-enable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any */
- content = content.replace(
- this.emojiRegexp,
- (match) =>
- `${match}`
- );
-
- return content;
+ displayTranslatedMessage() {
+ this.shouldDisplayTranslationNotice = true;
+ this.displayedMessageTextContent = 'translation';
}
- private wrapLinksWithAnchorTag(content: string) {
- if (this.displayAs === 'html') {
- return content;
- }
- content = content.replace(this.urlRegexp, (match) => {
- if (this.messageService.customLinkRenderer) {
- return this.messageService.customLinkRenderer(match);
- } else {
- let href = match;
- if (
- !href.startsWith('http') &&
- !href.startsWith('ftp') &&
- !href.startsWith('file')
- ) {
- href = `https://${match}`;
- }
- return `${match}`;
- }
- });
-
- return content;
+ displayOriginalMessage() {
+ this.displayedMessageTextContent = 'original';
}
private updateReadByText() {
diff --git a/projects/stream-chat-angular/src/lib/stream-chat.module.ts b/projects/stream-chat-angular/src/lib/stream-chat.module.ts
index 9b487fd3..23d1ed21 100644
--- a/projects/stream-chat-angular/src/lib/stream-chat.module.ts
+++ b/projects/stream-chat-angular/src/lib/stream-chat.module.ts
@@ -26,6 +26,7 @@ import { UserListComponent } from './user-list/user-list.component';
import { VoiceRecordingModule } from './voice-recording/voice-recording.module';
import { IconModule } from './icon/icon.module';
import { VoiceRecorderService } from './message-input/voice-recorder.service';
+import { MessageTextComponent } from './message-text/message-text.component';
@NgModule({
declarations: [
@@ -49,6 +50,7 @@ import { VoiceRecorderService } from './message-input/voice-recorder.service';
MessageReactionsSelectorComponent,
UserListComponent,
PaginatedListComponent,
+ MessageTextComponent,
],
imports: [
CommonModule,
@@ -81,6 +83,7 @@ import { VoiceRecorderService } from './message-input/voice-recorder.service';
UserListComponent,
PaginatedListComponent,
IconModule,
+ MessageTextComponent,
],
providers: [VoiceRecorderService],
})
diff --git a/projects/stream-chat-angular/src/lib/types.ts b/projects/stream-chat-angular/src/lib/types.ts
index 615b1099..6b53514e 100644
--- a/projects/stream-chat-angular/src/lib/types.ts
+++ b/projects/stream-chat-angular/src/lib/types.ts
@@ -540,3 +540,9 @@ export type CustomAutocomplete = {
*/
updateOptions?: (searchTerm: string) => Promise;
};
+
+export type MessageTextContext = {
+ message: StreamMessage | undefined | MessageResponseBase;
+ isQuoted: boolean;
+ shouldTranslate: boolean;
+};
diff --git a/projects/stream-chat-angular/src/public-api.ts b/projects/stream-chat-angular/src/public-api.ts
index 52033c53..5644d4eb 100644
--- a/projects/stream-chat-angular/src/public-api.ts
+++ b/projects/stream-chat-angular/src/public-api.ts
@@ -80,3 +80,4 @@ export * from './lib/voice-recorder//voice-recorder-wavebar/voice-recorder-waveb
export * from './lib/format-duration';
export * from './lib/message-input/voice-recorder.service';
export * from './lib/voice-recorder/mp3-transcoder';
+export * from './lib/message-text/message-text.component';