stream-chat-angular 5.0.0 → 5.1.0
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 +9 -24
- 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 +548 -284
- package/fesm2015/stream-chat-angular.mjs.map +1 -1
- package/fesm2020/stream-chat-angular.mjs +534 -273
- 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.0';
|
|
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.
|
|
@@ -447,6 +447,7 @@ class ChannelService {
|
|
|
447
447
|
this.chatClientService = chatClientService;
|
|
448
448
|
this.ngZone = ngZone;
|
|
449
449
|
this.notificationService = notificationService;
|
|
450
|
+
this.messagePageSize = 25;
|
|
450
451
|
this.channelsSubject = new BehaviorSubject(undefined);
|
|
451
452
|
this.activeChannelSubject = new BehaviorSubject(undefined);
|
|
452
453
|
this.activeChannelMessagesSubject = new BehaviorSubject([]);
|
|
@@ -458,7 +459,6 @@ class ChannelService {
|
|
|
458
459
|
this.activeThreadMessagesSubject = new BehaviorSubject([]);
|
|
459
460
|
this.jumpToMessageSubject = new BehaviorSubject({ id: undefined, parentId: undefined });
|
|
460
461
|
this.latestMessageDateByUserByChannelsSubject = new BehaviorSubject({});
|
|
461
|
-
this.messagePageSize = 25;
|
|
462
462
|
this.attachmentMaxSizeFallbackInMB = 100;
|
|
463
463
|
this.messageToQuoteSubject = new BehaviorSubject(undefined);
|
|
464
464
|
this.usersTypingInChannelSubject = new BehaviorSubject([]);
|
|
@@ -567,23 +567,6 @@ class ChannelService {
|
|
|
567
567
|
.asObservable()
|
|
568
568
|
.pipe(shareReplay(1));
|
|
569
569
|
}
|
|
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
570
|
/**
|
|
588
571
|
* 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
572
|
*/
|
|
@@ -1466,6 +1449,12 @@ class ChannelService {
|
|
|
1466
1449
|
get activeChannelMessages() {
|
|
1467
1450
|
return this.activeChannelMessagesSubject.getValue() || [];
|
|
1468
1451
|
}
|
|
1452
|
+
/**
|
|
1453
|
+
* The current thread replies
|
|
1454
|
+
*/
|
|
1455
|
+
get activeChannelThreadReplies() {
|
|
1456
|
+
return this.activeThreadMessagesSubject.getValue() || [];
|
|
1457
|
+
}
|
|
1469
1458
|
/**
|
|
1470
1459
|
* 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
1460
|
* @param messageId
|
|
@@ -1547,7 +1536,7 @@ class ChannelService {
|
|
|
1547
1536
|
return;
|
|
1548
1537
|
}
|
|
1549
1538
|
const messageIndex = messages.findIndex((m) => m.id === event?.message?.id);
|
|
1550
|
-
if (messageIndex !== -1) {
|
|
1539
|
+
if (messageIndex !== -1 || event.type === 'message.deleted') {
|
|
1551
1540
|
isThreadReply
|
|
1552
1541
|
? this.activeThreadMessagesSubject.next([...messages])
|
|
1553
1542
|
: this.activeChannelMessagesSubject.next([...messages]);
|
|
@@ -1928,10 +1917,6 @@ class ChannelService {
|
|
|
1928
1917
|
}
|
|
1929
1918
|
}
|
|
1930
1919
|
}
|
|
1931
|
-
/**
|
|
1932
|
-
* @internal
|
|
1933
|
-
*/
|
|
1934
|
-
ChannelService.MAX_MESSAGE_COUNT_IN_MESSAGE_LIST = 250;
|
|
1935
1920
|
/**
|
|
1936
1921
|
* @internal
|
|
1937
1922
|
*/
|
|
@@ -2692,6 +2677,9 @@ class AvatarComponent {
|
|
|
2692
2677
|
this.setFallbackChannelImage();
|
|
2693
2678
|
}
|
|
2694
2679
|
}
|
|
2680
|
+
ngOnDestroy() {
|
|
2681
|
+
this.subscriptions.forEach((s) => s.unsubscribe());
|
|
2682
|
+
}
|
|
2695
2683
|
setFallbackChannelImage() {
|
|
2696
2684
|
if (this.type !== 'channel') {
|
|
2697
2685
|
this.fallbackChannelImage = undefined;
|
|
@@ -3166,7 +3154,7 @@ class MessageActionsService {
|
|
|
3166
3154
|
actionHandler: (message) => {
|
|
3167
3155
|
void this.channelService.markMessageUnread(message.id);
|
|
3168
3156
|
},
|
|
3169
|
-
isVisible: (enabledActions,
|
|
3157
|
+
isVisible: (enabledActions, _, message) => enabledActions.indexOf('read-events') !== -1 && !message.parent_id,
|
|
3170
3158
|
},
|
|
3171
3159
|
{
|
|
3172
3160
|
actionName: 'quote',
|
|
@@ -3182,7 +3170,7 @@ class MessageActionsService {
|
|
|
3182
3170
|
actionHandler: (message) => {
|
|
3183
3171
|
void this.channelService.setAsActiveParentMessage(message);
|
|
3184
3172
|
},
|
|
3185
|
-
isVisible: (enabledActions,
|
|
3173
|
+
isVisible: (enabledActions, _, message) => enabledActions.indexOf('send-reply') !== -1 && !message.parent_id,
|
|
3186
3174
|
},
|
|
3187
3175
|
{
|
|
3188
3176
|
actionName: 'pin',
|
|
@@ -3203,7 +3191,7 @@ class MessageActionsService {
|
|
|
3203
3191
|
await this.chatClientService.flagMessage(message.id);
|
|
3204
3192
|
this.notificationService.addTemporaryNotification('streamChat.Message has been successfully flagged', 'success');
|
|
3205
3193
|
}
|
|
3206
|
-
catch (
|
|
3194
|
+
catch (error) {
|
|
3207
3195
|
this.notificationService.addTemporaryNotification('streamChat.Error adding flag');
|
|
3208
3196
|
}
|
|
3209
3197
|
},
|
|
@@ -4097,7 +4085,7 @@ class ChannelListComponent {
|
|
|
4097
4085
|
await this.channelService.loadMoreChannels();
|
|
4098
4086
|
this.isLoadingMoreChannels = false;
|
|
4099
4087
|
}
|
|
4100
|
-
trackByChannelId(
|
|
4088
|
+
trackByChannelId(_, item) {
|
|
4101
4089
|
return item.cid;
|
|
4102
4090
|
}
|
|
4103
4091
|
}
|
|
@@ -4337,7 +4325,7 @@ class VoiceRecordingComponent {
|
|
|
4337
4325
|
: this.audioElement.nativeElement.pause();
|
|
4338
4326
|
this.isError = false;
|
|
4339
4327
|
}
|
|
4340
|
-
catch (
|
|
4328
|
+
catch (error) {
|
|
4341
4329
|
this.isError = true;
|
|
4342
4330
|
}
|
|
4343
4331
|
}
|
|
@@ -6271,6 +6259,347 @@ const isOnSameDay = (date1, date2) => {
|
|
|
6271
6259
|
date1.getDate() === date2.getDate());
|
|
6272
6260
|
};
|
|
6273
6261
|
|
|
6262
|
+
/**
|
|
6263
|
+
* The `VirtualizedListService` removes items from a list that are not currently displayed. This is a high-level overview of how it works:
|
|
6264
|
+
* - Create a new instance for each list that needs virtualization
|
|
6265
|
+
* - Input: Provide a reactive stream that emits all items in the list
|
|
6266
|
+
* - Input: Provide a reactive stream that emit the current scroll position (top, middle or bottom)
|
|
6267
|
+
* - 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)
|
|
6268
|
+
* - Output: The service will emit the current list of displayed items via the virtualized items reactive stream
|
|
6269
|
+
* - 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:
|
|
6270
|
+
* - If scroll location is bottom/top items around the current bottom/top item will be emitted in the virtualized items stream
|
|
6271
|
+
* - 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)
|
|
6272
|
+
* - 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
|
|
6273
|
+
* - 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
|
|
6274
|
+
* - Input: you should provide the page size to use, in order for the service to determine if loading is necessary
|
|
6275
|
+
*
|
|
6276
|
+
* The `VirtualizedMessageListService` provides an implementation for the message list component.
|
|
6277
|
+
*/
|
|
6278
|
+
class VirtualizedListService {
|
|
6279
|
+
constructor(allItems$, scrollPosition$, jumpToItem$, pageSize = 25, maxItemCount = pageSize * 4) {
|
|
6280
|
+
this.allItems$ = allItems$;
|
|
6281
|
+
this.scrollPosition$ = scrollPosition$;
|
|
6282
|
+
this.jumpToItem$ = jumpToItem$;
|
|
6283
|
+
this.pageSize = pageSize;
|
|
6284
|
+
this.maxItemCount = maxItemCount;
|
|
6285
|
+
this.queryStateSubject = new BehaviorSubject({
|
|
6286
|
+
state: 'success',
|
|
6287
|
+
});
|
|
6288
|
+
this.bufferOnTop = 0;
|
|
6289
|
+
this.bufferOnBottom = 0;
|
|
6290
|
+
this.loadFromBuffer$ = new Subject();
|
|
6291
|
+
this.virtualizedItemsSubject = new BehaviorSubject([]);
|
|
6292
|
+
this.subscriptions = [];
|
|
6293
|
+
this.virtualizedItems$ = this.virtualizedItemsSubject.asObservable();
|
|
6294
|
+
this.queryState$ = this.queryStateSubject.asObservable();
|
|
6295
|
+
this.subscriptions.push(this.virtualizedItems$.subscribe((virtaluzedItems) => {
|
|
6296
|
+
this.allItems$.pipe(take$1(1)).subscribe((allItems) => {
|
|
6297
|
+
if (virtaluzedItems.length === allItems.length) {
|
|
6298
|
+
this.bufferOnTop = 0;
|
|
6299
|
+
this.bufferOnBottom = 0;
|
|
6300
|
+
}
|
|
6301
|
+
else if (virtaluzedItems.length === 0) {
|
|
6302
|
+
this.bufferOnTop = allItems.length;
|
|
6303
|
+
this.bufferOnBottom = 0;
|
|
6304
|
+
}
|
|
6305
|
+
else {
|
|
6306
|
+
this.bufferOnTop = allItems.indexOf(virtaluzedItems[0]);
|
|
6307
|
+
this.bufferOnBottom =
|
|
6308
|
+
allItems.length -
|
|
6309
|
+
allItems.indexOf(virtaluzedItems[virtaluzedItems.length - 1]) -
|
|
6310
|
+
1;
|
|
6311
|
+
}
|
|
6312
|
+
});
|
|
6313
|
+
}));
|
|
6314
|
+
this.subscriptions.push(merge(this.allItems$, this.loadFromBuffer$)
|
|
6315
|
+
.pipe(switchMap(() => {
|
|
6316
|
+
return combineLatest([
|
|
6317
|
+
this.allItems$.pipe(take$1(1)),
|
|
6318
|
+
this.scrollPosition$.pipe(take$1(1)),
|
|
6319
|
+
]);
|
|
6320
|
+
}))
|
|
6321
|
+
.subscribe(([items, scrollPosition]) => {
|
|
6322
|
+
if (scrollPosition === 'middle') {
|
|
6323
|
+
return;
|
|
6324
|
+
}
|
|
6325
|
+
const currentItems = this.virtualizedItemsSubject.getValue();
|
|
6326
|
+
if (items.length <= this.maxItemCount) {
|
|
6327
|
+
this.virtualizedItemsSubject.next(items);
|
|
6328
|
+
}
|
|
6329
|
+
else {
|
|
6330
|
+
let startIndex = 0;
|
|
6331
|
+
let endIndex = undefined;
|
|
6332
|
+
const numberOfItemsToRemove = items.length - Math.round(this.maxItemCount / 2);
|
|
6333
|
+
const numberOfItemsAfterRemove = items.length - numberOfItemsToRemove;
|
|
6334
|
+
switch (scrollPosition) {
|
|
6335
|
+
case 'top':
|
|
6336
|
+
if (currentItems.length > 0) {
|
|
6337
|
+
const middleIndex = items.findIndex((i) => this.isEqual(i, currentItems[0]));
|
|
6338
|
+
if (middleIndex !== -1) {
|
|
6339
|
+
startIndex = Math.max(0, middleIndex - Math.ceil(numberOfItemsAfterRemove / 2));
|
|
6340
|
+
endIndex = startIndex + numberOfItemsAfterRemove;
|
|
6341
|
+
}
|
|
6342
|
+
}
|
|
6343
|
+
else {
|
|
6344
|
+
endIndex = numberOfItemsAfterRemove;
|
|
6345
|
+
}
|
|
6346
|
+
break;
|
|
6347
|
+
case 'bottom':
|
|
6348
|
+
if (currentItems.length > 0) {
|
|
6349
|
+
const middleIndex = items.findIndex((i) => this.isEqual(i, currentItems[currentItems.length - 1]));
|
|
6350
|
+
if (middleIndex !== -1) {
|
|
6351
|
+
endIndex = Math.min(items.length, middleIndex + Math.floor(numberOfItemsAfterRemove / 2) + 1);
|
|
6352
|
+
startIndex = endIndex - numberOfItemsAfterRemove;
|
|
6353
|
+
}
|
|
6354
|
+
}
|
|
6355
|
+
else {
|
|
6356
|
+
startIndex = items.length - numberOfItemsAfterRemove;
|
|
6357
|
+
}
|
|
6358
|
+
break;
|
|
6359
|
+
}
|
|
6360
|
+
const virtualizedItems = items.slice(startIndex, endIndex);
|
|
6361
|
+
this.virtualizedItemsSubject.next(virtualizedItems);
|
|
6362
|
+
}
|
|
6363
|
+
}));
|
|
6364
|
+
this.subscriptions.push(this.scrollPosition$
|
|
6365
|
+
.pipe(distinctUntilChanged())
|
|
6366
|
+
.subscribe((position) => {
|
|
6367
|
+
if (this.queryStateSubject.getValue().state === `loading-${position}`) {
|
|
6368
|
+
return;
|
|
6369
|
+
}
|
|
6370
|
+
if (position === 'top') {
|
|
6371
|
+
if (this.bufferOnTop < this.pageSize) {
|
|
6372
|
+
void this.loadMore(position);
|
|
6373
|
+
}
|
|
6374
|
+
else {
|
|
6375
|
+
this.loadMoreFromBuffer('top');
|
|
6376
|
+
}
|
|
6377
|
+
}
|
|
6378
|
+
else if (position === 'bottom') {
|
|
6379
|
+
if (this.bufferOnBottom < this.pageSize) {
|
|
6380
|
+
void this.loadMore(position);
|
|
6381
|
+
}
|
|
6382
|
+
else {
|
|
6383
|
+
this.loadMoreFromBuffer('bottom');
|
|
6384
|
+
}
|
|
6385
|
+
}
|
|
6386
|
+
}));
|
|
6387
|
+
this.subscriptions.push(this.allItems$
|
|
6388
|
+
.pipe(pairwise(), filter$1(() => {
|
|
6389
|
+
let scrollPosition;
|
|
6390
|
+
this.scrollPosition$
|
|
6391
|
+
.pipe(take$1(1))
|
|
6392
|
+
.subscribe((s) => (scrollPosition = s));
|
|
6393
|
+
return scrollPosition === 'middle';
|
|
6394
|
+
}))
|
|
6395
|
+
.subscribe(([prevItems, currentItems]) => {
|
|
6396
|
+
if (currentItems.length < this.maxItemCount ||
|
|
6397
|
+
this.virtualizedItems.length === 0) {
|
|
6398
|
+
this.virtualizedItemsSubject.next(currentItems);
|
|
6399
|
+
}
|
|
6400
|
+
else {
|
|
6401
|
+
const currentFirstItem = this.virtualizedItems[0];
|
|
6402
|
+
const currentLastItem = this.virtualizedItems[this.virtualizedItems.length - 1];
|
|
6403
|
+
const prevStartIndex = prevItems.findIndex((i) => this.isEqual(i, currentFirstItem));
|
|
6404
|
+
const prevEndIndex = prevItems.findIndex((i) => this.isEqual(i, currentLastItem));
|
|
6405
|
+
const isStartRemainedSame = currentItems[prevStartIndex]
|
|
6406
|
+
? this.isEqual(currentItems[prevStartIndex], currentFirstItem)
|
|
6407
|
+
: false;
|
|
6408
|
+
const isEndRemainedSame = currentItems[prevEndIndex]
|
|
6409
|
+
? this.isEqual(currentItems[prevEndIndex], currentLastItem)
|
|
6410
|
+
: false;
|
|
6411
|
+
const hasNewItemsBottom = prevEndIndex === prevItems.length - 1 && isEndRemainedSame
|
|
6412
|
+
? prevItems.length !== currentItems.length
|
|
6413
|
+
: false;
|
|
6414
|
+
if (isStartRemainedSame && isEndRemainedSame) {
|
|
6415
|
+
const endIndex = hasNewItemsBottom ? undefined : prevEndIndex + 1;
|
|
6416
|
+
this.virtualizedItemsSubject.next(currentItems.slice(prevStartIndex, endIndex));
|
|
6417
|
+
}
|
|
6418
|
+
let currentStartIndex = isStartRemainedSame ? prevStartIndex : -1;
|
|
6419
|
+
let currentEndIndex = isEndRemainedSame ? prevEndIndex : -1;
|
|
6420
|
+
if (!isStartRemainedSame) {
|
|
6421
|
+
currentStartIndex = currentItems.findIndex((i) => this.isEqual(i, currentFirstItem));
|
|
6422
|
+
}
|
|
6423
|
+
if (!isEndRemainedSame) {
|
|
6424
|
+
currentEndIndex = currentItems.findIndex((i) => this.isEqual(i, currentLastItem));
|
|
6425
|
+
}
|
|
6426
|
+
const hasNewItemsTop = prevStartIndex === 0 && !isStartRemainedSame
|
|
6427
|
+
? currentStartIndex !== 0
|
|
6428
|
+
: false;
|
|
6429
|
+
if (currentStartIndex !== -1 && currentEndIndex !== -1) {
|
|
6430
|
+
const startIndex = hasNewItemsTop ? 0 : currentStartIndex;
|
|
6431
|
+
this.virtualizedItemsSubject.next(currentItems.slice(startIndex, currentEndIndex + 1));
|
|
6432
|
+
}
|
|
6433
|
+
else {
|
|
6434
|
+
if (currentStartIndex === -1 && currentEndIndex !== -1) {
|
|
6435
|
+
currentStartIndex = Math.max(0, currentEndIndex - (prevEndIndex - prevStartIndex));
|
|
6436
|
+
}
|
|
6437
|
+
if (currentEndIndex === -1 && currentStartIndex !== -1) {
|
|
6438
|
+
currentEndIndex = Math.min(currentItems.length - 1, currentStartIndex + (prevEndIndex - prevStartIndex));
|
|
6439
|
+
}
|
|
6440
|
+
this.virtualizedItemsSubject.next(currentItems.slice(currentStartIndex, currentEndIndex + 1));
|
|
6441
|
+
}
|
|
6442
|
+
}
|
|
6443
|
+
}));
|
|
6444
|
+
if (this.jumpToItem$) {
|
|
6445
|
+
this.subscriptions.push(this.jumpToItem$
|
|
6446
|
+
.pipe(switchMap((jumpToItem) => combineLatest([this.allItems$.pipe(take$1(1)), of(jumpToItem)])))
|
|
6447
|
+
.subscribe(([allItems, jumpToItem]) => {
|
|
6448
|
+
if (jumpToItem.item) {
|
|
6449
|
+
if (allItems.length < this.maxItemCount) {
|
|
6450
|
+
this.virtualizedItemsSubject.next(allItems);
|
|
6451
|
+
}
|
|
6452
|
+
else {
|
|
6453
|
+
const itemIndex = allItems.findIndex((i) =>
|
|
6454
|
+
// @ts-expect-error TODO: do we know a better typing here?
|
|
6455
|
+
this.isEqual(i, jumpToItem.item));
|
|
6456
|
+
if (itemIndex === -1) {
|
|
6457
|
+
return;
|
|
6458
|
+
}
|
|
6459
|
+
else {
|
|
6460
|
+
const position = jumpToItem.position || 'middle';
|
|
6461
|
+
const numberOfItemsToRemove = allItems.length - Math.round(this.maxItemCount / 2);
|
|
6462
|
+
const numberOfItemsAfterRemove = allItems.length - numberOfItemsToRemove;
|
|
6463
|
+
let startIndex = -1;
|
|
6464
|
+
let endIndex = -1;
|
|
6465
|
+
switch (position) {
|
|
6466
|
+
case 'top':
|
|
6467
|
+
startIndex = itemIndex;
|
|
6468
|
+
endIndex = Math.min(allItems.length, startIndex + numberOfItemsAfterRemove);
|
|
6469
|
+
break;
|
|
6470
|
+
case 'bottom':
|
|
6471
|
+
endIndex = itemIndex + 1;
|
|
6472
|
+
startIndex = Math.max(0, endIndex - numberOfItemsAfterRemove);
|
|
6473
|
+
break;
|
|
6474
|
+
case 'middle': {
|
|
6475
|
+
const itemsOnTop = itemIndex;
|
|
6476
|
+
const itemsOnBottom = allItems.length - itemIndex;
|
|
6477
|
+
if (itemsOnTop < Math.ceil(numberOfItemsAfterRemove / 2)) {
|
|
6478
|
+
startIndex = 0;
|
|
6479
|
+
}
|
|
6480
|
+
if (itemsOnBottom <
|
|
6481
|
+
Math.floor(numberOfItemsAfterRemove / 2) + 1) {
|
|
6482
|
+
endIndex = allItems.length;
|
|
6483
|
+
}
|
|
6484
|
+
if (startIndex === -1) {
|
|
6485
|
+
if (endIndex !== -1) {
|
|
6486
|
+
startIndex = endIndex - numberOfItemsAfterRemove;
|
|
6487
|
+
}
|
|
6488
|
+
else {
|
|
6489
|
+
startIndex =
|
|
6490
|
+
itemIndex - Math.ceil(numberOfItemsAfterRemove / 2);
|
|
6491
|
+
}
|
|
6492
|
+
}
|
|
6493
|
+
if (endIndex === -1) {
|
|
6494
|
+
endIndex = startIndex + numberOfItemsAfterRemove;
|
|
6495
|
+
}
|
|
6496
|
+
}
|
|
6497
|
+
}
|
|
6498
|
+
this.virtualizedItemsSubject.next(allItems.slice(startIndex, endIndex));
|
|
6499
|
+
}
|
|
6500
|
+
}
|
|
6501
|
+
}
|
|
6502
|
+
}));
|
|
6503
|
+
}
|
|
6504
|
+
}
|
|
6505
|
+
/**
|
|
6506
|
+
* The current value of virtualized items
|
|
6507
|
+
*/
|
|
6508
|
+
get virtualizedItems() {
|
|
6509
|
+
return this.virtualizedItemsSubject.getValue();
|
|
6510
|
+
}
|
|
6511
|
+
/**
|
|
6512
|
+
* Remove all subscriptions, call this once you're done using an instance of this service
|
|
6513
|
+
*/
|
|
6514
|
+
dispose() {
|
|
6515
|
+
this.subscriptions.forEach((s) => s.unsubscribe());
|
|
6516
|
+
}
|
|
6517
|
+
loadMoreFromBuffer(_) {
|
|
6518
|
+
this.loadFromBuffer$.next();
|
|
6519
|
+
}
|
|
6520
|
+
async loadMore(direction) {
|
|
6521
|
+
this.queryStateSubject.next({ state: `loading-${direction}` });
|
|
6522
|
+
try {
|
|
6523
|
+
await this.query(direction);
|
|
6524
|
+
this.queryStateSubject.next({ state: 'success' });
|
|
6525
|
+
}
|
|
6526
|
+
catch (e) {
|
|
6527
|
+
this.queryStateSubject.next({ state: 'error', error: e });
|
|
6528
|
+
}
|
|
6529
|
+
}
|
|
6530
|
+
}
|
|
6531
|
+
|
|
6532
|
+
/**
|
|
6533
|
+
* The `VirtualizedMessageListService` removes messages from the message list that are currently not in view
|
|
6534
|
+
*/
|
|
6535
|
+
class VirtualizedMessageListService extends VirtualizedListService {
|
|
6536
|
+
constructor(mode, scrollPosition$, channelService) {
|
|
6537
|
+
const jumpToMessage$ = channelService.jumpToMessage$.pipe(map$1((jumpToMessage) => {
|
|
6538
|
+
let result = {
|
|
6539
|
+
item: undefined,
|
|
6540
|
+
};
|
|
6541
|
+
let targetMessageId;
|
|
6542
|
+
if (mode === 'main') {
|
|
6543
|
+
targetMessageId = jumpToMessage.parentId
|
|
6544
|
+
? jumpToMessage.parentId
|
|
6545
|
+
: jumpToMessage.id;
|
|
6546
|
+
}
|
|
6547
|
+
else {
|
|
6548
|
+
targetMessageId = jumpToMessage.parentId
|
|
6549
|
+
? jumpToMessage.id
|
|
6550
|
+
: undefined;
|
|
6551
|
+
}
|
|
6552
|
+
if (targetMessageId) {
|
|
6553
|
+
const messages = mode === 'main'
|
|
6554
|
+
? channelService.activeChannelMessages
|
|
6555
|
+
: channelService.activeChannelThreadReplies;
|
|
6556
|
+
const id = targetMessageId === 'latest'
|
|
6557
|
+
? messages[messages.length - 1]?.id
|
|
6558
|
+
: targetMessageId;
|
|
6559
|
+
if (id) {
|
|
6560
|
+
result = {
|
|
6561
|
+
item: { id },
|
|
6562
|
+
position: jumpToMessage.id === 'latest' ? 'bottom' : 'middle',
|
|
6563
|
+
};
|
|
6564
|
+
}
|
|
6565
|
+
channelService.clearMessageJump();
|
|
6566
|
+
}
|
|
6567
|
+
return result;
|
|
6568
|
+
}));
|
|
6569
|
+
const messages$ = mode === 'main'
|
|
6570
|
+
? channelService.activeChannelMessages$
|
|
6571
|
+
: channelService.activeThreadMessages$;
|
|
6572
|
+
super(messages$, scrollPosition$, jumpToMessage$, channelService.messagePageSize);
|
|
6573
|
+
this.mode = mode;
|
|
6574
|
+
this.channelService = channelService;
|
|
6575
|
+
this.isEqual = (t1, t2) => t1.id === t2.id;
|
|
6576
|
+
this.query = (direction) => {
|
|
6577
|
+
const request = this.mode === 'main'
|
|
6578
|
+
? (direction) => this.channelService.loadMoreMessages(direction)
|
|
6579
|
+
: (direction) => this.channelService.loadMoreThreadReplies(direction);
|
|
6580
|
+
const result = request(direction === 'top' ? 'older' : 'newer');
|
|
6581
|
+
if (result) {
|
|
6582
|
+
return result;
|
|
6583
|
+
}
|
|
6584
|
+
else {
|
|
6585
|
+
this.queryStateSubject.next({ state: 'success' });
|
|
6586
|
+
if ((direction === 'top' && this.bufferOnTop > 0) ||
|
|
6587
|
+
(direction === 'bottom' && this.bufferOnBottom > 0)) {
|
|
6588
|
+
this.loadFromBuffer$.next();
|
|
6589
|
+
}
|
|
6590
|
+
return Promise.resolve();
|
|
6591
|
+
}
|
|
6592
|
+
};
|
|
6593
|
+
}
|
|
6594
|
+
loadMoreFromBuffer(direction) {
|
|
6595
|
+
this.queryStateSubject.next({ state: `loading-${direction}` });
|
|
6596
|
+
setTimeout(() => {
|
|
6597
|
+
this.loadFromBuffer$.next();
|
|
6598
|
+
this.queryStateSubject.next({ state: 'success' });
|
|
6599
|
+
});
|
|
6600
|
+
}
|
|
6601
|
+
}
|
|
6602
|
+
|
|
6274
6603
|
/**
|
|
6275
6604
|
* The `MessageList` component renders a scrollable list of messages.
|
|
6276
6605
|
*/
|
|
@@ -6321,10 +6650,6 @@ class MessageListComponent {
|
|
|
6321
6650
|
* You can turn on and off the loading indicator that signals to users that more messages are being loaded to the message list
|
|
6322
6651
|
*/
|
|
6323
6652
|
this.displayLoadingIndicator = true;
|
|
6324
|
-
/**
|
|
6325
|
-
* @internal
|
|
6326
|
-
*/
|
|
6327
|
-
this.limitNumberOfMessagesInList = true;
|
|
6328
6653
|
this.emptyMainMessageListTemplate = null;
|
|
6329
6654
|
this.emptyThreadMessageListTemplate = null;
|
|
6330
6655
|
this.enabledMessageActions = [];
|
|
@@ -6332,17 +6657,20 @@ class MessageListComponent {
|
|
|
6332
6657
|
this.newMessageCountWhileBeingScrolled = 0;
|
|
6333
6658
|
this.groupStyles = [];
|
|
6334
6659
|
this.isNextMessageOnSeparateDate = [];
|
|
6335
|
-
this.
|
|
6660
|
+
this.loadingState = 'idle';
|
|
6336
6661
|
this.isUnreadNotificationVisible = true;
|
|
6337
6662
|
this.isJumpingToLatestUnreadMessage = false;
|
|
6338
6663
|
this.isJumpToLatestButtonVisible = true;
|
|
6664
|
+
this.isJumpingToMessage = false;
|
|
6339
6665
|
this.scroll$ = new Subject();
|
|
6666
|
+
this.isNewMessageSentByUser = false;
|
|
6340
6667
|
this.subscriptions = [];
|
|
6341
6668
|
this.isLatestMessageInList = true;
|
|
6342
6669
|
this.parsedDates = new Map();
|
|
6343
6670
|
this.isViewInited = false;
|
|
6344
6671
|
this.isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
|
6345
6672
|
this.forceRepaintSubject = new Subject();
|
|
6673
|
+
this.scrollPosition$ = new BehaviorSubject('bottom');
|
|
6346
6674
|
this.messageNotificationJumpClicked = () => {
|
|
6347
6675
|
this.jumpToFirstUnreadMessage();
|
|
6348
6676
|
this.isUnreadNotificationVisible = false;
|
|
@@ -6361,7 +6689,6 @@ class MessageListComponent {
|
|
|
6361
6689
|
this.forceRepaint();
|
|
6362
6690
|
}));
|
|
6363
6691
|
this.subscriptions.push(this.channelService.activeChannel$.subscribe((channel) => {
|
|
6364
|
-
this.chatClientService.chatClient?.logger?.('info', `${channel?.cid || 'undefined'} selected`, { tags: `message list ${this.mode}` });
|
|
6365
6692
|
let isNewChannel = false;
|
|
6366
6693
|
if (this.channelId !== channel?.id) {
|
|
6367
6694
|
isNewChannel = true;
|
|
@@ -6369,12 +6696,9 @@ class MessageListComponent {
|
|
|
6369
6696
|
clearTimeout(this.checkIfUnreadNotificationIsVisibleTimeout);
|
|
6370
6697
|
}
|
|
6371
6698
|
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
6699
|
this.parsedDates = new Map();
|
|
6374
|
-
if (this.messageRemoveTimeout) {
|
|
6375
|
-
clearTimeout(this.messageRemoveTimeout);
|
|
6376
|
-
}
|
|
6377
6700
|
this.resetScrollState();
|
|
6701
|
+
this.setMessages$();
|
|
6378
6702
|
this.channelId = channel?.id;
|
|
6379
6703
|
if (this.isViewInited) {
|
|
6380
6704
|
this.cdRef.detectChanges();
|
|
@@ -6434,17 +6758,12 @@ class MessageListComponent {
|
|
|
6434
6758
|
this.newMessageSubscription?.unsubscribe();
|
|
6435
6759
|
if (channel) {
|
|
6436
6760
|
this.newMessageSubscription = channel.on('message.new', (event) => {
|
|
6437
|
-
|
|
6438
|
-
if (!event.message ||
|
|
6439
|
-
channel.state.messages === channel.state.latestMessages ||
|
|
6440
|
-
this.mode === 'thread') {
|
|
6761
|
+
if (!event.message) {
|
|
6441
6762
|
return;
|
|
6442
6763
|
}
|
|
6443
|
-
|
|
6444
|
-
|
|
6445
|
-
|
|
6446
|
-
created_at: new Date(event.message.created_at || ''),
|
|
6447
|
-
});
|
|
6764
|
+
else {
|
|
6765
|
+
this.newMessageReceived(event.message);
|
|
6766
|
+
}
|
|
6448
6767
|
});
|
|
6449
6768
|
}
|
|
6450
6769
|
}));
|
|
@@ -6454,6 +6773,7 @@ class MessageListComponent {
|
|
|
6454
6773
|
message.id !== this.parentMessage.id &&
|
|
6455
6774
|
this.mode === 'thread') {
|
|
6456
6775
|
this.resetScrollState();
|
|
6776
|
+
this.setMessages$();
|
|
6457
6777
|
}
|
|
6458
6778
|
if (this.parentMessage === message) {
|
|
6459
6779
|
return;
|
|
@@ -6508,41 +6828,6 @@ class MessageListComponent {
|
|
|
6508
6828
|
this.cdRef.detectChanges();
|
|
6509
6829
|
}
|
|
6510
6830
|
}));
|
|
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
6831
|
this.subscriptions.push(this.customTemplatesService.emptyMainMessageListPlaceholder$.subscribe((template) => {
|
|
6547
6832
|
const isChanged = this.emptyMainMessageListTemplate !== template;
|
|
6548
6833
|
this.emptyMainMessageListTemplate = template || null;
|
|
@@ -6561,6 +6846,7 @@ class MessageListComponent {
|
|
|
6561
6846
|
}
|
|
6562
6847
|
ngOnChanges(changes) {
|
|
6563
6848
|
if (changes.mode || changes.direction) {
|
|
6849
|
+
this.resetScrollState();
|
|
6564
6850
|
this.setMessages$();
|
|
6565
6851
|
}
|
|
6566
6852
|
if (changes.direction) {
|
|
@@ -6572,51 +6858,27 @@ class MessageListComponent {
|
|
|
6572
6858
|
ngAfterViewInit() {
|
|
6573
6859
|
this.isViewInited = true;
|
|
6574
6860
|
this.ngZone.runOutsideAngular(() => {
|
|
6575
|
-
this.scrollContainer
|
|
6861
|
+
this.scrollContainer?.nativeElement?.addEventListener('scroll', () => this.scrolled());
|
|
6576
6862
|
});
|
|
6577
6863
|
}
|
|
6578
6864
|
ngAfterViewChecked() {
|
|
6579
|
-
if (this.
|
|
6580
|
-
|
|
6581
|
-
this.
|
|
6582
|
-
this.
|
|
6865
|
+
if (this.isJumpingToMessage) {
|
|
6866
|
+
this.isNewMessageSentByUser = false;
|
|
6867
|
+
this.messageIdToAnchorTo = undefined;
|
|
6868
|
+
this.anchorMessageTopOffset = undefined;
|
|
6869
|
+
return;
|
|
6583
6870
|
}
|
|
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
|
-
}
|
|
6871
|
+
if (this.messageIdToAnchorTo && this.loadingState === 'idle') {
|
|
6872
|
+
this.preserveScrollbarPosition();
|
|
6593
6873
|
}
|
|
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
|
-
}
|
|
6874
|
+
else if ((!this.isUserScrolled &&
|
|
6875
|
+
this.scrollContainer.nativeElement?.scrollHeight >
|
|
6876
|
+
this.scrollContainer?.nativeElement.clientHeight &&
|
|
6877
|
+
this.getScrollPosition() !==
|
|
6878
|
+
(this.direction === 'bottom-to-top' ? 'bottom' : 'top')) ||
|
|
6879
|
+
(this.isUserScrolled && this.isNewMessageSentByUser)) {
|
|
6880
|
+
this.isNewMessageSentByUser = false;
|
|
6881
|
+
this.jumpToLatestMessage();
|
|
6620
6882
|
}
|
|
6621
6883
|
}
|
|
6622
6884
|
ngOnDestroy() {
|
|
@@ -6631,19 +6893,23 @@ class MessageListComponent {
|
|
|
6631
6893
|
if (this.jumpToLatestButtonVisibilityTimeout) {
|
|
6632
6894
|
clearTimeout(this.jumpToLatestButtonVisibilityTimeout);
|
|
6633
6895
|
}
|
|
6634
|
-
|
|
6635
|
-
clearTimeout(this.messageRemoveTimeout);
|
|
6636
|
-
}
|
|
6637
|
-
this.removeOldMessagesSubscription?.unsubscribe();
|
|
6896
|
+
this.disposeVirtualizedList();
|
|
6638
6897
|
}
|
|
6639
|
-
trackByMessageId(
|
|
6898
|
+
trackByMessageId(_, item) {
|
|
6640
6899
|
return item.id;
|
|
6641
6900
|
}
|
|
6642
|
-
trackByUserId(
|
|
6901
|
+
trackByUserId(_, user) {
|
|
6643
6902
|
return user.id;
|
|
6644
6903
|
}
|
|
6645
6904
|
jumpToLatestMessage() {
|
|
6646
|
-
|
|
6905
|
+
if (this.isLatestMessageInList) {
|
|
6906
|
+
this.direction === 'bottom-to-top'
|
|
6907
|
+
? this.scrollToBottom()
|
|
6908
|
+
: this.scrollToTop();
|
|
6909
|
+
}
|
|
6910
|
+
else {
|
|
6911
|
+
void this.channelService.jumpToMessage('latest', this.mode === 'thread' ? this.parentMessage?.id : undefined);
|
|
6912
|
+
}
|
|
6647
6913
|
}
|
|
6648
6914
|
scrollToBottom() {
|
|
6649
6915
|
this.scrollContainer.nativeElement.scrollTop =
|
|
@@ -6666,8 +6932,7 @@ class MessageListComponent {
|
|
|
6666
6932
|
return;
|
|
6667
6933
|
}
|
|
6668
6934
|
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}` });
|
|
6935
|
+
let scrollPosition = this.getScrollPosition();
|
|
6671
6936
|
const isUserScrolled = (this.direction === 'bottom-to-top'
|
|
6672
6937
|
? scrollPosition !== 'bottom'
|
|
6673
6938
|
: scrollPosition !== 'top') || !this.isLatestMessageInList;
|
|
@@ -6696,30 +6961,33 @@ class MessageListComponent {
|
|
|
6696
6961
|
}
|
|
6697
6962
|
}, 100);
|
|
6698
6963
|
}
|
|
6699
|
-
|
|
6964
|
+
const prevScrollPosition = this.scrollPosition$.getValue();
|
|
6965
|
+
if (this.direction === 'top-to-bottom') {
|
|
6966
|
+
if (scrollPosition === 'top') {
|
|
6967
|
+
scrollPosition = 'bottom';
|
|
6968
|
+
}
|
|
6969
|
+
else if (scrollPosition === 'bottom') {
|
|
6970
|
+
scrollPosition = 'top';
|
|
6971
|
+
}
|
|
6972
|
+
}
|
|
6973
|
+
if (prevScrollPosition !== scrollPosition && !this.isJumpingToMessage) {
|
|
6974
|
+
if (scrollPosition === 'top' || scrollPosition === 'bottom') {
|
|
6975
|
+
this.virtualizedList?.virtualizedItems$
|
|
6976
|
+
.pipe(take(1))
|
|
6977
|
+
.subscribe((items) => {
|
|
6978
|
+
this.messageIdToAnchorTo =
|
|
6979
|
+
scrollPosition === 'top'
|
|
6980
|
+
? items[0]?.id
|
|
6981
|
+
: items[items.length - 1]?.id;
|
|
6982
|
+
this.anchorMessageTopOffset = document
|
|
6983
|
+
.getElementById(this.messageIdToAnchorTo)
|
|
6984
|
+
?.getBoundingClientRect()?.top;
|
|
6985
|
+
});
|
|
6986
|
+
}
|
|
6700
6987
|
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();
|
|
6988
|
+
this.scrollPosition$.next(scrollPosition);
|
|
6720
6989
|
});
|
|
6721
6990
|
}
|
|
6722
|
-
this.prevScrollTop = this.scrollContainer.nativeElement.scrollTop;
|
|
6723
6991
|
}
|
|
6724
6992
|
jumpToFirstUnreadMessage() {
|
|
6725
6993
|
if (!this.lastReadMessageId) {
|
|
@@ -6760,9 +7028,18 @@ class MessageListComponent {
|
|
|
6760
7028
|
: this.emptyThreadMessageListTemplate;
|
|
6761
7029
|
}
|
|
6762
7030
|
preserveScrollbarPosition() {
|
|
6763
|
-
this.
|
|
6764
|
-
|
|
6765
|
-
|
|
7031
|
+
if (!this.messageIdToAnchorTo) {
|
|
7032
|
+
return;
|
|
7033
|
+
}
|
|
7034
|
+
const messageToAlignTo = document.getElementById(this.messageIdToAnchorTo);
|
|
7035
|
+
this.messageIdToAnchorTo = undefined;
|
|
7036
|
+
this.scrollContainer.nativeElement.scrollTop +=
|
|
7037
|
+
(messageToAlignTo?.getBoundingClientRect()?.top || 0) -
|
|
7038
|
+
(this.anchorMessageTopOffset || 0);
|
|
7039
|
+
this.anchorMessageTopOffset = undefined;
|
|
7040
|
+
if (this.isSafari) {
|
|
7041
|
+
this.forceRepaintSubject.next();
|
|
7042
|
+
}
|
|
6766
7043
|
}
|
|
6767
7044
|
forceRepaint() {
|
|
6768
7045
|
// Solves the issue of empty screen on Safari when scrolling
|
|
@@ -6773,10 +7050,7 @@ class MessageListComponent {
|
|
|
6773
7050
|
getScrollPosition() {
|
|
6774
7051
|
let position = 'middle';
|
|
6775
7052
|
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))) {
|
|
7053
|
+
(this.parentMessageElement?.nativeElement.clientHeight || 0)) {
|
|
6780
7054
|
position = 'top';
|
|
6781
7055
|
}
|
|
6782
7056
|
else if (Math.ceil(this.scrollContainer.nativeElement.scrollTop) +
|
|
@@ -6787,22 +7061,23 @@ class MessageListComponent {
|
|
|
6787
7061
|
}
|
|
6788
7062
|
return position;
|
|
6789
7063
|
}
|
|
6790
|
-
shouldLoadMoreMessages(scrollPosition) {
|
|
6791
|
-
return (scrollPosition !== 'middle' &&
|
|
6792
|
-
!this.highlightedMessageId &&
|
|
6793
|
-
!this.isLoading);
|
|
6794
|
-
}
|
|
6795
7064
|
setMessages$() {
|
|
6796
|
-
this.
|
|
6797
|
-
|
|
6798
|
-
|
|
6799
|
-
|
|
6800
|
-
|
|
7065
|
+
this.disposeVirtualizedList();
|
|
7066
|
+
this.virtualizedList = new VirtualizedMessageListService(this.mode, this.scrollPosition$, this.channelService);
|
|
7067
|
+
this.queryStateSubscription = this.virtualizedList.queryState$.subscribe((queryState) => {
|
|
7068
|
+
let mappedState = 'idle';
|
|
7069
|
+
if (queryState.state.includes('loading')) {
|
|
7070
|
+
mappedState = queryState.state || 'loading-bottom';
|
|
7071
|
+
}
|
|
7072
|
+
if (mappedState !== this.loadingState) {
|
|
7073
|
+
this.loadingState = mappedState;
|
|
7074
|
+
if (this.isViewInited) {
|
|
7075
|
+
this.cdRef.detectChanges();
|
|
7076
|
+
}
|
|
6801
7077
|
}
|
|
7078
|
+
});
|
|
7079
|
+
this.messages$ = this.virtualizedList.virtualizedItems$.pipe(tap((messages) => {
|
|
6802
7080
|
if (messages.length === 0) {
|
|
6803
|
-
this.chatClientService.chatClient?.logger?.('info', `Empty messages array, reseting scroll state`, {
|
|
6804
|
-
tags: `message list ${this.mode}`,
|
|
6805
|
-
});
|
|
6806
7081
|
this.resetScrollState();
|
|
6807
7082
|
return;
|
|
6808
7083
|
}
|
|
@@ -6810,21 +7085,6 @@ class MessageListComponent {
|
|
|
6810
7085
|
// cdRef.detectChanges() isn't enough here, test will fail
|
|
6811
7086
|
setTimeout(() => (this.isEmpty = false), 0);
|
|
6812
7087
|
}
|
|
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
7088
|
}), tap((messages) => {
|
|
6829
7089
|
if (this.isJumpingToLatestUnreadMessage &&
|
|
6830
7090
|
!this.firstUnreadMessageId &&
|
|
@@ -6837,82 +7097,98 @@ class MessageListComponent {
|
|
|
6837
7097
|
.reverse()
|
|
6838
7098
|
.find((m) => m.user?.id === this.chatClientService.chatClient?.user?.id &&
|
|
6839
7099
|
m.status !== 'sending')?.id)), tap((messages) => {
|
|
7100
|
+
const latestMessageInList = messages[messages.length - 1];
|
|
7101
|
+
const channel = this.channelService.activeChannel;
|
|
7102
|
+
const messagesFromState = (this.mode === 'main'
|
|
7103
|
+
? channel?.state.latestMessages
|
|
7104
|
+
: channel?.state.threads[this.parentMessage?.id || '']) || [];
|
|
6840
7105
|
this.isLatestMessageInList =
|
|
6841
|
-
!
|
|
6842
|
-
|
|
6843
|
-
|
|
6844
|
-
|
|
7106
|
+
!latestMessageInList ||
|
|
7107
|
+
latestMessageInList.cid !== channel?.cid ||
|
|
7108
|
+
latestMessageInList?.id ===
|
|
7109
|
+
messagesFromState[messagesFromState.length - 1]?.id;
|
|
6845
7110
|
if (!this.isLatestMessageInList) {
|
|
6846
7111
|
this.isUserScrolled = true;
|
|
6847
7112
|
}
|
|
6848
|
-
}), map((messages) =>
|
|
7113
|
+
}), map((messages) => {
|
|
7114
|
+
return this.direction === 'bottom-to-top'
|
|
7115
|
+
? messages
|
|
7116
|
+
: [...messages].reverse();
|
|
7117
|
+
}), tap((messages) => {
|
|
6849
7118
|
this.groupStyles = messages.map((m, i) => getGroupStyles(m, messages[i - 1], messages[i + 1], {
|
|
6850
7119
|
lastReadMessageId: this.lastReadMessageId,
|
|
6851
7120
|
}));
|
|
6852
7121
|
this.isNextMessageOnSeparateDate = messages.map((m, i) => this.checkIfOnSeparateDates(m, messages[i + 1]));
|
|
6853
7122
|
}), 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);
|
|
7123
|
+
if (this.virtualizedList?.jumpToItem$) {
|
|
7124
|
+
this.jumpToItemSubscription = this.virtualizedList.jumpToItem$
|
|
7125
|
+
.pipe(filter((jumpToMessage) => !!jumpToMessage.item?.id))
|
|
7126
|
+
.subscribe((jumpToMessage) => {
|
|
7127
|
+
let messageId = jumpToMessage.item?.id;
|
|
7128
|
+
if (messageId) {
|
|
7129
|
+
if (this.isJumpingToLatestUnreadMessage) {
|
|
7130
|
+
messageId = this.firstUnreadMessageId || messageId;
|
|
7131
|
+
}
|
|
7132
|
+
if (jumpToMessage.position !== 'bottom') {
|
|
7133
|
+
this.highlightedMessageId = messageId;
|
|
7134
|
+
}
|
|
7135
|
+
else if (this.direction === 'top-to-bottom') {
|
|
7136
|
+
jumpToMessage.position = 'top';
|
|
7137
|
+
}
|
|
7138
|
+
this.isJumpingToMessage = true;
|
|
7139
|
+
this.scrollMessageIntoView({
|
|
7140
|
+
messageId: this.firstUnreadMessageId || messageId,
|
|
7141
|
+
position: jumpToMessage.position || 'middle',
|
|
7142
|
+
});
|
|
6884
7143
|
}
|
|
6885
|
-
}
|
|
6886
|
-
}
|
|
7144
|
+
});
|
|
7145
|
+
}
|
|
6887
7146
|
}
|
|
6888
7147
|
resetScrollState() {
|
|
6889
7148
|
this.isEmpty = true;
|
|
6890
|
-
this.latestMessage = undefined;
|
|
6891
|
-
this.hasNewMessages = true;
|
|
6892
7149
|
this.isUserScrolled = false;
|
|
6893
|
-
this.
|
|
6894
|
-
this.
|
|
6895
|
-
this.oldestMessage = undefined;
|
|
7150
|
+
this.messageIdToAnchorTo = undefined;
|
|
7151
|
+
this.anchorMessageTopOffset = undefined;
|
|
6896
7152
|
this.newMessageCountWhileBeingScrolled = 0;
|
|
6897
|
-
this.
|
|
6898
|
-
this.isNewMessageSentByUser = undefined;
|
|
7153
|
+
this.isNewMessageSentByUser = false;
|
|
6899
7154
|
this.isLatestMessageInList = true;
|
|
7155
|
+
this.isJumpingToMessage = false;
|
|
7156
|
+
this.scrollPosition$.next('bottom');
|
|
7157
|
+
this.loadingState = 'idle';
|
|
7158
|
+
}
|
|
7159
|
+
disposeVirtualizedList() {
|
|
7160
|
+
this.virtualizedList?.dispose();
|
|
7161
|
+
this.jumpToItemSubscription?.unsubscribe();
|
|
7162
|
+
this.queryStateSubscription?.unsubscribe();
|
|
6900
7163
|
}
|
|
6901
7164
|
get usersTyping$() {
|
|
6902
7165
|
return this.mode === 'thread'
|
|
6903
7166
|
? this.usersTypingInThread$
|
|
6904
7167
|
: this.usersTypingInChannel$;
|
|
6905
7168
|
}
|
|
6906
|
-
scrollMessageIntoView(
|
|
6907
|
-
const element = document.getElementById(messageId);
|
|
7169
|
+
scrollMessageIntoView(options, withRetry = true) {
|
|
7170
|
+
const element = document.getElementById(options.messageId);
|
|
6908
7171
|
if (!element && withRetry) {
|
|
6909
7172
|
// 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(
|
|
7173
|
+
setTimeout(() => this.scrollMessageIntoView(options, false));
|
|
6911
7174
|
}
|
|
6912
7175
|
else if (element) {
|
|
7176
|
+
const blockMapping = {
|
|
7177
|
+
top: 'start',
|
|
7178
|
+
bottom: 'end',
|
|
7179
|
+
middle: 'center',
|
|
7180
|
+
};
|
|
6913
7181
|
element.scrollIntoView({
|
|
6914
|
-
block:
|
|
7182
|
+
block: blockMapping[options.position],
|
|
6915
7183
|
});
|
|
7184
|
+
if (options.position !== 'middle') {
|
|
7185
|
+
options.position === 'bottom'
|
|
7186
|
+
? this.scrollToBottom()
|
|
7187
|
+
: this.scrollToTop();
|
|
7188
|
+
}
|
|
7189
|
+
setTimeout(() => {
|
|
7190
|
+
this.isJumpingToMessage = false;
|
|
7191
|
+
}, 0);
|
|
6916
7192
|
setTimeout(() => {
|
|
6917
7193
|
this.highlightedMessageId = undefined;
|
|
6918
7194
|
this.firstUnreadMessageId = undefined;
|
|
@@ -6921,39 +7197,26 @@ class MessageListComponent {
|
|
|
6921
7197
|
}, 1000);
|
|
6922
7198
|
}
|
|
6923
7199
|
}
|
|
6924
|
-
|
|
6925
|
-
if (
|
|
6926
|
-
this.
|
|
6927
|
-
|
|
6928
|
-
: this.scrollToTop();
|
|
7200
|
+
newMessageReceived(message) {
|
|
7201
|
+
if ((this.mode === 'main' && message.parent_id) ||
|
|
7202
|
+
(this.mode === 'thread' && message.parent_id !== this.parentMessage?.id)) {
|
|
7203
|
+
return;
|
|
6929
7204
|
}
|
|
6930
|
-
|
|
6931
|
-
|
|
6932
|
-
|
|
7205
|
+
const isNewMessageSentByCurrentUser = message.user?.id === this.chatClientService.chatClient?.user?.id;
|
|
7206
|
+
let shouldDetectChanges = false;
|
|
7207
|
+
if (!this.isNewMessageSentByUser && isNewMessageSentByCurrentUser) {
|
|
7208
|
+
this.isNewMessageSentByUser = true;
|
|
7209
|
+
shouldDetectChanges = true;
|
|
6933
7210
|
}
|
|
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
|
-
}
|
|
7211
|
+
if (this.isUserScrolled) {
|
|
7212
|
+
this.newMessageCountWhileBeingScrolled++;
|
|
7213
|
+
shouldDetectChanges = true;
|
|
7214
|
+
}
|
|
7215
|
+
if (!this.isNewMessageSentByUser && this.unreadCount !== undefined) {
|
|
7216
|
+
this.unreadCount++;
|
|
7217
|
+
shouldDetectChanges = true;
|
|
7218
|
+
}
|
|
7219
|
+
if (shouldDetectChanges && this.isViewInited) {
|
|
6957
7220
|
this.cdRef.detectChanges();
|
|
6958
7221
|
}
|
|
6959
7222
|
}
|
|
@@ -6965,10 +7228,10 @@ class MessageListComponent {
|
|
|
6965
7228
|
}
|
|
6966
7229
|
}
|
|
6967
7230
|
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 });
|
|
7231
|
+
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
7232
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: MessageListComponent, decorators: [{
|
|
6970
7233
|
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" }]
|
|
7234
|
+
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
7235
|
}], ctorParameters: function () { return [{ type: ChannelService }, { type: ChatClientService }, { type: CustomTemplatesService }, { type: DateParserService }, { type: i0.NgZone }, { type: i0.ChangeDetectorRef }]; }, propDecorators: { mode: [{
|
|
6973
7236
|
type: Input
|
|
6974
7237
|
}], direction: [{
|
|
@@ -6987,8 +7250,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImpor
|
|
|
6987
7250
|
type: Input
|
|
6988
7251
|
}], displayLoadingIndicator: [{
|
|
6989
7252
|
type: Input
|
|
6990
|
-
}], limitNumberOfMessagesInList: [{
|
|
6991
|
-
type: Input
|
|
6992
7253
|
}], scrollContainer: [{
|
|
6993
7254
|
type: ViewChild,
|
|
6994
7255
|
args: ['scrollContainer']
|
|
@@ -7235,5 +7496,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImpor
|
|
|
7235
7496
|
* Generated bundle index. Do not edit.
|
|
7236
7497
|
*/
|
|
7237
7498
|
|
|
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 };
|
|
7499
|
+
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
7500
|
//# sourceMappingURL=stream-chat-angular.mjs.map
|