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.
@@ -1,7 +1,7 @@
1
1
  import { __awaiter } from 'tslib';
2
2
  import * as i0 from '@angular/core';
3
3
  import { Injectable, Component, Input, EventEmitter, Output, ViewChild, HostBinding, ChangeDetectionStrategy, InjectionToken, Directive, Inject, NgModule } from '@angular/core';
4
- import { BehaviorSubject, ReplaySubject, combineLatest, take as take$1, Subject, timer } from 'rxjs';
4
+ 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';
5
5
  import { StreamChat } from 'stream-chat';
6
6
  import { take, shareReplay, map, first, filter, tap, debounceTime, throttleTime } from 'rxjs/operators';
7
7
  import { v4 } from 'uuid';
@@ -20,7 +20,7 @@ import transliterate from '@stream-io/transliterate';
20
20
  import * as i8$1 from 'angular-mentions';
21
21
  import { MentionModule } from 'angular-mentions';
22
22
 
23
- const version = '5.0.0';
23
+ const version = '5.1.0';
24
24
 
25
25
  /**
26
26
  * The `NotificationService` can be used to add or remove notifications. By default the [`NotificationList`](../components/NotificationListComponent.mdx) component displays the currently active notifications.
@@ -437,6 +437,7 @@ class ChannelService {
437
437
  this.chatClientService = chatClientService;
438
438
  this.ngZone = ngZone;
439
439
  this.notificationService = notificationService;
440
+ this.messagePageSize = 25;
440
441
  this.channelsSubject = new BehaviorSubject(undefined);
441
442
  this.activeChannelSubject = new BehaviorSubject(undefined);
442
443
  this.activeChannelMessagesSubject = new BehaviorSubject([]);
@@ -448,7 +449,6 @@ class ChannelService {
448
449
  this.activeThreadMessagesSubject = new BehaviorSubject([]);
449
450
  this.jumpToMessageSubject = new BehaviorSubject({ id: undefined, parentId: undefined });
450
451
  this.latestMessageDateByUserByChannelsSubject = new BehaviorSubject({});
451
- this.messagePageSize = 25;
452
452
  this.attachmentMaxSizeFallbackInMB = 100;
453
453
  this.messageToQuoteSubject = new BehaviorSubject(undefined);
454
454
  this.usersTypingInChannelSubject = new BehaviorSubject([]);
@@ -556,23 +556,6 @@ class ChannelService {
556
556
  .asObservable()
557
557
  .pipe(shareReplay(1));
558
558
  }
559
- /**
560
- * internal
561
- */
562
- removeOldMessageFromMessageList() {
563
- const channel = this.activeChannelSubject.getValue();
564
- const channelMessages = channel === null || channel === void 0 ? void 0 : channel.state.latestMessages;
565
- const targetLength = Math.ceil(ChannelService.MAX_MESSAGE_COUNT_IN_MESSAGE_LIST / 2);
566
- if (!channel ||
567
- !channelMessages ||
568
- channelMessages !== (channel === null || channel === void 0 ? void 0 : channel.state.latestMessages) ||
569
- channelMessages.length <= targetLength) {
570
- return;
571
- }
572
- const messages = channelMessages;
573
- messages.splice(0, messages.length - targetLength);
574
- this.activeChannelMessagesSubject.next(messages);
575
- }
576
559
  /**
577
560
  * 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.
578
561
  */
@@ -1469,6 +1452,12 @@ class ChannelService {
1469
1452
  get activeChannelMessages() {
1470
1453
  return this.activeChannelMessagesSubject.getValue() || [];
1471
1454
  }
1455
+ /**
1456
+ * The current thread replies
1457
+ */
1458
+ get activeChannelThreadReplies() {
1459
+ return this.activeThreadMessagesSubject.getValue() || [];
1460
+ }
1472
1461
  /**
1473
1462
  * 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).
1474
1463
  * @param messageId
@@ -1556,7 +1545,7 @@ class ChannelService {
1556
1545
  return;
1557
1546
  }
1558
1547
  const messageIndex = messages.findIndex((m) => { var _a; return m.id === ((_a = event === null || event === void 0 ? void 0 : event.message) === null || _a === void 0 ? void 0 : _a.id); });
1559
- if (messageIndex !== -1) {
1548
+ if (messageIndex !== -1 || event.type === 'message.deleted') {
1560
1549
  isThreadReply
1561
1550
  ? this.activeThreadMessagesSubject.next([...messages])
1562
1551
  : this.activeChannelMessagesSubject.next([...messages]);
@@ -1944,10 +1933,6 @@ class ChannelService {
1944
1933
  });
1945
1934
  }
1946
1935
  }
1947
- /**
1948
- * @internal
1949
- */
1950
- ChannelService.MAX_MESSAGE_COUNT_IN_MESSAGE_LIST = 250;
1951
1936
  /**
1952
1937
  * @internal
1953
1938
  */
@@ -2725,6 +2710,9 @@ class AvatarComponent {
2725
2710
  this.setFallbackChannelImage();
2726
2711
  }
2727
2712
  }
2713
+ ngOnDestroy() {
2714
+ this.subscriptions.forEach((s) => s.unsubscribe());
2715
+ }
2728
2716
  setFallbackChannelImage() {
2729
2717
  if (this.type !== 'channel') {
2730
2718
  this.fallbackChannelImage = undefined;
@@ -3204,7 +3192,7 @@ class MessageActionsService {
3204
3192
  actionHandler: (message) => {
3205
3193
  void this.channelService.markMessageUnread(message.id);
3206
3194
  },
3207
- isVisible: (enabledActions, isMine, message) => enabledActions.indexOf('read-events') !== -1 && !message.parent_id,
3195
+ isVisible: (enabledActions, _, message) => enabledActions.indexOf('read-events') !== -1 && !message.parent_id,
3208
3196
  },
3209
3197
  {
3210
3198
  actionName: 'quote',
@@ -3220,7 +3208,7 @@ class MessageActionsService {
3220
3208
  actionHandler: (message) => {
3221
3209
  void this.channelService.setAsActiveParentMessage(message);
3222
3210
  },
3223
- isVisible: (enabledActions, isMine, message) => enabledActions.indexOf('send-reply') !== -1 && !message.parent_id,
3211
+ isVisible: (enabledActions, _, message) => enabledActions.indexOf('send-reply') !== -1 && !message.parent_id,
3224
3212
  },
3225
3213
  {
3226
3214
  actionName: 'pin',
@@ -3241,7 +3229,7 @@ class MessageActionsService {
3241
3229
  yield this.chatClientService.flagMessage(message.id);
3242
3230
  this.notificationService.addTemporaryNotification('streamChat.Message has been successfully flagged', 'success');
3243
3231
  }
3244
- catch (err) {
3232
+ catch (error) {
3245
3233
  this.notificationService.addTemporaryNotification('streamChat.Error adding flag');
3246
3234
  }
3247
3235
  }),
@@ -4158,7 +4146,7 @@ class ChannelListComponent {
4158
4146
  this.isLoadingMoreChannels = false;
4159
4147
  });
4160
4148
  }
4161
- trackByChannelId(index, item) {
4149
+ trackByChannelId(_, item) {
4162
4150
  return item.cid;
4163
4151
  }
4164
4152
  }
@@ -4408,7 +4396,7 @@ class VoiceRecordingComponent {
4408
4396
  : this.audioElement.nativeElement.pause();
4409
4397
  this.isError = false;
4410
4398
  }
4411
- catch (e) {
4399
+ catch (error) {
4412
4400
  this.isError = true;
4413
4401
  }
4414
4402
  });
@@ -6365,6 +6353,350 @@ const isOnSameDay = (date1, date2) => {
6365
6353
  date1.getDate() === date2.getDate());
6366
6354
  };
6367
6355
 
