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.
- package/assets/version.d.ts +1 -1
- package/esm2020/assets/version.mjs +2 -2
- package/esm2020/lib/channel/channel.component.mjs +5 -6
- package/esm2020/lib/custom-templates.service.mjs +1 -10
- package/esm2020/lib/message/message.component.mjs +51 -21
- package/esm2020/lib/message-actions-box/message-actions-box.component.mjs +1 -1
- package/esm2020/lib/message-actions.service.mjs +22 -4
- package/esm2020/lib/message-input/autocomplete-textarea/autocomplete-textarea.component.mjs +13 -1
- package/esm2020/lib/message-input/message-input.component.mjs +75 -23
- package/esm2020/lib/message-input/textarea/textarea.component.mjs +13 -1
- package/esm2020/lib/message-list/message-list.component.mjs +5 -3
- package/esm2020/lib/stream-chat.module.mjs +1 -6
- package/esm2020/lib/types.mjs +1 -1
- package/esm2020/public-api.mjs +1 -2
- package/fesm2015/stream-chat-angular.mjs +2028 -1989
- package/fesm2015/stream-chat-angular.mjs.map +1 -1
- package/fesm2020/stream-chat-angular.mjs +1669 -1631
- package/fesm2020/stream-chat-angular.mjs.map +1 -1
- package/lib/custom-templates.service.d.ts +1 -10
- package/lib/message/message.component.d.ts +11 -3
- package/lib/message-actions-box/message-actions-box.component.d.ts +1 -2
- package/lib/message-actions.service.d.ts +1 -0
- package/lib/message-input/autocomplete-textarea/autocomplete-textarea.component.d.ts +1 -0
- package/lib/message-input/message-input.component.d.ts +19 -3
- package/lib/message-input/textarea/textarea.component.d.ts +1 -0
- package/lib/message-list/message-list.component.d.ts +2 -1
- package/lib/stream-chat.module.d.ts +9 -10
- package/lib/types.d.ts +1 -10
- package/package.json +1 -1
- package/public-api.d.ts +0 -1
- package/src/assets/version.ts +1 -1
- package/esm2020/lib/edit-message-form/edit-message-form.component.mjs +0 -83
- 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,
|
|
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.
|
|
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(`
|
|
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
|
-
*
|
|
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
|
|
3564
|
-
constructor(
|
|
3565
|
-
this.
|
|
3566
|
-
this.
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
this.
|
|
3571
|
-
this.
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
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
|
-
|
|
3608
|
-
this.
|
|
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
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
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
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
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 `
|
|
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
|
|
4067
|
-
constructor(
|
|
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.
|
|
4076
|
-
this.
|
|
4366
|
+
this.channelService = channelService;
|
|
4367
|
+
this.messageReactionsService = messageReactionsService;
|
|
4077
4368
|
this.customTemplatesService = customTemplatesService;
|
|
4078
4369
|
/**
|
|
4079
|
-
*
|
|
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.
|
|
4372
|
+
this.messageReactionCounts = {};
|
|
4082
4373
|
/**
|
|
4083
|
-
*
|
|
4374
|
+
* List of reactions of a [message](../types/stream-message.mdx), used to display the users of a reaction type.
|
|
4084
4375
|
*/
|
|
4085
|
-
this.
|
|
4376
|
+
this.latestReactions = [];
|
|
4086
4377
|
/**
|
|
4087
|
-
*
|
|
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.
|
|
4090
|
-
this.
|
|
4091
|
-
this.
|
|
4092
|
-
this.
|
|
4093
|
-
this.
|
|
4094
|
-
this.
|
|
4095
|
-
this.
|
|
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.
|
|
4099
|
-
|
|
4100
|
-
|
|
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.
|
|
4162
|
-
this.
|
|
4163
|
-
this.
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
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.
|
|
4180
|
-
this.
|
|
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.
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
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
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
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
|
-
|
|
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 (
|
|
4245
|
-
|
|
4429
|
+
if (this.messageReactionsService.customReactionClickHandler) {
|
|
4430
|
+
this.messageReactionsService.customReactionClickHandler({
|
|
4431
|
+
messageId: this.messageId,
|
|
4432
|
+
reactionType: reactionType,
|
|
4433
|
+
});
|
|
4246
4434
|
}
|
|
4247
|
-
|
|
4248
|
-
this.
|
|
4249
|
-
|
|
4435
|
+
else {
|
|
4436
|
+
this.selectedReactionType = reactionType;
|
|
4437
|
+
void this.fetchAllReactions();
|
|
4250
4438
|
}
|
|
4251
|
-
|
|
4252
|
-
|
|
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
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
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
|
-
|
|
4268
|
-
if (this.isUpdate) {
|
|
4269
|
-
this.notificationService.addTemporaryNotification('streamChat.Edit message request failed');
|
|
4461
|
+
if (!name2) {
|
|
4462
|
+
return -1;
|
|
4270
4463
|
}
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
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
|
-
|
|
4278
|
-
return
|
|
4476
|
+
trackByMessageReaction(_, item) {
|
|
4477
|
+
return item;
|
|
4279
4478
|
}
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
return originalAttachments && originalAttachments.length
|
|
4283
|
-
? [originalAttachments[0]]
|
|
4284
|
-
: [];
|
|
4479
|
+
trackByUserId(_, item) {
|
|
4480
|
+
return item.id;
|
|
4285
4481
|
}
|
|
4286
|
-
|
|
4287
|
-
|
|
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
|
|
4295
|
-
if (!
|
|
4485
|
+
async fetchAllReactions() {
|
|
4486
|
+
if (!this.messageId) {
|
|
4296
4487
|
return;
|
|
4297
4488
|
}
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
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
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
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
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
|
|
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) }} \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) }} \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) }} \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) }} \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
|
-
|
|
4317
|
-
return
|
|
4318
|
-
isMultipleFileUploadEnabled: this.isMultipleFileUploadEnabled,
|
|
4319
|
-
attachmentService: this.attachmentService,
|
|
4320
|
-
};
|
|
4582
|
+
get visibleMessageActionsCount() {
|
|
4583
|
+
return this._visibleMessageActionsCount;
|
|
4321
4584
|
}
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
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
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
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
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
this.
|
|
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 (
|
|
4348
|
-
|
|
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
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
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 (
|
|
4360
|
-
|
|
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
|
-
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4419
|
-
|
|
4420
|
-
if (this.
|
|
4421
|
-
this.
|
|
4422
|
-
.
|
|
4423
|
-
|
|
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
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
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
|
-
|
|
4536
|
-
this.
|
|
4537
|
-
this.message = undefined;
|
|
4538
|
-
this.messageActionsService.messageToEdit$.next(undefined);
|
|
4712
|
+
mouseReleased() {
|
|
4713
|
+
this.stopMessageMenuShowTimer();
|
|
4539
4714
|
}
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
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
|
-
|
|
4583
|
-
this.
|
|
4721
|
+
touchEnded() {
|
|
4722
|
+
this.stopMessageMenuShowTimer();
|
|
4584
4723
|
}
|
|
4585
|
-
|
|
4586
|
-
this.
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
this.
|
|
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
|
-
|
|
4736
|
+
messageOptionsButtonClicked() {
|
|
4592
4737
|
if (!this.message) {
|
|
4593
4738
|
return;
|
|
4594
4739
|
}
|
|
4595
|
-
this.
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
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
|
-
|
|
4601
|
-
|
|
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
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
}
|
|
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
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
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
|
-
|
|
4659
|
-
|
|
4660
|
-
|
|
4661
|
-
|
|
4662
|
-
|
|
4663
|
-
|
|
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
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
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
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
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
|
-
|
|
4696
|
-
this.
|
|
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
|
-
|
|
4706
|
-
this.
|
|
4807
|
+
setAsActiveParentMessage() {
|
|
4808
|
+
void this.channelService.setAsActiveParentMessage(this.message);
|
|
4707
4809
|
}
|
|
4708
|
-
|
|
4709
|
-
return {
|
|
4810
|
+
getMentionContext(messagePart) {
|
|
4811
|
+
return {
|
|
4812
|
+
content: messagePart.content,
|
|
4813
|
+
user: messagePart.user,
|
|
4814
|
+
};
|
|
4710
4815
|
}
|
|
4711
|
-
|
|
4712
|
-
return {
|
|
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
|
-
|
|
4715
|
-
return {
|
|
4824
|
+
getDeliveredStatusContext() {
|
|
4825
|
+
return {
|
|
4826
|
+
message: this.message,
|
|
4827
|
+
};
|
|
4716
4828
|
}
|
|
4717
|
-
|
|
4718
|
-
return {
|
|
4829
|
+
getSendingStatusContext() {
|
|
4830
|
+
return {
|
|
4831
|
+
message: this.message,
|
|
4832
|
+
};
|
|
4719
4833
|
}
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4834
|
+
getReadStatusContext() {
|
|
4835
|
+
return {
|
|
4836
|
+
message: this.message,
|
|
4837
|
+
readByText: this.readByText,
|
|
4838
|
+
};
|
|
4725
4839
|
}
|
|
4726
|
-
|
|
4727
|
-
return
|
|
4840
|
+
getMessageMetadataContext() {
|
|
4841
|
+
return {
|
|
4842
|
+
message: this.message,
|
|
4843
|
+
};
|
|
4728
4844
|
}
|
|
4729
|
-
|
|
4730
|
-
|
|
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
|
-
|
|
4742
|
-
|
|
4743
|
-
return true;
|
|
4848
|
+
displayTranslatedMessage() {
|
|
4849
|
+
this.createMessageParts(true);
|
|
4744
4850
|
}
|
|
4745
|
-
|
|
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
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
return
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
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
|
-
|
|
4788
|
-
|
|
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
|
-
|
|
4791
|
-
|
|
4914
|
+
else {
|
|
4915
|
+
this.displayedMessageTextContent = 'original';
|
|
4916
|
+
return originalContent;
|
|
4792
4917
|
}
|
|
4793
4918
|
}
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
|
|
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
|
-
|
|
4814
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4863
|
-
this.
|
|
4864
|
-
|
|
4865
|
-
|
|
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.
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
|
|
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.
|
|
4890
|
-
|
|
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
|
-
|
|
4901
|
-
|
|
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
|
-
|
|
4904
|
-
if (
|
|
4905
|
-
|
|
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
|
-
|
|
4910
|
-
|
|
4911
|
-
}
|
|
4912
|
-
|
|
4913
|
-
|
|
4914
|
-
if (this.channel?.state.latestMessages.length === 0) {
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
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
|
-
|
|
4930
|
-
this.
|
|
4931
|
-
if (
|
|
4932
|
-
|
|
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 (
|
|
4942
|
-
this.
|
|
4943
|
-
|
|
4944
|
-
|
|
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
|
-
|
|
4947
|
-
this.
|
|
5054
|
+
if (changes.areMentionsEnabled) {
|
|
5055
|
+
this.componentRef.instance.areMentionsEnabled = this.areMentionsEnabled;
|
|
4948
5056
|
}
|
|
4949
|
-
|
|
4950
|
-
|
|
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
|
-
|
|
4962
|
-
this.
|
|
5060
|
+
if (changes.value) {
|
|
5061
|
+
this.componentRef.instance.value = this.value;
|
|
4963
5062
|
}
|
|
4964
|
-
if (
|
|
4965
|
-
|
|
4966
|
-
this.unreadCount = 0;
|
|
4967
|
-
this.isUnread = false;
|
|
4968
|
-
return;
|
|
5063
|
+
if (changes.placeholder) {
|
|
5064
|
+
this.componentRef.instance.placeholder = this.placeholder;
|
|
4969
5065
|
}
|
|
4970
|
-
|
|
4971
|
-
|
|
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
|
-
|
|
4975
|
-
|
|
4976
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type:
|
|
4977
|
-
type:
|
|
4978
|
-
args: [{
|
|
4979
|
-
|
|
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
|
+
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 });
|
|
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
|
-
*
|
|
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
|
|
4987
|
-
constructor(
|
|
4988
|
-
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
this.
|
|
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
|
-
|
|
5013
|
-
|
|
5014
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type:
|
|
5015
|
-
type:
|
|
5016
|
-
args: [{
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
}] } });
|
|
5120
|
+
EmojiInputService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: EmojiInputService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
5121
|
+
EmojiInputService.ɵprov = 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 `
|
|
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
|
|
5026
|
-
constructor(
|
|
5027
|
-
this.cdRef = cdRef;
|
|
5028
|
-
this.channelService = channelService;
|
|
5029
|
-
this.messageReactionsService = messageReactionsService;
|
|
5030
|
-
this.customTemplatesService = customTemplatesService;
|
|
5132
|
+
class MessageInputConfigService {
|
|
5133
|
+
constructor() {
|
|
5031
5134
|
/**
|
|
5032
|
-
*
|
|
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.
|
|
5137
|
+
this.isFileUploadEnabled = true;
|
|
5035
5138
|
/**
|
|
5036
|
-
*
|
|
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.
|
|
5141
|
+
this.areMentionsEnabled = true;
|
|
5039
5142
|
/**
|
|
5040
|
-
*
|
|
5143
|
+
* If `false`, users can only upload one attachment per message
|
|
5041
5144
|
*/
|
|
5042
|
-
this.
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
this.
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
this.
|
|
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
|
-
|
|
5142
|
-
|
|
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
|
-
|
|
5145
|
-
|
|
5179
|
+
attachmentUploadRetried(file) {
|
|
5180
|
+
this.retryAttachmentUpload.emit(file);
|
|
5146
5181
|
}
|
|
5147
|
-
|
|
5148
|
-
|
|
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
|
-
|
|
5164
|
-
|
|
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
|
-
|
|
5171
|
-
|
|
5172
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type:
|
|
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-
|
|
5175
|
-
}], ctorParameters: function () { return [
|
|
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
|
-
}],
|
|
5184
|
-
type:
|
|
5185
|
-
|
|
5196
|
+
}], retryAttachmentUpload: [{
|
|
5197
|
+
type: Output
|
|
5198
|
+
}], deleteAttachment: [{
|
|
5199
|
+
type: Output
|
|
5186
5200
|
}] } });
|
|
5187
5201
|
|
|
5188
5202
|
/**
|
|
5189
|
-
* The `
|
|
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
|
|
5192
|
-
constructor(
|
|
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.
|
|
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.
|
|
5198
|
-
this.
|
|
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
|
-
*
|
|
5223
|
+
* Enables or disables auto focus on the textarea element
|
|
5210
5224
|
*/
|
|
5211
|
-
this.
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
this.
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
this.
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
this.
|
|
5226
|
-
this.
|
|
5227
|
-
this.
|
|
5228
|
-
this.
|
|
5229
|
-
this.
|
|
5230
|
-
|
|
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.
|
|
5238
|
-
this.
|
|
5239
|
-
this.
|
|
5240
|
-
this.
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
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.
|
|
5264
|
-
if (this.
|
|
5265
|
-
|
|
5266
|
-
|
|
5267
|
-
|
|
5268
|
-
|
|
5269
|
-
|
|
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
|
-
|
|
5276
|
-
|
|
5277
|
-
this.
|
|
5278
|
-
this.
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
this.
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
|
|
5285
|
-
this.
|
|
5286
|
-
this.
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
|
|
5291
|
-
|
|
5292
|
-
|
|
5293
|
-
|
|
5294
|
-
|
|
5295
|
-
|
|
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.
|
|
5318
|
-
this.
|
|
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.
|
|
5327
|
-
this.
|
|
5328
|
-
|
|
5339
|
+
if (changes.isMultipleFileUploadEnabled) {
|
|
5340
|
+
this.configService.isMultipleFileUploadEnabled =
|
|
5341
|
+
this.isMultipleFileUploadEnabled;
|
|
5329
5342
|
}
|
|
5330
|
-
if (changes.
|
|
5331
|
-
this.
|
|
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.
|
|
5345
|
-
|
|
5346
|
-
|
|
5347
|
-
|
|
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
|
-
|
|
5350
|
-
this.
|
|
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
|
-
|
|
5361
|
-
if (
|
|
5362
|
-
event.button !== 0 ||
|
|
5363
|
-
!this.areOptionsVisible) {
|
|
5374
|
+
async messageSent() {
|
|
5375
|
+
if (this.isCooldownInProgress) {
|
|
5364
5376
|
return;
|
|
5365
5377
|
}
|
|
5366
|
-
|
|
5367
|
-
|
|
5368
|
-
|
|
5369
|
-
|
|
5370
|
-
|
|
5371
|
-
|
|
5372
|
-
|
|
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.
|
|
5376
|
-
|
|
5377
|
-
|
|
5378
|
-
|
|
5379
|
-
|
|
5380
|
-
|
|
5381
|
-
|
|
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 (
|
|
5385
|
-
|
|
5386
|
-
this.shouldPreventMessageMenuClose = false;
|
|
5387
|
-
}
|
|
5388
|
-
else if (this.areMessageOptionsOpen) {
|
|
5389
|
-
this.messageMenuTrigger?.hide();
|
|
5398
|
+
if (textContainsOnlySpaceChars) {
|
|
5399
|
+
text = '';
|
|
5390
5400
|
}
|
|
5391
|
-
|
|
5392
|
-
|
|
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.
|
|
5397
|
-
this.
|
|
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
|
-
|
|
5410
|
-
|
|
5411
|
-
|
|
5412
|
-
|
|
5413
|
-
|
|
5414
|
-
|
|
5415
|
-
|
|
5416
|
-
|
|
5417
|
-
|
|
5418
|
-
|
|
5419
|
-
|
|
5420
|
-
|
|
5421
|
-
|
|
5422
|
-
|
|
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
|
-
|
|
5452
|
-
this.
|
|
5453
|
-
|
|
5424
|
+
catch (error) {
|
|
5425
|
+
if (this.isUpdate) {
|
|
5426
|
+
this.notificationService.addTemporaryNotification('streamChat.Edit message request failed');
|
|
5427
|
+
}
|
|
5454
5428
|
}
|
|
5455
|
-
|
|
5456
|
-
|
|
5429
|
+
void this.channelService.typingStopped(this.parentMessageId);
|
|
5430
|
+
if (this.quotedMessage) {
|
|
5431
|
+
this.deselectMessageToQuote();
|
|
5457
5432
|
}
|
|
5458
5433
|
}
|
|
5459
|
-
|
|
5460
|
-
|
|
5434
|
+
get containsLinks() {
|
|
5435
|
+
return /(?:(?:https?|ftp):\/\/)?[\w/\-?=%.]+\.[\w/\-&?=%.]+/.test(this.textareaValue);
|
|
5461
5436
|
}
|
|
5462
|
-
|
|
5463
|
-
|
|
5437
|
+
get quotedMessageAttachments() {
|
|
5438
|
+
const originalAttachments = this.quotedMessage?.attachments;
|
|
5439
|
+
return originalAttachments && originalAttachments.length
|
|
5440
|
+
? [originalAttachments[0]]
|
|
5441
|
+
: [];
|
|
5464
5442
|
}
|
|
5465
|
-
|
|
5466
|
-
|
|
5467
|
-
|
|
5468
|
-
|
|
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
|
-
|
|
5472
|
-
|
|
5473
|
-
|
|
5474
|
-
|
|
5475
|
-
|
|
5476
|
-
|
|
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
|
-
|
|
5480
|
-
|
|
5481
|
-
message: this.message,
|
|
5482
|
-
};
|
|
5458
|
+
deselectMessageToQuote() {
|
|
5459
|
+
this.channelService.selectMessageToQuote(undefined);
|
|
5483
5460
|
}
|
|
5484
|
-
|
|
5461
|
+
deselectMessageToEdit() {
|
|
5462
|
+
this.messageActionsService.messageToEdit$.next(undefined);
|
|
5463
|
+
}
|
|
5464
|
+
getEmojiPickerContext() {
|
|
5485
5465
|
return {
|
|
5486
|
-
|
|
5466
|
+
emojiInput$: this.emojiInputService.emojiInput$,
|
|
5487
5467
|
};
|
|
5488
5468
|
}
|
|
5489
|
-
|
|
5469
|
+
getAttachmentPreviewListContext() {
|
|
5490
5470
|
return {
|
|
5491
|
-
|
|
5492
|
-
|
|
5471
|
+
attachmentUploads$: this.attachmentService.attachmentUploads$,
|
|
5472
|
+
deleteUploadHandler: this.deleteUpload.bind(this),
|
|
5473
|
+
retryUploadHandler: this.retryUpload.bind(this),
|
|
5493
5474
|
};
|
|
5494
5475
|
}
|
|
5495
|
-
|
|
5476
|
+
getAttachmentUploadContext() {
|
|
5496
5477
|
return {
|
|
5497
|
-
|
|
5478
|
+
isMultipleFileUploadEnabled: this.isMultipleFileUploadEnabled,
|
|
5479
|
+
attachmentService: this.attachmentService,
|
|
5498
5480
|
};
|
|
5499
5481
|
}
|
|
5500
|
-
|
|
5501
|
-
|
|
5482
|
+
get isUpdate() {
|
|
5483
|
+
return !!this.message;
|
|
5502
5484
|
}
|
|
5503
|
-
|
|
5504
|
-
this.
|
|
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
|
-
|
|
5507
|
-
this.
|
|
5496
|
+
retryUpload(file) {
|
|
5497
|
+
void this.attachmentService.retryAttachmentUpload(file);
|
|
5508
5498
|
}
|
|
5509
|
-
|
|
5510
|
-
this.
|
|
5499
|
+
clearFileInput() {
|
|
5500
|
+
this.fileInput.nativeElement.value = '';
|
|
5511
5501
|
}
|
|
5512
|
-
|
|
5513
|
-
|
|
5514
|
-
this.
|
|
5515
|
-
|
|
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 (!
|
|
5507
|
+
if (!this.canSendMessages || this.textareaRef || !this.textareaAnchor) {
|
|
5525
5508
|
return;
|
|
5526
5509
|
}
|
|
5527
|
-
|
|
5528
|
-
|
|
5529
|
-
|
|
5530
|
-
|
|
5531
|
-
|
|
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
|
-
|
|
5534
|
-
this.
|
|
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
|
-
|
|
5560
|
-
|
|
5561
|
-
|
|
5562
|
-
|
|
5563
|
-
|
|
5564
|
-
|
|
5565
|
-
|
|
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
|
-
|
|
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.
|
|
5571
|
-
|
|
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
|
-
|
|
5575
|
-
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
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
|
-
|
|
5588
|
-
? this.messageService.customLinkRenderer(match)
|
|
5589
|
-
: `<a href="${match}" target="_blank" rel="nofollow">${match}</a>`);
|
|
5590
|
-
return content;
|
|
5585
|
+
return parentMessageId;
|
|
5591
5586
|
}
|
|
5592
|
-
|
|
5593
|
-
this.
|
|
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
|
-
|
|
5596
|
-
this.
|
|
5596
|
+
stopCooldown() {
|
|
5597
|
+
this.cooldown$ = undefined;
|
|
5598
|
+
this.isCooldownInProgress = false;
|
|
5599
|
+
this.textareaPlaceholder = this.defaultTextareaPlaceholder;
|
|
5597
5600
|
}
|
|
5598
|
-
|
|
5599
|
-
this.
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
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
|
-
|
|
5622
|
-
|
|
5623
|
-
|
|
5624
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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
|
-
}],
|
|
5638
|
+
}], areMentionsEnabled: [{
|
|
5636
5639
|
type: Input
|
|
5637
|
-
}],
|
|
5640
|
+
}], mentionScope: [{
|
|
5638
5641
|
type: Input
|
|
5639
5642
|
}], mode: [{
|
|
5640
5643
|
type: Input
|
|
5641
|
-
}],
|
|
5644
|
+
}], isMultipleFileUploadEnabled: [{
|
|
5642
5645
|
type: Input
|
|
5643
|
-
}],
|
|
5644
|
-
type:
|
|
5645
|
-
|
|
5646
|
-
|
|
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: ['
|
|
5649
|
-
}],
|
|
5664
|
+
args: ['fileInput']
|
|
5665
|
+
}], textareaAnchor: [{
|
|
5650
5666
|
type: ViewChild,
|
|
5651
|
-
args: [
|
|
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,
|
|
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
|