stream-chat-angular 5.0.0 → 5.1.1
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/avatar/avatar.component.mjs +4 -1
- package/esm2020/lib/channel-list/channel-list.component.mjs +2 -2
- package/esm2020/lib/channel.service.mjs +10 -26
- package/esm2020/lib/chat-client.service.mjs +2 -3
- package/esm2020/lib/message-actions.service.mjs +4 -4
- package/esm2020/lib/message-list/message-list.component.mjs +181 -249
- package/esm2020/lib/types.mjs +1 -1
- package/esm2020/lib/virtualized-list.service.mjs +271 -0
- package/esm2020/lib/virtualized-message-list.service.mjs +73 -0
- package/esm2020/lib/voice-recording/voice-recording.component.mjs +2 -2
- package/esm2020/public-api.mjs +3 -1
- package/fesm2015/stream-chat-angular.mjs +550 -288
- package/fesm2015/stream-chat-angular.mjs.map +1 -1
- package/fesm2020/stream-chat-angular.mjs +536 -277
- package/fesm2020/stream-chat-angular.mjs.map +1 -1
- package/lib/avatar/avatar.component.d.ts +3 -2
- package/lib/channel-list/channel-list.component.d.ts +1 -1
- package/lib/channel.service.d.ts +6 -12
- package/lib/message-list/message-list.component.d.ts +12 -18
- package/lib/types.d.ts +7 -0
- package/lib/virtualized-list.service.d.ts +58 -0
- package/lib/virtualized-message-list.service.d.ts +15 -0
- package/package.json +1 -1
- package/public-api.d.ts +2 -0
- package/src/assets/styles/css/index.css +1 -1
- package/src/assets/styles/css/index.layout.css +1 -1
- package/src/assets/styles/scss/LoadingIndicator/LoadingIndicator-layout.scss +16 -0
- package/src/assets/version.ts +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
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';
|
|
3
|
+
import { BehaviorSubject, ReplaySubject, combineLatest, take as take$1, Subject, timer, merge, switchMap, distinctUntilChanged, pairwise, filter as filter$1, of, map as map$1 } 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';
|
|
@@ -19,7 +19,7 @@ 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.
|
|
22
|
+
const version = '5.1.1';
|
|
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.
|
|
@@ -241,9 +241,8 @@ class ChatClientService {
|
|
|
241
241
|
{ id: { $autocomplete: searchTerm } },
|
|
242
242
|
{ name: { $autocomplete: searchTerm } },
|
|
243
243
|
],
|
|
244
|
-
id: { $ne: this.chatClient.userID },
|
|
245
244
|
}); // TODO: find out why we need this typecast
|
|
246
|
-
return result.users;
|
|
245
|
+
return result.users.filter((u) => u.id !== this.chatClient?.user?.id);
|
|
247
246
|
}
|
|
248
247
|
updatePendingInvites(e) {
|
|
249
248
|
if (!this.trackPendingChannelInvites) {
|
|
@@ -447,6 +446,7 @@ class ChannelService {
|
|
|
447
446
|
this.chatClientService = chatClientService;
|
|
448
447
|
this.ngZone = ngZone;
|
|
449
448
|
this.notificationService = notificationService;
|
|
449
|
+
this.messagePageSize = 25;
|
|
450
450
|
this.channelsSubject = new BehaviorSubject(undefined);
|
|
451
451
|
this.activeChannelSubject = new BehaviorSubject(undefined);
|
|
452
452
|
this.activeChannelMessagesSubject = new BehaviorSubject([]);
|
|
@@ -458,7 +458,6 @@ class ChannelService {
|
|
|
458
458
|
this.activeThreadMessagesSubject = new BehaviorSubject([]);
|
|
459
459
|
this.jumpToMessageSubject = new BehaviorSubject({ id: undefined, parentId: undefined });
|
|
460
460
|
this.latestMessageDateByUserByChannelsSubject = new BehaviorSubject({});
|
|
461
|
-
this.messagePageSize = 25;
|
|
462
461
|
this.attachmentMaxSizeFallbackInMB = 100;
|
|
463
462
|
this.messageToQuoteSubject = new BehaviorSubject(undefined);
|
|
464
463
|
this.usersTypingInChannelSubject = new BehaviorSubject([]);
|
|
@@ -567,23 +566,6 @@ class ChannelService {
|
|
|
567
566
|
.asObservable()
|
|
568
567
|
.pipe(shareReplay(1));
|
|
569
568
|
}
|
|
570
|
-
/**
|
|
571
|
-
* internal
|
|
572
|
-
*/
|
|
573
|
-
removeOldMessageFromMessageList() {
|
|
574
|
-
const channel = this.activeChannelSubject.getValue();
|
|
575
|
-
const channelMessages = channel?.state.latestMessages;
|
|
576
|
-
const targetLength = Math.ceil(ChannelService.MAX_MESSAGE_COUNT_IN_MESSAGE_LIST / 2);
|
|
577
|
-
if (!channel ||
|
|
578
|
-
!channelMessages ||
|
|
579
|
-
channelMessages !== channel?.state.latestMessages ||
|
|
580
|
-
channelMessages.length <= targetLength) {
|
|
581
|
-
return;
|
|
582
|
-
}
|
|
583
|
-
const messages = channelMessages;
|
|
584
|
-
messages.splice(0, messages.length - targetLength);
|
|
585
|
-
this.activeChannelMessagesSubject.next(messages);
|
|
586
|
-
}
|
|
587
569
|
/**
|
|
588
570
|
* If set to false, read events won't be sent as new messages are received. If set to true active channel (if any) will immediately be marked as read.
|
|
589
571
|
*/
|
|
@@ -1030,9 +1012,8 @@ class ChannelService {
|
|
|
1030
1012
|
}
|
|
1031
1013
|
const result = await activeChannel.queryMembers({
|
|
1032
1014
|
name: { $autocomplete: searchTerm },
|
|
1033
|
-
id: { $ne: this.chatClientService.chatClient.userID },
|
|
1034
1015
|
}); // TODO: find out why we need typecast here
|
|
1035
|
-
return
|
|
1016
|
+
return result.members.filter((m) => m.user_id !== this.chatClientService.chatClient?.user?.id);
|
|
1036
1017
|
}
|
|
1037
1018
|
}
|
|
1038
1019
|
/**
|
|
@@ -1466,6 +1447,12 @@ class ChannelService {
|
|
|
1466
1447
|
get activeChannelMessages() {
|
|
1467
1448
|
return this.activeChannelMessagesSubject.getValue() || [];
|
|
1468
1449
|
}
|
|
1450
|
+
/**
|
|
1451
|
+
* The current thread replies
|
|
1452
|
+
*/
|
|
1453
|
+
get activeChannelThreadReplies() {
|
|
1454
|
+
return this.activeThreadMessagesSubject.getValue() || [];
|
|
1455
|
+
}
|
|
1469
1456
|
/**
|
|
1470
1457
|
* Get the last 1200 reactions of a message in the current active channel. If you need to fetch more reactions please use the [following endpoint](https://getstream.io/chat/docs/javascript/send_reaction/?language=javascript#paginating-reactions).
|
|
1471
1458
|
* @param messageId
|
|
@@ -1547,7 +1534,7 @@ class ChannelService {
|
|
|
1547
1534
|
return;
|
|
1548
1535
|
}
|
|
1549
1536
|
const messageIndex = messages.findIndex((m) => m.id === event?.message?.id);
|
|
1550
|
-
if (messageIndex !== -1) {
|
|
1537
|
+
if (messageIndex !== -1 || event.type === 'message.deleted') {
|
|
1551
1538
|
isThreadReply
|
|
1552
1539
|
? this.activeThreadMessagesSubject.next([...messages])
|
|
1553
1540
|
: this.activeChannelMessagesSubject.next([...messages]);
|
|
@@ -1928,10 +1915,6 @@ class ChannelService {
|
|
|
1928
1915
|
}
|
|
1929
1916
|
}
|
|
1930
1917
|
}
|
|
1931
|
-
/**
|
|
1932
|
-
* @internal
|
|
1933
|
-
*/
|
|
1934
|
-
ChannelService.MAX_MESSAGE_COUNT_IN_MESSAGE_LIST = 250;
|
|
1935
1918
|
/**
|
|
1936
1919
|
* @internal
|
|
1937
1920
|
*/
|
|
@@ -2692,6 +2675,9 @@ class AvatarComponent {
|
|
|
2692
2675
|
this.setFallbackChannelImage();
|
|
2693
2676
|
}
|
|
2694
2677
|
}
|
|
2678
|
+
ngOnDestroy() {
|
|
2679
|
+
this.subscriptions.forEach((s) => s.unsubscribe());
|
|
2680
|
+
}
|
|
2695
2681
|
setFallbackChannelImage() {
|
|
2696
2682
|
if (this.type !== 'channel') {
|
|
2697
2683
|
this.fallbackChannelImage = undefined;
|
|
@@ -3166,7 +3152,7 @@ class MessageActionsService {
|
|
|
3166
3152
|
actionHandler: (message) => {
|
|
3167
3153
|
void this.channelService.markMessageUnread(message.id);
|
|
3168
3154
|
},
|
|
3169
|
-
isVisible: (enabledActions,
|
|
3155
|
+
isVisible: (enabledActions, _, message) => enabledActions.indexOf('read-events') !== -1 && !message.parent_id,
|
|
3170
3156
|
},
|
|
3171
3157
|
{
|
|
3172
3158
|
actionName: 'quote',
|
|
@@ -3182,7 +3168,7 @@ class MessageActionsService {
|
|
|
3182
3168
|
actionHandler: (message) => {
|
|
3183
3169
|
void this.channelService.setAsActiveParentMessage(message);
|
|
3184
3170
|
},
|
|
3185
|
-
isVisible: (enabledActions,
|
|
3171
|
+
isVisible: (enabledActions, _, message) => enabledActions.indexOf('send-reply') !== -1 && !message.parent_id,
|
|
3186
3172
|
},
|
|
3187
3173
|
{
|
|
3188
3174
|
actionName: 'pin',
|
|
@@ -3203,7 +3189,7 @@ class MessageActionsService {
|
|
|
3203
3189
|
await this.chatClientService.flagMessage(message.id);
|
|
3204
3190
|
this.notificationService.addTemporaryNotification('streamChat.Message has been successfully flagged', 'success');
|
|
3205
3191
|
}
|
|
3206
|
-
catch (
|
|
3192
|
+
catch (error) {
|
|
3207
3193
|
this.notificationService.addTemporaryNotification('streamChat.Error adding flag');
|
|
3208
3194
|
}
|
|
3209
3195
|
},
|
|
@@ -4097,7 +4083,7 @@ class ChannelListComponent {
|
|
|
4097
4083
|
await this.channelService.loadMoreChannels();
|
|
4098
4084
|
this.isLoadingMoreChannels = false;
|
|
4099
4085
|
}
|
|
4100
|
-
trackByChannelId(
|
|
4086
|
+
trackByChannelId(_, item) {
|
|
4101
4087
|
return item.cid;
|
|
4102
4088
|
}
|
|
4103
4089
|
}
|
|
@@ -4337,7 +4323,7 @@ class VoiceRecordingComponent {
|
|
|
4337
4323
|
: this.audioElement.nativeElement.pause();
|
|
4338
4324
|
this.isError = false;
|
|
4339
4325
|
}
|
|
4340
|
-
catch (
|
|
4326
|
+
catch (error) {
|
|
4341
4327
|
this.isError = true;
|
|
4342
4328
|
}
|
|
4343
4329
|
}
|
|
@@ -6271,6 +6257,347 @@ const isOnSameDay = (date1, date2) => {
|
|
|
6271
6257
|
date1.getDate() === date2.getDate());
|
|
6272
6258
|
};
|
|
6273
6259
|
|
|
6260
|
+
/**
|
|
6261
|
+
* The `VirtualizedListService` removes items from a list that are not currently displayed. This is a high-level overview of how it works:
|
|
6262
|
+
* - Create a new instance for each list that needs virtualization
|
|
6263
|
+
* - Input: Provide a reactive stream that emits all items in the list
|
|
6264
|
+
* - Input: Provide a reactive stream that emit the current scroll position (top, middle or bottom)
|
|
6265
|
+
* - Input: maximum number of items that are allowed in the list (in practice the service can make the virtualized list half this number, you should take this into account when choosing the value)
|
|
6266
|
+
* - Output: The service will emit the current list of displayed items via the virtualized items reactive stream
|
|
6267
|
+
* - For simplicity, the service won't track the height of the items, nor it needs an exact scroll location -> this is how removing items work:
|
|
6268
|
+
* - If scroll location is bottom/top items around the current bottom/top item will be emitted in the virtualized items stream
|
|
6269
|
+
* - If scroll location is middle, the service won't remove items, if new items are received, those will be appended to the virtualized list (this means that in theory the list can grow very big if a lot of new items are received while the user is scrolled somewhere, this is a trade-off for the simplicity of no height tracking)
|
|
6270
|
+
* - Since there is no height tracking, you should make sure to provide a maximum number that is big enough to fill the biggest expected screen size twice
|
|
6271
|
+
* - If the user scrolls to the bottom/top and there are no more local items to show, the service will trigger a query to load more items
|
|
6272
|
+
* - Input: you should provide the page size to use, in order for the service to determine if loading is necessary
|
|
6273
|
+
*
|
|
6274
|
+
* The `VirtualizedMessageListService` provides an implementation for the message list component.
|
|
6275
|
+
*/
|
|
6276
|
+
class VirtualizedListService {
|
|
6277
|
+
constructor(allItems$, scrollPosition$, jumpToItem$, pageSize = 25, maxItemCount = pageSize * 4) {
|
|
6278
|
+
this.allItems$ = allItems$;
|
|
6279
|
+
this.scrollPosition$ = scrollPosition$;
|
|
6280
|
+
this.jumpToItem$ = jumpToItem$;
|
|
6281
|
+
this.pageSize = pageSize;
|
|
6282
|
+
this.maxItemCount = maxItemCount;
|
|
6283
|
+
this.queryStateSubject = new BehaviorSubject({
|
|
6284
|
+
state: 'success',
|
|
6285
|
+
});
|
|
6286
|
+
this.bufferOnTop = 0;
|
|
6287
|
+
this.bufferOnBottom = 0;
|
|
6288
|
+
this.loadFromBuffer$ = new Subject();
|
|
6289
|
+
this.virtualizedItemsSubject = new BehaviorSubject([]);
|
|
6290
|
+
this.subscriptions = [];
|
|
6291
|
+
this.virtualizedItems$ = this.virtualizedItemsSubject.asObservable();
|
|
6292
|
+
this.queryState$ = this.queryStateSubject.asObservable();
|
|
6293
|
+
this.subscriptions.push(this.virtualizedItems$.subscribe((virtaluzedItems) => {
|
|
6294
|
+
this.allItems$.pipe(take$1(1)).subscribe((allItems) => {
|
|
6295
|
+
if (virtaluzedItems.length === allItems.length) {
|
|
6296
|
+
this.bufferOnTop = 0;
|
|
6297
|
+
this.bufferOnBottom = 0;
|
|
6298
|
+
}
|
|
6299
|
+
else if (virtaluzedItems.length === 0) {
|
|
6300
|
+
this.bufferOnTop = allItems.length;
|
|
6301
|
+
this.bufferOnBottom = 0;
|
|
6302
|
+
}
|
|
6303
|
+
else {
|
|
6304
|
+
this.bufferOnTop = allItems.indexOf(virtaluzedItems[0]);
|
|
6305
|
+
this.bufferOnBottom =
|
|
6306
|
+
allItems.length -
|
|
6307
|
+
allItems.indexOf(virtaluzedItems[virtaluzedItems.length - 1]) -
|
|
6308
|
+
1;
|
|
6309
|
+
}
|
|
6310
|
+
});
|
|
6311
|
+
}));
|
|
6312
|
+
this.subscriptions.push(merge(this.allItems$, this.loadFromBuffer$)
|
|
6313
|
+
.pipe(switchMap(() => {
|
|
6314
|
+
return combineLatest([
|
|
6315
|
+
this.allItems$.pipe(take$1(1)),
|
|
6316
|
+
this.scrollPosition$.pipe(take$1(1)),
|
|
6317
|
+
]);
|
|
6318
|
+
}))
|
|
6319
|
+
.subscribe(([items, scrollPosition]) => {
|
|
6320
|
+
if (scrollPosition === 'middle') {
|
|
6321
|
+
return;
|
|
6322
|
+
}
|
|
6323
|
+
const currentItems = this.virtualizedItemsSubject.getValue();
|
|
6324
|
+
if (items.length <= this.maxItemCount) {
|
|
6325
|
+
this.virtualizedItemsSubject.next(items);
|
|
6326
|
+
}
|
|
6327
|
+
else {
|
|
6328
|
+
let startIndex = 0;
|
|
6329
|
+
let endIndex = undefined;
|
|
6330
|
+
const numberOfItemsToRemove = items.length - Math.round(this.maxItemCount / 2);
|
|
6331
|
+
const numberOfItemsAfterRemove = items.length - numberOfItemsToRemove;
|
|
6332
|
+
switch (scrollPosition) {
|
|
6333
|
+
case 'top':
|
|
6334
|
+
if (currentItems.length > 0) {
|
|
6335
|
+
const middleIndex = items.findIndex((i) => this.isEqual(i, currentItems[0]));
|
|
6336
|
+
if (middleIndex !== -1) {
|
|
6337
|
+
startIndex = Math.max(0, middleIndex - Math.ceil(numberOfItemsAfterRemove / 2));
|
|
6338
|
+
endIndex = startIndex + numberOfItemsAfterRemove;
|
|
6339
|
+
}
|
|
6340
|
+
}
|
|
6341
|
+
else {
|
|
6342
|
+
endIndex = numberOfItemsAfterRemove;
|
|
6343
|
+
}
|
|
6344
|
+
break;
|
|
6345
|
+
case 'bottom':
|
|
6346
|
+
if (currentItems.length > 0) {
|
|
6347
|
+
const middleIndex = items.findIndex((i) => this.isEqual(i, currentItems[currentItems.length - 1]));
|
|
6348
|
+
if (middleIndex !== -1) {
|
|
6349
|
+
endIndex = Math.min(items.length, middleIndex + Math.floor(numberOfItemsAfterRemove / 2) + 1);
|
|
6350
|
+
startIndex = endIndex - numberOfItemsAfterRemove;
|
|
6351
|
+
}
|
|
6352
|
+
}
|
|
6353
|
+
else {
|
|
6354
|
+
startIndex = items.length - numberOfItemsAfterRemove;
|
|
6355
|
+
}
|
|
6356
|
+
break;
|
|
6357
|
+
}
|
|
6358
|
+
const virtualizedItems = items.slice(startIndex, endIndex);
|
|
6359
|
+
this.virtualizedItemsSubject.next(virtualizedItems);
|
|
6360
|
+
}
|
|
6361
|
+
}));
|
|
6362
|
+
this.subscriptions.push(this.scrollPosition$
|
|
6363
|
+
.pipe(distinctUntilChanged())
|
|
6364
|
+
.subscribe((position) => {
|
|
6365
|
+
if (this.queryStateSubject.getValue().state === `loading-${position}`) {
|
|
6366
|
+
return;
|
|
6367
|
+
}
|
|
6368
|
+
if (position === 'top') {
|
|
6369
|
+
if (this.bufferOnTop < this.pageSize) {
|
|
6370
|
+
void this.loadMore(position);
|
|
6371
|
+
}
|
|
6372
|
+
else {
|
|
6373
|
+
this.loadMoreFromBuffer('top');
|
|
6374
|
+
}
|
|
6375
|
+
}
|
|
6376
|
+
else if (position === 'bottom') {
|
|
6377
|
+
if (this.bufferOnBottom < this.pageSize) {
|
|
6378
|
+
void this.loadMore(position);
|
|
6379
|
+
}
|
|
6380
|
+
else {
|
|
6381
|
+
this.loadMoreFromBuffer('bottom');
|
|
6382
|
+
}
|
|
6383
|
+
}
|
|
6384
|
+
}));
|
|
6385
|
+
this.subscriptions.push(this.allItems$
|
|
6386
|
+
.pipe(pairwise(), filter$1(() => {
|
|
6387
|
+
let scrollPosition;
|
|
6388
|
+
this.scrollPosition$
|
|
6389
|
+
.pipe(take$1(1))
|
|
6390
|
+
.subscribe((s) => (scrollPosition = s));
|
|
6391
|
+
return scrollPosition === 'middle';
|
|
6392
|
+
}))
|
|
6393
|
+
.subscribe(([prevItems, currentItems]) => {
|
|
6394
|
+
if (currentItems.length < this.maxItemCount ||
|
|
6395
|
+
this.virtualizedItems.length === 0) {
|
|
6396
|
+
this.virtualizedItemsSubject.next(currentItems);
|
|
6397
|
+
}
|
|
6398
|
+
else {
|
|
6399
|
+
const currentFirstItem = this.virtualizedItems[0];
|
|
6400
|
+
const currentLastItem = this.virtualizedItems[this.virtualizedItems.length - 1];
|
|
6401
|
+
const prevStartIndex = prevItems.findIndex((i) => this.isEqual(i, currentFirstItem));
|
|
6402
|
+
const prevEndIndex = prevItems.findIndex((i) => this.isEqual(i, currentLastItem));
|
|
6403
|
+
const isStartRemainedSame = currentItems[prevStartIndex]
|
|
6404
|
+
? this.isEqual(currentItems[prevStartIndex], currentFirstItem)
|
|
6405
|
+
: false;
|
|
6406
|
+
const isEndRemainedSame = currentItems[prevEndIndex]
|
|
6407
|
+
? this.isEqual(currentItems[prevEndIndex], currentLastItem)
|
|
6408
|
+
: false;
|
|
6409
|
+
const hasNewItemsBottom = prevEndIndex === prevItems.length - 1 && isEndRemainedSame
|
|
6410
|
+
? prevItems.length !== currentItems.length
|
|
6411
|
+
: false;
|
|
6412
|
+
if (isStartRemainedSame && isEndRemainedSame) {
|
|
6413
|
+
const endIndex = hasNewItemsBottom ? undefined : prevEndIndex + 1;
|
|
6414
|
+
this.virtualizedItemsSubject.next(currentItems.slice(prevStartIndex, endIndex));
|
|
6415
|
+
}
|
|
6416
|
+
let currentStartIndex = isStartRemainedSame ? prevStartIndex : -1;
|
|
6417
|
+
let currentEndIndex = isEndRemainedSame ? prevEndIndex : -1;
|
|
6418
|
+
if (!isStartRemainedSame) {
|
|
6419
|
+
currentStartIndex = currentItems.findIndex((i) => this.isEqual(i, currentFirstItem));
|
|
6420
|
+
}
|
|
6421
|
+
if (!isEndRemainedSame) {
|
|
6422
|
+
currentEndIndex = currentItems.findIndex((i) => this.isEqual(i, currentLastItem));
|
|
6423
|
+
}
|
|
6424
|
+
const hasNewItemsTop = prevStartIndex === 0 && !isStartRemainedSame
|
|
6425
|
+
? currentStartIndex !== 0
|
|
6426
|
+
: false;
|
|
6427
|
+
if (currentStartIndex !== -1 && currentEndIndex !== -1) {
|
|
6428
|
+
const startIndex = hasNewItemsTop ? 0 : currentStartIndex;
|
|
6429
|
+
this.virtualizedItemsSubject.next(currentItems.slice(startIndex, currentEndIndex + 1));
|
|
6430
|
+
}
|
|
6431
|
+
else {
|
|
6432
|
+
if (currentStartIndex === -1 && currentEndIndex !== -1) {
|
|
6433
|
+
currentStartIndex = Math.max(0, currentEndIndex - (prevEndIndex - prevStartIndex));
|
|
6434
|
+
}
|
|
6435
|
+
if (currentEndIndex === -1 && currentStartIndex !== -1) {
|
|
6436
|
+
currentEndIndex = Math.min(currentItems.length - 1, currentStartIndex + (prevEndIndex - prevStartIndex));
|
|
6437
|
+
}
|
|
6438
|
+
this.virtualizedItemsSubject.next(currentItems.slice(currentStartIndex, currentEndIndex + 1));
|
|
6439
|
+
}
|
|
6440
|
+
}
|
|
6441
|
+
}));
|
|
6442
|
+
if (this.jumpToItem$) {
|
|
6443
|
+
this.subscriptions.push(this.jumpToItem$
|
|
6444
|
+
.pipe(switchMap((jumpToItem) => combineLatest([this.allItems$.pipe(take$1(1)), of(jumpToItem)])))
|
|
6445
|
+
.subscribe(([allItems, jumpToItem]) => {
|
|
6446
|
+
if (jumpToItem.item) {
|
|
6447
|
+
if (allItems.length < this.maxItemCount) {
|
|
6448
|
+
this.virtualizedItemsSubject.next(allItems);
|
|
6449
|
+
}
|
|
6450
|
+
else {
|
|
6451
|
+
const itemIndex = allItems.findIndex((i) =>
|
|
6452
|
+
// @ts-expect-error TODO: do we know a better typing here?
|
|
6453
|
+
this.isEqual(i, jumpToItem.item));
|
|
6454
|
+
if (itemIndex === -1) {
|
|
6455
|
+
return;
|
|
6456
|
+
}
|
|
6457
|
+
else {
|
|
6458
|
+
const position = jumpToItem.position || 'middle';
|
|
6459
|
+
const numberOfItemsToRemove = allItems.length - Math.round(this.maxItemCount / 2);
|
|
6460
|
+
const numberOfItemsAfterRemove = allItems.length - numberOfItemsToRemove;
|
|
6461
|
+
let startIndex = -1;
|
|
6462
|
+
let endIndex = -1;
|
|
6463
|
+
switch (position) {
|
|
6464
|
+
case 'top':
|
|
6465
|
+
startIndex = itemIndex;
|
|
6466
|
+
endIndex = Math.min(allItems.length, startIndex + numberOfItemsAfterRemove);
|
|
6467
|
+
break;
|
|
6468
|
+
case 'bottom':
|
|
6469
|
+
endIndex = itemIndex + 1;
|
|
6470
|
+
startIndex = Math.max(0, endIndex - numberOfItemsAfterRemove);
|
|
6471
|
+
break;
|
|
6472
|
+
case 'middle': {
|
|
6473
|
+
const itemsOnTop = itemIndex;
|
|
6474
|
+
const itemsOnBottom = allItems.length - itemIndex;
|
|
6475
|
+
if (itemsOnTop < Math.ceil(numberOfItemsAfterRemove / 2)) {
|
|
6476
|
+
startIndex = 0;
|
|
6477
|
+
}
|
|
6478
|
+
if (itemsOnBottom <
|
|
6479
|
+
Math.floor(numberOfItemsAfterRemove / 2) + 1) {
|
|
6480
|
+
endIndex = allItems.length;
|
|
6481
|
+
}
|
|
6482
|
+
if (startIndex === -1) {
|
|
6483
|
+
if (endIndex !== -1) {
|
|
6484
|
+
startIndex = endIndex - numberOfItemsAfterRemove;
|
|
6485
|
+
}
|
|
6486
|
+
else {
|
|
6487
|
+
startIndex =
|
|
6488
|
+
itemIndex - Math.ceil(numberOfItemsAfterRemove / 2);
|
|
6489
|
+
}
|
|
6490
|
+
}
|
|
6491
|
+
if (endIndex === -1) {
|
|
6492
|
+
endIndex = startIndex + numberOfItemsAfterRemove;
|
|
6493
|
+
}
|
|
6494
|
+
}
|
|
6495
|
+
}
|
|
6496
|
+
this.virtualizedItemsSubject.next(allItems.slice(startIndex, endIndex));
|
|
6497
|
+
}
|
|
6498
|
+
}
|
|
6499
|
+
}
|
|
6500
|
+
}));
|
|
6501
|
+
}
|
|
6502
|
+
}
|
|
6503
|
+
/**
|
|
6504
|
+
* The current value of virtualized items
|
|
6505
|
+
*/
|
|
6506
|
+
get virtualizedItems() {
|
|
6507
|
+
return this.virtualizedItemsSubject.getValue();
|
|
6508
|
+
}
|
|
6509
|
+
/**
|
|
6510
|
+
* Remove all subscriptions, call this once you're done using an instance of this service
|
|
6511
|
+
*/
|
|
6512
|
+
dispose() {
|
|
6513
|
+
this.subscriptions.forEach((s) => s.unsubscribe());
|
|
6514
|
+
}
|
|
6515
|
+
loadMoreFromBuffer(_) {
|
|
6516
|
+
this.loadFromBuffer$.next();
|
|
6517
|
+
}
|
|
6518
|
+
async loadMore(direction) {
|
|
6519
|
+
this.queryStateSubject.next({ state: `loading-${direction}` });
|
|
6520
|
+
try {
|
|
6521
|
+
await this.query(direction);
|
|
6522
|
+
this.queryStateSubject.next({ state: 'success' });
|
|
6523
|
+
}
|
|
6524
|
+
catch (e) {
|
|
6525
|
+
this.queryStateSubject.next({ state: 'error', error: e });
|
|
6526
|
+
}
|
|
6527
|
+
}
|
|
6528
|
+
}
|
|
6529
|
+
|
|
6530
|
+
/**
|
|
6531
|
+
* The `VirtualizedMessageListService` removes messages from the message list that are currently not in view
|
|
6532
|
+
*/
|
|
6533
|
+
class VirtualizedMessageListService extends VirtualizedListService {
|
|
6534
|
+
constructor(mode, scrollPosition$, channelService) {
|
|
6535
|
+
const jumpToMessage$ = channelService.jumpToMessage$.pipe(map$1((jumpToMessage) => {
|
|
6536
|
+
let result = {
|
|
6537
|
+
item: undefined,
|
|
6538
|
+
};
|
|
6539
|
+
let targetMessageId;
|
|
6540
|
+
if (mode === 'main') {
|
|
6541
|
+
targetMessageId = jumpToMessage.parentId
|
|
6542
|
+
? jumpToMessage.parentId
|
|
6543
|
+
: jumpToMessage.id;
|
|
6544
|
+
}
|
|
6545
|
+
else {
|
|
6546
|
+
targetMessageId = jumpToMessage.parentId
|
|
6547
|
+
? jumpToMessage.id
|
|
6548
|
+
: undefined;
|
|
6549
|
+
}
|
|
6550
|
+
if (targetMessageId) {
|
|
6551
|
+
const messages = mode === 'main'
|
|
6552
|
+
? channelService.activeChannelMessages
|
|
6553
|
+
: channelService.activeChannelThreadReplies;
|
|
6554
|
+
const id = targetMessageId === 'latest'
|
|
6555
|
+
? messages[messages.length - 1]?.id
|
|
6556
|
+
: targetMessageId;
|
|
6557
|
+
if (id) {
|
|
6558
|
+
result = {
|
|
6559
|
+
item: { id },
|
|
6560
|
+
position: jumpToMessage.id === 'latest' ? 'bottom' : 'middle',
|
|
6561
|
+
};
|
|
6562
|
+
}
|
|
6563
|
+
channelService.clearMessageJump();
|
|
6564
|
+
}
|
|
6565
|
+
return result;
|
|
6566
|
+
}));
|
|
6567
|
+
const messages$ = mode === 'main'
|
|
6568
|
+
? channelService.activeChannelMessages$
|
|
6569
|
+
: channelService.activeThreadMessages$;
|
|
6570
|
+
super(messages$, scrollPosition$, jumpToMessage$, channelService.messagePageSize);
|
|
6571
|
+
this.mode = mode;
|
|
6572
|
+
this.channelService = channelService;
|
|
6573
|
+
this.isEqual = (t1, t2) => t1.id === t2.id;
|
|
6574
|
+
this.query = (direction) => {
|
|
6575
|
+
const request = this.mode === 'main'
|
|
6576
|
+
? (direction) => this.channelService.loadMoreMessages(direction)
|
|
6577
|
+
: (direction) => this.channelService.loadMoreThreadReplies(direction);
|
|
6578
|
+
const result = request(direction === 'top' ? 'older' : 'newer');
|
|
6579
|
+
if (result) {
|
|
6580
|
+
return result;
|
|
6581
|
+
}
|
|
6582
|
+
else {
|
|
6583
|
+
this.queryStateSubject.next({ state: 'success' });
|
|
6584
|
+
if ((direction === 'top' && this.bufferOnTop > 0) ||
|
|
6585
|
+
(direction === 'bottom' && this.bufferOnBottom > 0)) {
|
|
6586
|
+
this.loadFromBuffer$.next();
|
|
6587
|
+
}
|
|
6588
|
+
return Promise.resolve();
|
|
6589
|
+
}
|
|
6590
|
+
};
|
|
6591
|
+
}
|
|
6592
|
+
loadMoreFromBuffer(direction) {
|
|
6593
|
+
this.queryStateSubject.next({ state: `loading-${direction}` });
|
|
6594
|
+
setTimeout(() => {
|
|
6595
|
+
this.loadFromBuffer$.next();
|
|
6596
|
+
this.queryStateSubject.next({ state: 'success' });
|
|
6597
|
+
});
|
|
6598
|
+
}
|
|
6599
|
+
}
|
|
6600
|
+
|
|
6274
6601
|
/**
|
|
6275
6602
|
* The `MessageList` component renders a scrollable list of messages.
|
|
6276
6603
|
*/
|
|
@@ -6321,10 +6648,6 @@ class MessageListComponent {
|
|
|
6321
6648
|
* You can turn on and off the loading indicator that signals to users that more messages are being loaded to the message list
|
|
6322
6649
|
*/
|
|
6323
6650
|
this.displayLoadingIndicator = true;
|
|
6324
|
-
/**
|
|
6325
|
-
* @internal
|
|
6326
|
-
*/
|
|
6327
|
-
this.limitNumberOfMessagesInList = true;
|
|
6328
6651
|
this.emptyMainMessageListTemplate = null;
|
|
6329
6652
|
this.emptyThreadMessageListTemplate = null;
|
|
6330
6653
|
this.enabledMessageActions = [];
|
|
@@ -6332,17 +6655,20 @@ class MessageListComponent {
|
|
|
6332
6655
|
this.newMessageCountWhileBeingScrolled = 0;
|
|
6333
6656
|
this.groupStyles = [];
|
|
6334
6657
|
this.isNextMessageOnSeparateDate = [];
|
|
6335
|
-
this.
|
|
6658
|
+
this.loadingState = 'idle';
|
|
6336
6659
|
this.isUnreadNotificationVisible = true;
|
|
6337
6660
|
this.isJumpingToLatestUnreadMessage = false;
|
|
6338
6661
|
this.isJumpToLatestButtonVisible = true;
|
|
6662
|
+
this.isJumpingToMessage = false;
|
|
6339
6663
|
this.scroll$ = new Subject();
|
|
6664
|
+
this.isNewMessageSentByUser = false;
|
|
6340
6665
|
this.subscriptions = [];
|
|
6341
6666
|
this.isLatestMessageInList = true;
|
|
6342
6667
|
this.parsedDates = new Map();
|
|
6343
6668
|
this.isViewInited = false;
|
|
6344
6669
|
this.isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
|
6345
6670
|
this.forceRepaintSubject = new Subject();
|
|
6671
|
+
this.scrollPosition$ = new BehaviorSubject('bottom');
|
|
6346
6672
|
this.messageNotificationJumpClicked = () => {
|
|
6347
6673
|
this.jumpToFirstUnreadMessage();
|
|
6348
6674
|
this.isUnreadNotificationVisible = false;
|
|
@@ -6361,7 +6687,6 @@ class MessageListComponent {
|
|
|
6361
6687
|
this.forceRepaint();
|
|
6362
6688
|
}));
|
|
6363
6689
|
this.subscriptions.push(this.channelService.activeChannel$.subscribe((channel) => {
|
|
6364
|
-
this.chatClientService.chatClient?.logger?.('info', `${channel?.cid || 'undefined'} selected`, { tags: `message list ${this.mode}` });
|
|
6365
6690
|
let isNewChannel = false;
|
|
6366
6691
|
if (this.channelId !== channel?.id) {
|
|
6367
6692
|
isNewChannel = true;
|
|
@@ -6369,12 +6694,9 @@ class MessageListComponent {
|
|
|
6369
6694
|
clearTimeout(this.checkIfUnreadNotificationIsVisibleTimeout);
|
|
6370
6695
|
}
|
|
6371
6696
|
this.isUnreadNotificationVisible = false;
|
|
6372
|
-
this.chatClientService?.chatClient?.logger?.('info', `new channel is different from prev channel, reseting scroll state`, { tags: `message list ${this.mode}` });
|
|
6373
6697
|
this.parsedDates = new Map();
|
|
6374
|
-
if (this.messageRemoveTimeout) {
|
|
6375
|
-
clearTimeout(this.messageRemoveTimeout);
|
|
6376
|
-
}
|
|
6377
6698
|
this.resetScrollState();
|
|
6699
|
+
this.setMessages$();
|
|
6378
6700
|
this.channelId = channel?.id;
|
|
6379
6701
|
if (this.isViewInited) {
|
|
6380
6702
|
this.cdRef.detectChanges();
|
|
@@ -6434,17 +6756,12 @@ class MessageListComponent {
|
|
|
6434
6756
|
this.newMessageSubscription?.unsubscribe();
|
|
6435
6757
|
if (channel) {
|
|
6436
6758
|
this.newMessageSubscription = channel.on('message.new', (event) => {
|
|
6437
|
-
|
|
6438
|
-
if (!event.message ||
|
|
6439
|
-
channel.state.messages === channel.state.latestMessages ||
|
|
6440
|
-
this.mode === 'thread') {
|
|
6759
|
+
if (!event.message) {
|
|
6441
6760
|
return;
|
|
6442
6761
|
}
|
|
6443
|
-
|
|
6444
|
-
|
|
6445
|
-
|
|
6446
|
-
created_at: new Date(event.message.created_at || ''),
|
|
6447
|
-
});
|
|
6762
|
+
else {
|
|
6763
|
+
this.newMessageReceived(event.message);
|
|
6764
|
+
}
|
|
6448
6765
|
});
|
|
6449
6766
|
}
|
|
6450
6767
|
}));
|
|
@@ -6454,6 +6771,7 @@ class MessageListComponent {
|
|
|
6454
6771
|
message.id !== this.parentMessage.id &&
|
|
6455
6772
|
this.mode === 'thread') {
|
|
6456
6773
|
this.resetScrollState();
|
|
6774
|
+
this.setMessages$();
|
|
6457
6775
|
}
|
|
6458
6776
|
if (this.parentMessage === message) {
|
|
6459
6777
|
return;
|
|
@@ -6508,41 +6826,6 @@ class MessageListComponent {
|
|
|
6508
6826
|
this.cdRef.detectChanges();
|
|
6509
6827
|
}
|
|
6510
6828
|
}));
|
|
6511
|
-
this.subscriptions.push(this.channelService.jumpToMessage$
|
|
6512
|
-
.pipe(filter((config) => !!config.id))
|
|
6513
|
-
.subscribe((config) => {
|
|
6514
|
-
let messageId = undefined;
|
|
6515
|
-
if (this.messageRemoveTimeout) {
|
|
6516
|
-
clearTimeout(this.messageRemoveTimeout);
|
|
6517
|
-
}
|
|
6518
|
-
if (this.mode === 'main') {
|
|
6519
|
-
messageId = config.parentId || config.id;
|
|
6520
|
-
}
|
|
6521
|
-
else if (config.parentId) {
|
|
6522
|
-
messageId = config.id;
|
|
6523
|
-
}
|
|
6524
|
-
this.chatClientService.chatClient?.logger?.('info', `Jumping to ${messageId || ''}`, { tags: `message list ${this.mode}` });
|
|
6525
|
-
if (messageId) {
|
|
6526
|
-
if (messageId === 'latest') {
|
|
6527
|
-
this.scrollToLatestMessage();
|
|
6528
|
-
if (this.isViewInited) {
|
|
6529
|
-
this.cdRef.detectChanges();
|
|
6530
|
-
}
|
|
6531
|
-
}
|
|
6532
|
-
else {
|
|
6533
|
-
if (this.isJumpingToLatestUnreadMessage) {
|
|
6534
|
-
this.scrollMessageIntoView(this.firstUnreadMessageId || messageId);
|
|
6535
|
-
this.highlightedMessageId =
|
|
6536
|
-
this.firstUnreadMessageId || messageId;
|
|
6537
|
-
}
|
|
6538
|
-
else {
|
|
6539
|
-
this.scrollMessageIntoView(messageId);
|
|
6540
|
-
this.highlightedMessageId = messageId;
|
|
6541
|
-
}
|
|
6542
|
-
}
|
|
6543
|
-
}
|
|
6544
|
-
this.channelService.clearMessageJump();
|
|
6545
|
-
}));
|
|
6546
6829
|
this.subscriptions.push(this.customTemplatesService.emptyMainMessageListPlaceholder$.subscribe((template) => {
|
|
6547
6830
|
const isChanged = this.emptyMainMessageListTemplate !== template;
|
|
6548
6831
|
this.emptyMainMessageListTemplate = template || null;
|
|
@@ -6561,6 +6844,7 @@ class MessageListComponent {
|
|
|
6561
6844
|
}
|
|
6562
6845
|
ngOnChanges(changes) {
|
|
6563
6846
|
if (changes.mode || changes.direction) {
|
|
6847
|
+
this.resetScrollState();
|
|
6564
6848
|
this.setMessages$();
|
|
6565
6849
|
}
|
|
6566
6850
|
if (changes.direction) {
|
|
@@ -6572,51 +6856,27 @@ class MessageListComponent {
|
|
|
6572
6856
|
ngAfterViewInit() {
|
|
6573
6857
|
this.isViewInited = true;
|
|
6574
6858
|
this.ngZone.runOutsideAngular(() => {
|
|
6575
|
-
this.scrollContainer
|
|
6859
|
+
this.scrollContainer?.nativeElement?.addEventListener('scroll', () => this.scrolled());
|
|
6576
6860
|
});
|
|
6577
6861
|
}
|
|
6578
6862
|
ngAfterViewChecked() {
|
|
6579
|
-
if (this.
|
|
6580
|
-
|
|
6581
|
-
this.
|
|
6582
|
-
this.
|
|
6863
|
+
if (this.isJumpingToMessage) {
|
|
6864
|
+
this.isNewMessageSentByUser = false;
|
|
6865
|
+
this.messageIdToAnchorTo = undefined;
|
|
6866
|
+
this.anchorMessageTopOffset = undefined;
|
|
6867
|
+
return;
|
|
6583
6868
|
}
|
|
6584
|
-
if (this.
|
|
6585
|
-
|
|
6586
|
-
(this.isNewMessageSentByUser || !this.isUserScrolled)) {
|
|
6587
|
-
this.isLatestMessageInList
|
|
6588
|
-
? this.scrollToTop()
|
|
6589
|
-
: this.jumpToLatestMessage();
|
|
6590
|
-
this.hasNewMessages = false;
|
|
6591
|
-
this.containerHeight = this.scrollContainer.nativeElement.scrollHeight;
|
|
6592
|
-
}
|
|
6869
|
+
if (this.messageIdToAnchorTo && this.loadingState === 'idle') {
|
|
6870
|
+
this.preserveScrollbarPosition();
|
|
6593
6871
|
}
|
|
6594
|
-
else
|
|
6595
|
-
|
|
6596
|
-
|
|
6597
|
-
|
|
6598
|
-
|
|
6599
|
-
|
|
6600
|
-
|
|
6601
|
-
|
|
6602
|
-
this.hasNewMessages = false;
|
|
6603
|
-
this.containerHeight = this.scrollContainer.nativeElement.scrollHeight;
|
|
6604
|
-
}
|
|
6605
|
-
else if (this.olderMassagesLoaded) {
|
|
6606
|
-
this.chatClientService.chatClient?.logger?.('info', `Older messages are loaded, we preserve the scroll position`, { tags: `message list ${this.mode}` });
|
|
6607
|
-
this.preserveScrollbarPosition();
|
|
6608
|
-
this.containerHeight = this.scrollContainer.nativeElement.scrollHeight;
|
|
6609
|
-
this.olderMassagesLoaded = false;
|
|
6610
|
-
}
|
|
6611
|
-
else if (this.getScrollPosition() !== 'bottom' &&
|
|
6612
|
-
!this.isUserScrolled &&
|
|
6613
|
-
!this.highlightedMessageId) {
|
|
6614
|
-
this.chatClientService.chatClient?.logger?.('info', `Container grew and user didn't scroll therefore we ${this.isLatestMessageInList ? 'scroll' : 'jump'} to latest message`, { tags: `message list ${this.mode}` });
|
|
6615
|
-
this.isLatestMessageInList
|
|
6616
|
-
? this.scrollToBottom()
|
|
6617
|
-
: this.jumpToLatestMessage();
|
|
6618
|
-
this.containerHeight = this.scrollContainer.nativeElement.scrollHeight;
|
|
6619
|
-
}
|
|
6872
|
+
else if ((!this.isUserScrolled &&
|
|
6873
|
+
this.scrollContainer.nativeElement?.scrollHeight >
|
|
6874
|
+
this.scrollContainer?.nativeElement.clientHeight &&
|
|
6875
|
+
this.getScrollPosition() !==
|
|
6876
|
+
(this.direction === 'bottom-to-top' ? 'bottom' : 'top')) ||
|
|
6877
|
+
(this.isUserScrolled && this.isNewMessageSentByUser)) {
|
|
6878
|
+
this.isNewMessageSentByUser = false;
|
|
6879
|
+
this.jumpToLatestMessage();
|
|
6620
6880
|
}
|
|
6621
6881
|
}
|
|
6622
6882
|
ngOnDestroy() {
|
|
@@ -6631,19 +6891,23 @@ class MessageListComponent {
|
|
|
6631
6891
|
if (this.jumpToLatestButtonVisibilityTimeout) {
|
|
6632
6892
|
clearTimeout(this.jumpToLatestButtonVisibilityTimeout);
|
|
6633
6893
|
}
|
|
6634
|
-
|
|
6635
|
-
clearTimeout(this.messageRemoveTimeout);
|
|
6636
|
-
}
|
|
6637
|
-
this.removeOldMessagesSubscription?.unsubscribe();
|
|
6894
|
+
this.disposeVirtualizedList();
|
|
6638
6895
|
}
|
|
6639
|
-
trackByMessageId(
|
|
6896
|
+
trackByMessageId(_, item) {
|
|
6640
6897
|
return item.id;
|
|
6641
6898
|
}
|
|
6642
|
-
trackByUserId(
|
|
6899
|
+
trackByUserId(_, user) {
|
|
6643
6900
|
return user.id;
|
|
6644
6901
|
}
|
|
6645
6902
|
jumpToLatestMessage() {
|
|
6646
|
-
|
|
6903
|
+
if (this.isLatestMessageInList) {
|
|
6904
|
+
this.direction === 'bottom-to-top'
|
|
6905
|
+
? this.scrollToBottom()
|
|
6906
|
+
: this.scrollToTop();
|
|
6907
|
+
}
|
|
6908
|
+
else {
|
|
6909
|
+
void this.channelService.jumpToMessage('latest', this.mode === 'thread' ? this.parentMessage?.id : undefined);
|
|
6910
|
+
}
|
|
6647
6911
|
}
|
|
6648
6912
|
scrollToBottom() {
|
|
6649
6913
|
this.scrollContainer.nativeElement.scrollTop =
|
|
@@ -6666,8 +6930,7 @@ class MessageListComponent {
|
|
|
6666
6930
|
return;
|
|
6667
6931
|
}
|
|
6668
6932
|
this.scroll$.next();
|
|
6669
|
-
|
|
6670
|
-
this.chatClientService.chatClient?.logger?.('info', `Scrolled - scroll position: ${scrollPosition}, container height: ${this.scrollContainer.nativeElement.scrollHeight}`, { tags: `message list ${this.mode}` });
|
|
6933
|
+
let scrollPosition = this.getScrollPosition();
|
|
6671
6934
|
const isUserScrolled = (this.direction === 'bottom-to-top'
|
|
6672
6935
|
? scrollPosition !== 'bottom'
|
|
6673
6936
|
: scrollPosition !== 'top') || !this.isLatestMessageInList;
|
|
@@ -6696,30 +6959,33 @@ class MessageListComponent {
|
|
|
6696
6959
|
}
|
|
6697
6960
|
}, 100);
|
|
6698
6961
|
}
|
|
6699
|
-
|
|
6962
|
+
const prevScrollPosition = this.scrollPosition$.getValue();
|
|
6963
|
+
if (this.direction === 'top-to-bottom') {
|
|
6964
|
+
if (scrollPosition === 'top') {
|
|
6965
|
+
scrollPosition = 'bottom';
|
|
6966
|
+
}
|
|
6967
|
+
else if (scrollPosition === 'bottom') {
|
|
6968
|
+
scrollPosition = 'top';
|
|
6969
|
+
}
|
|
6970
|
+
}
|
|
6971
|
+
if (prevScrollPosition !== scrollPosition && !this.isJumpingToMessage) {
|
|
6972
|
+
if (scrollPosition === 'top' || scrollPosition === 'bottom') {
|
|
6973
|
+
this.virtualizedList?.virtualizedItems$
|
|
6974
|
+
.pipe(take(1))
|
|
6975
|
+
.subscribe((items) => {
|
|
6976
|
+
this.messageIdToAnchorTo =
|
|
6977
|
+
scrollPosition === 'top'
|
|
6978
|
+
? items[0]?.id
|
|
6979
|
+
: items[items.length - 1]?.id;
|
|
6980
|
+
this.anchorMessageTopOffset = document
|
|
6981
|
+
.getElementById(this.messageIdToAnchorTo)
|
|
6982
|
+
?.getBoundingClientRect()?.top;
|
|
6983
|
+
});
|
|
6984
|
+
}
|
|
6700
6985
|
this.ngZone.run(() => {
|
|
6701
|
-
this.
|
|
6702
|
-
let direction;
|
|
6703
|
-
if (this.direction === 'top-to-bottom') {
|
|
6704
|
-
direction = scrollPosition === 'top' ? 'newer' : 'older';
|
|
6705
|
-
}
|
|
6706
|
-
else {
|
|
6707
|
-
direction = scrollPosition === 'top' ? 'older' : 'newer';
|
|
6708
|
-
}
|
|
6709
|
-
const result = this.mode === 'main'
|
|
6710
|
-
? this.channelService.loadMoreMessages(direction)
|
|
6711
|
-
: this.channelService.loadMoreThreadReplies(direction);
|
|
6712
|
-
if (result) {
|
|
6713
|
-
this.chatClientService.chatClient?.logger?.('info', `Displaying loading indicator`, { tags: `message list ${this.mode}` });
|
|
6714
|
-
this.isLoading = true;
|
|
6715
|
-
result.catch?.(() => {
|
|
6716
|
-
this.isLoading = false;
|
|
6717
|
-
});
|
|
6718
|
-
}
|
|
6719
|
-
this.cdRef.detectChanges();
|
|
6986
|
+
this.scrollPosition$.next(scrollPosition);
|
|
6720
6987
|
});
|
|
6721
6988
|
}
|
|
6722
|
-
this.prevScrollTop = this.scrollContainer.nativeElement.scrollTop;
|
|
6723
6989
|
}
|
|
6724
6990
|
jumpToFirstUnreadMessage() {
|
|
6725
6991
|
if (!this.lastReadMessageId) {
|
|
@@ -6760,9 +7026,18 @@ class MessageListComponent {
|
|
|
6760
7026
|
: this.emptyThreadMessageListTemplate;
|
|
6761
7027
|
}
|
|
6762
7028
|
preserveScrollbarPosition() {
|
|
6763
|
-
this.
|
|
6764
|
-
|
|
6765
|
-
|
|
7029
|
+
if (!this.messageIdToAnchorTo) {
|
|
7030
|
+
return;
|
|
7031
|
+
}
|
|
7032
|
+
const messageToAlignTo = document.getElementById(this.messageIdToAnchorTo);
|
|
7033
|
+
this.messageIdToAnchorTo = undefined;
|
|
7034
|
+
this.scrollContainer.nativeElement.scrollTop +=
|
|
7035
|
+
(messageToAlignTo?.getBoundingClientRect()?.top || 0) -
|
|
7036
|
+
(this.anchorMessageTopOffset || 0);
|
|
7037
|
+
this.anchorMessageTopOffset = undefined;
|
|
7038
|
+
if (this.isSafari) {
|
|
7039
|
+
this.forceRepaintSubject.next();
|
|
7040
|
+
}
|
|
6766
7041
|
}
|
|
6767
7042
|
forceRepaint() {
|
|
6768
7043
|
// Solves the issue of empty screen on Safari when scrolling
|
|
@@ -6773,10 +7048,7 @@ class MessageListComponent {
|
|
|
6773
7048
|
getScrollPosition() {
|
|
6774
7049
|
let position = 'middle';
|
|
6775
7050
|
if (Math.floor(this.scrollContainer.nativeElement.scrollTop) <=
|
|
6776
|
-
(this.parentMessageElement?.nativeElement.clientHeight || 0)
|
|
6777
|
-
(this.prevScrollTop === undefined ||
|
|
6778
|
-
this.prevScrollTop >
|
|
6779
|
-
(this.parentMessageElement?.nativeElement.clientHeight || 0))) {
|
|
7051
|
+
(this.parentMessageElement?.nativeElement.clientHeight || 0)) {
|
|
6780
7052
|
position = 'top';
|
|
6781
7053
|
}
|
|
6782
7054
|
else if (Math.ceil(this.scrollContainer.nativeElement.scrollTop) +
|
|
@@ -6787,22 +7059,23 @@ class MessageListComponent {
|
|
|
6787
7059
|
}
|
|
6788
7060
|
return position;
|
|
6789
7061
|
}
|
|
6790
|
-
shouldLoadMoreMessages(scrollPosition) {
|
|
6791
|
-
return (scrollPosition !== 'middle' &&
|
|
6792
|
-
!this.highlightedMessageId &&
|
|
6793
|
-
!this.isLoading);
|
|
6794
|
-
}
|
|
6795
7062
|
setMessages$() {
|
|
6796
|
-
this.
|
|
6797
|
-
|
|
6798
|
-
|
|
6799
|
-
|
|
6800
|
-
|
|
7063
|
+
this.disposeVirtualizedList();
|
|
7064
|
+
this.virtualizedList = new VirtualizedMessageListService(this.mode, this.scrollPosition$, this.channelService);
|
|
7065
|
+
this.queryStateSubscription = this.virtualizedList.queryState$.subscribe((queryState) => {
|
|
7066
|
+
let mappedState = 'idle';
|
|
7067
|
+
if (queryState.state.includes('loading')) {
|
|
7068
|
+
mappedState = queryState.state || 'loading-bottom';
|
|
7069
|
+
}
|
|
7070
|
+
if (mappedState !== this.loadingState) {
|
|
7071
|
+
this.loadingState = mappedState;
|
|
7072
|
+
if (this.isViewInited) {
|
|
7073
|
+
this.cdRef.detectChanges();
|
|
7074
|
+
}
|
|
6801
7075
|
}
|
|
7076
|
+
});
|
|
7077
|
+
this.messages$ = this.virtualizedList.virtualizedItems$.pipe(tap((messages) => {
|
|
6802
7078
|
if (messages.length === 0) {
|
|
6803
|
-
this.chatClientService.chatClient?.logger?.('info', `Empty messages array, reseting scroll state`, {
|
|
6804
|
-
tags: `message list ${this.mode}`,
|
|
6805
|
-
});
|
|
6806
7079
|
this.resetScrollState();
|
|
6807
7080
|
return;
|
|
6808
7081
|
}
|
|
@@ -6810,21 +7083,6 @@ class MessageListComponent {
|
|
|
6810
7083
|
// cdRef.detectChanges() isn't enough here, test will fail
|
|
6811
7084
|
setTimeout(() => (this.isEmpty = false), 0);
|
|
6812
7085
|
}
|
|
6813
|
-
this.chatClientService.chatClient?.logger?.('info', `Received one or more messages`, {
|
|
6814
|
-
tags: `message list ${this.mode}`,
|
|
6815
|
-
});
|
|
6816
|
-
const currentLatestMessageInState = messages[messages.length - 1];
|
|
6817
|
-
this.newMessageReceived(currentLatestMessageInState);
|
|
6818
|
-
const currentOldestMessage = messages[0];
|
|
6819
|
-
if (!this.oldestMessage ||
|
|
6820
|
-
!messages.find((m) => m.id === this.oldestMessage.id)) {
|
|
6821
|
-
this.oldestMessage = currentOldestMessage;
|
|
6822
|
-
}
|
|
6823
|
-
else if (this.oldestMessage.created_at.getTime() >
|
|
6824
|
-
currentOldestMessage.created_at.getTime()) {
|
|
6825
|
-
this.oldestMessage = currentOldestMessage;
|
|
6826
|
-
this.olderMassagesLoaded = true;
|
|
6827
|
-
}
|
|
6828
7086
|
}), tap((messages) => {
|
|
6829
7087
|
if (this.isJumpingToLatestUnreadMessage &&
|
|
6830
7088
|
!this.firstUnreadMessageId &&
|
|
@@ -6837,82 +7095,98 @@ class MessageListComponent {
|
|
|
6837
7095
|
.reverse()
|
|
6838
7096
|
.find((m) => m.user?.id === this.chatClientService.chatClient?.user?.id &&
|
|
6839
7097
|
m.status !== 'sending')?.id)), tap((messages) => {
|
|
7098
|
+
const latestMessageInList = messages[messages.length - 1];
|
|
7099
|
+
const channel = this.channelService.activeChannel;
|
|
7100
|
+
const messagesFromState = (this.mode === 'main'
|
|
7101
|
+
? channel?.state.latestMessages
|
|
7102
|
+
: channel?.state.threads[this.parentMessage?.id || '']) || [];
|
|
6840
7103
|
this.isLatestMessageInList =
|
|
6841
|
-
!
|
|
6842
|
-
|
|
6843
|
-
|
|
6844
|
-
|
|
7104
|
+
!latestMessageInList ||
|
|
7105
|
+
latestMessageInList.cid !== channel?.cid ||
|
|
7106
|
+
latestMessageInList?.id ===
|
|
7107
|
+
messagesFromState[messagesFromState.length - 1]?.id;
|
|
6845
7108
|
if (!this.isLatestMessageInList) {
|
|
6846
7109
|
this.isUserScrolled = true;
|
|
6847
7110
|
}
|
|
6848
|
-
}), map((messages) =>
|
|
7111
|
+
}), map((messages) => {
|
|
7112
|
+
return this.direction === 'bottom-to-top'
|
|
7113
|
+
? messages
|
|
7114
|
+
: [...messages].reverse();
|
|
7115
|
+
}), tap((messages) => {
|
|
6849
7116
|
this.groupStyles = messages.map((m, i) => getGroupStyles(m, messages[i - 1], messages[i + 1], {
|
|
6850
7117
|
lastReadMessageId: this.lastReadMessageId,
|
|
6851
7118
|
}));
|
|
6852
7119
|
this.isNextMessageOnSeparateDate = messages.map((m, i) => this.checkIfOnSeparateDates(m, messages[i + 1]));
|
|
6853
7120
|
}), shareReplay(1));
|
|
6854
|
-
this.
|
|
6855
|
-
|
|
6856
|
-
|
|
6857
|
-
|
|
6858
|
-
|
|
6859
|
-
|
|
6860
|
-
|
|
6861
|
-
|
|
6862
|
-
|
|
6863
|
-
|
|
6864
|
-
|
|
6865
|
-
|
|
6866
|
-
|
|
6867
|
-
|
|
6868
|
-
|
|
6869
|
-
|
|
6870
|
-
this.
|
|
6871
|
-
|
|
6872
|
-
|
|
6873
|
-
|
|
6874
|
-
if (this.limitNumberOfMessagesInList &&
|
|
6875
|
-
this.mode === 'main' &&
|
|
6876
|
-
messages.length >
|
|
6877
|
-
ChannelService.MAX_MESSAGE_COUNT_IN_MESSAGE_LIST * 0.5 &&
|
|
6878
|
-
!this.isUserScrolled &&
|
|
6879
|
-
!this.highlightedMessageId &&
|
|
6880
|
-
this.isLatestMessageInList) {
|
|
6881
|
-
this.channelService.removeOldMessageFromMessageList();
|
|
6882
|
-
}
|
|
6883
|
-
}, 1500);
|
|
7121
|
+
if (this.virtualizedList?.jumpToItem$) {
|
|
7122
|
+
this.jumpToItemSubscription = this.virtualizedList.jumpToItem$
|
|
7123
|
+
.pipe(filter((jumpToMessage) => !!jumpToMessage.item?.id))
|
|
7124
|
+
.subscribe((jumpToMessage) => {
|
|
7125
|
+
let messageId = jumpToMessage.item?.id;
|
|
7126
|
+
if (messageId) {
|
|
7127
|
+
if (this.isJumpingToLatestUnreadMessage) {
|
|
7128
|
+
messageId = this.firstUnreadMessageId || messageId;
|
|
7129
|
+
}
|
|
7130
|
+
if (jumpToMessage.position !== 'bottom') {
|
|
7131
|
+
this.highlightedMessageId = messageId;
|
|
7132
|
+
}
|
|
7133
|
+
else if (this.direction === 'top-to-bottom') {
|
|
7134
|
+
jumpToMessage.position = 'top';
|
|
7135
|
+
}
|
|
7136
|
+
this.isJumpingToMessage = true;
|
|
7137
|
+
this.scrollMessageIntoView({
|
|
7138
|
+
messageId: this.firstUnreadMessageId || messageId,
|
|
7139
|
+
position: jumpToMessage.position || 'middle',
|
|
7140
|
+
});
|
|
6884
7141
|
}
|
|
6885
|
-
}
|
|
6886
|
-
}
|
|
7142
|
+
});
|
|
7143
|
+
}
|
|
6887
7144
|
}
|
|
6888
7145
|
resetScrollState() {
|
|
6889
7146
|
this.isEmpty = true;
|
|
6890
|
-
this.latestMessage = undefined;
|
|
6891
|
-
this.hasNewMessages = true;
|
|
6892
7147
|
this.isUserScrolled = false;
|
|
6893
|
-
this.
|
|
6894
|
-
this.
|
|
6895
|
-
this.oldestMessage = undefined;
|
|
7148
|
+
this.messageIdToAnchorTo = undefined;
|
|
7149
|
+
this.anchorMessageTopOffset = undefined;
|
|
6896
7150
|
this.newMessageCountWhileBeingScrolled = 0;
|
|
6897
|
-
this.
|
|
6898
|
-
this.isNewMessageSentByUser = undefined;
|
|
7151
|
+
this.isNewMessageSentByUser = false;
|
|
6899
7152
|
this.isLatestMessageInList = true;
|
|
7153
|
+
this.isJumpingToMessage = false;
|
|
7154
|
+
this.scrollPosition$.next('bottom');
|
|
7155
|
+
this.loadingState = 'idle';
|
|
7156
|
+
}
|
|
7157
|
+
disposeVirtualizedList() {
|
|
7158
|
+
this.virtualizedList?.dispose();
|
|
7159
|
+
this.jumpToItemSubscription?.unsubscribe();
|
|
7160
|
+
this.queryStateSubscription?.unsubscribe();
|
|
6900
7161
|
}
|
|
6901
7162
|
get usersTyping$() {
|
|
6902
7163
|
return this.mode === 'thread'
|
|
6903
7164
|
? this.usersTypingInThread$
|
|
6904
7165
|
: this.usersTypingInChannel$;
|
|
6905
7166
|
}
|
|
6906
|
-
scrollMessageIntoView(
|
|
6907
|
-
const element = document.getElementById(messageId);
|
|
7167
|
+
scrollMessageIntoView(options, withRetry = true) {
|
|
7168
|
+
const element = document.getElementById(options.messageId);
|
|
6908
7169
|
if (!element && withRetry) {
|
|
6909
7170
|
// If the message was newly inserted into activeChannelMessages$, the message will be rendered after the current change detection cycle -> wait for this cycle to complete
|
|
6910
|
-
setTimeout(() => this.scrollMessageIntoView(
|
|
7171
|
+
setTimeout(() => this.scrollMessageIntoView(options, false));
|
|
6911
7172
|
}
|
|
6912
7173
|
else if (element) {
|
|
7174
|
+
const blockMapping = {
|
|
7175
|
+
top: 'start',
|
|
7176
|
+
bottom: 'end',
|
|
7177
|
+
middle: 'center',
|
|
7178
|
+
};
|
|
6913
7179
|
element.scrollIntoView({
|
|
6914
|
-
block:
|
|
7180
|
+
block: blockMapping[options.position],
|
|
6915
7181
|
});
|
|
7182
|
+
if (options.position !== 'middle') {
|
|
7183
|
+
options.position === 'bottom'
|
|
7184
|
+
? this.scrollToBottom()
|
|
7185
|
+
: this.scrollToTop();
|
|
7186
|
+
}
|
|
7187
|
+
setTimeout(() => {
|
|
7188
|
+
this.isJumpingToMessage = false;
|
|
7189
|
+
}, 0);
|
|
6916
7190
|
setTimeout(() => {
|
|
6917
7191
|
this.highlightedMessageId = undefined;
|
|
6918
7192
|
this.firstUnreadMessageId = undefined;
|
|
@@ -6921,39 +7195,26 @@ class MessageListComponent {
|
|
|
6921
7195
|
}, 1000);
|
|
6922
7196
|
}
|
|
6923
7197
|
}
|
|
6924
|
-
|
|
6925
|
-
if (
|
|
6926
|
-
this.
|
|
6927
|
-
|
|
6928
|
-
: this.scrollToTop();
|
|
7198
|
+
newMessageReceived(message) {
|
|
7199
|
+
if ((this.mode === 'main' && message.parent_id) ||
|
|
7200
|
+
(this.mode === 'thread' && message.parent_id !== this.parentMessage?.id)) {
|
|
7201
|
+
return;
|
|
6929
7202
|
}
|
|
6930
|
-
|
|
6931
|
-
|
|
6932
|
-
|
|
7203
|
+
const isNewMessageSentByCurrentUser = message.user?.id === this.chatClientService.chatClient?.user?.id;
|
|
7204
|
+
let shouldDetectChanges = false;
|
|
7205
|
+
if (!this.isNewMessageSentByUser && isNewMessageSentByCurrentUser) {
|
|
7206
|
+
this.isNewMessageSentByUser = true;
|
|
7207
|
+
shouldDetectChanges = true;
|
|
6933
7208
|
}
|
|
6934
|
-
|
|
6935
|
-
|
|
6936
|
-
|
|
6937
|
-
|
|
6938
|
-
|
|
6939
|
-
|
|
6940
|
-
|
|
6941
|
-
|
|
6942
|
-
|
|
6943
|
-
this.chatClientService.chatClient?.logger?.('info', `Received new message`, { tags: `message list ${this.mode}` });
|
|
6944
|
-
const isNewChannel = !this.latestMessage;
|
|
6945
|
-
this.latestMessage = message;
|
|
6946
|
-
this.hasNewMessages = true;
|
|
6947
|
-
this.isNewMessageSentByUser =
|
|
6948
|
-
message.user?.id === this.chatClientService.chatClient?.user?.id;
|
|
6949
|
-
if (this.isUserScrolled) {
|
|
6950
|
-
this.newMessageCountWhileBeingScrolled++;
|
|
6951
|
-
}
|
|
6952
|
-
if (!this.isNewMessageSentByUser &&
|
|
6953
|
-
this.unreadCount !== undefined &&
|
|
6954
|
-
!isNewChannel) {
|
|
6955
|
-
this.unreadCount++;
|
|
6956
|
-
}
|
|
7209
|
+
if (this.isUserScrolled) {
|
|
7210
|
+
this.newMessageCountWhileBeingScrolled++;
|
|
7211
|
+
shouldDetectChanges = true;
|
|
7212
|
+
}
|
|
7213
|
+
if (!this.isNewMessageSentByUser && this.unreadCount !== undefined) {
|
|
7214
|
+
this.unreadCount++;
|
|
7215
|
+
shouldDetectChanges = true;
|
|
7216
|
+
}
|
|
7217
|
+
if (shouldDetectChanges && this.isViewInited) {
|
|
6957
7218
|
this.cdRef.detectChanges();
|
|
6958
7219
|
}
|
|
6959
7220
|
}
|
|
@@ -6965,10 +7226,10 @@ class MessageListComponent {
|
|
|
6965
7226
|
}
|
|
6966
7227
|
}
|
|
6967
7228
|
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 });
|
|
6968
|
-
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 });
|
|
7229
|
+
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" }, 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\n #scrollContainer\n data-testid=\"scroll-container\"\n class=\"str-chat__list\"\n style=\"overscroll-behavior-y: none\"\n>\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-placeholder\n *ngIf=\"\n ((loadingState === 'loading-top' && direction === 'bottom-to-top') ||\n (loadingState === 'loading-bottom' &&\n direction === 'top-to-bottom')) &&\n displayLoadingIndicator;\n else loadingIndicatorPlaceholder\n \"\n data-testid=\"top-loading-indicator\"\n ></stream-loading-indicator-placeholder>\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-placeholder\n *ngIf=\"\n ((loadingState === 'loading-bottom' &&\n direction === 'bottom-to-top') ||\n (loadingState === 'loading-top' &&\n direction === 'top-to-bottom')) &&\n displayLoadingIndicator;\n else loadingIndicatorPlaceholder\n \"\n data-testid=\"bottom-loading-indicator\"\n ></stream-loading-indicator-placeholder>\n <ng-template #loadingIndicatorPlaceholder>\n <div class=\"str-chat__loading-indicator-placeholder\"></div>\n </ng-template>\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: IconComponent, selector: "stream-icon", inputs: ["icon"] }, { 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 });
|
|
6969
7230
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: MessageListComponent, decorators: [{
|
|
6970
7231
|
type: Component,
|
|
6971
|
-
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" }]
|
|
7232
|
+
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\n #scrollContainer\n data-testid=\"scroll-container\"\n class=\"str-chat__list\"\n style=\"overscroll-behavior-y: none\"\n>\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-placeholder\n *ngIf=\"\n ((loadingState === 'loading-top' && direction === 'bottom-to-top') ||\n (loadingState === 'loading-bottom' &&\n direction === 'top-to-bottom')) &&\n displayLoadingIndicator;\n else loadingIndicatorPlaceholder\n \"\n data-testid=\"top-loading-indicator\"\n ></stream-loading-indicator-placeholder>\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-placeholder\n *ngIf=\"\n ((loadingState === 'loading-bottom' &&\n direction === 'bottom-to-top') ||\n (loadingState === 'loading-top' &&\n direction === 'top-to-bottom')) &&\n displayLoadingIndicator;\n else loadingIndicatorPlaceholder\n \"\n data-testid=\"bottom-loading-indicator\"\n ></stream-loading-indicator-placeholder>\n <ng-template #loadingIndicatorPlaceholder>\n <div class=\"str-chat__loading-indicator-placeholder\"></div>\n </ng-template>\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" }]
|
|
6972
7233
|
}], ctorParameters: function () { return [{ type: ChannelService }, { type: ChatClientService }, { type: CustomTemplatesService }, { type: DateParserService }, { type: i0.NgZone }, { type: i0.ChangeDetectorRef }]; }, propDecorators: { mode: [{
|
|
6973
7234
|
type: Input
|
|
6974
7235
|
}], direction: [{
|
|
@@ -6987,8 +7248,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImpor
|
|
|
6987
7248
|
type: Input
|
|
6988
7249
|
}], displayLoadingIndicator: [{
|
|
6989
7250
|
type: Input
|
|
6990
|
-
}], limitNumberOfMessagesInList: [{
|
|
6991
|
-
type: Input
|
|
6992
7251
|
}], scrollContainer: [{
|
|
6993
7252
|
type: ViewChild,
|
|
6994
7253
|
args: ['scrollContainer']
|
|
@@ -7235,5 +7494,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImpor
|
|
|
7235
7494
|
* Generated bundle index. Do not edit.
|
|
7236
7495
|
*/
|
|
7237
7496
|
|
|
7238
|
-
export { AttachmentConfigurationService, AttachmentListComponent, AttachmentPreviewListComponent, AttachmentService, AutocompleteTextareaComponent, AvatarComponent, AvatarPlaceholderComponent, ChannelComponent, ChannelHeaderComponent, ChannelListComponent, ChannelPreviewComponent, ChannelQuery, 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 };
|
|
7497
|
+
export { AttachmentConfigurationService, AttachmentListComponent, AttachmentPreviewListComponent, AttachmentService, AutocompleteTextareaComponent, AvatarComponent, AvatarPlaceholderComponent, ChannelComponent, ChannelHeaderComponent, ChannelListComponent, ChannelPreviewComponent, ChannelQuery, 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, VirtualizedListService, VirtualizedMessageListService, VoiceRecordingComponent, VoiceRecordingWavebarComponent, createMessagePreview, getChannelDisplayText, getGroupStyles, getMessageTranslation, getReadBy, isImageAttachment, isImageFile, isOnSeparateDate, listUsers, parseDate, textareaInjectionToken };
|
|
7239
7498
|
//# sourceMappingURL=stream-chat-angular.mjs.map
|