6356
+ /**
6357
+ * The `VirtualizedListService` removes items from a list that are not currently displayed. This is a high-level overview of how it works:
6358
+ * - Create a new instance for each list that needs virtualization
6359
+ * - Input: Provide a reactive stream that emits all items in the list
6360
+ * - Input: Provide a reactive stream that emit the current scroll position (top, middle or bottom)
6361
+ * - 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)
6362
+ * - Output: The service will emit the current list of displayed items via the virtualized items reactive stream
6363
+ * - 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:
6364
+ * - If scroll location is bottom/top items around the current bottom/top item will be emitted in the virtualized items stream
6365
+ * - 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)
6366
+ * - 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
6367
+ * - 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
6368
+ * - Input: you should provide the page size to use, in order for the service to determine if loading is necessary
6369
+ *
6370
+ * The `VirtualizedMessageListService` provides an implementation for the message list component.
6371
+ */
6372
+ class VirtualizedListService {
6373
+ constructor(allItems$, scrollPosition$, jumpToItem$, pageSize = 25, maxItemCount = pageSize * 4) {
6374
+ this.allItems$ = allItems$;
6375
+ this.scrollPosition$ = scrollPosition$;
6376
+ this.jumpToItem$ = jumpToItem$;
6377
+ this.pageSize = pageSize;
6378
+ this.maxItemCount = maxItemCount;
6379
+ this.queryStateSubject = new BehaviorSubject({
6380
+ state: 'success',
6381
+ });
6382
+ this.bufferOnTop = 0;
6383
+ this.bufferOnBottom = 0;
6384
+ this.loadFromBuffer$ = new Subject();
6385
+ this.virtualizedItemsSubject = new BehaviorSubject([]);
6386
+ this.subscriptions = [];
6387
+ this.virtualizedItems$ = this.virtualizedItemsSubject.asObservable();
6388
+ this.queryState$ = this.queryStateSubject.asObservable();
6389
+ this.subscriptions.push(this.virtualizedItems$.subscribe((virtaluzedItems) => {
6390
+ this.allItems$.pipe(take$1(1)).subscribe((allItems) => {
6391
+ if (virtaluzedItems.length === allItems.length) {
6392
+ this.bufferOnTop = 0;
6393
+ this.bufferOnBottom = 0;
6394
+ }
6395
+ else if (virtaluzedItems.length === 0) {
6396
+ this.bufferOnTop = allItems.length;
6397
+ this.bufferOnBottom = 0;
6398
+ }
6399
+ else {
6400
+ this.bufferOnTop = allItems.indexOf(virtaluzedItems[0]);
6401
+ this.bufferOnBottom =
6402
+ allItems.length -
6403
+ allItems.indexOf(virtaluzedItems[virtaluzedItems.length - 1]) -
6404
+ 1;
6405
+ }
6406
+ });
6407
+ }));
6408
+ this.subscriptions.push(merge(this.allItems$, this.loadFromBuffer$)
6409
+ .pipe(switchMap(() => {
6410
+ return combineLatest([
6411
+ this.allItems$.pipe(take$1(1)),
6412
+ this.scrollPosition$.pipe(take$1(1)),
6413
+ ]);
6414
+ }))
6415
+ .subscribe(([items, scrollPosition]) => {
6416
+ if (scrollPosition === 'middle') {
6417
+ return;
6418
+ }
6419
+ const currentItems = this.virtualizedItemsSubject.getValue();
6420
+ if (items.length <= this.maxItemCount) {
6421
+ this.virtualizedItemsSubject.next(items);
6422
+ }
6423
+ else {
6424
+ let startIndex = 0;
6425
+ let endIndex = undefined;
6426
+ const numberOfItemsToRemove = items.length - Math.round(this.maxItemCount / 2);
6427
+ const numberOfItemsAfterRemove = items.length - numberOfItemsToRemove;
6428
+ switch (scrollPosition) {
6429
+ case 'top':
6430
+ if (currentItems.length > 0) {
6431
+ const middleIndex = items.findIndex((i) => this.isEqual(i, currentItems[0]));
6432
+ if (middleIndex !== -1) {
6433
+ startIndex = Math.max(0, middleIndex - Math.ceil(numberOfItemsAfterRemove / 2));
6434
+ endIndex = startIndex + numberOfItemsAfterRemove;
6435
+ }
6436
+ }
6437
+ else {
6438
+ endIndex = numberOfItemsAfterRemove;
6439
+ }
6440
+ break;
6441
+ case 'bottom':
6442
+ if (currentItems.length > 0) {
6443
+ const middleIndex = items.findIndex((i) => this.isEqual(i, currentItems[currentItems.length - 1]));
6444
+ if (middleIndex !== -1) {
6445
+ endIndex = Math.min(items.length, middleIndex + Math.floor(numberOfItemsAfterRemove / 2) + 1);
6446
+ startIndex = endIndex - numberOfItemsAfterRemove;
6447
+ }
6448
+ }
6449
+ else {
6450
+ startIndex = items.length - numberOfItemsAfterRemove;
6451
+ }
6452
+ break;
6453
+ }
6454
+ const virtualizedItems = items.slice(startIndex, endIndex);
6455
+ this.virtualizedItemsSubject.next(virtualizedItems);
6456
+ }
6457
+ }));
6458
+ this.subscriptions.push(this.scrollPosition$
6459
+ .pipe(distinctUntilChanged())
6460
+ .subscribe((position) => {
6461
+ if (this.queryStateSubject.getValue().state === `loading-${position}`) {
6462
+ return;
6463
+ }
6464
+ if (position === 'top') {
6465
+ if (this.bufferOnTop < this.pageSize) {
6466
+ void this.loadMore(position);
6467
+ }
6468
+ else {
6469
+ this.loadMoreFromBuffer('top');
6470
+ }
6471
+ }
6472
+ else if (position === 'bottom') {
6473
+ if (this.bufferOnBottom < this.pageSize) {
6474
+ void this.loadMore(position);
6475
+ }
6476
+ else {
6477
+ this.loadMoreFromBuffer('bottom');
6478
+ }
6479
+ }
6480
+ }));
6481
+ this.subscriptions.push(this.allItems$
6482
+ .pipe(pairwise(), filter$1(() => {
6483
+ let scrollPosition;
6484
+ this.scrollPosition$
6485
+ .pipe(take$1(1))
6486
+ .subscribe((s) => (scrollPosition = s));
6487
+ return scrollPosition === 'middle';
6488
+ }))
6489
+ .subscribe(([prevItems, currentItems]) => {
6490
+ if (currentItems.length < this.maxItemCount ||
6491
+ this.virtualizedItems.length === 0) {
6492
+ this.virtualizedItemsSubject.next(currentItems);
6493
+ }
6494
+ else {
6495
+ const currentFirstItem = this.virtualizedItems[0];
6496
+ const currentLastItem = this.virtualizedItems[this.virtualizedItems.length - 1];
6497
+ const prevStartIndex = prevItems.findIndex((i) => this.isEqual(i, currentFirstItem));
6498
+ const prevEndIndex = prevItems.findIndex((i) => this.isEqual(i, currentLastItem));
6499
+ const isStartRemainedSame = currentItems[prevStartIndex]
6500
+ ? this.isEqual(currentItems[prevStartIndex], currentFirstItem)
6501
+ : false;
6502
+ const isEndRemainedSame = currentItems[prevEndIndex]
6503
+ ? this.isEqual(currentItems[prevEndIndex], currentLastItem)
6504
+ : false;
6505
+ const hasNewItemsBottom = prevEndIndex === prevItems.length - 1 && isEndRemainedSame
6506
+ ? prevItems.length !== currentItems.length
6507
+ : false;
6508
+ if (isStartRemainedSame && isEndRemainedSame) {
6509
+ const endIndex = hasNewItemsBottom ? undefined : prevEndIndex + 1;
6510
+ this.virtualizedItemsSubject.next(currentItems.slice(prevStartIndex, endIndex));
6511
+ }
6512
+ let currentStartIndex = isStartRemainedSame ? prevStartIndex : -1;
6513
+ let currentEndIndex = isEndRemainedSame ? prevEndIndex : -1;
6514
+ if (!isStartRemainedSame) {
6515
+ currentStartIndex = currentItems.findIndex((i) => this.isEqual(i, currentFirstItem));
6516
+ }
6517
+ if (!isEndRemainedSame) {
6518
+ currentEndIndex = currentItems.findIndex((i) => this.isEqual(i, currentLastItem));
6519
+ }
6520
+ const hasNewItemsTop = prevStartIndex === 0 && !isStartRemainedSame
6521
+ ? currentStartIndex !== 0
6522
+ : false;
6523
+ if (currentStartIndex !== -1 && currentEndIndex !== -1) {
6524
+ const startIndex = hasNewItemsTop ? 0 : currentStartIndex;
6525
+ this.virtualizedItemsSubject.next(currentItems.slice(startIndex, currentEndIndex + 1));
6526
+ }
6527
+ else {
6528
+ if (currentStartIndex === -1 && currentEndIndex !== -1) {
6529
+ currentStartIndex = Math.max(0, currentEndIndex - (prevEndIndex - prevStartIndex));
6530
+ }
6531
+ if (currentEndIndex === -1 && currentStartIndex !== -1) {
6532
+ currentEndIndex = Math.min(currentItems.length - 1, currentStartIndex + (prevEndIndex - prevStartIndex));
6533
+ }
6534
+ this.virtualizedItemsSubject.next(currentItems.slice(currentStartIndex, currentEndIndex + 1));
6535
+ }
6536
+ }
6537
+ }));
6538
+ if (this.jumpToItem$) {
6539
+ this.subscriptions.push(this.jumpToItem$
6540
+ .pipe(switchMap((jumpToItem) => combineLatest([this.allItems$.pipe(take$1(1)), of(jumpToItem)])))
6541
+ .subscribe(([allItems, jumpToItem]) => {
6542
+ if (jumpToItem.item) {
6543
+ if (allItems.length < this.maxItemCount) {
6544
+ this.virtualizedItemsSubject.next(allItems);
6545
+ }
6546
+ else {
6547
+ const itemIndex = allItems.findIndex((i) =>
6548
+ // @ts-expect-error TODO: do we know a better typing here?
6549
+ this.isEqual(i, jumpToItem.item));
6550
+ if (itemIndex === -1) {
6551
+ return;
6552
+ }
6553
+ else {
6554
+ const position = jumpToItem.position || 'middle';
6555
+ const numberOfItemsToRemove = allItems.length - Math.round(this.maxItemCount / 2);
6556
+ const numberOfItemsAfterRemove = allItems.length - numberOfItemsToRemove;
6557
+ let startIndex = -1;
6558
+ let endIndex = -1;
6559
+ switch (position) {
6560
+ case 'top':
6561
+ startIndex = itemIndex;
6562
+ endIndex = Math.min(allItems.length, startIndex + numberOfItemsAfterRemove);
6563
+ break;
6564
+ case 'bottom':
6565
+ endIndex = itemIndex + 1;
6566
+ startIndex = Math.max(0, endIndex - numberOfItemsAfterRemove);
6567
+ break;
6568
+ case 'middle': {
6569
+ const itemsOnTop = itemIndex;
6570
+ const itemsOnBottom = allItems.length - itemIndex;
6571
+ if (itemsOnTop < Math.ceil(numberOfItemsAfterRemove / 2)) {
6572
+ startIndex = 0;
6573
+ }
6574
+ if (itemsOnBottom <
6575
+ Math.floor(numberOfItemsAfterRemove / 2) + 1) {
6576
+ endIndex = allItems.length;
6577
+ }
6578
+ if (startIndex === -1) {
6579
+ if (endIndex !== -1) {
6580
+ startIndex = endIndex - numberOfItemsAfterRemove;
6581
+ }
6582
+ else {
6583
+ startIndex =
6584
+ itemIndex - Math.ceil(numberOfItemsAfterRemove / 2);
6585
+ }
6586
+ }
6587
+ if (endIndex === -1) {
6588
+ endIndex = startIndex + numberOfItemsAfterRemove;
6589
+ }
6590
+ }
6591
+ }
6592
+ this.virtualizedItemsSubject.next(allItems.slice(startIndex, endIndex));
6593
+ }
6594
+ }
6595
+ }
6596
+ }));
6597
+ }
6598
+ }
6599
+ /**
6600
+ * The current value of virtualized items
6601
+ */
6602
+ get virtualizedItems() {
6603
+ return this.virtualizedItemsSubject.getValue();
6604
+ }
6605
+ /**
6606
+ * Remove all subscriptions, call this once you're done using an instance of this service
6607
+ */
6608
+ dispose() {
6609
+ this.subscriptions.forEach((s) => s.unsubscribe());
6610
+ }
6611
+ loadMoreFromBuffer(_) {
6612
+ this.loadFromBuffer$.next();
6613
+ }
6614
+ loadMore(direction) {
6615
+ return __awaiter(this, void 0, void 0, function* () {
6616
+ this.queryStateSubject.next({ state: `loading-${direction}` });
6617
+ try {
6618
+ yield this.query(direction);
6619
+ this.queryStateSubject.next({ state: 'success' });
6620
+ }
6621
+ catch (e) {
6622
+ this.queryStateSubject.next({ state: 'error', error: e });
6623
+ }
6624
+ });
6625
+ }
6626
+ }
6627
+
6628
+ /**
6629
+ * The `VirtualizedMessageListService` removes messages from the message list that are currently not in view
6630
+ */
6631
+ class VirtualizedMessageListService extends VirtualizedListService {
6632
+ constructor(mode, scrollPosition$, channelService) {
6633
+ const jumpToMessage$ = channelService.jumpToMessage$.pipe(map$1((jumpToMessage) => {
6634
+ var _a;
6635
+ let result = {
6636
+ item: undefined,
6637
+ };
6638
+ let targetMessageId;
6639
+ if (mode === 'main') {
6640
+ targetMessageId = jumpToMessage.parentId
6641
+ ? jumpToMessage.parentId
6642
+ : jumpToMessage.id;
6643
+ }
6644
+ else {
6645
+ targetMessageId = jumpToMessage.parentId
6646
+ ? jumpToMessage.id
6647
+ : undefined;
6648
+ }
6649
+ if (targetMessageId) {
6650
+ const messages = mode === 'main'
6651
+ ? channelService.activeChannelMessages
6652
+ : channelService.activeChannelThreadReplies;
6653
+ const id = targetMessageId === 'latest'
6654
+ ? (_a = messages[messages.length - 1]) === null || _a === void 0 ? void 0 : _a.id
6655
+ : targetMessageId;
6656
+ if (id) {
6657
+ result = {
6658
+ item: { id },
6659
+ position: jumpToMessage.id === 'latest' ? 'bottom' : 'middle',
6660
+ };
6661
+ }
6662
+ channelService.clearMessageJump();
6663
+ }
6664
+ return result;
6665
+ }));
6666
+ const messages$ = mode === 'main'
6667
+ ? channelService.activeChannelMessages$
6668
+ : channelService.activeThreadMessages$;
6669
+ super(messages$, scrollPosition$, jumpToMessage$, channelService.messagePageSize);
6670
+ this.mode = mode;
6671
+ this.channelService = channelService;
6672
+ this.isEqual = (t1, t2) => t1.id === t2.id;
6673
+ this.query = (direction) => {
6674
+ const request = this.mode === 'main'
6675
+ ? (direction) => this.channelService.loadMoreMessages(direction)
6676
+ : (direction) => this.channelService.loadMoreThreadReplies(direction);
6677
+ const result = request(direction === 'top' ? 'older' : 'newer');
6678
+ if (result) {
6679
+ return result;
6680
+ }
6681
+ else {
6682
+ this.queryStateSubject.next({ state: 'success' });
6683
+ if ((direction === 'top' && this.bufferOnTop > 0) ||
6684
+ (direction === 'bottom' && this.bufferOnBottom > 0)) {
6685
+ this.loadFromBuffer$.next();
6686
+ }
6687
+ return Promise.resolve();
6688
+ }
6689
+ };
6690
+ }
6691
+ loadMoreFromBuffer(direction) {
6692
+ this.queryStateSubject.next({ state: `loading-${direction}` });
6693
+ setTimeout(() => {
6694
+ this.loadFromBuffer$.next();
6695
+ this.queryStateSubject.next({ state: 'success' });
6696
+ });
6697
+ }
6698
+ }
6699
+
6368
6700
  /**
6369
6701
  * The `MessageList` component renders a scrollable list of messages.
6370
6702
  */
