stream-chat-angular 5.0.0-v5.21 → 5.0.0-v5.23

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.
Files changed (33) hide show
  1. package/assets/version.d.ts +1 -1
  2. package/esm2020/assets/version.mjs +2 -2
  3. package/esm2020/lib/channel/channel.component.mjs +5 -6
  4. package/esm2020/lib/custom-templates.service.mjs +1 -10
  5. package/esm2020/lib/message/message.component.mjs +51 -21
  6. package/esm2020/lib/message-actions-box/message-actions-box.component.mjs +1 -1
  7. package/esm2020/lib/message-actions.service.mjs +22 -4
  8. package/esm2020/lib/message-input/autocomplete-textarea/autocomplete-textarea.component.mjs +13 -1
  9. package/esm2020/lib/message-input/message-input.component.mjs +75 -23
  10. package/esm2020/lib/message-input/textarea/textarea.component.mjs +13 -1
  11. package/esm2020/lib/message-list/message-list.component.mjs +5 -3
  12. package/esm2020/lib/stream-chat.module.mjs +1 -6
  13. package/esm2020/lib/types.mjs +1 -1
  14. package/esm2020/public-api.mjs +1 -2
  15. package/fesm2015/stream-chat-angular.mjs +2028 -1989
  16. package/fesm2015/stream-chat-angular.mjs.map +1 -1
  17. package/fesm2020/stream-chat-angular.mjs +1669 -1631
  18. package/fesm2020/stream-chat-angular.mjs.map +1 -1
  19. package/lib/custom-templates.service.d.ts +1 -10
  20. package/lib/message/message.component.d.ts +11 -3
  21. package/lib/message-actions-box/message-actions-box.component.d.ts +1 -2
  22. package/lib/message-actions.service.d.ts +1 -0
  23. package/lib/message-input/autocomplete-textarea/autocomplete-textarea.component.d.ts +1 -0
  24. package/lib/message-input/message-input.component.d.ts +19 -3
  25. package/lib/message-input/textarea/textarea.component.d.ts +1 -0
  26. package/lib/message-list/message-list.component.d.ts +2 -1
  27. package/lib/stream-chat.module.d.ts +9 -10
  28. package/lib/types.d.ts +1 -10
  29. package/package.json +1 -1
  30. package/public-api.d.ts +0 -1
  31. package/src/assets/version.ts +1 -1
  32. package/esm2020/lib/edit-message-form/edit-message-form.component.mjs +0 -83
  33. package/lib/edit-message-form/edit-message-form.component.d.ts +0 -31
@@ -1,6 +1,6 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Injectable, Component, Input, InjectionToken, EventEmitter, Directive, Output, ViewChild, HostBinding, Inject, ChangeDetectionStrategy, NgModule } from '@angular/core';
3
- import { BehaviorSubject, ReplaySubject, combineLatest, Subject, timer } from 'rxjs';
2
+ import { Injectable, Component, Input, EventEmitter, Output, ViewChild, HostBinding, ChangeDetectionStrategy, InjectionToken, Directive, Inject, NgModule } from '@angular/core';
3
+ import { BehaviorSubject, ReplaySubject, combineLatest, take as take$1, Subject, timer } from 'rxjs';
4
4
  import { StreamChat } from 'stream-chat';
5
5
  import { take, shareReplay, map, first, filter, tap, debounceTime, throttleTime } from 'rxjs/operators';
6
6
  import { v4 } from 'uuid';
@@ -8,18 +8,18 @@ import * as i10 from '@ngx-translate/core';
8
8
  import { TranslateModule } from '@ngx-translate/core';
9
9
  import * as i4 from '@angular/common';
10
10
  import { CommonModule } from '@angular/common';
11
- import prettybytes from 'pretty-bytes';
12
11
  import Dayjs from 'dayjs';
13
12
  import calendar from 'dayjs/plugin/calendar';
14
13
  import relativeTime from 'dayjs/plugin/relativeTime';
15
14
  import emojiRegex from 'emoji-regex';
16
15
  import * as i8 from 'ngx-float-ui';
17
16
  import { NgxFloatUiModule } from 'ngx-float-ui';
17
+ import prettybytes from 'pretty-bytes';
18
18
  import transliterate from '@stream-io/transliterate';
19
19
  import * as i8$1 from 'angular-mentions';
20
20
  import { MentionModule } from 'angular-mentions';
21
21
 
22
- const version = '5.0.0-v5.21';
22
+ const version = '5.0.0-v5.23';
23
23
 
24
24
  /**
25
25
  * The `NotificationService` can be used to add or remove notifications. By default the [`NotificationList`](../components/NotificationListComponent.mdx) component displays the currently active notifications.
@@ -2601,11 +2601,6 @@ class CustomTemplatesService {
2601
2601
  *
2602
2602
  */
2603
2603
  this.channelPreviewTemplate$ = new BehaviorSubject(undefined);
2604
- /**
2605
- * The message input template used when editing a message (instead of the [default message input](../components/MessageInputComponent.mdx))
2606
- *
2607
- */
2608
- this.messageInputTemplate$ = new BehaviorSubject(undefined);
2609
2604
  /**
2610
2605
  * The template used for displaying a [mention inside a message](../code-examples/mention-actions.mdx)
2611
2606
  *
@@ -2783,10 +2778,6 @@ class CustomTemplatesService {
2783
2778
  * The template to show if the thread message list is empty
2784
2779
  */
2785
2780
  this.emptyThreadMessageListPlaceholder$ = new BehaviorSubject(undefined);
2786
- /**
2787
- * The template used to display the [edit message form](../components/EditMessageFormComponent.mdx)
2788
- */
2789
- this.editMessageFormTemplate$ = new BehaviorSubject(undefined);
2790
2781
  /**
2791
2782
  * The template used to display the [message bounce prompt](../components/MessageBouncePromptComponent.mdx)
2792
2783
  */
@@ -3033,8 +3024,9 @@ class MessageActionsService {
3033
3024
  actionLabelOrTranslationKey: 'streamChat.Copy text',
3034
3025
  isVisible: (_, __, message) => {
3035
3026
  const isClipboardSupported = navigator?.clipboard?.write !== undefined;
3036
- if (!isClipboardSupported) {
3037
- console.warn(`Clipboard API isn't available, please check security and browser requirements: https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/write#security_considerations`);
3027
+ if (!isClipboardSupported && !this.hasDisplayedClipboardWarning) {
3028
+ console.warn(`[Stream Chat] Copy action is disabled because clipboard API isn't available, please check security and browser requirements: https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/write#security_considerations`);
3029
+ this.hasDisplayedClipboardWarning = true;
3038
3030
  }
3039
3031
  return (!!message.text &&
3040
3032
  (message.type === 'regular' || message.type === 'reply') &&
@@ -3066,6 +3058,23 @@ class MessageActionsService {
3066
3058
  * You can pass your own custom actions that will be displayed inside the built-in message actions component
3067
3059
  */
3068
3060
  this.customActions$ = new BehaviorSubject([]);
3061
+ this.hasDisplayedClipboardWarning = false;
3062
+ combineLatest([
3063
+ this.messageToEdit$,
3064
+ this.channelService.activeChannel$,
3065
+ ]).subscribe(([messageToEdit, activeChannel]) => {
3066
+ if (messageToEdit && !activeChannel) {
3067
+ this.messageToEdit$.next(undefined);
3068
+ }
3069
+ });
3070
+ combineLatest([
3071
+ this.messageToEdit$,
3072
+ this.channelService.activeParentMessageId$,
3073
+ ]).subscribe(([messageToEdit, parentMessageId]) => {
3074
+ if (messageToEdit && messageToEdit.parent_id !== parentMessageId) {
3075
+ this.messageToEdit$.next(undefined);
3076
+ }
3077
+ });
3069
3078
  }
3070
3079
  /**
3071
3080
  * This method returns how many authorized actions are available to the given message
@@ -3348,150 +3357,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImpor
3348
3357
  args: [{ selector: 'stream-notification-list', template: "<div\n data-testid=\"notification-list\"\n class=\"str-chat str-chat__theme-{{\n theme$ | async\n }} str-chat__list-notifications\"\n>\n <ng-container\n *ngFor=\"let notification of notifications$ | async; trackBy: trackById\"\n >\n <ng-template #notificationContent>\n <div\n *ngIf=\"notification.text !== undefined\"\n data-testclass=\"notification-content\"\n >\n {{ notification.text | translate: notification.translateParams }}\n </div>\n <ng-container *ngIf=\"notification.template !== undefined\">\n <ng-container\n *ngTemplateOutlet=\"\n notification.template;\n context: getNotificationContentContext(notification)\n \"\n ></ng-container>\n </ng-container>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.notificationTemplate$ | async) ||\n defaultNotification;\n context: { type: notification.type, content: notificationContent }\n \"\n ></ng-container>\n </ng-container>\n</div>\n\n<ng-template #defaultNotification let-type=\"type\" let-content=\"content\">\n <stream-notification [type]=\"type\" [content]=\"content\"></stream-notification>\n</ng-template>\n" }]
3349
3358
  }], ctorParameters: function () { return [{ type: CustomTemplatesService }, { type: NotificationService }, { type: ThemeService }]; } });
3350
3359
 
3351
- const textareaInjectionToken = new InjectionToken('textareaInjectionToken');
3352
-
3353
- class TextareaDirective {
3354
- constructor(viewContainerRef) {
3355
- this.viewContainerRef = viewContainerRef;
3356
- this.value = '';
3357
- this.valueChange = new EventEmitter();
3358
- this.send = new EventEmitter();
3359
- this.userMentions = new EventEmitter();
3360
- this.subscriptions = [];
3361
- this.unpropagatedChanges = [];
3362
- }
3363
- ngOnChanges(changes) {
3364
- this.unpropagatedChanges.push(changes);
3365
- if (!this.componentRef) {
3366
- return;
3367
- }
3368
- if (changes.componentRef) {
3369
- this.subscriptions.forEach((s) => s.unsubscribe());
3370
- if (this.componentRef) {
3371
- this.subscriptions.push(this.componentRef.instance.valueChange.subscribe((value) => this.valueChange.next(value)));
3372
- this.subscriptions.push(this.componentRef.instance.send.subscribe((value) => this.send.next(value)));
3373
- if (this.componentRef.instance.userMentions) {
3374
- this.subscriptions.push(this.componentRef.instance.userMentions.subscribe((value) => this.userMentions.next(value)));
3375
- }
3376
- this.componentRef.instance.areMentionsEnabled = this.areMentionsEnabled;
3377
- this.componentRef.instance.mentionScope = this.mentionScope;
3378
- this.componentRef.instance.value = this.value;
3379
- this.componentRef.instance.placeholder = this.placeholder;
3380
- this.componentRef.instance.inputMode = this.inputMode;
3381
- this.componentRef.instance.autoFocus = this.autoFocus;
3382
- }
3383
- }
3384
- if (changes.areMentionsEnabled) {
3385
- this.componentRef.instance.areMentionsEnabled = this.areMentionsEnabled;
3386
- }
3387
- if (changes.mentionScope) {
3388
- this.componentRef.instance.mentionScope = this.mentionScope;
3389
- }
3390
- if (changes.value) {
3391
- this.componentRef.instance.value = this.value;
3392
- }
3393
- if (changes.placeholder) {
3394
- this.componentRef.instance.placeholder = this.placeholder;
3395
- }
3396
- if (changes.inputMode) {
3397
- this.componentRef.instance.inputMode = this.inputMode;
3398
- }
3399
- if (changes.autoFocus) {
3400
- this.componentRef.instance.autoFocus = this.autoFocus;
3401
- }
3402
- // ngOnChanges not called for dynamic components since we don't use template binding
3403
- let changesToPropagate = {};
3404
- this.unpropagatedChanges.forEach((c) => (changesToPropagate = { ...changesToPropagate, ...c }));
3405
- // eslint-disable-next-line @angular-eslint/no-lifecycle-call
3406
- this.componentRef.instance.ngOnChanges?.(changesToPropagate);
3407
- this.unpropagatedChanges = [];
3408
- }
3409
- }
3410
- TextareaDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: TextareaDirective, deps: [{ token: i0.ViewContainerRef }], target: i0.ɵɵFactoryTarget.Directive });
3411
- TextareaDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "15.0.4", type: TextareaDirective, selector: "[streamTextarea]", inputs: { componentRef: "componentRef", areMentionsEnabled: "areMentionsEnabled", mentionScope: "mentionScope", inputMode: "inputMode", value: "value", placeholder: "placeholder", autoFocus: "autoFocus" }, outputs: { valueChange: "valueChange", send: "send", userMentions: "userMentions" }, usesOnChanges: true, ngImport: i0 });
3412
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: TextareaDirective, decorators: [{
3413
- type: Directive,
3414
- args: [{
3415
- selector: '[streamTextarea]',
3416
- }]
3417
- }], ctorParameters: function () { return [{ type: i0.ViewContainerRef }]; }, propDecorators: { componentRef: [{
3418
- type: Input
3419
- }], areMentionsEnabled: [{
3420
- type: Input
3421
- }], mentionScope: [{
3422
- type: Input
3423
- }], inputMode: [{
3424
- type: Input
3425
- }], value: [{
3426
- type: Input
3427
- }], placeholder: [{
3428
- type: Input
3429
- }], autoFocus: [{
3430
- type: Input
3431
- }], valueChange: [{
3432
- type: Output
3433
- }], send: [{
3434
- type: Output
3435
- }], userMentions: [{
3436
- type: Output
3437
- }] } });
3438
-
3439
- /**
3440
- * If you have an emoji picker in your application, you can propagate the selected emoji to the textarea using this service, more info can be found in [custom emoji picker guide](../code-examples/emoji-picker.mdx)
3441
- */
3442
- class EmojiInputService {
3443
- constructor() {
3444
- /**
3445
- * If you have an emoji picker in your application, you can propagate the selected emoji to the textarea using this Subject, more info can be found in [custom emoji picker guide](../code-examples/emoji-picker.mdx)
3446
- */
3447
- this.emojiInput$ = new Subject();
3448
- }
3449
- }
3450
- EmojiInputService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: EmojiInputService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3451
- EmojiInputService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: EmojiInputService, providedIn: 'root' });
3452
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: EmojiInputService, decorators: [{
3453
- type: Injectable,
3454
- args: [{
3455
- providedIn: 'root',
3456
- }]
3457
- }], ctorParameters: function () { return []; } });
3458
-
3459
- /**
3460
- * The `MessageInputConfigService` is used to keep a consistent configuration among the different [`MessageInput`](../components/MessageInputComponent.mdx) components if your UI has more than one input component.
3461
- */
3462
- class MessageInputConfigService {
3463
- constructor() {
3464
- /**
3465
- * If file upload is enabled, the user can open a file selector from the input. Please note that the user also needs to have the necessary [channel capability](https://getstream.io/chat/docs/javascript/channel_capabilities/?language=javascript).
3466
- */
3467
- this.isFileUploadEnabled = true;
3468
- /**
3469
- * If true, users can mention other users in messages. You also [need to use the `AutocompleteTextarea`](../concepts/opt-in-architecture.mdx) for this feature to work.
3470
- */
3471
- this.areMentionsEnabled = true;
3472
- /**
3473
- * If `false`, users can only upload one attachment per message
3474
- */
3475
- this.isMultipleFileUploadEnabled = true;
3476
- /**
3477
- * The scope for user mentions, either members of the current channel of members of the application
3478
- */
3479
- this.mentionScope = 'channel';
3480
- /**
3481
- * In `desktop` mode the `Enter` key will trigger message sending, in `mobile` mode the `Enter` key will insert a new line to the message input.
3482
- */
3483
- this.inputMode = 'desktop';
3484
- }
3485
- }
3486
- MessageInputConfigService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: MessageInputConfigService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3487
- MessageInputConfigService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: MessageInputConfigService, providedIn: 'root' });
3488
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: MessageInputConfigService, decorators: [{
3489
- type: Injectable,
3490
- args: [{
3491
- providedIn: 'root',
3492
- }]
3493
- }], ctorParameters: function () { return []; } });
3494
-
3495
3360
  /**
3496
3361
  * The `Modal` component displays its content in an overlay. The modal can be closed with a close button, if the user clicks outside of the modal content, or if the escape button is pressed. The modal can also be closed from outside.
3497
3362
  */
@@ -3558,77 +3423,546 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImpor
3558
3423
  }] } });
3559
3424
 
3560
3425
  /**
3561
- * This component can be used to visualize the wave bar of a voice recording
3426
+ * The component watches for the [`channelService.bouncedMessage$` stream](../../services/ChannelService/#bouncedmessage) and opens the bounce modal if a message is emitted.
3427
+ *
3428
+ * To bounce messages, you need to set up [semantic filters for moderation](https://getstream.io/automated-moderation/docs/automod_configuration/?q=semantic%20filters).
3562
3429
  */
3563
- class VoiceRecordingWavebarComponent {
3564
- constructor(ngZone, cdRef) {
3565
- this.ngZone = ngZone;
3566
- this.cdRef = cdRef;
3567
- /**
3568
- * The waveform data to visualize
3569
- */
3570
- this.waveFormData = [];
3571
- this.resampledWaveFormData = [];
3572
- this.progress = 0;
3573
- this.isDragging = false;
3574
- this.sampleSize = 40;
3575
- this.isViewInited = false;
3576
- this.upsample = () => {
3577
- if (this.sampleSize === this.waveFormData.length)
3578
- return this.waveFormData;
3579
- // eslint-disable-next-line prefer-const
3580
- let [bucketSize, remainder] = this.divMod(this.sampleSize, this.waveFormData.length);
3581
- const result = [];
3582
- for (let i = 0; i < this.waveFormData.length; i++) {
3583
- const extra = remainder && remainder-- ? 1 : 0;
3584
- result.push(...Array(bucketSize + extra).fill(this.waveFormData[i]));
3430
+ class MessageBouncePromptComponent {
3431
+ constructor(channelService, customTemplatesService, messageActionsService) {
3432
+ this.channelService = channelService;
3433
+ this.customTemplatesService = customTemplatesService;
3434
+ this.messageActionsService = messageActionsService;
3435
+ this.class = 'str-chat__message-bounce-prompt';
3436
+ this.isModalOpen = false;
3437
+ this.subscriptions = [];
3438
+ this.messageBounceModalOpenChanged = (isOpen) => {
3439
+ this.isModalOpen = isOpen;
3440
+ if (!isOpen) {
3441
+ this.message = undefined;
3442
+ this.channelService.bouncedMessage$.next(undefined);
3585
3443
  }
3586
- return result;
3587
- };
3588
- this.getNextBucketMean = (data, currentBucketIndex, bucketSize) => {
3589
- const nextBucketStartIndex = Math.floor(currentBucketIndex * bucketSize) + 1;
3590
- let nextNextBucketStartIndex = Math.floor((currentBucketIndex + 1) * bucketSize) + 1;
3591
- nextNextBucketStartIndex =
3592
- nextNextBucketStartIndex < data.length
3593
- ? nextNextBucketStartIndex
3594
- : data.length;
3595
- return this.mean(data.slice(nextBucketStartIndex, nextNextBucketStartIndex));
3596
- };
3597
- this.mean = (values) => values.reduce((acc, value) => acc + value, 0) / values.length;
3598
- this.triangleAreaHeron = (a, b, c) => {
3599
- const s = (a + b + c) / 2;
3600
- return Math.sqrt(s * (s - a) * (s - b) * (s - c));
3601
- };
3602
- this.triangleBase = (a, b) => Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2));
3603
- this.divMod = (num, divisor) => {
3604
- return [Math.floor(num / divisor), num % divisor];
3605
3444
  };
3445
+ this.subscriptions.push(this.channelService.bouncedMessage$.subscribe((m) => {
3446
+ if (m !== this.message) {
3447
+ this.message = m;
3448
+ if (this.message) {
3449
+ this.isModalOpen = true;
3450
+ }
3451
+ }
3452
+ }));
3606
3453
  }
3607
- ngOnInit() {
3608
- this.containerSizeChanged();
3609
- if (this.container?.nativeElement) {
3610
- this.ngZone.runOutsideAngular(() => {
3611
- new ResizeObserver(() => {
3612
- this.containerSizeChanged();
3613
- }).observe(this.container.nativeElement);
3614
- });
3615
- }
3454
+ ngOnDestroy() {
3455
+ this.subscriptions.forEach((s) => s.unsubscribe());
3616
3456
  }
3617
- ngOnChanges(changes) {
3618
- if (changes.waveFormData) {
3619
- this.resampledWaveFormData =
3620
- this.waveFormData.length > this.sampleSize
3621
- ? this.downsample()
3622
- : this.upsample();
3457
+ async resendMessage() {
3458
+ this.isModalOpen = false;
3459
+ await this.channelService.resendMessage(this.message);
3460
+ this.message = undefined;
3461
+ this.channelService.bouncedMessage$.next(undefined);
3462
+ }
3463
+ async deleteMessage() {
3464
+ if (!this.message) {
3465
+ return;
3623
3466
  }
3624
- if (changes.audioElement) {
3625
- this.ngZone.runOutsideAngular(() => {
3626
- this.audioElement?.addEventListener('timeupdate', () => {
3627
- const progress = (this.audioElement?.currentTime || 0) / (this.duration || 0) || 0;
3628
- if (Math.abs(progress - this.progress) >= 0.02) {
3629
- this.ngZone.run(() => {
3630
- this.progress = progress;
3631
- this.cdRef.detectChanges();
3467
+ this.isModalOpen = false;
3468
+ await this.channelService.deleteMessage(this.message, true);
3469
+ this.message = undefined;
3470
+ this.channelService.bouncedMessage$.next(undefined);
3471
+ }
3472
+ editMessage() {
3473
+ this.isModalOpen = false;
3474
+ this.messageActionsService.messageToEdit$.next(this.message);
3475
+ this.message = undefined;
3476
+ this.channelService.bouncedMessage$.next(undefined);
3477
+ }
3478
+ }
3479
+ MessageBouncePromptComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: MessageBouncePromptComponent, deps: [{ token: ChannelService }, { token: CustomTemplatesService }, { token: MessageActionsService }], target: i0.ɵɵFactoryTarget.Component });
3480
+ MessageBouncePromptComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.0.4", type: MessageBouncePromptComponent, selector: "stream-message-bounce-prompt", host: { properties: { "class": "this.class" } }, ngImport: i0, template: "<ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.modalTemplate$ | async) || defaultModal;\n context: {\n message: message,\n isOpen: isModalOpen,\n isOpenChangeHandler: messageBounceModalOpenChanged,\n content: modalContent\n }\n \"\n></ng-container>\n\n<ng-template\n #defaultModal\n let-isOpen=\"isOpen\"\n let-isOpenChangeHandler=\"isOpenChangeHandler\"\n let-content=\"content\"\n>\n <stream-modal\n *ngIf=\"isOpen\"\n [isOpen]=\"isOpen\"\n [content]=\"content\"\n (isOpenChange)=\"isOpenChangeHandler($event)\"\n >\n </stream-modal>\n</ng-template>\n\n<ng-template #modalContent>\n <div\n class=\"str-chat__message-bounce-prompt\"\n data-testid=\"message-bounce-prompt\"\n >\n <div class=\"str-chat__message-bounce-prompt-header\">\n {{\n \"streamChat.This message did not meet our content guidelines\"\n | translate\n }}\n </div>\n <div class=\"str-chat__message-bounce-actions\">\n <button\n class=\"str-chat__message-bounce-edit\"\n data-testid=\"message-bounce-edit\"\n type=\"button\"\n (click)=\"editMessage()\"\n (keyup.enter)=\"editMessage()\"\n >\n {{ \"streamChat.Edit Message\" | translate }}\n </button>\n <button\n class=\"str-chat__message-bounce-send\"\n data-testid=\"message-bounce-send\"\n (click)=\"resendMessage()\"\n (keyup.enter)=\"resendMessage()\"\n >\n {{ \"streamChat.Send Anyway\" | translate }}\n </button>\n <button\n class=\"str-chat__message-bounce-delete\"\n data-testid=\"message-bounce-delete\"\n (click)=\"deleteMessage()\"\n (keyup.enter)=\"deleteMessage()\"\n >\n {{ \"streamChat.Delete\" | translate }}\n </button>\n </div>\n </div>\n</ng-template>\n", dependencies: [{ kind: "directive", type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i4.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: ModalComponent, selector: "stream-modal", inputs: ["isOpen", "content"], outputs: ["isOpenChange"] }, { kind: "pipe", type: i4.AsyncPipe, name: "async" }, { kind: "pipe", type: i10.TranslatePipe, name: "translate" }] });
3481
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: MessageBouncePromptComponent, decorators: [{
3482
+ type: Component,
3483
+ args: [{ selector: 'stream-message-bounce-prompt', template: "<ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.modalTemplate$ | async) || defaultModal;\n context: {\n message: message,\n isOpen: isModalOpen,\n isOpenChangeHandler: messageBounceModalOpenChanged,\n content: modalContent\n }\n \"\n></ng-container>\n\n<ng-template\n #defaultModal\n let-isOpen=\"isOpen\"\n let-isOpenChangeHandler=\"isOpenChangeHandler\"\n let-content=\"content\"\n>\n <stream-modal\n *ngIf=\"isOpen\"\n [isOpen]=\"isOpen\"\n [content]=\"content\"\n (isOpenChange)=\"isOpenChangeHandler($event)\"\n >\n </stream-modal>\n</ng-template>\n\n<ng-template #modalContent>\n <div\n class=\"str-chat__message-bounce-prompt\"\n data-testid=\"message-bounce-prompt\"\n >\n <div class=\"str-chat__message-bounce-prompt-header\">\n {{\n \"streamChat.This message did not meet our content guidelines\"\n | translate\n }}\n </div>\n <div class=\"str-chat__message-bounce-actions\">\n <button\n class=\"str-chat__message-bounce-edit\"\n data-testid=\"message-bounce-edit\"\n type=\"button\"\n (click)=\"editMessage()\"\n (keyup.enter)=\"editMessage()\"\n >\n {{ \"streamChat.Edit Message\" | translate }}\n </button>\n <button\n class=\"str-chat__message-bounce-send\"\n data-testid=\"message-bounce-send\"\n (click)=\"resendMessage()\"\n (keyup.enter)=\"resendMessage()\"\n >\n {{ \"streamChat.Send Anyway\" | translate }}\n </button>\n <button\n class=\"str-chat__message-bounce-delete\"\n data-testid=\"message-bounce-delete\"\n (click)=\"deleteMessage()\"\n (keyup.enter)=\"deleteMessage()\"\n >\n {{ \"streamChat.Delete\" | translate }}\n </button>\n </div>\n </div>\n</ng-template>\n" }]
3484
+ }], ctorParameters: function () { return [{ type: ChannelService }, { type: CustomTemplatesService }, { type: MessageActionsService }]; }, propDecorators: { class: [{
3485
+ type: HostBinding
3486
+ }] } });
3487
+
3488
+ /**
3489
+ * The `Channel` component is a container component that displays the [`ChannelHeader`](./ChannelHeaderComponent.mdx), [`MessageList`](./MessageListComponent.mdx), [`NotificationList`](./NotificationListComponent.mdx) and [`MessageInput`](./MessageInputComponent.mdx) components. You can also provide the [`Thread`](./ThreadComponent.mdx) component to use message [threads](https://getstream.io/chat/docs/javascript/threads/?language=javascript).
3490
+ */
3491
+ class ChannelComponent {
3492
+ constructor(channelService, themeService, customTemplatesService) {
3493
+ this.channelService = channelService;
3494
+ this.themeService = themeService;
3495
+ this.customTemplatesService = customTemplatesService;
3496
+ this.subscriptions = [];
3497
+ this.isError$ = combineLatest([
3498
+ this.channelService.channelQueryState$,
3499
+ this.channelService.activeChannel$,
3500
+ ]).pipe(map(([state, activeChannel]) => {
3501
+ return !activeChannel && state?.state === 'error';
3502
+ }));
3503
+ this.isInitializing$ = combineLatest([
3504
+ this.channelService.channelQueryState$,
3505
+ this.channelService.activeChannel$,
3506
+ ]).pipe(map(([state, activeChannel]) => {
3507
+ return !activeChannel && state?.state === 'in-progress';
3508
+ }));
3509
+ this.isActiveThread$ = this.channelService.activeParentMessageId$.pipe(map((id) => !!id));
3510
+ this.theme$ = this.themeService.theme$;
3511
+ this.isActiveChannel$ = this.channelService.activeChannel$.pipe(map((c) => !!c));
3512
+ }
3513
+ }
3514
+ ChannelComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: ChannelComponent, deps: [{ token: ChannelService }, { token: ThemeService }, { token: CustomTemplatesService }], target: i0.ɵɵFactoryTarget.Component });
3515
+ ChannelComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.0.4", type: ChannelComponent, selector: "stream-channel", ngImport: i0, template: "<div\n class=\"str-chat str-chat-channel messaging str-chat__channel str-chat__theme-{{\n theme$ | async\n }}\"\n>\n <div\n *ngIf=\"\n (isError$ | async) === false &&\n (isInitializing$ | async) === false &&\n (isActiveChannel$ | async) === true;\n else noChannel\n \"\n class=\"str-chat__container\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.messageBouncePromptTemplate$ | async) ||\n defaultMessageBouncePrompt\n \"\n ></ng-container>\n <ng-template #defaultMessageBouncePrompt>\n <stream-message-bounce-prompt></stream-message-bounce-prompt>\n </ng-template>\n <div class=\"str-chat__main-panel\">\n <ng-content></ng-content>\n </div>\n <ng-content\n *ngIf=\"isActiveThread$ | async\"\n select='[name=\"thread\"]'\n ></ng-content>\n </div>\n <ng-template #noChannel>\n <div\n *ngIf=\"\n (isInitializing$ | async) === false &&\n ((isError$ | async) === true || (isActiveChannel$ | async) === false)\n \"\n class=\"str-chat__empty-channel\"\n >\n <stream-icon icon=\"chat-bubble\"></stream-icon>\n <p class=\"str-chat__empty-channel-text\">\n {{ \"streamChat.No chats here yet\u2026\" | translate }}\n </p>\n <div class=\"str-chat__empty-channel-notifications\">\n <stream-notification-list></stream-notification-list>\n </div>\n </div>\n <div\n *ngIf=\"\n (isInitializing$ | async) === true &&\n (isError$ | async) === false &&\n (isActiveChannel$ | async) === false\n \"\n class=\"str-chat__loading-channel\"\n >\n <div class=\"str-chat__loading-channel-header\">\n <div class=\"str-chat__loading-channel-header-avatar\"></div>\n <div class=\"str-chat__loading-channel-header-end\">\n <div class=\"str-chat__loading-channel-header-name\"></div>\n <div class=\"str-chat__loading-channel-header-info\"></div>\n </div>\n </div>\n <div class=\"str-chat__loading-channel-message-list\">\n <div class=\"str-chat__loading-channel-message\">\n <div class=\"str-chat__loading-channel-message-avatar\"></div>\n <div class=\"str-chat__loading-channel-message-end\">\n <div class=\"str-chat__loading-channel-message-sender\"></div>\n <div class=\"str-chat__loading-channel-message-last-row\">\n <div class=\"str-chat__loading-channel-message-text\"></div>\n <div class=\"str-chat__loading-channel-message-date\"></div>\n </div>\n </div>\n </div>\n <div class=\"str-chat__loading-channel-message\">\n <div class=\"str-chat__loading-channel-message-avatar\"></div>\n <div class=\"str-chat__loading-channel-message-end\">\n <div class=\"str-chat__loading-channel-message-sender\"></div>\n <div class=\"str-chat__loading-channel-message-last-row\">\n <div class=\"str-chat__loading-channel-message-text\"></div>\n <div class=\"str-chat__loading-channel-message-date\"></div>\n </div>\n </div>\n </div>\n <div class=\"str-chat__loading-channel-message\">\n <div class=\"str-chat__loading-channel-message-avatar\"></div>\n <div class=\"str-chat__loading-channel-message-end\">\n <div class=\"str-chat__loading-channel-message-sender\"></div>\n <div class=\"str-chat__loading-channel-message-last-row\">\n <div class=\"str-chat__loading-channel-message-text\"></div>\n <div class=\"str-chat__loading-channel-message-date\"></div>\n </div>\n </div>\n </div>\n </div>\n <div class=\"str-chat__loading-channel-message-input-row\">\n <div class=\"str-chat__loading-channel-message-input\"></div>\n <div class=\"str-chat__loading-channel-message-send\"></div>\n </div>\n </div>\n </ng-template>\n</div>\n", dependencies: [{ kind: "directive", type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i4.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: IconComponent, selector: "stream-icon", inputs: ["icon"] }, { kind: "component", type: NotificationListComponent, selector: "stream-notification-list" }, { kind: "component", type: MessageBouncePromptComponent, selector: "stream-message-bounce-prompt" }, { kind: "pipe", type: i4.AsyncPipe, name: "async" }, { kind: "pipe", type: i10.TranslatePipe, name: "translate" }] });
3516
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: ChannelComponent, decorators: [{
3517
+ type: Component,
3518
+ args: [{ selector: 'stream-channel', template: "<div\n class=\"str-chat str-chat-channel messaging str-chat__channel str-chat__theme-{{\n theme$ | async\n }}\"\n>\n <div\n *ngIf=\"\n (isError$ | async) === false &&\n (isInitializing$ | async) === false &&\n (isActiveChannel$ | async) === true;\n else noChannel\n \"\n class=\"str-chat__container\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.messageBouncePromptTemplate$ | async) ||\n defaultMessageBouncePrompt\n \"\n ></ng-container>\n <ng-template #defaultMessageBouncePrompt>\n <stream-message-bounce-prompt></stream-message-bounce-prompt>\n </ng-template>\n <div class=\"str-chat__main-panel\">\n <ng-content></ng-content>\n </div>\n <ng-content\n *ngIf=\"isActiveThread$ | async\"\n select='[name=\"thread\"]'\n ></ng-content>\n </div>\n <ng-template #noChannel>\n <div\n *ngIf=\"\n (isInitializing$ | async) === false &&\n ((isError$ | async) === true || (isActiveChannel$ | async) === false)\n \"\n class=\"str-chat__empty-channel\"\n >\n <stream-icon icon=\"chat-bubble\"></stream-icon>\n <p class=\"str-chat__empty-channel-text\">\n {{ \"streamChat.No chats here yet\u2026\" | translate }}\n </p>\n <div class=\"str-chat__empty-channel-notifications\">\n <stream-notification-list></stream-notification-list>\n </div>\n </div>\n <div\n *ngIf=\"\n (isInitializing$ | async) === true &&\n (isError$ | async) === false &&\n (isActiveChannel$ | async) === false\n \"\n class=\"str-chat__loading-channel\"\n >\n <div class=\"str-chat__loading-channel-header\">\n <div class=\"str-chat__loading-channel-header-avatar\"></div>\n <div class=\"str-chat__loading-channel-header-end\">\n <div class=\"str-chat__loading-channel-header-name\"></div>\n <div class=\"str-chat__loading-channel-header-info\"></div>\n </div>\n </div>\n <div class=\"str-chat__loading-channel-message-list\">\n <div class=\"str-chat__loading-channel-message\">\n <div class=\"str-chat__loading-channel-message-avatar\"></div>\n <div class=\"str-chat__loading-channel-message-end\">\n <div class=\"str-chat__loading-channel-message-sender\"></div>\n <div class=\"str-chat__loading-channel-message-last-row\">\n <div class=\"str-chat__loading-channel-message-text\"></div>\n <div class=\"str-chat__loading-channel-message-date\"></div>\n </div>\n </div>\n </div>\n <div class=\"str-chat__loading-channel-message\">\n <div class=\"str-chat__loading-channel-message-avatar\"></div>\n <div class=\"str-chat__loading-channel-message-end\">\n <div class=\"str-chat__loading-channel-message-sender\"></div>\n <div class=\"str-chat__loading-channel-message-last-row\">\n <div class=\"str-chat__loading-channel-message-text\"></div>\n <div class=\"str-chat__loading-channel-message-date\"></div>\n </div>\n </div>\n </div>\n <div class=\"str-chat__loading-channel-message\">\n <div class=\"str-chat__loading-channel-message-avatar\"></div>\n <div class=\"str-chat__loading-channel-message-end\">\n <div class=\"str-chat__loading-channel-message-sender\"></div>\n <div class=\"str-chat__loading-channel-message-last-row\">\n <div class=\"str-chat__loading-channel-message-text\"></div>\n <div class=\"str-chat__loading-channel-message-date\"></div>\n </div>\n </div>\n </div>\n </div>\n <div class=\"str-chat__loading-channel-message-input-row\">\n <div class=\"str-chat__loading-channel-message-input\"></div>\n <div class=\"str-chat__loading-channel-message-send\"></div>\n </div>\n </div>\n </ng-template>\n</div>\n" }]
3519
+ }], ctorParameters: function () { return [{ type: ChannelService }, { type: ThemeService }, { type: CustomTemplatesService }]; } });
3520
+
3521
+ const listUsers = (users) => {
3522
+ let outStr = '';
3523
+ const slicedArr = users.map((item) => item.name || item.id).slice(0, 5);
3524
+ const restLength = users.length - slicedArr.length;
3525
+ const commaSeparatedUsers = slicedArr.join(', ');
3526
+ outStr = commaSeparatedUsers;
3527
+ if (restLength > 0) {
3528
+ outStr += ` +${restLength}`;
3529
+ }
3530
+ return outStr;
3531
+ };
3532
+
3533
+ const getChannelDisplayText = (channel, currentUser) => {
3534
+ if (channel.data?.name) {
3535
+ return channel.data.name;
3536
+ }
3537
+ if (channel.state.members && Object.keys(channel.state.members).length > 0) {
3538
+ const members = Object.values(channel.state.members)
3539
+ .map((m) => m.user || { id: m.user_id })
3540
+ .filter((m) => m.id !== currentUser?.id);
3541
+ return listUsers(members);
3542
+ }
3543
+ return channel.id;
3544
+ };
3545
+
3546
+ /**
3547
+ * The `ChannelHeader` component displays the avatar and name of the currently active channel along with member and watcher information. You can read about [the difference between members and watchers](https://getstream.io/chat/docs/javascript/watch_channel/?language=javascript#watchers-vs-members) in the platform documentation. Please note that number of watchers is only displayed if the user has [`connect-events` capability](https://getstream.io/chat/docs/javascript/channel_capabilities/?language=javascript)
3548
+ */
3549
+ class ChannelHeaderComponent {
3550
+ constructor(channelService, customTemplatesService, cdRef, chatClientService) {
3551
+ this.channelService = channelService;
3552
+ this.customTemplatesService = customTemplatesService;
3553
+ this.cdRef = cdRef;
3554
+ this.chatClientService = chatClientService;
3555
+ this.subscriptions = [];
3556
+ this.channelService.activeChannel$.subscribe((c) => {
3557
+ this.activeChannel = c;
3558
+ const capabilities = this.activeChannel?.data
3559
+ ?.own_capabilities;
3560
+ if (!capabilities) {
3561
+ return;
3562
+ }
3563
+ this.canReceiveConnectEvents =
3564
+ capabilities.indexOf('connect-events') !== -1;
3565
+ });
3566
+ }
3567
+ ngOnInit() {
3568
+ this.subscriptions.push(this.customTemplatesService.channelActionsTemplate$.subscribe((template) => {
3569
+ this.channelActionsTemplate = template;
3570
+ this.cdRef.detectChanges();
3571
+ }));
3572
+ this.subscriptions.push(this.customTemplatesService.channelHeaderInfoTemplate$.subscribe((template) => {
3573
+ this.channelHeaderInfoTemplate = template;
3574
+ this.cdRef.detectChanges();
3575
+ }));
3576
+ }
3577
+ ngOnDestroy() {
3578
+ this.subscriptions.forEach((s) => s.unsubscribe());
3579
+ }
3580
+ getChannelActionsContext() {
3581
+ return { channel: this.activeChannel };
3582
+ }
3583
+ getChannelInfoContext() {
3584
+ return { channel: this.activeChannel };
3585
+ }
3586
+ get memberCountParam() {
3587
+ return { memberCount: this.activeChannel?.data?.member_count || 0 };
3588
+ }
3589
+ get watcherCountParam() {
3590
+ return { watcherCount: this.activeChannel?.state?.watcher_count || 0 };
3591
+ }
3592
+ get displayText() {
3593
+ if (!this.activeChannel) {
3594
+ return '';
3595
+ }
3596
+ return getChannelDisplayText(this.activeChannel, this.chatClientService.chatClient.user);
3597
+ }
3598
+ get avatarName() {
3599
+ return this.activeChannel?.data?.name;
3600
+ }
3601
+ }
3602
+ ChannelHeaderComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: ChannelHeaderComponent, deps: [{ token: ChannelService }, { token: CustomTemplatesService }, { token: i0.ChangeDetectorRef }, { token: ChatClientService }], target: i0.ɵɵFactoryTarget.Component });
3603
+ ChannelHeaderComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.0.4", type: ChannelHeaderComponent, selector: "stream-channel-header", ngImport: i0, template: "<div class=\"str-chat__header-livestream str-chat__channel-header\">\n <ng-content></ng-content>\n <stream-avatar-placeholder\n type=\"channel\"\n location=\"channel-header\"\n imageUrl=\"{{ activeChannel?.data?.image }}\"\n name=\"{{ avatarName }}\"\n [channel]=\"activeChannel\"\n ></stream-avatar-placeholder>\n <div class=\"str-chat__header-livestream-left str-chat__channel-header-end\">\n <p\n data-testid=\"name\"\n class=\"\n str-chat__header-livestream-left--title str-chat__channel-header-title\n \"\n >\n {{ displayText }}\n </p>\n <ng-container\n *ngTemplateOutlet=\"\n channelHeaderInfoTemplate || defaultChannelInfo;\n context: getChannelInfoContext()\n \"\n ></ng-container>\n <ng-template #defaultChannelInfo>\n <p\n data-testid=\"info\"\n class=\"\n str-chat__header-livestream-left--members\n str-chat__channel-header-info\n \"\n >\n {{'streamChat.{{ memberCount }} members' | translate:memberCountParam}}\n {{canReceiveConnectEvents ? ('streamChat.{{ watcherCount }} online' |\n translate:watcherCountParam) : ''}}\n </p>\n </ng-template>\n </div>\n <ng-container *ngIf=\"channelActionsTemplate\">\n <ng-container\n *ngTemplateOutlet=\"\n channelActionsTemplate;\n context: getChannelActionsContext()\n \"\n ></ng-container>\n </ng-container>\n</div>\n", dependencies: [{ kind: "directive", type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i4.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: AvatarPlaceholderComponent, selector: "stream-avatar-placeholder", inputs: ["name", "imageUrl", "location", "channel", "user", "type", "initialsType", "showOnlineIndicator"] }, { kind: "pipe", type: i10.TranslatePipe, name: "translate" }] });
3604
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: ChannelHeaderComponent, decorators: [{
3605
+ type: Component,
3606
+ args: [{ selector: 'stream-channel-header', template: "<div class=\"str-chat__header-livestream str-chat__channel-header\">\n <ng-content></ng-content>\n <stream-avatar-placeholder\n type=\"channel\"\n location=\"channel-header\"\n imageUrl=\"{{ activeChannel?.data?.image }}\"\n name=\"{{ avatarName }}\"\n [channel]=\"activeChannel\"\n ></stream-avatar-placeholder>\n <div class=\"str-chat__header-livestream-left str-chat__channel-header-end\">\n <p\n data-testid=\"name\"\n class=\"\n str-chat__header-livestream-left--title str-chat__channel-header-title\n \"\n >\n {{ displayText }}\n </p>\n <ng-container\n *ngTemplateOutlet=\"\n channelHeaderInfoTemplate || defaultChannelInfo;\n context: getChannelInfoContext()\n \"\n ></ng-container>\n <ng-template #defaultChannelInfo>\n <p\n data-testid=\"info\"\n class=\"\n str-chat__header-livestream-left--members\n str-chat__channel-header-info\n \"\n >\n {{'streamChat.{{ memberCount }} members' | translate:memberCountParam}}\n {{canReceiveConnectEvents ? ('streamChat.{{ watcherCount }} online' |\n translate:watcherCountParam) : ''}}\n </p>\n </ng-template>\n </div>\n <ng-container *ngIf=\"channelActionsTemplate\">\n <ng-container\n *ngTemplateOutlet=\"\n channelActionsTemplate;\n context: getChannelActionsContext()\n \"\n ></ng-container>\n </ng-container>\n</div>\n" }]
3607
+ }], ctorParameters: function () { return [{ type: ChannelService }, { type: CustomTemplatesService }, { type: i0.ChangeDetectorRef }, { type: ChatClientService }]; } });
3608
+
3609
+ const isOnSeparateDate = (date1, date2) => {
3610
+ if (date1.getDate() !== date2.getDate()) {
3611
+ return true;
3612
+ }
3613
+ else if (date1.getFullYear() !== date2.getFullYear() ||
3614
+ date1.getMonth() !== date2.getMonth()) {
3615
+ return true;
3616
+ }
3617
+ return false;
3618
+ };
3619
+
3620
+ /**
3621
+ * The message service contains configuration options related to displaying the message content
3622
+ */
3623
+ class MessageService {
3624
+ constructor() {
3625
+ /**
3626
+ * Decides if the message content should be formatted as text or HTML
3627
+ *
3628
+ * If you display messages as text the following parts are still be displayed as HTML:
3629
+ * - user mentions -> you can customize this with your own template using the [`customTemplatesService.mentionTemplate$`](https://getstream.io/chat/docs/sdk/angular/services/CustomTemplatesService/#mentiontemplate)
3630
+ * - links -> you can customize this by providing you own [`customLinkRenderer`](#customlinkrenderer) method
3631
+ */
3632
+ this.displayAs = 'text';
3633
+ }
3634
+ }
3635
+ MessageService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: MessageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3636
+ MessageService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: MessageService, providedIn: 'root' });
3637
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: MessageService, decorators: [{
3638
+ type: Injectable,
3639
+ args: [{
3640
+ providedIn: 'root',
3641
+ }]
3642
+ }], ctorParameters: function () { return []; } });
3643
+
3644
+ Dayjs.extend(calendar);
3645
+ Dayjs.extend(relativeTime);
3646
+ const parseDate = (date, format = 'date-time') => {
3647
+ const parsedTime = Dayjs(date);
3648
+ switch (format) {
3649
+ case 'date': {
3650
+ return parsedTime.calendar(null, {
3651
+ sameDay: '[Today]',
3652
+ nextDay: '[Tomorrow]',
3653
+ nextWeek: 'dddd',
3654
+ lastDay: '[Yesterday]',
3655
+ lastWeek: '[Last] dddd',
3656
+ sameElse: 'MM/DD/YYYY', // Everything else ( 10/17/2011 )
3657
+ });
3658
+ }
3659
+ case 'date-time': {
3660
+ return parsedTime.calendar();
3661
+ }
3662
+ case 'time': {
3663
+ return parsedTime.format('h:mm A');
3664
+ }
3665
+ }
3666
+ };
3667
+
3668
+ /**
3669
+ * The `DateParserService` parses dates into user-friendly string representations.
3670
+ */
3671
+ class DateParserService {
3672
+ constructor() { }
3673
+ /**
3674
+ * Return a user-friendly string representation of the time
3675
+ * @param date
3676
+ * @returns The parsed time
3677
+ */
3678
+ parseTime(date) {
3679
+ if (this.customTimeParser) {
3680
+ return this.customTimeParser(date);
3681
+ }
3682
+ return parseDate(date, 'time');
3683
+ }
3684
+ /**
3685
+ * Return a user-friendly string representation of the date (year, month and date)
3686
+ * @param date
3687
+ * @returns The parsed date
3688
+ */
3689
+ parseDate(date) {
3690
+ if (this.customDateParser) {
3691
+ return this.customDateParser(date);
3692
+ }
3693
+ return parseDate(date, 'date');
3694
+ }
3695
+ /**
3696
+ * Return a user-friendly string representation of the date and time
3697
+ * @param date
3698
+ * @returns The parsed date
3699
+ */
3700
+ parseDateTime(date) {
3701
+ if (this.customDateTimeParser) {
3702
+ return this.customDateTimeParser(date);
3703
+ }
3704
+ return parseDate(date, 'date-time');
3705
+ }
3706
+ }
3707
+ DateParserService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: DateParserService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3708
+ DateParserService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: DateParserService, providedIn: 'root' });
3709
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: DateParserService, decorators: [{
3710
+ type: Injectable,
3711
+ args: [{
3712
+ providedIn: 'root',
3713
+ }]
3714
+ }], ctorParameters: function () { return []; } });
3715
+
3716
+ /**
3717
+ * The `ChannelPreview` component displays a channel preview in the channel list, it consists of the image, name and latest message of the channel.
3718
+ */
3719
+ class ChannelPreviewComponent {
3720
+ constructor(channelService, ngZone, chatClientService, messageService, customTemplatesService, dateParser) {
3721
+ this.channelService = channelService;
3722
+ this.ngZone = ngZone;
3723
+ this.chatClientService = chatClientService;
3724
+ this.customTemplatesService = customTemplatesService;
3725
+ this.dateParser = dateParser;
3726
+ this.isActive = false;
3727
+ this.isUnreadMessageWasCalled = false;
3728
+ this.isUnread = false;
3729
+ this.latestMessageText = 'streamChat.Nothing yet...';
3730
+ this.subscriptions = [];
3731
+ this.canSendReadEvents = true;
3732
+ this.displayAs = messageService.displayAs;
3733
+ }
3734
+ ngOnInit() {
3735
+ this.subscriptions.push(this.chatClientService.user$.subscribe((user) => {
3736
+ if (user?.id !== this.userId) {
3737
+ this.userId = user?.id;
3738
+ }
3739
+ }));
3740
+ this.subscriptions.push(this.channelService.activeChannel$.subscribe((activeChannel) => (this.isActive = activeChannel?.id === this.channel?.id)));
3741
+ const messages = this.channel?.state?.latestMessages;
3742
+ if (messages && messages.length > 0) {
3743
+ this.setLatestMessage(messages[messages.length - 1]);
3744
+ }
3745
+ this.updateUnreadState();
3746
+ const capabilities = this.channel?.data?.own_capabilities || [];
3747
+ this.canSendReadEvents = capabilities.indexOf('read-events') !== -1;
3748
+ this.subscriptions.push(this.channel.on('message.new', this.handleMessageEvent.bind(this)));
3749
+ this.subscriptions.push(this.channel.on('message.updated', this.handleMessageEvent.bind(this)));
3750
+ this.subscriptions.push(this.channel.on('message.deleted', this.handleMessageEvent.bind(this)));
3751
+ this.subscriptions.push(this.channel.on('channel.truncated', this.handleMessageEvent.bind(this)));
3752
+ this.subscriptions.push(this.channel.on('message.read', () => this.ngZone.run(() => {
3753
+ this.isUnreadMessageWasCalled = false;
3754
+ this.updateUnreadState();
3755
+ })));
3756
+ this.subscriptions.push(this.chatClientService.events$
3757
+ .pipe(filter((e) => e.eventType === 'notification.mark_unread' &&
3758
+ this.channel.id === e.event?.channel_id))
3759
+ .subscribe(() => {
3760
+ this.ngZone.run(() => {
3761
+ this.isUnreadMessageWasCalled = true;
3762
+ this.updateUnreadState();
3763
+ });
3764
+ }));
3765
+ }
3766
+ ngOnDestroy() {
3767
+ this.subscriptions.forEach((s) => s.unsubscribe());
3768
+ }
3769
+ get avatarImage() {
3770
+ return this.channel?.data?.image;
3771
+ }
3772
+ get avatarName() {
3773
+ return this.channel?.data?.name;
3774
+ }
3775
+ get title() {
3776
+ if (!this.channel) {
3777
+ return '';
3778
+ }
3779
+ return getChannelDisplayText(this.channel, this.chatClientService.chatClient.user);
3780
+ }
3781
+ setAsActiveChannel() {
3782
+ void this.channelService.setAsActiveChannel(this.channel);
3783
+ }
3784
+ handleMessageEvent(event) {
3785
+ this.ngZone.run(() => {
3786
+ if (this.channel?.state.latestMessages.length === 0) {
3787
+ this.latestMessage = undefined;
3788
+ this.latestMessageStatus = undefined;
3789
+ this.latestMessageText = 'streamChat.Nothing yet...';
3790
+ this.latestMessageTime = undefined;
3791
+ return;
3792
+ }
3793
+ const latestMessage = this.channel?.state.latestMessages[this.channel?.state.latestMessages.length - 1];
3794
+ if (!event.message || latestMessage?.id !== event.message.id) {
3795
+ return;
3796
+ }
3797
+ this.setLatestMessage(latestMessage);
3798
+ this.updateUnreadState();
3799
+ });
3800
+ }
3801
+ setLatestMessage(message) {
3802
+ this.latestMessage = message;
3803
+ if (message?.deleted_at) {
3804
+ this.latestMessageText = 'streamChat.Message deleted';
3805
+ }
3806
+ else if (message?.text) {
3807
+ this.latestMessageText =
3808
+ getMessageTranslation(message, this.channel, this.chatClientService.chatClient.user) || message.text;
3809
+ }
3810
+ else if (message?.attachments && message.attachments.length) {
3811
+ this.latestMessageText = 'streamChat.🏙 Attachment...';
3812
+ }
3813
+ if (this.latestMessage && this.latestMessage.type === 'regular') {
3814
+ this.latestMessageTime = isOnSeparateDate(new Date(), this.latestMessage.created_at)
3815
+ ? this.dateParser.parseDate(this.latestMessage.created_at)
3816
+ : this.dateParser.parseTime(this.latestMessage.created_at);
3817
+ }
3818
+ else {
3819
+ this.latestMessageTime = undefined;
3820
+ }
3821
+ }
3822
+ updateUnreadState() {
3823
+ if (this.channel &&
3824
+ this.latestMessage &&
3825
+ this.latestMessage.user?.id === this.userId &&
3826
+ this.latestMessage.status === 'received' &&
3827
+ this.latestMessage.type === 'regular') {
3828
+ this.latestMessageStatus =
3829
+ getReadBy(this.latestMessage, this.channel).length > 0
3830
+ ? 'read'
3831
+ : 'delivered';
3832
+ }
3833
+ else {
3834
+ this.latestMessageStatus = undefined;
3835
+ }
3836
+ if ((this.isActive && !this.isUnreadMessageWasCalled) ||
3837
+ !this.canSendReadEvents) {
3838
+ this.unreadCount = 0;
3839
+ this.isUnread = false;
3840
+ return;
3841
+ }
3842
+ this.unreadCount = this.channel.countUnread();
3843
+ this.isUnread = !!this.unreadCount;
3844
+ }
3845
+ }
3846
+ ChannelPreviewComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: ChannelPreviewComponent, deps: [{ token: ChannelService }, { token: i0.NgZone }, { token: ChatClientService }, { token: MessageService }, { token: CustomTemplatesService }, { token: DateParserService }], target: i0.ɵɵFactoryTarget.Component });
3847
+ ChannelPreviewComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.0.4", type: ChannelPreviewComponent, selector: "stream-channel-preview", inputs: { channel: "channel" }, ngImport: i0, template: "<button\n class=\"str-chat__channel-preview-messenger str-chat__channel-preview\"\n data-testid=\"channel-preview-container\"\n [class.str-chat__channel-preview-messenger--active]=\"isActive\"\n [class.str-chat__channel-preview--active]=\"isActive\"\n [class.str-chat__channel-preview-messenger--unread]=\"isUnread\"\n (click)=\"setAsActiveChannel()\"\n>\n <div class=\"str-chat__channel-preview-messenger--left\">\n <stream-avatar-placeholder\n type=\"channel\"\n location=\"channel-preview\"\n name=\"{{ avatarName }}\"\n imageUrl=\"{{ avatarImage }}\"\n [channel]=\"channel\"\n ></stream-avatar-placeholder>\n </div>\n <div\n class=\"\n str-chat__channel-preview-messenger--right str-chat__channel-preview-end\n \"\n >\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.channelPreviewInfoTemplate$ | async) ||\n defaultChannelInfo;\n context: {\n channelDisplayTitle: title,\n channel: channel,\n unreadCount: unreadCount,\n latestMessageText: latestMessageText,\n latestMessageStatus: latestMessageStatus,\n latestMessageTime: latestMessageTime,\n latestMessage: latestMessage\n }\n \"\n ></ng-container>\n <ng-template\n #defaultChannelInfo\n let-channelDisplayTitle=\"channelDisplayTitle\"\n let-unreadCount=\"unreadCount\"\n let-latestMessageText=\"latestMessageText\"\n let-latestMessageStatus=\"latestMessageStatus\"\n let-latestMessageTime=\"latestMessageTime\"\n >\n <div class=\"str-chat__channel-preview-end-first-row\">\n <div class=\"str-chat__channel-preview-messenger--name\">\n <span data-testid=\"channel-preview-title\">{{\n channelDisplayTitle\n }}</span>\n </div>\n <div\n *ngIf=\"unreadCount\"\n data-testid=\"unread-badge\"\n class=\"str-chat__channel-preview-unread-badge\"\n >\n {{ unreadCount }}\n </div>\n </div>\n <div class=\"str-chat__channel-preview-end-second-row\">\n <div\n data-testid=\"latest-message\"\n class=\"str-chat__channel-preview-messenger--last-message\"\n >\n <ng-container *ngIf=\"displayAs === 'text'; else asHTML\">\n {{ latestMessageText | translate }}\n </ng-container>\n <ng-template #asHTML>\n <span\n data-testid=\"html-content\"\n [innerHTML]=\"latestMessageText | translate\"\n ></span>\n </ng-template>\n </div>\n <div\n *ngIf=\"latestMessageStatus\"\n data-testid=\"latest-message-status\"\n class=\"str-chat__channel-preview-messenger--status str-chat__channel-preview-messenger--status-{{\n latestMessageStatus\n }}\"\n >\n <stream-icon-placeholder\n [icon]=\"latestMessageStatus === 'delivered' ? 'delivered' : 'read'\"\n ></stream-icon-placeholder>\n </div>\n <div\n *ngIf=\"latestMessageTime\"\n data-testid=\"latest-message-time\"\n class=\"str-chat__channel-preview-messenger--time\"\n >\n {{ latestMessageTime }}\n </div>\n </div>\n </ng-template>\n </div>\n</button>\n", dependencies: [{ kind: "directive", type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i4.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: AvatarPlaceholderComponent, selector: "stream-avatar-placeholder", inputs: ["name", "imageUrl", "location", "channel", "user", "type", "initialsType", "showOnlineIndicator"] }, { kind: "component", type: IconPlaceholderComponent, selector: "stream-icon-placeholder", inputs: ["icon"] }, { kind: "pipe", type: i4.AsyncPipe, name: "async" }, { kind: "pipe", type: i10.TranslatePipe, name: "translate" }] });
3848
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: ChannelPreviewComponent, decorators: [{
3849
+ type: Component,
3850
+ args: [{ selector: 'stream-channel-preview', template: "<button\n class=\"str-chat__channel-preview-messenger str-chat__channel-preview\"\n data-testid=\"channel-preview-container\"\n [class.str-chat__channel-preview-messenger--active]=\"isActive\"\n [class.str-chat__channel-preview--active]=\"isActive\"\n [class.str-chat__channel-preview-messenger--unread]=\"isUnread\"\n (click)=\"setAsActiveChannel()\"\n>\n <div class=\"str-chat__channel-preview-messenger--left\">\n <stream-avatar-placeholder\n type=\"channel\"\n location=\"channel-preview\"\n name=\"{{ avatarName }}\"\n imageUrl=\"{{ avatarImage }}\"\n [channel]=\"channel\"\n ></stream-avatar-placeholder>\n </div>\n <div\n class=\"\n str-chat__channel-preview-messenger--right str-chat__channel-preview-end\n \"\n >\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.channelPreviewInfoTemplate$ | async) ||\n defaultChannelInfo;\n context: {\n channelDisplayTitle: title,\n channel: channel,\n unreadCount: unreadCount,\n latestMessageText: latestMessageText,\n latestMessageStatus: latestMessageStatus,\n latestMessageTime: latestMessageTime,\n latestMessage: latestMessage\n }\n \"\n ></ng-container>\n <ng-template\n #defaultChannelInfo\n let-channelDisplayTitle=\"channelDisplayTitle\"\n let-unreadCount=\"unreadCount\"\n let-latestMessageText=\"latestMessageText\"\n let-latestMessageStatus=\"latestMessageStatus\"\n let-latestMessageTime=\"latestMessageTime\"\n >\n <div class=\"str-chat__channel-preview-end-first-row\">\n <div class=\"str-chat__channel-preview-messenger--name\">\n <span data-testid=\"channel-preview-title\">{{\n channelDisplayTitle\n }}</span>\n </div>\n <div\n *ngIf=\"unreadCount\"\n data-testid=\"unread-badge\"\n class=\"str-chat__channel-preview-unread-badge\"\n >\n {{ unreadCount }}\n </div>\n </div>\n <div class=\"str-chat__channel-preview-end-second-row\">\n <div\n data-testid=\"latest-message\"\n class=\"str-chat__channel-preview-messenger--last-message\"\n >\n <ng-container *ngIf=\"displayAs === 'text'; else asHTML\">\n {{ latestMessageText | translate }}\n </ng-container>\n <ng-template #asHTML>\n <span\n data-testid=\"html-content\"\n [innerHTML]=\"latestMessageText | translate\"\n ></span>\n </ng-template>\n </div>\n <div\n *ngIf=\"latestMessageStatus\"\n data-testid=\"latest-message-status\"\n class=\"str-chat__channel-preview-messenger--status str-chat__channel-preview-messenger--status-{{\n latestMessageStatus\n }}\"\n >\n <stream-icon-placeholder\n [icon]=\"latestMessageStatus === 'delivered' ? 'delivered' : 'read'\"\n ></stream-icon-placeholder>\n </div>\n <div\n *ngIf=\"latestMessageTime\"\n data-testid=\"latest-message-time\"\n class=\"str-chat__channel-preview-messenger--time\"\n >\n {{ latestMessageTime }}\n </div>\n </div>\n </ng-template>\n </div>\n</button>\n" }]
3851
+ }], ctorParameters: function () { return [{ type: ChannelService }, { type: i0.NgZone }, { type: ChatClientService }, { type: MessageService }, { type: CustomTemplatesService }, { type: DateParserService }]; }, propDecorators: { channel: [{
3852
+ type: Input
3853
+ }] } });
3854
+
3855
+ /**
3856
+ * The `ChannelList` component renders the list of channels.
3857
+ */
3858
+ class ChannelListComponent {
3859
+ constructor(channelService, customTemplatesService, themeService) {
3860
+ this.channelService = channelService;
3861
+ this.customTemplatesService = customTemplatesService;
3862
+ this.themeService = themeService;
3863
+ this.isLoadingMoreChannels = false;
3864
+ this.subscriptions = [];
3865
+ this.theme$ = this.themeService.theme$;
3866
+ this.channels$ = this.channelService.channels$;
3867
+ this.hasMoreChannels$ = this.channelService.hasMoreChannels$;
3868
+ this.isError$ = this.channelService.channelQueryState$.pipe(map((s) => !this.isLoadingMoreChannels && s?.state === 'error'));
3869
+ this.isInitializing$ = this.channelService.channelQueryState$.pipe(map((s) => !this.isLoadingMoreChannels && s?.state === 'in-progress'));
3870
+ this.subscriptions.push(this.customTemplatesService.channelPreviewTemplate$.subscribe((template) => (this.customChannelPreviewTemplate = template)));
3871
+ }
3872
+ ngOnDestroy() {
3873
+ this.subscriptions.forEach((s) => s.unsubscribe());
3874
+ }
3875
+ async loadMoreChannels() {
3876
+ this.isLoadingMoreChannels = true;
3877
+ await this.channelService.loadMoreChannels();
3878
+ this.isLoadingMoreChannels = false;
3879
+ }
3880
+ trackByChannelId(index, item) {
3881
+ return item.cid;
3882
+ }
3883
+ }
3884
+ ChannelListComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: ChannelListComponent, deps: [{ token: ChannelService }, { token: CustomTemplatesService }, { token: ThemeService }], target: i0.ɵɵFactoryTarget.Component });
3885
+ ChannelListComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.0.4", type: ChannelListComponent, selector: "stream-channel-list", viewQueries: [{ propertyName: "container", first: true, predicate: ["container"], descendants: true }], ngImport: i0, template: "<div\n #container\n data-testid=\"channel-list-container\"\n class=\"str-chat str-chat__channel-list str-chat-channel-list messaging str-chat__theme-{{\n theme$ | async\n }}\"\n>\n <div\n *ngIf=\"\n (isError$ | async) === false && (isInitializing$ | async) === false;\n else statusIndicator\n \"\n class=\"str-chat__channel-list-messenger\"\n >\n <div class=\"str-chat__channel-list-messenger__main\">\n <ng-content select=\"[channel-list-top]\"></ng-content>\n <div\n *ngIf=\"!(channels$ | async)?.length\"\n class=\"str-chat__channel-list-empty\"\n >\n <stream-icon icon=\"chat-bubble\"></stream-icon>\n <p data-testid=\"empty-channel-list-indicator\">\n {{ \"streamChat.You have no channels currently\" | translate }}\n </p>\n </div>\n <p\n *ngIf=\"!(channels$ | async)?.length\"\n class=\"str-chat__channel-list-empty-v1\"\n data-testid=\"empty-channel-list-indicator\"\n >\n {{ \"streamChat.You have no channels currently\" | translate }}\n </p>\n <ng-container\n *ngFor=\"let channel of channels$ | async; trackBy: trackByChannelId\"\n >\n <ng-template #defaultTemplate let-channelInput=\"channel\">\n <stream-channel-preview\n data-testclass=\"channel-preview\"\n [channel]=\"channelInput\"\n ></stream-channel-preview>\n </ng-template>\n <div>\n <ng-container\n *ngTemplateOutlet=\"\n customChannelPreviewTemplate || defaultTemplate;\n context: { channel: channel }\n \"\n ></ng-container>\n </div>\n </ng-container>\n <div\n *ngIf=\"hasMoreChannels$ | async\"\n class=\"str-chat__load-more-button\"\n data-testid=\"load-more\"\n (click)=\"loadMoreChannels()\"\n (keyup.enter)=\"loadMoreChannels()\"\n >\n <button\n class=\"str-chat__load-more-button__button str-chat__cta-button\"\n data-testid=\"load-more-button\"\n [disabled]=\"isLoadingMoreChannels\"\n >\n <span *ngIf=\"!isLoadingMoreChannels; else loadingIndicator\">{{\n \"Load more\" | translate\n }}</span>\n <ng-template #loadingIndicator\n ><stream-loading-indicator-placeholder></stream-loading-indicator-placeholder\n ></ng-template>\n </button>\n </div>\n <ng-content select=\"[channel-list-bottom]\"></ng-content>\n </div>\n </div>\n</div>\n\n<ng-template #statusIndicator>\n <ng-container *ngIf=\"isError$ | async\">\n <ng-container *ngTemplateOutlet=\"chatDown\"></ng-container>\n </ng-container>\n <ng-container *ngIf=\"isInitializing$ | async\">\n <ng-container *ngTemplateOutlet=\"loadingChannels\"></ng-container>\n </ng-container>\n</ng-template>\n\n<ng-template #chatDown>\n <div data-testid=\"chatdown-container\" class=\"str-chat__down\">\n <ng-container *ngTemplateOutlet=\"loadingChannels\"></ng-container>\n </div>\n</ng-template>\n\n<ng-template #loadingChannels>\n <div data-testid=\"loading-indicator\" class=\"str-chat__loading-channels\">\n <ng-container *ngTemplateOutlet=\"loadingChannel\"></ng-container>\n <ng-container *ngTemplateOutlet=\"loadingChannel\"></ng-container>\n <ng-container *ngTemplateOutlet=\"loadingChannel\"></ng-container>\n </div>\n</ng-template>\n\n<ng-template #loadingChannel>\n <div\n class=\"str-chat__loading-channels-item str-chat__channel-preview-loading\"\n >\n <div class=\"str-chat__loading-channels-avatar\"></div>\n <div\n class=\"\n str-chat__loading-channels-meta str-chat__channel-preview-end-loading\n \"\n >\n <div class=\"str-chat__loading-channels-username\"></div>\n <div class=\"str-chat__loading-channels-status\"></div>\n </div>\n </div>\n</ng-template>\n", dependencies: [{ kind: "directive", type: i4.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i4.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: ChannelPreviewComponent, selector: "stream-channel-preview", inputs: ["channel"] }, { kind: "component", type: IconComponent, selector: "stream-icon", inputs: ["icon"] }, { kind: "component", type: LoadingIndicatorPlaceholderComponent, selector: "stream-loading-indicator-placeholder" }, { kind: "pipe", type: i4.AsyncPipe, name: "async" }, { kind: "pipe", type: i10.TranslatePipe, name: "translate" }] });
3886
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: ChannelListComponent, decorators: [{
3887
+ type: Component,
3888
+ args: [{ selector: 'stream-channel-list', template: "<div\n #container\n data-testid=\"channel-list-container\"\n class=\"str-chat str-chat__channel-list str-chat-channel-list messaging str-chat__theme-{{\n theme$ | async\n }}\"\n>\n <div\n *ngIf=\"\n (isError$ | async) === false && (isInitializing$ | async) === false;\n else statusIndicator\n \"\n class=\"str-chat__channel-list-messenger\"\n >\n <div class=\"str-chat__channel-list-messenger__main\">\n <ng-content select=\"[channel-list-top]\"></ng-content>\n <div\n *ngIf=\"!(channels$ | async)?.length\"\n class=\"str-chat__channel-list-empty\"\n >\n <stream-icon icon=\"chat-bubble\"></stream-icon>\n <p data-testid=\"empty-channel-list-indicator\">\n {{ \"streamChat.You have no channels currently\" | translate }}\n </p>\n </div>\n <p\n *ngIf=\"!(channels$ | async)?.length\"\n class=\"str-chat__channel-list-empty-v1\"\n data-testid=\"empty-channel-list-indicator\"\n >\n {{ \"streamChat.You have no channels currently\" | translate }}\n </p>\n <ng-container\n *ngFor=\"let channel of channels$ | async; trackBy: trackByChannelId\"\n >\n <ng-template #defaultTemplate let-channelInput=\"channel\">\n <stream-channel-preview\n data-testclass=\"channel-preview\"\n [channel]=\"channelInput\"\n ></stream-channel-preview>\n </ng-template>\n <div>\n <ng-container\n *ngTemplateOutlet=\"\n customChannelPreviewTemplate || defaultTemplate;\n context: { channel: channel }\n \"\n ></ng-container>\n </div>\n </ng-container>\n <div\n *ngIf=\"hasMoreChannels$ | async\"\n class=\"str-chat__load-more-button\"\n data-testid=\"load-more\"\n (click)=\"loadMoreChannels()\"\n (keyup.enter)=\"loadMoreChannels()\"\n >\n <button\n class=\"str-chat__load-more-button__button str-chat__cta-button\"\n data-testid=\"load-more-button\"\n [disabled]=\"isLoadingMoreChannels\"\n >\n <span *ngIf=\"!isLoadingMoreChannels; else loadingIndicator\">{{\n \"Load more\" | translate\n }}</span>\n <ng-template #loadingIndicator\n ><stream-loading-indicator-placeholder></stream-loading-indicator-placeholder\n ></ng-template>\n </button>\n </div>\n <ng-content select=\"[channel-list-bottom]\"></ng-content>\n </div>\n </div>\n</div>\n\n<ng-template #statusIndicator>\n <ng-container *ngIf=\"isError$ | async\">\n <ng-container *ngTemplateOutlet=\"chatDown\"></ng-container>\n </ng-container>\n <ng-container *ngIf=\"isInitializing$ | async\">\n <ng-container *ngTemplateOutlet=\"loadingChannels\"></ng-container>\n </ng-container>\n</ng-template>\n\n<ng-template #chatDown>\n <div data-testid=\"chatdown-container\" class=\"str-chat__down\">\n <ng-container *ngTemplateOutlet=\"loadingChannels\"></ng-container>\n </div>\n</ng-template>\n\n<ng-template #loadingChannels>\n <div data-testid=\"loading-indicator\" class=\"str-chat__loading-channels\">\n <ng-container *ngTemplateOutlet=\"loadingChannel\"></ng-container>\n <ng-container *ngTemplateOutlet=\"loadingChannel\"></ng-container>\n <ng-container *ngTemplateOutlet=\"loadingChannel\"></ng-container>\n </div>\n</ng-template>\n\n<ng-template #loadingChannel>\n <div\n class=\"str-chat__loading-channels-item str-chat__channel-preview-loading\"\n >\n <div class=\"str-chat__loading-channels-avatar\"></div>\n <div\n class=\"\n str-chat__loading-channels-meta str-chat__channel-preview-end-loading\n \"\n >\n <div class=\"str-chat__loading-channels-username\"></div>\n <div class=\"str-chat__loading-channels-status\"></div>\n </div>\n </div>\n</ng-template>\n" }]
3889
+ }], ctorParameters: function () { return [{ type: ChannelService }, { type: CustomTemplatesService }, { type: ThemeService }]; }, propDecorators: { container: [{
3890
+ type: ViewChild,
3891
+ args: ['container']
3892
+ }] } });
3893
+
3894
+ /**
3895
+ * This component can be used to visualize the wave bar of a voice recording
3896
+ */
3897
+ class VoiceRecordingWavebarComponent {
3898
+ constructor(ngZone, cdRef) {
3899
+ this.ngZone = ngZone;
3900
+ this.cdRef = cdRef;
3901
+ /**
3902
+ * The waveform data to visualize
3903
+ */
3904
+ this.waveFormData = [];
3905
+ this.resampledWaveFormData = [];
3906
+ this.progress = 0;
3907
+ this.isDragging = false;
3908
+ this.sampleSize = 40;
3909
+ this.isViewInited = false;
3910
+ this.upsample = () => {
3911
+ if (this.sampleSize === this.waveFormData.length)
3912
+ return this.waveFormData;
3913
+ // eslint-disable-next-line prefer-const
3914
+ let [bucketSize, remainder] = this.divMod(this.sampleSize, this.waveFormData.length);
3915
+ const result = [];
3916
+ for (let i = 0; i < this.waveFormData.length; i++) {
3917
+ const extra = remainder && remainder-- ? 1 : 0;
3918
+ result.push(...Array(bucketSize + extra).fill(this.waveFormData[i]));
3919
+ }
3920
+ return result;
3921
+ };
3922
+ this.getNextBucketMean = (data, currentBucketIndex, bucketSize) => {
3923
+ const nextBucketStartIndex = Math.floor(currentBucketIndex * bucketSize) + 1;
3924
+ let nextNextBucketStartIndex = Math.floor((currentBucketIndex + 1) * bucketSize) + 1;
3925
+ nextNextBucketStartIndex =
3926
+ nextNextBucketStartIndex < data.length
3927
+ ? nextNextBucketStartIndex
3928
+ : data.length;
3929
+ return this.mean(data.slice(nextBucketStartIndex, nextNextBucketStartIndex));
3930
+ };
3931
+ this.mean = (values) => values.reduce((acc, value) => acc + value, 0) / values.length;
3932
+ this.triangleAreaHeron = (a, b, c) => {
3933
+ const s = (a + b + c) / 2;
3934
+ return Math.sqrt(s * (s - a) * (s - b) * (s - c));
3935
+ };
3936
+ this.triangleBase = (a, b) => Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2));
3937
+ this.divMod = (num, divisor) => {
3938
+ return [Math.floor(num / divisor), num % divisor];
3939
+ };
3940
+ }
3941
+ ngOnInit() {
3942
+ this.containerSizeChanged();
3943
+ if (this.container?.nativeElement) {
3944
+ this.ngZone.runOutsideAngular(() => {
3945
+ new ResizeObserver(() => {
3946
+ this.containerSizeChanged();
3947
+ }).observe(this.container.nativeElement);
3948
+ });
3949
+ }
3950
+ }
3951
+ ngOnChanges(changes) {
3952
+ if (changes.waveFormData) {
3953
+ this.resampledWaveFormData =
3954
+ this.waveFormData.length > this.sampleSize
3955
+ ? this.downsample()
3956
+ : this.upsample();
3957
+ }
3958
+ if (changes.audioElement) {
3959
+ this.ngZone.runOutsideAngular(() => {
3960
+ this.audioElement?.addEventListener('timeupdate', () => {
3961
+ const progress = (this.audioElement?.currentTime || 0) / (this.duration || 0) || 0;
3962
+ if (Math.abs(progress - this.progress) >= 0.02) {
3963
+ this.ngZone.run(() => {
3964
+ this.progress = progress;
3965
+ this.cdRef.detectChanges();
3632
3966
  });
3633
3967
  }
3634
3968
  });
@@ -4024,1631 +4358,1313 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImpor
4024
4358
  }] } });
4025
4359
 
4026
4360
  /**
4027
- * The `AttachmentPreviewList` component displays a preview of the attachments uploaded to a message. Users can delete attachments using the preview component, or retry upload if it failed previously.
4028
- */
4029
- class AttachmentPreviewListComponent {
4030
- constructor() {
4031
- /**
4032
- * An output to notify the parent component if the user tries to retry a failed upload
4033
- */
4034
- this.retryAttachmentUpload = new EventEmitter();
4035
- /**
4036
- * An output to notify the parent component if the user wants to delete a file
4037
- */
4038
- this.deleteAttachment = new EventEmitter();
4039
- }
4040
- attachmentUploadRetried(file) {
4041
- this.retryAttachmentUpload.emit(file);
4042
- }
4043
- attachmentDeleted(upload) {
4044
- this.deleteAttachment.emit(upload);
4045
- }
4046
- trackByFile(_, item) {
4047
- return item.file;
4048
- }
4049
- }
4050
- AttachmentPreviewListComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: AttachmentPreviewListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4051
- AttachmentPreviewListComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.0.4", type: AttachmentPreviewListComponent, selector: "stream-attachment-preview-list", inputs: { attachmentUploads$: "attachmentUploads$" }, outputs: { retryAttachmentUpload: "retryAttachmentUpload", deleteAttachment: "deleteAttachment" }, ngImport: i0, template: "<div\n *ngIf=\"(attachmentUploads$ | async)?.length\"\n class=\"str-chat__attachment-preview-list\"\n>\n <div class=\"str-chat__attachment-list-scroll-container\">\n <ng-container\n *ngFor=\"\n let attachmentUpload of attachmentUploads$ | async;\n trackBy: trackByFile\n \"\n >\n <div\n *ngIf=\"attachmentUpload.type === 'image'\"\n class=\"str-chat__attachment-preview-image\"\n data-testclass=\"attachment-image-preview\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n deleteButton;\n context: { attachmentUpload: attachmentUpload }\n \"\n ></ng-container>\n <div\n *ngIf=\"attachmentUpload.state === 'uploading'\"\n class=\"str-chat__attachment-preview-image-loading\"\n >\n <stream-loading-indicator-placeholder\n data-testclass=\"loading-indicator\"\n ></stream-loading-indicator-placeholder>\n </div>\n <ng-container\n *ngTemplateOutlet=\"\n retryButton;\n context: { attachmentUpload: attachmentUpload }\n \"\n ></ng-container>\n <img\n *ngIf=\"attachmentUpload.url || attachmentUpload.previewUri\"\n class=\"str-chat__attachment-preview-thumbnail\"\n data-testclass=\"attachment-image\"\n src=\"{{\n attachmentUpload.url\n ? attachmentUpload.url\n : attachmentUpload.previewUri\n }}\"\n alt=\"{{ attachmentUpload.file.name }}\"\n />\n </div>\n <div\n *ngIf=\"\n attachmentUpload.type === 'file' || attachmentUpload.type === 'video'\n \"\n class=\"str-chat__attachment-preview-file\"\n data-testclass=\"attachment-file-preview\"\n >\n <stream-icon-placeholder\n class=\"str-chat__attachment-preview-file-icon\"\n icon=\"unspecified-filetype\"\n ></stream-icon-placeholder>\n\n <div class=\"str-chat__attachment-preview-file-end\">\n <div class=\"str-chat__attachment-preview-file-name\">\n {{ attachmentUpload.file.name }}\n </div>\n <a\n *ngIf=\"attachmentUpload.state === 'success'\"\n class=\"str-chat__attachment-preview-file-download\"\n data-testclass=\"file-download-link\"\n download\n href=\"{{ attachmentUpload.url }}\"\n (click)=\"attachmentUpload.url ? null : $event.preventDefault()\"\n (keyup.enter)=\"\n attachmentUpload.url ? null : $event.preventDefault()\n \"\n >\n <stream-icon-placeholder icon=\"download\"></stream-icon-placeholder>\n </a>\n <stream-loading-indicator-placeholder\n *ngIf=\"attachmentUpload.state === 'uploading'\"\n data-testclass=\"loading-indicator\"\n ></stream-loading-indicator-placeholder>\n </div>\n <ng-container\n *ngTemplateOutlet=\"\n deleteButton;\n context: { attachmentUpload: attachmentUpload }\n \"\n ></ng-container>\n <ng-container\n *ngTemplateOutlet=\"\n retryButton;\n context: { attachmentUpload: attachmentUpload }\n \"\n ></ng-container>\n </div>\n </ng-container>\n </div>\n</div>\n\n<ng-template #deleteButton let-attachmentUpload=\"attachmentUpload\">\n <div\n class=\"str-chat__attachment-preview-delete\"\n data-testclass=\"file-delete\"\n role=\"button\"\n (click)=\"attachmentDeleted(attachmentUpload)\"\n (keyup.enter)=\"attachmentDeleted(attachmentUpload)\"\n >\n <stream-icon-placeholder icon=\"close\"></stream-icon-placeholder>\n </div>\n</ng-template>\n\n<ng-template #retryButton let-attachmentUpload=\"attachmentUpload\">\n <div\n *ngIf=\"attachmentUpload.state === 'error'\"\n data-testclass=\"upload-retry\"\n class=\"str-chat__attachment-preview-error str-chat__attachment-preview-error-{{\n attachmentUpload.type === 'image' ? 'image' : 'file'\n }}\"\n (click)=\"attachmentUploadRetried(attachmentUpload.file)\"\n (keyup.enter)=\"attachmentUploadRetried(attachmentUpload.file)\"\n >\n <stream-icon-placeholder icon=\"retry\"></stream-icon-placeholder>\n </div>\n</ng-template>\n", dependencies: [{ kind: "directive", type: i4.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i4.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: IconPlaceholderComponent, selector: "stream-icon-placeholder", inputs: ["icon"] }, { kind: "component", type: LoadingIndicatorPlaceholderComponent, selector: "stream-loading-indicator-placeholder" }, { kind: "pipe", type: i4.AsyncPipe, name: "async" }] });
4052
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: AttachmentPreviewListComponent, decorators: [{
4053
- type: Component,
4054
- args: [{ selector: 'stream-attachment-preview-list', template: "<div\n *ngIf=\"(attachmentUploads$ | async)?.length\"\n class=\"str-chat__attachment-preview-list\"\n>\n <div class=\"str-chat__attachment-list-scroll-container\">\n <ng-container\n *ngFor=\"\n let attachmentUpload of attachmentUploads$ | async;\n trackBy: trackByFile\n \"\n >\n <div\n *ngIf=\"attachmentUpload.type === 'image'\"\n class=\"str-chat__attachment-preview-image\"\n data-testclass=\"attachment-image-preview\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n deleteButton;\n context: { attachmentUpload: attachmentUpload }\n \"\n ></ng-container>\n <div\n *ngIf=\"attachmentUpload.state === 'uploading'\"\n class=\"str-chat__attachment-preview-image-loading\"\n >\n <stream-loading-indicator-placeholder\n data-testclass=\"loading-indicator\"\n ></stream-loading-indicator-placeholder>\n </div>\n <ng-container\n *ngTemplateOutlet=\"\n retryButton;\n context: { attachmentUpload: attachmentUpload }\n \"\n ></ng-container>\n <img\n *ngIf=\"attachmentUpload.url || attachmentUpload.previewUri\"\n class=\"str-chat__attachment-preview-thumbnail\"\n data-testclass=\"attachment-image\"\n src=\"{{\n attachmentUpload.url\n ? attachmentUpload.url\n : attachmentUpload.previewUri\n }}\"\n alt=\"{{ attachmentUpload.file.name }}\"\n />\n </div>\n <div\n *ngIf=\"\n attachmentUpload.type === 'file' || attachmentUpload.type === 'video'\n \"\n class=\"str-chat__attachment-preview-file\"\n data-testclass=\"attachment-file-preview\"\n >\n <stream-icon-placeholder\n class=\"str-chat__attachment-preview-file-icon\"\n icon=\"unspecified-filetype\"\n ></stream-icon-placeholder>\n\n <div class=\"str-chat__attachment-preview-file-end\">\n <div class=\"str-chat__attachment-preview-file-name\">\n {{ attachmentUpload.file.name }}\n </div>\n <a\n *ngIf=\"attachmentUpload.state === 'success'\"\n class=\"str-chat__attachment-preview-file-download\"\n data-testclass=\"file-download-link\"\n download\n href=\"{{ attachmentUpload.url }}\"\n (click)=\"attachmentUpload.url ? null : $event.preventDefault()\"\n (keyup.enter)=\"\n attachmentUpload.url ? null : $event.preventDefault()\n \"\n >\n <stream-icon-placeholder icon=\"download\"></stream-icon-placeholder>\n </a>\n <stream-loading-indicator-placeholder\n *ngIf=\"attachmentUpload.state === 'uploading'\"\n data-testclass=\"loading-indicator\"\n ></stream-loading-indicator-placeholder>\n </div>\n <ng-container\n *ngTemplateOutlet=\"\n deleteButton;\n context: { attachmentUpload: attachmentUpload }\n \"\n ></ng-container>\n <ng-container\n *ngTemplateOutlet=\"\n retryButton;\n context: { attachmentUpload: attachmentUpload }\n \"\n ></ng-container>\n </div>\n </ng-container>\n </div>\n</div>\n\n<ng-template #deleteButton let-attachmentUpload=\"attachmentUpload\">\n <div\n class=\"str-chat__attachment-preview-delete\"\n data-testclass=\"file-delete\"\n role=\"button\"\n (click)=\"attachmentDeleted(attachmentUpload)\"\n (keyup.enter)=\"attachmentDeleted(attachmentUpload)\"\n >\n <stream-icon-placeholder icon=\"close\"></stream-icon-placeholder>\n </div>\n</ng-template>\n\n<ng-template #retryButton let-attachmentUpload=\"attachmentUpload\">\n <div\n *ngIf=\"attachmentUpload.state === 'error'\"\n data-testclass=\"upload-retry\"\n class=\"str-chat__attachment-preview-error str-chat__attachment-preview-error-{{\n attachmentUpload.type === 'image' ? 'image' : 'file'\n }}\"\n (click)=\"attachmentUploadRetried(attachmentUpload.file)\"\n (keyup.enter)=\"attachmentUploadRetried(attachmentUpload.file)\"\n >\n <stream-icon-placeholder icon=\"retry\"></stream-icon-placeholder>\n </div>\n</ng-template>\n" }]
4055
- }], ctorParameters: function () { return []; }, propDecorators: { attachmentUploads$: [{
4056
- type: Input
4057
- }], retryAttachmentUpload: [{
4058
- type: Output
4059
- }], deleteAttachment: [{
4060
- type: Output
4061
- }] } });
4062
-
4063
- /**
4064
- * The `MessageInput` component displays an input where users can type their messages and upload files, and sends the message to the active channel. The component can be used to compose new messages or update existing ones. To send messages, the chat user needs to have the necessary [channel capability](https://getstream.io/chat/docs/javascript/channel_capabilities/?language=javascript).
4361
+ * The `MessageReactions` component displays the reactions of a message. You can read more about [message reactions](https://getstream.io/chat/docs/javascript/send_reaction/?language=javascript) in the platform documentation.
4065
4362
  */
4066
- class MessageInputComponent {
4067
- constructor(channelService, notificationService, attachmentService, configService, textareaType, componentFactoryResolver, cdRef, chatClient, emojiInputService, customTemplatesService) {
4068
- this.channelService = channelService;
4069
- this.notificationService = notificationService;
4070
- this.attachmentService = attachmentService;
4071
- this.configService = configService;
4072
- this.textareaType = textareaType;
4073
- this.componentFactoryResolver = componentFactoryResolver;
4363
+ class MessageReactionsComponent {
4364
+ constructor(cdRef, channelService, messageReactionsService, customTemplatesService) {
4074
4365
  this.cdRef = cdRef;
4075
- this.chatClient = chatClient;
4076
- this.emojiInputService = emojiInputService;
4366
+ this.channelService = channelService;
4367
+ this.messageReactionsService = messageReactionsService;
4077
4368
  this.customTemplatesService = customTemplatesService;
4078
4369
  /**
4079
- * Determines if the message is being dispalyed in a channel or in a [thread](https://getstream.io/chat/docs/javascript/threads/?language=javascript).
4370
+ * The number of reactions grouped by [reaction types](https://github.com/GetStream/stream-chat-angular/tree/master/projects/stream-chat-angular/src/lib/message-reactions/message-reactions.component.ts)
4080
4371
  */
4081
- this.mode = 'main';
4372
+ this.messageReactionCounts = {};
4082
4373
  /**
4083
- * Enables or disables auto focus on the textarea element
4374
+ * List of reactions of a [message](../types/stream-message.mdx), used to display the users of a reaction type.
4084
4375
  */
4085
- this.autoFocus = true;
4376
+ this.latestReactions = [];
4086
4377
  /**
4087
- * Emits when a message was successfuly sent or updated
4378
+ * List of the user's own reactions of a [message](../types/stream-message.mdx), used to display the users of a reaction type.
4088
4379
  */
4089
- this.messageUpdate = new EventEmitter();
4090
- this.class = 'str-chat__message-input-angular-host';
4091
- this.textareaValue = '';
4092
- this.mentionedUsers = [];
4093
- this.typingStart$ = new Subject();
4094
- this.isCooldownInProgress = false;
4095
- this.fileInputId = v4();
4380
+ this.ownReactions = [];
4381
+ this.isLoading = true;
4382
+ this.reactions = [];
4383
+ this.shouldHandleReactionClick = true;
4384
+ this.existingReactions = [];
4385
+ this.reactionsCount = 0;
4386
+ this.reactionOptions = [];
4096
4387
  this.subscriptions = [];
4097
4388
  this.isViewInited = false;
4098
- this.defaultTextareaPlaceholder = 'streamChat.Type your message';
4099
- this.slowModeTextareaPlaceholder = 'streamChat.Slow Mode ON';
4100
- this.textareaPlaceholder = this.defaultTextareaPlaceholder;
4101
- this.subscriptions.push(this.attachmentService.attachmentUploadInProgressCounter$.subscribe((counter) => {
4102
- if (counter === 0 && this.hideNotification) {
4103
- this.hideNotification();
4104
- this.hideNotification = undefined;
4105
- }
4106
- }));
4107
- this.subscriptions.push(this.channelService.activeChannel$.subscribe((channel) => {
4108
- if (channel && this.channel && channel.id !== this.channel.id) {
4109
- this.textareaValue = '';
4110
- this.attachmentService.resetAttachmentUploads();
4111
- }
4112
- const capabilities = channel?.data?.own_capabilities;
4113
- if (capabilities) {
4114
- this.isFileUploadAuthorized =
4115
- capabilities.indexOf('upload-file') !== -1;
4116
- this.canSendLinks = capabilities.indexOf('send-links') !== -1;
4117
- this.channel = channel;
4118
- this.setCanSendMessages();
4119
- }
4120
- }));
4121
- this.subscriptions.push(this.chatClient.appSettings$.subscribe((appSettings) => (this.appSettings = appSettings)));
4122
- this.subscriptions.push(this.channelService.messageToQuote$.subscribe((m) => {
4123
- const isThreadReply = m && m.parent_id;
4124
- if ((this.mode === 'thread' && isThreadReply) ||
4125
- (this.mode === 'thread' && this.quotedMessage && !m) ||
4126
- (this.mode === 'main' && !isThreadReply)) {
4127
- this.quotedMessage = m;
4128
- }
4129
- }));
4130
- this.attachmentUploads$ = this.attachmentService.attachmentUploads$;
4131
- this.attachmentUploadInProgressCounter$ =
4132
- this.attachmentService.attachmentUploadInProgressCounter$;
4133
- this.isFileUploadEnabled = this.configService.isFileUploadEnabled;
4134
- this.isMultipleFileUploadEnabled =
4135
- this.configService.isMultipleFileUploadEnabled;
4136
- this.areMentionsEnabled = this.configService.areMentionsEnabled;
4137
- this.mentionScope = this.configService.mentionScope;
4138
- this.inputMode = this.configService.inputMode;
4139
- this.subscriptions.push(this.typingStart$.subscribe(() => void this.channelService.typingStarted(this.parentMessageId)));
4140
- this.subscriptions.push(combineLatest([
4141
- this.channelService.latestMessageDateByUserByChannels$,
4142
- this.channelService.activeChannel$,
4143
- ])
4144
- .pipe(map(([latestMessages, channel]) => [latestMessages[channel?.cid || ''], channel]))
4145
- .subscribe(([latestMessageDate, channel]) => {
4146
- const cooldown = channel?.data?.cooldown &&
4147
- latestMessageDate &&
4148
- Math.round(channel?.data?.cooldown -
4149
- (new Date().getTime() - latestMessageDate.getTime()) / 1000);
4150
- if (cooldown &&
4151
- cooldown > 0 &&
4152
- (channel?.data?.own_capabilities).includes('slow-mode')) {
4153
- this.startCooldown(cooldown);
4154
- }
4155
- else if (this.isCooldownInProgress) {
4156
- this.stopCooldown();
4157
- }
4158
- }));
4389
+ this.isOpenChange = (isOpen) => {
4390
+ this.selectedReactionType = isOpen ? this.selectedReactionType : undefined;
4391
+ };
4159
4392
  }
4160
4393
  ngOnInit() {
4161
- this.subscriptions.push(this.customTemplatesService.emojiPickerTemplate$.subscribe((template) => {
4162
- this.emojiPickerTemplate = template;
4163
- this.cdRef.detectChanges();
4164
- }));
4165
- this.subscriptions.push(this.customTemplatesService.attachmentPreviewListTemplate$.subscribe((template) => {
4166
- this.attachmentPreviewListTemplate = template;
4167
- this.cdRef.detectChanges();
4168
- }));
4169
- this.subscriptions.push(this.customTemplatesService.customAttachmentUploadTemplate$.subscribe((template) => {
4170
- this.customAttachmentUploadTemplate = template;
4171
- this.cdRef.detectChanges();
4394
+ this.subscriptions.push(this.messageReactionsService.reactions$.subscribe((reactions) => {
4395
+ this.reactionOptions = Object.keys(reactions);
4396
+ this.setExistingReactions();
4397
+ if (this.isViewInited) {
4398
+ this.cdRef.detectChanges();
4399
+ }
4172
4400
  }));
4173
4401
  }
4174
- ngAfterViewInit() {
4175
- this.isViewInited = true;
4176
- this.initTextarea();
4177
- }
4178
4402
  ngOnChanges(changes) {
4179
- if (changes.message) {
4180
- this.attachmentService.resetAttachmentUploads();
4181
- if (this.isUpdate) {
4182
- this.attachmentService.createFromAttachments(this.message.attachments || []);
4183
- this.textareaValue = this.message.text || '';
4184
- }
4185
- }
4186
- if (changes.isFileUploadEnabled) {
4187
- this.configService.isFileUploadEnabled = this.isFileUploadEnabled;
4188
- }
4189
- if (changes.isMultipleFileUploadEnabled) {
4190
- this.configService.isMultipleFileUploadEnabled =
4191
- this.isMultipleFileUploadEnabled;
4192
- }
4193
- if (changes.areMentionsEnabled) {
4194
- this.configService.areMentionsEnabled = this.areMentionsEnabled;
4195
- }
4196
- if (changes.mentionScope) {
4197
- this.configService.mentionScope = this.mentionScope;
4198
- }
4199
- if (changes.mode) {
4200
- this.setCanSendMessages();
4201
- }
4202
- if (changes.inputMode) {
4203
- this.configService.inputMode = this.inputMode;
4403
+ if (changes.messageReactionCounts) {
4404
+ this.setExistingReactions();
4204
4405
  }
4205
- if (changes.sendMessage$) {
4206
- if (this.sendMessageSubcription) {
4207
- this.sendMessageSubcription.unsubscribe();
4208
- }
4209
- if (this.sendMessage$) {
4210
- this.sendMessageSubcription = this.sendMessage$.subscribe(() => void this.messageSent());
4211
- }
4406
+ if (changes.messageReactionCounts && this.messageReactionCounts) {
4407
+ const reactionsCount = Object.keys(this.messageReactionCounts).reduce((acc, key) => acc + (this.messageReactionCounts[key] || 0), 0);
4408
+ this.shouldHandleReactionClick =
4409
+ reactionsCount <= ChannelService.MAX_MESSAGE_REACTIONS_TO_FETCH ||
4410
+ !!this.messageReactionsService.customReactionClickHandler;
4212
4411
  }
4213
4412
  }
4413
+ ngAfterViewInit() {
4414
+ this.isViewInited = true;
4415
+ }
4214
4416
  ngOnDestroy() {
4215
- if (this.sendMessageSubcription) {
4216
- this.sendMessageSubcription.unsubscribe();
4217
- }
4218
4417
  this.subscriptions.forEach((s) => s.unsubscribe());
4219
4418
  }
4220
- async messageSent() {
4221
- if (this.isCooldownInProgress) {
4222
- return;
4223
- }
4224
- let attachmentUploadInProgressCounter;
4225
- this.attachmentService.attachmentUploadInProgressCounter$
4226
- .pipe(first())
4227
- .subscribe((counter) => (attachmentUploadInProgressCounter = counter));
4228
- if (attachmentUploadInProgressCounter > 0) {
4229
- if (!this.hideNotification) {
4230
- this.hideNotification =
4231
- this.notificationService.addPermanentNotification('streamChat.Wait until all attachments have uploaded');
4232
- }
4419
+ getEmojiByReaction(reactionType) {
4420
+ return this.messageReactionsService.reactions[reactionType];
4421
+ }
4422
+ reactionSelected(reactionType) {
4423
+ if (!this.shouldHandleReactionClick) {
4233
4424
  return;
4234
4425
  }
4235
- const attachments = this.attachmentService.mapToAttachments();
4236
- let text = this.textareaValue;
4237
- text = text.replace(/^\n+/g, ''); // leading empty lines
4238
- text = text.replace(/\n+$/g, ''); // ending empty lines
4239
- const textContainsOnlySpaceChars = !text.replace(/ /g, ''); //spcae
4240
- if ((!text || textContainsOnlySpaceChars) &&
4241
- (!attachments || attachments.length === 0)) {
4426
+ if (!this.messageId) {
4242
4427
  return;
4243
4428
  }
4244
- if (textContainsOnlySpaceChars) {
4245
- text = '';
4429
+ if (this.messageReactionsService.customReactionClickHandler) {
4430
+ this.messageReactionsService.customReactionClickHandler({
4431
+ messageId: this.messageId,
4432
+ reactionType: reactionType,
4433
+ });
4246
4434
  }
4247
- if (this.containsLinks && !this.canSendLinks) {
4248
- this.notificationService.addTemporaryNotification('streamChat.Sending links is not allowed in this conversation');
4249
- return;
4435
+ else {
4436
+ this.selectedReactionType = reactionType;
4437
+ void this.fetchAllReactions();
4250
4438
  }
4251
- if (!this.isUpdate) {
4252
- this.textareaValue = '';
4439
+ }
4440
+ getUsersByReaction(reactionType) {
4441
+ return this.latestReactions
4442
+ .filter((r) => r.type === reactionType)
4443
+ .map((r) => r.user?.name || r.user?.id)
4444
+ .filter((i) => !!i)
4445
+ .join(', ');
4446
+ }
4447
+ getAllUsersByReaction(reactionType) {
4448
+ if (!reactionType) {
4449
+ return [];
4253
4450
  }
4254
- try {
4255
- const message = await (this.isUpdate
4256
- ? this.channelService.updateMessage({
4257
- ...this.message,
4258
- text: text,
4259
- attachments: attachments,
4260
- })
4261
- : this.channelService.sendMessage(text, attachments, this.mentionedUsers, this.parentMessageId, this.quotedMessage?.id));
4262
- this.messageUpdate.emit({ message });
4263
- if (!this.isUpdate) {
4264
- this.attachmentService.resetAttachmentUploads();
4451
+ const users = this.reactions
4452
+ .filter((r) => r.type === reactionType)
4453
+ .map((r) => r.user)
4454
+ .filter((i) => !!i);
4455
+ users.sort((u1, u2) => {
4456
+ const name1 = u1.name?.toLowerCase();
4457
+ const name2 = u2.name?.toLowerCase();
4458
+ if (!name1) {
4459
+ return 1;
4265
4460
  }
4266
- }
4267
- catch (error) {
4268
- if (this.isUpdate) {
4269
- this.notificationService.addTemporaryNotification('streamChat.Edit message request failed');
4461
+ if (!name2) {
4462
+ return -1;
4270
4463
  }
4271
- }
4272
- void this.channelService.typingStopped(this.parentMessageId);
4273
- if (this.quotedMessage) {
4274
- this.deselectMessageToQuote();
4275
- }
4464
+ if (name1 === name2) {
4465
+ return 0;
4466
+ }
4467
+ if (name1 < name2) {
4468
+ return -1;
4469
+ }
4470
+ else {
4471
+ return 1;
4472
+ }
4473
+ });
4474
+ return users;
4276
4475
  }
4277
- get containsLinks() {
4278
- return /(?:(?:https?|ftp):\/\/)?[\w/\-?=%.]+\.[\w/\-&?=%.]+/.test(this.textareaValue);
4476
+ trackByMessageReaction(_, item) {
4477
+ return item;
4279
4478
  }
4280
- get quotedMessageAttachments() {
4281
- const originalAttachments = this.quotedMessage?.attachments;
4282
- return originalAttachments && originalAttachments.length
4283
- ? [originalAttachments[0]]
4284
- : [];
4479
+ trackByUserId(_, item) {
4480
+ return item.id;
4285
4481
  }
4286
- get disabledTextareaText() {
4287
- if (!this.canSendMessages) {
4288
- return this.mode === 'thread'
4289
- ? "streamChat.You can't send thread replies in this channel"
4290
- : "streamChat.You can't send messages in this channel";
4291
- }
4292
- return '';
4482
+ isOwnReaction(reactionType) {
4483
+ return !!this.ownReactions.find((r) => r.type === reactionType);
4293
4484
  }
4294
- async filesSelected(fileList) {
4295
- if (!(await this.areAttachemntsValid(fileList))) {
4485
+ async fetchAllReactions() {
4486
+ if (!this.messageId) {
4296
4487
  return;
4297
4488
  }
4298
- await this.attachmentService.filesSelected(fileList);
4299
- this.clearFileInput();
4300
- }
4301
- deselectMessageToQuote() {
4302
- this.channelService.selectMessageToQuote(undefined);
4489
+ this.isLoading = true;
4490
+ try {
4491
+ this.reactions = await this.channelService.getMessageReactions(this.messageId);
4492
+ }
4493
+ catch (error) {
4494
+ this.selectedReactionType = undefined;
4495
+ }
4496
+ finally {
4497
+ this.isLoading = false;
4498
+ this.cdRef.detectChanges();
4499
+ }
4303
4500
  }
4304
- getEmojiPickerContext() {
4305
- return {
4306
- emojiInput$: this.emojiInputService.emojiInput$,
4307
- };
4501
+ setExistingReactions() {
4502
+ this.existingReactions = Object.keys(this.messageReactionCounts)
4503
+ .filter((k) => this.reactionOptions.indexOf(k) !== -1)
4504
+ .filter((k) => this.messageReactionCounts[k] > 0);
4505
+ this.reactionsCount = this.existingReactions.reduce((total, reaction) => total + this.messageReactionCounts[reaction], 0);
4308
4506
  }
4309
- getAttachmentPreviewListContext() {
4310
- return {
4311
- attachmentUploads$: this.attachmentService.attachmentUploads$,
4312
- deleteUploadHandler: this.deleteUpload.bind(this),
4313
- retryUploadHandler: this.retryUpload.bind(this),
4507
+ }
4508
+ MessageReactionsComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: MessageReactionsComponent, deps: [{ token: i0.ChangeDetectorRef }, { token: ChannelService }, { token: MessageReactionsService }, { token: CustomTemplatesService }], target: i0.ɵɵFactoryTarget.Component });
4509
+ MessageReactionsComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.0.4", type: MessageReactionsComponent, selector: "stream-message-reactions", inputs: { messageId: "messageId", messageReactionCounts: "messageReactionCounts", latestReactions: "latestReactions", ownReactions: "ownReactions" }, viewQueries: [{ propertyName: "selectorContainer", first: true, predicate: ["selectorContainer"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div\n *ngIf=\"existingReactions.length > 0\"\n data-testid=\"reaction-list\"\n class=\"str-chat__reaction-list str-chat__message-reactions-container\"\n [class.str-chat__reaction-list--reverse]=\"true\"\n>\n <ul class=\"str-chat__message-reactions\">\n <li\n *ngFor=\"\n let reactionType of existingReactions;\n trackBy: trackByMessageReaction\n \"\n class=\"str-chat__message-reaction\"\n data-testclass=\"emoji\"\n [ngStyle]=\"{ cursor: shouldHandleReactionClick ? 'pointer' : 'default' }\"\n [class.str-chat__message-reaction-own]=\"isOwnReaction(reactionType)\"\n (click)=\"reactionSelected(reactionType)\"\n (keyup.enter)=\"reactionSelected(reactionType)\"\n >\n <span class=\"emoji str-chat__message-reaction-emoji\">\n {{ getEmojiByReaction(reactionType) }}&nbsp;\n </span>\n <span\n data-testclass=\"reaction-list-reaction-count\"\n class=\"str-chat__message-reaction-count\"\n >\n {{ messageReactionCounts[reactionType] }}\n </span>\n </li>\n <li>\n <span\n data-testid=\"reactions-count\"\n class=\"str-chat__reaction-list--counter\"\n >{{ reactionsCount }}</span\n >\n </li>\n </ul>\n</div>\n\n<ng-container *ngIf=\"selectedReactionType\">\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.modalTemplate$ | async) || defaultModal;\n context: {\n isOpen: !!selectedReactionType,\n messageId: messageId,\n reactionType: selectedReactionType,\n isOpenChangeHandler: isOpenChange,\n content: modalContent\n }\n \"\n ></ng-container>\n</ng-container>\n\n<ng-template\n #defaultModal\n let-isOpen=\"isOpen\"\n let-messageId=\"messageId\"\n let-reactionType=\"reactionType\"\n let-isOpenChangeHandler=\"isOpenChangeHandler\"\n let-content=\"content\"\n>\n <stream-modal\n class=\"str-chat__message-reactions-details-modal\"\n [isOpen]=\"isOpen\"\n [content]=\"content\"\n (isOpenChange)=\"isOpenChangeHandler($event)\"\n >\n </stream-modal>\n</ng-template>\n\n<ng-template #modalContent>\n <div class=\"str-chat__message-reactions-details\">\n <div class=\"str-chat__message-reactions-details-reaction-types\">\n <div\n *ngFor=\"\n let reactionType of existingReactions;\n trackBy: trackByMessageReaction\n \"\n class=\"str-chat__message-reactions-details-reaction-type\"\n [ngStyle]=\"{\n cursor: shouldHandleReactionClick ? 'pointer' : 'default'\n }\"\n attr.data-testid=\"reaction-details-selector-{{ reactionType }}\"\n [class.str-chat__message-reactions-details-reaction-type--selected]=\"\n reactionType === selectedReactionType\n \"\n (click)=\"selectedReactionType = reactionType; allUsers.scrollTop = 0\"\n (keyup.enter)=\"\n selectedReactionType = reactionType; allUsers.scrollTop = 0\n \"\n >\n <span class=\"emoji str-chat__message-reaction-emoji\">\n {{ getEmojiByReaction(reactionType) }}&nbsp;\n </span>\n <span class=\"str-chat__message-reaction-count\">\n {{ messageReactionCounts[reactionType] }}\n </span>\n </div>\n </div>\n <div\n class=\"\n emoji\n str-chat__message-reaction-emoji str-chat__message-reaction-emoji-big\n \"\n >\n {{ getEmojiByReaction(selectedReactionType!) }}\n </div>\n <div\n #allUsers\n data-testid=\"all-reacting-users\"\n class=\"str-chat__message-reactions-details-reacting-users\"\n >\n <stream-loading-indicator\n *ngIf=\"isLoading; else reactions\"\n ></stream-loading-indicator>\n <ng-template #reactions>\n <div\n *ngFor=\"\n let user of getAllUsersByReaction(selectedReactionType);\n trackBy: trackByUserId\n \"\n class=\"str-chat__message-reactions-details-reacting-user\"\n >\n <stream-avatar-placeholder\n data-testclass=\"avatar\"\n class=\"str-chat__avatar str-chat__avatar--circle\"\n type=\"user\"\n location=\"reaction\"\n [imageUrl]=\"user.image\"\n [name]=\"user.name\"\n [user]=\"user\"\n ></stream-avatar-placeholder>\n <span\n data-testclass=\"reaction-user-username\"\n class=\"str-chat__user-item--name\"\n >{{ user.name }}</span\n >\n </div>\n </ng-template>\n </div>\n </div>\n</ng-template>\n", dependencies: [{ kind: "directive", type: i4.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i4.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i4.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: AvatarPlaceholderComponent, selector: "stream-avatar-placeholder", inputs: ["name", "imageUrl", "location", "channel", "user", "type", "initialsType", "showOnlineIndicator"] }, { kind: "component", type: LoadingIndicatorComponent, selector: "stream-loading-indicator" }, { kind: "component", type: ModalComponent, selector: "stream-modal", inputs: ["isOpen", "content"], outputs: ["isOpenChange"] }, { kind: "pipe", type: i4.AsyncPipe, name: "async" }] });
4510
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: MessageReactionsComponent, decorators: [{
4511
+ type: Component,
4512
+ args: [{ selector: 'stream-message-reactions', template: "<div\n *ngIf=\"existingReactions.length > 0\"\n data-testid=\"reaction-list\"\n class=\"str-chat__reaction-list str-chat__message-reactions-container\"\n [class.str-chat__reaction-list--reverse]=\"true\"\n>\n <ul class=\"str-chat__message-reactions\">\n <li\n *ngFor=\"\n let reactionType of existingReactions;\n trackBy: trackByMessageReaction\n \"\n class=\"str-chat__message-reaction\"\n data-testclass=\"emoji\"\n [ngStyle]=\"{ cursor: shouldHandleReactionClick ? 'pointer' : 'default' }\"\n [class.str-chat__message-reaction-own]=\"isOwnReaction(reactionType)\"\n (click)=\"reactionSelected(reactionType)\"\n (keyup.enter)=\"reactionSelected(reactionType)\"\n >\n <span class=\"emoji str-chat__message-reaction-emoji\">\n {{ getEmojiByReaction(reactionType) }}&nbsp;\n </span>\n <span\n data-testclass=\"reaction-list-reaction-count\"\n class=\"str-chat__message-reaction-count\"\n >\n {{ messageReactionCounts[reactionType] }}\n </span>\n </li>\n <li>\n <span\n data-testid=\"reactions-count\"\n class=\"str-chat__reaction-list--counter\"\n >{{ reactionsCount }}</span\n >\n </li>\n </ul>\n</div>\n\n<ng-container *ngIf=\"selectedReactionType\">\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.modalTemplate$ | async) || defaultModal;\n context: {\n isOpen: !!selectedReactionType,\n messageId: messageId,\n reactionType: selectedReactionType,\n isOpenChangeHandler: isOpenChange,\n content: modalContent\n }\n \"\n ></ng-container>\n</ng-container>\n\n<ng-template\n #defaultModal\n let-isOpen=\"isOpen\"\n let-messageId=\"messageId\"\n let-reactionType=\"reactionType\"\n let-isOpenChangeHandler=\"isOpenChangeHandler\"\n let-content=\"content\"\n>\n <stream-modal\n class=\"str-chat__message-reactions-details-modal\"\n [isOpen]=\"isOpen\"\n [content]=\"content\"\n (isOpenChange)=\"isOpenChangeHandler($event)\"\n >\n </stream-modal>\n</ng-template>\n\n<ng-template #modalContent>\n <div class=\"str-chat__message-reactions-details\">\n <div class=\"str-chat__message-reactions-details-reaction-types\">\n <div\n *ngFor=\"\n let reactionType of existingReactions;\n trackBy: trackByMessageReaction\n \"\n class=\"str-chat__message-reactions-details-reaction-type\"\n [ngStyle]=\"{\n cursor: shouldHandleReactionClick ? 'pointer' : 'default'\n }\"\n attr.data-testid=\"reaction-details-selector-{{ reactionType }}\"\n [class.str-chat__message-reactions-details-reaction-type--selected]=\"\n reactionType === selectedReactionType\n \"\n (click)=\"selectedReactionType = reactionType; allUsers.scrollTop = 0\"\n (keyup.enter)=\"\n selectedReactionType = reactionType; allUsers.scrollTop = 0\n \"\n >\n <span class=\"emoji str-chat__message-reaction-emoji\">\n {{ getEmojiByReaction(reactionType) }}&nbsp;\n </span>\n <span class=\"str-chat__message-reaction-count\">\n {{ messageReactionCounts[reactionType] }}\n </span>\n </div>\n </div>\n <div\n class=\"\n emoji\n str-chat__message-reaction-emoji str-chat__message-reaction-emoji-big\n \"\n >\n {{ getEmojiByReaction(selectedReactionType!) }}\n </div>\n <div\n #allUsers\n data-testid=\"all-reacting-users\"\n class=\"str-chat__message-reactions-details-reacting-users\"\n >\n <stream-loading-indicator\n *ngIf=\"isLoading; else reactions\"\n ></stream-loading-indicator>\n <ng-template #reactions>\n <div\n *ngFor=\"\n let user of getAllUsersByReaction(selectedReactionType);\n trackBy: trackByUserId\n \"\n class=\"str-chat__message-reactions-details-reacting-user\"\n >\n <stream-avatar-placeholder\n data-testclass=\"avatar\"\n class=\"str-chat__avatar str-chat__avatar--circle\"\n type=\"user\"\n location=\"reaction\"\n [imageUrl]=\"user.image\"\n [name]=\"user.name\"\n [user]=\"user\"\n ></stream-avatar-placeholder>\n <span\n data-testclass=\"reaction-user-username\"\n class=\"str-chat__user-item--name\"\n >{{ user.name }}</span\n >\n </div>\n </ng-template>\n </div>\n </div>\n</ng-template>\n" }]
4513
+ }], ctorParameters: function () { return [{ type: i0.ChangeDetectorRef }, { type: ChannelService }, { type: MessageReactionsService }, { type: CustomTemplatesService }]; }, propDecorators: { messageId: [{
4514
+ type: Input
4515
+ }], messageReactionCounts: [{
4516
+ type: Input
4517
+ }], latestReactions: [{
4518
+ type: Input
4519
+ }], ownReactions: [{
4520
+ type: Input
4521
+ }], selectorContainer: [{
4522
+ type: ViewChild,
4523
+ args: ['selectorContainer']
4524
+ }] } });
4525
+
4526
+ /**
4527
+ * 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)](../concepts/message-interactions.mdx).
4528
+ */
4529
+ class MessageComponent {
4530
+ constructor(chatClientService, channelService, customTemplatesService, cdRef, dateParser, messageService, messageActionsService, ngZone) {
4531
+ this.chatClientService = chatClientService;
4532
+ this.channelService = channelService;
4533
+ this.customTemplatesService = customTemplatesService;
4534
+ this.cdRef = cdRef;
4535
+ this.dateParser = dateParser;
4536
+ this.messageService = messageService;
4537
+ this.messageActionsService = messageActionsService;
4538
+ this.ngZone = ngZone;
4539
+ /**
4540
+ * The list of [channel capabilities](https://getstream.io/chat/docs/javascript/channel_capabilities/?language=javascript) that are enabled for the current user, the list of [supported interactions](../concepts/message-interactions.mdx) can be found in our message interaction guide. Unathorized actions won't be displayed on the UI. The [`MessageList`](./MessageListComponent.mdx) component automatically sets this based on [channel capabilities](https://getstream.io/chat/docs/javascript/channel_capabilities/?language=javascript).
4541
+ */
4542
+ this.enabledMessageActions = [];
4543
+ /**
4544
+ * Determines if the message is being dispalyed in a channel or in a [thread](https://getstream.io/chat/docs/javascript/threads/?language=javascript).
4545
+ */
4546
+ this.mode = 'main';
4547
+ /**
4548
+ * Highlighting is used to add visual emphasize to a message when jumping to the message
4549
+ */
4550
+ this.isHighlighted = false;
4551
+ this.isEditedFlagOpened = false;
4552
+ this.messageTextParts = [];
4553
+ this.shouldDisplayTranslationNotice = false;
4554
+ this.displayedMessageTextContent = 'original';
4555
+ this.imageAttachmentModalState = 'closed';
4556
+ this.shouldDisplayThreadLink = false;
4557
+ this.isSentByCurrentUser = false;
4558
+ this.readByText = '';
4559
+ this.lastReadUser = undefined;
4560
+ this.isOnlyReadByMe = false;
4561
+ this.isReadByMultipleUsers = false;
4562
+ this.isMessageDeliveredAndRead = false;
4563
+ this.parsedDate = '';
4564
+ this.pasedEditedDate = '';
4565
+ this.areOptionsVisible = false;
4566
+ this.hasAttachment = false;
4567
+ this.hasReactions = false;
4568
+ this.replyCountParam = {
4569
+ replyCount: undefined,
4314
4570
  };
4571
+ this.areMessageOptionsOpen = false;
4572
+ this.canDisplayReadStatus = false;
4573
+ this.hasTouchSupport = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
4574
+ this.subscriptions = [];
4575
+ this.isViewInited = false;
4576
+ this.urlRegexp = /(?:(?:https?|ftp|file):\/\/|www\.|ftp\.)(?:\([-A-Z0-9+&@#/%=~_|$?!:,.]*\)|[-A-Z0-9+&@#/%=~_|$?!:,.])*(?:\([-A-Z0-9+&@#/%=~_|$?!:,.]*\)|[A-Z0-9+&@#/%=~_|$])/gim;
4577
+ this.emojiRegexp = new RegExp(emojiRegex(), 'g');
4578
+ this.shouldPreventMessageMenuClose = false;
4579
+ this._visibleMessageActionsCount = 0;
4580
+ this.displayAs = this.messageService.displayAs;
4315
4581
  }
4316
- getAttachmentUploadContext() {
4317
- return {
4318
- isMultipleFileUploadEnabled: this.isMultipleFileUploadEnabled,
4319
- attachmentService: this.attachmentService,
4320
- };
4582
+ get visibleMessageActionsCount() {
4583
+ return this._visibleMessageActionsCount;
4321
4584
  }
4322
- deleteUpload(upload) {
4323
- if (this.isUpdate) {
4324
- // Delay delete to avoid modal detecting this click as outside click
4325
- setTimeout(() => {
4326
- void this.attachmentService.deleteAttachment(upload);
4327
- });
4328
- }
4329
- else {
4330
- void this.attachmentService.deleteAttachment(upload);
4585
+ set visibleMessageActionsCount(count) {
4586
+ this._visibleMessageActionsCount = count;
4587
+ if (this.areOptionsVisible && this._visibleMessageActionsCount === 0) {
4588
+ this.areOptionsVisible = false;
4331
4589
  }
4332
4590
  }
4333
- retryUpload(file) {
4334
- void this.attachmentService.retryAttachmentUpload(file);
4335
- }
4336
- clearFileInput() {
4337
- this.fileInput.nativeElement.value = '';
4338
- }
4339
- get isUpdate() {
4340
- return !!this.message;
4591
+ ngOnInit() {
4592
+ this.subscriptions.push(this.chatClientService.user$.subscribe((u) => {
4593
+ if (u?.id !== this.userId) {
4594
+ this.userId = u?.id;
4595
+ this.setIsSentByCurrentUser();
4596
+ this.setLastReadUser();
4597
+ if (this.isViewInited) {
4598
+ this.cdRef.detectChanges();
4599
+ }
4600
+ }
4601
+ }));
4602
+ this.subscriptions.push(this.messageActionsService.customActions$.subscribe(() => {
4603
+ if (this.message) {
4604
+ const numberOfEnabledActions = this.messageActionsService.getAuthorizedMessageActionsCount(this.message, this.enabledMessageActions);
4605
+ if (numberOfEnabledActions !== this.visibleMessageActionsCount) {
4606
+ this.visibleMessageActionsCount = numberOfEnabledActions;
4607
+ if (this.isViewInited) {
4608
+ this.cdRef.detectChanges();
4609
+ }
4610
+ }
4611
+ }
4612
+ }));
4341
4613
  }
4342
- initTextarea() {
4343
- // cleanup previously built textarea
4344
- if (!this.canSendMessages) {
4345
- this.textareaRef = undefined;
4614
+ ngOnChanges(changes) {
4615
+ if (changes.message) {
4616
+ this.shouldDisplayTranslationNotice = false;
4617
+ this.displayedMessageTextContent = 'original';
4618
+ this.createMessageParts();
4619
+ const originalAttachments = this.message?.quoted_message?.attachments;
4620
+ this.quotedMessageAttachments =
4621
+ originalAttachments && originalAttachments.length
4622
+ ? [originalAttachments[0]]
4623
+ : [];
4624
+ this.setIsSentByCurrentUser();
4625
+ this.setLastReadUser();
4626
+ this.readByText = this.message?.readBy
4627
+ ? listUsers(this.message.readBy)
4628
+ : '';
4629
+ this.isOnlyReadByMe = !!(this.message &&
4630
+ this.message.readBy &&
4631
+ this.message.readBy.length === 0);
4632
+ this.isReadByMultipleUsers = !!(this.message &&
4633
+ this.message.readBy &&
4634
+ this.message.readBy.length > 1);
4635
+ this.isMessageDeliveredAndRead = !!(this.message &&
4636
+ this.message.readBy &&
4637
+ this.message.status === 'received' &&
4638
+ this.message.readBy.length > 0);
4639
+ this.parsedDate =
4640
+ (this.message &&
4641
+ this.message.created_at &&
4642
+ this.dateParser.parseDateTime(this.message.created_at)) ||
4643
+ '';
4644
+ this.pasedEditedDate =
4645
+ (this.message &&
4646
+ this.message.message_text_updated_at &&
4647
+ this.dateParser.parseDateTime(new Date(this.message.message_text_updated_at))) ||
4648
+ '';
4649
+ this.hasAttachment =
4650
+ !!this.message?.attachments && !!this.message.attachments.length;
4651
+ this.hasReactions =
4652
+ !!this.message?.reaction_counts &&
4653
+ Object.keys(this.message.reaction_counts).length > 0;
4654
+ this.replyCountParam = { replyCount: this.message?.reply_count };
4346
4655
  }
4347
- if (!this.canSendMessages || this.textareaRef || !this.textareaAnchor) {
4348
- return;
4656
+ if (changes.enabledMessageActions) {
4657
+ this.canReactToMessage =
4658
+ this.enabledMessageActions.indexOf('send-reaction') !== -1;
4659
+ this.canReceiveReadEvents =
4660
+ this.enabledMessageActions.indexOf('read-events') !== -1;
4661
+ this.canDisplayReadStatus =
4662
+ this.canReceiveReadEvents !== false &&
4663
+ this.enabledMessageActions.indexOf('read-events') !== -1;
4349
4664
  }
4350
- const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.textareaType);
4351
- this.textareaRef =
4352
- this.textareaAnchor.viewContainerRef.createComponent(componentFactory);
4353
- this.cdRef.detectChanges();
4354
- }
4355
- async areAttachemntsValid(fileList) {
4356
- if (!fileList) {
4357
- return true;
4665
+ if (changes.message || changes.enabledMessageActions || changes.mode) {
4666
+ this.shouldDisplayThreadLink =
4667
+ !!this.message?.reply_count && this.mode !== 'thread';
4358
4668
  }
4359
- if (!this.appSettings) {
4360
- await this.chatClient.getAppSettings();
4669
+ if (changes.message || changes.mode || changes.enabledMessageActions) {
4670
+ this.areOptionsVisible = this.message
4671
+ ? !(!this.message.type ||
4672
+ this.message.type === 'error' ||
4673
+ this.message.type === 'system' ||
4674
+ this.message.type === 'deleted' ||
4675
+ this.message.type === 'ephemeral' ||
4676
+ this.message.status === 'failed' ||
4677
+ this.message.status === 'sending' ||
4678
+ (this.mode === 'thread' && !this.message.parent_id) ||
4679
+ this.message.deleted_at ||
4680
+ this.enabledMessageActions.length === 0)
4681
+ : false;
4361
4682
  }
4362
- let isValid = true;
4363
- Array.from(fileList).forEach((f) => {
4364
- let hasBlockedExtension;
4365
- let hasBlockedMimeType;
4366
- let hasNotAllowedExtension;
4367
- let hasNotAllowedMimeType;
4368
- if (isImageFile(f)) {
4369
- hasBlockedExtension =
4370
- !!this.appSettings?.image_upload_config?.blocked_file_extensions?.find((ext) => f.name.endsWith(ext));
4371
- hasBlockedMimeType =
4372
- !!this.appSettings?.image_upload_config?.blocked_mime_types?.find((type) => f.type === type);
4373
- hasNotAllowedExtension =
4374
- !!this.appSettings?.image_upload_config?.allowed_file_extensions
4375
- ?.length &&
4376
- !this.appSettings?.image_upload_config?.allowed_file_extensions?.find((ext) => f.name.endsWith(ext));
4377
- hasNotAllowedMimeType =
4378
- !!this.appSettings?.image_upload_config?.allowed_mime_types?.length &&
4379
- !this.appSettings?.image_upload_config?.allowed_mime_types?.find((type) => f.type === type);
4683
+ if (changes.message || changes.enabledMessageActions) {
4684
+ if (this.message) {
4685
+ this.visibleMessageActionsCount =
4686
+ this.messageActionsService.getAuthorizedMessageActionsCount(this.message, this.enabledMessageActions);
4380
4687
  }
4381
4688
  else {
4382
- hasBlockedExtension =
4383
- !!this.appSettings?.file_upload_config?.blocked_file_extensions?.find((ext) => f.name.endsWith(ext));
4384
- hasBlockedMimeType =
4385
- !!this.appSettings?.file_upload_config?.blocked_mime_types?.find((type) => f.type === type);
4386
- hasNotAllowedExtension =
4387
- !!this.appSettings?.file_upload_config?.allowed_file_extensions
4388
- ?.length &&
4389
- !this.appSettings?.file_upload_config?.allowed_file_extensions?.find((ext) => f.name.endsWith(ext));
4390
- hasNotAllowedMimeType =
4391
- !!this.appSettings?.file_upload_config?.allowed_mime_types?.length &&
4392
- !this.appSettings?.file_upload_config?.allowed_mime_types?.find((type) => f.type === type);
4393
- }
4394
- if (hasBlockedExtension ||
4395
- hasBlockedMimeType ||
4396
- hasNotAllowedExtension ||
4397
- hasNotAllowedMimeType) {
4398
- this.notificationService.addTemporaryNotification('streamChat.Error uploading file, extension not supported', undefined, undefined, { name: f.name, ext: f.type });
4399
- isValid = false;
4689
+ this.visibleMessageActionsCount = 0;
4400
4690
  }
4401
- });
4402
- return isValid;
4403
- }
4404
- setCanSendMessages() {
4405
- const capabilities = this.channel?.data?.own_capabilities;
4406
- if (!capabilities) {
4407
- this.canSendMessages = false;
4408
- }
4409
- else {
4410
- this.canSendMessages =
4411
- capabilities.indexOf(this.mode === 'main' ? 'send-message' : 'send-reply') !== -1;
4412
- }
4413
- if (this.isViewInited) {
4414
- this.cdRef.detectChanges();
4415
- this.initTextarea();
4416
4691
  }
4417
4692
  }
4418
- get parentMessageId() {
4419
- let parentMessageId = undefined;
4420
- if (this.mode === 'thread') {
4421
- this.channelService.activeParentMessageId$
4422
- .pipe(first())
4423
- .subscribe((id) => (parentMessageId = id));
4693
+ ngAfterViewInit() {
4694
+ this.isViewInited = true;
4695
+ if (this.hasTouchSupport && this.messageBubble?.nativeElement) {
4696
+ this.ngZone.runOutsideAngular(() => {
4697
+ this.registerMenuTriggerEventHandlers();
4698
+ });
4424
4699
  }
4425
- return parentMessageId;
4426
- }
4427
- startCooldown(cooldown) {
4428
- this.textareaPlaceholder = this.slowModeTextareaPlaceholder;
4429
- this.isCooldownInProgress = true;
4430
- this.cooldown$ = timer(0, 1000).pipe(take(cooldown + 1), map((v) => cooldown - v), tap((v) => {
4431
- if (v === 0) {
4432
- this.stopCooldown();
4433
- }
4434
- }));
4435
- }
4436
- stopCooldown() {
4437
- this.cooldown$ = undefined;
4438
- this.isCooldownInProgress = false;
4439
- this.textareaPlaceholder = this.defaultTextareaPlaceholder;
4440
- }
4441
- }
4442
- MessageInputComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: MessageInputComponent, deps: [{ token: ChannelService }, { token: NotificationService }, { token: AttachmentService }, { token: MessageInputConfigService }, { token: textareaInjectionToken }, { token: i0.ComponentFactoryResolver }, { token: i0.ChangeDetectorRef }, { token: ChatClientService }, { token: EmojiInputService }, { token: CustomTemplatesService }], target: i0.ɵɵFactoryTarget.Component });
4443
- MessageInputComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.0.4", type: MessageInputComponent, selector: "stream-message-input", inputs: { isFileUploadEnabled: "isFileUploadEnabled", areMentionsEnabled: "areMentionsEnabled", mentionScope: "mentionScope", mode: "mode", isMultipleFileUploadEnabled: "isMultipleFileUploadEnabled", message: "message", sendMessage$: "sendMessage$", inputMode: "inputMode", autoFocus: "autoFocus" }, outputs: { messageUpdate: "messageUpdate" }, host: { properties: { "class": "this.class" } }, providers: [AttachmentService, EmojiInputService], viewQueries: [{ propertyName: "fileInput", first: true, predicate: ["fileInput"], descendants: true }, { propertyName: "textareaAnchor", first: true, predicate: TextareaDirective, descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"str-chat__message-input str-chat-angular__message-input\">\n <div *ngIf=\"quotedMessage\" class=\"str-chat__quoted-message-preview-header\">\n <div class=\"str-chat__quoted-message-reply-to-message\">\n {{ \"streamChat.Reply to Message\" | translate }}\n </div>\n <button\n class=\"str-chat__quoted-message-remove\"\n data-testid=\"remove-quote\"\n (click)=\"deselectMessageToQuote()\"\n (keyup.enter)=\"deselectMessageToQuote()\"\n >\n <stream-icon-placeholder icon=\"close\"></stream-icon-placeholder>\n </button>\n </div>\n <ng-container *ngIf=\"canSendMessages; else notAllowed\">\n <div\n class=\"\n str-chat__message-input-inner\n str-chat-angular__message-input-inner\n \"\n >\n <ng-container\n *ngIf=\"isFileUploadEnabled && isFileUploadAuthorized && canSendMessages\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n customAttachmentUploadTemplate || defaultAttachmentUpload;\n context: getAttachmentUploadContext()\n \"\n ></ng-container>\n <ng-template #defaultAttachmentUpload>\n <div\n class=\"str-chat__file-input-container\"\n data-testid=\"file-upload-button\"\n >\n <input\n #fileInput\n type=\"file\"\n class=\"str-chat__file-input\"\n data-testid=\"file-input\"\n [multiple]=\"isMultipleFileUploadEnabled\"\n id=\"{{ fileInputId }}\"\n (change)=\"filesSelected(fileInput.files)\"\n />\n <label class=\"str-chat__file-input-label\" for=\"{{ fileInputId }}\">\n <stream-icon-placeholder icon=\"attach\"></stream-icon-placeholder>\n </label>\n </div>\n </ng-template>\n </ng-container>\n <div class=\"str-chat__message-textarea-container\">\n <div\n *ngIf=\"quotedMessage\"\n data-testid=\"quoted-message-container\"\n class=\"str-chat__quoted-message-preview\"\n >\n <stream-avatar-placeholder\n data-testid=\"qouted-message-avatar\"\n class=\"\n str-chat-angular__avatar-host\n str-chat__message-sender-avatar\n \"\n type=\"user\"\n location=\"quoted-message-sender\"\n [imageUrl]=\"quotedMessage.user?.image\"\n [name]=\"quotedMessage.user?.name || quotedMessage.user?.id\"\n [user]=\"quotedMessage.user || undefined\"\n ></stream-avatar-placeholder>\n <div\n class=\"\n quoted-message-preview-content-inner\n str-chat__quoted-message-bubble\n \"\n >\n <stream-attachment-list\n *ngIf=\"\n quotedMessage?.attachments && quotedMessage?.attachments?.length\n \"\n [attachments]=\"quotedMessageAttachments\"\n [messageId]=\"quotedMessage.id\"\n ></stream-attachment-list>\n <div\n class=\"str-chat__quoted-message-text\"\n data-testid=\"quoted-message-text\"\n [innerHTML]=\"\n quotedMessage.translation ||\n quotedMessage.html ||\n quotedMessage.text\n \"\n ></div>\n </div>\n </div>\n <ng-template\n #defaultAttachmentsPreview\n let-attachmentUploads$=\"attachmentUploads$\"\n let-retryUploadHandler=\"retryUploadHandler\"\n let-deleteUploadHandler=\"deleteUploadHandler\"\n >\n <stream-attachment-preview-list\n class=\"str-chat__attachment-preview-list-angular-host\"\n [attachmentUploads$]=\"attachmentUploads$\"\n (retryAttachmentUpload)=\"retryUploadHandler($event)\"\n (deleteAttachment)=\"deleteUploadHandler($event)\"\n ></stream-attachment-preview-list>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n attachmentPreviewListTemplate || defaultAttachmentsPreview;\n context: getAttachmentPreviewListContext()\n \"\n ></ng-container>\n <div class=\"str-chat__message-textarea-with-emoji-picker\">\n <ng-container\n streamTextarea\n [componentRef]=\"textareaRef\"\n [areMentionsEnabled]=\"areMentionsEnabled\"\n [mentionScope]=\"mentionScope\"\n [inputMode]=\"inputMode\"\n [autoFocus]=\"autoFocus\"\n [placeholder]=\"textareaPlaceholder\"\n [(value)]=\"textareaValue\"\n (valueChange)=\"typingStart$.next()\"\n (send)=\"messageSent()\"\n (userMentions)=\"mentionedUsers = $event\"\n ></ng-container>\n <ng-container *ngIf=\"emojiPickerTemplate\" data-testid=\"emoji-picker\">\n <ng-container\n *ngTemplateOutlet=\"\n emojiPickerTemplate;\n context: getEmojiPickerContext()\n \"\n ></ng-container>\n </ng-container>\n </div>\n </div>\n <button\n *ngIf=\"canSendMessages && !isCooldownInProgress && !message\"\n data-testid=\"send-button\"\n class=\"str-chat__send-button\"\n [disabled]=\"\n (attachmentUploadInProgressCounter$ | async)! > 0 ||\n (!textareaValue && (attachmentUploads$ | async)!.length === 0)\n \"\n (click)=\"messageSent()\"\n (keyup.enter)=\"messageSent()\"\n >\n <stream-icon-placeholder icon=\"send\"></stream-icon-placeholder>\n </button>\n <div\n *ngIf=\"isCooldownInProgress\"\n class=\"str-chat__message-input-cooldown\"\n data-testid=\"cooldown-timer\"\n >\n {{ cooldown$ | async }}\n </div>\n </div>\n </ng-container>\n <ng-template #notAllowed>\n <div\n class=\"str-chat__message-input-not-allowed\"\n data-testid=\"disabled-textarea\"\n >\n {{ disabledTextareaText | translate }}\n </div>\n </ng-template>\n</div>\n", dependencies: [{ kind: "directive", type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i4.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: AvatarPlaceholderComponent, selector: "stream-avatar-placeholder", inputs: ["name", "imageUrl", "location", "channel", "user", "type", "initialsType", "showOnlineIndicator"] }, { kind: "component", type: AttachmentListComponent, selector: "stream-attachment-list", inputs: ["messageId", "parentMessageId", "attachments"], outputs: ["imageModalStateChange"] }, { kind: "component", type: AttachmentPreviewListComponent, selector: "stream-attachment-preview-list", inputs: ["attachmentUploads$"], outputs: ["retryAttachmentUpload", "deleteAttachment"] }, { kind: "directive", type: TextareaDirective, selector: "[streamTextarea]", inputs: ["componentRef", "areMentionsEnabled", "mentionScope", "inputMode", "value", "placeholder", "autoFocus"], outputs: ["valueChange", "send", "userMentions"] }, { kind: "component", type: IconPlaceholderComponent, selector: "stream-icon-placeholder", inputs: ["icon"] }, { kind: "pipe", type: i4.AsyncPipe, name: "async" }, { kind: "pipe", type: i10.TranslatePipe, name: "translate" }] });
4444
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: MessageInputComponent, decorators: [{
4445
- type: Component,
4446
- args: [{ selector: 'stream-message-input', providers: [AttachmentService, EmojiInputService], template: "<div class=\"str-chat__message-input str-chat-angular__message-input\">\n <div *ngIf=\"quotedMessage\" class=\"str-chat__quoted-message-preview-header\">\n <div class=\"str-chat__quoted-message-reply-to-message\">\n {{ \"streamChat.Reply to Message\" | translate }}\n </div>\n <button\n class=\"str-chat__quoted-message-remove\"\n data-testid=\"remove-quote\"\n (click)=\"deselectMessageToQuote()\"\n (keyup.enter)=\"deselectMessageToQuote()\"\n >\n <stream-icon-placeholder icon=\"close\"></stream-icon-placeholder>\n </button>\n </div>\n <ng-container *ngIf=\"canSendMessages; else notAllowed\">\n <div\n class=\"\n str-chat__message-input-inner\n str-chat-angular__message-input-inner\n \"\n >\n <ng-container\n *ngIf=\"isFileUploadEnabled && isFileUploadAuthorized && canSendMessages\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n customAttachmentUploadTemplate || defaultAttachmentUpload;\n context: getAttachmentUploadContext()\n \"\n ></ng-container>\n <ng-template #defaultAttachmentUpload>\n <div\n class=\"str-chat__file-input-container\"\n data-testid=\"file-upload-button\"\n >\n <input\n #fileInput\n type=\"file\"\n class=\"str-chat__file-input\"\n data-testid=\"file-input\"\n [multiple]=\"isMultipleFileUploadEnabled\"\n id=\"{{ fileInputId }}\"\n (change)=\"filesSelected(fileInput.files)\"\n />\n <label class=\"str-chat__file-input-label\" for=\"{{ fileInputId }}\">\n <stream-icon-placeholder icon=\"attach\"></stream-icon-placeholder>\n </label>\n </div>\n </ng-template>\n </ng-container>\n <div class=\"str-chat__message-textarea-container\">\n <div\n *ngIf=\"quotedMessage\"\n data-testid=\"quoted-message-container\"\n class=\"str-chat__quoted-message-preview\"\n >\n <stream-avatar-placeholder\n data-testid=\"qouted-message-avatar\"\n class=\"\n str-chat-angular__avatar-host\n str-chat__message-sender-avatar\n \"\n type=\"user\"\n location=\"quoted-message-sender\"\n [imageUrl]=\"quotedMessage.user?.image\"\n [name]=\"quotedMessage.user?.name || quotedMessage.user?.id\"\n [user]=\"quotedMessage.user || undefined\"\n ></stream-avatar-placeholder>\n <div\n class=\"\n quoted-message-preview-content-inner\n str-chat__quoted-message-bubble\n \"\n >\n <stream-attachment-list\n *ngIf=\"\n quotedMessage?.attachments && quotedMessage?.attachments?.length\n \"\n [attachments]=\"quotedMessageAttachments\"\n [messageId]=\"quotedMessage.id\"\n ></stream-attachment-list>\n <div\n class=\"str-chat__quoted-message-text\"\n data-testid=\"quoted-message-text\"\n [innerHTML]=\"\n quotedMessage.translation ||\n quotedMessage.html ||\n quotedMessage.text\n \"\n ></div>\n </div>\n </div>\n <ng-template\n #defaultAttachmentsPreview\n let-attachmentUploads$=\"attachmentUploads$\"\n let-retryUploadHandler=\"retryUploadHandler\"\n let-deleteUploadHandler=\"deleteUploadHandler\"\n >\n <stream-attachment-preview-list\n class=\"str-chat__attachment-preview-list-angular-host\"\n [attachmentUploads$]=\"attachmentUploads$\"\n (retryAttachmentUpload)=\"retryUploadHandler($event)\"\n (deleteAttachment)=\"deleteUploadHandler($event)\"\n ></stream-attachment-preview-list>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n attachmentPreviewListTemplate || defaultAttachmentsPreview;\n context: getAttachmentPreviewListContext()\n \"\n ></ng-container>\n <div class=\"str-chat__message-textarea-with-emoji-picker\">\n <ng-container\n streamTextarea\n [componentRef]=\"textareaRef\"\n [areMentionsEnabled]=\"areMentionsEnabled\"\n [mentionScope]=\"mentionScope\"\n [inputMode]=\"inputMode\"\n [autoFocus]=\"autoFocus\"\n [placeholder]=\"textareaPlaceholder\"\n [(value)]=\"textareaValue\"\n (valueChange)=\"typingStart$.next()\"\n (send)=\"messageSent()\"\n (userMentions)=\"mentionedUsers = $event\"\n ></ng-container>\n <ng-container *ngIf=\"emojiPickerTemplate\" data-testid=\"emoji-picker\">\n <ng-container\n *ngTemplateOutlet=\"\n emojiPickerTemplate;\n context: getEmojiPickerContext()\n \"\n ></ng-container>\n </ng-container>\n </div>\n </div>\n <button\n *ngIf=\"canSendMessages && !isCooldownInProgress && !message\"\n data-testid=\"send-button\"\n class=\"str-chat__send-button\"\n [disabled]=\"\n (attachmentUploadInProgressCounter$ | async)! > 0 ||\n (!textareaValue && (attachmentUploads$ | async)!.length === 0)\n \"\n (click)=\"messageSent()\"\n (keyup.enter)=\"messageSent()\"\n >\n <stream-icon-placeholder icon=\"send\"></stream-icon-placeholder>\n </button>\n <div\n *ngIf=\"isCooldownInProgress\"\n class=\"str-chat__message-input-cooldown\"\n data-testid=\"cooldown-timer\"\n >\n {{ cooldown$ | async }}\n </div>\n </div>\n </ng-container>\n <ng-template #notAllowed>\n <div\n class=\"str-chat__message-input-not-allowed\"\n data-testid=\"disabled-textarea\"\n >\n {{ disabledTextareaText | translate }}\n </div>\n </ng-template>\n</div>\n" }]
4447
- }], ctorParameters: function () { return [{ type: ChannelService }, { type: NotificationService }, { type: AttachmentService }, { type: MessageInputConfigService }, { type: i0.Type, decorators: [{
4448
- type: Inject,
4449
- args: [textareaInjectionToken]
4450
- }] }, { type: i0.ComponentFactoryResolver }, { type: i0.ChangeDetectorRef }, { type: ChatClientService }, { type: EmojiInputService }, { type: CustomTemplatesService }]; }, propDecorators: { isFileUploadEnabled: [{
4451
- type: Input
4452
- }], areMentionsEnabled: [{
4453
- type: Input
4454
- }], mentionScope: [{
4455
- type: Input
4456
- }], mode: [{
4457
- type: Input
4458
- }], isMultipleFileUploadEnabled: [{
4459
- type: Input
4460
- }], message: [{
4461
- type: Input
4462
- }], sendMessage$: [{
4463
- type: Input
4464
- }], inputMode: [{
4465
- type: Input
4466
- }], autoFocus: [{
4467
- type: Input
4468
- }], messageUpdate: [{
4469
- type: Output
4470
- }], class: [{
4471
- type: HostBinding
4472
- }], fileInput: [{
4473
- type: ViewChild,
4474
- args: ['fileInput']
4475
- }], textareaAnchor: [{
4476
- type: ViewChild,
4477
- args: [TextareaDirective, { static: false }]
4478
- }] } });
4479
-
4480
- /**
4481
- * The edit message form displays a modal that's opened when a user edits a message. The component uses the [`MessageActionsService`](../../services/MessageActionsService) to know which message is being edited.
4482
- *
4483
- * By default this is displayed within the [`stream-channel` component](../../components/ChannelComponent).
4484
- */
4485
- class EditMessageFormComponent {
4486
- constructor(customTemplatesService, messageActionsService) {
4487
- this.customTemplatesService = customTemplatesService;
4488
- this.messageActionsService = messageActionsService;
4489
- this.class = 'str-chat-angular__edit-message-form';
4490
- this.isModalOpen = false;
4491
- this.sendMessageSubject = new Subject();
4492
- this.subscriptions = [];
4493
- this.sendMessage$ = this.sendMessageSubject.asObservable();
4494
- }
4495
- ngOnInit() {
4496
- this.messageActionsService.messageToEdit$.subscribe((message) => {
4497
- if ((message && !this.isModalOpen) || (!message && this.isModalOpen)) {
4498
- this.message = message;
4499
- this.isModalOpen = !!message;
4500
- }
4501
- });
4502
4700
  }
4503
4701
  ngOnDestroy() {
4504
4702
  this.subscriptions.forEach((s) => s.unsubscribe());
4505
4703
  }
4506
- getEditModalContext() {
4507
- return {
4508
- isOpen: this.isModalOpen,
4509
- isOpenChangeHandler: (isOpen) => {
4510
- this.isModalOpen = isOpen;
4511
- if (!this.isModalOpen) {
4512
- this.dismissed();
4513
- }
4514
- },
4515
- content: this.modalContent,
4516
- };
4517
- }
4518
- getMessageInputContext() {
4519
- return {
4520
- message: this.message,
4521
- messageUpdateHandler: () => {
4522
- this.dismissed();
4523
- },
4524
- isFileUploadEnabled: undefined,
4525
- areMentionsEnabled: undefined,
4526
- isMultipleFileUploadEnabled: undefined,
4527
- mentionScope: undefined,
4528
- mode: undefined,
4529
- sendMessage$: this.sendMessage$,
4530
- };
4531
- }
4532
- sendClicked() {
4533
- this.sendMessageSubject.next();
4704
+ mousePushedDown(event) {
4705
+ if (!this.hasTouchSupport ||
4706
+ event.button !== 0 ||
4707
+ !this.areOptionsVisible) {
4708
+ return;
4709
+ }
4710
+ this.startMessageMenuShowTimer({ fromTouch: false });
4534
4711
  }
4535
- dismissed() {
4536
- this.isModalOpen = false;
4537
- this.message = undefined;
4538
- this.messageActionsService.messageToEdit$.next(undefined);
4712
+ mouseReleased() {
4713
+ this.stopMessageMenuShowTimer();
4539
4714
  }
4540
- }
4541
- EditMessageFormComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: EditMessageFormComponent, deps: [{ token: CustomTemplatesService }, { token: MessageActionsService }], target: i0.ɵɵFactoryTarget.Component });
4542
- EditMessageFormComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.0.4", type: EditMessageFormComponent, selector: "stream-edit-message-form", host: { properties: { "class": "this.class" } }, viewQueries: [{ propertyName: "modalContent", first: true, predicate: ["editMessageForm"], descendants: true, static: true }], ngImport: i0, template: "<ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.modalTemplate$ | async) || defaultModal;\n context: getEditModalContext()\n \"\n></ng-container>\n\n<ng-template\n #defaultModal\n let-isOpen=\"isOpen\"\n let-isOpenChangeHandler=\"isOpenChangeHandler\"\n let-content=\"content\"\n>\n <stream-modal\n *ngIf=\"isOpen\"\n [isOpen]=\"isOpen\"\n [content]=\"content\"\n (isOpenChange)=\"isOpenChangeHandler($event)\"\n >\n </stream-modal>\n</ng-template>\n\n<ng-template #editMessageForm>\n <div class=\"str-chat__edit-message-form\">\n <ng-template\n #defaultInput\n let-messageInput=\"message\"\n let-messageUpdateHandler=\"messageUpdateHandler\"\n let-sendMessage$Input=\"sendMessage$\"\n >\n <stream-message-input\n [message]=\"messageInput\"\n [sendMessage$]=\"sendMessage$Input\"\n (messageUpdate)=\"messageUpdateHandler()\"\n ></stream-message-input>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.messageInputTemplate$ | async) || defaultInput;\n context: getMessageInputContext()\n \"\n >\n </ng-container>\n\n <stream-notification-list></stream-notification-list>\n <div\n class=\"\n str-chat__message-team-form-footer\n str-chat__message-team-form-footer-angular\n \"\n >\n <div class=\"str-chat__edit-message-form-options\">\n <button\n class=\"str-chat__edit-message-cancel\"\n translate\n data-testid=\"cancel-button\"\n (click)=\"dismissed()\"\n >\n streamChat.Cancel\n </button>\n <button\n type=\"submit\"\n translate\n class=\"str-chat__edit-message-send\"\n data-testid=\"send-button\"\n (click)=\"sendClicked()\"\n (keyup.enter)=\"sendClicked()\"\n >\n streamChat.Send\n </button>\n </div>\n </div>\n </div>\n</ng-template>\n", dependencies: [{ kind: "directive", type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i4.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i10.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "component", type: MessageInputComponent, selector: "stream-message-input", inputs: ["isFileUploadEnabled", "areMentionsEnabled", "mentionScope", "mode", "isMultipleFileUploadEnabled", "message", "sendMessage$", "inputMode", "autoFocus"], outputs: ["messageUpdate"] }, { kind: "component", type: NotificationListComponent, selector: "stream-notification-list" }, { kind: "component", type: ModalComponent, selector: "stream-modal", inputs: ["isOpen", "content"], outputs: ["isOpenChange"] }, { kind: "pipe", type: i4.AsyncPipe, name: "async" }] });
4543
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: EditMessageFormComponent, decorators: [{
4544
- type: Component,
4545
- args: [{ selector: 'stream-edit-message-form', template: "<ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.modalTemplate$ | async) || defaultModal;\n context: getEditModalContext()\n \"\n></ng-container>\n\n<ng-template\n #defaultModal\n let-isOpen=\"isOpen\"\n let-isOpenChangeHandler=\"isOpenChangeHandler\"\n let-content=\"content\"\n>\n <stream-modal\n *ngIf=\"isOpen\"\n [isOpen]=\"isOpen\"\n [content]=\"content\"\n (isOpenChange)=\"isOpenChangeHandler($event)\"\n >\n </stream-modal>\n</ng-template>\n\n<ng-template #editMessageForm>\n <div class=\"str-chat__edit-message-form\">\n <ng-template\n #defaultInput\n let-messageInput=\"message\"\n let-messageUpdateHandler=\"messageUpdateHandler\"\n let-sendMessage$Input=\"sendMessage$\"\n >\n <stream-message-input\n [message]=\"messageInput\"\n [sendMessage$]=\"sendMessage$Input\"\n (messageUpdate)=\"messageUpdateHandler()\"\n ></stream-message-input>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.messageInputTemplate$ | async) || defaultInput;\n context: getMessageInputContext()\n \"\n >\n </ng-container>\n\n <stream-notification-list></stream-notification-list>\n <div\n class=\"\n str-chat__message-team-form-footer\n str-chat__message-team-form-footer-angular\n \"\n >\n <div class=\"str-chat__edit-message-form-options\">\n <button\n class=\"str-chat__edit-message-cancel\"\n translate\n data-testid=\"cancel-button\"\n (click)=\"dismissed()\"\n >\n streamChat.Cancel\n </button>\n <button\n type=\"submit\"\n translate\n class=\"str-chat__edit-message-send\"\n data-testid=\"send-button\"\n (click)=\"sendClicked()\"\n (keyup.enter)=\"sendClicked()\"\n >\n streamChat.Send\n </button>\n </div>\n </div>\n </div>\n</ng-template>\n" }]
4546
- }], ctorParameters: function () { return [{ type: CustomTemplatesService }, { type: MessageActionsService }]; }, propDecorators: { class: [{
4547
- type: HostBinding
4548
- }], modalContent: [{
4549
- type: ViewChild,
4550
- args: ['editMessageForm', { static: true }]
4551
- }] } });
4552
-
4553
- /**
4554
- * The component watches for the [`channelService.bouncedMessage$` stream](../../services/ChannelService/#bouncedmessage) and opens the bounce modal if a message is emitted.
4555
- *
4556
- * To bounce messages, you need to set up [semantic filters for moderation](https://getstream.io/automated-moderation/docs/automod_configuration/?q=semantic%20filters).
4557
- */
4558
- class MessageBouncePromptComponent {
4559
- constructor(channelService, customTemplatesService, messageActionsService) {
4560
- this.channelService = channelService;
4561
- this.customTemplatesService = customTemplatesService;
4562
- this.messageActionsService = messageActionsService;
4563
- this.class = 'str-chat__message-bounce-prompt';
4564
- this.isModalOpen = false;
4565
- this.subscriptions = [];
4566
- this.messageBounceModalOpenChanged = (isOpen) => {
4567
- this.isModalOpen = isOpen;
4568
- if (!isOpen) {
4569
- this.message = undefined;
4570
- this.channelService.bouncedMessage$.next(undefined);
4571
- }
4572
- };
4573
- this.subscriptions.push(this.channelService.bouncedMessage$.subscribe((m) => {
4574
- if (m !== this.message) {
4575
- this.message = m;
4576
- if (this.message) {
4577
- this.isModalOpen = true;
4578
- }
4579
- }
4580
- }));
4715
+ touchStarted() {
4716
+ if (!this.areOptionsVisible) {
4717
+ return;
4718
+ }
4719
+ this.startMessageMenuShowTimer({ fromTouch: true });
4581
4720
  }
4582
- ngOnDestroy() {
4583
- this.subscriptions.forEach((s) => s.unsubscribe());
4721
+ touchEnded() {
4722
+ this.stopMessageMenuShowTimer();
4584
4723
  }
4585
- async resendMessage() {
4586
- this.isModalOpen = false;
4587
- await this.channelService.resendMessage(this.message);
4588
- this.message = undefined;
4589
- this.channelService.bouncedMessage$.next(undefined);
4724
+ messageBubbleClicked(event) {
4725
+ if (!this.hasTouchSupport) {
4726
+ return;
4727
+ }
4728
+ if (this.shouldPreventMessageMenuClose) {
4729
+ event.stopPropagation();
4730
+ this.shouldPreventMessageMenuClose = false;
4731
+ }
4732
+ else if (this.areMessageOptionsOpen) {
4733
+ this.messageMenuTrigger?.hide();
4734
+ }
4590
4735
  }
4591
- async deleteMessage() {
4736
+ messageOptionsButtonClicked() {
4592
4737
  if (!this.message) {
4593
4738
  return;
4594
4739
  }
4595
- this.isModalOpen = false;
4596
- await this.channelService.deleteMessage(this.message, true);
4597
- this.message = undefined;
4598
- this.channelService.bouncedMessage$.next(undefined);
4740
+ if (this.messageActionsService.customActionClickHandler) {
4741
+ this.messageActionsService.customActionClickHandler({
4742
+ message: this.message,
4743
+ enabledActions: this.enabledMessageActions,
4744
+ customActions: this.messageActionsService.customActions$.getValue(),
4745
+ isMine: this.isSentByCurrentUser,
4746
+ messageTextHtmlElement: this.messageTextElement?.nativeElement,
4747
+ });
4748
+ }
4749
+ else {
4750
+ this.areMessageOptionsOpen = !this.areMessageOptionsOpen;
4751
+ }
4599
4752
  }
4600
- editMessage() {
4601
- this.isModalOpen = false;
4602
- this.messageActionsService.messageToEdit$.next(this.message);
4603
- this.message = undefined;
4604
- this.channelService.bouncedMessage$.next(undefined);
4753
+ messageActionsBoxClicked(floatingContent) {
4754
+ floatingContent.hide();
4605
4755
  }
4606
- }
4607
- MessageBouncePromptComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: MessageBouncePromptComponent, deps: [{ token: ChannelService }, { token: CustomTemplatesService }, { token: MessageActionsService }], target: i0.ɵɵFactoryTarget.Component });
4608
- MessageBouncePromptComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.0.4", type: MessageBouncePromptComponent, selector: "stream-message-bounce-prompt", host: { properties: { "class": "this.class" } }, ngImport: i0, template: "<ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.modalTemplate$ | async) || defaultModal;\n context: {\n message: message,\n isOpen: isModalOpen,\n isOpenChangeHandler: messageBounceModalOpenChanged,\n content: modalContent\n }\n \"\n></ng-container>\n\n<ng-template\n #defaultModal\n let-isOpen=\"isOpen\"\n let-isOpenChangeHandler=\"isOpenChangeHandler\"\n let-content=\"content\"\n>\n <stream-modal\n *ngIf=\"isOpen\"\n [isOpen]=\"isOpen\"\n [content]=\"content\"\n (isOpenChange)=\"isOpenChangeHandler($event)\"\n >\n </stream-modal>\n</ng-template>\n\n<ng-template #modalContent>\n <div\n class=\"str-chat__message-bounce-prompt\"\n data-testid=\"message-bounce-prompt\"\n >\n <div class=\"str-chat__message-bounce-prompt-header\">\n {{\n \"streamChat.This message did not meet our content guidelines\"\n | translate\n }}\n </div>\n <div class=\"str-chat__message-bounce-actions\">\n <button\n class=\"str-chat__message-bounce-edit\"\n data-testid=\"message-bounce-edit\"\n type=\"button\"\n (click)=\"editMessage()\"\n (keyup.enter)=\"editMessage()\"\n >\n {{ \"streamChat.Edit Message\" | translate }}\n </button>\n <button\n class=\"str-chat__message-bounce-send\"\n data-testid=\"message-bounce-send\"\n (click)=\"resendMessage()\"\n (keyup.enter)=\"resendMessage()\"\n >\n {{ \"streamChat.Send Anyway\" | translate }}\n </button>\n <button\n class=\"str-chat__message-bounce-delete\"\n data-testid=\"message-bounce-delete\"\n (click)=\"deleteMessage()\"\n (keyup.enter)=\"deleteMessage()\"\n >\n {{ \"streamChat.Delete\" | translate }}\n </button>\n </div>\n </div>\n</ng-template>\n", dependencies: [{ kind: "directive", type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i4.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: ModalComponent, selector: "stream-modal", inputs: ["isOpen", "content"], outputs: ["isOpenChange"] }, { kind: "pipe", type: i4.AsyncPipe, name: "async" }, { kind: "pipe", type: i10.TranslatePipe, name: "translate" }] });
4609
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: MessageBouncePromptComponent, decorators: [{
4610
- type: Component,
4611
- args: [{ selector: 'stream-message-bounce-prompt', template: "<ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.modalTemplate$ | async) || defaultModal;\n context: {\n message: message,\n isOpen: isModalOpen,\n isOpenChangeHandler: messageBounceModalOpenChanged,\n content: modalContent\n }\n \"\n></ng-container>\n\n<ng-template\n #defaultModal\n let-isOpen=\"isOpen\"\n let-isOpenChangeHandler=\"isOpenChangeHandler\"\n let-content=\"content\"\n>\n <stream-modal\n *ngIf=\"isOpen\"\n [isOpen]=\"isOpen\"\n [content]=\"content\"\n (isOpenChange)=\"isOpenChangeHandler($event)\"\n >\n </stream-modal>\n</ng-template>\n\n<ng-template #modalContent>\n <div\n class=\"str-chat__message-bounce-prompt\"\n data-testid=\"message-bounce-prompt\"\n >\n <div class=\"str-chat__message-bounce-prompt-header\">\n {{\n \"streamChat.This message did not meet our content guidelines\"\n | translate\n }}\n </div>\n <div class=\"str-chat__message-bounce-actions\">\n <button\n class=\"str-chat__message-bounce-edit\"\n data-testid=\"message-bounce-edit\"\n type=\"button\"\n (click)=\"editMessage()\"\n (keyup.enter)=\"editMessage()\"\n >\n {{ \"streamChat.Edit Message\" | translate }}\n </button>\n <button\n class=\"str-chat__message-bounce-send\"\n data-testid=\"message-bounce-send\"\n (click)=\"resendMessage()\"\n (keyup.enter)=\"resendMessage()\"\n >\n {{ \"streamChat.Send Anyway\" | translate }}\n </button>\n <button\n class=\"str-chat__message-bounce-delete\"\n data-testid=\"message-bounce-delete\"\n (click)=\"deleteMessage()\"\n (keyup.enter)=\"deleteMessage()\"\n >\n {{ \"streamChat.Delete\" | translate }}\n </button>\n </div>\n </div>\n</ng-template>\n" }]
4612
- }], ctorParameters: function () { return [{ type: ChannelService }, { type: CustomTemplatesService }, { type: MessageActionsService }]; }, propDecorators: { class: [{
4613
- type: HostBinding
4614
- }] } });
4615
-
4616
- /**
4617
- * The `Channel` component is a container component that displays the [`ChannelHeader`](./ChannelHeaderComponent.mdx), [`MessageList`](./MessageListComponent.mdx), [`NotificationList`](./NotificationListComponent.mdx) and [`MessageInput`](./MessageInputComponent.mdx) components. You can also provide the [`Thread`](./ThreadComponent.mdx) component to use message [threads](https://getstream.io/chat/docs/javascript/threads/?language=javascript).
4618
- */
4619
- class ChannelComponent {
4620
- constructor(channelService, themeService, customTemplatesService) {
4621
- this.channelService = channelService;
4622
- this.themeService = themeService;
4623
- this.customTemplatesService = customTemplatesService;
4624
- this.subscriptions = [];
4625
- this.isError$ = combineLatest([
4626
- this.channelService.channelQueryState$,
4627
- this.channelService.activeChannel$,
4628
- ]).pipe(map(([state, activeChannel]) => {
4629
- return !activeChannel && state?.state === 'error';
4630
- }));
4631
- this.isInitializing$ = combineLatest([
4632
- this.channelService.channelQueryState$,
4633
- this.channelService.activeChannel$,
4634
- ]).pipe(map(([state, activeChannel]) => {
4635
- return !activeChannel && state?.state === 'in-progress';
4636
- }));
4637
- this.isActiveThread$ = this.channelService.activeParentMessageId$.pipe(map((id) => !!id));
4638
- this.theme$ = this.themeService.theme$;
4639
- this.isActiveChannel$ = this.channelService.activeChannel$.pipe(map((c) => !!c));
4756
+ getAttachmentListContext() {
4757
+ return {
4758
+ messageId: this.message?.id || '',
4759
+ attachments: this.message?.attachments || [],
4760
+ parentMessageId: this.message?.parent_id,
4761
+ imageModalStateChangeHandler: (state) => (this.imageAttachmentModalState = state),
4762
+ };
4640
4763
  }
4641
- }
4642
- ChannelComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: ChannelComponent, deps: [{ token: ChannelService }, { token: ThemeService }, { token: CustomTemplatesService }], target: i0.ɵɵFactoryTarget.Component });
4643
- ChannelComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.0.4", type: ChannelComponent, selector: "stream-channel", ngImport: i0, template: "<div\n class=\"str-chat str-chat-channel messaging str-chat__channel str-chat__theme-{{\n theme$ | async\n }}\"\n>\n <div\n *ngIf=\"\n (isError$ | async) === false &&\n (isInitializing$ | async) === false &&\n (isActiveChannel$ | async) === true;\n else noChannel\n \"\n class=\"str-chat__container\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.editMessageFormTemplate$ | async) ||\n defaultEditMessageForm\n \"\n ></ng-container>\n <ng-template #defaultEditMessageForm>\n <stream-edit-message-form></stream-edit-message-form>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.messageBouncePromptTemplate$ | async) ||\n defaultMessageBouncePrompt\n \"\n ></ng-container>\n <ng-template #defaultMessageBouncePrompt>\n <stream-message-bounce-prompt></stream-message-bounce-prompt>\n </ng-template>\n <div class=\"str-chat__main-panel\">\n <ng-content></ng-content>\n </div>\n <ng-content\n *ngIf=\"isActiveThread$ | async\"\n select='[name=\"thread\"]'\n ></ng-content>\n </div>\n <ng-template #noChannel>\n <div\n *ngIf=\"\n (isInitializing$ | async) === false &&\n ((isError$ | async) === true || (isActiveChannel$ | async) === false)\n \"\n class=\"str-chat__empty-channel\"\n >\n <stream-icon icon=\"chat-bubble\"></stream-icon>\n <p class=\"str-chat__empty-channel-text\">\n {{ \"streamChat.No chats here yet\u2026\" | translate }}\n </p>\n <div class=\"str-chat__empty-channel-notifications\">\n <stream-notification-list></stream-notification-list>\n </div>\n </div>\n <div\n *ngIf=\"\n (isInitializing$ | async) === true &&\n (isError$ | async) === false &&\n (isActiveChannel$ | async) === false\n \"\n class=\"str-chat__loading-channel\"\n >\n <div class=\"str-chat__loading-channel-header\">\n <div class=\"str-chat__loading-channel-header-avatar\"></div>\n <div class=\"str-chat__loading-channel-header-end\">\n <div class=\"str-chat__loading-channel-header-name\"></div>\n <div class=\"str-chat__loading-channel-header-info\"></div>\n </div>\n </div>\n <div class=\"str-chat__loading-channel-message-list\">\n <div class=\"str-chat__loading-channel-message\">\n <div class=\"str-chat__loading-channel-message-avatar\"></div>\n <div class=\"str-chat__loading-channel-message-end\">\n <div class=\"str-chat__loading-channel-message-sender\"></div>\n <div class=\"str-chat__loading-channel-message-last-row\">\n <div class=\"str-chat__loading-channel-message-text\"></div>\n <div class=\"str-chat__loading-channel-message-date\"></div>\n </div>\n </div>\n </div>\n <div class=\"str-chat__loading-channel-message\">\n <div class=\"str-chat__loading-channel-message-avatar\"></div>\n <div class=\"str-chat__loading-channel-message-end\">\n <div class=\"str-chat__loading-channel-message-sender\"></div>\n <div class=\"str-chat__loading-channel-message-last-row\">\n <div class=\"str-chat__loading-channel-message-text\"></div>\n <div class=\"str-chat__loading-channel-message-date\"></div>\n </div>\n </div>\n </div>\n <div class=\"str-chat__loading-channel-message\">\n <div class=\"str-chat__loading-channel-message-avatar\"></div>\n <div class=\"str-chat__loading-channel-message-end\">\n <div class=\"str-chat__loading-channel-message-sender\"></div>\n <div class=\"str-chat__loading-channel-message-last-row\">\n <div class=\"str-chat__loading-channel-message-text\"></div>\n <div class=\"str-chat__loading-channel-message-date\"></div>\n </div>\n </div>\n </div>\n </div>\n <div class=\"str-chat__loading-channel-message-input-row\">\n <div class=\"str-chat__loading-channel-message-input\"></div>\n <div class=\"str-chat__loading-channel-message-send\"></div>\n </div>\n </div>\n </ng-template>\n</div>\n", dependencies: [{ kind: "directive", type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i4.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: IconComponent, selector: "stream-icon", inputs: ["icon"] }, { kind: "component", type: NotificationListComponent, selector: "stream-notification-list" }, { kind: "component", type: EditMessageFormComponent, selector: "stream-edit-message-form" }, { kind: "component", type: MessageBouncePromptComponent, selector: "stream-message-bounce-prompt" }, { kind: "pipe", type: i4.AsyncPipe, name: "async" }, { kind: "pipe", type: i10.TranslatePipe, name: "translate" }] });
4644
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: ChannelComponent, decorators: [{
4645
- type: Component,
4646
- args: [{ selector: 'stream-channel', template: "<div\n class=\"str-chat str-chat-channel messaging str-chat__channel str-chat__theme-{{\n theme$ | async\n }}\"\n>\n <div\n *ngIf=\"\n (isError$ | async) === false &&\n (isInitializing$ | async) === false &&\n (isActiveChannel$ | async) === true;\n else noChannel\n \"\n class=\"str-chat__container\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.editMessageFormTemplate$ | async) ||\n defaultEditMessageForm\n \"\n ></ng-container>\n <ng-template #defaultEditMessageForm>\n <stream-edit-message-form></stream-edit-message-form>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.messageBouncePromptTemplate$ | async) ||\n defaultMessageBouncePrompt\n \"\n ></ng-container>\n <ng-template #defaultMessageBouncePrompt>\n <stream-message-bounce-prompt></stream-message-bounce-prompt>\n </ng-template>\n <div class=\"str-chat__main-panel\">\n <ng-content></ng-content>\n </div>\n <ng-content\n *ngIf=\"isActiveThread$ | async\"\n select='[name=\"thread\"]'\n ></ng-content>\n </div>\n <ng-template #noChannel>\n <div\n *ngIf=\"\n (isInitializing$ | async) === false &&\n ((isError$ | async) === true || (isActiveChannel$ | async) === false)\n \"\n class=\"str-chat__empty-channel\"\n >\n <stream-icon icon=\"chat-bubble\"></stream-icon>\n <p class=\"str-chat__empty-channel-text\">\n {{ \"streamChat.No chats here yet\u2026\" | translate }}\n </p>\n <div class=\"str-chat__empty-channel-notifications\">\n <stream-notification-list></stream-notification-list>\n </div>\n </div>\n <div\n *ngIf=\"\n (isInitializing$ | async) === true &&\n (isError$ | async) === false &&\n (isActiveChannel$ | async) === false\n \"\n class=\"str-chat__loading-channel\"\n >\n <div class=\"str-chat__loading-channel-header\">\n <div class=\"str-chat__loading-channel-header-avatar\"></div>\n <div class=\"str-chat__loading-channel-header-end\">\n <div class=\"str-chat__loading-channel-header-name\"></div>\n <div class=\"str-chat__loading-channel-header-info\"></div>\n </div>\n </div>\n <div class=\"str-chat__loading-channel-message-list\">\n <div class=\"str-chat__loading-channel-message\">\n <div class=\"str-chat__loading-channel-message-avatar\"></div>\n <div class=\"str-chat__loading-channel-message-end\">\n <div class=\"str-chat__loading-channel-message-sender\"></div>\n <div class=\"str-chat__loading-channel-message-last-row\">\n <div class=\"str-chat__loading-channel-message-text\"></div>\n <div class=\"str-chat__loading-channel-message-date\"></div>\n </div>\n </div>\n </div>\n <div class=\"str-chat__loading-channel-message\">\n <div class=\"str-chat__loading-channel-message-avatar\"></div>\n <div class=\"str-chat__loading-channel-message-end\">\n <div class=\"str-chat__loading-channel-message-sender\"></div>\n <div class=\"str-chat__loading-channel-message-last-row\">\n <div class=\"str-chat__loading-channel-message-text\"></div>\n <div class=\"str-chat__loading-channel-message-date\"></div>\n </div>\n </div>\n </div>\n <div class=\"str-chat__loading-channel-message\">\n <div class=\"str-chat__loading-channel-message-avatar\"></div>\n <div class=\"str-chat__loading-channel-message-end\">\n <div class=\"str-chat__loading-channel-message-sender\"></div>\n <div class=\"str-chat__loading-channel-message-last-row\">\n <div class=\"str-chat__loading-channel-message-text\"></div>\n <div class=\"str-chat__loading-channel-message-date\"></div>\n </div>\n </div>\n </div>\n </div>\n <div class=\"str-chat__loading-channel-message-input-row\">\n <div class=\"str-chat__loading-channel-message-input\"></div>\n <div class=\"str-chat__loading-channel-message-send\"></div>\n </div>\n </div>\n </ng-template>\n</div>\n" }]
4647
- }], ctorParameters: function () { return [{ type: ChannelService }, { type: ThemeService }, { type: CustomTemplatesService }]; } });
4648
-
4649
- const listUsers = (users) => {
4650
- let outStr = '';
4651
- const slicedArr = users.map((item) => item.name || item.id).slice(0, 5);
4652
- const restLength = users.length - slicedArr.length;
4653
- const commaSeparatedUsers = slicedArr.join(', ');
4654
- outStr = commaSeparatedUsers;
4655
- if (restLength > 0) {
4656
- outStr += ` +${restLength}`;
4764
+ getMessageContext() {
4765
+ return {
4766
+ message: this.message,
4767
+ enabledMessageActions: this.enabledMessageActions,
4768
+ isHighlighted: this.isHighlighted,
4769
+ isLastSentMessage: this.isLastSentMessage,
4770
+ mode: this.mode,
4771
+ customActions: this.messageActionsService.customActions$.getValue(),
4772
+ parsedDate: this.parsedDate,
4773
+ scroll$: this.scroll$,
4774
+ };
4657
4775
  }
4658
- return outStr;
4659
- };
4660
-
4661
- const getChannelDisplayText = (channel, currentUser) => {
4662
- if (channel.data?.name) {
4663
- return channel.data.name;
4776
+ getQuotedMessageAttachmentListContext() {
4777
+ return {
4778
+ messageId: this.message?.quoted_message?.id || '',
4779
+ attachments: this.quotedMessageAttachments,
4780
+ parentMessageId: this?.message?.quoted_message?.parent_id,
4781
+ };
4664
4782
  }
4665
- if (channel.state.members && Object.keys(channel.state.members).length > 0) {
4666
- const members = Object.values(channel.state.members)
4667
- .map((m) => m.user || { id: m.user_id })
4668
- .filter((m) => m.id !== currentUser?.id);
4669
- return listUsers(members);
4783
+ getMessageReactionsContext() {
4784
+ return {
4785
+ messageReactionCounts: this.message?.reaction_counts || {},
4786
+ latestReactions: this.message?.latest_reactions || [],
4787
+ messageId: this.message?.id,
4788
+ ownReactions: this.message?.own_reactions || [],
4789
+ };
4670
4790
  }
4671
- return channel.id;
4672
- };
4673
-
4674
- /**
4675
- * The `ChannelHeader` component displays the avatar and name of the currently active channel along with member and watcher information. You can read about [the difference between members and watchers](https://getstream.io/chat/docs/javascript/watch_channel/?language=javascript#watchers-vs-members) in the platform documentation. Please note that number of watchers is only displayed if the user has [`connect-events` capability](https://getstream.io/chat/docs/javascript/channel_capabilities/?language=javascript)
4676
- */
4677
- class ChannelHeaderComponent {
4678
- constructor(channelService, customTemplatesService, cdRef, chatClientService) {
4679
- this.channelService = channelService;
4680
- this.customTemplatesService = customTemplatesService;
4681
- this.cdRef = cdRef;
4682
- this.chatClientService = chatClientService;
4683
- this.subscriptions = [];
4684
- this.channelService.activeChannel$.subscribe((c) => {
4685
- this.activeChannel = c;
4686
- const capabilities = this.activeChannel?.data
4687
- ?.own_capabilities;
4688
- if (!capabilities) {
4689
- return;
4690
- }
4691
- this.canReceiveConnectEvents =
4692
- capabilities.indexOf('connect-events') !== -1;
4693
- });
4791
+ messageClicked() {
4792
+ if (this.message?.status === 'failed' &&
4793
+ this.message?.errorStatusCode !== 403) {
4794
+ this.resendMessage();
4795
+ }
4796
+ else if (this.message?.type === 'error' &&
4797
+ this.message?.moderation_details) {
4798
+ this.openMessageBouncePrompt();
4799
+ }
4800
+ else {
4801
+ this.isEditedFlagOpened = !this.isEditedFlagOpened;
4802
+ }
4694
4803
  }
4695
- ngOnInit() {
4696
- this.subscriptions.push(this.customTemplatesService.channelActionsTemplate$.subscribe((template) => {
4697
- this.channelActionsTemplate = template;
4698
- this.cdRef.detectChanges();
4699
- }));
4700
- this.subscriptions.push(this.customTemplatesService.channelHeaderInfoTemplate$.subscribe((template) => {
4701
- this.channelHeaderInfoTemplate = template;
4702
- this.cdRef.detectChanges();
4703
- }));
4804
+ resendMessage() {
4805
+ void this.channelService.resendMessage(this.message);
4704
4806
  }
4705
- ngOnDestroy() {
4706
- this.subscriptions.forEach((s) => s.unsubscribe());
4807
+ setAsActiveParentMessage() {
4808
+ void this.channelService.setAsActiveParentMessage(this.message);
4707
4809
  }
4708
- getChannelActionsContext() {
4709
- return { channel: this.activeChannel };
4810
+ getMentionContext(messagePart) {
4811
+ return {
4812
+ content: messagePart.content,
4813
+ user: messagePart.user,
4814
+ };
4710
4815
  }
4711
- getChannelInfoContext() {
4712
- return { channel: this.activeChannel };
4816
+ getMessageActionsBoxContext() {
4817
+ return {
4818
+ isMine: this.isSentByCurrentUser,
4819
+ enabledActions: this.enabledMessageActions,
4820
+ message: this.message,
4821
+ messageTextHtmlElement: this.messageTextElement?.nativeElement,
4822
+ };
4713
4823
  }
4714
- get memberCountParam() {
4715
- return { memberCount: this.activeChannel?.data?.member_count || 0 };
4824
+ getDeliveredStatusContext() {
4825
+ return {
4826
+ message: this.message,
4827
+ };
4716
4828
  }
4717
- get watcherCountParam() {
4718
- return { watcherCount: this.activeChannel?.state?.watcher_count || 0 };
4829
+ getSendingStatusContext() {
4830
+ return {
4831
+ message: this.message,
4832
+ };
4719
4833
  }
4720
- get displayText() {
4721
- if (!this.activeChannel) {
4722
- return '';
4723
- }
4724
- return getChannelDisplayText(this.activeChannel, this.chatClientService.chatClient.user);
4834
+ getReadStatusContext() {
4835
+ return {
4836
+ message: this.message,
4837
+ readByText: this.readByText,
4838
+ };
4725
4839
  }
4726
- get avatarName() {
4727
- return this.activeChannel?.data?.name;
4840
+ getMessageMetadataContext() {
4841
+ return {
4842
+ message: this.message,
4843
+ };
4728
4844
  }
4729
- }
4730
- ChannelHeaderComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: ChannelHeaderComponent, deps: [{ token: ChannelService }, { token: CustomTemplatesService }, { token: i0.ChangeDetectorRef }, { token: ChatClientService }], target: i0.ɵɵFactoryTarget.Component });
4731
- ChannelHeaderComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.0.4", type: ChannelHeaderComponent, selector: "stream-channel-header", ngImport: i0, template: "<div class=\"str-chat__header-livestream str-chat__channel-header\">\n <ng-content></ng-content>\n <stream-avatar-placeholder\n type=\"channel\"\n location=\"channel-header\"\n imageUrl=\"{{ activeChannel?.data?.image }}\"\n name=\"{{ avatarName }}\"\n [channel]=\"activeChannel\"\n ></stream-avatar-placeholder>\n <div class=\"str-chat__header-livestream-left str-chat__channel-header-end\">\n <p\n data-testid=\"name\"\n class=\"\n str-chat__header-livestream-left--title str-chat__channel-header-title\n \"\n >\n {{ displayText }}\n </p>\n <ng-container\n *ngTemplateOutlet=\"\n channelHeaderInfoTemplate || defaultChannelInfo;\n context: getChannelInfoContext()\n \"\n ></ng-container>\n <ng-template #defaultChannelInfo>\n <p\n data-testid=\"info\"\n class=\"\n str-chat__header-livestream-left--members\n str-chat__channel-header-info\n \"\n >\n {{'streamChat.{{ memberCount }} members' | translate:memberCountParam}}\n {{canReceiveConnectEvents ? ('streamChat.{{ watcherCount }} online' |\n translate:watcherCountParam) : ''}}\n </p>\n </ng-template>\n </div>\n <ng-container *ngIf=\"channelActionsTemplate\">\n <ng-container\n *ngTemplateOutlet=\"\n channelActionsTemplate;\n context: getChannelActionsContext()\n \"\n ></ng-container>\n </ng-container>\n</div>\n", dependencies: [{ kind: "directive", type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i4.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: AvatarPlaceholderComponent, selector: "stream-avatar-placeholder", inputs: ["name", "imageUrl", "location", "channel", "user", "type", "initialsType", "showOnlineIndicator"] }, { kind: "pipe", type: i10.TranslatePipe, name: "translate" }] });
4732
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: ChannelHeaderComponent, decorators: [{
4733
- type: Component,
4734
- args: [{ selector: 'stream-channel-header', template: "<div class=\"str-chat__header-livestream str-chat__channel-header\">\n <ng-content></ng-content>\n <stream-avatar-placeholder\n type=\"channel\"\n location=\"channel-header\"\n imageUrl=\"{{ activeChannel?.data?.image }}\"\n name=\"{{ avatarName }}\"\n [channel]=\"activeChannel\"\n ></stream-avatar-placeholder>\n <div class=\"str-chat__header-livestream-left str-chat__channel-header-end\">\n <p\n data-testid=\"name\"\n class=\"\n str-chat__header-livestream-left--title str-chat__channel-header-title\n \"\n >\n {{ displayText }}\n </p>\n <ng-container\n *ngTemplateOutlet=\"\n channelHeaderInfoTemplate || defaultChannelInfo;\n context: getChannelInfoContext()\n \"\n ></ng-container>\n <ng-template #defaultChannelInfo>\n <p\n data-testid=\"info\"\n class=\"\n str-chat__header-livestream-left--members\n str-chat__channel-header-info\n \"\n >\n {{'streamChat.{{ memberCount }} members' | translate:memberCountParam}}\n {{canReceiveConnectEvents ? ('streamChat.{{ watcherCount }} online' |\n translate:watcherCountParam) : ''}}\n </p>\n </ng-template>\n </div>\n <ng-container *ngIf=\"channelActionsTemplate\">\n <ng-container\n *ngTemplateOutlet=\"\n channelActionsTemplate;\n context: getChannelActionsContext()\n \"\n ></ng-container>\n </ng-container>\n</div>\n" }]
4735
- }], ctorParameters: function () { return [{ type: ChannelService }, { type: CustomTemplatesService }, { type: i0.ChangeDetectorRef }, { type: ChatClientService }]; } });
4736
-
4737
- const isOnSeparateDate = (date1, date2) => {
4738
- if (date1.getDate() !== date2.getDate()) {
4739
- return true;
4845
+ jumpToMessage(messageId, parentMessageId) {
4846
+ void this.channelService.jumpToMessage(messageId, parentMessageId);
4740
4847
  }
4741
- else if (date1.getFullYear() !== date2.getFullYear() ||
4742
- date1.getMonth() !== date2.getMonth()) {
4743
- return true;
4848
+ displayTranslatedMessage() {
4849
+ this.createMessageParts(true);
4744
4850
  }
4745
- return false;
4746
- };
4747
-
4748
- /**
4749
- * The message service contains configuration options related to displaying the message content
4750
- */
4751
- class MessageService {
4752
- constructor() {
4753
- /**
4754
- * Decides if the message content should be formatted as text or HTML
4755
- *
4756
- * If you display messages as text the following parts are still be displayed as HTML:
4757
- * - user mentions -> you can customize this with your own template using the [`customTemplatesService.mentionTemplate$`](https://getstream.io/chat/docs/sdk/angular/services/CustomTemplatesService/#mentiontemplate)
4758
- * - links -> you can customize this by providing you own [`customLinkRenderer`](#customlinkrenderer) method
4759
- */
4760
- this.displayAs = 'text';
4851
+ displayOriginalMessage() {
4852
+ this.createMessageParts(false);
4761
4853
  }
4762
- }
4763
- MessageService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: MessageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
4764
- MessageService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: MessageService, providedIn: 'root' });
4765
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: MessageService, decorators: [{
4766
- type: Injectable,
4767
- args: [{
4768
- providedIn: 'root',
4769
- }]
4770
- }], ctorParameters: function () { return []; } });
4771
-
4772
- Dayjs.extend(calendar);
4773
- Dayjs.extend(relativeTime);
4774
- const parseDate = (date, format = 'date-time') => {
4775
- const parsedTime = Dayjs(date);
4776
- switch (format) {
4777
- case 'date': {
4778
- return parsedTime.calendar(null, {
4779
- sameDay: '[Today]',
4780
- nextDay: '[Tomorrow]',
4781
- nextWeek: 'dddd',
4782
- lastDay: '[Yesterday]',
4783
- lastWeek: '[Last] dddd',
4784
- sameElse: 'MM/DD/YYYY', // Everything else ( 10/17/2011 )
4854
+ openMessageBouncePrompt() {
4855
+ this.channelService.bouncedMessage$.next(this.message);
4856
+ }
4857
+ createMessageParts(shouldTranslate = true) {
4858
+ this.messageTextParts = undefined;
4859
+ this.messageText = undefined;
4860
+ let content = this.getMessageContent(shouldTranslate);
4861
+ if ((!this.message.mentioned_users ||
4862
+ this.message.mentioned_users.length === 0) &&
4863
+ !content?.match(this.emojiRegexp) &&
4864
+ !content?.match(this.urlRegexp)) {
4865
+ this.messageTextParts = undefined;
4866
+ this.messageText = content;
4867
+ return;
4868
+ }
4869
+ if (!content) {
4870
+ return;
4871
+ }
4872
+ if (!this.message.mentioned_users ||
4873
+ this.message.mentioned_users.length === 0) {
4874
+ content = this.fixEmojiDisplay(content);
4875
+ content = this.wrapLinksWithAnchorTag(content);
4876
+ this.messageTextParts = [{ content, type: 'text' }];
4877
+ }
4878
+ else {
4879
+ this.messageTextParts = [];
4880
+ let text = content;
4881
+ this.message.mentioned_users.forEach((user) => {
4882
+ const mention = `@${user.name || user.id}`;
4883
+ const precedingText = text.substring(0, text.indexOf(mention));
4884
+ let formattedPrecedingText = this.fixEmojiDisplay(precedingText);
4885
+ formattedPrecedingText = this.wrapLinksWithAnchorTag(formattedPrecedingText);
4886
+ this.messageTextParts.push({
4887
+ content: formattedPrecedingText,
4888
+ type: 'text',
4889
+ });
4890
+ this.messageTextParts.push({
4891
+ content: mention,
4892
+ type: 'mention',
4893
+ user,
4894
+ });
4895
+ text = text.replace(precedingText + mention, '');
4785
4896
  });
4897
+ if (text) {
4898
+ text = this.fixEmojiDisplay(text);
4899
+ text = this.wrapLinksWithAnchorTag(text);
4900
+ this.messageTextParts.push({ content: text, type: 'text' });
4901
+ }
4786
4902
  }
4787
- case 'date-time': {
4788
- return parsedTime.calendar();
4903
+ }
4904
+ getMessageContent(shouldTranslate) {
4905
+ const originalContent = this.message?.text;
4906
+ if (shouldTranslate) {
4907
+ const translation = this.message?.translation;
4908
+ if (translation) {
4909
+ this.shouldDisplayTranslationNotice = true;
4910
+ this.displayedMessageTextContent = 'translation';
4911
+ }
4912
+ return translation || originalContent;
4789
4913
  }
4790
- case 'time': {
4791
- return parsedTime.format('h:mm A');
4914
+ else {
4915
+ this.displayedMessageTextContent = 'original';
4916
+ return originalContent;
4792
4917
  }
4793
4918
  }
4794
- };
4795
-
4796
- /**
4797
- * The `DateParserService` parses dates into user-friendly string representations.
4798
- */
4799
- class DateParserService {
4800
- constructor() { }
4801
- /**
4802
- * Return a user-friendly string representation of the time
4803
- * @param date
4804
- * @returns The parsed time
4805
- */
4806
- parseTime(date) {
4807
- if (this.customTimeParser) {
4808
- return this.customTimeParser(date);
4809
- }
4810
- return parseDate(date, 'time');
4919
+ fixEmojiDisplay(content) {
4920
+ // Wrap emojis in span to display emojis correctly in Chrome https://bugs.chromium.org/p/chromium/issues/detail?id=596223
4921
+ // Based on this: https://stackoverflow.com/questions/4565112/javascript-how-to-find-out-if-the-user-browser-is-chrome
4922
+ /* eslint-disable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any */
4923
+ const isChrome = !!window.chrome && typeof window.opr === 'undefined';
4924
+ /* eslint-enable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any */
4925
+ content = content.replace(this.emojiRegexp, (match) => `<span ${isChrome ? 'class="str-chat__emoji-display-fix"' : ''}>${match}</span>`);
4926
+ return content;
4811
4927
  }
4812
- /**
4813
- * Return a user-friendly string representation of the date (year, month and date)
4814
- * @param date
4815
- * @returns The parsed date
4816
- */
4817
- parseDate(date) {
4818
- if (this.customDateParser) {
4819
- return this.customDateParser(date);
4928
+ wrapLinksWithAnchorTag(content) {
4929
+ if (this.displayAs === 'html') {
4930
+ return content;
4820
4931
  }
4821
- return parseDate(date, 'date');
4932
+ content = content.replace(this.urlRegexp, (match) => this.messageService.customLinkRenderer
4933
+ ? this.messageService.customLinkRenderer(match)
4934
+ : `<a href="${match}" target="_blank" rel="nofollow">${match}</a>`);
4935
+ return content;
4822
4936
  }
4823
- /**
4824
- * Return a user-friendly string representation of the date and time
4825
- * @param date
4826
- * @returns The parsed date
4827
- */
4828
- parseDateTime(date) {
4829
- if (this.customDateTimeParser) {
4830
- return this.customDateTimeParser(date);
4831
- }
4832
- return parseDate(date, 'date-time');
4937
+ setIsSentByCurrentUser() {
4938
+ this.isSentByCurrentUser = this.message?.user?.id === this.userId;
4833
4939
  }
4834
- }
4835
- DateParserService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: DateParserService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
4836
- DateParserService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: DateParserService, providedIn: 'root' });
4837
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: DateParserService, decorators: [{
4838
- type: Injectable,
4839
- args: [{
4840
- providedIn: 'root',
4841
- }]
4842
- }], ctorParameters: function () { return []; } });
4843
-
4844
- /**
4845
- * The `ChannelPreview` component displays a channel preview in the channel list, it consists of the image, name and latest message of the channel.
4846
- */
4847
- class ChannelPreviewComponent {
4848
- constructor(channelService, ngZone, chatClientService, messageService, customTemplatesService, dateParser) {
4849
- this.channelService = channelService;
4850
- this.ngZone = ngZone;
4851
- this.chatClientService = chatClientService;
4852
- this.customTemplatesService = customTemplatesService;
4853
- this.dateParser = dateParser;
4854
- this.isActive = false;
4855
- this.isUnreadMessageWasCalled = false;
4856
- this.isUnread = false;
4857
- this.latestMessageText = 'streamChat.Nothing yet...';
4858
- this.subscriptions = [];
4859
- this.canSendReadEvents = true;
4860
- this.displayAs = messageService.displayAs;
4940
+ setLastReadUser() {
4941
+ this.lastReadUser = this.message?.readBy?.filter((u) => u.id !== this.userId)[0];
4861
4942
  }
4862
- ngOnInit() {
4863
- this.subscriptions.push(this.chatClientService.user$.subscribe((user) => {
4864
- if (user?.id !== this.userId) {
4865
- this.userId = user?.id;
4866
- }
4867
- }));
4868
- this.subscriptions.push(this.channelService.activeChannel$.subscribe((activeChannel) => (this.isActive = activeChannel?.id === this.channel?.id)));
4869
- const messages = this.channel?.state?.latestMessages;
4870
- if (messages && messages.length > 0) {
4871
- this.setLatestMessage(messages[messages.length - 1]);
4943
+ startMessageMenuShowTimer(options) {
4944
+ this.stopMessageMenuShowTimer();
4945
+ if (this.scroll$) {
4946
+ this.subscriptions.push(this.scroll$.pipe(take$1(1)).subscribe(() => {
4947
+ this.stopMessageMenuShowTimer();
4948
+ }));
4872
4949
  }
4873
- this.updateUnreadState();
4874
- const capabilities = this.channel?.data?.own_capabilities || [];
4875
- this.canSendReadEvents = capabilities.indexOf('read-events') !== -1;
4876
- this.subscriptions.push(this.channel.on('message.new', this.handleMessageEvent.bind(this)));
4877
- this.subscriptions.push(this.channel.on('message.updated', this.handleMessageEvent.bind(this)));
4878
- this.subscriptions.push(this.channel.on('message.deleted', this.handleMessageEvent.bind(this)));
4879
- this.subscriptions.push(this.channel.on('channel.truncated', this.handleMessageEvent.bind(this)));
4880
- this.subscriptions.push(this.channel.on('message.read', () => this.ngZone.run(() => {
4881
- this.isUnreadMessageWasCalled = false;
4882
- this.updateUnreadState();
4883
- })));
4884
- this.subscriptions.push(this.chatClientService.events$
4885
- .pipe(filter((e) => e.eventType === 'notification.mark_unread' &&
4886
- this.channel.id === e.event?.channel_id))
4887
- .subscribe(() => {
4950
+ this.showMessageMenuTimeout = setTimeout(() => {
4951
+ if (!this.message) {
4952
+ return;
4953
+ }
4888
4954
  this.ngZone.run(() => {
4889
- this.isUnreadMessageWasCalled = true;
4890
- this.updateUnreadState();
4955
+ if (this.messageActionsService.customActionClickHandler) {
4956
+ this.messageActionsService.customActionClickHandler({
4957
+ message: this.message,
4958
+ enabledActions: this.enabledMessageActions,
4959
+ customActions: this.messageActionsService.customActions$.getValue(),
4960
+ isMine: this.isSentByCurrentUser,
4961
+ messageTextHtmlElement: this.messageTextElement?.nativeElement,
4962
+ });
4963
+ return;
4964
+ }
4965
+ else {
4966
+ this.shouldPreventMessageMenuClose = !options.fromTouch;
4967
+ this.messageMenuTrigger?.show();
4968
+ }
4969
+ if (this.isViewInited) {
4970
+ this.cdRef.detectChanges();
4971
+ }
4972
+ this.showMessageMenuTimeout = undefined;
4891
4973
  });
4892
- }));
4893
- }
4894
- ngOnDestroy() {
4895
- this.subscriptions.forEach((s) => s.unsubscribe());
4896
- }
4897
- get avatarImage() {
4898
- return this.channel?.data?.image;
4974
+ }, 400);
4899
4975
  }
4900
- get avatarName() {
4901
- return this.channel?.data?.name;
4976
+ registerMenuTriggerEventHandlers() {
4977
+ this.messageBubble.nativeElement.addEventListener('touchstart', () => this.touchStarted());
4978
+ this.messageBubble.nativeElement.addEventListener('touchend', () => this.touchEnded());
4979
+ this.messageBubble.nativeElement.addEventListener('mousedown', (e) => this.mousePushedDown(e));
4980
+ this.messageBubble.nativeElement.addEventListener('mouseup', () => this.mouseReleased());
4981
+ this.messageBubble.nativeElement.addEventListener('click', (e) => this.messageBubbleClicked(e));
4902
4982
  }
4903
- get title() {
4904
- if (!this.channel) {
4905
- return '';
4983
+ stopMessageMenuShowTimer() {
4984
+ if (this.showMessageMenuTimeout) {
4985
+ clearTimeout(this.showMessageMenuTimeout);
4986
+ this.showMessageMenuTimeout = undefined;
4906
4987
  }
4907
- return getChannelDisplayText(this.channel, this.chatClientService.chatClient.user);
4908
4988
  }
4909
- setAsActiveChannel() {
4910
- void this.channelService.setAsActiveChannel(this.channel);
4911
- }
4912
- handleMessageEvent(event) {
4913
- this.ngZone.run(() => {
4914
- if (this.channel?.state.latestMessages.length === 0) {
4915
- this.latestMessage = undefined;
4916
- this.latestMessageStatus = undefined;
4917
- this.latestMessageText = 'streamChat.Nothing yet...';
4918
- this.latestMessageTime = undefined;
4919
- return;
4920
- }
4921
- const latestMessage = this.channel?.state.latestMessages[this.channel?.state.latestMessages.length - 1];
4922
- if (!event.message || latestMessage?.id !== event.message.id) {
4923
- return;
4924
- }
4925
- this.setLatestMessage(latestMessage);
4926
- this.updateUnreadState();
4927
- });
4989
+ }
4990
+ MessageComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: MessageComponent, deps: [{ token: ChatClientService }, { token: ChannelService }, { token: CustomTemplatesService }, { token: i0.ChangeDetectorRef }, { token: DateParserService }, { token: MessageService }, { token: MessageActionsService }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component });
4991
+ MessageComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.0.4", type: MessageComponent, selector: "stream-message", inputs: { message: "message", enabledMessageActions: "enabledMessageActions", isLastSentMessage: "isLastSentMessage", mode: "mode", isHighlighted: "isHighlighted", scroll$: "scroll$" }, viewQueries: [{ propertyName: "messageMenuTrigger", first: true, predicate: ["messageMenuTrigger"], descendants: true }, { propertyName: "messageMenuFloat", first: true, predicate: ["messageMenuFloat"], descendants: true }, { propertyName: "messageTextElement", first: true, predicate: ["messageTextElement"], descendants: true }, { propertyName: "messageBubble", first: true, predicate: ["messageBubble"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div\n #container\n data-testid=\"message-container\"\n class=\"str-chat__message-simple str-chat__message str-chat__message--{{\n message?.type\n }} str-chat__message--{{ message?.status }} {{\n message?.text ? 'str-chat__message--has-text' : 'has-no-text'\n }} str-chat__message-menu-{{ areMessageOptionsOpen ? 'opened' : 'closed' }}\"\n [class.str-chat__message--me]=\"isSentByCurrentUser\"\n [class.str-chat__message--other]=\"!isSentByCurrentUser\"\n [class.str-chat__message-simple--me]=\"isSentByCurrentUser\"\n [class.str-chat__message--has-attachment]=\"hasAttachment\"\n [class.str-chat__message--with-reactions]=\"hasReactions\"\n [class.str-chat__message--highlighted]=\"isHighlighted\"\n [class.str-chat__message-with-thread-link]=\"shouldDisplayThreadLink\"\n [class.str-chat__message-send-can-be-retried]=\"\n (message?.status === 'failed' && message?.errorStatusCode !== 403) ||\n (message?.type === 'error' && message?.moderation_details)\n \"\n [class.str-chat__message-with-touch-support]=\"hasTouchSupport\"\n [class.str-chat__message-without-touch-support]=\"!hasTouchSupport\"\n>\n <ng-container *ngIf=\"!message?.deleted_at; else deletedMessage\">\n <ng-container *ngIf=\"message?.type !== 'system'; else systemMessage\">\n <stream-avatar-placeholder\n data-testid=\"avatar\"\n class=\"str-chat-angular__avatar-host str-chat__message-sender-avatar\"\n type=\"user\"\n location=\"message-sender\"\n [imageUrl]=\"message?.user?.image\"\n [name]=\"message?.user?.name || message?.user?.id\"\n [user]=\"message?.user || undefined\"\n ></stream-avatar-placeholder>\n <div class=\"str-chat__message-inner\">\n <div\n *ngIf=\"!hasTouchSupport && areOptionsVisible\"\n class=\"str-chat__message-simple__actions str-chat__message-options\"\n data-testid=\"message-options\"\n [class.str-chat__message-actions-open]=\"areMessageOptionsOpen\"\n >\n <div\n #messageActionsToggle\n data-testid=\"message-actions-container\"\n class=\"\n str-chat__message-actions-container\n str-chat__message-simple__actions__action\n str-chat__message-simple__actions__action--options\n \"\n [floatUiLoose]=\"messageMenuFloat\"\n looseTrigger=\"click\"\n [hideOnScroll]=\"false\"\n [hideOnClickOutside]=\"true\"\n [hideOnMouseLeave]=\"false\"\n [disableAnimation]=\"false\"\n [preventOverflow]=\"true\"\n [positionFixed]=\"true\"\n (onSHown)=\"areMessageOptionsOpen = true\"\n (onHidden)=\"areMessageOptionsOpen = false\"\n >\n <div\n *ngIf=\"visibleMessageActionsCount > 0\"\n class=\"str-chat__message-actions-box-button\"\n data-testid=\"message-options-button\"\n (click)=\"messageOptionsButtonClicked()\"\n (keyup.enter)=\"messageOptionsButtonClicked()\"\n >\n <stream-icon-placeholder\n icon=\"action\"\n class=\"str-chat__message-action-icon\"\n ></stream-icon-placeholder>\n </div>\n </div>\n </div>\n <div class=\"str-chat__message-reactions-host\">\n <ng-template\n #defaultMessageReactions\n let-messageReactionCounts=\"messageReactionCounts\"\n let-latestReactions=\"latestReactions\"\n let-messageId=\"messageId\"\n let-ownReactions=\"ownReactions\"\n >\n <stream-message-reactions\n [messageReactionCounts]=\"messageReactionCounts\"\n [latestReactions]=\"latestReactions\"\n [messageId]=\"messageId\"\n [ownReactions]=\"ownReactions\"\n ></stream-message-reactions>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.messageReactionsTemplate$ | async) ||\n defaultMessageReactions;\n context: getMessageReactionsContext()\n \"\n ></ng-container>\n </div>\n <float-ui-content #messageMenuFloat>\n <ng-template\n #defaultMessageActionsBox\n let-isMine=\"isMine\"\n let-messageInput=\"message\"\n let-enabledActions=\"enabledActions\"\n let-messageTextHtmlElement=\"messageTextHtmlElement\"\n >\n <stream-message-actions-box\n [isMine]=\"isMine\"\n [message]=\"messageInput\"\n [enabledActions]=\"enabledActions\"\n [messageTextHtmlElement]=\"messageTextHtmlElement\"\n (click)=\"messageActionsBoxClicked(messageMenuFloat)\"\n ></stream-message-actions-box>\n </ng-template>\n <ng-container>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.messageActionsBoxTemplate$ | async) ||\n defaultMessageActionsBox;\n context: getMessageActionsBoxContext()\n \"\n >\n </ng-container>\n </ng-container>\n </float-ui-content>\n <div\n class=\"str-chat__message-bubble str-chat-angular__message-bubble\"\n [class.str-chat-angular__message-bubble--attachment-modal-open]=\"\n imageAttachmentModalState === 'opened'\n \"\n data-testid=\"message-bubble\"\n [floatUiLoose]=\"messageMenuFloat\"\n #messageMenuTrigger=\"floatUiLoose\"\n #messageBubble\n looseTrigger=\"none\"\n [hideOnScroll]=\"false\"\n [hideOnClickOutside]=\"true\"\n [hideOnMouseLeave]=\"false\"\n [disableAnimation]=\"true\"\n [preventOverflow]=\"true\"\n (onShown)=\"areMessageOptionsOpen = true\"\n (onHidden)=\"areMessageOptionsOpen = false\"\n [positionFixed]=\"true\"\n >\n <ng-container *ngIf=\"hasAttachment && !message?.quoted_message\">\n <div class=\"str-chat__attachments-container\">\n <ng-container\n *ngTemplateOutlet=\"attachmentsTemplate\"\n ></ng-container>\n </div>\n </ng-container>\n <div\n *ngIf=\"message?.text || (message?.quoted_message && hasAttachment)\"\n class=\"str-chat__message-text\"\n tabindex=\"0\"\n [class.str-chat__message-text--pointer-cursor]=\"\n (message?.status === 'failed' &&\n message?.errorStatusCode !== 403) ||\n (this.message?.type === 'error' &&\n this.message?.moderation_details) ||\n message?.message_text_updated_at\n \"\n (click)=\"messageClicked()\"\n (keyup.enter)=\"messageClicked()\"\n >\n <div\n data-testid=\"inner-message\"\n class=\"\n str-chat__message-text-inner str-chat__message-simple-text-inner\n \"\n [class.str-chat__message-light-text-inner--has-attachment]=\"\n hasAttachment\n \"\n >\n <ng-container *ngTemplateOutlet=\"quotedMessage\"></ng-container>\n <ng-container *ngIf=\"hasAttachment && message?.quoted_message\">\n <ng-container\n *ngTemplateOutlet=\"attachmentsTemplate\"\n ></ng-container>\n </ng-container>\n <div\n *ngIf=\"message?.type === 'error'\"\n data-testid=\"client-error-message\"\n class=\"\n str-chat__simple-message--error-message\n str-chat__message--error-message\n \"\n >\n <ng-container *ngIf=\"!message?.moderation_details\">{{\n \"streamChat.Error \u00B7 Unsent\" | translate\n }}</ng-container>\n </div>\n <div\n *ngIf=\"message?.status === 'failed'\"\n data-testid=\"error-message\"\n class=\"\n str-chat__simple-message--error-message\n str-chat__message--error-message\n \"\n >\n {{\n (message?.errorStatusCode === 403\n ? \"streamChat.Message Failed \u00B7 Unauthorized\"\n : \"streamChat.Message Failed \u00B7 Click to try again\"\n ) | translate\n }}\n </div>\n <div #messageTextElement data-testid=\"text\">\n <p>\n <ng-container *ngIf=\"messageTextParts; else defaultContent\">\n <!-- eslint-disable-next-line @angular-eslint/template/use-track-by-function -->\n <ng-container *ngFor=\"let part of messageTextParts\">\n <span\n *ngIf=\"part.type === 'text'; else mention\"\n [innerHTML]=\"part.content\"\n ></span>\n <ng-template #mention>\n <ng-template #defaultMention let-content=\"content\">\n <span class=\"str-chat__message-mention\">{{\n content\n }}</span>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.mentionTemplate$ | async) ||\n defaultMention;\n context: getMentionContext(part)\n \"\n ></ng-container>\n </ng-template>\n </ng-container>\n </ng-container>\n <ng-template #defaultContent>\n <ng-container *ngIf=\"displayAs === 'text'; else asHTML\">\n {{ messageText || \"\" }}\n </ng-container>\n <ng-template #asHTML\n ><span\n data-testid=\"html-content\"\n [innerHTML]=\"messageText\"\n ></span\n ></ng-template>\n </ng-template>\n </p>\n </div>\n </div>\n </div>\n <stream-icon-placeholder\n class=\"str-chat__message-error-icon\"\n icon=\"error\"\n ></stream-icon-placeholder>\n </div>\n </div>\n <ng-container\n *ngTemplateOutlet=\"replyCountButton; context: { message: message }\"\n ></ng-container>\n\n <ng-container *ngTemplateOutlet=\"messageDateAndSender\"></ng-container>\n </ng-container>\n </ng-container>\n</div>\n\n<ng-template #deletedMessage>\n <div data-testid=\"message-deleted-component\">\n <div class=\"str-chat__message--deleted-inner\" translate>\n streamChat.This message was deleted...\n </div>\n </div>\n</ng-template>\n\n<ng-template #systemMessage>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.systemMessageTemplate$ | async) ||\n defaultSystemMessage;\n context: getMessageContext()\n \"\n ></ng-container>\n <ng-template #defaultSystemMessage let-messageInput=\"message\">\n <div data-testid=\"system-message\" class=\"str-chat__message--system\">\n <div class=\"str-chat__message--system__text\">\n <div class=\"str-chat__message--system__line\"></div>\n <p>{{ messageInput?.text }}</p>\n <div class=\"str-chat__message--system__line\"></div>\n </div>\n <div class=\"str-chat__message--system__date\">\n {{ parsedDate }}\n </div>\n </div>\n </ng-template>\n</ng-template>\n\n<ng-template #quotedMessage>\n <div\n *ngIf=\"message?.quoted_message\"\n class=\"quoted-message str-chat__quoted-message-preview\"\n data-testid=\"quoted-message-container\"\n [class.mine]=\"isSentByCurrentUser\"\n (click)=\"\n jumpToMessage(\n (message?.quoted_message)!.id,\n message?.quoted_message?.parent_id\n )\n \"\n (keyup.enter)=\"\n jumpToMessage(\n (message?.quoted_message)!.id,\n message?.quoted_message?.parent_id\n )\n \"\n >\n <stream-avatar-placeholder\n data-testid=\"qouted-message-avatar\"\n class=\"str-chat-angular__avatar-host str-chat__message-sender-avatar\"\n type=\"user\"\n location=\"quoted-message-sender\"\n [imageUrl]=\"message?.quoted_message?.user?.image\"\n [name]=\"\n message?.quoted_message?.user?.name || message?.quoted_message?.user?.id\n \"\n [user]=\"message?.quoted_message?.user || undefined\"\n ></stream-avatar-placeholder>\n <div class=\"quoted-message-inner str-chat__quoted-message-bubble\">\n <ng-container\n *ngIf=\"\n message?.quoted_message?.attachments &&\n message?.quoted_message?.attachments?.length\n \"\n >\n <ng-template\n #defaultAttachments\n let-messageId=\"messageId\"\n let-attachments=\"attachments\"\n let-parentMessageId=\"parentMessageId\"\n let-imageModalStateChangeHandler=\"imageModalStateChangeHandler\"\n >\n <stream-attachment-list\n [messageId]=\"messageId\"\n [attachments]=\"attachments\"\n [parentMessageId]=\"parentMessageId\"\n (imageModalStateChange)=\"imageModalStateChangeHandler($event)\"\n ></stream-attachment-list>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.attachmentListTemplate$ | async) ||\n defaultAttachments;\n context: getQuotedMessageAttachmentListContext()\n \"\n ></ng-container>\n </ng-container>\n <div\n data-testid=\"quoted-message-text\"\n [innerHTML]=\"\n message?.quoted_message?.translation ||\n message?.quoted_message?.html ||\n message?.quoted_message?.text\n \"\n ></div>\n </div>\n </div>\n</ng-template>\n\n<ng-template #messageDateAndSender>\n <ng-container>\n <div\n *ngIf=\"shouldDisplayTranslationNotice\"\n class=\"str-chat__translation-notice\"\n data-testid=\"translation-notice\"\n >\n <button\n *ngIf=\"displayedMessageTextContent === 'translation'\"\n data-testid=\"see-original\"\n translate\n (click)=\"displayOriginalMessage()\"\n (keyup.enter)=\"displayOriginalMessage()\"\n >\n streamChat.See original (automatically translated)\n </button>\n <button\n *ngIf=\"displayedMessageTextContent === 'original'\"\n data-testid=\"see-translation\"\n translate\n (click)=\"displayTranslatedMessage()\"\n (keyup.enter)=\"displayTranslatedMessage()\"\n >\n streamChat.See translation\n </button>\n </div>\n <ng-container\n *ngIf=\"customTemplatesService.customMessageMetadataTemplate$ | async\"\n >\n <div class=\"str-chat__custom-message-metadata\">\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.customMessageMetadataTemplate$ | async)!;\n context: getMessageMetadataContext()\n \"\n ></ng-container>\n </div>\n </ng-container>\n <div\n class=\"\n str-chat__message-data\n str-chat__message-simple-data\n str-chat__message-metadata\n \"\n >\n <ng-container *ngTemplateOutlet=\"messageStatus\"></ng-container>\n\n <span\n *ngIf=\"!isSentByCurrentUser\"\n data-testid=\"sender\"\n class=\"str-chat__message-simple-name str-chat__message-sender-name\"\n >\n {{ message?.user?.name ? message?.user?.name : message?.user?.id }}\n </span>\n <span\n data-testid=\"date\"\n class=\"str-chat__message-simple-timestamp str-chat__message-simple-time\"\n >\n {{ parsedDate }}\n </span>\n <ng-container *ngIf=\"message?.message_text_updated_at\">\n <span\n data-testid=\"edited-flag\"\n class=\"str-chat__mesage-simple-edited\"\n translate\n >streamChat.Edited</span\n >\n <div\n data-testid=\"edited-timestamp\"\n class=\"str-chat__message-edited-timestamp\"\n [ngClass]=\"{\n 'str-chat__message-edited-timestamp--open': isEditedFlagOpened,\n 'str-chat__message-edited-timestamp--collapsed': !isEditedFlagOpened\n }\"\n >\n <span translate>streamChat.Edited</span>\n <time\n dateTime=\"{{ message?.message_text_updated_at }}\"\n title=\"{{ message?.message_text_updated_at }}\"\n >\n {{ pasedEditedDate }}\n </time>\n </div>\n </ng-container>\n </div>\n </ng-container>\n</ng-template>\n\n<ng-template #messageStatus>\n <ng-container\n *ngIf=\"\n isSentByCurrentUser &&\n ((isLastSentMessage && message?.status === 'received') ||\n message?.status === 'sending')\n \"\n >\n <ng-container *ngIf=\"message?.status === 'sending'; else sentStatus\">\n <ng-container *ngTemplateOutlet=\"sendingStatus\"></ng-container>\n </ng-container>\n <ng-template #sentStatus>\n <ng-container\n *ngIf=\"\n mode === 'main' && isMessageDeliveredAndRead && canDisplayReadStatus;\n else deliveredStatus\n \"\n >\n <ng-container *ngTemplateOutlet=\"readStatus\"></ng-container>\n </ng-container>\n </ng-template>\n <ng-template #deliveredStatus>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.deliveredStatusTemplate$ | async) ||\n defaultDeliveredStatus;\n context: getDeliveredStatusContext()\n \"\n ></ng-container>\n </ng-template>\n <ng-template #defaultDeliveredStatus>\n <span\n *ngIf=\"mode === 'main'\"\n class=\"\n str-chat__message-simple-status\n str-chat__message-simple-status-angular\n str-chat__message-status\n \"\n data-testid=\"delivered-indicator\"\n tabindex=\"0\"\n [floatUiLoose]=\"floatingContent\"\n loosePlacement=\"top\"\n looseTrigger=\"hover\"\n >\n <float-ui-content #floatingContent>\n <div class=\"str-chat__tooltip str-chat__tooltip-angular\">\n {{ \"streamChat.Delivered\" | translate }}\n </div>\n </float-ui-content>\n <stream-icon-placeholder\n data-testid=\"delivered-icon\"\n icon=\"delivered\"\n ></stream-icon-placeholder>\n </span>\n </ng-template>\n <ng-template #sendingStatus>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.sendingStatusTemplate$ | async) ||\n defaultSendingStatus;\n context: getSendingStatusContext()\n \"\n ></ng-container>\n </ng-template>\n <ng-template #defaultSendingStatus>\n <span\n class=\"\n str-chat__message-simple-status\n str-chat__message-simple-status-angular\n str-chat__message-status\n \"\n data-testid=\"sending-indicator\"\n tabindex=\"0\"\n [floatUiLoose]=\"floatingContent\"\n loosePlacement=\"top\"\n looseTrigger=\"hover\"\n [disableAnimation]=\"false\"\n [preventOverflow]=\"true\"\n [positionFixed]=\"true\"\n >\n <float-ui-content #floatingContent>\n <div class=\"str-chat__tooltip str-chat__tooltip-angular\">\n {{ \"streamChat.Sending...\" | translate }}\n </div>\n </float-ui-content>\n <stream-loading-indicator-placeholder\n data-testid=\"loading-indicator\"\n ></stream-loading-indicator-placeholder>\n </span>\n </ng-template>\n <ng-template #readStatus>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.readStatusTemplate$ | async) ||\n defaultReadStatus;\n context: getReadStatusContext()\n \"\n ></ng-container>\n </ng-template>\n <ng-template #defaultReadStatus let-readByText=\"readByText\">\n <span\n class=\"\n str-chat__message-simple-status\n str-chat__message-simple-status-angular\n str-chat__message-status\n \"\n data-testid=\"read-indicator\"\n tabindex=\"0\"\n [floatUiLoose]=\"floatingContent\"\n loosePlacement=\"top\"\n looseTrigger=\"hover\"\n [disableAnimation]=\"false\"\n [preventOverflow]=\"true\"\n [positionFixed]=\"true\"\n >\n <float-ui-content #floatingContent>\n <div\n class=\"str-chat__tooltip str-chat__tooltip-angular\"\n data-testid=\"read-by-tooltip\"\n >\n {{ readByText }}\n </div>\n </float-ui-content>\n <stream-icon-placeholder icon=\"read\"></stream-icon-placeholder>\n </span>\n </ng-template>\n </ng-container>\n</ng-template>\n\n<ng-template #replyCountButton>\n <div\n class=\"\n str-chat__message-simple-reply-button\n str-chat__message-replies-count-button-wrapper\n \"\n >\n <button\n *ngIf=\"shouldDisplayThreadLink\"\n class=\"str-chat__message-replies-count-button\"\n data-testid=\"reply-count-button\"\n (click)=\"setAsActiveParentMessage()\"\n >\n {{message?.reply_count === 1 ? ('streamChat.1 reply' | translate) : ('streamChat.{{ replyCount }}\n replies' | translate:replyCountParam)}}\n </button>\n </div>\n</ng-template>\n\n<ng-template #attachmentsTemplate>\n <ng-template\n #defaultAttachments\n let-messageId=\"messageId\"\n let-attachments=\"attachments\"\n let-parentMessageId=\"parentMessageId\"\n let-imageModalStateChangeHandler=\"imageModalStateChangeHandler\"\n >\n <stream-attachment-list\n [messageId]=\"messageId\"\n [attachments]=\"attachments\"\n [parentMessageId]=\"parentMessageId\"\n (imageModalStateChange)=\"imageModalStateChangeHandler($event)\"\n ></stream-attachment-list>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.attachmentListTemplate$ | async) ||\n defaultAttachments;\n context: getAttachmentListContext()\n \"\n ></ng-container>\n</ng-template>\n", dependencies: [{ kind: "directive", type: i4.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i4.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i4.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: i8.NgxFloatUiContentComponent, selector: "float-ui-content", exportAs: ["ngxFloatUiContent"] }, { kind: "directive", type: i8.NgxFloatUiLooseDirective, selector: "[floatUiLoose]", inputs: ["floatUiLoose", "loosePlacement", "looseTrigger"], exportAs: ["floatUiLoose"] }, { kind: "component", type: AvatarPlaceholderComponent, selector: "stream-avatar-placeholder", inputs: ["name", "imageUrl", "location", "channel", "user", "type", "initialsType", "showOnlineIndicator"] }, { kind: "directive", type: i10.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "component", type: MessageActionsBoxComponent, selector: "stream-message-actions-box", inputs: ["isMine", "message", "messageTextHtmlElement", "enabledActions"] }, { kind: "component", type: AttachmentListComponent, selector: "stream-attachment-list", inputs: ["messageId", "parentMessageId", "attachments"], outputs: ["imageModalStateChange"] }, { kind: "component", type: MessageReactionsComponent, selector: "stream-message-reactions", inputs: ["messageId", "messageReactionCounts", "latestReactions", "ownReactions"] }, { kind: "component", type: IconPlaceholderComponent, selector: "stream-icon-placeholder", inputs: ["icon"] }, { kind: "component", type: LoadingIndicatorPlaceholderComponent, selector: "stream-loading-indicator-placeholder" }, { kind: "pipe", type: i4.AsyncPipe, name: "async" }, { kind: "pipe", type: i10.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4992
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: MessageComponent, decorators: [{
4993
+ type: Component,
4994
+ args: [{ selector: 'stream-message', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n #container\n data-testid=\"message-container\"\n class=\"str-chat__message-simple str-chat__message str-chat__message--{{\n message?.type\n }} str-chat__message--{{ message?.status }} {{\n message?.text ? 'str-chat__message--has-text' : 'has-no-text'\n }} str-chat__message-menu-{{ areMessageOptionsOpen ? 'opened' : 'closed' }}\"\n [class.str-chat__message--me]=\"isSentByCurrentUser\"\n [class.str-chat__message--other]=\"!isSentByCurrentUser\"\n [class.str-chat__message-simple--me]=\"isSentByCurrentUser\"\n [class.str-chat__message--has-attachment]=\"hasAttachment\"\n [class.str-chat__message--with-reactions]=\"hasReactions\"\n [class.str-chat__message--highlighted]=\"isHighlighted\"\n [class.str-chat__message-with-thread-link]=\"shouldDisplayThreadLink\"\n [class.str-chat__message-send-can-be-retried]=\"\n (message?.status === 'failed' && message?.errorStatusCode !== 403) ||\n (message?.type === 'error' && message?.moderation_details)\n \"\n [class.str-chat__message-with-touch-support]=\"hasTouchSupport\"\n [class.str-chat__message-without-touch-support]=\"!hasTouchSupport\"\n>\n <ng-container *ngIf=\"!message?.deleted_at; else deletedMessage\">\n <ng-container *ngIf=\"message?.type !== 'system'; else systemMessage\">\n <stream-avatar-placeholder\n data-testid=\"avatar\"\n class=\"str-chat-angular__avatar-host str-chat__message-sender-avatar\"\n type=\"user\"\n location=\"message-sender\"\n [imageUrl]=\"message?.user?.image\"\n [name]=\"message?.user?.name || message?.user?.id\"\n [user]=\"message?.user || undefined\"\n ></stream-avatar-placeholder>\n <div class=\"str-chat__message-inner\">\n <div\n *ngIf=\"!hasTouchSupport && areOptionsVisible\"\n class=\"str-chat__message-simple__actions str-chat__message-options\"\n data-testid=\"message-options\"\n [class.str-chat__message-actions-open]=\"areMessageOptionsOpen\"\n >\n <div\n #messageActionsToggle\n data-testid=\"message-actions-container\"\n class=\"\n str-chat__message-actions-container\n str-chat__message-simple__actions__action\n str-chat__message-simple__actions__action--options\n \"\n [floatUiLoose]=\"messageMenuFloat\"\n looseTrigger=\"click\"\n [hideOnScroll]=\"false\"\n [hideOnClickOutside]=\"true\"\n [hideOnMouseLeave]=\"false\"\n [disableAnimation]=\"false\"\n [preventOverflow]=\"true\"\n [positionFixed]=\"true\"\n (onSHown)=\"areMessageOptionsOpen = true\"\n (onHidden)=\"areMessageOptionsOpen = false\"\n >\n <div\n *ngIf=\"visibleMessageActionsCount > 0\"\n class=\"str-chat__message-actions-box-button\"\n data-testid=\"message-options-button\"\n (click)=\"messageOptionsButtonClicked()\"\n (keyup.enter)=\"messageOptionsButtonClicked()\"\n >\n <stream-icon-placeholder\n icon=\"action\"\n class=\"str-chat__message-action-icon\"\n ></stream-icon-placeholder>\n </div>\n </div>\n </div>\n <div class=\"str-chat__message-reactions-host\">\n <ng-template\n #defaultMessageReactions\n let-messageReactionCounts=\"messageReactionCounts\"\n let-latestReactions=\"latestReactions\"\n let-messageId=\"messageId\"\n let-ownReactions=\"ownReactions\"\n >\n <stream-message-reactions\n [messageReactionCounts]=\"messageReactionCounts\"\n [latestReactions]=\"latestReactions\"\n [messageId]=\"messageId\"\n [ownReactions]=\"ownReactions\"\n ></stream-message-reactions>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.messageReactionsTemplate$ | async) ||\n defaultMessageReactions;\n context: getMessageReactionsContext()\n \"\n ></ng-container>\n </div>\n <float-ui-content #messageMenuFloat>\n <ng-template\n #defaultMessageActionsBox\n let-isMine=\"isMine\"\n let-messageInput=\"message\"\n let-enabledActions=\"enabledActions\"\n let-messageTextHtmlElement=\"messageTextHtmlElement\"\n >\n <stream-message-actions-box\n [isMine]=\"isMine\"\n [message]=\"messageInput\"\n [enabledActions]=\"enabledActions\"\n [messageTextHtmlElement]=\"messageTextHtmlElement\"\n (click)=\"messageActionsBoxClicked(messageMenuFloat)\"\n ></stream-message-actions-box>\n </ng-template>\n <ng-container>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.messageActionsBoxTemplate$ | async) ||\n defaultMessageActionsBox;\n context: getMessageActionsBoxContext()\n \"\n >\n </ng-container>\n </ng-container>\n </float-ui-content>\n <div\n class=\"str-chat__message-bubble str-chat-angular__message-bubble\"\n [class.str-chat-angular__message-bubble--attachment-modal-open]=\"\n imageAttachmentModalState === 'opened'\n \"\n data-testid=\"message-bubble\"\n [floatUiLoose]=\"messageMenuFloat\"\n #messageMenuTrigger=\"floatUiLoose\"\n #messageBubble\n looseTrigger=\"none\"\n [hideOnScroll]=\"false\"\n [hideOnClickOutside]=\"true\"\n [hideOnMouseLeave]=\"false\"\n [disableAnimation]=\"true\"\n [preventOverflow]=\"true\"\n (onShown)=\"areMessageOptionsOpen = true\"\n (onHidden)=\"areMessageOptionsOpen = false\"\n [positionFixed]=\"true\"\n >\n <ng-container *ngIf=\"hasAttachment && !message?.quoted_message\">\n <div class=\"str-chat__attachments-container\">\n <ng-container\n *ngTemplateOutlet=\"attachmentsTemplate\"\n ></ng-container>\n </div>\n </ng-container>\n <div\n *ngIf=\"message?.text || (message?.quoted_message && hasAttachment)\"\n class=\"str-chat__message-text\"\n tabindex=\"0\"\n [class.str-chat__message-text--pointer-cursor]=\"\n (message?.status === 'failed' &&\n message?.errorStatusCode !== 403) ||\n (this.message?.type === 'error' &&\n this.message?.moderation_details) ||\n message?.message_text_updated_at\n \"\n (click)=\"messageClicked()\"\n (keyup.enter)=\"messageClicked()\"\n >\n <div\n data-testid=\"inner-message\"\n class=\"\n str-chat__message-text-inner str-chat__message-simple-text-inner\n \"\n [class.str-chat__message-light-text-inner--has-attachment]=\"\n hasAttachment\n \"\n >\n <ng-container *ngTemplateOutlet=\"quotedMessage\"></ng-container>\n <ng-container *ngIf=\"hasAttachment && message?.quoted_message\">\n <ng-container\n *ngTemplateOutlet=\"attachmentsTemplate\"\n ></ng-container>\n </ng-container>\n <div\n *ngIf=\"message?.type === 'error'\"\n data-testid=\"client-error-message\"\n class=\"\n str-chat__simple-message--error-message\n str-chat__message--error-message\n \"\n >\n <ng-container *ngIf=\"!message?.moderation_details\">{{\n \"streamChat.Error \u00B7 Unsent\" | translate\n }}</ng-container>\n </div>\n <div\n *ngIf=\"message?.status === 'failed'\"\n data-testid=\"error-message\"\n class=\"\n str-chat__simple-message--error-message\n str-chat__message--error-message\n \"\n >\n {{\n (message?.errorStatusCode === 403\n ? \"streamChat.Message Failed \u00B7 Unauthorized\"\n : \"streamChat.Message Failed \u00B7 Click to try again\"\n ) | translate\n }}\n </div>\n <div #messageTextElement data-testid=\"text\">\n <p>\n <ng-container *ngIf=\"messageTextParts; else defaultContent\">\n <!-- eslint-disable-next-line @angular-eslint/template/use-track-by-function -->\n <ng-container *ngFor=\"let part of messageTextParts\">\n <span\n *ngIf=\"part.type === 'text'; else mention\"\n [innerHTML]=\"part.content\"\n ></span>\n <ng-template #mention>\n <ng-template #defaultMention let-content=\"content\">\n <span class=\"str-chat__message-mention\">{{\n content\n }}</span>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.mentionTemplate$ | async) ||\n defaultMention;\n context: getMentionContext(part)\n \"\n ></ng-container>\n </ng-template>\n </ng-container>\n </ng-container>\n <ng-template #defaultContent>\n <ng-container *ngIf=\"displayAs === 'text'; else asHTML\">\n {{ messageText || \"\" }}\n </ng-container>\n <ng-template #asHTML\n ><span\n data-testid=\"html-content\"\n [innerHTML]=\"messageText\"\n ></span\n ></ng-template>\n </ng-template>\n </p>\n </div>\n </div>\n </div>\n <stream-icon-placeholder\n class=\"str-chat__message-error-icon\"\n icon=\"error\"\n ></stream-icon-placeholder>\n </div>\n </div>\n <ng-container\n *ngTemplateOutlet=\"replyCountButton; context: { message: message }\"\n ></ng-container>\n\n <ng-container *ngTemplateOutlet=\"messageDateAndSender\"></ng-container>\n </ng-container>\n </ng-container>\n</div>\n\n<ng-template #deletedMessage>\n <div data-testid=\"message-deleted-component\">\n <div class=\"str-chat__message--deleted-inner\" translate>\n streamChat.This message was deleted...\n </div>\n </div>\n</ng-template>\n\n<ng-template #systemMessage>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.systemMessageTemplate$ | async) ||\n defaultSystemMessage;\n context: getMessageContext()\n \"\n ></ng-container>\n <ng-template #defaultSystemMessage let-messageInput=\"message\">\n <div data-testid=\"system-message\" class=\"str-chat__message--system\">\n <div class=\"str-chat__message--system__text\">\n <div class=\"str-chat__message--system__line\"></div>\n <p>{{ messageInput?.text }}</p>\n <div class=\"str-chat__message--system__line\"></div>\n </div>\n <div class=\"str-chat__message--system__date\">\n {{ parsedDate }}\n </div>\n </div>\n </ng-template>\n</ng-template>\n\n<ng-template #quotedMessage>\n <div\n *ngIf=\"message?.quoted_message\"\n class=\"quoted-message str-chat__quoted-message-preview\"\n data-testid=\"quoted-message-container\"\n [class.mine]=\"isSentByCurrentUser\"\n (click)=\"\n jumpToMessage(\n (message?.quoted_message)!.id,\n message?.quoted_message?.parent_id\n )\n \"\n (keyup.enter)=\"\n jumpToMessage(\n (message?.quoted_message)!.id,\n message?.quoted_message?.parent_id\n )\n \"\n >\n <stream-avatar-placeholder\n data-testid=\"qouted-message-avatar\"\n class=\"str-chat-angular__avatar-host str-chat__message-sender-avatar\"\n type=\"user\"\n location=\"quoted-message-sender\"\n [imageUrl]=\"message?.quoted_message?.user?.image\"\n [name]=\"\n message?.quoted_message?.user?.name || message?.quoted_message?.user?.id\n \"\n [user]=\"message?.quoted_message?.user || undefined\"\n ></stream-avatar-placeholder>\n <div class=\"quoted-message-inner str-chat__quoted-message-bubble\">\n <ng-container\n *ngIf=\"\n message?.quoted_message?.attachments &&\n message?.quoted_message?.attachments?.length\n \"\n >\n <ng-template\n #defaultAttachments\n let-messageId=\"messageId\"\n let-attachments=\"attachments\"\n let-parentMessageId=\"parentMessageId\"\n let-imageModalStateChangeHandler=\"imageModalStateChangeHandler\"\n >\n <stream-attachment-list\n [messageId]=\"messageId\"\n [attachments]=\"attachments\"\n [parentMessageId]=\"parentMessageId\"\n (imageModalStateChange)=\"imageModalStateChangeHandler($event)\"\n ></stream-attachment-list>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.attachmentListTemplate$ | async) ||\n defaultAttachments;\n context: getQuotedMessageAttachmentListContext()\n \"\n ></ng-container>\n </ng-container>\n <div\n data-testid=\"quoted-message-text\"\n [innerHTML]=\"\n message?.quoted_message?.translation ||\n message?.quoted_message?.html ||\n message?.quoted_message?.text\n \"\n ></div>\n </div>\n </div>\n</ng-template>\n\n<ng-template #messageDateAndSender>\n <ng-container>\n <div\n *ngIf=\"shouldDisplayTranslationNotice\"\n class=\"str-chat__translation-notice\"\n data-testid=\"translation-notice\"\n >\n <button\n *ngIf=\"displayedMessageTextContent === 'translation'\"\n data-testid=\"see-original\"\n translate\n (click)=\"displayOriginalMessage()\"\n (keyup.enter)=\"displayOriginalMessage()\"\n >\n streamChat.See original (automatically translated)\n </button>\n <button\n *ngIf=\"displayedMessageTextContent === 'original'\"\n data-testid=\"see-translation\"\n translate\n (click)=\"displayTranslatedMessage()\"\n (keyup.enter)=\"displayTranslatedMessage()\"\n >\n streamChat.See translation\n </button>\n </div>\n <ng-container\n *ngIf=\"customTemplatesService.customMessageMetadataTemplate$ | async\"\n >\n <div class=\"str-chat__custom-message-metadata\">\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.customMessageMetadataTemplate$ | async)!;\n context: getMessageMetadataContext()\n \"\n ></ng-container>\n </div>\n </ng-container>\n <div\n class=\"\n str-chat__message-data\n str-chat__message-simple-data\n str-chat__message-metadata\n \"\n >\n <ng-container *ngTemplateOutlet=\"messageStatus\"></ng-container>\n\n <span\n *ngIf=\"!isSentByCurrentUser\"\n data-testid=\"sender\"\n class=\"str-chat__message-simple-name str-chat__message-sender-name\"\n >\n {{ message?.user?.name ? message?.user?.name : message?.user?.id }}\n </span>\n <span\n data-testid=\"date\"\n class=\"str-chat__message-simple-timestamp str-chat__message-simple-time\"\n >\n {{ parsedDate }}\n </span>\n <ng-container *ngIf=\"message?.message_text_updated_at\">\n <span\n data-testid=\"edited-flag\"\n class=\"str-chat__mesage-simple-edited\"\n translate\n >streamChat.Edited</span\n >\n <div\n data-testid=\"edited-timestamp\"\n class=\"str-chat__message-edited-timestamp\"\n [ngClass]=\"{\n 'str-chat__message-edited-timestamp--open': isEditedFlagOpened,\n 'str-chat__message-edited-timestamp--collapsed': !isEditedFlagOpened\n }\"\n >\n <span translate>streamChat.Edited</span>\n <time\n dateTime=\"{{ message?.message_text_updated_at }}\"\n title=\"{{ message?.message_text_updated_at }}\"\n >\n {{ pasedEditedDate }}\n </time>\n </div>\n </ng-container>\n </div>\n </ng-container>\n</ng-template>\n\n<ng-template #messageStatus>\n <ng-container\n *ngIf=\"\n isSentByCurrentUser &&\n ((isLastSentMessage && message?.status === 'received') ||\n message?.status === 'sending')\n \"\n >\n <ng-container *ngIf=\"message?.status === 'sending'; else sentStatus\">\n <ng-container *ngTemplateOutlet=\"sendingStatus\"></ng-container>\n </ng-container>\n <ng-template #sentStatus>\n <ng-container\n *ngIf=\"\n mode === 'main' && isMessageDeliveredAndRead && canDisplayReadStatus;\n else deliveredStatus\n \"\n >\n <ng-container *ngTemplateOutlet=\"readStatus\"></ng-container>\n </ng-container>\n </ng-template>\n <ng-template #deliveredStatus>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.deliveredStatusTemplate$ | async) ||\n defaultDeliveredStatus;\n context: getDeliveredStatusContext()\n \"\n ></ng-container>\n </ng-template>\n <ng-template #defaultDeliveredStatus>\n <span\n *ngIf=\"mode === 'main'\"\n class=\"\n str-chat__message-simple-status\n str-chat__message-simple-status-angular\n str-chat__message-status\n \"\n data-testid=\"delivered-indicator\"\n tabindex=\"0\"\n [floatUiLoose]=\"floatingContent\"\n loosePlacement=\"top\"\n looseTrigger=\"hover\"\n >\n <float-ui-content #floatingContent>\n <div class=\"str-chat__tooltip str-chat__tooltip-angular\">\n {{ \"streamChat.Delivered\" | translate }}\n </div>\n </float-ui-content>\n <stream-icon-placeholder\n data-testid=\"delivered-icon\"\n icon=\"delivered\"\n ></stream-icon-placeholder>\n </span>\n </ng-template>\n <ng-template #sendingStatus>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.sendingStatusTemplate$ | async) ||\n defaultSendingStatus;\n context: getSendingStatusContext()\n \"\n ></ng-container>\n </ng-template>\n <ng-template #defaultSendingStatus>\n <span\n class=\"\n str-chat__message-simple-status\n str-chat__message-simple-status-angular\n str-chat__message-status\n \"\n data-testid=\"sending-indicator\"\n tabindex=\"0\"\n [floatUiLoose]=\"floatingContent\"\n loosePlacement=\"top\"\n looseTrigger=\"hover\"\n [disableAnimation]=\"false\"\n [preventOverflow]=\"true\"\n [positionFixed]=\"true\"\n >\n <float-ui-content #floatingContent>\n <div class=\"str-chat__tooltip str-chat__tooltip-angular\">\n {{ \"streamChat.Sending...\" | translate }}\n </div>\n </float-ui-content>\n <stream-loading-indicator-placeholder\n data-testid=\"loading-indicator\"\n ></stream-loading-indicator-placeholder>\n </span>\n </ng-template>\n <ng-template #readStatus>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.readStatusTemplate$ | async) ||\n defaultReadStatus;\n context: getReadStatusContext()\n \"\n ></ng-container>\n </ng-template>\n <ng-template #defaultReadStatus let-readByText=\"readByText\">\n <span\n class=\"\n str-chat__message-simple-status\n str-chat__message-simple-status-angular\n str-chat__message-status\n \"\n data-testid=\"read-indicator\"\n tabindex=\"0\"\n [floatUiLoose]=\"floatingContent\"\n loosePlacement=\"top\"\n looseTrigger=\"hover\"\n [disableAnimation]=\"false\"\n [preventOverflow]=\"true\"\n [positionFixed]=\"true\"\n >\n <float-ui-content #floatingContent>\n <div\n class=\"str-chat__tooltip str-chat__tooltip-angular\"\n data-testid=\"read-by-tooltip\"\n >\n {{ readByText }}\n </div>\n </float-ui-content>\n <stream-icon-placeholder icon=\"read\"></stream-icon-placeholder>\n </span>\n </ng-template>\n </ng-container>\n</ng-template>\n\n<ng-template #replyCountButton>\n <div\n class=\"\n str-chat__message-simple-reply-button\n str-chat__message-replies-count-button-wrapper\n \"\n >\n <button\n *ngIf=\"shouldDisplayThreadLink\"\n class=\"str-chat__message-replies-count-button\"\n data-testid=\"reply-count-button\"\n (click)=\"setAsActiveParentMessage()\"\n >\n {{message?.reply_count === 1 ? ('streamChat.1 reply' | translate) : ('streamChat.{{ replyCount }}\n replies' | translate:replyCountParam)}}\n </button>\n </div>\n</ng-template>\n\n<ng-template #attachmentsTemplate>\n <ng-template\n #defaultAttachments\n let-messageId=\"messageId\"\n let-attachments=\"attachments\"\n let-parentMessageId=\"parentMessageId\"\n let-imageModalStateChangeHandler=\"imageModalStateChangeHandler\"\n >\n <stream-attachment-list\n [messageId]=\"messageId\"\n [attachments]=\"attachments\"\n [parentMessageId]=\"parentMessageId\"\n (imageModalStateChange)=\"imageModalStateChangeHandler($event)\"\n ></stream-attachment-list>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.attachmentListTemplate$ | async) ||\n defaultAttachments;\n context: getAttachmentListContext()\n \"\n ></ng-container>\n</ng-template>\n" }]
4995
+ }], ctorParameters: function () { return [{ type: ChatClientService }, { type: ChannelService }, { type: CustomTemplatesService }, { type: i0.ChangeDetectorRef }, { type: DateParserService }, { type: MessageService }, { type: MessageActionsService }, { type: i0.NgZone }]; }, propDecorators: { message: [{
4996
+ type: Input
4997
+ }], enabledMessageActions: [{
4998
+ type: Input
4999
+ }], isLastSentMessage: [{
5000
+ type: Input
5001
+ }], mode: [{
5002
+ type: Input
5003
+ }], isHighlighted: [{
5004
+ type: Input
5005
+ }], scroll$: [{
5006
+ type: Input
5007
+ }], messageMenuTrigger: [{
5008
+ type: ViewChild,
5009
+ args: ['messageMenuTrigger']
5010
+ }], messageMenuFloat: [{
5011
+ type: ViewChild,
5012
+ args: ['messageMenuFloat']
5013
+ }], messageTextElement: [{
5014
+ type: ViewChild,
5015
+ args: ['messageTextElement']
5016
+ }], messageBubble: [{
5017
+ type: ViewChild,
5018
+ args: ['messageBubble']
5019
+ }] } });
5020
+
5021
+ const textareaInjectionToken = new InjectionToken('textareaInjectionToken');
5022
+
5023
+ class TextareaDirective {
5024
+ constructor(viewContainerRef) {
5025
+ this.viewContainerRef = viewContainerRef;
5026
+ this.value = '';
5027
+ this.valueChange = new EventEmitter();
5028
+ this.send = new EventEmitter();
5029
+ this.userMentions = new EventEmitter();
5030
+ this.subscriptions = [];
5031
+ this.unpropagatedChanges = [];
4928
5032
  }
4929
- setLatestMessage(message) {
4930
- this.latestMessage = message;
4931
- if (message?.deleted_at) {
4932
- this.latestMessageText = 'streamChat.Message deleted';
4933
- }
4934
- else if (message?.text) {
4935
- this.latestMessageText =
4936
- getMessageTranslation(message, this.channel, this.chatClientService.chatClient.user) || message.text;
4937
- }
4938
- else if (message?.attachments && message.attachments.length) {
4939
- this.latestMessageText = 'streamChat.🏙 Attachment...';
5033
+ ngOnChanges(changes) {
5034
+ this.unpropagatedChanges.push(changes);
5035
+ if (!this.componentRef) {
5036
+ return;
4940
5037
  }
4941
- if (this.latestMessage && this.latestMessage.type === 'regular') {
4942
- this.latestMessageTime = isOnSeparateDate(new Date(), this.latestMessage.created_at)
4943
- ? this.dateParser.parseDate(this.latestMessage.created_at)
4944
- : this.dateParser.parseTime(this.latestMessage.created_at);
5038
+ if (changes.componentRef) {
5039
+ this.subscriptions.forEach((s) => s.unsubscribe());
5040
+ if (this.componentRef) {
5041
+ this.subscriptions.push(this.componentRef.instance.valueChange.subscribe((value) => this.valueChange.next(value)));
5042
+ this.subscriptions.push(this.componentRef.instance.send.subscribe((value) => this.send.next(value)));
5043
+ if (this.componentRef.instance.userMentions) {
5044
+ this.subscriptions.push(this.componentRef.instance.userMentions.subscribe((value) => this.userMentions.next(value)));
5045
+ }
5046
+ this.componentRef.instance.areMentionsEnabled = this.areMentionsEnabled;
5047
+ this.componentRef.instance.mentionScope = this.mentionScope;
5048
+ this.componentRef.instance.value = this.value;
5049
+ this.componentRef.instance.placeholder = this.placeholder;
5050
+ this.componentRef.instance.inputMode = this.inputMode;
5051
+ this.componentRef.instance.autoFocus = this.autoFocus;
5052
+ }
4945
5053
  }
4946
- else {
4947
- this.latestMessageTime = undefined;
5054
+ if (changes.areMentionsEnabled) {
5055
+ this.componentRef.instance.areMentionsEnabled = this.areMentionsEnabled;
4948
5056
  }
4949
- }
4950
- updateUnreadState() {
4951
- if (this.channel &&
4952
- this.latestMessage &&
4953
- this.latestMessage.user?.id === this.userId &&
4954
- this.latestMessage.status === 'received' &&
4955
- this.latestMessage.type === 'regular') {
4956
- this.latestMessageStatus =
4957
- getReadBy(this.latestMessage, this.channel).length > 0
4958
- ? 'read'
4959
- : 'delivered';
5057
+ if (changes.mentionScope) {
5058
+ this.componentRef.instance.mentionScope = this.mentionScope;
4960
5059
  }
4961
- else {
4962
- this.latestMessageStatus = undefined;
5060
+ if (changes.value) {
5061
+ this.componentRef.instance.value = this.value;
4963
5062
  }
4964
- if ((this.isActive && !this.isUnreadMessageWasCalled) ||
4965
- !this.canSendReadEvents) {
4966
- this.unreadCount = 0;
4967
- this.isUnread = false;
4968
- return;
5063
+ if (changes.placeholder) {
5064
+ this.componentRef.instance.placeholder = this.placeholder;
4969
5065
  }
4970
- this.unreadCount = this.channel.countUnread();
4971
- this.isUnread = !!this.unreadCount;
5066
+ if (changes.inputMode) {
5067
+ this.componentRef.instance.inputMode = this.inputMode;
5068
+ }
5069
+ if (changes.autoFocus) {
5070
+ this.componentRef.instance.autoFocus = this.autoFocus;
5071
+ }
5072
+ // ngOnChanges not called for dynamic components since we don't use template binding
5073
+ let changesToPropagate = {};
5074
+ this.unpropagatedChanges.forEach((c) => (changesToPropagate = { ...changesToPropagate, ...c }));
5075
+ // eslint-disable-next-line @angular-eslint/no-lifecycle-call
5076
+ this.componentRef.instance.ngOnChanges?.(changesToPropagate);
5077
+ this.unpropagatedChanges = [];
4972
5078
  }
4973
5079
  }
4974
- ChannelPreviewComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: ChannelPreviewComponent, deps: [{ token: ChannelService }, { token: i0.NgZone }, { token: ChatClientService }, { token: MessageService }, { token: CustomTemplatesService }, { token: DateParserService }], target: i0.ɵɵFactoryTarget.Component });
4975
- ChannelPreviewComponentcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.0.4", type: ChannelPreviewComponent, selector: "stream-channel-preview", inputs: { channel: "channel" }, ngImport: i0, template: "<button\n class=\"str-chat__channel-preview-messenger str-chat__channel-preview\"\n data-testid=\"channel-preview-container\"\n [class.str-chat__channel-preview-messenger--active]=\"isActive\"\n [class.str-chat__channel-preview--active]=\"isActive\"\n [class.str-chat__channel-preview-messenger--unread]=\"isUnread\"\n (click)=\"setAsActiveChannel()\"\n>\n <div class=\"str-chat__channel-preview-messenger--left\">\n <stream-avatar-placeholder\n type=\"channel\"\n location=\"channel-preview\"\n name=\"{{ avatarName }}\"\n imageUrl=\"{{ avatarImage }}\"\n [channel]=\"channel\"\n ></stream-avatar-placeholder>\n </div>\n <div\n class=\"\n str-chat__channel-preview-messenger--right str-chat__channel-preview-end\n \"\n >\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.channelPreviewInfoTemplate$ | async) ||\n defaultChannelInfo;\n context: {\n channelDisplayTitle: title,\n channel: channel,\n unreadCount: unreadCount,\n latestMessageText: latestMessageText,\n latestMessageStatus: latestMessageStatus,\n latestMessageTime: latestMessageTime,\n latestMessage: latestMessage\n }\n \"\n ></ng-container>\n <ng-template\n #defaultChannelInfo\n let-channelDisplayTitle=\"channelDisplayTitle\"\n let-unreadCount=\"unreadCount\"\n let-latestMessageText=\"latestMessageText\"\n let-latestMessageStatus=\"latestMessageStatus\"\n let-latestMessageTime=\"latestMessageTime\"\n >\n <div class=\"str-chat__channel-preview-end-first-row\">\n <div class=\"str-chat__channel-preview-messenger--name\">\n <span data-testid=\"channel-preview-title\">{{\n channelDisplayTitle\n }}</span>\n </div>\n <div\n *ngIf=\"unreadCount\"\n data-testid=\"unread-badge\"\n class=\"str-chat__channel-preview-unread-badge\"\n >\n {{ unreadCount }}\n </div>\n </div>\n <div class=\"str-chat__channel-preview-end-second-row\">\n <div\n data-testid=\"latest-message\"\n class=\"str-chat__channel-preview-messenger--last-message\"\n >\n <ng-container *ngIf=\"displayAs === 'text'; else asHTML\">\n {{ latestMessageText | translate }}\n </ng-container>\n <ng-template #asHTML>\n <span\n data-testid=\"html-content\"\n [innerHTML]=\"latestMessageText | translate\"\n ></span>\n </ng-template>\n </div>\n <div\n *ngIf=\"latestMessageStatus\"\n data-testid=\"latest-message-status\"\n class=\"str-chat__channel-preview-messenger--status str-chat__channel-preview-messenger--status-{{\n latestMessageStatus\n }}\"\n >\n <stream-icon-placeholder\n [icon]=\"latestMessageStatus === 'delivered' ? 'delivered' : 'read'\"\n ></stream-icon-placeholder>\n </div>\n <div\n *ngIf=\"latestMessageTime\"\n data-testid=\"latest-message-time\"\n class=\"str-chat__channel-preview-messenger--time\"\n >\n {{ latestMessageTime }}\n </div>\n </div>\n </ng-template>\n </div>\n</button>\n", dependencies: [{ kind: "directive", type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i4.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: AvatarPlaceholderComponent, selector: "stream-avatar-placeholder", inputs: ["name", "imageUrl", "location", "channel", "user", "type", "initialsType", "showOnlineIndicator"] }, { kind: "component", type: IconPlaceholderComponent, selector: "stream-icon-placeholder", inputs: ["icon"] }, { kind: "pipe", type: i4.AsyncPipe, name: "async" }, { kind: "pipe", type: i10.TranslatePipe, name: "translate" }] });
4976
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: ChannelPreviewComponent, decorators: [{
4977
- type: Component,
4978
- args: [{ selector: 'stream-channel-preview', template: "<button\n class=\"str-chat__channel-preview-messenger str-chat__channel-preview\"\n data-testid=\"channel-preview-container\"\n [class.str-chat__channel-preview-messenger--active]=\"isActive\"\n [class.str-chat__channel-preview--active]=\"isActive\"\n [class.str-chat__channel-preview-messenger--unread]=\"isUnread\"\n (click)=\"setAsActiveChannel()\"\n>\n <div class=\"str-chat__channel-preview-messenger--left\">\n <stream-avatar-placeholder\n type=\"channel\"\n location=\"channel-preview\"\n name=\"{{ avatarName }}\"\n imageUrl=\"{{ avatarImage }}\"\n [channel]=\"channel\"\n ></stream-avatar-placeholder>\n </div>\n <div\n class=\"\n str-chat__channel-preview-messenger--right str-chat__channel-preview-end\n \"\n >\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.channelPreviewInfoTemplate$ | async) ||\n defaultChannelInfo;\n context: {\n channelDisplayTitle: title,\n channel: channel,\n unreadCount: unreadCount,\n latestMessageText: latestMessageText,\n latestMessageStatus: latestMessageStatus,\n latestMessageTime: latestMessageTime,\n latestMessage: latestMessage\n }\n \"\n ></ng-container>\n <ng-template\n #defaultChannelInfo\n let-channelDisplayTitle=\"channelDisplayTitle\"\n let-unreadCount=\"unreadCount\"\n let-latestMessageText=\"latestMessageText\"\n let-latestMessageStatus=\"latestMessageStatus\"\n let-latestMessageTime=\"latestMessageTime\"\n >\n <div class=\"str-chat__channel-preview-end-first-row\">\n <div class=\"str-chat__channel-preview-messenger--name\">\n <span data-testid=\"channel-preview-title\">{{\n channelDisplayTitle\n }}</span>\n </div>\n <div\n *ngIf=\"unreadCount\"\n data-testid=\"unread-badge\"\n class=\"str-chat__channel-preview-unread-badge\"\n >\n {{ unreadCount }}\n </div>\n </div>\n <div class=\"str-chat__channel-preview-end-second-row\">\n <div\n data-testid=\"latest-message\"\n class=\"str-chat__channel-preview-messenger--last-message\"\n >\n <ng-container *ngIf=\"displayAs === 'text'; else asHTML\">\n {{ latestMessageText | translate }}\n </ng-container>\n <ng-template #asHTML>\n <span\n data-testid=\"html-content\"\n [innerHTML]=\"latestMessageText | translate\"\n ></span>\n </ng-template>\n </div>\n <div\n *ngIf=\"latestMessageStatus\"\n data-testid=\"latest-message-status\"\n class=\"str-chat__channel-preview-messenger--status str-chat__channel-preview-messenger--status-{{\n latestMessageStatus\n }}\"\n >\n <stream-icon-placeholder\n [icon]=\"latestMessageStatus === 'delivered' ? 'delivered' : 'read'\"\n ></stream-icon-placeholder>\n </div>\n <div\n *ngIf=\"latestMessageTime\"\n data-testid=\"latest-message-time\"\n class=\"str-chat__channel-preview-messenger--time\"\n >\n {{ latestMessageTime }}\n </div>\n </div>\n </ng-template>\n </div>\n</button>\n" }]
4979
- }], ctorParameters: function () { return [{ type: ChannelService }, { type: i0.NgZone }, { type: ChatClientService }, { type: MessageService }, { type: CustomTemplatesService }, { type: DateParserService }]; }, propDecorators: { channel: [{
5080
+ TextareaDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: TextareaDirective, deps: [{ token: i0.ViewContainerRef }], target: i0.ɵɵFactoryTarget.Directive });
5081
+ TextareaDirectivedir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "15.0.4", type: TextareaDirective, selector: "[streamTextarea]", inputs: { componentRef: "componentRef", areMentionsEnabled: "areMentionsEnabled", mentionScope: "mentionScope", inputMode: "inputMode", value: "value", placeholder: "placeholder", autoFocus: "autoFocus" }, outputs: { valueChange: "valueChange", send: "send", userMentions: "userMentions" }, usesOnChanges: true, ngImport: i0 });
5082
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: TextareaDirective, decorators: [{
5083
+ type: Directive,
5084
+ args: [{
5085
+ selector: '[streamTextarea]',
5086
+ }]
5087
+ }], ctorParameters: function () { return [{ type: i0.ViewContainerRef }]; }, propDecorators: { componentRef: [{
5088
+ type: Input
5089
+ }], areMentionsEnabled: [{
5090
+ type: Input
5091
+ }], mentionScope: [{
5092
+ type: Input
5093
+ }], inputMode: [{
5094
+ type: Input
5095
+ }], value: [{
5096
+ type: Input
5097
+ }], placeholder: [{
5098
+ type: Input
5099
+ }], autoFocus: [{
4980
5100
  type: Input
5101
+ }], valueChange: [{
5102
+ type: Output
5103
+ }], send: [{
5104
+ type: Output
5105
+ }], userMentions: [{
5106
+ type: Output
4981
5107
  }] } });
4982
5108
 
4983
5109
  /**
4984
- * The `ChannelList` component renders the list of channels.
5110
+ * If you have an emoji picker in your application, you can propagate the selected emoji to the textarea using this service, more info can be found in [custom emoji picker guide](../code-examples/emoji-picker.mdx)
4985
5111
  */
4986
- class ChannelListComponent {
4987
- constructor(channelService, customTemplatesService, themeService) {
4988
- this.channelService = channelService;
4989
- this.customTemplatesService = customTemplatesService;
4990
- this.themeService = themeService;
4991
- this.isLoadingMoreChannels = false;
4992
- this.subscriptions = [];
4993
- this.theme$ = this.themeService.theme$;
4994
- this.channels$ = this.channelService.channels$;
4995
- this.hasMoreChannels$ = this.channelService.hasMoreChannels$;
4996
- this.isError$ = this.channelService.channelQueryState$.pipe(map((s) => !this.isLoadingMoreChannels && s?.state === 'error'));
4997
- this.isInitializing$ = this.channelService.channelQueryState$.pipe(map((s) => !this.isLoadingMoreChannels && s?.state === 'in-progress'));
4998
- this.subscriptions.push(this.customTemplatesService.channelPreviewTemplate$.subscribe((template) => (this.customChannelPreviewTemplate = template)));
4999
- }
5000
- ngOnDestroy() {
5001
- this.subscriptions.forEach((s) => s.unsubscribe());
5002
- }
5003
- async loadMoreChannels() {
5004
- this.isLoadingMoreChannels = true;
5005
- await this.channelService.loadMoreChannels();
5006
- this.isLoadingMoreChannels = false;
5007
- }
5008
- trackByChannelId(index, item) {
5009
- return item.cid;
5112
+ class EmojiInputService {
5113
+ constructor() {
5114
+ /**
5115
+ * If you have an emoji picker in your application, you can propagate the selected emoji to the textarea using this Subject, more info can be found in [custom emoji picker guide](../code-examples/emoji-picker.mdx)
5116
+ */
5117
+ this.emojiInput$ = new Subject();
5010
5118
  }
5011
5119
  }
5012
- ChannelListComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: ChannelListComponent, deps: [{ token: ChannelService }, { token: CustomTemplatesService }, { token: ThemeService }], target: i0.ɵɵFactoryTarget.Component });
5013
- ChannelListComponentcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.0.4", type: ChannelListComponent, selector: "stream-channel-list", viewQueries: [{ propertyName: "container", first: true, predicate: ["container"], descendants: true }], ngImport: i0, template: "<div\n #container\n data-testid=\"channel-list-container\"\n class=\"str-chat str-chat__channel-list str-chat-channel-list messaging str-chat__theme-{{\n theme$ | async\n }}\"\n>\n <div\n *ngIf=\"\n (isError$ | async) === false && (isInitializing$ | async) === false;\n else statusIndicator\n \"\n class=\"str-chat__channel-list-messenger\"\n >\n <div class=\"str-chat__channel-list-messenger__main\">\n <ng-content select=\"[channel-list-top]\"></ng-content>\n <div\n *ngIf=\"!(channels$ | async)?.length\"\n class=\"str-chat__channel-list-empty\"\n >\n <stream-icon icon=\"chat-bubble\"></stream-icon>\n <p data-testid=\"empty-channel-list-indicator\">\n {{ \"streamChat.You have no channels currently\" | translate }}\n </p>\n </div>\n <p\n *ngIf=\"!(channels$ | async)?.length\"\n class=\"str-chat__channel-list-empty-v1\"\n data-testid=\"empty-channel-list-indicator\"\n >\n {{ \"streamChat.You have no channels currently\" | translate }}\n </p>\n <ng-container\n *ngFor=\"let channel of channels$ | async; trackBy: trackByChannelId\"\n >\n <ng-template #defaultTemplate let-channelInput=\"channel\">\n <stream-channel-preview\n data-testclass=\"channel-preview\"\n [channel]=\"channelInput\"\n ></stream-channel-preview>\n </ng-template>\n <div>\n <ng-container\n *ngTemplateOutlet=\"\n customChannelPreviewTemplate || defaultTemplate;\n context: { channel: channel }\n \"\n ></ng-container>\n </div>\n </ng-container>\n <div\n *ngIf=\"hasMoreChannels$ | async\"\n class=\"str-chat__load-more-button\"\n data-testid=\"load-more\"\n (click)=\"loadMoreChannels()\"\n (keyup.enter)=\"loadMoreChannels()\"\n >\n <button\n class=\"str-chat__load-more-button__button str-chat__cta-button\"\n data-testid=\"load-more-button\"\n [disabled]=\"isLoadingMoreChannels\"\n >\n <span *ngIf=\"!isLoadingMoreChannels; else loadingIndicator\">{{\n \"Load more\" | translate\n }}</span>\n <ng-template #loadingIndicator\n ><stream-loading-indicator-placeholder></stream-loading-indicator-placeholder\n ></ng-template>\n </button>\n </div>\n <ng-content select=\"[channel-list-bottom]\"></ng-content>\n </div>\n </div>\n</div>\n\n<ng-template #statusIndicator>\n <ng-container *ngIf=\"isError$ | async\">\n <ng-container *ngTemplateOutlet=\"chatDown\"></ng-container>\n </ng-container>\n <ng-container *ngIf=\"isInitializing$ | async\">\n <ng-container *ngTemplateOutlet=\"loadingChannels\"></ng-container>\n </ng-container>\n</ng-template>\n\n<ng-template #chatDown>\n <div data-testid=\"chatdown-container\" class=\"str-chat__down\">\n <ng-container *ngTemplateOutlet=\"loadingChannels\"></ng-container>\n </div>\n</ng-template>\n\n<ng-template #loadingChannels>\n <div data-testid=\"loading-indicator\" class=\"str-chat__loading-channels\">\n <ng-container *ngTemplateOutlet=\"loadingChannel\"></ng-container>\n <ng-container *ngTemplateOutlet=\"loadingChannel\"></ng-container>\n <ng-container *ngTemplateOutlet=\"loadingChannel\"></ng-container>\n </div>\n</ng-template>\n\n<ng-template #loadingChannel>\n <div\n class=\"str-chat__loading-channels-item str-chat__channel-preview-loading\"\n >\n <div class=\"str-chat__loading-channels-avatar\"></div>\n <div\n class=\"\n str-chat__loading-channels-meta str-chat__channel-preview-end-loading\n \"\n >\n <div class=\"str-chat__loading-channels-username\"></div>\n <div class=\"str-chat__loading-channels-status\"></div>\n </div>\n </div>\n</ng-template>\n", dependencies: [{ kind: "directive", type: i4.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i4.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: ChannelPreviewComponent, selector: "stream-channel-preview", inputs: ["channel"] }, { kind: "component", type: IconComponent, selector: "stream-icon", inputs: ["icon"] }, { kind: "component", type: LoadingIndicatorPlaceholderComponent, selector: "stream-loading-indicator-placeholder" }, { kind: "pipe", type: i4.AsyncPipe, name: "async" }, { kind: "pipe", type: i10.TranslatePipe, name: "translate" }] });
5014
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: ChannelListComponent, decorators: [{
5015
- type: Component,
5016
- args: [{ selector: 'stream-channel-list', template: "<div\n #container\n data-testid=\"channel-list-container\"\n class=\"str-chat str-chat__channel-list str-chat-channel-list messaging str-chat__theme-{{\n theme$ | async\n }}\"\n>\n <div\n *ngIf=\"\n (isError$ | async) === false && (isInitializing$ | async) === false;\n else statusIndicator\n \"\n class=\"str-chat__channel-list-messenger\"\n >\n <div class=\"str-chat__channel-list-messenger__main\">\n <ng-content select=\"[channel-list-top]\"></ng-content>\n <div\n *ngIf=\"!(channels$ | async)?.length\"\n class=\"str-chat__channel-list-empty\"\n >\n <stream-icon icon=\"chat-bubble\"></stream-icon>\n <p data-testid=\"empty-channel-list-indicator\">\n {{ \"streamChat.You have no channels currently\" | translate }}\n </p>\n </div>\n <p\n *ngIf=\"!(channels$ | async)?.length\"\n class=\"str-chat__channel-list-empty-v1\"\n data-testid=\"empty-channel-list-indicator\"\n >\n {{ \"streamChat.You have no channels currently\" | translate }}\n </p>\n <ng-container\n *ngFor=\"let channel of channels$ | async; trackBy: trackByChannelId\"\n >\n <ng-template #defaultTemplate let-channelInput=\"channel\">\n <stream-channel-preview\n data-testclass=\"channel-preview\"\n [channel]=\"channelInput\"\n ></stream-channel-preview>\n </ng-template>\n <div>\n <ng-container\n *ngTemplateOutlet=\"\n customChannelPreviewTemplate || defaultTemplate;\n context: { channel: channel }\n \"\n ></ng-container>\n </div>\n </ng-container>\n <div\n *ngIf=\"hasMoreChannels$ | async\"\n class=\"str-chat__load-more-button\"\n data-testid=\"load-more\"\n (click)=\"loadMoreChannels()\"\n (keyup.enter)=\"loadMoreChannels()\"\n >\n <button\n class=\"str-chat__load-more-button__button str-chat__cta-button\"\n data-testid=\"load-more-button\"\n [disabled]=\"isLoadingMoreChannels\"\n >\n <span *ngIf=\"!isLoadingMoreChannels; else loadingIndicator\">{{\n \"Load more\" | translate\n }}</span>\n <ng-template #loadingIndicator\n ><stream-loading-indicator-placeholder></stream-loading-indicator-placeholder\n ></ng-template>\n </button>\n </div>\n <ng-content select=\"[channel-list-bottom]\"></ng-content>\n </div>\n </div>\n</div>\n\n<ng-template #statusIndicator>\n <ng-container *ngIf=\"isError$ | async\">\n <ng-container *ngTemplateOutlet=\"chatDown\"></ng-container>\n </ng-container>\n <ng-container *ngIf=\"isInitializing$ | async\">\n <ng-container *ngTemplateOutlet=\"loadingChannels\"></ng-container>\n </ng-container>\n</ng-template>\n\n<ng-template #chatDown>\n <div data-testid=\"chatdown-container\" class=\"str-chat__down\">\n <ng-container *ngTemplateOutlet=\"loadingChannels\"></ng-container>\n </div>\n</ng-template>\n\n<ng-template #loadingChannels>\n <div data-testid=\"loading-indicator\" class=\"str-chat__loading-channels\">\n <ng-container *ngTemplateOutlet=\"loadingChannel\"></ng-container>\n <ng-container *ngTemplateOutlet=\"loadingChannel\"></ng-container>\n <ng-container *ngTemplateOutlet=\"loadingChannel\"></ng-container>\n </div>\n</ng-template>\n\n<ng-template #loadingChannel>\n <div\n class=\"str-chat__loading-channels-item str-chat__channel-preview-loading\"\n >\n <div class=\"str-chat__loading-channels-avatar\"></div>\n <div\n class=\"\n str-chat__loading-channels-meta str-chat__channel-preview-end-loading\n \"\n >\n <div class=\"str-chat__loading-channels-username\"></div>\n <div class=\"str-chat__loading-channels-status\"></div>\n </div>\n </div>\n</ng-template>\n" }]
5017
- }], ctorParameters: function () { return [{ type: ChannelService }, { type: CustomTemplatesService }, { type: ThemeService }]; }, propDecorators: { container: [{
5018
- type: ViewChild,
5019
- args: ['container']
5020
- }] } });
5120
+ EmojiInputService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: EmojiInputService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
5121
+ EmojiInputServiceprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: EmojiInputService, providedIn: 'root' });
5122
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: EmojiInputService, decorators: [{
5123
+ type: Injectable,
5124
+ args: [{
5125
+ providedIn: 'root',
5126
+ }]
5127
+ }], ctorParameters: function () { return []; } });
5021
5128
 
5022
5129
  /**
5023
- * The `MessageReactions` component displays the reactions of a message. You can read more about [message reactions](https://getstream.io/chat/docs/javascript/send_reaction/?language=javascript) in the platform documentation.
5130
+ * The `MessageInputConfigService` is used to keep a consistent configuration among the different [`MessageInput`](../components/MessageInputComponent.mdx) components if your UI has more than one input component.
5024
5131
  */
5025
- class MessageReactionsComponent {
5026
- constructor(cdRef, channelService, messageReactionsService, customTemplatesService) {
5027
- this.cdRef = cdRef;
5028
- this.channelService = channelService;
5029
- this.messageReactionsService = messageReactionsService;
5030
- this.customTemplatesService = customTemplatesService;
5132
+ class MessageInputConfigService {
5133
+ constructor() {
5031
5134
  /**
5032
- * The number of reactions grouped by [reaction types](https://github.com/GetStream/stream-chat-angular/tree/master/projects/stream-chat-angular/src/lib/message-reactions/message-reactions.component.ts)
5135
+ * If file upload is enabled, the user can open a file selector from the input. Please note that the user also needs to have the necessary [channel capability](https://getstream.io/chat/docs/javascript/channel_capabilities/?language=javascript).
5033
5136
  */
5034
- this.messageReactionCounts = {};
5137
+ this.isFileUploadEnabled = true;
5035
5138
  /**
5036
- * List of reactions of a [message](../types/stream-message.mdx), used to display the users of a reaction type.
5139
+ * If true, users can mention other users in messages. You also [need to use the `AutocompleteTextarea`](../concepts/opt-in-architecture.mdx) for this feature to work.
5037
5140
  */
5038
- this.latestReactions = [];
5141
+ this.areMentionsEnabled = true;
5039
5142
  /**
5040
- * List of the user's own reactions of a [message](../types/stream-message.mdx), used to display the users of a reaction type.
5143
+ * If `false`, users can only upload one attachment per message
5041
5144
  */
5042
- this.ownReactions = [];
5043
- this.isLoading = true;
5044
- this.reactions = [];
5045
- this.shouldHandleReactionClick = true;
5046
- this.existingReactions = [];
5047
- this.reactionsCount = 0;
5048
- this.reactionOptions = [];
5049
- this.subscriptions = [];
5050
- this.isViewInited = false;
5051
- this.isOpenChange = (isOpen) => {
5052
- this.selectedReactionType = isOpen ? this.selectedReactionType : undefined;
5053
- };
5054
- }
5055
- ngOnInit() {
5056
- this.subscriptions.push(this.messageReactionsService.reactions$.subscribe((reactions) => {
5057
- this.reactionOptions = Object.keys(reactions);
5058
- this.setExistingReactions();
5059
- if (this.isViewInited) {
5060
- this.cdRef.detectChanges();
5061
- }
5062
- }));
5063
- }
5064
- ngOnChanges(changes) {
5065
- if (changes.messageReactionCounts) {
5066
- this.setExistingReactions();
5067
- }
5068
- if (changes.messageReactionCounts && this.messageReactionCounts) {
5069
- const reactionsCount = Object.keys(this.messageReactionCounts).reduce((acc, key) => acc + (this.messageReactionCounts[key] || 0), 0);
5070
- this.shouldHandleReactionClick =
5071
- reactionsCount <= ChannelService.MAX_MESSAGE_REACTIONS_TO_FETCH ||
5072
- !!this.messageReactionsService.customReactionClickHandler;
5073
- }
5074
- }
5075
- ngAfterViewInit() {
5076
- this.isViewInited = true;
5077
- }
5078
- ngOnDestroy() {
5079
- this.subscriptions.forEach((s) => s.unsubscribe());
5080
- }
5081
- getEmojiByReaction(reactionType) {
5082
- return this.messageReactionsService.reactions[reactionType];
5083
- }
5084
- reactionSelected(reactionType) {
5085
- if (!this.shouldHandleReactionClick) {
5086
- return;
5087
- }
5088
- if (!this.messageId) {
5089
- return;
5090
- }
5091
- if (this.messageReactionsService.customReactionClickHandler) {
5092
- this.messageReactionsService.customReactionClickHandler({
5093
- messageId: this.messageId,
5094
- reactionType: reactionType,
5095
- });
5096
- }
5097
- else {
5098
- this.selectedReactionType = reactionType;
5099
- void this.fetchAllReactions();
5100
- }
5101
- }
5102
- getUsersByReaction(reactionType) {
5103
- return this.latestReactions
5104
- .filter((r) => r.type === reactionType)
5105
- .map((r) => r.user?.name || r.user?.id)
5106
- .filter((i) => !!i)
5107
- .join(', ');
5108
- }
5109
- getAllUsersByReaction(reactionType) {
5110
- if (!reactionType) {
5111
- return [];
5112
- }
5113
- const users = this.reactions
5114
- .filter((r) => r.type === reactionType)
5115
- .map((r) => r.user)
5116
- .filter((i) => !!i);
5117
- users.sort((u1, u2) => {
5118
- const name1 = u1.name?.toLowerCase();
5119
- const name2 = u2.name?.toLowerCase();
5120
- if (!name1) {
5121
- return 1;
5122
- }
5123
- if (!name2) {
5124
- return -1;
5125
- }
5126
- if (name1 === name2) {
5127
- return 0;
5128
- }
5129
- if (name1 < name2) {
5130
- return -1;
5131
- }
5132
- else {
5133
- return 1;
5134
- }
5135
- });
5136
- return users;
5137
- }
5138
- trackByMessageReaction(_, item) {
5139
- return item;
5145
+ this.isMultipleFileUploadEnabled = true;
5146
+ /**
5147
+ * The scope for user mentions, either members of the current channel of members of the application
5148
+ */
5149
+ this.mentionScope = 'channel';
5150
+ /**
5151
+ * In `desktop` mode the `Enter` key will trigger message sending, in `mobile` mode the `Enter` key will insert a new line to the message input.
5152
+ */
5153
+ this.inputMode = 'desktop';
5140
5154
  }
5141
- trackByUserId(_, item) {
5142
- return item.id;
5155
+ }
5156
+ MessageInputConfigService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: MessageInputConfigService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
5157
+ MessageInputConfigService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: MessageInputConfigService, providedIn: 'root' });
5158
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: MessageInputConfigService, decorators: [{
5159
+ type: Injectable,
5160
+ args: [{
5161
+ providedIn: 'root',
5162
+ }]
5163
+ }], ctorParameters: function () { return []; } });
5164
+
5165
+ /**
5166
+ * The `AttachmentPreviewList` component displays a preview of the attachments uploaded to a message. Users can delete attachments using the preview component, or retry upload if it failed previously.
5167
+ */
5168
+ class AttachmentPreviewListComponent {
5169
+ constructor() {
5170
+ /**
5171
+ * An output to notify the parent component if the user tries to retry a failed upload
5172
+ */
5173
+ this.retryAttachmentUpload = new EventEmitter();
5174
+ /**
5175
+ * An output to notify the parent component if the user wants to delete a file
5176
+ */
5177
+ this.deleteAttachment = new EventEmitter();
5143
5178
  }
5144
- isOwnReaction(reactionType) {
5145
- return !!this.ownReactions.find((r) => r.type === reactionType);
5179
+ attachmentUploadRetried(file) {
5180
+ this.retryAttachmentUpload.emit(file);
5146
5181
  }
5147
- async fetchAllReactions() {
5148
- if (!this.messageId) {
5149
- return;
5150
- }
5151
- this.isLoading = true;
5152
- try {
5153
- this.reactions = await this.channelService.getMessageReactions(this.messageId);
5154
- }
5155
- catch (error) {
5156
- this.selectedReactionType = undefined;
5157
- }
5158
- finally {
5159
- this.isLoading = false;
5160
- this.cdRef.detectChanges();
5161
- }
5182
+ attachmentDeleted(upload) {
5183
+ this.deleteAttachment.emit(upload);
5162
5184
  }
5163
- setExistingReactions() {
5164
- this.existingReactions = Object.keys(this.messageReactionCounts)
5165
- .filter((k) => this.reactionOptions.indexOf(k) !== -1)
5166
- .filter((k) => this.messageReactionCounts[k] > 0);
5167
- this.reactionsCount = this.existingReactions.reduce((total, reaction) => total + this.messageReactionCounts[reaction], 0);
5185
+ trackByFile(_, item) {
5186
+ return item.file;
5168
5187
  }
5169
5188
  }
5170
- MessageReactionsComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: MessageReactionsComponent, deps: [{ token: i0.ChangeDetectorRef }, { token: ChannelService }, { token: MessageReactionsService }, { token: CustomTemplatesService }], target: i0.ɵɵFactoryTarget.Component });
5171
- MessageReactionsComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.0.4", type: MessageReactionsComponent, selector: "stream-message-reactions", inputs: { messageId: "messageId", messageReactionCounts: "messageReactionCounts", latestReactions: "latestReactions", ownReactions: "ownReactions" }, viewQueries: [{ propertyName: "selectorContainer", first: true, predicate: ["selectorContainer"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div\n *ngIf=\"existingReactions.length > 0\"\n data-testid=\"reaction-list\"\n class=\"str-chat__reaction-list str-chat__message-reactions-container\"\n [class.str-chat__reaction-list--reverse]=\"true\"\n>\n <ul class=\"str-chat__message-reactions\">\n <li\n *ngFor=\"\n let reactionType of existingReactions;\n trackBy: trackByMessageReaction\n \"\n class=\"str-chat__message-reaction\"\n data-testclass=\"emoji\"\n [ngStyle]=\"{ cursor: shouldHandleReactionClick ? 'pointer' : 'default' }\"\n [class.str-chat__message-reaction-own]=\"isOwnReaction(reactionType)\"\n (click)=\"reactionSelected(reactionType)\"\n (keyup.enter)=\"reactionSelected(reactionType)\"\n >\n <span class=\"emoji str-chat__message-reaction-emoji\">\n {{ getEmojiByReaction(reactionType) }}&nbsp;\n </span>\n <span\n data-testclass=\"reaction-list-reaction-count\"\n class=\"str-chat__message-reaction-count\"\n >\n {{ messageReactionCounts[reactionType] }}\n </span>\n </li>\n <li>\n <span\n data-testid=\"reactions-count\"\n class=\"str-chat__reaction-list--counter\"\n >{{ reactionsCount }}</span\n >\n </li>\n </ul>\n</div>\n\n<ng-container *ngIf=\"selectedReactionType\">\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.modalTemplate$ | async) || defaultModal;\n context: {\n isOpen: !!selectedReactionType,\n messageId: messageId,\n reactionType: selectedReactionType,\n isOpenChangeHandler: isOpenChange,\n content: modalContent\n }\n \"\n ></ng-container>\n</ng-container>\n\n<ng-template\n #defaultModal\n let-isOpen=\"isOpen\"\n let-messageId=\"messageId\"\n let-reactionType=\"reactionType\"\n let-isOpenChangeHandler=\"isOpenChangeHandler\"\n let-content=\"content\"\n>\n <stream-modal\n class=\"str-chat__message-reactions-details-modal\"\n [isOpen]=\"isOpen\"\n [content]=\"content\"\n (isOpenChange)=\"isOpenChangeHandler($event)\"\n >\n </stream-modal>\n</ng-template>\n\n<ng-template #modalContent>\n <div class=\"str-chat__message-reactions-details\">\n <div class=\"str-chat__message-reactions-details-reaction-types\">\n <div\n *ngFor=\"\n let reactionType of existingReactions;\n trackBy: trackByMessageReaction\n \"\n class=\"str-chat__message-reactions-details-reaction-type\"\n [ngStyle]=\"{\n cursor: shouldHandleReactionClick ? 'pointer' : 'default'\n }\"\n attr.data-testid=\"reaction-details-selector-{{ reactionType }}\"\n [class.str-chat__message-reactions-details-reaction-type--selected]=\"\n reactionType === selectedReactionType\n \"\n (click)=\"selectedReactionType = reactionType; allUsers.scrollTop = 0\"\n (keyup.enter)=\"\n selectedReactionType = reactionType; allUsers.scrollTop = 0\n \"\n >\n <span class=\"emoji str-chat__message-reaction-emoji\">\n {{ getEmojiByReaction(reactionType) }}&nbsp;\n </span>\n <span class=\"str-chat__message-reaction-count\">\n {{ messageReactionCounts[reactionType] }}\n </span>\n </div>\n </div>\n <div\n class=\"\n emoji\n str-chat__message-reaction-emoji str-chat__message-reaction-emoji-big\n \"\n >\n {{ getEmojiByReaction(selectedReactionType!) }}\n </div>\n <div\n #allUsers\n data-testid=\"all-reacting-users\"\n class=\"str-chat__message-reactions-details-reacting-users\"\n >\n <stream-loading-indicator\n *ngIf=\"isLoading; else reactions\"\n ></stream-loading-indicator>\n <ng-template #reactions>\n <div\n *ngFor=\"\n let user of getAllUsersByReaction(selectedReactionType);\n trackBy: trackByUserId\n \"\n class=\"str-chat__message-reactions-details-reacting-user\"\n >\n <stream-avatar-placeholder\n data-testclass=\"avatar\"\n class=\"str-chat__avatar str-chat__avatar--circle\"\n type=\"user\"\n location=\"reaction\"\n [imageUrl]=\"user.image\"\n [name]=\"user.name\"\n [user]=\"user\"\n ></stream-avatar-placeholder>\n <span\n data-testclass=\"reaction-user-username\"\n class=\"str-chat__user-item--name\"\n >{{ user.name }}</span\n >\n </div>\n </ng-template>\n </div>\n </div>\n</ng-template>\n", dependencies: [{ kind: "directive", type: i4.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i4.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i4.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: AvatarPlaceholderComponent, selector: "stream-avatar-placeholder", inputs: ["name", "imageUrl", "location", "channel", "user", "type", "initialsType", "showOnlineIndicator"] }, { kind: "component", type: LoadingIndicatorComponent, selector: "stream-loading-indicator" }, { kind: "component", type: ModalComponent, selector: "stream-modal", inputs: ["isOpen", "content"], outputs: ["isOpenChange"] }, { kind: "pipe", type: i4.AsyncPipe, name: "async" }] });
5172
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: MessageReactionsComponent, decorators: [{
5189
+ AttachmentPreviewListComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: AttachmentPreviewListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5190
+ AttachmentPreviewListComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.0.4", type: AttachmentPreviewListComponent, selector: "stream-attachment-preview-list", inputs: { attachmentUploads$: "attachmentUploads$" }, outputs: { retryAttachmentUpload: "retryAttachmentUpload", deleteAttachment: "deleteAttachment" }, ngImport: i0, template: "<div\n *ngIf=\"(attachmentUploads$ | async)?.length\"\n class=\"str-chat__attachment-preview-list\"\n>\n <div class=\"str-chat__attachment-list-scroll-container\">\n <ng-container\n *ngFor=\"\n let attachmentUpload of attachmentUploads$ | async;\n trackBy: trackByFile\n \"\n >\n <div\n *ngIf=\"attachmentUpload.type === 'image'\"\n class=\"str-chat__attachment-preview-image\"\n data-testclass=\"attachment-image-preview\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n deleteButton;\n context: { attachmentUpload: attachmentUpload }\n \"\n ></ng-container>\n <div\n *ngIf=\"attachmentUpload.state === 'uploading'\"\n class=\"str-chat__attachment-preview-image-loading\"\n >\n <stream-loading-indicator-placeholder\n data-testclass=\"loading-indicator\"\n ></stream-loading-indicator-placeholder>\n </div>\n <ng-container\n *ngTemplateOutlet=\"\n retryButton;\n context: { attachmentUpload: attachmentUpload }\n \"\n ></ng-container>\n <img\n *ngIf=\"attachmentUpload.url || attachmentUpload.previewUri\"\n class=\"str-chat__attachment-preview-thumbnail\"\n data-testclass=\"attachment-image\"\n src=\"{{\n attachmentUpload.url\n ? attachmentUpload.url\n : attachmentUpload.previewUri\n }}\"\n alt=\"{{ attachmentUpload.file.name }}\"\n />\n </div>\n <div\n *ngIf=\"\n attachmentUpload.type === 'file' || attachmentUpload.type === 'video'\n \"\n class=\"str-chat__attachment-preview-file\"\n data-testclass=\"attachment-file-preview\"\n >\n <stream-icon-placeholder\n class=\"str-chat__attachment-preview-file-icon\"\n icon=\"unspecified-filetype\"\n ></stream-icon-placeholder>\n\n <div class=\"str-chat__attachment-preview-file-end\">\n <div class=\"str-chat__attachment-preview-file-name\">\n {{ attachmentUpload.file.name }}\n </div>\n <a\n *ngIf=\"attachmentUpload.state === 'success'\"\n class=\"str-chat__attachment-preview-file-download\"\n data-testclass=\"file-download-link\"\n download\n href=\"{{ attachmentUpload.url }}\"\n (click)=\"attachmentUpload.url ? null : $event.preventDefault()\"\n (keyup.enter)=\"\n attachmentUpload.url ? null : $event.preventDefault()\n \"\n >\n <stream-icon-placeholder icon=\"download\"></stream-icon-placeholder>\n </a>\n <stream-loading-indicator-placeholder\n *ngIf=\"attachmentUpload.state === 'uploading'\"\n data-testclass=\"loading-indicator\"\n ></stream-loading-indicator-placeholder>\n </div>\n <ng-container\n *ngTemplateOutlet=\"\n deleteButton;\n context: { attachmentUpload: attachmentUpload }\n \"\n ></ng-container>\n <ng-container\n *ngTemplateOutlet=\"\n retryButton;\n context: { attachmentUpload: attachmentUpload }\n \"\n ></ng-container>\n </div>\n </ng-container>\n </div>\n</div>\n\n<ng-template #deleteButton let-attachmentUpload=\"attachmentUpload\">\n <div\n class=\"str-chat__attachment-preview-delete\"\n data-testclass=\"file-delete\"\n role=\"button\"\n (click)=\"attachmentDeleted(attachmentUpload)\"\n (keyup.enter)=\"attachmentDeleted(attachmentUpload)\"\n >\n <stream-icon-placeholder icon=\"close\"></stream-icon-placeholder>\n </div>\n</ng-template>\n\n<ng-template #retryButton let-attachmentUpload=\"attachmentUpload\">\n <div\n *ngIf=\"attachmentUpload.state === 'error'\"\n data-testclass=\"upload-retry\"\n class=\"str-chat__attachment-preview-error str-chat__attachment-preview-error-{{\n attachmentUpload.type === 'image' ? 'image' : 'file'\n }}\"\n (click)=\"attachmentUploadRetried(attachmentUpload.file)\"\n (keyup.enter)=\"attachmentUploadRetried(attachmentUpload.file)\"\n >\n <stream-icon-placeholder icon=\"retry\"></stream-icon-placeholder>\n </div>\n</ng-template>\n", dependencies: [{ kind: "directive", type: i4.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i4.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: IconPlaceholderComponent, selector: "stream-icon-placeholder", inputs: ["icon"] }, { kind: "component", type: LoadingIndicatorPlaceholderComponent, selector: "stream-loading-indicator-placeholder" }, { kind: "pipe", type: i4.AsyncPipe, name: "async" }] });
5191
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: AttachmentPreviewListComponent, decorators: [{
5173
5192
  type: Component,
5174
- args: [{ selector: 'stream-message-reactions', template: "<div\n *ngIf=\"existingReactions.length > 0\"\n data-testid=\"reaction-list\"\n class=\"str-chat__reaction-list str-chat__message-reactions-container\"\n [class.str-chat__reaction-list--reverse]=\"true\"\n>\n <ul class=\"str-chat__message-reactions\">\n <li\n *ngFor=\"\n let reactionType of existingReactions;\n trackBy: trackByMessageReaction\n \"\n class=\"str-chat__message-reaction\"\n data-testclass=\"emoji\"\n [ngStyle]=\"{ cursor: shouldHandleReactionClick ? 'pointer' : 'default' }\"\n [class.str-chat__message-reaction-own]=\"isOwnReaction(reactionType)\"\n (click)=\"reactionSelected(reactionType)\"\n (keyup.enter)=\"reactionSelected(reactionType)\"\n >\n <span class=\"emoji str-chat__message-reaction-emoji\">\n {{ getEmojiByReaction(reactionType) }}&nbsp;\n </span>\n <span\n data-testclass=\"reaction-list-reaction-count\"\n class=\"str-chat__message-reaction-count\"\n >\n {{ messageReactionCounts[reactionType] }}\n </span>\n </li>\n <li>\n <span\n data-testid=\"reactions-count\"\n class=\"str-chat__reaction-list--counter\"\n >{{ reactionsCount }}</span\n >\n </li>\n </ul>\n</div>\n\n<ng-container *ngIf=\"selectedReactionType\">\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.modalTemplate$ | async) || defaultModal;\n context: {\n isOpen: !!selectedReactionType,\n messageId: messageId,\n reactionType: selectedReactionType,\n isOpenChangeHandler: isOpenChange,\n content: modalContent\n }\n \"\n ></ng-container>\n</ng-container>\n\n<ng-template\n #defaultModal\n let-isOpen=\"isOpen\"\n let-messageId=\"messageId\"\n let-reactionType=\"reactionType\"\n let-isOpenChangeHandler=\"isOpenChangeHandler\"\n let-content=\"content\"\n>\n <stream-modal\n class=\"str-chat__message-reactions-details-modal\"\n [isOpen]=\"isOpen\"\n [content]=\"content\"\n (isOpenChange)=\"isOpenChangeHandler($event)\"\n >\n </stream-modal>\n</ng-template>\n\n<ng-template #modalContent>\n <div class=\"str-chat__message-reactions-details\">\n <div class=\"str-chat__message-reactions-details-reaction-types\">\n <div\n *ngFor=\"\n let reactionType of existingReactions;\n trackBy: trackByMessageReaction\n \"\n class=\"str-chat__message-reactions-details-reaction-type\"\n [ngStyle]=\"{\n cursor: shouldHandleReactionClick ? 'pointer' : 'default'\n }\"\n attr.data-testid=\"reaction-details-selector-{{ reactionType }}\"\n [class.str-chat__message-reactions-details-reaction-type--selected]=\"\n reactionType === selectedReactionType\n \"\n (click)=\"selectedReactionType = reactionType; allUsers.scrollTop = 0\"\n (keyup.enter)=\"\n selectedReactionType = reactionType; allUsers.scrollTop = 0\n \"\n >\n <span class=\"emoji str-chat__message-reaction-emoji\">\n {{ getEmojiByReaction(reactionType) }}&nbsp;\n </span>\n <span class=\"str-chat__message-reaction-count\">\n {{ messageReactionCounts[reactionType] }}\n </span>\n </div>\n </div>\n <div\n class=\"\n emoji\n str-chat__message-reaction-emoji str-chat__message-reaction-emoji-big\n \"\n >\n {{ getEmojiByReaction(selectedReactionType!) }}\n </div>\n <div\n #allUsers\n data-testid=\"all-reacting-users\"\n class=\"str-chat__message-reactions-details-reacting-users\"\n >\n <stream-loading-indicator\n *ngIf=\"isLoading; else reactions\"\n ></stream-loading-indicator>\n <ng-template #reactions>\n <div\n *ngFor=\"\n let user of getAllUsersByReaction(selectedReactionType);\n trackBy: trackByUserId\n \"\n class=\"str-chat__message-reactions-details-reacting-user\"\n >\n <stream-avatar-placeholder\n data-testclass=\"avatar\"\n class=\"str-chat__avatar str-chat__avatar--circle\"\n type=\"user\"\n location=\"reaction\"\n [imageUrl]=\"user.image\"\n [name]=\"user.name\"\n [user]=\"user\"\n ></stream-avatar-placeholder>\n <span\n data-testclass=\"reaction-user-username\"\n class=\"str-chat__user-item--name\"\n >{{ user.name }}</span\n >\n </div>\n </ng-template>\n </div>\n </div>\n</ng-template>\n" }]
5175
- }], ctorParameters: function () { return [{ type: i0.ChangeDetectorRef }, { type: ChannelService }, { type: MessageReactionsService }, { type: CustomTemplatesService }]; }, propDecorators: { messageId: [{
5176
- type: Input
5177
- }], messageReactionCounts: [{
5178
- type: Input
5179
- }], latestReactions: [{
5180
- type: Input
5181
- }], ownReactions: [{
5193
+ args: [{ selector: 'stream-attachment-preview-list', template: "<div\n *ngIf=\"(attachmentUploads$ | async)?.length\"\n class=\"str-chat__attachment-preview-list\"\n>\n <div class=\"str-chat__attachment-list-scroll-container\">\n <ng-container\n *ngFor=\"\n let attachmentUpload of attachmentUploads$ | async;\n trackBy: trackByFile\n \"\n >\n <div\n *ngIf=\"attachmentUpload.type === 'image'\"\n class=\"str-chat__attachment-preview-image\"\n data-testclass=\"attachment-image-preview\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n deleteButton;\n context: { attachmentUpload: attachmentUpload }\n \"\n ></ng-container>\n <div\n *ngIf=\"attachmentUpload.state === 'uploading'\"\n class=\"str-chat__attachment-preview-image-loading\"\n >\n <stream-loading-indicator-placeholder\n data-testclass=\"loading-indicator\"\n ></stream-loading-indicator-placeholder>\n </div>\n <ng-container\n *ngTemplateOutlet=\"\n retryButton;\n context: { attachmentUpload: attachmentUpload }\n \"\n ></ng-container>\n <img\n *ngIf=\"attachmentUpload.url || attachmentUpload.previewUri\"\n class=\"str-chat__attachment-preview-thumbnail\"\n data-testclass=\"attachment-image\"\n src=\"{{\n attachmentUpload.url\n ? attachmentUpload.url\n : attachmentUpload.previewUri\n }}\"\n alt=\"{{ attachmentUpload.file.name }}\"\n />\n </div>\n <div\n *ngIf=\"\n attachmentUpload.type === 'file' || attachmentUpload.type === 'video'\n \"\n class=\"str-chat__attachment-preview-file\"\n data-testclass=\"attachment-file-preview\"\n >\n <stream-icon-placeholder\n class=\"str-chat__attachment-preview-file-icon\"\n icon=\"unspecified-filetype\"\n ></stream-icon-placeholder>\n\n <div class=\"str-chat__attachment-preview-file-end\">\n <div class=\"str-chat__attachment-preview-file-name\">\n {{ attachmentUpload.file.name }}\n </div>\n <a\n *ngIf=\"attachmentUpload.state === 'success'\"\n class=\"str-chat__attachment-preview-file-download\"\n data-testclass=\"file-download-link\"\n download\n href=\"{{ attachmentUpload.url }}\"\n (click)=\"attachmentUpload.url ? null : $event.preventDefault()\"\n (keyup.enter)=\"\n attachmentUpload.url ? null : $event.preventDefault()\n \"\n >\n <stream-icon-placeholder icon=\"download\"></stream-icon-placeholder>\n </a>\n <stream-loading-indicator-placeholder\n *ngIf=\"attachmentUpload.state === 'uploading'\"\n data-testclass=\"loading-indicator\"\n ></stream-loading-indicator-placeholder>\n </div>\n <ng-container\n *ngTemplateOutlet=\"\n deleteButton;\n context: { attachmentUpload: attachmentUpload }\n \"\n ></ng-container>\n <ng-container\n *ngTemplateOutlet=\"\n retryButton;\n context: { attachmentUpload: attachmentUpload }\n \"\n ></ng-container>\n </div>\n </ng-container>\n </div>\n</div>\n\n<ng-template #deleteButton let-attachmentUpload=\"attachmentUpload\">\n <div\n class=\"str-chat__attachment-preview-delete\"\n data-testclass=\"file-delete\"\n role=\"button\"\n (click)=\"attachmentDeleted(attachmentUpload)\"\n (keyup.enter)=\"attachmentDeleted(attachmentUpload)\"\n >\n <stream-icon-placeholder icon=\"close\"></stream-icon-placeholder>\n </div>\n</ng-template>\n\n<ng-template #retryButton let-attachmentUpload=\"attachmentUpload\">\n <div\n *ngIf=\"attachmentUpload.state === 'error'\"\n data-testclass=\"upload-retry\"\n class=\"str-chat__attachment-preview-error str-chat__attachment-preview-error-{{\n attachmentUpload.type === 'image' ? 'image' : 'file'\n }}\"\n (click)=\"attachmentUploadRetried(attachmentUpload.file)\"\n (keyup.enter)=\"attachmentUploadRetried(attachmentUpload.file)\"\n >\n <stream-icon-placeholder icon=\"retry\"></stream-icon-placeholder>\n </div>\n</ng-template>\n" }]
5194
+ }], ctorParameters: function () { return []; }, propDecorators: { attachmentUploads$: [{
5182
5195
  type: Input
5183
- }], selectorContainer: [{
5184
- type: ViewChild,
5185
- args: ['selectorContainer']
5196
+ }], retryAttachmentUpload: [{
5197
+ type: Output
5198
+ }], deleteAttachment: [{
5199
+ type: Output
5186
5200
  }] } });
5187
5201
 
5188
5202
  /**
5189
- * 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)](../concepts/message-interactions.mdx).
5203
+ * The `MessageInput` component displays an input where users can type their messages and upload files, and sends the message to the active channel. The component can be used to compose new messages or update existing ones. To send messages, the chat user needs to have the necessary [channel capability](https://getstream.io/chat/docs/javascript/channel_capabilities/?language=javascript).
5190
5204
  */
5191
- class MessageComponent {
5192
- constructor(chatClientService, channelService, customTemplatesService, cdRef, dateParser, messageService, messageActionsService) {
5193
- this.chatClientService = chatClientService;
5205
+ class MessageInputComponent {
5206
+ constructor(channelService, notificationService, attachmentService, configService, textareaType, componentFactoryResolver, cdRef, chatClient, emojiInputService, customTemplatesService, messageActionsService) {
5194
5207
  this.channelService = channelService;
5195
- this.customTemplatesService = customTemplatesService;
5208
+ this.notificationService = notificationService;
5209
+ this.attachmentService = attachmentService;
5210
+ this.configService = configService;
5211
+ this.textareaType = textareaType;
5212
+ this.componentFactoryResolver = componentFactoryResolver;
5196
5213
  this.cdRef = cdRef;
5197
- this.dateParser = dateParser;
5198
- this.messageService = messageService;
5214
+ this.chatClient = chatClient;
5215
+ this.emojiInputService = emojiInputService;
5216
+ this.customTemplatesService = customTemplatesService;
5199
5217
  this.messageActionsService = messageActionsService;
5200
- /**
5201
- * The list of [channel capabilities](https://getstream.io/chat/docs/javascript/channel_capabilities/?language=javascript) that are enabled for the current user, the list of [supported interactions](../concepts/message-interactions.mdx) can be found in our message interaction guide. Unathorized actions won't be displayed on the UI. The [`MessageList`](./MessageListComponent.mdx) component automatically sets this based on [channel capabilities](https://getstream.io/chat/docs/javascript/channel_capabilities/?language=javascript).
5202
- */
5203
- this.enabledMessageActions = [];
5204
5218
  /**
5205
5219
  * Determines if the message is being dispalyed in a channel or in a [thread](https://getstream.io/chat/docs/javascript/threads/?language=javascript).
5206
5220
  */
5207
5221
  this.mode = 'main';
5208
5222
  /**
5209
- * Highlighting is used to add visual emphasize to a message when jumping to the message
5223
+ * Enables or disables auto focus on the textarea element
5210
5224
  */
5211
- this.isHighlighted = false;
5212
- this.isEditedFlagOpened = false;
5213
- this.messageTextParts = [];
5214
- this.shouldDisplayTranslationNotice = false;
5215
- this.displayedMessageTextContent = 'original';
5216
- this.imageAttachmentModalState = 'closed';
5217
- this.shouldDisplayThreadLink = false;
5218
- this.isSentByCurrentUser = false;
5219
- this.readByText = '';
5220
- this.lastReadUser = undefined;
5221
- this.isOnlyReadByMe = false;
5222
- this.isReadByMultipleUsers = false;
5223
- this.isMessageDeliveredAndRead = false;
5224
- this.parsedDate = '';
5225
- this.pasedEditedDate = '';
5226
- this.areOptionsVisible = false;
5227
- this.hasAttachment = false;
5228
- this.hasReactions = false;
5229
- this.replyCountParam = {
5230
- replyCount: undefined,
5231
- };
5232
- this.areMessageOptionsOpen = false;
5233
- this.canDisplayReadStatus = false;
5234
- this.hasTouchSupport = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
5225
+ this.autoFocus = true;
5226
+ /**
5227
+ * By default the input will react to changes in `messageToEdit$` from [`MessageActionsService`](../services/MessageActionsService.mdx) and display the message to be edited (taking into account the current `mode`).
5228
+ *
5229
+ * If you don't need that behavior, you can turn this of with this flag. In that case you should create your own edit message UI.
5230
+ */
5231
+ this.watchForMessageToEdit = true;
5232
+ /**
5233
+ * Use this input to control wether a send button is rendered or not. If you don't render a send button, you can still trigger message send using the `sendMessage$` input.
5234
+ */
5235
+ this.displaySendButton = true;
5236
+ /**
5237
+ * Emits when a message was successfuly sent or updated
5238
+ */
5239
+ this.messageUpdate = new EventEmitter();
5240
+ this.class = 'str-chat__message-input-angular-host';
5241
+ this.textareaValue = '';
5242
+ this.mentionedUsers = [];
5243
+ this.typingStart$ = new Subject();
5244
+ this.isCooldownInProgress = false;
5245
+ this.fileInputId = v4();
5235
5246
  this.subscriptions = [];
5236
5247
  this.isViewInited = false;
5237
- this.urlRegexp = /(?:(?:https?|ftp|file):\/\/|www\.|ftp\.)(?:\([-A-Z0-9+&@#/%=~_|$?!:,.]*\)|[-A-Z0-9+&@#/%=~_|$?!:,.])*(?:\([-A-Z0-9+&@#/%=~_|$?!:,.]*\)|[A-Z0-9+&@#/%=~_|$])/gim;
5238
- this.emojiRegexp = new RegExp(emojiRegex(), 'g');
5239
- this.shouldPreventMessageMenuClose = false;
5240
- this._visibleMessageActionsCount = 0;
5241
- this.displayAs = this.messageService.displayAs;
5242
- }
5243
- get visibleMessageActionsCount() {
5244
- return this._visibleMessageActionsCount;
5245
- }
5246
- set visibleMessageActionsCount(count) {
5247
- this._visibleMessageActionsCount = count;
5248
- if (this.areOptionsVisible && this._visibleMessageActionsCount === 0) {
5249
- this.areOptionsVisible = false;
5250
- }
5251
- }
5252
- ngOnInit() {
5253
- this.subscriptions.push(this.chatClientService.user$.subscribe((u) => {
5254
- if (u?.id !== this.userId) {
5255
- this.userId = u?.id;
5256
- this.setIsSentByCurrentUser();
5257
- this.setLastReadUser();
5258
- if (this.isViewInited) {
5259
- this.cdRef.detectChanges();
5260
- }
5248
+ this.defaultTextareaPlaceholder = 'streamChat.Type your message';
5249
+ this.slowModeTextareaPlaceholder = 'streamChat.Slow Mode ON';
5250
+ this.textareaPlaceholder = this.defaultTextareaPlaceholder;
5251
+ this.subscriptions.push(this.attachmentService.attachmentUploadInProgressCounter$.subscribe((counter) => {
5252
+ if (counter === 0 && this.hideNotification) {
5253
+ this.hideNotification();
5254
+ this.hideNotification = undefined;
5261
5255
  }
5262
5256
  }));
5263
- this.subscriptions.push(this.messageActionsService.customActions$.subscribe(() => {
5264
- if (this.message) {
5265
- const numberOfEnabledActions = this.messageActionsService.getAuthorizedMessageActionsCount(this.message, this.enabledMessageActions);
5266
- if (numberOfEnabledActions !== this.visibleMessageActionsCount) {
5267
- this.visibleMessageActionsCount = numberOfEnabledActions;
5268
- if (this.isViewInited) {
5269
- this.cdRef.detectChanges();
5270
- }
5271
- }
5257
+ this.subscriptions.push(this.channelService.activeChannel$.subscribe((channel) => {
5258
+ if (channel && this.channel && channel.id !== this.channel.id) {
5259
+ this.textareaValue = '';
5260
+ this.attachmentService.resetAttachmentUploads();
5261
+ }
5262
+ const capabilities = channel?.data?.own_capabilities;
5263
+ if (capabilities) {
5264
+ this.isFileUploadAuthorized =
5265
+ capabilities.indexOf('upload-file') !== -1;
5266
+ this.canSendLinks = capabilities.indexOf('send-links') !== -1;
5267
+ this.channel = channel;
5268
+ this.setCanSendMessages();
5269
+ }
5270
+ }));
5271
+ this.subscriptions.push(this.chatClient.appSettings$.subscribe((appSettings) => (this.appSettings = appSettings)));
5272
+ this.subscriptions.push(this.channelService.messageToQuote$.subscribe((m) => {
5273
+ const isThreadReply = m && m.parent_id;
5274
+ if ((this.mode === 'thread' && isThreadReply) ||
5275
+ (this.mode === 'thread' && this.quotedMessage && !m) ||
5276
+ (this.mode === 'main' && !isThreadReply)) {
5277
+ this.quotedMessage = m;
5278
+ }
5279
+ }));
5280
+ this.subscriptions.push(this.messageActionsService.messageToEdit$.subscribe((message) => {
5281
+ this.messageToEdit = message;
5282
+ this.checkIfInEditMode();
5283
+ }));
5284
+ this.attachmentUploads$ = this.attachmentService.attachmentUploads$;
5285
+ this.attachmentUploadInProgressCounter$ =
5286
+ this.attachmentService.attachmentUploadInProgressCounter$;
5287
+ this.isFileUploadEnabled = this.configService.isFileUploadEnabled;
5288
+ this.isMultipleFileUploadEnabled =
5289
+ this.configService.isMultipleFileUploadEnabled;
5290
+ this.areMentionsEnabled = this.configService.areMentionsEnabled;
5291
+ this.mentionScope = this.configService.mentionScope;
5292
+ this.inputMode = this.configService.inputMode;
5293
+ this.subscriptions.push(this.typingStart$.subscribe(() => void this.channelService.typingStarted(this.parentMessageId)));
5294
+ this.subscriptions.push(combineLatest([
5295
+ this.channelService.latestMessageDateByUserByChannels$,
5296
+ this.channelService.activeChannel$,
5297
+ ])
5298
+ .pipe(map(([latestMessages, channel]) => [latestMessages[channel?.cid || ''], channel]))
5299
+ .subscribe(([latestMessageDate, channel]) => {
5300
+ const cooldown = channel?.data?.cooldown &&
5301
+ latestMessageDate &&
5302
+ Math.round(channel?.data?.cooldown -
5303
+ (new Date().getTime() - latestMessageDate.getTime()) / 1000);
5304
+ if (cooldown &&
5305
+ cooldown > 0 &&
5306
+ (channel?.data?.own_capabilities).includes('slow-mode')) {
5307
+ this.startCooldown(cooldown);
5308
+ }
5309
+ else if (this.isCooldownInProgress) {
5310
+ this.stopCooldown();
5272
5311
  }
5273
5312
  }));
5274
5313
  }
5275
- ngOnChanges(changes) {
5276
- if (changes.message) {
5277
- this.shouldDisplayTranslationNotice = false;
5278
- this.displayedMessageTextContent = 'original';
5279
- this.createMessageParts();
5280
- const originalAttachments = this.message?.quoted_message?.attachments;
5281
- this.quotedMessageAttachments =
5282
- originalAttachments && originalAttachments.length
5283
- ? [originalAttachments[0]]
5284
- : [];
5285
- this.setIsSentByCurrentUser();
5286
- this.setLastReadUser();
5287
- this.readByText = this.message?.readBy
5288
- ? listUsers(this.message.readBy)
5289
- : '';
5290
- this.isOnlyReadByMe = !!(this.message &&
5291
- this.message.readBy &&
5292
- this.message.readBy.length === 0);
5293
- this.isReadByMultipleUsers = !!(this.message &&
5294
- this.message.readBy &&
5295
- this.message.readBy.length > 1);
5296
- this.isMessageDeliveredAndRead = !!(this.message &&
5297
- this.message.readBy &&
5298
- this.message.status === 'received' &&
5299
- this.message.readBy.length > 0);
5300
- this.parsedDate =
5301
- (this.message &&
5302
- this.message.created_at &&
5303
- this.dateParser.parseDateTime(this.message.created_at)) ||
5304
- '';
5305
- this.pasedEditedDate =
5306
- (this.message &&
5307
- this.message.message_text_updated_at &&
5308
- this.dateParser.parseDateTime(new Date(this.message.message_text_updated_at))) ||
5309
- '';
5310
- this.hasAttachment =
5311
- !!this.message?.attachments && !!this.message.attachments.length;
5312
- this.hasReactions =
5313
- !!this.message?.reaction_counts &&
5314
- Object.keys(this.message.reaction_counts).length > 0;
5315
- this.replyCountParam = { replyCount: this.message?.reply_count };
5314
+ ngOnInit() {
5315
+ this.subscriptions.push(this.customTemplatesService.emojiPickerTemplate$.subscribe((template) => {
5316
+ this.emojiPickerTemplate = template;
5317
+ this.cdRef.detectChanges();
5318
+ }));
5319
+ this.subscriptions.push(this.customTemplatesService.attachmentPreviewListTemplate$.subscribe((template) => {
5320
+ this.attachmentPreviewListTemplate = template;
5321
+ this.cdRef.detectChanges();
5322
+ }));
5323
+ this.subscriptions.push(this.customTemplatesService.customAttachmentUploadTemplate$.subscribe((template) => {
5324
+ this.customAttachmentUploadTemplate = template;
5325
+ this.cdRef.detectChanges();
5326
+ }));
5327
+ }
5328
+ ngAfterViewInit() {
5329
+ this.isViewInited = true;
5330
+ this.initTextarea();
5331
+ }
5332
+ ngOnChanges(changes) {
5333
+ if (changes.message) {
5334
+ this.messageToUpdateChanged();
5316
5335
  }
5317
- if (changes.enabledMessageActions) {
5318
- this.canReactToMessage =
5319
- this.enabledMessageActions.indexOf('send-reaction') !== -1;
5320
- this.canReceiveReadEvents =
5321
- this.enabledMessageActions.indexOf('read-events') !== -1;
5322
- this.canDisplayReadStatus =
5323
- this.canReceiveReadEvents !== false &&
5324
- this.enabledMessageActions.indexOf('read-events') !== -1;
5336
+ if (changes.isFileUploadEnabled) {
5337
+ this.configService.isFileUploadEnabled = this.isFileUploadEnabled;
5325
5338
  }
5326
- if (changes.message || changes.enabledMessageActions || changes.mode) {
5327
- this.shouldDisplayThreadLink =
5328
- !!this.message?.reply_count && this.mode !== 'thread';
5339
+ if (changes.isMultipleFileUploadEnabled) {
5340
+ this.configService.isMultipleFileUploadEnabled =
5341
+ this.isMultipleFileUploadEnabled;
5329
5342
  }
5330
- if (changes.message || changes.mode || changes.enabledMessageActions) {
5331
- this.areOptionsVisible = this.message
5332
- ? !(!this.message.type ||
5333
- this.message.type === 'error' ||
5334
- this.message.type === 'system' ||
5335
- this.message.type === 'deleted' ||
5336
- this.message.type === 'ephemeral' ||
5337
- this.message.status === 'failed' ||
5338
- this.message.status === 'sending' ||
5339
- (this.mode === 'thread' && !this.message.parent_id) ||
5340
- this.message.deleted_at ||
5341
- this.enabledMessageActions.length === 0)
5342
- : false;
5343
+ if (changes.areMentionsEnabled) {
5344
+ this.configService.areMentionsEnabled = this.areMentionsEnabled;
5343
5345
  }
5344
- if (changes.message || changes.enabledMessageActions) {
5345
- if (this.message) {
5346
- this.visibleMessageActionsCount =
5347
- this.messageActionsService.getAuthorizedMessageActionsCount(this.message, this.enabledMessageActions);
5346
+ if (changes.mentionScope) {
5347
+ this.configService.mentionScope = this.mentionScope;
5348
+ }
5349
+ if (changes.mode) {
5350
+ this.setCanSendMessages();
5351
+ this.checkIfInEditMode();
5352
+ }
5353
+ if (changes.watchForMessageToEdit) {
5354
+ this.checkIfInEditMode();
5355
+ }
5356
+ if (changes.inputMode) {
5357
+ this.configService.inputMode = this.inputMode;
5358
+ }
5359
+ if (changes.sendMessage$) {
5360
+ if (this.sendMessageSubcription) {
5361
+ this.sendMessageSubcription.unsubscribe();
5348
5362
  }
5349
- else {
5350
- this.visibleMessageActionsCount = 0;
5363
+ if (this.sendMessage$) {
5364
+ this.sendMessageSubcription = this.sendMessage$.subscribe(() => void this.messageSent());
5351
5365
  }
5352
5366
  }
5353
5367
  }
5354
- ngAfterViewInit() {
5355
- this.isViewInited = true;
5356
- }
5357
5368
  ngOnDestroy() {
5369
+ if (this.sendMessageSubcription) {
5370
+ this.sendMessageSubcription.unsubscribe();
5371
+ }
5358
5372
  this.subscriptions.forEach((s) => s.unsubscribe());
5359
5373
  }
5360
- mousePushedDown(event) {
5361
- if (!this.hasTouchSupport ||
5362
- event.button !== 0 ||
5363
- !this.areOptionsVisible) {
5374
+ async messageSent() {
5375
+ if (this.isCooldownInProgress) {
5364
5376
  return;
5365
5377
  }
5366
- this.startMessageMenuShowTimer({ fromTouch: false });
5367
- }
5368
- mouseReleased() {
5369
- this.stopMessageMenuShowTimer();
5370
- }
5371
- touchStarted() {
5372
- if (!this.areOptionsVisible) {
5378
+ let attachmentUploadInProgressCounter;
5379
+ this.attachmentService.attachmentUploadInProgressCounter$
5380
+ .pipe(first())
5381
+ .subscribe((counter) => (attachmentUploadInProgressCounter = counter));
5382
+ if (attachmentUploadInProgressCounter > 0) {
5383
+ if (!this.hideNotification) {
5384
+ this.hideNotification =
5385
+ this.notificationService.addPermanentNotification('streamChat.Wait until all attachments have uploaded');
5386
+ }
5373
5387
  return;
5374
5388
  }
5375
- this.startMessageMenuShowTimer({ fromTouch: true });
5376
- }
5377
- touchEnded() {
5378
- this.stopMessageMenuShowTimer();
5379
- }
5380
- messageBubbleClicked(event) {
5381
- if (!this.hasTouchSupport) {
5389
+ const attachments = this.attachmentService.mapToAttachments();
5390
+ let text = this.textareaValue;
5391
+ text = text.replace(/^\n+/g, ''); // leading empty lines
5392
+ text = text.replace(/\n+$/g, ''); // ending empty lines
5393
+ const textContainsOnlySpaceChars = !text.replace(/ /g, ''); //spcae
5394
+ if ((!text || textContainsOnlySpaceChars) &&
5395
+ (!attachments || attachments.length === 0)) {
5382
5396
  return;
5383
5397
  }
5384
- if (this.shouldPreventMessageMenuClose) {
5385
- event.stopPropagation();
5386
- this.shouldPreventMessageMenuClose = false;
5387
- }
5388
- else if (this.areMessageOptionsOpen) {
5389
- this.messageMenuTrigger?.hide();
5398
+ if (textContainsOnlySpaceChars) {
5399
+ text = '';
5390
5400
  }
5391
- }
5392
- messageOptionsButtonClicked() {
5393
- if (!this.message) {
5401
+ if (this.containsLinks && !this.canSendLinks) {
5402
+ this.notificationService.addTemporaryNotification('streamChat.Sending links is not allowed in this conversation');
5394
5403
  return;
5395
5404
  }
5396
- if (this.messageActionsService.customActionClickHandler) {
5397
- this.messageActionsService.customActionClickHandler({
5398
- message: this.message,
5399
- enabledActions: this.enabledMessageActions,
5400
- customActions: this.messageActionsService.customActions$.getValue(),
5401
- isMine: this.isSentByCurrentUser,
5402
- messageTextHtmlElement: this.messageTextElement?.nativeElement,
5403
- });
5404
- }
5405
- else {
5406
- this.areMessageOptionsOpen = !this.areMessageOptionsOpen;
5405
+ if (!this.isUpdate) {
5406
+ this.textareaValue = '';
5407
5407
  }
5408
- }
5409
- messageActionsBoxClicked(floatingContent) {
5410
- floatingContent.hide();
5411
- }
5412
- getAttachmentListContext() {
5413
- return {
5414
- messageId: this.message?.id || '',
5415
- attachments: this.message?.attachments || [],
5416
- parentMessageId: this.message?.parent_id,
5417
- imageModalStateChangeHandler: (state) => (this.imageAttachmentModalState = state),
5418
- };
5419
- }
5420
- getMessageContext() {
5421
- return {
5422
- message: this.message,
5423
- enabledMessageActions: this.enabledMessageActions,
5424
- isHighlighted: this.isHighlighted,
5425
- isLastSentMessage: this.isLastSentMessage,
5426
- mode: this.mode,
5427
- customActions: this.messageActionsService.customActions$.getValue(),
5428
- parsedDate: this.parsedDate,
5429
- };
5430
- }
5431
- getQuotedMessageAttachmentListContext() {
5432
- return {
5433
- messageId: this.message?.quoted_message?.id || '',
5434
- attachments: this.quotedMessageAttachments,
5435
- parentMessageId: this?.message?.quoted_message?.parent_id,
5436
- };
5437
- }
5438
- getMessageReactionsContext() {
5439
- return {
5440
- messageReactionCounts: this.message?.reaction_counts || {},
5441
- latestReactions: this.message?.latest_reactions || [],
5442
- messageId: this.message?.id,
5443
- ownReactions: this.message?.own_reactions || [],
5444
- };
5445
- }
5446
- messageClicked() {
5447
- if (this.message?.status === 'failed' &&
5448
- this.message?.errorStatusCode !== 403) {
5449
- this.resendMessage();
5408
+ try {
5409
+ const message = await (this.isUpdate
5410
+ ? this.channelService.updateMessage({
5411
+ ...this.message,
5412
+ text: text,
5413
+ attachments: attachments,
5414
+ })
5415
+ : this.channelService.sendMessage(text, attachments, this.mentionedUsers, this.parentMessageId, this.quotedMessage?.id));
5416
+ this.messageUpdate.emit({ message });
5417
+ if (this.isUpdate) {
5418
+ this.deselectMessageToEdit();
5419
+ }
5420
+ else {
5421
+ this.attachmentService.resetAttachmentUploads();
5422
+ }
5450
5423
  }
5451
- else if (this.message?.type === 'error' &&
5452
- this.message?.moderation_details) {
5453
- this.openMessageBouncePrompt();
5424
+ catch (error) {
5425
+ if (this.isUpdate) {
5426
+ this.notificationService.addTemporaryNotification('streamChat.Edit message request failed');
5427
+ }
5454
5428
  }
5455
- else {
5456
- this.isEditedFlagOpened = !this.isEditedFlagOpened;
5429
+ void this.channelService.typingStopped(this.parentMessageId);
5430
+ if (this.quotedMessage) {
5431
+ this.deselectMessageToQuote();
5457
5432
  }
5458
5433
  }
5459
- resendMessage() {
5460
- void this.channelService.resendMessage(this.message);
5434
+ get containsLinks() {
5435
+ return /(?:(?:https?|ftp):\/\/)?[\w/\-?=%.]+\.[\w/\-&?=%.]+/.test(this.textareaValue);
5461
5436
  }
5462
- setAsActiveParentMessage() {
5463
- void this.channelService.setAsActiveParentMessage(this.message);
5437
+ get quotedMessageAttachments() {
5438
+ const originalAttachments = this.quotedMessage?.attachments;
5439
+ return originalAttachments && originalAttachments.length
5440
+ ? [originalAttachments[0]]
5441
+ : [];
5464
5442
  }
5465
- getMentionContext(messagePart) {
5466
- return {
5467
- content: messagePart.content,
5468
- user: messagePart.user,
5469
- };
5443
+ get disabledTextareaText() {
5444
+ if (!this.canSendMessages) {
5445
+ return this.mode === 'thread'
5446
+ ? "streamChat.You can't send thread replies in this channel"
5447
+ : "streamChat.You can't send messages in this channel";
5448
+ }
5449
+ return '';
5470
5450
  }
5471
- getMessageActionsBoxContext() {
5472
- return {
5473
- isMine: this.isSentByCurrentUser,
5474
- enabledActions: this.enabledMessageActions,
5475
- message: this.message,
5476
- messageTextHtmlElement: this.messageTextElement?.nativeElement,
5477
- };
5451
+ async filesSelected(fileList) {
5452
+ if (!(await this.areAttachemntsValid(fileList))) {
5453
+ return;
5454
+ }
5455
+ await this.attachmentService.filesSelected(fileList);
5456
+ this.clearFileInput();
5478
5457
  }
5479
- getDeliveredStatusContext() {
5480
- return {
5481
- message: this.message,
5482
- };
5458
+ deselectMessageToQuote() {
5459
+ this.channelService.selectMessageToQuote(undefined);
5483
5460
  }
5484
- getSendingStatusContext() {
5461
+ deselectMessageToEdit() {
5462
+ this.messageActionsService.messageToEdit$.next(undefined);
5463
+ }
5464
+ getEmojiPickerContext() {
5485
5465
  return {
5486
- message: this.message,
5466
+ emojiInput$: this.emojiInputService.emojiInput$,
5487
5467
  };
5488
5468
  }
5489
- getReadStatusContext() {
5469
+ getAttachmentPreviewListContext() {
5490
5470
  return {
5491
- message: this.message,
5492
- readByText: this.readByText,
5471
+ attachmentUploads$: this.attachmentService.attachmentUploads$,
5472
+ deleteUploadHandler: this.deleteUpload.bind(this),
5473
+ retryUploadHandler: this.retryUpload.bind(this),
5493
5474
  };
5494
5475
  }
5495
- getMessageMetadataContext() {
5476
+ getAttachmentUploadContext() {
5496
5477
  return {
5497
- message: this.message,
5478
+ isMultipleFileUploadEnabled: this.isMultipleFileUploadEnabled,
5479
+ attachmentService: this.attachmentService,
5498
5480
  };
5499
5481
  }
5500
- jumpToMessage(messageId, parentMessageId) {
5501
- void this.channelService.jumpToMessage(messageId, parentMessageId);
5482
+ get isUpdate() {
5483
+ return !!this.message;
5502
5484
  }
5503
- displayTranslatedMessage() {
5504
- this.createMessageParts(true);
5485
+ deleteUpload(upload) {
5486
+ if (this.isUpdate) {
5487
+ // Delay delete to avoid modal detecting this click as outside click
5488
+ setTimeout(() => {
5489
+ void this.attachmentService.deleteAttachment(upload);
5490
+ });
5491
+ }
5492
+ else {
5493
+ void this.attachmentService.deleteAttachment(upload);
5494
+ }
5505
5495
  }
5506
- displayOriginalMessage() {
5507
- this.createMessageParts(false);
5496
+ retryUpload(file) {
5497
+ void this.attachmentService.retryAttachmentUpload(file);
5508
5498
  }
5509
- openMessageBouncePrompt() {
5510
- this.channelService.bouncedMessage$.next(this.message);
5499
+ clearFileInput() {
5500
+ this.fileInput.nativeElement.value = '';
5511
5501
  }
5512
- createMessageParts(shouldTranslate = true) {
5513
- this.messageTextParts = undefined;
5514
- this.messageText = undefined;
5515
- let content = this.getMessageContent(shouldTranslate);
5516
- if ((!this.message.mentioned_users ||
5517
- this.message.mentioned_users.length === 0) &&
5518
- !content?.match(this.emojiRegexp) &&
5519
- !content?.match(this.urlRegexp)) {
5520
- this.messageTextParts = undefined;
5521
- this.messageText = content;
5522
- return;
5502
+ initTextarea() {
5503
+ // cleanup previously built textarea
5504
+ if (!this.canSendMessages) {
5505
+ this.textareaRef = undefined;
5523
5506
  }
5524
- if (!content) {
5507
+ if (!this.canSendMessages || this.textareaRef || !this.textareaAnchor) {
5525
5508
  return;
5526
5509
  }
5527
- if (!this.message.mentioned_users ||
5528
- this.message.mentioned_users.length === 0) {
5529
- content = this.fixEmojiDisplay(content);
5530
- content = this.wrapLinksWithAnchorTag(content);
5531
- this.messageTextParts = [{ content, type: 'text' }];
5510
+ const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.textareaType);
5511
+ this.textareaRef =
5512
+ this.textareaAnchor.viewContainerRef.createComponent(componentFactory);
5513
+ this.cdRef.detectChanges();
5514
+ }
5515
+ async areAttachemntsValid(fileList) {
5516
+ if (!fileList) {
5517
+ return true;
5532
5518
  }
5533
- else {
5534
- this.messageTextParts = [];
5535
- let text = content;
5536
- this.message.mentioned_users.forEach((user) => {
5537
- const mention = `@${user.name || user.id}`;
5538
- const precedingText = text.substring(0, text.indexOf(mention));
5539
- let formattedPrecedingText = this.fixEmojiDisplay(precedingText);
5540
- formattedPrecedingText = this.wrapLinksWithAnchorTag(formattedPrecedingText);
5541
- this.messageTextParts.push({
5542
- content: formattedPrecedingText,
5543
- type: 'text',
5544
- });
5545
- this.messageTextParts.push({
5546
- content: mention,
5547
- type: 'mention',
5548
- user,
5549
- });
5550
- text = text.replace(precedingText + mention, '');
5551
- });
5552
- if (text) {
5553
- text = this.fixEmojiDisplay(text);
5554
- text = this.wrapLinksWithAnchorTag(text);
5555
- this.messageTextParts.push({ content: text, type: 'text' });
5556
- }
5519
+ if (!this.appSettings) {
5520
+ await this.chatClient.getAppSettings();
5557
5521
  }
5558
- }
5559
- getMessageContent(shouldTranslate) {
5560
- const originalContent = this.message?.text;
5561
- if (shouldTranslate) {
5562
- const translation = this.message?.translation;
5563
- if (translation) {
5564
- this.shouldDisplayTranslationNotice = true;
5565
- this.displayedMessageTextContent = 'translation';
5522
+ let isValid = true;
5523
+ Array.from(fileList).forEach((f) => {
5524
+ let hasBlockedExtension;
5525
+ let hasBlockedMimeType;
5526
+ let hasNotAllowedExtension;
5527
+ let hasNotAllowedMimeType;
5528
+ if (isImageFile(f)) {
5529
+ hasBlockedExtension =
5530
+ !!this.appSettings?.image_upload_config?.blocked_file_extensions?.find((ext) => f.name.endsWith(ext));
5531
+ hasBlockedMimeType =
5532
+ !!this.appSettings?.image_upload_config?.blocked_mime_types?.find((type) => f.type === type);
5533
+ hasNotAllowedExtension =
5534
+ !!this.appSettings?.image_upload_config?.allowed_file_extensions
5535
+ ?.length &&
5536
+ !this.appSettings?.image_upload_config?.allowed_file_extensions?.find((ext) => f.name.endsWith(ext));
5537
+ hasNotAllowedMimeType =
5538
+ !!this.appSettings?.image_upload_config?.allowed_mime_types?.length &&
5539
+ !this.appSettings?.image_upload_config?.allowed_mime_types?.find((type) => f.type === type);
5566
5540
  }
5567
- return translation || originalContent;
5541
+ else {
5542
+ hasBlockedExtension =
5543
+ !!this.appSettings?.file_upload_config?.blocked_file_extensions?.find((ext) => f.name.endsWith(ext));
5544
+ hasBlockedMimeType =
5545
+ !!this.appSettings?.file_upload_config?.blocked_mime_types?.find((type) => f.type === type);
5546
+ hasNotAllowedExtension =
5547
+ !!this.appSettings?.file_upload_config?.allowed_file_extensions
5548
+ ?.length &&
5549
+ !this.appSettings?.file_upload_config?.allowed_file_extensions?.find((ext) => f.name.endsWith(ext));
5550
+ hasNotAllowedMimeType =
5551
+ !!this.appSettings?.file_upload_config?.allowed_mime_types?.length &&
5552
+ !this.appSettings?.file_upload_config?.allowed_mime_types?.find((type) => f.type === type);
5553
+ }
5554
+ if (hasBlockedExtension ||
5555
+ hasBlockedMimeType ||
5556
+ hasNotAllowedExtension ||
5557
+ hasNotAllowedMimeType) {
5558
+ this.notificationService.addTemporaryNotification('streamChat.Error uploading file, extension not supported', undefined, undefined, { name: f.name, ext: f.type });
5559
+ isValid = false;
5560
+ }
5561
+ });
5562
+ return isValid;
5563
+ }
5564
+ setCanSendMessages() {
5565
+ const capabilities = this.channel?.data?.own_capabilities;
5566
+ if (!capabilities) {
5567
+ this.canSendMessages = false;
5568
5568
  }
5569
5569
  else {
5570
- this.displayedMessageTextContent = 'original';
5571
- return originalContent;
5570
+ this.canSendMessages =
5571
+ capabilities.indexOf(this.mode === 'main' ? 'send-message' : 'send-reply') !== -1 || this.isUpdate;
5572
+ }
5573
+ if (this.isViewInited) {
5574
+ this.cdRef.detectChanges();
5575
+ this.initTextarea();
5572
5576
  }
5573
5577
  }
5574
- fixEmojiDisplay(content) {
5575
- // Wrap emojis in span to display emojis correctly in Chrome https://bugs.chromium.org/p/chromium/issues/detail?id=596223
5576
- // Based on this: https://stackoverflow.com/questions/4565112/javascript-how-to-find-out-if-the-user-browser-is-chrome
5577
- /* eslint-disable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any */
5578
- const isChrome = !!window.chrome && typeof window.opr === 'undefined';
5579
- /* eslint-enable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any */
5580
- content = content.replace(this.emojiRegexp, (match) => `<span ${isChrome ? 'class="str-chat__emoji-display-fix"' : ''}>${match}</span>`);
5581
- return content;
5582
- }
5583
- wrapLinksWithAnchorTag(content) {
5584
- if (this.displayAs === 'html') {
5585
- return content;
5578
+ get parentMessageId() {
5579
+ let parentMessageId = undefined;
5580
+ if (this.mode === 'thread') {
5581
+ this.channelService.activeParentMessageId$
5582
+ .pipe(first())
5583
+ .subscribe((id) => (parentMessageId = id));
5586
5584
  }
5587
- content = content.replace(this.urlRegexp, (match) => this.messageService.customLinkRenderer
5588
- ? this.messageService.customLinkRenderer(match)
5589
- : `<a href="${match}" target="_blank" rel="nofollow">${match}</a>`);
5590
- return content;
5585
+ return parentMessageId;
5591
5586
  }
5592
- setIsSentByCurrentUser() {
5593
- this.isSentByCurrentUser = this.message?.user?.id === this.userId;
5587
+ startCooldown(cooldown) {
5588
+ this.textareaPlaceholder = this.slowModeTextareaPlaceholder;
5589
+ this.isCooldownInProgress = true;
5590
+ this.cooldown$ = timer(0, 1000).pipe(take(cooldown + 1), map((v) => cooldown - v), tap((v) => {
5591
+ if (v === 0) {
5592
+ this.stopCooldown();
5593
+ }
5594
+ }));
5594
5595
  }
5595
- setLastReadUser() {
5596
- this.lastReadUser = this.message?.readBy?.filter((u) => u.id !== this.userId)[0];
5596
+ stopCooldown() {
5597
+ this.cooldown$ = undefined;
5598
+ this.isCooldownInProgress = false;
5599
+ this.textareaPlaceholder = this.defaultTextareaPlaceholder;
5597
5600
  }
5598
- startMessageMenuShowTimer(options) {
5599
- this.stopMessageMenuShowTimer();
5600
- this.showMessageMenuTimeout = setTimeout(() => {
5601
- if (!this.message) {
5602
- return;
5603
- }
5604
- if (this.messageActionsService.customActionClickHandler) {
5605
- this.messageActionsService.customActionClickHandler({
5606
- message: this.message,
5607
- enabledActions: this.enabledMessageActions,
5608
- customActions: this.messageActionsService.customActions$.getValue(),
5609
- isMine: this.isSentByCurrentUser,
5610
- messageTextHtmlElement: this.messageTextElement?.nativeElement,
5611
- });
5612
- return;
5613
- }
5614
- else {
5615
- this.shouldPreventMessageMenuClose = !options.fromTouch;
5616
- this.messageMenuTrigger?.show();
5617
- }
5618
- this.showMessageMenuTimeout = undefined;
5619
- }, 400);
5601
+ checkIfInEditMode() {
5602
+ if (!this.watchForMessageToEdit) {
5603
+ return;
5604
+ }
5605
+ if (!this.messageToEdit && this.message) {
5606
+ this.message = undefined;
5607
+ this.messageToUpdateChanged();
5608
+ }
5609
+ if (this.messageToEdit &&
5610
+ ((this.mode === 'main' && !this.messageToEdit.parent_id) ||
5611
+ (this.mode === 'thread' && this.messageToEdit.parent_id))) {
5612
+ this.message = this.messageToEdit;
5613
+ this.messageToUpdateChanged();
5614
+ }
5620
5615
  }
5621
- stopMessageMenuShowTimer() {
5622
- if (this.showMessageMenuTimeout) {
5623
- clearTimeout(this.showMessageMenuTimeout);
5624
- this.showMessageMenuTimeout = undefined;
5616
+ messageToUpdateChanged() {
5617
+ this.attachmentService.resetAttachmentUploads();
5618
+ this.setCanSendMessages();
5619
+ if (this.isUpdate) {
5620
+ this.attachmentService.createFromAttachments(this.message.attachments || []);
5621
+ this.textareaValue = this.message.text || '';
5622
+ }
5623
+ else {
5624
+ this.textareaValue = '';
5625
5625
  }
5626
5626
  }
5627
5627
  }
5628
- MessageComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: MessageComponent, deps: [{ token: ChatClientService }, { token: ChannelService }, { token: CustomTemplatesService }, { token: i0.ChangeDetectorRef }, { token: DateParserService }, { token: MessageService }, { token: MessageActionsService }], target: i0.ɵɵFactoryTarget.Component });
5629
- MessageComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.0.4", type: MessageComponent, selector: "stream-message", inputs: { message: "message", enabledMessageActions: "enabledMessageActions", isLastSentMessage: "isLastSentMessage", mode: "mode", isHighlighted: "isHighlighted" }, viewQueries: [{ propertyName: "messageMenuTrigger", first: true, predicate: ["messageMenuTrigger"], descendants: true }, { propertyName: "messageMenuFloat", first: true, predicate: ["messageMenuFloat"], descendants: true }, { propertyName: "messageTextElement", first: true, predicate: ["messageTextElement"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div\n #container\n data-testid=\"message-container\"\n class=\"str-chat__message-simple str-chat__message str-chat__message--{{\n message?.type\n }} str-chat__message--{{ message?.status }} {{\n message?.text ? 'str-chat__message--has-text' : 'has-no-text'\n }} str-chat__message-menu-{{ areMessageOptionsOpen ? 'opened' : 'closed' }}\"\n [class.str-chat__message--me]=\"isSentByCurrentUser\"\n [class.str-chat__message--other]=\"!isSentByCurrentUser\"\n [class.str-chat__message-simple--me]=\"isSentByCurrentUser\"\n [class.str-chat__message--has-attachment]=\"hasAttachment\"\n [class.str-chat__message--with-reactions]=\"hasReactions\"\n [class.str-chat__message--highlighted]=\"isHighlighted\"\n [class.str-chat__message-with-thread-link]=\"shouldDisplayThreadLink\"\n [class.str-chat__message-send-can-be-retried]=\"\n (message?.status === 'failed' && message?.errorStatusCode !== 403) ||\n (message?.type === 'error' && message?.moderation_details)\n \"\n [class.str-chat__message-with-touch-support]=\"hasTouchSupport\"\n [class.str-chat__message-without-touch-support]=\"!hasTouchSupport\"\n>\n <ng-container *ngIf=\"!message?.deleted_at; else deletedMessage\">\n <ng-container *ngIf=\"message?.type !== 'system'; else systemMessage\">\n <stream-avatar-placeholder\n data-testid=\"avatar\"\n class=\"str-chat-angular__avatar-host str-chat__message-sender-avatar\"\n type=\"user\"\n location=\"message-sender\"\n [imageUrl]=\"message?.user?.image\"\n [name]=\"message?.user?.name || message?.user?.id\"\n [user]=\"message?.user || undefined\"\n ></stream-avatar-placeholder>\n <div class=\"str-chat__message-inner\">\n <div\n *ngIf=\"!hasTouchSupport && areOptionsVisible\"\n class=\"str-chat__message-simple__actions str-chat__message-options\"\n data-testid=\"message-options\"\n [class.str-chat__message-actions-open]=\"areMessageOptionsOpen\"\n >\n <div\n #messageActionsToggle\n data-testid=\"message-actions-container\"\n class=\"\n str-chat__message-actions-container\n str-chat__message-simple__actions__action\n str-chat__message-simple__actions__action--options\n \"\n [floatUiLoose]=\"messageMenuFloat\"\n looseTrigger=\"click\"\n [hideOnScroll]=\"false\"\n [hideOnClickOutside]=\"true\"\n [hideOnMouseLeave]=\"false\"\n [disableAnimation]=\"false\"\n [preventOverflow]=\"true\"\n [positionFixed]=\"true\"\n (onSHown)=\"areMessageOptionsOpen = true\"\n (onHidden)=\"areMessageOptionsOpen = false\"\n >\n <div\n *ngIf=\"visibleMessageActionsCount > 0\"\n class=\"str-chat__message-actions-box-button\"\n data-testid=\"message-options-button\"\n (click)=\"messageOptionsButtonClicked()\"\n (keyup.enter)=\"messageOptionsButtonClicked()\"\n >\n <stream-icon-placeholder\n icon=\"action\"\n class=\"str-chat__message-action-icon\"\n ></stream-icon-placeholder>\n </div>\n </div>\n </div>\n <div class=\"str-chat__message-reactions-host\">\n <ng-template\n #defaultMessageReactions\n let-messageReactionCounts=\"messageReactionCounts\"\n let-latestReactions=\"latestReactions\"\n let-messageId=\"messageId\"\n let-ownReactions=\"ownReactions\"\n >\n <stream-message-reactions\n [messageReactionCounts]=\"messageReactionCounts\"\n [latestReactions]=\"latestReactions\"\n [messageId]=\"messageId\"\n [ownReactions]=\"ownReactions\"\n ></stream-message-reactions>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.messageReactionsTemplate$ | async) ||\n defaultMessageReactions;\n context: getMessageReactionsContext()\n \"\n ></ng-container>\n </div>\n <float-ui-content #messageMenuFloat>\n <ng-template\n #defaultMessageActionsBox\n let-isMine=\"isMine\"\n let-messageInput=\"message\"\n let-enabledActions=\"enabledActions\"\n let-messageTextHtmlElement=\"messageTextHtmlElement\"\n >\n <stream-message-actions-box\n [isMine]=\"isMine\"\n [message]=\"messageInput\"\n [enabledActions]=\"enabledActions\"\n [messageTextHtmlElement]=\"messageTextHtmlElement\"\n (click)=\"messageActionsBoxClicked(messageMenuFloat)\"\n ></stream-message-actions-box>\n </ng-template>\n <ng-container>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.messageActionsBoxTemplate$ | async) ||\n defaultMessageActionsBox;\n context: getMessageActionsBoxContext()\n \"\n >\n </ng-container>\n </ng-container>\n </float-ui-content>\n <div\n class=\"str-chat__message-bubble str-chat-angular__message-bubble\"\n [class.str-chat-angular__message-bubble--attachment-modal-open]=\"\n imageAttachmentModalState === 'opened'\n \"\n data-testid=\"message-bubble\"\n [floatUiLoose]=\"messageMenuFloat\"\n #messageMenuTrigger=\"floatUiLoose\"\n looseTrigger=\"none\"\n [hideOnScroll]=\"false\"\n [hideOnClickOutside]=\"true\"\n [hideOnMouseLeave]=\"false\"\n [disableAnimation]=\"true\"\n [preventOverflow]=\"true\"\n (onShown)=\"areMessageOptionsOpen = true\"\n (onHidden)=\"areMessageOptionsOpen = false\"\n [positionFixed]=\"true\"\n (mousedown)=\"mousePushedDown($event)\"\n (mouseup)=\"mouseReleased()\"\n (touchstart)=\"touchStarted()\"\n (touchend)=\"touchEnded()\"\n (click)=\"messageBubbleClicked($event)\"\n (keyup.enter)=\"messageBubbleClicked($event)\"\n >\n <ng-container *ngIf=\"hasAttachment && !message?.quoted_message\">\n <div class=\"str-chat__attachments-container\">\n <ng-container\n *ngTemplateOutlet=\"attachmentsTemplate\"\n ></ng-container>\n </div>\n </ng-container>\n <div\n *ngIf=\"message?.text || (message?.quoted_message && hasAttachment)\"\n class=\"str-chat__message-text\"\n tabindex=\"0\"\n [class.str-chat__message-text--pointer-cursor]=\"\n (message?.status === 'failed' &&\n message?.errorStatusCode !== 403) ||\n (this.message?.type === 'error' &&\n this.message?.moderation_details) ||\n message?.message_text_updated_at\n \"\n (click)=\"messageClicked()\"\n (keyup.enter)=\"messageClicked()\"\n >\n <div\n data-testid=\"inner-message\"\n class=\"\n str-chat__message-text-inner str-chat__message-simple-text-inner\n \"\n [class.str-chat__message-light-text-inner--has-attachment]=\"\n hasAttachment\n \"\n >\n <ng-container *ngTemplateOutlet=\"quotedMessage\"></ng-container>\n <ng-container *ngIf=\"hasAttachment && message?.quoted_message\">\n <ng-container\n *ngTemplateOutlet=\"attachmentsTemplate\"\n ></ng-container>\n </ng-container>\n <div\n *ngIf=\"message?.type === 'error'\"\n data-testid=\"client-error-message\"\n class=\"\n str-chat__simple-message--error-message\n str-chat__message--error-message\n \"\n >\n <ng-container *ngIf=\"!message?.moderation_details\">{{\n \"streamChat.Error \u00B7 Unsent\" | translate\n }}</ng-container>\n </div>\n <div\n *ngIf=\"message?.status === 'failed'\"\n data-testid=\"error-message\"\n class=\"\n str-chat__simple-message--error-message\n str-chat__message--error-message\n \"\n >\n {{\n (message?.errorStatusCode === 403\n ? \"streamChat.Message Failed \u00B7 Unauthorized\"\n : \"streamChat.Message Failed \u00B7 Click to try again\"\n ) | translate\n }}\n </div>\n <div #messageTextElement data-testid=\"text\">\n <p>\n <ng-container *ngIf=\"messageTextParts; else defaultContent\">\n <!-- eslint-disable-next-line @angular-eslint/template/use-track-by-function -->\n <ng-container *ngFor=\"let part of messageTextParts\">\n <span\n *ngIf=\"part.type === 'text'; else mention\"\n [innerHTML]=\"part.content\"\n ></span>\n <ng-template #mention>\n <ng-template #defaultMention let-content=\"content\">\n <span class=\"str-chat__message-mention\">{{\n content\n }}</span>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.mentionTemplate$ | async) ||\n defaultMention;\n context: getMentionContext(part)\n \"\n ></ng-container>\n </ng-template>\n </ng-container>\n </ng-container>\n <ng-template #defaultContent>\n <ng-container *ngIf=\"displayAs === 'text'; else asHTML\">\n {{ messageText || \"\" }}\n </ng-container>\n <ng-template #asHTML\n ><span\n data-testid=\"html-content\"\n [innerHTML]=\"messageText\"\n ></span\n ></ng-template>\n </ng-template>\n </p>\n </div>\n </div>\n </div>\n <stream-icon-placeholder\n class=\"str-chat__message-error-icon\"\n icon=\"error\"\n ></stream-icon-placeholder>\n </div>\n </div>\n <ng-container\n *ngTemplateOutlet=\"replyCountButton; context: { message: message }\"\n ></ng-container>\n\n <ng-container *ngTemplateOutlet=\"messageDateAndSender\"></ng-container>\n </ng-container>\n </ng-container>\n</div>\n\n<ng-template #deletedMessage>\n <div data-testid=\"message-deleted-component\">\n <div class=\"str-chat__message--deleted-inner\" translate>\n streamChat.This message was deleted...\n </div>\n </div>\n</ng-template>\n\n<ng-template #systemMessage>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.systemMessageTemplate$ | async) ||\n defaultSystemMessage;\n context: getMessageContext()\n \"\n ></ng-container>\n <ng-template #defaultSystemMessage let-messageInput=\"message\">\n <div data-testid=\"system-message\" class=\"str-chat__message--system\">\n <div class=\"str-chat__message--system__text\">\n <div class=\"str-chat__message--system__line\"></div>\n <p>{{ messageInput?.text }}</p>\n <div class=\"str-chat__message--system__line\"></div>\n </div>\n <div class=\"str-chat__message--system__date\">\n {{ parsedDate }}\n </div>\n </div>\n </ng-template>\n</ng-template>\n\n<ng-template #quotedMessage>\n <div\n *ngIf=\"message?.quoted_message\"\n class=\"quoted-message str-chat__quoted-message-preview\"\n data-testid=\"quoted-message-container\"\n [class.mine]=\"isSentByCurrentUser\"\n (click)=\"\n jumpToMessage(\n (message?.quoted_message)!.id,\n message?.quoted_message?.parent_id\n )\n \"\n (keyup.enter)=\"\n jumpToMessage(\n (message?.quoted_message)!.id,\n message?.quoted_message?.parent_id\n )\n \"\n >\n <stream-avatar-placeholder\n data-testid=\"qouted-message-avatar\"\n class=\"str-chat-angular__avatar-host str-chat__message-sender-avatar\"\n type=\"user\"\n location=\"quoted-message-sender\"\n [imageUrl]=\"message?.quoted_message?.user?.image\"\n [name]=\"\n message?.quoted_message?.user?.name || message?.quoted_message?.user?.id\n \"\n [user]=\"message?.quoted_message?.user || undefined\"\n ></stream-avatar-placeholder>\n <div class=\"quoted-message-inner str-chat__quoted-message-bubble\">\n <ng-container\n *ngIf=\"\n message?.quoted_message?.attachments &&\n message?.quoted_message?.attachments?.length\n \"\n >\n <ng-template\n #defaultAttachments\n let-messageId=\"messageId\"\n let-attachments=\"attachments\"\n let-parentMessageId=\"parentMessageId\"\n let-imageModalStateChangeHandler=\"imageModalStateChangeHandler\"\n >\n <stream-attachment-list\n [messageId]=\"messageId\"\n [attachments]=\"attachments\"\n [parentMessageId]=\"parentMessageId\"\n (imageModalStateChange)=\"imageModalStateChangeHandler($event)\"\n ></stream-attachment-list>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.attachmentListTemplate$ | async) ||\n defaultAttachments;\n context: getQuotedMessageAttachmentListContext()\n \"\n ></ng-container>\n </ng-container>\n <div\n data-testid=\"quoted-message-text\"\n [innerHTML]=\"\n message?.quoted_message?.translation ||\n message?.quoted_message?.html ||\n message?.quoted_message?.text\n \"\n ></div>\n </div>\n </div>\n</ng-template>\n\n<ng-template #messageDateAndSender>\n <ng-container>\n <div\n *ngIf=\"shouldDisplayTranslationNotice\"\n class=\"str-chat__translation-notice\"\n data-testid=\"translation-notice\"\n >\n <button\n *ngIf=\"displayedMessageTextContent === 'translation'\"\n data-testid=\"see-original\"\n translate\n (click)=\"displayOriginalMessage()\"\n (keyup.enter)=\"displayOriginalMessage()\"\n >\n streamChat.See original (automatically translated)\n </button>\n <button\n *ngIf=\"displayedMessageTextContent === 'original'\"\n data-testid=\"see-translation\"\n translate\n (click)=\"displayTranslatedMessage()\"\n (keyup.enter)=\"displayTranslatedMessage()\"\n >\n streamChat.See translation\n </button>\n </div>\n <ng-container\n *ngIf=\"customTemplatesService.customMessageMetadataTemplate$ | async\"\n >\n <div class=\"str-chat__custom-message-metadata\">\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.customMessageMetadataTemplate$ | async)!;\n context: getMessageMetadataContext()\n \"\n ></ng-container>\n </div>\n </ng-container>\n <div\n class=\"\n str-chat__message-data\n str-chat__message-simple-data\n str-chat__message-metadata\n \"\n >\n <ng-container *ngTemplateOutlet=\"messageStatus\"></ng-container>\n\n <span\n *ngIf=\"!isSentByCurrentUser\"\n data-testid=\"sender\"\n class=\"str-chat__message-simple-name str-chat__message-sender-name\"\n >\n {{ message?.user?.name ? message?.user?.name : message?.user?.id }}\n </span>\n <span\n data-testid=\"date\"\n class=\"str-chat__message-simple-timestamp str-chat__message-simple-time\"\n >\n {{ parsedDate }}\n </span>\n <ng-container *ngIf=\"message?.message_text_updated_at\">\n <span\n data-testid=\"edited-flag\"\n class=\"str-chat__mesage-simple-edited\"\n translate\n >streamChat.Edited</span\n >\n <div\n data-testid=\"edited-timestamp\"\n class=\"str-chat__message-edited-timestamp\"\n [ngClass]=\"{\n 'str-chat__message-edited-timestamp--open': isEditedFlagOpened,\n 'str-chat__message-edited-timestamp--collapsed': !isEditedFlagOpened\n }\"\n >\n <span translate>streamChat.Edited</span>\n <time\n dateTime=\"{{ message?.message_text_updated_at }}\"\n title=\"{{ message?.message_text_updated_at }}\"\n >\n {{ pasedEditedDate }}\n </time>\n </div>\n </ng-container>\n </div>\n </ng-container>\n</ng-template>\n\n<ng-template #messageStatus>\n <ng-container\n *ngIf=\"\n isSentByCurrentUser &&\n ((isLastSentMessage && message?.status === 'received') ||\n message?.status === 'sending')\n \"\n >\n <ng-container *ngIf=\"message?.status === 'sending'; else sentStatus\">\n <ng-container *ngTemplateOutlet=\"sendingStatus\"></ng-container>\n </ng-container>\n <ng-template #sentStatus>\n <ng-container\n *ngIf=\"\n mode === 'main' && isMessageDeliveredAndRead && canDisplayReadStatus;\n else deliveredStatus\n \"\n >\n <ng-container *ngTemplateOutlet=\"readStatus\"></ng-container>\n </ng-container>\n </ng-template>\n <ng-template #deliveredStatus>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.deliveredStatusTemplate$ | async) ||\n defaultDeliveredStatus;\n context: getDeliveredStatusContext()\n \"\n ></ng-container>\n </ng-template>\n <ng-template #defaultDeliveredStatus>\n <span\n *ngIf=\"mode === 'main'\"\n class=\"\n str-chat__message-simple-status\n str-chat__message-simple-status-angular\n str-chat__message-status\n \"\n data-testid=\"delivered-indicator\"\n tabindex=\"0\"\n [floatUiLoose]=\"floatingContent\"\n loosePlacement=\"top\"\n looseTrigger=\"hover\"\n >\n <float-ui-content #floatingContent>\n <div class=\"str-chat__tooltip str-chat__tooltip-angular\">\n {{ \"streamChat.Delivered\" | translate }}\n </div>\n </float-ui-content>\n <stream-icon-placeholder\n data-testid=\"delivered-icon\"\n icon=\"delivered\"\n ></stream-icon-placeholder>\n </span>\n </ng-template>\n <ng-template #sendingStatus>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.sendingStatusTemplate$ | async) ||\n defaultSendingStatus;\n context: getSendingStatusContext()\n \"\n ></ng-container>\n </ng-template>\n <ng-template #defaultSendingStatus>\n <span\n class=\"\n str-chat__message-simple-status\n str-chat__message-simple-status-angular\n str-chat__message-status\n \"\n data-testid=\"sending-indicator\"\n tabindex=\"0\"\n [floatUiLoose]=\"floatingContent\"\n loosePlacement=\"top\"\n looseTrigger=\"hover\"\n [disableAnimation]=\"false\"\n [preventOverflow]=\"true\"\n [positionFixed]=\"true\"\n >\n <float-ui-content #floatingContent>\n <div class=\"str-chat__tooltip str-chat__tooltip-angular\">\n {{ \"streamChat.Sending...\" | translate }}\n </div>\n </float-ui-content>\n <stream-loading-indicator-placeholder\n data-testid=\"loading-indicator\"\n ></stream-loading-indicator-placeholder>\n </span>\n </ng-template>\n <ng-template #readStatus>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.readStatusTemplate$ | async) ||\n defaultReadStatus;\n context: getReadStatusContext()\n \"\n ></ng-container>\n </ng-template>\n <ng-template #defaultReadStatus let-readByText=\"readByText\">\n <span\n class=\"\n str-chat__message-simple-status\n str-chat__message-simple-status-angular\n str-chat__message-status\n \"\n data-testid=\"read-indicator\"\n tabindex=\"0\"\n [floatUiLoose]=\"floatingContent\"\n loosePlacement=\"top\"\n looseTrigger=\"hover\"\n [disableAnimation]=\"false\"\n [preventOverflow]=\"true\"\n [positionFixed]=\"true\"\n >\n <float-ui-content #floatingContent>\n <div\n class=\"str-chat__tooltip str-chat__tooltip-angular\"\n data-testid=\"read-by-tooltip\"\n >\n {{ readByText }}\n </div>\n </float-ui-content>\n <stream-icon-placeholder icon=\"read\"></stream-icon-placeholder>\n </span>\n </ng-template>\n </ng-container>\n</ng-template>\n\n<ng-template #replyCountButton>\n <div\n class=\"\n str-chat__message-simple-reply-button\n str-chat__message-replies-count-button-wrapper\n \"\n >\n <button\n *ngIf=\"shouldDisplayThreadLink\"\n class=\"str-chat__message-replies-count-button\"\n data-testid=\"reply-count-button\"\n (click)=\"setAsActiveParentMessage()\"\n >\n {{message?.reply_count === 1 ? ('streamChat.1 reply' | translate) : ('streamChat.{{ replyCount }}\n replies' | translate:replyCountParam)}}\n </button>\n </div>\n</ng-template>\n\n<ng-template #attachmentsTemplate>\n <ng-template\n #defaultAttachments\n let-messageId=\"messageId\"\n let-attachments=\"attachments\"\n let-parentMessageId=\"parentMessageId\"\n let-imageModalStateChangeHandler=\"imageModalStateChangeHandler\"\n >\n <stream-attachment-list\n [messageId]=\"messageId\"\n [attachments]=\"attachments\"\n [parentMessageId]=\"parentMessageId\"\n (imageModalStateChange)=\"imageModalStateChangeHandler($event)\"\n ></stream-attachment-list>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.attachmentListTemplate$ | async) ||\n defaultAttachments;\n context: getAttachmentListContext()\n \"\n ></ng-container>\n</ng-template>\n", dependencies: [{ kind: "directive", type: i4.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i4.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i4.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: i8.NgxFloatUiContentComponent, selector: "float-ui-content", exportAs: ["ngxFloatUiContent"] }, { kind: "directive", type: i8.NgxFloatUiLooseDirective, selector: "[floatUiLoose]", inputs: ["floatUiLoose", "loosePlacement", "looseTrigger"], exportAs: ["floatUiLoose"] }, { kind: "component", type: AvatarPlaceholderComponent, selector: "stream-avatar-placeholder", inputs: ["name", "imageUrl", "location", "channel", "user", "type", "initialsType", "showOnlineIndicator"] }, { kind: "directive", type: i10.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "component", type: MessageActionsBoxComponent, selector: "stream-message-actions-box", inputs: ["isMine", "message", "messageTextHtmlElement", "enabledActions"] }, { kind: "component", type: AttachmentListComponent, selector: "stream-attachment-list", inputs: ["messageId", "parentMessageId", "attachments"], outputs: ["imageModalStateChange"] }, { kind: "component", type: MessageReactionsComponent, selector: "stream-message-reactions", inputs: ["messageId", "messageReactionCounts", "latestReactions", "ownReactions"] }, { kind: "component", type: IconPlaceholderComponent, selector: "stream-icon-placeholder", inputs: ["icon"] }, { kind: "component", type: LoadingIndicatorPlaceholderComponent, selector: "stream-loading-indicator-placeholder" }, { kind: "pipe", type: i4.AsyncPipe, name: "async" }, { kind: "pipe", type: i10.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5630
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: MessageComponent, decorators: [{
5628
+ MessageInputComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: MessageInputComponent, deps: [{ token: ChannelService }, { token: NotificationService }, { token: AttachmentService }, { token: MessageInputConfigService }, { token: textareaInjectionToken }, { token: i0.ComponentFactoryResolver }, { token: i0.ChangeDetectorRef }, { token: ChatClientService }, { token: EmojiInputService }, { token: CustomTemplatesService }, { token: MessageActionsService }], target: i0.ɵɵFactoryTarget.Component });
5629
+ MessageInputComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.0.4", type: MessageInputComponent, selector: "stream-message-input", inputs: { isFileUploadEnabled: "isFileUploadEnabled", areMentionsEnabled: "areMentionsEnabled", mentionScope: "mentionScope", mode: "mode", isMultipleFileUploadEnabled: "isMultipleFileUploadEnabled", message: "message", sendMessage$: "sendMessage$", inputMode: "inputMode", autoFocus: "autoFocus", watchForMessageToEdit: "watchForMessageToEdit", displaySendButton: "displaySendButton" }, outputs: { messageUpdate: "messageUpdate" }, host: { properties: { "class": "this.class" } }, providers: [AttachmentService, EmojiInputService], viewQueries: [{ propertyName: "fileInput", first: true, predicate: ["fileInput"], descendants: true }, { propertyName: "textareaAnchor", first: true, predicate: TextareaDirective, descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"str-chat__message-input str-chat-angular__message-input\">\n <div *ngIf=\"quotedMessage\" class=\"str-chat__quoted-message-preview-header\">\n <div class=\"str-chat__quoted-message-reply-to-message\">\n {{ \"streamChat.Reply to Message\" | translate }}\n </div>\n <button\n class=\"str-chat__quoted-message-remove\"\n data-testid=\"remove-quote\"\n (click)=\"deselectMessageToQuote()\"\n (keyup.enter)=\"deselectMessageToQuote()\"\n >\n <stream-icon-placeholder icon=\"close\"></stream-icon-placeholder>\n </button>\n </div>\n <div *ngIf=\"isUpdate\" class=\"str-chat__quoted-message-preview-header\">\n <div class=\"str-chat__quoted-message-reply-to-message\">\n {{ \"streamChat.Edit Message\" | translate }}\n </div>\n <button\n class=\"str-chat__quoted-message-remove\"\n data-testid=\"remove-quote\"\n (click)=\"deselectMessageToEdit()\"\n (keyup.enter)=\"deselectMessageToEdit()\"\n >\n <stream-icon-placeholder icon=\"close\"></stream-icon-placeholder>\n </button>\n </div>\n <ng-container *ngIf=\"canSendMessages; else notAllowed\">\n <div\n class=\"\n str-chat__message-input-inner\n str-chat-angular__message-input-inner\n \"\n >\n <ng-container\n *ngIf=\"isFileUploadEnabled && isFileUploadAuthorized && canSendMessages\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n customAttachmentUploadTemplate || defaultAttachmentUpload;\n context: getAttachmentUploadContext()\n \"\n ></ng-container>\n <ng-template #defaultAttachmentUpload>\n <div\n class=\"str-chat__file-input-container\"\n data-testid=\"file-upload-button\"\n >\n <input\n #fileInput\n type=\"file\"\n class=\"str-chat__file-input\"\n data-testid=\"file-input\"\n [multiple]=\"isMultipleFileUploadEnabled\"\n id=\"{{ fileInputId }}\"\n (change)=\"filesSelected(fileInput.files)\"\n />\n <label class=\"str-chat__file-input-label\" for=\"{{ fileInputId }}\">\n <stream-icon-placeholder icon=\"attach\"></stream-icon-placeholder>\n </label>\n </div>\n </ng-template>\n </ng-container>\n <div class=\"str-chat__message-textarea-container\">\n <div\n *ngIf=\"quotedMessage\"\n data-testid=\"quoted-message-container\"\n class=\"str-chat__quoted-message-preview\"\n >\n <stream-avatar-placeholder\n data-testid=\"qouted-message-avatar\"\n class=\"\n str-chat-angular__avatar-host\n str-chat__message-sender-avatar\n \"\n type=\"user\"\n location=\"quoted-message-sender\"\n [imageUrl]=\"quotedMessage.user?.image\"\n [name]=\"quotedMessage.user?.name || quotedMessage.user?.id\"\n [user]=\"quotedMessage.user || undefined\"\n ></stream-avatar-placeholder>\n <div\n class=\"\n quoted-message-preview-content-inner\n str-chat__quoted-message-bubble\n \"\n >\n <stream-attachment-list\n *ngIf=\"\n quotedMessage?.attachments && quotedMessage?.attachments?.length\n \"\n [attachments]=\"quotedMessageAttachments\"\n [messageId]=\"quotedMessage.id\"\n ></stream-attachment-list>\n <div\n class=\"str-chat__quoted-message-text\"\n data-testid=\"quoted-message-text\"\n [innerHTML]=\"\n quotedMessage.translation ||\n quotedMessage.html ||\n quotedMessage.text\n \"\n ></div>\n </div>\n </div>\n <ng-template\n #defaultAttachmentsPreview\n let-attachmentUploads$=\"attachmentUploads$\"\n let-retryUploadHandler=\"retryUploadHandler\"\n let-deleteUploadHandler=\"deleteUploadHandler\"\n >\n <stream-attachment-preview-list\n class=\"str-chat__attachment-preview-list-angular-host\"\n [attachmentUploads$]=\"attachmentUploads$\"\n (retryAttachmentUpload)=\"retryUploadHandler($event)\"\n (deleteAttachment)=\"deleteUploadHandler($event)\"\n ></stream-attachment-preview-list>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n attachmentPreviewListTemplate || defaultAttachmentsPreview;\n context: getAttachmentPreviewListContext()\n \"\n ></ng-container>\n <div class=\"str-chat__message-textarea-with-emoji-picker\">\n <ng-container\n streamTextarea\n [componentRef]=\"textareaRef\"\n [areMentionsEnabled]=\"areMentionsEnabled\"\n [mentionScope]=\"mentionScope\"\n [inputMode]=\"inputMode\"\n [autoFocus]=\"autoFocus\"\n [placeholder]=\"textareaPlaceholder\"\n [(value)]=\"textareaValue\"\n (valueChange)=\"typingStart$.next()\"\n (send)=\"messageSent()\"\n (userMentions)=\"mentionedUsers = $event\"\n ></ng-container>\n <ng-container *ngIf=\"emojiPickerTemplate\" data-testid=\"emoji-picker\">\n <ng-container\n *ngTemplateOutlet=\"\n emojiPickerTemplate;\n context: getEmojiPickerContext()\n \"\n ></ng-container>\n </ng-container>\n </div>\n </div>\n <button\n *ngIf=\"canSendMessages && !isCooldownInProgress && displaySendButton\"\n data-testid=\"send-button\"\n class=\"str-chat__send-button\"\n [disabled]=\"\n (attachmentUploadInProgressCounter$ | async)! > 0 ||\n (!textareaValue && (attachmentUploads$ | async)!.length === 0)\n \"\n (click)=\"messageSent()\"\n (keyup.enter)=\"messageSent()\"\n >\n <stream-icon-placeholder icon=\"send\"></stream-icon-placeholder>\n </button>\n <div\n *ngIf=\"isCooldownInProgress\"\n class=\"str-chat__message-input-cooldown\"\n data-testid=\"cooldown-timer\"\n >\n {{ cooldown$ | async }}\n </div>\n </div>\n </ng-container>\n <ng-template #notAllowed>\n <div\n class=\"str-chat__message-input-not-allowed\"\n data-testid=\"disabled-textarea\"\n >\n {{ disabledTextareaText | translate }}\n </div>\n </ng-template>\n</div>\n", dependencies: [{ kind: "directive", type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i4.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: AvatarPlaceholderComponent, selector: "stream-avatar-placeholder", inputs: ["name", "imageUrl", "location", "channel", "user", "type", "initialsType", "showOnlineIndicator"] }, { kind: "component", type: AttachmentListComponent, selector: "stream-attachment-list", inputs: ["messageId", "parentMessageId", "attachments"], outputs: ["imageModalStateChange"] }, { kind: "component", type: AttachmentPreviewListComponent, selector: "stream-attachment-preview-list", inputs: ["attachmentUploads$"], outputs: ["retryAttachmentUpload", "deleteAttachment"] }, { kind: "directive", type: TextareaDirective, selector: "[streamTextarea]", inputs: ["componentRef", "areMentionsEnabled", "mentionScope", "inputMode", "value", "placeholder", "autoFocus"], outputs: ["valueChange", "send", "userMentions"] }, { kind: "component", type: IconPlaceholderComponent, selector: "stream-icon-placeholder", inputs: ["icon"] }, { kind: "pipe", type: i4.AsyncPipe, name: "async" }, { kind: "pipe", type: i10.TranslatePipe, name: "translate" }] });
5630
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: MessageInputComponent, decorators: [{
5631
5631
  type: Component,
5632
- args: [{ selector: 'stream-message', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n #container\n data-testid=\"message-container\"\n class=\"str-chat__message-simple str-chat__message str-chat__message--{{\n message?.type\n }} str-chat__message--{{ message?.status }} {{\n message?.text ? 'str-chat__message--has-text' : 'has-no-text'\n }} str-chat__message-menu-{{ areMessageOptionsOpen ? 'opened' : 'closed' }}\"\n [class.str-chat__message--me]=\"isSentByCurrentUser\"\n [class.str-chat__message--other]=\"!isSentByCurrentUser\"\n [class.str-chat__message-simple--me]=\"isSentByCurrentUser\"\n [class.str-chat__message--has-attachment]=\"hasAttachment\"\n [class.str-chat__message--with-reactions]=\"hasReactions\"\n [class.str-chat__message--highlighted]=\"isHighlighted\"\n [class.str-chat__message-with-thread-link]=\"shouldDisplayThreadLink\"\n [class.str-chat__message-send-can-be-retried]=\"\n (message?.status === 'failed' && message?.errorStatusCode !== 403) ||\n (message?.type === 'error' && message?.moderation_details)\n \"\n [class.str-chat__message-with-touch-support]=\"hasTouchSupport\"\n [class.str-chat__message-without-touch-support]=\"!hasTouchSupport\"\n>\n <ng-container *ngIf=\"!message?.deleted_at; else deletedMessage\">\n <ng-container *ngIf=\"message?.type !== 'system'; else systemMessage\">\n <stream-avatar-placeholder\n data-testid=\"avatar\"\n class=\"str-chat-angular__avatar-host str-chat__message-sender-avatar\"\n type=\"user\"\n location=\"message-sender\"\n [imageUrl]=\"message?.user?.image\"\n [name]=\"message?.user?.name || message?.user?.id\"\n [user]=\"message?.user || undefined\"\n ></stream-avatar-placeholder>\n <div class=\"str-chat__message-inner\">\n <div\n *ngIf=\"!hasTouchSupport && areOptionsVisible\"\n class=\"str-chat__message-simple__actions str-chat__message-options\"\n data-testid=\"message-options\"\n [class.str-chat__message-actions-open]=\"areMessageOptionsOpen\"\n >\n <div\n #messageActionsToggle\n data-testid=\"message-actions-container\"\n class=\"\n str-chat__message-actions-container\n str-chat__message-simple__actions__action\n str-chat__message-simple__actions__action--options\n \"\n [floatUiLoose]=\"messageMenuFloat\"\n looseTrigger=\"click\"\n [hideOnScroll]=\"false\"\n [hideOnClickOutside]=\"true\"\n [hideOnMouseLeave]=\"false\"\n [disableAnimation]=\"false\"\n [preventOverflow]=\"true\"\n [positionFixed]=\"true\"\n (onSHown)=\"areMessageOptionsOpen = true\"\n (onHidden)=\"areMessageOptionsOpen = false\"\n >\n <div\n *ngIf=\"visibleMessageActionsCount > 0\"\n class=\"str-chat__message-actions-box-button\"\n data-testid=\"message-options-button\"\n (click)=\"messageOptionsButtonClicked()\"\n (keyup.enter)=\"messageOptionsButtonClicked()\"\n >\n <stream-icon-placeholder\n icon=\"action\"\n class=\"str-chat__message-action-icon\"\n ></stream-icon-placeholder>\n </div>\n </div>\n </div>\n <div class=\"str-chat__message-reactions-host\">\n <ng-template\n #defaultMessageReactions\n let-messageReactionCounts=\"messageReactionCounts\"\n let-latestReactions=\"latestReactions\"\n let-messageId=\"messageId\"\n let-ownReactions=\"ownReactions\"\n >\n <stream-message-reactions\n [messageReactionCounts]=\"messageReactionCounts\"\n [latestReactions]=\"latestReactions\"\n [messageId]=\"messageId\"\n [ownReactions]=\"ownReactions\"\n ></stream-message-reactions>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.messageReactionsTemplate$ | async) ||\n defaultMessageReactions;\n context: getMessageReactionsContext()\n \"\n ></ng-container>\n </div>\n <float-ui-content #messageMenuFloat>\n <ng-template\n #defaultMessageActionsBox\n let-isMine=\"isMine\"\n let-messageInput=\"message\"\n let-enabledActions=\"enabledActions\"\n let-messageTextHtmlElement=\"messageTextHtmlElement\"\n >\n <stream-message-actions-box\n [isMine]=\"isMine\"\n [message]=\"messageInput\"\n [enabledActions]=\"enabledActions\"\n [messageTextHtmlElement]=\"messageTextHtmlElement\"\n (click)=\"messageActionsBoxClicked(messageMenuFloat)\"\n ></stream-message-actions-box>\n </ng-template>\n <ng-container>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.messageActionsBoxTemplate$ | async) ||\n defaultMessageActionsBox;\n context: getMessageActionsBoxContext()\n \"\n >\n </ng-container>\n </ng-container>\n </float-ui-content>\n <div\n class=\"str-chat__message-bubble str-chat-angular__message-bubble\"\n [class.str-chat-angular__message-bubble--attachment-modal-open]=\"\n imageAttachmentModalState === 'opened'\n \"\n data-testid=\"message-bubble\"\n [floatUiLoose]=\"messageMenuFloat\"\n #messageMenuTrigger=\"floatUiLoose\"\n looseTrigger=\"none\"\n [hideOnScroll]=\"false\"\n [hideOnClickOutside]=\"true\"\n [hideOnMouseLeave]=\"false\"\n [disableAnimation]=\"true\"\n [preventOverflow]=\"true\"\n (onShown)=\"areMessageOptionsOpen = true\"\n (onHidden)=\"areMessageOptionsOpen = false\"\n [positionFixed]=\"true\"\n (mousedown)=\"mousePushedDown($event)\"\n (mouseup)=\"mouseReleased()\"\n (touchstart)=\"touchStarted()\"\n (touchend)=\"touchEnded()\"\n (click)=\"messageBubbleClicked($event)\"\n (keyup.enter)=\"messageBubbleClicked($event)\"\n >\n <ng-container *ngIf=\"hasAttachment && !message?.quoted_message\">\n <div class=\"str-chat__attachments-container\">\n <ng-container\n *ngTemplateOutlet=\"attachmentsTemplate\"\n ></ng-container>\n </div>\n </ng-container>\n <div\n *ngIf=\"message?.text || (message?.quoted_message && hasAttachment)\"\n class=\"str-chat__message-text\"\n tabindex=\"0\"\n [class.str-chat__message-text--pointer-cursor]=\"\n (message?.status === 'failed' &&\n message?.errorStatusCode !== 403) ||\n (this.message?.type === 'error' &&\n this.message?.moderation_details) ||\n message?.message_text_updated_at\n \"\n (click)=\"messageClicked()\"\n (keyup.enter)=\"messageClicked()\"\n >\n <div\n data-testid=\"inner-message\"\n class=\"\n str-chat__message-text-inner str-chat__message-simple-text-inner\n \"\n [class.str-chat__message-light-text-inner--has-attachment]=\"\n hasAttachment\n \"\n >\n <ng-container *ngTemplateOutlet=\"quotedMessage\"></ng-container>\n <ng-container *ngIf=\"hasAttachment && message?.quoted_message\">\n <ng-container\n *ngTemplateOutlet=\"attachmentsTemplate\"\n ></ng-container>\n </ng-container>\n <div\n *ngIf=\"message?.type === 'error'\"\n data-testid=\"client-error-message\"\n class=\"\n str-chat__simple-message--error-message\n str-chat__message--error-message\n \"\n >\n <ng-container *ngIf=\"!message?.moderation_details\">{{\n \"streamChat.Error \u00B7 Unsent\" | translate\n }}</ng-container>\n </div>\n <div\n *ngIf=\"message?.status === 'failed'\"\n data-testid=\"error-message\"\n class=\"\n str-chat__simple-message--error-message\n str-chat__message--error-message\n \"\n >\n {{\n (message?.errorStatusCode === 403\n ? \"streamChat.Message Failed \u00B7 Unauthorized\"\n : \"streamChat.Message Failed \u00B7 Click to try again\"\n ) | translate\n }}\n </div>\n <div #messageTextElement data-testid=\"text\">\n <p>\n <ng-container *ngIf=\"messageTextParts; else defaultContent\">\n <!-- eslint-disable-next-line @angular-eslint/template/use-track-by-function -->\n <ng-container *ngFor=\"let part of messageTextParts\">\n <span\n *ngIf=\"part.type === 'text'; else mention\"\n [innerHTML]=\"part.content\"\n ></span>\n <ng-template #mention>\n <ng-template #defaultMention let-content=\"content\">\n <span class=\"str-chat__message-mention\">{{\n content\n }}</span>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.mentionTemplate$ | async) ||\n defaultMention;\n context: getMentionContext(part)\n \"\n ></ng-container>\n </ng-template>\n </ng-container>\n </ng-container>\n <ng-template #defaultContent>\n <ng-container *ngIf=\"displayAs === 'text'; else asHTML\">\n {{ messageText || \"\" }}\n </ng-container>\n <ng-template #asHTML\n ><span\n data-testid=\"html-content\"\n [innerHTML]=\"messageText\"\n ></span\n ></ng-template>\n </ng-template>\n </p>\n </div>\n </div>\n </div>\n <stream-icon-placeholder\n class=\"str-chat__message-error-icon\"\n icon=\"error\"\n ></stream-icon-placeholder>\n </div>\n </div>\n <ng-container\n *ngTemplateOutlet=\"replyCountButton; context: { message: message }\"\n ></ng-container>\n\n <ng-container *ngTemplateOutlet=\"messageDateAndSender\"></ng-container>\n </ng-container>\n </ng-container>\n</div>\n\n<ng-template #deletedMessage>\n <div data-testid=\"message-deleted-component\">\n <div class=\"str-chat__message--deleted-inner\" translate>\n streamChat.This message was deleted...\n </div>\n </div>\n</ng-template>\n\n<ng-template #systemMessage>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.systemMessageTemplate$ | async) ||\n defaultSystemMessage;\n context: getMessageContext()\n \"\n ></ng-container>\n <ng-template #defaultSystemMessage let-messageInput=\"message\">\n <div data-testid=\"system-message\" class=\"str-chat__message--system\">\n <div class=\"str-chat__message--system__text\">\n <div class=\"str-chat__message--system__line\"></div>\n <p>{{ messageInput?.text }}</p>\n <div class=\"str-chat__message--system__line\"></div>\n </div>\n <div class=\"str-chat__message--system__date\">\n {{ parsedDate }}\n </div>\n </div>\n </ng-template>\n</ng-template>\n\n<ng-template #quotedMessage>\n <div\n *ngIf=\"message?.quoted_message\"\n class=\"quoted-message str-chat__quoted-message-preview\"\n data-testid=\"quoted-message-container\"\n [class.mine]=\"isSentByCurrentUser\"\n (click)=\"\n jumpToMessage(\n (message?.quoted_message)!.id,\n message?.quoted_message?.parent_id\n )\n \"\n (keyup.enter)=\"\n jumpToMessage(\n (message?.quoted_message)!.id,\n message?.quoted_message?.parent_id\n )\n \"\n >\n <stream-avatar-placeholder\n data-testid=\"qouted-message-avatar\"\n class=\"str-chat-angular__avatar-host str-chat__message-sender-avatar\"\n type=\"user\"\n location=\"quoted-message-sender\"\n [imageUrl]=\"message?.quoted_message?.user?.image\"\n [name]=\"\n message?.quoted_message?.user?.name || message?.quoted_message?.user?.id\n \"\n [user]=\"message?.quoted_message?.user || undefined\"\n ></stream-avatar-placeholder>\n <div class=\"quoted-message-inner str-chat__quoted-message-bubble\">\n <ng-container\n *ngIf=\"\n message?.quoted_message?.attachments &&\n message?.quoted_message?.attachments?.length\n \"\n >\n <ng-template\n #defaultAttachments\n let-messageId=\"messageId\"\n let-attachments=\"attachments\"\n let-parentMessageId=\"parentMessageId\"\n let-imageModalStateChangeHandler=\"imageModalStateChangeHandler\"\n >\n <stream-attachment-list\n [messageId]=\"messageId\"\n [attachments]=\"attachments\"\n [parentMessageId]=\"parentMessageId\"\n (imageModalStateChange)=\"imageModalStateChangeHandler($event)\"\n ></stream-attachment-list>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.attachmentListTemplate$ | async) ||\n defaultAttachments;\n context: getQuotedMessageAttachmentListContext()\n \"\n ></ng-container>\n </ng-container>\n <div\n data-testid=\"quoted-message-text\"\n [innerHTML]=\"\n message?.quoted_message?.translation ||\n message?.quoted_message?.html ||\n message?.quoted_message?.text\n \"\n ></div>\n </div>\n </div>\n</ng-template>\n\n<ng-template #messageDateAndSender>\n <ng-container>\n <div\n *ngIf=\"shouldDisplayTranslationNotice\"\n class=\"str-chat__translation-notice\"\n data-testid=\"translation-notice\"\n >\n <button\n *ngIf=\"displayedMessageTextContent === 'translation'\"\n data-testid=\"see-original\"\n translate\n (click)=\"displayOriginalMessage()\"\n (keyup.enter)=\"displayOriginalMessage()\"\n >\n streamChat.See original (automatically translated)\n </button>\n <button\n *ngIf=\"displayedMessageTextContent === 'original'\"\n data-testid=\"see-translation\"\n translate\n (click)=\"displayTranslatedMessage()\"\n (keyup.enter)=\"displayTranslatedMessage()\"\n >\n streamChat.See translation\n </button>\n </div>\n <ng-container\n *ngIf=\"customTemplatesService.customMessageMetadataTemplate$ | async\"\n >\n <div class=\"str-chat__custom-message-metadata\">\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.customMessageMetadataTemplate$ | async)!;\n context: getMessageMetadataContext()\n \"\n ></ng-container>\n </div>\n </ng-container>\n <div\n class=\"\n str-chat__message-data\n str-chat__message-simple-data\n str-chat__message-metadata\n \"\n >\n <ng-container *ngTemplateOutlet=\"messageStatus\"></ng-container>\n\n <span\n *ngIf=\"!isSentByCurrentUser\"\n data-testid=\"sender\"\n class=\"str-chat__message-simple-name str-chat__message-sender-name\"\n >\n {{ message?.user?.name ? message?.user?.name : message?.user?.id }}\n </span>\n <span\n data-testid=\"date\"\n class=\"str-chat__message-simple-timestamp str-chat__message-simple-time\"\n >\n {{ parsedDate }}\n </span>\n <ng-container *ngIf=\"message?.message_text_updated_at\">\n <span\n data-testid=\"edited-flag\"\n class=\"str-chat__mesage-simple-edited\"\n translate\n >streamChat.Edited</span\n >\n <div\n data-testid=\"edited-timestamp\"\n class=\"str-chat__message-edited-timestamp\"\n [ngClass]=\"{\n 'str-chat__message-edited-timestamp--open': isEditedFlagOpened,\n 'str-chat__message-edited-timestamp--collapsed': !isEditedFlagOpened\n }\"\n >\n <span translate>streamChat.Edited</span>\n <time\n dateTime=\"{{ message?.message_text_updated_at }}\"\n title=\"{{ message?.message_text_updated_at }}\"\n >\n {{ pasedEditedDate }}\n </time>\n </div>\n </ng-container>\n </div>\n </ng-container>\n</ng-template>\n\n<ng-template #messageStatus>\n <ng-container\n *ngIf=\"\n isSentByCurrentUser &&\n ((isLastSentMessage && message?.status === 'received') ||\n message?.status === 'sending')\n \"\n >\n <ng-container *ngIf=\"message?.status === 'sending'; else sentStatus\">\n <ng-container *ngTemplateOutlet=\"sendingStatus\"></ng-container>\n </ng-container>\n <ng-template #sentStatus>\n <ng-container\n *ngIf=\"\n mode === 'main' && isMessageDeliveredAndRead && canDisplayReadStatus;\n else deliveredStatus\n \"\n >\n <ng-container *ngTemplateOutlet=\"readStatus\"></ng-container>\n </ng-container>\n </ng-template>\n <ng-template #deliveredStatus>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.deliveredStatusTemplate$ | async) ||\n defaultDeliveredStatus;\n context: getDeliveredStatusContext()\n \"\n ></ng-container>\n </ng-template>\n <ng-template #defaultDeliveredStatus>\n <span\n *ngIf=\"mode === 'main'\"\n class=\"\n str-chat__message-simple-status\n str-chat__message-simple-status-angular\n str-chat__message-status\n \"\n data-testid=\"delivered-indicator\"\n tabindex=\"0\"\n [floatUiLoose]=\"floatingContent\"\n loosePlacement=\"top\"\n looseTrigger=\"hover\"\n >\n <float-ui-content #floatingContent>\n <div class=\"str-chat__tooltip str-chat__tooltip-angular\">\n {{ \"streamChat.Delivered\" | translate }}\n </div>\n </float-ui-content>\n <stream-icon-placeholder\n data-testid=\"delivered-icon\"\n icon=\"delivered\"\n ></stream-icon-placeholder>\n </span>\n </ng-template>\n <ng-template #sendingStatus>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.sendingStatusTemplate$ | async) ||\n defaultSendingStatus;\n context: getSendingStatusContext()\n \"\n ></ng-container>\n </ng-template>\n <ng-template #defaultSendingStatus>\n <span\n class=\"\n str-chat__message-simple-status\n str-chat__message-simple-status-angular\n str-chat__message-status\n \"\n data-testid=\"sending-indicator\"\n tabindex=\"0\"\n [floatUiLoose]=\"floatingContent\"\n loosePlacement=\"top\"\n looseTrigger=\"hover\"\n [disableAnimation]=\"false\"\n [preventOverflow]=\"true\"\n [positionFixed]=\"true\"\n >\n <float-ui-content #floatingContent>\n <div class=\"str-chat__tooltip str-chat__tooltip-angular\">\n {{ \"streamChat.Sending...\" | translate }}\n </div>\n </float-ui-content>\n <stream-loading-indicator-placeholder\n data-testid=\"loading-indicator\"\n ></stream-loading-indicator-placeholder>\n </span>\n </ng-template>\n <ng-template #readStatus>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.readStatusTemplate$ | async) ||\n defaultReadStatus;\n context: getReadStatusContext()\n \"\n ></ng-container>\n </ng-template>\n <ng-template #defaultReadStatus let-readByText=\"readByText\">\n <span\n class=\"\n str-chat__message-simple-status\n str-chat__message-simple-status-angular\n str-chat__message-status\n \"\n data-testid=\"read-indicator\"\n tabindex=\"0\"\n [floatUiLoose]=\"floatingContent\"\n loosePlacement=\"top\"\n looseTrigger=\"hover\"\n [disableAnimation]=\"false\"\n [preventOverflow]=\"true\"\n [positionFixed]=\"true\"\n >\n <float-ui-content #floatingContent>\n <div\n class=\"str-chat__tooltip str-chat__tooltip-angular\"\n data-testid=\"read-by-tooltip\"\n >\n {{ readByText }}\n </div>\n </float-ui-content>\n <stream-icon-placeholder icon=\"read\"></stream-icon-placeholder>\n </span>\n </ng-template>\n </ng-container>\n</ng-template>\n\n<ng-template #replyCountButton>\n <div\n class=\"\n str-chat__message-simple-reply-button\n str-chat__message-replies-count-button-wrapper\n \"\n >\n <button\n *ngIf=\"shouldDisplayThreadLink\"\n class=\"str-chat__message-replies-count-button\"\n data-testid=\"reply-count-button\"\n (click)=\"setAsActiveParentMessage()\"\n >\n {{message?.reply_count === 1 ? ('streamChat.1 reply' | translate) : ('streamChat.{{ replyCount }}\n replies' | translate:replyCountParam)}}\n </button>\n </div>\n</ng-template>\n\n<ng-template #attachmentsTemplate>\n <ng-template\n #defaultAttachments\n let-messageId=\"messageId\"\n let-attachments=\"attachments\"\n let-parentMessageId=\"parentMessageId\"\n let-imageModalStateChangeHandler=\"imageModalStateChangeHandler\"\n >\n <stream-attachment-list\n [messageId]=\"messageId\"\n [attachments]=\"attachments\"\n [parentMessageId]=\"parentMessageId\"\n (imageModalStateChange)=\"imageModalStateChangeHandler($event)\"\n ></stream-attachment-list>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.attachmentListTemplate$ | async) ||\n defaultAttachments;\n context: getAttachmentListContext()\n \"\n ></ng-container>\n</ng-template>\n" }]
5633
- }], ctorParameters: function () { return [{ type: ChatClientService }, { type: ChannelService }, { type: CustomTemplatesService }, { type: i0.ChangeDetectorRef }, { type: DateParserService }, { type: MessageService }, { type: MessageActionsService }]; }, propDecorators: { message: [{
5632
+ args: [{ selector: 'stream-message-input', providers: [AttachmentService, EmojiInputService], template: "<div class=\"str-chat__message-input str-chat-angular__message-input\">\n <div *ngIf=\"quotedMessage\" class=\"str-chat__quoted-message-preview-header\">\n <div class=\"str-chat__quoted-message-reply-to-message\">\n {{ \"streamChat.Reply to Message\" | translate }}\n </div>\n <button\n class=\"str-chat__quoted-message-remove\"\n data-testid=\"remove-quote\"\n (click)=\"deselectMessageToQuote()\"\n (keyup.enter)=\"deselectMessageToQuote()\"\n >\n <stream-icon-placeholder icon=\"close\"></stream-icon-placeholder>\n </button>\n </div>\n <div *ngIf=\"isUpdate\" class=\"str-chat__quoted-message-preview-header\">\n <div class=\"str-chat__quoted-message-reply-to-message\">\n {{ \"streamChat.Edit Message\" | translate }}\n </div>\n <button\n class=\"str-chat__quoted-message-remove\"\n data-testid=\"remove-quote\"\n (click)=\"deselectMessageToEdit()\"\n (keyup.enter)=\"deselectMessageToEdit()\"\n >\n <stream-icon-placeholder icon=\"close\"></stream-icon-placeholder>\n </button>\n </div>\n <ng-container *ngIf=\"canSendMessages; else notAllowed\">\n <div\n class=\"\n str-chat__message-input-inner\n str-chat-angular__message-input-inner\n \"\n >\n <ng-container\n *ngIf=\"isFileUploadEnabled && isFileUploadAuthorized && canSendMessages\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n customAttachmentUploadTemplate || defaultAttachmentUpload;\n context: getAttachmentUploadContext()\n \"\n ></ng-container>\n <ng-template #defaultAttachmentUpload>\n <div\n class=\"str-chat__file-input-container\"\n data-testid=\"file-upload-button\"\n >\n <input\n #fileInput\n type=\"file\"\n class=\"str-chat__file-input\"\n data-testid=\"file-input\"\n [multiple]=\"isMultipleFileUploadEnabled\"\n id=\"{{ fileInputId }}\"\n (change)=\"filesSelected(fileInput.files)\"\n />\n <label class=\"str-chat__file-input-label\" for=\"{{ fileInputId }}\">\n <stream-icon-placeholder icon=\"attach\"></stream-icon-placeholder>\n </label>\n </div>\n </ng-template>\n </ng-container>\n <div class=\"str-chat__message-textarea-container\">\n <div\n *ngIf=\"quotedMessage\"\n data-testid=\"quoted-message-container\"\n class=\"str-chat__quoted-message-preview\"\n >\n <stream-avatar-placeholder\n data-testid=\"qouted-message-avatar\"\n class=\"\n str-chat-angular__avatar-host\n str-chat__message-sender-avatar\n \"\n type=\"user\"\n location=\"quoted-message-sender\"\n [imageUrl]=\"quotedMessage.user?.image\"\n [name]=\"quotedMessage.user?.name || quotedMessage.user?.id\"\n [user]=\"quotedMessage.user || undefined\"\n ></stream-avatar-placeholder>\n <div\n class=\"\n quoted-message-preview-content-inner\n str-chat__quoted-message-bubble\n \"\n >\n <stream-attachment-list\n *ngIf=\"\n quotedMessage?.attachments && quotedMessage?.attachments?.length\n \"\n [attachments]=\"quotedMessageAttachments\"\n [messageId]=\"quotedMessage.id\"\n ></stream-attachment-list>\n <div\n class=\"str-chat__quoted-message-text\"\n data-testid=\"quoted-message-text\"\n [innerHTML]=\"\n quotedMessage.translation ||\n quotedMessage.html ||\n quotedMessage.text\n \"\n ></div>\n </div>\n </div>\n <ng-template\n #defaultAttachmentsPreview\n let-attachmentUploads$=\"attachmentUploads$\"\n let-retryUploadHandler=\"retryUploadHandler\"\n let-deleteUploadHandler=\"deleteUploadHandler\"\n >\n <stream-attachment-preview-list\n class=\"str-chat__attachment-preview-list-angular-host\"\n [attachmentUploads$]=\"attachmentUploads$\"\n (retryAttachmentUpload)=\"retryUploadHandler($event)\"\n (deleteAttachment)=\"deleteUploadHandler($event)\"\n ></stream-attachment-preview-list>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n attachmentPreviewListTemplate || defaultAttachmentsPreview;\n context: getAttachmentPreviewListContext()\n \"\n ></ng-container>\n <div class=\"str-chat__message-textarea-with-emoji-picker\">\n <ng-container\n streamTextarea\n [componentRef]=\"textareaRef\"\n [areMentionsEnabled]=\"areMentionsEnabled\"\n [mentionScope]=\"mentionScope\"\n [inputMode]=\"inputMode\"\n [autoFocus]=\"autoFocus\"\n [placeholder]=\"textareaPlaceholder\"\n [(value)]=\"textareaValue\"\n (valueChange)=\"typingStart$.next()\"\n (send)=\"messageSent()\"\n (userMentions)=\"mentionedUsers = $event\"\n ></ng-container>\n <ng-container *ngIf=\"emojiPickerTemplate\" data-testid=\"emoji-picker\">\n <ng-container\n *ngTemplateOutlet=\"\n emojiPickerTemplate;\n context: getEmojiPickerContext()\n \"\n ></ng-container>\n </ng-container>\n </div>\n </div>\n <button\n *ngIf=\"canSendMessages && !isCooldownInProgress && displaySendButton\"\n data-testid=\"send-button\"\n class=\"str-chat__send-button\"\n [disabled]=\"\n (attachmentUploadInProgressCounter$ | async)! > 0 ||\n (!textareaValue && (attachmentUploads$ | async)!.length === 0)\n \"\n (click)=\"messageSent()\"\n (keyup.enter)=\"messageSent()\"\n >\n <stream-icon-placeholder icon=\"send\"></stream-icon-placeholder>\n </button>\n <div\n *ngIf=\"isCooldownInProgress\"\n class=\"str-chat__message-input-cooldown\"\n data-testid=\"cooldown-timer\"\n >\n {{ cooldown$ | async }}\n </div>\n </div>\n </ng-container>\n <ng-template #notAllowed>\n <div\n class=\"str-chat__message-input-not-allowed\"\n data-testid=\"disabled-textarea\"\n >\n {{ disabledTextareaText | translate }}\n </div>\n </ng-template>\n</div>\n" }]
5633
+ }], ctorParameters: function () { return [{ type: ChannelService }, { type: NotificationService }, { type: AttachmentService }, { type: MessageInputConfigService }, { type: i0.Type, decorators: [{
5634
+ type: Inject,
5635
+ args: [textareaInjectionToken]
5636
+ }] }, { type: i0.ComponentFactoryResolver }, { type: i0.ChangeDetectorRef }, { type: ChatClientService }, { type: EmojiInputService }, { type: CustomTemplatesService }, { type: MessageActionsService }]; }, propDecorators: { isFileUploadEnabled: [{
5634
5637
  type: Input
5635
- }], enabledMessageActions: [{
5638
+ }], areMentionsEnabled: [{
5636
5639
  type: Input
5637
- }], isLastSentMessage: [{
5640
+ }], mentionScope: [{
5638
5641
  type: Input
5639
5642
  }], mode: [{
5640
5643
  type: Input
5641
- }], isHighlighted: [{
5644
+ }], isMultipleFileUploadEnabled: [{
5642
5645
  type: Input
5643
- }], messageMenuTrigger: [{
5644
- type: ViewChild,
5645
- args: ['messageMenuTrigger']
5646
- }], messageMenuFloat: [{
5646
+ }], message: [{
5647
+ type: Input
5648
+ }], sendMessage$: [{
5649
+ type: Input
5650
+ }], inputMode: [{
5651
+ type: Input
5652
+ }], autoFocus: [{
5653
+ type: Input
5654
+ }], watchForMessageToEdit: [{
5655
+ type: Input
5656
+ }], displaySendButton: [{
5657
+ type: Input
5658
+ }], messageUpdate: [{
5659
+ type: Output
5660
+ }], class: [{
5661
+ type: HostBinding
5662
+ }], fileInput: [{
5647
5663
  type: ViewChild,
5648
- args: ['messageMenuFloat']
5649
- }], messageTextElement: [{
5664
+ args: ['fileInput']
5665
+ }], textareaAnchor: [{
5650
5666
  type: ViewChild,
5651
- args: ['messageTextElement']
5667
+ args: [TextareaDirective, { static: false }]
5652
5668
  }] } });
5653
5669
 
5654
5670
  /**
@@ -5680,6 +5696,7 @@ class TextareaComponent {
5680
5696
  */
5681
5697
  this.send = new EventEmitter();
5682
5698
  this.subscriptions = [];
5699
+ this.isViewInited = false;
5683
5700
  this.subscriptions.push(this.emojiInputService.emojiInput$.subscribe((emoji) => {
5684
5701
  this.messageInput.nativeElement.focus();
5685
5702
  const { selectionStart } = this.messageInput.nativeElement;
@@ -5695,8 +5712,19 @@ class TextareaComponent {
5695
5712
  if (changes.value && !this.value && this.messageInput) {
5696
5713
  this.messageInput.nativeElement.style.height = 'auto';
5697
5714
  }
5715
+ else if (changes.value &&
5716
+ this.value &&
5717
+ this.messageInput &&
5718
+ this.isViewInited) {
5719
+ setTimeout(() => {
5720
+ if (this.messageInput.nativeElement.scrollHeight > 0) {
5721
+ this.adjustTextareaHeight();
5722
+ }
5723
+ }, 0);
5724
+ }
5698
5725
  }
5699
5726
  ngAfterViewInit() {
5727
+ this.isViewInited = true;
5700
5728
  if (this.messageInput.nativeElement.scrollHeight > 0) {
5701
5729
  this.adjustTextareaHeight();
5702
5730
  }
@@ -5834,6 +5862,7 @@ class AutocompleteTextareaComponent {
5834
5862
  mentionSelect: (item, triggerChar) => this.itemSelectedFromAutocompleteList(item, triggerChar),
5835
5863
  };
5836
5864
  this.searchTerm$ = new BehaviorSubject('');
5865
+ this.isViewInited = false;
5837
5866
  this.searchTerm$.pipe(debounceTime(300)).subscribe((searchTerm) => {
5838
5867
  if (searchTerm.startsWith(this.mentionTriggerChar)) {
5839
5868
  void this.updateMentionOptions(searchTerm);
@@ -5888,8 +5917,19 @@ class AutocompleteTextareaComponent {
5888
5917
  this.messageInput.nativeElement.style.height = 'auto';
5889
5918
  this.updateMentionedUsersFromText();
5890
5919
  }
5920
+ else if (changes.value &&
5921
+ this.value &&
5922
+ this.messageInput &&
5923
+ this.isViewInited) {
5924
+ setTimeout(() => {
5925
+ if (this.messageInput.nativeElement.scrollHeight > 0) {
5926
+ this.adjustTextareaHeight();
5927
+ }
5928
+ }, 0);
5929
+ }
5891
5930
  }
5892
5931
  ngAfterViewInit() {
5932
+ this.isViewInited = true;
5893
5933
  if (this.messageInput.nativeElement.scrollHeight > 0) {
5894
5934
  this.adjustTextareaHeight();
5895
5935
  }
@@ -6126,6 +6166,7 @@ class MessageListComponent {
6126
6166
  this.isUnreadNotificationVisible = true;
6127
6167
  this.isJumpingToLatestUnreadMessage = false;
6128
6168
  this.isJumpToLatestButtonVisible = true;
6169
+ this.scroll$ = new Subject();
6129
6170
  this.subscriptions = [];
6130
6171
  this.isLatestMessageInList = true;
6131
6172
  this.parsedDates = new Map();
@@ -6449,6 +6490,7 @@ class MessageListComponent {
6449
6490
  this.scrollContainer.nativeElement.clientHeight) {
6450
6491
  return;
6451
6492
  }
6493
+ this.scroll$.next();
6452
6494
  const scrollPosition = this.getScrollPosition();
6453
6495
  this.chatClientService.chatClient?.logger?.('info', `Scrolled - scroll position: ${scrollPosition}, container height: ${this.scrollContainer.nativeElement.scrollHeight}`, { tags: `message list ${this.mode}` });
6454
6496
  const isUserScrolled = (this.direction === 'bottom-to-top'
@@ -6746,10 +6788,10 @@ class MessageListComponent {
6746
6788
  }
6747
6789
  }
6748
6790
  MessageListComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: MessageListComponent, deps: [{ token: ChannelService }, { token: ChatClientService }, { token: CustomTemplatesService }, { token: DateParserService }, { token: i0.NgZone }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
6749
- MessageListComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.0.4", type: MessageListComponent, selector: "stream-message-list", inputs: { mode: "mode", direction: "direction", hideJumpToLatestButtonDuringScroll: "hideJumpToLatestButtonDuringScroll", displayDateSeparator: "displayDateSeparator", displayUnreadSeparator: "displayUnreadSeparator", dateSeparatorTextPos: "dateSeparatorTextPos", openMessageListAt: "openMessageListAt", hideUnreadCountForNotificationAndIndicator: "hideUnreadCountForNotificationAndIndicator", displayLoadingIndicator: "displayLoadingIndicator", limitNumberOfMessagesInList: "limitNumberOfMessagesInList" }, host: { properties: { "class": "this.class" } }, viewQueries: [{ propertyName: "scrollContainer", first: true, predicate: ["scrollContainer"], descendants: true }, { propertyName: "parentMessageElement", first: true, predicate: ["parentMessageElement"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<ng-container\n *ngIf=\"\n lastReadMessageId &&\n isUnreadNotificationVisible &&\n openMessageListAt === 'last-message' &&\n displayUnreadSeparator\n \"\n>\n <ng-container\n *ngTemplateOutlet=\"\n customnewMessagesNotificationTemplate ||\n defaultUnreadMessagesNotification;\n context: {\n unreadCount: unreadCount,\n onDismiss: messageNotificationDismissClicked,\n onJump: messageNotificationJumpClicked\n }\n \"\n ></ng-container>\n</ng-container>\n<ng-template\n #defaultUnreadMessagesNotification\n let-unreadCount=\"unreadCount\"\n let-onDismiss=\"onDismiss\"\n let-onJump=\"onJump\"\n>\n <div\n class=\"str-chat__unread-messages-notification\"\n data-testid=\"unread-messages-notification\"\n >\n <button\n data-testid=\"unread-messages-notification-jump-to-message\"\n (click)=\"onJump()\"\n >\n <ng-container\n *ngIf=\"\n unreadCount > 0 && !hideUnreadCountForNotificationAndIndicator;\n else noUnreadCount\n \"\n >\n {{\n (unreadCount === 1\n ? \"streamChat.\\{\\{count\\}\\} unread message\"\n : \"streamChat.\\{\\{count\\}\\} unread messages\"\n ) | translate: { count: unreadCount }\n }}\n </ng-container>\n <ng-template #noUnreadCount>\n {{ \"streamChat.Unread messages\" | translate }}\n </ng-template>\n </button>\n <button\n data-testid=\"unread-messages-notification-dismiss\"\n (click)=\"onDismiss()\"\n >\n <stream-icon-placeholder icon=\"close\"></stream-icon-placeholder>\n </button>\n </div>\n</ng-template>\n<div #scrollContainer data-testid=\"scroll-container\" class=\"str-chat__list\">\n <ng-container *ngIf=\"mode === 'main' && isEmpty && emptyListTemplate\">\n <ng-container *ngTemplateOutlet=\"emptyListTemplate\"></ng-container>\n </ng-container>\n <div class=\"str-chat__reverse-infinite-scroll str-chat__message-list-scroll\">\n <ul class=\"str-chat__ul\">\n <li\n *ngIf=\"mode === 'thread' && parentMessage\"\n #parentMessageElement\n data-testid=\"parent-message\"\n class=\"str-chat__parent-message-li\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n messageTemplateContainer;\n context: { message: parentMessage, index: 'parent' }\n \"\n ></ng-container>\n <div data-testid=\"reply-count\" class=\"str-chat__thread-start\">\n {{parentMessage.reply_count === 1 ? ('streamChat.1 reply' | translate) : ('streamChat.{{ replyCount }}\n replies' | translate:replyCountParam)}}\n </div>\n </li>\n <ng-container *ngIf=\"mode === 'thread' && isEmpty && emptyListTemplate\">\n <ng-container *ngTemplateOutlet=\"emptyListTemplate\"></ng-container>\n </ng-container>\n <stream-loading-indicator\n *ngIf=\"\n isLoading && direction === 'bottom-to-top' && displayLoadingIndicator\n \"\n data-testid=\"top-loading-indicator\"\n ></stream-loading-indicator>\n <ng-container *ngIf=\"messages$ | async as messages\">\n <ng-container\n *ngFor=\"\n let message of messages;\n let i = index;\n let isFirst = first;\n let isLast = last;\n trackBy: trackByMessageId\n \"\n >\n <ng-container *ngIf=\"isFirst\">\n <ng-container\n *ngTemplateOutlet=\"\n dateSeparator;\n context: {\n date: message.created_at,\n parsedDate: parseDate(message.created_at)\n }\n \"\n ></ng-container>\n </ng-container>\n <li\n tabindex=\"0\"\n data-testclass=\"message\"\n class=\"str-chat__li str-chat__li--{{ groupStyles[i] }}\"\n id=\"{{ message.id }}\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n messageTemplateContainer;\n context: { message: message, index: i }\n \"\n ></ng-container>\n </li>\n <ng-container\n *ngIf=\"\n (lastReadMessageId === message?.id &&\n direction === 'bottom-to-top') ||\n (direction === 'top-to-bottom' &&\n lastReadMessageId === messages[i + 1]?.id)\n \"\n >\n <li\n *ngIf=\"displayUnreadSeparator\"\n id=\"stream-chat-new-message-indicator\"\n data-testid=\"new-messages-indicator\"\n class=\"str-chat__li str-chat__unread-messages-separator-wrapper\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n customnewMessagesIndicatorTemplate ||\n defaultNewMessagesIndicator;\n context: { unreadCount: unreadCount }\n \"\n ></ng-container>\n </li>\n </ng-container>\n <ng-container *ngIf=\"isNextMessageOnSeparateDate[i]\">\n <ng-container\n *ngTemplateOutlet=\"\n dateSeparator;\n context: {\n date: messages[i + 1].created_at,\n parsedDate: parseDate(messages[i + 1].created_at)\n }\n \"\n ></ng-container>\n </ng-container>\n </ng-container>\n </ng-container>\n <stream-loading-indicator\n *ngIf=\"\n isLoading && direction === 'top-to-bottom' && displayLoadingIndicator\n \"\n data-testid=\"bottom-loading-indicator\"\n ></stream-loading-indicator>\n </ul>\n <ng-template #defaultTypingIndicator let-usersTyping$=\"usersTyping$\">\n <!-- eslint-disable-next-line @angular-eslint/template/no-any -->\n <ng-container *ngIf=\"$any(usersTyping$ | async) as users\">\n <div\n *ngIf=\"users.length > 0\"\n data-testid=\"typing-indicator\"\n class=\"str-chat__typing-indicator str-chat__typing-indicator--typing\"\n >\n <div class=\"str-chat__typing-indicator__dots\">\n <span class=\"str-chat__typing-indicator__dot\"></span>\n <span class=\"str-chat__typing-indicator__dot\"></span>\n <span class=\"str-chat__typing-indicator__dot\"></span>\n </div>\n <div\n data-testid=\"typing-users\"\n class=\"str-chat__typing-indicator__users\"\n >\n {{\n users.length === 1\n ? (\"streamChat.user is typing\"\n | translate: { user: getTypingIndicatorText(users) })\n : (\"streamChat.users are typing\"\n | translate: { users: getTypingIndicatorText(users) })\n }}\n </div>\n </div>\n </ng-container>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n typingIndicatorTemplate || defaultTypingIndicator;\n context: getTypingIndicatorContext()\n \"\n ></ng-container>\n </div>\n</div>\n<div class=\"str-chat__jump-to-latest-message\">\n <button\n *ngIf=\"isUserScrolled && isJumpToLatestButtonVisible\"\n data-testid=\"scroll-to-latest\"\n class=\"\n str-chat__message-notification-scroll-to-latest\n str-chat__message-notification-scroll-to-latest-right\n str-chat__circle-fab\n \"\n (keyup.enter)=\"jumpToLatestMessage()\"\n (click)=\"jumpToLatestMessage()\"\n >\n <stream-icon\n class=\"str-chat__jump-to-latest-icon str-chat__circle-fab-icon\"\n [icon]=\"direction === 'bottom-to-top' ? 'arrow-down' : 'arrow-up'\"\n ></stream-icon>\n <div\n *ngIf=\"newMessageCountWhileBeingScrolled > 0\"\n class=\"\n str-chat__message-notification\n str-chat__message-notification-scroll-to-latest-unread-count\n str-chat__jump-to-latest-unread-count\n \"\n >\n {{ newMessageCountWhileBeingScrolled }}\n </div>\n </button>\n</div>\n\n<ng-template #messageTemplateContainer let-message=\"message\" let-index=\"index\">\n <ng-template\n #defaultMessageTemplate\n let-messageInput=\"message\"\n let-isLastSentMessage=\"isLastSentMessage\"\n let-enabledMessageActions=\"enabledMessageActions\"\n let-mode=\"mode\"\n let-isHighlighted=\"isHighlighted\"\n >\n <stream-message\n [message]=\"messageInput\"\n [isLastSentMessage]=\"isLastSentMessage\"\n [enabledMessageActions]=\"enabledMessageActions\"\n [mode]=\"mode\"\n [isHighlighted]=\"isHighlighted\"\n ></stream-message>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n messageTemplate || defaultMessageTemplate;\n context: {\n message: message,\n isLastSentMessage: !!(\n lastSentMessageId && message?.id === lastSentMessageId\n ),\n enabledMessageActions: enabledMessageActions,\n mode: mode,\n isHighlighted: message?.id === highlightedMessageId\n }\n \"\n ></ng-container>\n</ng-template>\n\n<ng-template #dateSeparator let-date=\"date\" let-parsedDate=\"parsedDate\">\n <ng-container *ngIf=\"displayDateSeparator\">\n <ng-container\n *ngTemplateOutlet=\"\n customDateSeparatorTemplate || defaultDateSeparator;\n context: {\n date: date,\n parsedDate: parsedDate\n }\n \"\n ></ng-container>\n </ng-container>\n\n <ng-template\n #defaultDateSeparator\n let-date=\"date\"\n let-parsedDate=\"parsedDate\"\n >\n <div data-testid=\"date-separator\" class=\"str-chat__date-separator\">\n <hr\n *ngIf=\"\n dateSeparatorTextPos === 'right' || dateSeparatorTextPos === 'center'\n \"\n class=\"str-chat__date-separator-line\"\n />\n <div class=\"str-chat__date-separator-date\">\n {{ parsedDate }}\n </div>\n <hr\n *ngIf=\"\n dateSeparatorTextPos === 'left' || dateSeparatorTextPos === 'center'\n \"\n class=\"str-chat__date-separator-line\"\n />\n </div>\n </ng-template>\n</ng-template>\n\n<ng-template #defaultNewMessagesIndicator let-unreadCount=\"unreadCount\">\n <div class=\"str-chat__unread-messages-separator\">\n <ng-container\n *ngIf=\"\n unreadCount > 0 && !hideUnreadCountForNotificationAndIndicator;\n else noUnreadCount\n \"\n >\n {{\n (unreadCount === 1\n ? \"streamChat.\\{\\{count\\}\\} unread message\"\n : \"streamChat.\\{\\{count\\}\\} unread messages\"\n ) | translate: { count: unreadCount }\n }}\n </ng-container>\n <ng-template #noUnreadCount>\n {{ \"streamChat.Unread messages\" | translate }}\n </ng-template>\n </div>\n</ng-template>\n", dependencies: [{ kind: "directive", type: i4.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i4.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: MessageComponent, selector: "stream-message", inputs: ["message", "enabledMessageActions", "isLastSentMessage", "mode", "isHighlighted"] }, { kind: "component", type: LoadingIndicatorComponent, selector: "stream-loading-indicator" }, { kind: "component", type: IconComponent, selector: "stream-icon", inputs: ["icon"] }, { kind: "component", type: IconPlaceholderComponent, selector: "stream-icon-placeholder", inputs: ["icon"] }, { kind: "pipe", type: i4.AsyncPipe, name: "async" }, { kind: "pipe", type: i10.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6791
+ MessageListComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.0.4", type: MessageListComponent, selector: "stream-message-list", inputs: { mode: "mode", direction: "direction", hideJumpToLatestButtonDuringScroll: "hideJumpToLatestButtonDuringScroll", displayDateSeparator: "displayDateSeparator", displayUnreadSeparator: "displayUnreadSeparator", dateSeparatorTextPos: "dateSeparatorTextPos", openMessageListAt: "openMessageListAt", hideUnreadCountForNotificationAndIndicator: "hideUnreadCountForNotificationAndIndicator", displayLoadingIndicator: "displayLoadingIndicator", limitNumberOfMessagesInList: "limitNumberOfMessagesInList" }, host: { properties: { "class": "this.class" } }, viewQueries: [{ propertyName: "scrollContainer", first: true, predicate: ["scrollContainer"], descendants: true }, { propertyName: "parentMessageElement", first: true, predicate: ["parentMessageElement"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<ng-container\n *ngIf=\"\n lastReadMessageId &&\n isUnreadNotificationVisible &&\n openMessageListAt === 'last-message' &&\n displayUnreadSeparator\n \"\n>\n <ng-container\n *ngTemplateOutlet=\"\n customnewMessagesNotificationTemplate ||\n defaultUnreadMessagesNotification;\n context: {\n unreadCount: unreadCount,\n onDismiss: messageNotificationDismissClicked,\n onJump: messageNotificationJumpClicked\n }\n \"\n ></ng-container>\n</ng-container>\n<ng-template\n #defaultUnreadMessagesNotification\n let-unreadCount=\"unreadCount\"\n let-onDismiss=\"onDismiss\"\n let-onJump=\"onJump\"\n>\n <div\n class=\"str-chat__unread-messages-notification\"\n data-testid=\"unread-messages-notification\"\n >\n <button\n data-testid=\"unread-messages-notification-jump-to-message\"\n (click)=\"onJump()\"\n >\n <ng-container\n *ngIf=\"\n unreadCount > 0 && !hideUnreadCountForNotificationAndIndicator;\n else noUnreadCount\n \"\n >\n {{\n (unreadCount === 1\n ? \"streamChat.\\{\\{count\\}\\} unread message\"\n : \"streamChat.\\{\\{count\\}\\} unread messages\"\n ) | translate: { count: unreadCount }\n }}\n </ng-container>\n <ng-template #noUnreadCount>\n {{ \"streamChat.Unread messages\" | translate }}\n </ng-template>\n </button>\n <button\n data-testid=\"unread-messages-notification-dismiss\"\n (click)=\"onDismiss()\"\n >\n <stream-icon-placeholder icon=\"close\"></stream-icon-placeholder>\n </button>\n </div>\n</ng-template>\n<div #scrollContainer data-testid=\"scroll-container\" class=\"str-chat__list\">\n <ng-container *ngIf=\"mode === 'main' && isEmpty && emptyListTemplate\">\n <ng-container *ngTemplateOutlet=\"emptyListTemplate\"></ng-container>\n </ng-container>\n <div class=\"str-chat__reverse-infinite-scroll str-chat__message-list-scroll\">\n <ul class=\"str-chat__ul\">\n <li\n *ngIf=\"mode === 'thread' && parentMessage\"\n #parentMessageElement\n data-testid=\"parent-message\"\n class=\"str-chat__parent-message-li\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n messageTemplateContainer;\n context: { message: parentMessage, index: 'parent' }\n \"\n ></ng-container>\n <div data-testid=\"reply-count\" class=\"str-chat__thread-start\">\n {{parentMessage.reply_count === 1 ? ('streamChat.1 reply' | translate) : ('streamChat.{{ replyCount }}\n replies' | translate:replyCountParam)}}\n </div>\n </li>\n <ng-container *ngIf=\"mode === 'thread' && isEmpty && emptyListTemplate\">\n <ng-container *ngTemplateOutlet=\"emptyListTemplate\"></ng-container>\n </ng-container>\n <stream-loading-indicator\n *ngIf=\"\n isLoading && direction === 'bottom-to-top' && displayLoadingIndicator\n \"\n data-testid=\"top-loading-indicator\"\n ></stream-loading-indicator>\n <ng-container *ngIf=\"messages$ | async as messages\">\n <ng-container\n *ngFor=\"\n let message of messages;\n let i = index;\n let isFirst = first;\n let isLast = last;\n trackBy: trackByMessageId\n \"\n >\n <ng-container *ngIf=\"isFirst\">\n <ng-container\n *ngTemplateOutlet=\"\n dateSeparator;\n context: {\n date: message.created_at,\n parsedDate: parseDate(message.created_at)\n }\n \"\n ></ng-container>\n </ng-container>\n <li\n tabindex=\"0\"\n data-testclass=\"message\"\n class=\"str-chat__li str-chat__li--{{ groupStyles[i] }}\"\n id=\"{{ message.id }}\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n messageTemplateContainer;\n context: { message: message, index: i }\n \"\n ></ng-container>\n </li>\n <ng-container\n *ngIf=\"\n (lastReadMessageId === message?.id &&\n direction === 'bottom-to-top') ||\n (direction === 'top-to-bottom' &&\n lastReadMessageId === messages[i + 1]?.id)\n \"\n >\n <li\n *ngIf=\"displayUnreadSeparator\"\n id=\"stream-chat-new-message-indicator\"\n data-testid=\"new-messages-indicator\"\n class=\"str-chat__li str-chat__unread-messages-separator-wrapper\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n customnewMessagesIndicatorTemplate ||\n defaultNewMessagesIndicator;\n context: { unreadCount: unreadCount }\n \"\n ></ng-container>\n </li>\n </ng-container>\n <ng-container *ngIf=\"isNextMessageOnSeparateDate[i]\">\n <ng-container\n *ngTemplateOutlet=\"\n dateSeparator;\n context: {\n date: messages[i + 1].created_at,\n parsedDate: parseDate(messages[i + 1].created_at)\n }\n \"\n ></ng-container>\n </ng-container>\n </ng-container>\n </ng-container>\n <stream-loading-indicator\n *ngIf=\"\n isLoading && direction === 'top-to-bottom' && displayLoadingIndicator\n \"\n data-testid=\"bottom-loading-indicator\"\n ></stream-loading-indicator>\n </ul>\n <ng-template #defaultTypingIndicator let-usersTyping$=\"usersTyping$\">\n <!-- eslint-disable-next-line @angular-eslint/template/no-any -->\n <ng-container *ngIf=\"$any(usersTyping$ | async) as users\">\n <div\n *ngIf=\"users.length > 0\"\n data-testid=\"typing-indicator\"\n class=\"str-chat__typing-indicator str-chat__typing-indicator--typing\"\n >\n <div class=\"str-chat__typing-indicator__dots\">\n <span class=\"str-chat__typing-indicator__dot\"></span>\n <span class=\"str-chat__typing-indicator__dot\"></span>\n <span class=\"str-chat__typing-indicator__dot\"></span>\n </div>\n <div\n data-testid=\"typing-users\"\n class=\"str-chat__typing-indicator__users\"\n >\n {{\n users.length === 1\n ? (\"streamChat.user is typing\"\n | translate: { user: getTypingIndicatorText(users) })\n : (\"streamChat.users are typing\"\n | translate: { users: getTypingIndicatorText(users) })\n }}\n </div>\n </div>\n </ng-container>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n typingIndicatorTemplate || defaultTypingIndicator;\n context: getTypingIndicatorContext()\n \"\n ></ng-container>\n </div>\n</div>\n<div class=\"str-chat__jump-to-latest-message\">\n <button\n *ngIf=\"isUserScrolled && isJumpToLatestButtonVisible\"\n data-testid=\"scroll-to-latest\"\n class=\"\n str-chat__message-notification-scroll-to-latest\n str-chat__message-notification-scroll-to-latest-right\n str-chat__circle-fab\n \"\n (keyup.enter)=\"jumpToLatestMessage()\"\n (click)=\"jumpToLatestMessage()\"\n >\n <stream-icon\n class=\"str-chat__jump-to-latest-icon str-chat__circle-fab-icon\"\n [icon]=\"direction === 'bottom-to-top' ? 'arrow-down' : 'arrow-up'\"\n ></stream-icon>\n <div\n *ngIf=\"newMessageCountWhileBeingScrolled > 0\"\n class=\"\n str-chat__message-notification\n str-chat__message-notification-scroll-to-latest-unread-count\n str-chat__jump-to-latest-unread-count\n \"\n >\n {{ newMessageCountWhileBeingScrolled }}\n </div>\n </button>\n</div>\n\n<ng-template #messageTemplateContainer let-message=\"message\" let-index=\"index\">\n <ng-template\n #defaultMessageTemplate\n let-messageInput=\"message\"\n let-isLastSentMessage=\"isLastSentMessage\"\n let-enabledMessageActions=\"enabledMessageActions\"\n let-mode=\"mode\"\n let-isHighlighted=\"isHighlighted\"\n let-scroll$=\"scroll$\"\n >\n <stream-message\n [message]=\"messageInput\"\n [isLastSentMessage]=\"isLastSentMessage\"\n [enabledMessageActions]=\"enabledMessageActions\"\n [mode]=\"mode\"\n [isHighlighted]=\"isHighlighted\"\n [scroll$]=\"scroll$\"\n ></stream-message>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n messageTemplate || defaultMessageTemplate;\n context: {\n message: message,\n isLastSentMessage: !!(\n lastSentMessageId && message?.id === lastSentMessageId\n ),\n enabledMessageActions: enabledMessageActions,\n mode: mode,\n isHighlighted: message?.id === highlightedMessageId,\n scroll$: scroll$\n }\n \"\n ></ng-container>\n</ng-template>\n\n<ng-template #dateSeparator let-date=\"date\" let-parsedDate=\"parsedDate\">\n <ng-container *ngIf=\"displayDateSeparator\">\n <ng-container\n *ngTemplateOutlet=\"\n customDateSeparatorTemplate || defaultDateSeparator;\n context: {\n date: date,\n parsedDate: parsedDate\n }\n \"\n ></ng-container>\n </ng-container>\n\n <ng-template\n #defaultDateSeparator\n let-date=\"date\"\n let-parsedDate=\"parsedDate\"\n >\n <div data-testid=\"date-separator\" class=\"str-chat__date-separator\">\n <hr\n *ngIf=\"\n dateSeparatorTextPos === 'right' || dateSeparatorTextPos === 'center'\n \"\n class=\"str-chat__date-separator-line\"\n />\n <div class=\"str-chat__date-separator-date\">\n {{ parsedDate }}\n </div>\n <hr\n *ngIf=\"\n dateSeparatorTextPos === 'left' || dateSeparatorTextPos === 'center'\n \"\n class=\"str-chat__date-separator-line\"\n />\n </div>\n </ng-template>\n</ng-template>\n\n<ng-template #defaultNewMessagesIndicator let-unreadCount=\"unreadCount\">\n <div class=\"str-chat__unread-messages-separator\">\n <ng-container\n *ngIf=\"\n unreadCount > 0 && !hideUnreadCountForNotificationAndIndicator;\n else noUnreadCount\n \"\n >\n {{\n (unreadCount === 1\n ? \"streamChat.\\{\\{count\\}\\} unread message\"\n : \"streamChat.\\{\\{count\\}\\} unread messages\"\n ) | translate: { count: unreadCount }\n }}\n </ng-container>\n <ng-template #noUnreadCount>\n {{ \"streamChat.Unread messages\" | translate }}\n </ng-template>\n </div>\n</ng-template>\n", dependencies: [{ kind: "directive", type: i4.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i4.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: MessageComponent, selector: "stream-message", inputs: ["message", "enabledMessageActions", "isLastSentMessage", "mode", "isHighlighted", "scroll$"] }, { kind: "component", type: LoadingIndicatorComponent, selector: "stream-loading-indicator" }, { kind: "component", type: IconComponent, selector: "stream-icon", inputs: ["icon"] }, { kind: "component", type: IconPlaceholderComponent, selector: "stream-icon-placeholder", inputs: ["icon"] }, { kind: "pipe", type: i4.AsyncPipe, name: "async" }, { kind: "pipe", type: i10.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6750
6792
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: MessageListComponent, decorators: [{
6751
6793
  type: Component,
6752
- args: [{ selector: 'stream-message-list', changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container\n *ngIf=\"\n lastReadMessageId &&\n isUnreadNotificationVisible &&\n openMessageListAt === 'last-message' &&\n displayUnreadSeparator\n \"\n>\n <ng-container\n *ngTemplateOutlet=\"\n customnewMessagesNotificationTemplate ||\n defaultUnreadMessagesNotification;\n context: {\n unreadCount: unreadCount,\n onDismiss: messageNotificationDismissClicked,\n onJump: messageNotificationJumpClicked\n }\n \"\n ></ng-container>\n</ng-container>\n<ng-template\n #defaultUnreadMessagesNotification\n let-unreadCount=\"unreadCount\"\n let-onDismiss=\"onDismiss\"\n let-onJump=\"onJump\"\n>\n <div\n class=\"str-chat__unread-messages-notification\"\n data-testid=\"unread-messages-notification\"\n >\n <button\n data-testid=\"unread-messages-notification-jump-to-message\"\n (click)=\"onJump()\"\n >\n <ng-container\n *ngIf=\"\n unreadCount > 0 && !hideUnreadCountForNotificationAndIndicator;\n else noUnreadCount\n \"\n >\n {{\n (unreadCount === 1\n ? \"streamChat.\\{\\{count\\}\\} unread message\"\n : \"streamChat.\\{\\{count\\}\\} unread messages\"\n ) | translate: { count: unreadCount }\n }}\n </ng-container>\n <ng-template #noUnreadCount>\n {{ \"streamChat.Unread messages\" | translate }}\n </ng-template>\n </button>\n <button\n data-testid=\"unread-messages-notification-dismiss\"\n (click)=\"onDismiss()\"\n >\n <stream-icon-placeholder icon=\"close\"></stream-icon-placeholder>\n </button>\n </div>\n</ng-template>\n<div #scrollContainer data-testid=\"scroll-container\" class=\"str-chat__list\">\n <ng-container *ngIf=\"mode === 'main' && isEmpty && emptyListTemplate\">\n <ng-container *ngTemplateOutlet=\"emptyListTemplate\"></ng-container>\n </ng-container>\n <div class=\"str-chat__reverse-infinite-scroll str-chat__message-list-scroll\">\n <ul class=\"str-chat__ul\">\n <li\n *ngIf=\"mode === 'thread' && parentMessage\"\n #parentMessageElement\n data-testid=\"parent-message\"\n class=\"str-chat__parent-message-li\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n messageTemplateContainer;\n context: { message: parentMessage, index: 'parent' }\n \"\n ></ng-container>\n <div data-testid=\"reply-count\" class=\"str-chat__thread-start\">\n {{parentMessage.reply_count === 1 ? ('streamChat.1 reply' | translate) : ('streamChat.{{ replyCount }}\n replies' | translate:replyCountParam)}}\n </div>\n </li>\n <ng-container *ngIf=\"mode === 'thread' && isEmpty && emptyListTemplate\">\n <ng-container *ngTemplateOutlet=\"emptyListTemplate\"></ng-container>\n </ng-container>\n <stream-loading-indicator\n *ngIf=\"\n isLoading && direction === 'bottom-to-top' && displayLoadingIndicator\n \"\n data-testid=\"top-loading-indicator\"\n ></stream-loading-indicator>\n <ng-container *ngIf=\"messages$ | async as messages\">\n <ng-container\n *ngFor=\"\n let message of messages;\n let i = index;\n let isFirst = first;\n let isLast = last;\n trackBy: trackByMessageId\n \"\n >\n <ng-container *ngIf=\"isFirst\">\n <ng-container\n *ngTemplateOutlet=\"\n dateSeparator;\n context: {\n date: message.created_at,\n parsedDate: parseDate(message.created_at)\n }\n \"\n ></ng-container>\n </ng-container>\n <li\n tabindex=\"0\"\n data-testclass=\"message\"\n class=\"str-chat__li str-chat__li--{{ groupStyles[i] }}\"\n id=\"{{ message.id }}\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n messageTemplateContainer;\n context: { message: message, index: i }\n \"\n ></ng-container>\n </li>\n <ng-container\n *ngIf=\"\n (lastReadMessageId === message?.id &&\n direction === 'bottom-to-top') ||\n (direction === 'top-to-bottom' &&\n lastReadMessageId === messages[i + 1]?.id)\n \"\n >\n <li\n *ngIf=\"displayUnreadSeparator\"\n id=\"stream-chat-new-message-indicator\"\n data-testid=\"new-messages-indicator\"\n class=\"str-chat__li str-chat__unread-messages-separator-wrapper\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n customnewMessagesIndicatorTemplate ||\n defaultNewMessagesIndicator;\n context: { unreadCount: unreadCount }\n \"\n ></ng-container>\n </li>\n </ng-container>\n <ng-container *ngIf=\"isNextMessageOnSeparateDate[i]\">\n <ng-container\n *ngTemplateOutlet=\"\n dateSeparator;\n context: {\n date: messages[i + 1].created_at,\n parsedDate: parseDate(messages[i + 1].created_at)\n }\n \"\n ></ng-container>\n </ng-container>\n </ng-container>\n </ng-container>\n <stream-loading-indicator\n *ngIf=\"\n isLoading && direction === 'top-to-bottom' && displayLoadingIndicator\n \"\n data-testid=\"bottom-loading-indicator\"\n ></stream-loading-indicator>\n </ul>\n <ng-template #defaultTypingIndicator let-usersTyping$=\"usersTyping$\">\n <!-- eslint-disable-next-line @angular-eslint/template/no-any -->\n <ng-container *ngIf=\"$any(usersTyping$ | async) as users\">\n <div\n *ngIf=\"users.length > 0\"\n data-testid=\"typing-indicator\"\n class=\"str-chat__typing-indicator str-chat__typing-indicator--typing\"\n >\n <div class=\"str-chat__typing-indicator__dots\">\n <span class=\"str-chat__typing-indicator__dot\"></span>\n <span class=\"str-chat__typing-indicator__dot\"></span>\n <span class=\"str-chat__typing-indicator__dot\"></span>\n </div>\n <div\n data-testid=\"typing-users\"\n class=\"str-chat__typing-indicator__users\"\n >\n {{\n users.length === 1\n ? (\"streamChat.user is typing\"\n | translate: { user: getTypingIndicatorText(users) })\n : (\"streamChat.users are typing\"\n | translate: { users: getTypingIndicatorText(users) })\n }}\n </div>\n </div>\n </ng-container>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n typingIndicatorTemplate || defaultTypingIndicator;\n context: getTypingIndicatorContext()\n \"\n ></ng-container>\n </div>\n</div>\n<div class=\"str-chat__jump-to-latest-message\">\n <button\n *ngIf=\"isUserScrolled && isJumpToLatestButtonVisible\"\n data-testid=\"scroll-to-latest\"\n class=\"\n str-chat__message-notification-scroll-to-latest\n str-chat__message-notification-scroll-to-latest-right\n str-chat__circle-fab\n \"\n (keyup.enter)=\"jumpToLatestMessage()\"\n (click)=\"jumpToLatestMessage()\"\n >\n <stream-icon\n class=\"str-chat__jump-to-latest-icon str-chat__circle-fab-icon\"\n [icon]=\"direction === 'bottom-to-top' ? 'arrow-down' : 'arrow-up'\"\n ></stream-icon>\n <div\n *ngIf=\"newMessageCountWhileBeingScrolled > 0\"\n class=\"\n str-chat__message-notification\n str-chat__message-notification-scroll-to-latest-unread-count\n str-chat__jump-to-latest-unread-count\n \"\n >\n {{ newMessageCountWhileBeingScrolled }}\n </div>\n </button>\n</div>\n\n<ng-template #messageTemplateContainer let-message=\"message\" let-index=\"index\">\n <ng-template\n #defaultMessageTemplate\n let-messageInput=\"message\"\n let-isLastSentMessage=\"isLastSentMessage\"\n let-enabledMessageActions=\"enabledMessageActions\"\n let-mode=\"mode\"\n let-isHighlighted=\"isHighlighted\"\n >\n <stream-message\n [message]=\"messageInput\"\n [isLastSentMessage]=\"isLastSentMessage\"\n [enabledMessageActions]=\"enabledMessageActions\"\n [mode]=\"mode\"\n [isHighlighted]=\"isHighlighted\"\n ></stream-message>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n messageTemplate || defaultMessageTemplate;\n context: {\n message: message,\n isLastSentMessage: !!(\n lastSentMessageId && message?.id === lastSentMessageId\n ),\n enabledMessageActions: enabledMessageActions,\n mode: mode,\n isHighlighted: message?.id === highlightedMessageId\n }\n \"\n ></ng-container>\n</ng-template>\n\n<ng-template #dateSeparator let-date=\"date\" let-parsedDate=\"parsedDate\">\n <ng-container *ngIf=\"displayDateSeparator\">\n <ng-container\n *ngTemplateOutlet=\"\n customDateSeparatorTemplate || defaultDateSeparator;\n context: {\n date: date,\n parsedDate: parsedDate\n }\n \"\n ></ng-container>\n </ng-container>\n\n <ng-template\n #defaultDateSeparator\n let-date=\"date\"\n let-parsedDate=\"parsedDate\"\n >\n <div data-testid=\"date-separator\" class=\"str-chat__date-separator\">\n <hr\n *ngIf=\"\n dateSeparatorTextPos === 'right' || dateSeparatorTextPos === 'center'\n \"\n class=\"str-chat__date-separator-line\"\n />\n <div class=\"str-chat__date-separator-date\">\n {{ parsedDate }}\n </div>\n <hr\n *ngIf=\"\n dateSeparatorTextPos === 'left' || dateSeparatorTextPos === 'center'\n \"\n class=\"str-chat__date-separator-line\"\n />\n </div>\n </ng-template>\n</ng-template>\n\n<ng-template #defaultNewMessagesIndicator let-unreadCount=\"unreadCount\">\n <div class=\"str-chat__unread-messages-separator\">\n <ng-container\n *ngIf=\"\n unreadCount > 0 && !hideUnreadCountForNotificationAndIndicator;\n else noUnreadCount\n \"\n >\n {{\n (unreadCount === 1\n ? \"streamChat.\\{\\{count\\}\\} unread message\"\n : \"streamChat.\\{\\{count\\}\\} unread messages\"\n ) | translate: { count: unreadCount }\n }}\n </ng-container>\n <ng-template #noUnreadCount>\n {{ \"streamChat.Unread messages\" | translate }}\n </ng-template>\n </div>\n</ng-template>\n" }]
6794
+ args: [{ selector: 'stream-message-list', changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container\n *ngIf=\"\n lastReadMessageId &&\n isUnreadNotificationVisible &&\n openMessageListAt === 'last-message' &&\n displayUnreadSeparator\n \"\n>\n <ng-container\n *ngTemplateOutlet=\"\n customnewMessagesNotificationTemplate ||\n defaultUnreadMessagesNotification;\n context: {\n unreadCount: unreadCount,\n onDismiss: messageNotificationDismissClicked,\n onJump: messageNotificationJumpClicked\n }\n \"\n ></ng-container>\n</ng-container>\n<ng-template\n #defaultUnreadMessagesNotification\n let-unreadCount=\"unreadCount\"\n let-onDismiss=\"onDismiss\"\n let-onJump=\"onJump\"\n>\n <div\n class=\"str-chat__unread-messages-notification\"\n data-testid=\"unread-messages-notification\"\n >\n <button\n data-testid=\"unread-messages-notification-jump-to-message\"\n (click)=\"onJump()\"\n >\n <ng-container\n *ngIf=\"\n unreadCount > 0 && !hideUnreadCountForNotificationAndIndicator;\n else noUnreadCount\n \"\n >\n {{\n (unreadCount === 1\n ? \"streamChat.\\{\\{count\\}\\} unread message\"\n : \"streamChat.\\{\\{count\\}\\} unread messages\"\n ) | translate: { count: unreadCount }\n }}\n </ng-container>\n <ng-template #noUnreadCount>\n {{ \"streamChat.Unread messages\" | translate }}\n </ng-template>\n </button>\n <button\n data-testid=\"unread-messages-notification-dismiss\"\n (click)=\"onDismiss()\"\n >\n <stream-icon-placeholder icon=\"close\"></stream-icon-placeholder>\n </button>\n </div>\n</ng-template>\n<div #scrollContainer data-testid=\"scroll-container\" class=\"str-chat__list\">\n <ng-container *ngIf=\"mode === 'main' && isEmpty && emptyListTemplate\">\n <ng-container *ngTemplateOutlet=\"emptyListTemplate\"></ng-container>\n </ng-container>\n <div class=\"str-chat__reverse-infinite-scroll str-chat__message-list-scroll\">\n <ul class=\"str-chat__ul\">\n <li\n *ngIf=\"mode === 'thread' && parentMessage\"\n #parentMessageElement\n data-testid=\"parent-message\"\n class=\"str-chat__parent-message-li\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n messageTemplateContainer;\n context: { message: parentMessage, index: 'parent' }\n \"\n ></ng-container>\n <div data-testid=\"reply-count\" class=\"str-chat__thread-start\">\n {{parentMessage.reply_count === 1 ? ('streamChat.1 reply' | translate) : ('streamChat.{{ replyCount }}\n replies' | translate:replyCountParam)}}\n </div>\n </li>\n <ng-container *ngIf=\"mode === 'thread' && isEmpty && emptyListTemplate\">\n <ng-container *ngTemplateOutlet=\"emptyListTemplate\"></ng-container>\n </ng-container>\n <stream-loading-indicator\n *ngIf=\"\n isLoading && direction === 'bottom-to-top' && displayLoadingIndicator\n \"\n data-testid=\"top-loading-indicator\"\n ></stream-loading-indicator>\n <ng-container *ngIf=\"messages$ | async as messages\">\n <ng-container\n *ngFor=\"\n let message of messages;\n let i = index;\n let isFirst = first;\n let isLast = last;\n trackBy: trackByMessageId\n \"\n >\n <ng-container *ngIf=\"isFirst\">\n <ng-container\n *ngTemplateOutlet=\"\n dateSeparator;\n context: {\n date: message.created_at,\n parsedDate: parseDate(message.created_at)\n }\n \"\n ></ng-container>\n </ng-container>\n <li\n tabindex=\"0\"\n data-testclass=\"message\"\n class=\"str-chat__li str-chat__li--{{ groupStyles[i] }}\"\n id=\"{{ message.id }}\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n messageTemplateContainer;\n context: { message: message, index: i }\n \"\n ></ng-container>\n </li>\n <ng-container\n *ngIf=\"\n (lastReadMessageId === message?.id &&\n direction === 'bottom-to-top') ||\n (direction === 'top-to-bottom' &&\n lastReadMessageId === messages[i + 1]?.id)\n \"\n >\n <li\n *ngIf=\"displayUnreadSeparator\"\n id=\"stream-chat-new-message-indicator\"\n data-testid=\"new-messages-indicator\"\n class=\"str-chat__li str-chat__unread-messages-separator-wrapper\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n customnewMessagesIndicatorTemplate ||\n defaultNewMessagesIndicator;\n context: { unreadCount: unreadCount }\n \"\n ></ng-container>\n </li>\n </ng-container>\n <ng-container *ngIf=\"isNextMessageOnSeparateDate[i]\">\n <ng-container\n *ngTemplateOutlet=\"\n dateSeparator;\n context: {\n date: messages[i + 1].created_at,\n parsedDate: parseDate(messages[i + 1].created_at)\n }\n \"\n ></ng-container>\n </ng-container>\n </ng-container>\n </ng-container>\n <stream-loading-indicator\n *ngIf=\"\n isLoading && direction === 'top-to-bottom' && displayLoadingIndicator\n \"\n data-testid=\"bottom-loading-indicator\"\n ></stream-loading-indicator>\n </ul>\n <ng-template #defaultTypingIndicator let-usersTyping$=\"usersTyping$\">\n <!-- eslint-disable-next-line @angular-eslint/template/no-any -->\n <ng-container *ngIf=\"$any(usersTyping$ | async) as users\">\n <div\n *ngIf=\"users.length > 0\"\n data-testid=\"typing-indicator\"\n class=\"str-chat__typing-indicator str-chat__typing-indicator--typing\"\n >\n <div class=\"str-chat__typing-indicator__dots\">\n <span class=\"str-chat__typing-indicator__dot\"></span>\n <span class=\"str-chat__typing-indicator__dot\"></span>\n <span class=\"str-chat__typing-indicator__dot\"></span>\n </div>\n <div\n data-testid=\"typing-users\"\n class=\"str-chat__typing-indicator__users\"\n >\n {{\n users.length === 1\n ? (\"streamChat.user is typing\"\n | translate: { user: getTypingIndicatorText(users) })\n : (\"streamChat.users are typing\"\n | translate: { users: getTypingIndicatorText(users) })\n }}\n </div>\n </div>\n </ng-container>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n typingIndicatorTemplate || defaultTypingIndicator;\n context: getTypingIndicatorContext()\n \"\n ></ng-container>\n </div>\n</div>\n<div class=\"str-chat__jump-to-latest-message\">\n <button\n *ngIf=\"isUserScrolled && isJumpToLatestButtonVisible\"\n data-testid=\"scroll-to-latest\"\n class=\"\n str-chat__message-notification-scroll-to-latest\n str-chat__message-notification-scroll-to-latest-right\n str-chat__circle-fab\n \"\n (keyup.enter)=\"jumpToLatestMessage()\"\n (click)=\"jumpToLatestMessage()\"\n >\n <stream-icon\n class=\"str-chat__jump-to-latest-icon str-chat__circle-fab-icon\"\n [icon]=\"direction === 'bottom-to-top' ? 'arrow-down' : 'arrow-up'\"\n ></stream-icon>\n <div\n *ngIf=\"newMessageCountWhileBeingScrolled > 0\"\n class=\"\n str-chat__message-notification\n str-chat__message-notification-scroll-to-latest-unread-count\n str-chat__jump-to-latest-unread-count\n \"\n >\n {{ newMessageCountWhileBeingScrolled }}\n </div>\n </button>\n</div>\n\n<ng-template #messageTemplateContainer let-message=\"message\" let-index=\"index\">\n <ng-template\n #defaultMessageTemplate\n let-messageInput=\"message\"\n let-isLastSentMessage=\"isLastSentMessage\"\n let-enabledMessageActions=\"enabledMessageActions\"\n let-mode=\"mode\"\n let-isHighlighted=\"isHighlighted\"\n let-scroll$=\"scroll$\"\n >\n <stream-message\n [message]=\"messageInput\"\n [isLastSentMessage]=\"isLastSentMessage\"\n [enabledMessageActions]=\"enabledMessageActions\"\n [mode]=\"mode\"\n [isHighlighted]=\"isHighlighted\"\n [scroll$]=\"scroll$\"\n ></stream-message>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n messageTemplate || defaultMessageTemplate;\n context: {\n message: message,\n isLastSentMessage: !!(\n lastSentMessageId && message?.id === lastSentMessageId\n ),\n enabledMessageActions: enabledMessageActions,\n mode: mode,\n isHighlighted: message?.id === highlightedMessageId,\n scroll$: scroll$\n }\n \"\n ></ng-container>\n</ng-template>\n\n<ng-template #dateSeparator let-date=\"date\" let-parsedDate=\"parsedDate\">\n <ng-container *ngIf=\"displayDateSeparator\">\n <ng-container\n *ngTemplateOutlet=\"\n customDateSeparatorTemplate || defaultDateSeparator;\n context: {\n date: date,\n parsedDate: parsedDate\n }\n \"\n ></ng-container>\n </ng-container>\n\n <ng-template\n #defaultDateSeparator\n let-date=\"date\"\n let-parsedDate=\"parsedDate\"\n >\n <div data-testid=\"date-separator\" class=\"str-chat__date-separator\">\n <hr\n *ngIf=\"\n dateSeparatorTextPos === 'right' || dateSeparatorTextPos === 'center'\n \"\n class=\"str-chat__date-separator-line\"\n />\n <div class=\"str-chat__date-separator-date\">\n {{ parsedDate }}\n </div>\n <hr\n *ngIf=\"\n dateSeparatorTextPos === 'left' || dateSeparatorTextPos === 'center'\n \"\n class=\"str-chat__date-separator-line\"\n />\n </div>\n </ng-template>\n</ng-template>\n\n<ng-template #defaultNewMessagesIndicator let-unreadCount=\"unreadCount\">\n <div class=\"str-chat__unread-messages-separator\">\n <ng-container\n *ngIf=\"\n unreadCount > 0 && !hideUnreadCountForNotificationAndIndicator;\n else noUnreadCount\n \"\n >\n {{\n (unreadCount === 1\n ? \"streamChat.\\{\\{count\\}\\} unread message\"\n : \"streamChat.\\{\\{count\\}\\} unread messages\"\n ) | translate: { count: unreadCount }\n }}\n </ng-container>\n <ng-template #noUnreadCount>\n {{ \"streamChat.Unread messages\" | translate }}\n </ng-template>\n </div>\n</ng-template>\n" }]
6753
6795
  }], ctorParameters: function () { return [{ type: ChannelService }, { type: ChatClientService }, { type: CustomTemplatesService }, { type: DateParserService }, { type: i0.NgZone }, { type: i0.ChangeDetectorRef }]; }, propDecorators: { mode: [{
6754
6796
  type: Input
6755
6797
  }], direction: [{
@@ -6860,7 +6902,6 @@ StreamChatModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", versio
6860
6902
  ThreadComponent,
6861
6903
  IconPlaceholderComponent,
6862
6904
  LoadingIndicatorPlaceholderComponent,
6863
- EditMessageFormComponent,
6864
6905
  MessageBouncePromptComponent,
6865
6906
  VoiceRecordingComponent,
6866
6907
  VoiceRecordingWavebarComponent,
@@ -6887,7 +6928,6 @@ StreamChatModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", versio
6887
6928
  ThreadComponent,
6888
6929
  IconPlaceholderComponent,
6889
6930
  LoadingIndicatorPlaceholderComponent,
6890
- EditMessageFormComponent,
6891
6931
  MessageBouncePromptComponent,
6892
6932
  VoiceRecordingComponent,
6893
6933
  VoiceRecordingWavebarComponent,
@@ -6920,7 +6960,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImpor
6920
6960
  ThreadComponent,
6921
6961
  IconPlaceholderComponent,
6922
6962
  LoadingIndicatorPlaceholderComponent,
6923
- EditMessageFormComponent,
6924
6963
  MessageBouncePromptComponent,
6925
6964
  VoiceRecordingComponent,
6926
6965
  VoiceRecordingWavebarComponent,
@@ -6953,7 +6992,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImpor
6953
6992
  ThreadComponent,
6954
6993
  IconPlaceholderComponent,
6955
6994
  LoadingIndicatorPlaceholderComponent,
6956
- EditMessageFormComponent,
6957
6995
  MessageBouncePromptComponent,
6958
6996
  VoiceRecordingComponent,
6959
6997
  VoiceRecordingWavebarComponent,
@@ -7020,5 +7058,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImpor
7020
7058
  * Generated bundle index. Do not edit.
7021
7059
  */
7022
7060
 
7023
- export { AttachmentConfigurationService, AttachmentListComponent, AttachmentPreviewListComponent, AttachmentService, AutocompleteTextareaComponent, AvatarComponent, AvatarPlaceholderComponent, ChannelComponent, ChannelHeaderComponent, ChannelListComponent, ChannelPreviewComponent, ChannelService, ChatClientService, CustomTemplatesService, DateParserService, EditMessageFormComponent, EmojiInputService, IconComponent, IconPlaceholderComponent, LoadingIndicatorComponent, LoadingIndicatorPlaceholderComponent, MessageActionsBoxComponent, MessageActionsService, MessageBouncePromptComponent, MessageComponent, MessageInputComponent, MessageInputConfigService, MessageListComponent, MessageReactionsComponent, MessageReactionsSelectorComponent, MessageReactionsService, MessageService, ModalComponent, NotificationComponent, NotificationListComponent, NotificationService, StreamAutocompleteTextareaModule, StreamAvatarModule, StreamChatModule, StreamI18nService, StreamTextareaModule, TextareaComponent, TextareaDirective, ThemeService, ThreadComponent, TransliterationService, VoiceRecordingComponent, VoiceRecordingWavebarComponent, createMessagePreview, getChannelDisplayText, getGroupStyles, getMessageTranslation, getReadBy, isImageAttachment, isImageFile, isOnSeparateDate, listUsers, parseDate, textareaInjectionToken };
7061
+ export { AttachmentConfigurationService, AttachmentListComponent, AttachmentPreviewListComponent, AttachmentService, AutocompleteTextareaComponent, AvatarComponent, AvatarPlaceholderComponent, ChannelComponent, ChannelHeaderComponent, ChannelListComponent, ChannelPreviewComponent, ChannelService, ChatClientService, CustomTemplatesService, DateParserService, EmojiInputService, IconComponent, IconPlaceholderComponent, LoadingIndicatorComponent, LoadingIndicatorPlaceholderComponent, MessageActionsBoxComponent, MessageActionsService, MessageBouncePromptComponent, MessageComponent, MessageInputComponent, MessageInputConfigService, MessageListComponent, MessageReactionsComponent, MessageReactionsSelectorComponent, MessageReactionsService, MessageService, ModalComponent, NotificationComponent, NotificationListComponent, NotificationService, StreamAutocompleteTextareaModule, StreamAvatarModule, StreamChatModule, StreamI18nService, StreamTextareaModule, TextareaComponent, TextareaDirective, ThemeService, ThreadComponent, TransliterationService, VoiceRecordingComponent, VoiceRecordingWavebarComponent, createMessagePreview, getChannelDisplayText, getGroupStyles, getMessageTranslation, getReadBy, isImageAttachment, isImageFile, isOnSeparateDate, listUsers, parseDate, textareaInjectionToken };
7024
7062
  //# sourceMappingURL=stream-chat-angular.mjs.map