@@ -6415,10 +6747,6 @@ class MessageListComponent {
6415
6747
  * You can turn on and off the loading indicator that signals to users that more messages are being loaded to the message list
6416
6748
  */
6417
6749
  this.displayLoadingIndicator = true;
6418
- /**
6419
- * @internal
6420
- */
6421
- this.limitNumberOfMessagesInList = true;
6422
6750
  this.emptyMainMessageListTemplate = null;
6423
6751
  this.emptyThreadMessageListTemplate = null;
6424
6752
  this.enabledMessageActions = [];
@@ -6426,17 +6754,20 @@ class MessageListComponent {
6426
6754
  this.newMessageCountWhileBeingScrolled = 0;
6427
6755
  this.groupStyles = [];
6428
6756
  this.isNextMessageOnSeparateDate = [];
6429
- this.isLoading = false;
6757
+ this.loadingState = 'idle';
6430
6758
  this.isUnreadNotificationVisible = true;
6431
6759
  this.isJumpingToLatestUnreadMessage = false;
6432
6760
  this.isJumpToLatestButtonVisible = true;
6761
+ this.isJumpingToMessage = false;
6433
6762
  this.scroll$ = new Subject();
6763
+ this.isNewMessageSentByUser = false;
6434
6764
  this.subscriptions = [];
6435
6765
  this.isLatestMessageInList = true;
6436
6766
  this.parsedDates = new Map();
6437
6767
  this.isViewInited = false;
6438
6768
  this.isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
6439
6769
  this.forceRepaintSubject = new Subject();
6770
+ this.scrollPosition$ = new BehaviorSubject('bottom');
6440
6771
  this.messageNotificationJumpClicked = () => {
6441
6772
  this.jumpToFirstUnreadMessage();
6442
6773
  this.isUnreadNotificationVisible = false;
@@ -6455,8 +6786,7 @@ class MessageListComponent {
6455
6786
  this.forceRepaint();
6456
6787
  }));
6457
6788
  this.subscriptions.push(this.channelService.activeChannel$.subscribe((channel) => {
6458
- var _a, _b, _c, _d, _e, _f, _g;
6459
- (_b = (_a = this.chatClientService.chatClient) === null || _a === void 0 ? void 0 : _a.logger) === null || _b === void 0 ? void 0 : _b.call(_a, 'info', `${(channel === null || channel === void 0 ? void 0 : channel.cid) || 'undefined'} selected`, { tags: `message list ${this.mode}` });
6789
+ var _a, _b;
6460
6790
  let isNewChannel = false;
6461
6791
  if (this.channelId !== (channel === null || channel === void 0 ? void 0 : channel.id)) {
6462
6792
  isNewChannel = true;
@@ -6464,12 +6794,9 @@ class MessageListComponent {
6464
6794
  clearTimeout(this.checkIfUnreadNotificationIsVisibleTimeout);
6465
6795
  }
6466
6796
  this.isUnreadNotificationVisible = false;
6467
- (_e = (_d = (_c = this.chatClientService) === null || _c === void 0 ? void 0 : _c.chatClient) === null || _d === void 0 ? void 0 : _d.logger) === null || _e === void 0 ? void 0 : _e.call(_d, 'info', `new channel is different from prev channel, reseting scroll state`, { tags: `message list ${this.mode}` });
6468
6797
  this.parsedDates = new Map();
6469
- if (this.messageRemoveTimeout) {
6470
- clearTimeout(this.messageRemoveTimeout);
6471
- }
6472
6798
  this.resetScrollState();
6799
+ this.setMessages$();
6473
6800
  this.channelId = channel === null || channel === void 0 ? void 0 : channel.id;
6474
6801
  if (this.isViewInited) {
6475
6802
  this.cdRef.detectChanges();
@@ -6516,7 +6843,7 @@ class MessageListComponent {
6516
6843
  this.cdRef.detectChanges();
6517
6844
  }
6518
6845
  }
6519
- const capabilites = (_f = channel === null || channel === void 0 ? void 0 : channel.data) === null || _f === void 0 ? void 0 : _f.own_capabilities;
6846
+ const capabilites = (_a = channel === null || channel === void 0 ? void 0 : channel.data) === null || _a === void 0 ? void 0 : _a.own_capabilities;
6520
6847
  const capabilitesString = [...(capabilites || [])].sort().join('');
6521
6848
  const enabledActionsString = [...(this.enabledMessageActions || [])]
6522
6849
  .sort()
@@ -6527,20 +6854,15 @@ class MessageListComponent {
6527
6854
  this.cdRef.detectChanges();
6528
6855
  }
6529
6856
  }
6530
- (_g = this.newMessageSubscription) === null || _g === void 0 ? void 0 : _g.unsubscribe();
6857
+ (_b = this.newMessageSubscription) === null || _b === void 0 ? void 0 : _b.unsubscribe();
6531
6858
  if (channel) {
6532
6859
  this.newMessageSubscription = channel.on('message.new', (event) => {
6533
- // If we display main channel messages and we're switched to an older message set -> use message.new event to update unread count and detect new messages sent by current user
6534
- if (!event.message ||
6535
- channel.state.messages === channel.state.latestMessages ||
6536
- this.mode === 'thread') {
6860
+ if (!event.message) {
6537
6861
  return;
6538
6862
  }
6539
- this.newMessageReceived({
6540
- id: event.message.id,
6541
- user: event.message.user,
6542
- created_at: new Date(event.message.created_at || ''),
6543
- });
6863
+ else {
6864
+ this.newMessageReceived(event.message);
6865
+ }
6544
6866
  });
6545
6867
  }
6546
6868
  }));
@@ -6550,6 +6872,7 @@ class MessageListComponent {
6550
6872
  message.id !== this.parentMessage.id &&
6551
6873
  this.mode === 'thread') {
6552
6874
  this.resetScrollState();
6875
+ this.setMessages$();
6553
6876
  }
6554
6877
  if (this.parentMessage === message) {
6555
6878
  return;
@@ -6604,42 +6927,6 @@ class MessageListComponent {
6604
6927
  this.cdRef.detectChanges();
6605
6928
  }
6606
6929
  }));
6607
- this.subscriptions.push(this.channelService.jumpToMessage$
6608
- .pipe(filter((config) => !!config.id))
6609
- .subscribe((config) => {
6610
- var _a, _b;
6611
- let messageId = undefined;
6612
- if (this.messageRemoveTimeout) {
6613
- clearTimeout(this.messageRemoveTimeout);
6614
- }
6615
- if (this.mode === 'main') {
6616
- messageId = config.parentId || config.id;
6617
- }
6618
- else if (config.parentId) {
6619
- messageId = config.id;
6620
- }
6621
- (_b = (_a = this.chatClientService.chatClient) === null || _a === void 0 ? void 0 : _a.logger) === null || _b === void 0 ? void 0 : _b.call(_a, 'info', `Jumping to ${messageId || ''}`, { tags: `message list ${this.mode}` });
6622
- if (messageId) {
6623
- if (messageId === 'latest') {
6624
- this.scrollToLatestMessage();
6625
- if (this.isViewInited) {
6626
- this.cdRef.detectChanges();
6627
- }
6628
- }
6629
- else {
6630
- if (this.isJumpingToLatestUnreadMessage) {
6631
- this.scrollMessageIntoView(this.firstUnreadMessageId || messageId);
6632
- this.highlightedMessageId =
6633
- this.firstUnreadMessageId || messageId;
6634
- }
6635
- else {
6636
- this.scrollMessageIntoView(messageId);
6637
- this.highlightedMessageId = messageId;
6638
- }
6639
- }
6640
- }
6641
- this.channelService.clearMessageJump();
6642
- }));
6643
6930
  this.subscriptions.push(this.customTemplatesService.emptyMainMessageListPlaceholder$.subscribe((template) => {
6644
6931
  const isChanged = this.emptyMainMessageListTemplate !== template;
6645
6932
  this.emptyMainMessageListTemplate = template || null;
@@ -6659,6 +6946,7 @@ class MessageListComponent {
6659
6946
  ngOnChanges(changes) {
6660
6947
  var _a;
6661
6948
  if (changes.mode || changes.direction) {
6949
+ this.resetScrollState();
6662
6950
  this.setMessages$();
6663
6951
  }
6664
6952
  if (changes.direction) {
@@ -6670,56 +6958,33 @@ class MessageListComponent {
6670
6958
  ngAfterViewInit() {
6671
6959
  this.isViewInited = true;
6672
6960
  this.ngZone.runOutsideAngular(() => {
6673
- this.scrollContainer.nativeElement.addEventListener('scroll', () => this.scrolled());
6961
+ var _a, _b;
6962
+ (_b = (_a = this.scrollContainer) === null || _a === void 0 ? void 0 : _a.nativeElement) === null || _b === void 0 ? void 0 : _b.addEventListener('scroll', () => this.scrolled());
6674
6963
  });
6675
6964
  }
6676
6965
  ngAfterViewChecked() {
6677
- var _a, _b, _c, _d, _e, _f;
6678
- if (this.highlightedMessageId) {
6679
- // Turn off programatic scroll adjustments while jump to message is in progress
6680
- this.hasNewMessages = false;
6681
- this.olderMassagesLoaded = false;
6966
+ var _a, _b;
6967
+ if (this.isJumpingToMessage) {
6968
+ this.isNewMessageSentByUser = false;
6969
+ this.messageIdToAnchorTo = undefined;
6970
+ this.anchorMessageTopOffset = undefined;
6971
+ return;
6682
6972
  }
6683
- if (this.direction === 'top-to-bottom') {
6684
- if (this.hasNewMessages &&
6685
- (this.isNewMessageSentByUser || !this.isUserScrolled)) {
6686
- this.isLatestMessageInList
6687
- ? this.scrollToTop()
6688
- : this.jumpToLatestMessage();
6689
- this.hasNewMessages = false;
6690
- this.containerHeight = this.scrollContainer.nativeElement.scrollHeight;
6691
- }
6973
+ if (this.messageIdToAnchorTo && this.loadingState === 'idle') {
6974
+ this.preserveScrollbarPosition();
6692
6975
  }
6693
- else {
6694
- if (this.hasNewMessages) {
6695
- if (!this.isUserScrolled || this.isNewMessageSentByUser) {
6696
- (_b = (_a = this.chatClientService.chatClient) === null || _a === void 0 ? void 0 : _a.logger) === null || _b === void 0 ? void 0 : _b.call(_a, 'info', `User has new messages, and not scrolled or sent new messages, therefore we ${this.isLatestMessageInList ? 'scroll' : 'jump'} to latest message`, { tags: `message list ${this.mode}` });
6697
- this.isLatestMessageInList
6698
- ? this.scrollToBottom()
6699
- : this.jumpToLatestMessage();
6700
- }
6701
- this.hasNewMessages = false;
6702
- this.containerHeight = this.scrollContainer.nativeElement.scrollHeight;
6703
- }
6704
- else if (this.olderMassagesLoaded) {
6705
- (_d = (_c = this.chatClientService.chatClient) === null || _c === void 0 ? void 0 : _c.logger) === null || _d === void 0 ? void 0 : _d.call(_c, 'info', `Older messages are loaded, we preserve the scroll position`, { tags: `message list ${this.mode}` });
6706
- this.preserveScrollbarPosition();
6707
- this.containerHeight = this.scrollContainer.nativeElement.scrollHeight;
6708
- this.olderMassagesLoaded = false;
6709
- }
6710
- else if (this.getScrollPosition() !== 'bottom' &&
6711
- !this.isUserScrolled &&
6712
- !this.highlightedMessageId) {
6713
- (_f = (_e = this.chatClientService.chatClient) === null || _e === void 0 ? void 0 : _e.logger) === null || _f === void 0 ? void 0 : _f.call(_e, 'info', `Container grew and user didn't scroll therefore we ${this.isLatestMessageInList ? 'scroll' : 'jump'} to latest message`, { tags: `message list ${this.mode}` });
6714
- this.isLatestMessageInList
6715
- ? this.scrollToBottom()
6716
- : this.jumpToLatestMessage();
6717
- this.containerHeight = this.scrollContainer.nativeElement.scrollHeight;
6718
- }
6976
+ else if ((!this.isUserScrolled &&
6977
+ ((_a = this.scrollContainer.nativeElement) === null || _a === void 0 ? void 0 : _a.scrollHeight) >
6978
+ ((_b = this.scrollContainer) === null || _b === void 0 ? void 0 : _b.nativeElement.clientHeight) &&
6979
+ this.getScrollPosition() !==
6980
+ (this.direction === 'bottom-to-top' ? 'bottom' : 'top')) ||
6981
+ (this.isUserScrolled && this.isNewMessageSentByUser)) {
6982
+ this.isNewMessageSentByUser = false;
6983
+ this.jumpToLatestMessage();
6719
6984
  }
6720
6985
  }
6721
6986
  ngOnDestroy() {
6722
- var _a, _b;
6987
+ var _a;
6723
6988
  this.subscriptions.forEach((s) => s.unsubscribe());
6724
6989
  (_a = this.newMessageSubscription) === null || _a === void 0 ? void 0 : _a.unsubscribe();
6725
6990
  if (this.scrollEndTimeout) {
@@ -6731,20 +6996,24 @@ class MessageListComponent {
6731
6996
  if (this.jumpToLatestButtonVisibilityTimeout) {
6732
6997
  clearTimeout(this.jumpToLatestButtonVisibilityTimeout);
6733
6998
  }
6734
- if (this.messageRemoveTimeout) {
6735
- clearTimeout(this.messageRemoveTimeout);
6736
- }
6737
- (_b = this.removeOldMessagesSubscription) === null || _b === void 0 ? void 0 : _b.unsubscribe();
6999
+ this.disposeVirtualizedList();
6738
7000
  }
6739
- trackByMessageId(index, item) {
7001
+ trackByMessageId(_, item) {
6740
7002
  return item.id;
6741
7003
  }
6742
- trackByUserId(index, user) {
7004
+ trackByUserId(_, user) {
6743
7005
  return user.id;
6744
7006
  }
6745
7007
  jumpToLatestMessage() {
6746
7008
  var _a;
6747
- void this.channelService.jumpToMessage('latest', this.mode === 'thread' ? (_a = this.parentMessage) === null || _a === void 0 ? void 0 : _a.id : undefined);
7009
+ if (this.isLatestMessageInList) {
7010
+ this.direction === 'bottom-to-top'
7011
+ ? this.scrollToBottom()
7012
+ : this.scrollToTop();
7013
+ }
7014
+ else {
7015
+ void this.channelService.jumpToMessage('latest', this.mode === 'thread' ? (_a = this.parentMessage) === null || _a === void 0 ? void 0 : _a.id : undefined);
7016
+ }
6748
7017
  }
6749
7018
  scrollToBottom() {
6750
7019
  this.scrollContainer.nativeElement.scrollTop =
@@ -6757,7 +7026,7 @@ class MessageListComponent {
6757
7026
  this.scrollContainer.nativeElement.scrollTop = 0;
6758
7027
  }
6759
7028
  scrolled() {
6760
- var _a, _b;
7029
+ var _a;
6761
7030
  if (this.scrollContainer.nativeElement.scrollHeight ===
6762
7031
  this.scrollContainer.nativeElement.clientHeight) {
6763
7032
  if (this.isJumpToLatestButtonVisible) {
@@ -6768,8 +7037,7 @@ class MessageListComponent {
6768
7037
  return;
6769
7038
  }
6770
7039
  this.scroll$.next();
6771
- const scrollPosition = this.getScrollPosition();
6772
- (_b = (_a = this.chatClientService.chatClient) === null || _a === void 0 ? void 0 : _a.logger) === null || _b === void 0 ? void 0 : _b.call(_a, 'info', `Scrolled - scroll position: ${scrollPosition}, container height: ${this.scrollContainer.nativeElement.scrollHeight}`, { tags: `message list ${this.mode}` });
7040
+ let scrollPosition = this.getScrollPosition();
6773
7041
  const isUserScrolled = (this.direction === 'bottom-to-top'
6774
7042
  ? scrollPosition !== 'bottom'
6775
7043
  : scrollPosition !== 'top') || !this.isLatestMessageInList;
@@ -6798,31 +7066,31 @@ class MessageListComponent {
6798
7066
  }
6799
7067
  }, 100);
6800
7068
  }
6801
- if (this.shouldLoadMoreMessages(scrollPosition)) {
7069
+ const prevScrollPosition = this.scrollPosition$.getValue();
7070
+ if (this.direction === 'top-to-bottom') {
7071
+ if (scrollPosition === 'top') {
7072
+ scrollPosition = 'bottom';
7073
+ }
7074
+ else if (scrollPosition === 'bottom') {
7075
+ scrollPosition = 'top';
7076
+ }
7077
+ }
7078
+ if (prevScrollPosition !== scrollPosition && !this.isJumpingToMessage) {
7079
+ if (scrollPosition === 'top' || scrollPosition === 'bottom') {
7080
+ (_a = this.virtualizedList) === null || _a === void 0 ? void 0 : _a.virtualizedItems$.pipe(take(1)).subscribe((items) => {
7081
+ var _a, _b, _c, _d;
7082
+ this.messageIdToAnchorTo =
7083
+ scrollPosition === 'top'
7084
+ ? (_a = items[0]) === null || _a === void 0 ? void 0 : _a.id
7085
+ : (_b = items[items.length - 1]) === null || _b === void 0 ? void 0 : _b.id;
7086
+ this.anchorMessageTopOffset = (_d = (_c = document
7087
+ .getElementById(this.messageIdToAnchorTo)) === null || _c === void 0 ? void 0 : _c.getBoundingClientRect()) === null || _d === void 0 ? void 0 : _d.top;
7088
+ });
7089
+ }
6802
7090
  this.ngZone.run(() => {
6803
- var _a, _b, _c;
6804
- this.containerHeight = this.scrollContainer.nativeElement.scrollHeight;
6805
- let direction;
6806
- if (this.direction === 'top-to-bottom') {
6807
- direction = scrollPosition === 'top' ? 'newer' : 'older';
6808
- }
6809
- else {
6810
- direction = scrollPosition === 'top' ? 'older' : 'newer';
6811
- }
6812
- const result = this.mode === 'main'
6813
- ? this.channelService.loadMoreMessages(direction)
6814
- : this.channelService.loadMoreThreadReplies(direction);
6815
- if (result) {
6816
- (_b = (_a = this.chatClientService.chatClient) === null || _a === void 0 ? void 0 : _a.logger) === null || _b === void 0 ? void 0 : _b.call(_a, 'info', `Displaying loading indicator`, { tags: `message list ${this.mode}` });
6817
- this.isLoading = true;
6818
- (_c = result.catch) === null || _c === void 0 ? void 0 : _c.call(result, () => {
6819
- this.isLoading = false;
6820
- });
6821
- }
6822
- this.cdRef.detectChanges();
7091
+ this.scrollPosition$.next(scrollPosition);
6823
7092
  });
6824
7093
  }
6825
- this.prevScrollTop = this.scrollContainer.nativeElement.scrollTop;
6826
7094
  }
6827
7095
  jumpToFirstUnreadMessage() {
6828
7096
  if (!this.lastReadMessageId) {
@@ -6865,9 +7133,19 @@ class MessageListComponent {
6865
7133
  : this.emptyThreadMessageListTemplate;
6866
7134
  }
6867
7135
  preserveScrollbarPosition() {
6868
- this.scrollContainer.nativeElement.scrollTop =
6869
- (this.prevScrollTop || 0) +
6870
- (this.scrollContainer.nativeElement.scrollHeight - this.containerHeight);
7136
+ var _a;
7137
+ if (!this.messageIdToAnchorTo) {
7138
+ return;
7139
+ }
7140
+ const messageToAlignTo = document.getElementById(this.messageIdToAnchorTo);
7141
+ this.messageIdToAnchorTo = undefined;
7142
+ this.scrollContainer.nativeElement.scrollTop +=
7143
+ (((_a = messageToAlignTo === null || messageToAlignTo === void 0 ? void 0 : messageToAlignTo.getBoundingClientRect()) === null || _a === void 0 ? void 0 : _a.top) || 0) -
7144
+ (this.anchorMessageTopOffset || 0);
7145
+ this.anchorMessageTopOffset = undefined;
7146
+ if (this.isSafari) {
7147
+ this.forceRepaintSubject.next();
7148
+ }
6871
7149
  }
6872
7150
  forceRepaint() {
6873
7151
  // Solves the issue of empty screen on Safari when scrolling
@@ -6876,13 +7154,10 @@ class MessageListComponent {
6876
7154
  this.scrollContainer.nativeElement.style.display = '';
6877
7155
  }
6878
7156
  getScrollPosition() {
6879
- var _a, _b;
7157
+ var _a;
6880
7158
  let position = 'middle';
6881
7159
  if (Math.floor(this.scrollContainer.nativeElement.scrollTop) <=
6882
- (((_a = this.parentMessageElement) === null || _a === void 0 ? void 0 : _a.nativeElement.clientHeight) || 0) &&
6883
- (this.prevScrollTop === undefined ||
6884
- this.prevScrollTop >
6885
- (((_b = this.parentMessageElement) === null || _b === void 0 ? void 0 : _b.nativeElement.clientHeight) || 0))) {
7160
+ (((_a = this.parentMessageElement) === null || _a === void 0 ? void 0 : _a.nativeElement.clientHeight) || 0)) {
6886
7161
  position = 'top';
6887
7162
  }
6888
7163
  else if (Math.ceil(this.scrollContainer.nativeElement.scrollTop) +
@@ -6893,24 +7168,24 @@ class MessageListComponent {
6893
7168
  }
6894
7169
  return position;
6895
7170
  }
6896
- shouldLoadMoreMessages(scrollPosition) {
6897
- return (scrollPosition !== 'middle' &&
6898
- !this.highlightedMessageId &&
6899
- !this.isLoading);
6900
- }
6901
7171
  setMessages$() {
6902
7172
  var _a;
6903
- this.messages$ = (this.mode === 'main'
6904
- ? this.channelService.activeChannelMessages$
6905
- : this.channelService.activeThreadMessages$).pipe(tap((messages) => {
6906
- var _a, _b, _c, _d;
6907
- if (this.isLoading) {
6908
- this.isLoading = false;
7173
+ this.disposeVirtualizedList();
7174
+ this.virtualizedList = new VirtualizedMessageListService(this.mode, this.scrollPosition$, this.channelService);
7175
+ this.queryStateSubscription = this.virtualizedList.queryState$.subscribe((queryState) => {
7176
+ let mappedState = 'idle';
7177
+ if (queryState.state.includes('loading')) {
7178
+ mappedState = queryState.state || 'loading-bottom';
7179
+ }
7180
+ if (mappedState !== this.loadingState) {
7181
+ this.loadingState = mappedState;
7182
+ if (this.isViewInited) {
7183
+ this.cdRef.detectChanges();
7184
+ }
6909
7185
  }
7186
+ });
7187
+ this.messages$ = this.virtualizedList.virtualizedItems$.pipe(tap((messages) => {
6910
7188
  if (messages.length === 0) {
6911
- (_b = (_a = this.chatClientService.chatClient) === null || _a === void 0 ? void 0 : _a.logger) === null || _b === void 0 ? void 0 : _b.call(_a, 'info', `Empty messages array, reseting scroll state`, {
6912
- tags: `message list ${this.mode}`,
6913
- });
6914
7189
  this.resetScrollState();
6915
7190
  return;
6916
7191
  }
@@ -6918,21 +7193,6 @@ class MessageListComponent {
6918
7193
  // cdRef.detectChanges() isn't enough here, test will fail
6919
7194
  setTimeout(() => (this.isEmpty = false), 0);
6920
7195
  }
6921
- (_d = (_c = this.chatClientService.chatClient) === null || _c === void 0 ? void 0 : _c.logger) === null || _d === void 0 ? void 0 : _d.call(_c, 'info', `Received one or more messages`, {
6922
- tags: `message list ${this.mode}`,
6923
- });
6924
- const currentLatestMessageInState = messages[messages.length - 1];
6925
- this.newMessageReceived(currentLatestMessageInState);
6926
- const currentOldestMessage = messages[0];
6927
- if (!this.oldestMessage ||
6928
- !messages.find((m) => m.id === this.oldestMessage.id)) {
6929
- this.oldestMessage = currentOldestMessage;
6930
- }
6931
- else if (this.oldestMessage.created_at.getTime() >
6932
- currentOldestMessage.created_at.getTime()) {
6933
- this.oldestMessage = currentOldestMessage;
6934
- this.olderMassagesLoaded = true;
6935
- }
6936
7196
  }), tap((messages) => {
6937
7197
  var _a;
6938
7198
  if (this.isJumpingToLatestUnreadMessage &&
@@ -6952,82 +7212,101 @@ class MessageListComponent {
6952
7212
  m.status !== 'sending';
6953
7213
  })) === null || _a === void 0 ? void 0 : _a.id);
6954
7214
  }), tap((messages) => {
7215
+ var _a, _b;
7216
+ const latestMessageInList = messages[messages.length - 1];
7217
+ const channel = this.channelService.activeChannel;
7218
+ const messagesFromState = (this.mode === 'main'
7219
+ ? channel === null || channel === void 0 ? void 0 : channel.state.latestMessages
7220
+ : channel === null || channel === void 0 ? void 0 : channel.state.threads[((_a = this.parentMessage) === null || _a === void 0 ? void 0 : _a.id) || '']) || [];
6955
7221
  this.isLatestMessageInList =
6956
- !this.latestMessage ||
6957
- messages.length === 0 ||
6958
- messages[messages.length - 1].id === this.latestMessage.id ||
6959
- this.mode === 'thread';
7222
+ !latestMessageInList ||
7223
+ latestMessageInList.cid !== (channel === null || channel === void 0 ? void 0 : channel.cid) ||
7224
+ (latestMessageInList === null || latestMessageInList === void 0 ? void 0 : latestMessageInList.id) ===
7225
+ ((_b = messagesFromState[messagesFromState.length - 1]) === null || _b === void 0 ? void 0 : _b.id);
6960
7226
  if (!this.isLatestMessageInList) {
6961
7227
  this.isUserScrolled = true;
6962
7228
  }
6963
- }), map((messages) => this.direction === 'bottom-to-top' ? messages : [...messages].reverse()), tap((messages) => {
7229
+ }), map((messages) => {
7230
+ return this.direction === 'bottom-to-top'
7231
+ ? messages
7232
+ : [...messages].reverse();
7233
+ }), tap((messages) => {
6964
7234
  this.groupStyles = messages.map((m, i) => getGroupStyles(m, messages[i - 1], messages[i + 1], {
6965
7235
  lastReadMessageId: this.lastReadMessageId,
6966
7236
  }));
6967
7237
  this.isNextMessageOnSeparateDate = messages.map((m, i) => this.checkIfOnSeparateDates(m, messages[i + 1]));
6968
7238
  }), shareReplay(1));
6969
- (_a = this.removeOldMessagesSubscription) === null || _a === void 0 ? void 0 : _a.unsubscribe();
6970
- this.removeOldMessagesSubscription = combineLatest([
6971
- this.channelService.jumpToMessage$,
6972
- this.messages$,
6973
- ]).subscribe(([jumpToMessage, messages]) => {
6974
- if (this.limitNumberOfMessagesInList &&
6975
- this.mode === 'main' &&
6976
- messages.length >
6977
- ChannelService.MAX_MESSAGE_COUNT_IN_MESSAGE_LIST * 0.5 &&
6978
- !this.isUserScrolled &&
6979
- !(jumpToMessage === null || jumpToMessage === void 0 ? void 0 : jumpToMessage.id) &&
6980
- this.isLatestMessageInList) {
6981
- if (this.messageRemoveTimeout) {
6982
- clearTimeout(this.messageRemoveTimeout);
6983
- }
6984
- if (messages.length >= ChannelService.MAX_MESSAGE_COUNT_IN_MESSAGE_LIST) {
6985
- this.channelService.removeOldMessageFromMessageList();
6986
- }
6987
- else {
6988
- this.messageRemoveTimeout = setTimeout(() => {
6989
- if (this.limitNumberOfMessagesInList &&
6990
- this.mode === 'main' &&
6991
- messages.length >
6992
- ChannelService.MAX_MESSAGE_COUNT_IN_MESSAGE_LIST * 0.5 &&
6993
- !this.isUserScrolled &&
6994
- !this.highlightedMessageId &&
6995
- this.isLatestMessageInList) {
6996
- this.channelService.removeOldMessageFromMessageList();
6997
- }
6998
- }, 1500);
7239
+ if ((_a = this.virtualizedList) === null || _a === void 0 ? void 0 : _a.jumpToItem$) {
7240
+ this.jumpToItemSubscription = this.virtualizedList.jumpToItem$
7241
+ .pipe(filter((jumpToMessage) => { var _a; return !!((_a = jumpToMessage.item) === null || _a === void 0 ? void 0 : _a.id); }))
7242
+ .subscribe((jumpToMessage) => {
7243
+ var _a;
7244
+ let messageId = (_a = jumpToMessage.item) === null || _a === void 0 ? void 0 : _a.id;
7245
+ if (messageId) {
7246
+ if (this.isJumpingToLatestUnreadMessage) {
7247
+ messageId = this.firstUnreadMessageId || messageId;
7248
+ }
7249
+ if (jumpToMessage.position !== 'bottom') {
7250
+ this.highlightedMessageId = messageId;
7251
+ }
7252
+ else if (this.direction === 'top-to-bottom') {
7253
+ jumpToMessage.position = 'top';
7254
+ }
7255
+ this.isJumpingToMessage = true;
7256
+ this.scrollMessageIntoView({
7257
+ messageId: this.firstUnreadMessageId || messageId,
7258
+ position: jumpToMessage.position || 'middle',
7259
+ });
6999
7260
  }
7000
- }
7001
- });
7261
+ });
7262
+ }
7002
7263
  }
7003
7264
  resetScrollState() {
7004
7265
  this.isEmpty = true;
7005
- this.latestMessage = undefined;
7006
- this.hasNewMessages = true;
7007
7266
  this.isUserScrolled = false;
7008
- this.containerHeight = undefined;
7009
- this.olderMassagesLoaded = false;
7010
- this.oldestMessage = undefined;
7267
+ this.messageIdToAnchorTo = undefined;
7268
+ this.anchorMessageTopOffset = undefined;
7011
7269
  this.newMessageCountWhileBeingScrolled = 0;
7012
- this.prevScrollTop = undefined;
7013
- this.isNewMessageSentByUser = undefined;
7270
+ this.isNewMessageSentByUser = false;
7014
7271
  this.isLatestMessageInList = true;
7272
+ this.isJumpingToMessage = false;
7273
+ this.scrollPosition$.next('bottom');
7274
+ this.loadingState = 'idle';
7275
+ }
7276
+ disposeVirtualizedList() {
7277
+ var _a, _b, _c;
7278
+ (_a = this.virtualizedList) === null || _a === void 0 ? void 0 : _a.dispose();
7279
+ (_b = this.jumpToItemSubscription) === null || _b === void 0 ? void 0 : _b.unsubscribe();
7280
+ (_c = this.queryStateSubscription) === null || _c === void 0 ? void 0 : _c.unsubscribe();
7015
7281
  }
7016
7282
  get usersTyping$() {
7017
7283
  return this.mode === 'thread'
7018
7284
  ? this.usersTypingInThread$
7019
7285
  : this.usersTypingInChannel$;
7020
7286
  }
7021
- scrollMessageIntoView(messageId, withRetry = true) {
7022
- const element = document.getElementById(messageId);
7287
+ scrollMessageIntoView(options, withRetry = true) {
7288
+ const element = document.getElementById(options.messageId);
7023
7289
  if (!element && withRetry) {
7024
7290
  // 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
7025
- setTimeout(() => this.scrollMessageIntoView(messageId, false));
7291
+ setTimeout(() => this.scrollMessageIntoView(options, false));
7026
7292
  }
7027
7293
  else if (element) {
7294
+ const blockMapping = {
7295
+ top: 'start',
7296
+ bottom: 'end',
7297
+ middle: 'center',
7298
+ };
7028
7299
  element.scrollIntoView({
7029
- block: 'center',
7300
+ block: blockMapping[options.position],
7030
7301
  });
7302
+ if (options.position !== 'middle') {
7303
+ options.position === 'bottom'
7304
+ ? this.scrollToBottom()
7305
+ : this.scrollToTop();
7306
+ }
7307
+ setTimeout(() => {
7308
+ this.isJumpingToMessage = false;
7309
+ }, 0);
7031
7310
  setTimeout(() => {
7032
7311
  this.highlightedMessageId = undefined;
7033
7312
  this.firstUnreadMessageId = undefined;
@@ -7036,40 +7315,27 @@ class MessageListComponent {
7036
7315
  }, 1000);
7037
7316
  }
7038
7317
  }
7039
- scrollToLatestMessage(withRetry = true) {
7040
- if (document.getElementById(this.latestMessage.id)) {
7041
- this.direction === 'bottom-to-top'
7042
- ? this.scrollToBottom()
7043
- : this.scrollToTop();
7318
+ newMessageReceived(message) {
7319
+ var _a, _b, _c, _d;
7320
+ if ((this.mode === 'main' && message.parent_id) ||
7321
+ (this.mode === 'thread' && message.parent_id !== ((_a = this.parentMessage) === null || _a === void 0 ? void 0 : _a.id))) {
7322
+ return;
7044
7323
  }
7045
- else if (withRetry) {
7046
- // 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
7047
- setTimeout(() => this.scrollToLatestMessage(false), 0);
7324
+ const isNewMessageSentByCurrentUser = ((_b = message.user) === null || _b === void 0 ? void 0 : _b.id) === ((_d = (_c = this.chatClientService.chatClient) === null || _c === void 0 ? void 0 : _c.user) === null || _d === void 0 ? void 0 : _d.id);
7325
+ let shouldDetectChanges = false;
7326
+ if (!this.isNewMessageSentByUser && isNewMessageSentByCurrentUser) {
7327
+ this.isNewMessageSentByUser = true;
7328
+ shouldDetectChanges = true;
7048
7329
  }
7049
- }
7050
- newMessageReceived(message) {
7051
- var _a, _b, _c, _d, _e, _f, _g, _h, _j;
7052
- const latestMessages = (_b = (_a = this.channelService.activeChannel) === null || _a === void 0 ? void 0 : _a.state) === null || _b === void 0 ? void 0 : _b.latestMessages;
7053
- if (!this.latestMessage ||
7054
- ((_c = this.latestMessage.created_at) === null || _c === void 0 ? void 0 : _c.getTime()) < message.created_at.getTime() ||
7055
- (this.mode === 'main' &&
7056
- latestMessages &&
7057
- this.latestMessage &&
7058
- ((_d = latestMessages[latestMessages.length - 1]) === null || _d === void 0 ? void 0 : _d.id) !== this.latestMessage.id)) {
7059
- (_f = (_e = this.chatClientService.chatClient) === null || _e === void 0 ? void 0 : _e.logger) === null || _f === void 0 ? void 0 : _f.call(_e, 'info', `Received new message`, { tags: `message list ${this.mode}` });
7060
- const isNewChannel = !this.latestMessage;
7061
- this.latestMessage = message;
7062
- this.hasNewMessages = true;
7063
- this.isNewMessageSentByUser =
7064
- ((_g = message.user) === null || _g === void 0 ? void 0 : _g.id) === ((_j = (_h = this.chatClientService.chatClient) === null || _h === void 0 ? void 0 : _h.user) === null || _j === void 0 ? void 0 : _j.id);
7065
- if (this.isUserScrolled) {
7066
- this.newMessageCountWhileBeingScrolled++;
7067
- }
7068
- if (!this.isNewMessageSentByUser &&
7069
- this.unreadCount !== undefined &&
7070
- !isNewChannel) {
7071
- this.unreadCount++;
7072
- }
7330
+ if (this.isUserScrolled) {
7331
+ this.newMessageCountWhileBeingScrolled++;
7332
+ shouldDetectChanges = true;
7333
+ }
7334
+ if (!this.isNewMessageSentByUser && this.unreadCount !== undefined) {
7335
+ this.unreadCount++;
7336
+ shouldDetectChanges = true;
7337
+ }
7338
+ if (shouldDetectChanges && this.isViewInited) {
7073
7339
  this.cdRef.detectChanges();
7074
7340
  }
7075
7341
  }
@@ -7081,10 +7347,10 @@ class MessageListComponent {
7081
7347
  }
7082
7348
  }
7083
7349
  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 });
7084
- 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 });
7350
+ 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 });
7085
7351
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: MessageListComponent, decorators: [{
7086
7352
  type: Component,
7087
- 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" }]
7353
+ 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" }]
7088
7354
  }], ctorParameters: function () { return [{ type: ChannelService }, { type: ChatClientService }, { type: CustomTemplatesService }, { type: DateParserService }, { type: i0.NgZone }, { type: i0.ChangeDetectorRef }]; }, propDecorators: { mode: [{
7089
7355
  type: Input
7090
7356
  }], direction: [{
@@ -7103,8 +7369,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImpor
7103
7369
  type: Input
7104
7370
  }], displayLoadingIndicator: [{
7105
7371
  type: Input
7106
- }], limitNumberOfMessagesInList: [{
7107
- type: Input
7108
7372
  }], scrollContainer: [{
7109
7373
  type: ViewChild,
7110
7374
  args: ['scrollContainer']
@@ -7351,5 +7615,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImpor
7351
7615
  * Generated bundle index. Do not edit.
7352
7616
  */
7353
7617
 
7354
- 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 };
7618
+ 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 };
7355
7619
  //# sourceMappingURL=stream-chat-angular.mjs.map