stream-chat-angular 5.0.0 → 5.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/assets/version.d.ts +1 -1
  2. package/esm2020/assets/version.mjs +2 -2
  3. package/esm2020/lib/avatar/avatar.component.mjs +4 -1
  4. package/esm2020/lib/channel-list/channel-list.component.mjs +2 -2
  5. package/esm2020/lib/channel.service.mjs +10 -26
  6. package/esm2020/lib/chat-client.service.mjs +2 -3
  7. package/esm2020/lib/message-actions.service.mjs +4 -4
  8. package/esm2020/lib/message-list/message-list.component.mjs +181 -249
  9. package/esm2020/lib/types.mjs +1 -1
  10. package/esm2020/lib/virtualized-list.service.mjs +271 -0
  11. package/esm2020/lib/virtualized-message-list.service.mjs +73 -0
  12. package/esm2020/lib/voice-recording/voice-recording.component.mjs +2 -2
  13. package/esm2020/public-api.mjs +3 -1
  14. package/fesm2015/stream-chat-angular.mjs +550 -288
  15. package/fesm2015/stream-chat-angular.mjs.map +1 -1
  16. package/fesm2020/stream-chat-angular.mjs +536 -277
  17. package/fesm2020/stream-chat-angular.mjs.map +1 -1
  18. package/lib/avatar/avatar.component.d.ts +3 -2
  19. package/lib/channel-list/channel-list.component.d.ts +1 -1
  20. package/lib/channel.service.d.ts +6 -12
  21. package/lib/message-list/message-list.component.d.ts +12 -18
  22. package/lib/types.d.ts +7 -0
  23. package/lib/virtualized-list.service.d.ts +58 -0
  24. package/lib/virtualized-message-list.service.d.ts +15 -0
  25. package/package.json +1 -1
  26. package/public-api.d.ts +2 -0
  27. package/src/assets/styles/css/index.css +1 -1
  28. package/src/assets/styles/css/index.layout.css +1 -1
  29. package/src/assets/styles/scss/LoadingIndicator/LoadingIndicator-layout.scss +16 -0
  30. package/src/assets/version.ts +1 -1
@@ -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.1';
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.
@@ -252,9 +252,8 @@ class ChatClientService {
252
252
  { id: { $autocomplete: searchTerm } },
253
253
  { name: { $autocomplete: searchTerm } },
254
254
  ],
255
- id: { $ne: this.chatClient.userID },
256
255
  }); // TODO: find out why we need this typecast
257
- return result.users;
256
+ return result.users.filter((u) => { var _a, _b; return u.id !== ((_b = (_a = this.chatClient) === null || _a === void 0 ? void 0 : _a.user) === null || _b === void 0 ? void 0 : _b.id); });
258
257
  });
259
258
  }
260
259
  updatePendingInvites(e) {
@@ -437,6 +436,7 @@ class ChannelService {
437
436
  this.chatClientService = chatClientService;
438
437
  this.ngZone = ngZone;
439
438
  this.notificationService = notificationService;
439
+ this.messagePageSize = 25;
440
440
  this.channelsSubject = new BehaviorSubject(undefined);
441
441
  this.activeChannelSubject = new BehaviorSubject(undefined);
442
442
  this.activeChannelMessagesSubject = new BehaviorSubject([]);
@@ -448,7 +448,6 @@ class ChannelService {
448
448
  this.activeThreadMessagesSubject = new BehaviorSubject([]);
449
449
  this.jumpToMessageSubject = new BehaviorSubject({ id: undefined, parentId: undefined });
450
450
  this.latestMessageDateByUserByChannelsSubject = new BehaviorSubject({});
451
- this.messagePageSize = 25;
452
451
  this.attachmentMaxSizeFallbackInMB = 100;
453
452
  this.messageToQuoteSubject = new BehaviorSubject(undefined);
454
453
  this.usersTypingInChannelSubject = new BehaviorSubject([]);
@@ -556,23 +555,6 @@ class ChannelService {
556
555
  .asObservable()
557
556
  .pipe(shareReplay(1));
558
557
  }
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
558
  /**
577
559
  * 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
560
  */
@@ -1031,9 +1013,8 @@ class ChannelService {
1031
1013
  }
1032
1014
  const result = yield activeChannel.queryMembers({
1033
1015
  name: { $autocomplete: searchTerm },
1034
- id: { $ne: this.chatClientService.chatClient.userID },
1035
1016
  }); // TODO: find out why we need typecast here
1036
- return Object.values(result.members);
1017
+ return result.members.filter((m) => { var _a, _b; return m.user_id !== ((_b = (_a = this.chatClientService.chatClient) === null || _a === void 0 ? void 0 : _a.user) === null || _b === void 0 ? void 0 : _b.id); });
1037
1018
  }
1038
1019
  });
1039
1020
  }
@@ -1469,6 +1450,12 @@ class ChannelService {
1469
1450
  get activeChannelMessages() {
1470
1451
  return this.activeChannelMessagesSubject.getValue() || [];
1471
1452
  }
1453
+ /**
1454
+ * The current thread replies
1455
+ */
1456
+ get activeChannelThreadReplies() {
1457
+ return this.activeThreadMessagesSubject.getValue() || [];
1458
+ }
1472
1459
  /**
1473
1460
  * 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
1461
  * @param messageId
@@ -1556,7 +1543,7 @@ class ChannelService {
1556
1543
  return;
1557
1544
  }
1558
1545
  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) {
1546
+ if (messageIndex !== -1 || event.type === 'message.deleted') {
1560
1547
  isThreadReply
1561
1548
  ? this.activeThreadMessagesSubject.next([...messages])
1562
1549
  : this.activeChannelMessagesSubject.next([...messages]);
@@ -1944,10 +1931,6 @@ class ChannelService {
1944
1931
  });
1945
1932
  }
1946
1933
  }
1947
- /**
1948
- * @internal
1949
- */
1950
- ChannelService.MAX_MESSAGE_COUNT_IN_MESSAGE_LIST = 250;
1951
1934
  /**
1952
1935
  * @internal
1953
1936
  */
@@ -2725,6 +2708,9 @@ class AvatarComponent {
2725
2708
  this.setFallbackChannelImage();
2726
2709
  }
2727
2710
  }
2711
+ ngOnDestroy() {
2712
+ this.subscriptions.forEach((s) => s.unsubscribe());
2713
+ }
2728
2714
  setFallbackChannelImage() {
2729
2715
  if (this.type !== 'channel') {
2730
2716
  this.fallbackChannelImage = undefined;
@@ -3204,7 +3190,7 @@ class MessageActionsService {
3204
3190
  actionHandler: (message) => {
3205
3191
  void this.channelService.markMessageUnread(message.id);
3206
3192
  },
3207
- isVisible: (enabledActions, isMine, message) => enabledActions.indexOf('read-events') !== -1 && !message.parent_id,
3193
+ isVisible: (enabledActions, _, message) => enabledActions.indexOf('read-events') !== -1 && !message.parent_id,
3208
3194
  },
3209
3195
  {
3210
3196
  actionName: 'quote',
@@ -3220,7 +3206,7 @@ class MessageActionsService {
3220
3206
  actionHandler: (message) => {
3221
3207
  void this.channelService.setAsActiveParentMessage(message);
3222
3208
  },
3223
- isVisible: (enabledActions, isMine, message) => enabledActions.indexOf('send-reply') !== -1 && !message.parent_id,
3209
+ isVisible: (enabledActions, _, message) => enabledActions.indexOf('send-reply') !== -1 && !message.parent_id,
3224
3210
  },
3225
3211
  {
3226
3212
  actionName: 'pin',
@@ -3241,7 +3227,7 @@ class MessageActionsService {
3241
3227
  yield this.chatClientService.flagMessage(message.id);
3242
3228
  this.notificationService.addTemporaryNotification('streamChat.Message has been successfully flagged', 'success');
3243
3229
  }
3244
- catch (err) {
3230
+ catch (error) {
3245
3231
  this.notificationService.addTemporaryNotification('streamChat.Error adding flag');
3246
3232
  }
3247
3233
  }),
@@ -4158,7 +4144,7 @@ class ChannelListComponent {
4158
4144
  this.isLoadingMoreChannels = false;
4159
4145
  });
4160
4146
  }
4161
- trackByChannelId(index, item) {
4147
+ trackByChannelId(_, item) {
4162
4148
  return item.cid;
4163
4149
  }
4164
4150
  }
@@ -4408,7 +4394,7 @@ class VoiceRecordingComponent {
4408
4394
  : this.audioElement.nativeElement.pause();
4409
4395
  this.isError = false;
4410
4396
  }
4411
- catch (e) {
4397
+ catch (error) {
4412
4398
  this.isError = true;
4413
4399
  }
4414
4400
  });
@@ -6365,6 +6351,350 @@ const isOnSameDay = (date1, date2) => {
6365
6351
  date1.getDate() === date2.getDate());
6366
6352
  };
6367
6353
 
6354
+ /**
6355
+ * The `VirtualizedListService` removes items from a list that are not currently displayed. This is a high-level overview of how it works:
6356
+ * - Create a new instance for each list that needs virtualization
6357
+ * - Input: Provide a reactive stream that emits all items in the list
6358
+ * - Input: Provide a reactive stream that emit the current scroll position (top, middle or bottom)
6359
+ * - 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)
6360
+ * - Output: The service will emit the current list of displayed items via the virtualized items reactive stream
6361
+ * - 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:
6362
+ * - If scroll location is bottom/top items around the current bottom/top item will be emitted in the virtualized items stream
6363
+ * - 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)
6364
+ * - 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
6365
+ * - 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
6366
+ * - Input: you should provide the page size to use, in order for the service to determine if loading is necessary
6367
+ *
6368
+ * The `VirtualizedMessageListService` provides an implementation for the message list component.
6369
+ */
6370
+ class VirtualizedListService {
6371
+ constructor(allItems$, scrollPosition$, jumpToItem$, pageSize = 25, maxItemCount = pageSize * 4) {
6372
+ this.allItems$ = allItems$;
6373
+ this.scrollPosition$ = scrollPosition$;
6374
+ this.jumpToItem$ = jumpToItem$;
6375
+ this.pageSize = pageSize;
6376
+ this.maxItemCount = maxItemCount;
6377
+ this.queryStateSubject = new BehaviorSubject({
6378
+ state: 'success',
6379
+ });
6380
+ this.bufferOnTop = 0;
6381
+ this.bufferOnBottom = 0;
6382
+ this.loadFromBuffer$ = new Subject();
6383
+ this.virtualizedItemsSubject = new BehaviorSubject([]);
6384
+ this.subscriptions = [];
6385
+ this.virtualizedItems$ = this.virtualizedItemsSubject.asObservable();
6386
+ this.queryState$ = this.queryStateSubject.asObservable();
6387
+ this.subscriptions.push(this.virtualizedItems$.subscribe((virtaluzedItems) => {
6388
+ this.allItems$.pipe(take$1(1)).subscribe((allItems) => {
6389
+ if (virtaluzedItems.length === allItems.length) {
6390
+ this.bufferOnTop = 0;
6391
+ this.bufferOnBottom = 0;
6392
+ }
6393
+ else if (virtaluzedItems.length === 0) {
6394
+ this.bufferOnTop = allItems.length;
6395
+ this.bufferOnBottom = 0;
6396
+ }
6397
+ else {
6398
+ this.bufferOnTop = allItems.indexOf(virtaluzedItems[0]);
6399
+ this.bufferOnBottom =
6400
+ allItems.length -
6401
+ allItems.indexOf(virtaluzedItems[virtaluzedItems.length - 1]) -
6402
+ 1;
6403
+ }
6404
+ });
6405
+ }));
6406
+ this.subscriptions.push(merge(this.allItems$, this.loadFromBuffer$)
6407
+ .pipe(switchMap(() => {
6408
+ return combineLatest([
6409
+ this.allItems$.pipe(take$1(1)),
6410
+ this.scrollPosition$.pipe(take$1(1)),
6411
+ ]);
6412
+ }))
6413
+ .subscribe(([items, scrollPosition]) => {
6414
+ if (scrollPosition === 'middle') {
6415
+ return;
6416
+ }
6417
+ const currentItems = this.virtualizedItemsSubject.getValue();
6418
+ if (items.length <= this.maxItemCount) {
6419
+ this.virtualizedItemsSubject.next(items);
6420
+ }
6421
+ else {
6422
+ let startIndex = 0;
6423
+ let endIndex = undefined;
6424
+ const numberOfItemsToRemove = items.length - Math.round(this.maxItemCount / 2);
6425
+ const numberOfItemsAfterRemove = items.length - numberOfItemsToRemove;
6426
+ switch (scrollPosition) {
6427
+ case 'top':
6428
+ if (currentItems.length > 0) {
6429
+ const middleIndex = items.findIndex((i) => this.isEqual(i, currentItems[0]));
6430
+ if (middleIndex !== -1) {
6431
+ startIndex = Math.max(0, middleIndex - Math.ceil(numberOfItemsAfterRemove / 2));
6432
+ endIndex = startIndex + numberOfItemsAfterRemove;
6433
+ }
6434
+ }
6435
+ else {
6436
+ endIndex = numberOfItemsAfterRemove;
6437
+ }
6438
+ break;
6439
+ case 'bottom':
6440
+ if (currentItems.length > 0) {
6441
+ const middleIndex = items.findIndex((i) => this.isEqual(i, currentItems[currentItems.length - 1]));
6442
+ if (middleIndex !== -1) {
6443
+ endIndex = Math.min(items.length, middleIndex + Math.floor(numberOfItemsAfterRemove / 2) + 1);
6444
+ startIndex = endIndex - numberOfItemsAfterRemove;
6445
+ }
6446
+ }
6447
+ else {
6448
+ startIndex = items.length - numberOfItemsAfterRemove;
6449
+ }
6450
+ break;
6451
+ }
6452
+ const virtualizedItems = items.slice(startIndex, endIndex);
6453
+ this.virtualizedItemsSubject.next(virtualizedItems);
6454
+ }
6455
+ }));
6456
+ this.subscriptions.push(this.scrollPosition$
6457
+ .pipe(distinctUntilChanged())
6458
+ .subscribe((position) => {
6459
+ if (this.queryStateSubject.getValue().state === `loading-${position}`) {
6460
+ return;
6461
+ }
6462
+ if (position === 'top') {
6463
+ if (this.bufferOnTop < this.pageSize) {
6464
+ void this.loadMore(position);
6465
+ }
6466
+ else {
6467
+ this.loadMoreFromBuffer('top');
6468
+ }
6469
+ }
6470
+ else if (position === 'bottom') {
6471
+ if (this.bufferOnBottom < this.pageSize) {
6472
+ void this.loadMore(position);
6473
+ }
6474
+ else {
6475
+ this.loadMoreFromBuffer('bottom');
6476
+ }
6477
+ }
6478
+ }));
6479
+ this.subscriptions.push(this.allItems$
6480
+ .pipe(pairwise(), filter$1(() => {
6481
+ let scrollPosition;
6482
+ this.scrollPosition$
6483
+ .pipe(take$1(1))
6484
+ .subscribe((s) => (scrollPosition = s));
6485
+ return scrollPosition === 'middle';
6486
+ }))
6487
+ .subscribe(([prevItems, currentItems]) => {
6488
+ if (currentItems.length < this.maxItemCount ||
6489
+ this.virtualizedItems.length === 0) {
6490
+ this.virtualizedItemsSubject.next(currentItems);
6491
+ }
6492
+ else {
6493
+ const currentFirstItem = this.virtualizedItems[0];
6494
+ const currentLastItem = this.virtualizedItems[this.virtualizedItems.length - 1];
6495
+ const prevStartIndex = prevItems.findIndex((i) => this.isEqual(i, currentFirstItem));
6496
+ const prevEndIndex = prevItems.findIndex((i) => this.isEqual(i, currentLastItem));
6497
+ const isStartRemainedSame = currentItems[prevStartIndex]
6498
+ ? this.isEqual(currentItems[prevStartIndex], currentFirstItem)
6499
+ : false;
6500
+ const isEndRemainedSame = currentItems[prevEndIndex]
6501
+ ? this.isEqual(currentItems[prevEndIndex], currentLastItem)
6502
+ : false;
6503
+ const hasNewItemsBottom = prevEndIndex === prevItems.length - 1 && isEndRemainedSame
6504
+ ? prevItems.length !== currentItems.length
6505
+ : false;
6506
+ if (isStartRemainedSame && isEndRemainedSame) {
6507
+ const endIndex = hasNewItemsBottom ? undefined : prevEndIndex + 1;
6508
+ this.virtualizedItemsSubject.next(currentItems.slice(prevStartIndex, endIndex));
6509
+ }
6510
+ let currentStartIndex = isStartRemainedSame ? prevStartIndex : -1;
6511
+ let currentEndIndex = isEndRemainedSame ? prevEndIndex : -1;
6512
+ if (!isStartRemainedSame) {
6513
+ currentStartIndex = currentItems.findIndex((i) => this.isEqual(i, currentFirstItem));
6514
+ }
6515
+ if (!isEndRemainedSame) {
6516
+ currentEndIndex = currentItems.findIndex((i) => this.isEqual(i, currentLastItem));
6517
+ }
6518
+ const hasNewItemsTop = prevStartIndex === 0 && !isStartRemainedSame
6519
+ ? currentStartIndex !== 0
6520
+ : false;
6521
+ if (currentStartIndex !== -1 && currentEndIndex !== -1) {
6522
+ const startIndex = hasNewItemsTop ? 0 : currentStartIndex;
6523
+ this.virtualizedItemsSubject.next(currentItems.slice(startIndex, currentEndIndex + 1));
6524
+ }
6525
+ else {
6526
+ if (currentStartIndex === -1 && currentEndIndex !== -1) {
6527
+ currentStartIndex = Math.max(0, currentEndIndex - (prevEndIndex - prevStartIndex));
6528
+ }
6529
+ if (currentEndIndex === -1 && currentStartIndex !== -1) {
6530
+ currentEndIndex = Math.min(currentItems.length - 1, currentStartIndex + (prevEndIndex - prevStartIndex));
6531
+ }
6532
+ this.virtualizedItemsSubject.next(currentItems.slice(currentStartIndex, currentEndIndex + 1));
6533
+ }
6534
+ }
6535
+ }));
6536
+ if (this.jumpToItem$) {
6537
+ this.subscriptions.push(this.jumpToItem$
6538
+ .pipe(switchMap((jumpToItem) => combineLatest([this.allItems$.pipe(take$1(1)), of(jumpToItem)])))
6539
+ .subscribe(([allItems, jumpToItem]) => {
6540
+ if (jumpToItem.item) {
6541
+ if (allItems.length < this.maxItemCount) {
6542
+ this.virtualizedItemsSubject.next(allItems);
6543
+ }
6544
+ else {
6545
+ const itemIndex = allItems.findIndex((i) =>
6546
+ // @ts-expect-error TODO: do we know a better typing here?
6547
+ this.isEqual(i, jumpToItem.item));
6548
+ if (itemIndex === -1) {
6549
+ return;
6550
+ }
6551
+ else {
6552
+ const position = jumpToItem.position || 'middle';
6553
+ const numberOfItemsToRemove = allItems.length - Math.round(this.maxItemCount / 2);
6554
+ const numberOfItemsAfterRemove = allItems.length - numberOfItemsToRemove;
6555
+ let startIndex = -1;
6556
+ let endIndex = -1;
6557
+ switch (position) {
6558
+ case 'top':
6559
+ startIndex = itemIndex;
6560
+ endIndex = Math.min(allItems.length, startIndex + numberOfItemsAfterRemove);
6561
+ break;
6562
+ case 'bottom':
6563
+ endIndex = itemIndex + 1;
6564
+ startIndex = Math.max(0, endIndex - numberOfItemsAfterRemove);
6565
+ break;
6566
+ case 'middle': {
6567
+ const itemsOnTop = itemIndex;
6568
+ const itemsOnBottom = allItems.length - itemIndex;
6569
+ if (itemsOnTop < Math.ceil(numberOfItemsAfterRemove / 2)) {
6570
+ startIndex = 0;
6571
+ }
6572
+ if (itemsOnBottom <
6573
+ Math.floor(numberOfItemsAfterRemove / 2) + 1) {
6574
+ endIndex = allItems.length;
6575
+ }
6576
+ if (startIndex === -1) {
6577
+ if (endIndex !== -1) {
6578
+ startIndex = endIndex - numberOfItemsAfterRemove;
6579
+ }
6580
+ else {
6581
+ startIndex =
6582
+ itemIndex - Math.ceil(numberOfItemsAfterRemove / 2);
6583
+ }
6584
+ }
6585
+ if (endIndex === -1) {
6586
+ endIndex = startIndex + numberOfItemsAfterRemove;
6587
+ }
6588
+ }
6589
+ }
6590
+ this.virtualizedItemsSubject.next(allItems.slice(startIndex, endIndex));
6591
+ }
6592
+ }
6593
+ }
6594
+ }));
6595
+ }
6596
+ }
6597
+ /**
6598
+ * The current value of virtualized items
6599
+ */
6600
+ get virtualizedItems() {
6601
+ return this.virtualizedItemsSubject.getValue();
6602
+ }
6603
+ /**
6604
+ * Remove all subscriptions, call this once you're done using an instance of this service
6605
+ */
6606
+ dispose() {
6607
+ this.subscriptions.forEach((s) => s.unsubscribe());
6608
+ }
6609
+ loadMoreFromBuffer(_) {
6610
+ this.loadFromBuffer$.next();
6611
+ }
6612
+ loadMore(direction) {
6613
+ return __awaiter(this, void 0, void 0, function* () {
6614
+ this.queryStateSubject.next({ state: `loading-${direction}` });
6615
+ try {
6616
+ yield this.query(direction);
6617
+ this.queryStateSubject.next({ state: 'success' });
6618
+ }
6619
+ catch (e) {
6620
+ this.queryStateSubject.next({ state: 'error', error: e });
6621
+ }
6622
+ });
6623
+ }
6624
+ }
6625
+
6626
+ /**
6627
+ * The `VirtualizedMessageListService` removes messages from the message list that are currently not in view
6628
+ */
6629
+ class VirtualizedMessageListService extends VirtualizedListService {
6630
+ constructor(mode, scrollPosition$, channelService) {
6631
+ const jumpToMessage$ = channelService.jumpToMessage$.pipe(map$1((jumpToMessage) => {
6632
+ var _a;
6633
+ let result = {
6634
+ item: undefined,
6635
+ };
6636
+ let targetMessageId;
6637
+ if (mode === 'main') {
6638
+ targetMessageId = jumpToMessage.parentId
6639
+ ? jumpToMessage.parentId
6640
+ : jumpToMessage.id;
6641
+ }
6642
+ else {
6643
+ targetMessageId = jumpToMessage.parentId
6644
+ ? jumpToMessage.id
6645
+ : undefined;
6646
+ }
6647
+ if (targetMessageId) {
6648
+ const messages = mode === 'main'
6649
+ ? channelService.activeChannelMessages
6650
+ : channelService.activeChannelThreadReplies;
6651
+ const id = targetMessageId === 'latest'
6652
+ ? (_a = messages[messages.length - 1]) === null || _a === void 0 ? void 0 : _a.id
6653
+ : targetMessageId;
6654
+ if (id) {
6655
+ result = {
6656
+ item: { id },
6657
+ position: jumpToMessage.id === 'latest' ? 'bottom' : 'middle',
6658
+ };
6659
+ }
6660
+ channelService.clearMessageJump();
6661
+ }
6662
+ return result;
6663
+ }));
6664
+ const messages$ = mode === 'main'
6665
+ ? channelService.activeChannelMessages$
6666
+ : channelService.activeThreadMessages$;
6667
+ super(messages$, scrollPosition$, jumpToMessage$, channelService.messagePageSize);
6668
+ this.mode = mode;
6669
+ this.channelService = channelService;
6670
+ this.isEqual = (t1, t2) => t1.id === t2.id;
6671
+ this.query = (direction) => {
6672
+ const request = this.mode === 'main'
6673
+ ? (direction) => this.channelService.loadMoreMessages(direction)
6674
+ : (direction) => this.channelService.loadMoreThreadReplies(direction);
6675
+ const result = request(direction === 'top' ? 'older' : 'newer');
6676
+ if (result) {
6677
+ return result;
6678
+ }
6679
+ else {
6680
+ this.queryStateSubject.next({ state: 'success' });
6681
+ if ((direction === 'top' && this.bufferOnTop > 0) ||
6682
+ (direction === 'bottom' && this.bufferOnBottom > 0)) {
6683
+ this.loadFromBuffer$.next();
6684
+ }
6685
+ return Promise.resolve();
6686
+ }
6687
+ };
6688
+ }
6689
+ loadMoreFromBuffer(direction) {
6690
+ this.queryStateSubject.next({ state: `loading-${direction}` });
6691
+ setTimeout(() => {
6692
+ this.loadFromBuffer$.next();
6693
+ this.queryStateSubject.next({ state: 'success' });
6694
+ });
6695
+ }
6696
+ }
6697
+
6368
6698
  /**
6369
6699
  * The `MessageList` component renders a scrollable list of messages.
6370
6700
  */
@@ -6415,10 +6745,6 @@ class MessageListComponent {
6415
6745
  * You can turn on and off the loading indicator that signals to users that more messages are being loaded to the message list
6416
6746
  */
6417
6747
  this.displayLoadingIndicator = true;
6418
- /**
6419
- * @internal
6420
- */
6421
- this.limitNumberOfMessagesInList = true;
6422
6748
  this.emptyMainMessageListTemplate = null;
6423
6749
  this.emptyThreadMessageListTemplate = null;
6424
6750
  this.enabledMessageActions = [];
@@ -6426,17 +6752,20 @@ class MessageListComponent {
6426
6752
  this.newMessageCountWhileBeingScrolled = 0;
6427
6753
  this.groupStyles = [];
6428
6754
  this.isNextMessageOnSeparateDate = [];
6429
- this.isLoading = false;
6755
+ this.loadingState = 'idle';
6430
6756
  this.isUnreadNotificationVisible = true;
6431
6757
  this.isJumpingToLatestUnreadMessage = false;
6432
6758
  this.isJumpToLatestButtonVisible = true;
6759
+ this.isJumpingToMessage = false;
6433
6760
  this.scroll$ = new Subject();
6761
+ this.isNewMessageSentByUser = false;
6434
6762
  this.subscriptions = [];
6435
6763
  this.isLatestMessageInList = true;
6436
6764
  this.parsedDates = new Map();
6437
6765
  this.isViewInited = false;
6438
6766
  this.isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
6439
6767
  this.forceRepaintSubject = new Subject();
6768
+ this.scrollPosition$ = new BehaviorSubject('bottom');
6440
6769
  this.messageNotificationJumpClicked = () => {
6441
6770
  this.jumpToFirstUnreadMessage();
6442
6771
  this.isUnreadNotificationVisible = false;
@@ -6455,8 +6784,7 @@ class MessageListComponent {
6455
6784
  this.forceRepaint();
6456
6785
  }));
6457
6786
  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}` });
6787
+ var _a, _b;
6460
6788
  let isNewChannel = false;
6461
6789
  if (this.channelId !== (channel === null || channel === void 0 ? void 0 : channel.id)) {
6462
6790
  isNewChannel = true;
@@ -6464,12 +6792,9 @@ class MessageListComponent {
6464
6792
  clearTimeout(this.checkIfUnreadNotificationIsVisibleTimeout);
6465
6793
  }
6466
6794
  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
6795
  this.parsedDates = new Map();
6469
- if (this.messageRemoveTimeout) {
6470
- clearTimeout(this.messageRemoveTimeout);
6471
- }
6472
6796
  this.resetScrollState();
6797
+ this.setMessages$();
6473
6798
  this.channelId = channel === null || channel === void 0 ? void 0 : channel.id;
6474
6799
  if (this.isViewInited) {
6475
6800
  this.cdRef.detectChanges();
@@ -6516,7 +6841,7 @@ class MessageListComponent {
6516
6841
  this.cdRef.detectChanges();
6517
6842
  }
6518
6843
  }
6519
- const capabilites = (_f = channel === null || channel === void 0 ? void 0 : channel.data) === null || _f === void 0 ? void 0 : _f.own_capabilities;
6844
+ const capabilites = (_a = channel === null || channel === void 0 ? void 0 : channel.data) === null || _a === void 0 ? void 0 : _a.own_capabilities;
6520
6845
  const capabilitesString = [...(capabilites || [])].sort().join('');
6521
6846
  const enabledActionsString = [...(this.enabledMessageActions || [])]
6522
6847
  .sort()
@@ -6527,20 +6852,15 @@ class MessageListComponent {
6527
6852
  this.cdRef.detectChanges();
6528
6853
  }
6529
6854
  }
6530
- (_g = this.newMessageSubscription) === null || _g === void 0 ? void 0 : _g.unsubscribe();
6855
+ (_b = this.newMessageSubscription) === null || _b === void 0 ? void 0 : _b.unsubscribe();
6531
6856
  if (channel) {
6532
6857
  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') {
6858
+ if (!event.message) {
6537
6859
  return;
6538
6860
  }
6539
- this.newMessageReceived({
6540
- id: event.message.id,
6541
- user: event.message.user,
6542
- created_at: new Date(event.message.created_at || ''),
6543
- });
6861
+ else {
6862
+ this.newMessageReceived(event.message);
6863
+ }
6544
6864
  });
6545
6865
  }
6546
6866
  }));
@@ -6550,6 +6870,7 @@ class MessageListComponent {
6550
6870
  message.id !== this.parentMessage.id &&
6551
6871
  this.mode === 'thread') {
6552
6872
  this.resetScrollState();
6873
+ this.setMessages$();
6553
6874
  }
6554
6875
  if (this.parentMessage === message) {
6555
6876
  return;
@@ -6604,42 +6925,6 @@ class MessageListComponent {
6604
6925
  this.cdRef.detectChanges();
6605
6926
  }
6606
6927
  }));
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
6928
  this.subscriptions.push(this.customTemplatesService.emptyMainMessageListPlaceholder$.subscribe((template) => {
6644
6929
  const isChanged = this.emptyMainMessageListTemplate !== template;
6645
6930
  this.emptyMainMessageListTemplate = template || null;
@@ -6659,6 +6944,7 @@ class MessageListComponent {
6659
6944
  ngOnChanges(changes) {
6660
6945
  var _a;
6661
6946
  if (changes.mode || changes.direction) {
6947
+ this.resetScrollState();
6662
6948
  this.setMessages$();
6663
6949
  }
6664
6950
  if (changes.direction) {
@@ -6670,56 +6956,33 @@ class MessageListComponent {
6670
6956
  ngAfterViewInit() {
6671
6957
  this.isViewInited = true;
6672
6958
  this.ngZone.runOutsideAngular(() => {
6673
- this.scrollContainer.nativeElement.addEventListener('scroll', () => this.scrolled());
6959
+ var _a, _b;
6960
+ (_b = (_a = this.scrollContainer) === null || _a === void 0 ? void 0 : _a.nativeElement) === null || _b === void 0 ? void 0 : _b.addEventListener('scroll', () => this.scrolled());
6674
6961
  });
6675
6962
  }
6676
6963
  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;
6964
+ var _a, _b;
6965
+ if (this.isJumpingToMessage) {
6966
+ this.isNewMessageSentByUser = false;
6967
+ this.messageIdToAnchorTo = undefined;
6968
+ this.anchorMessageTopOffset = undefined;
6969
+ return;
6682
6970
  }
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
- }
6971
+ if (this.messageIdToAnchorTo && this.loadingState === 'idle') {
6972
+ this.preserveScrollbarPosition();
6692
6973
  }
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
- }
6974
+ else if ((!this.isUserScrolled &&
6975
+ ((_a = this.scrollContainer.nativeElement) === null || _a === void 0 ? void 0 : _a.scrollHeight) >
6976
+ ((_b = this.scrollContainer) === null || _b === void 0 ? void 0 : _b.nativeElement.clientHeight) &&
6977
+ this.getScrollPosition() !==
6978
+ (this.direction === 'bottom-to-top' ? 'bottom' : 'top')) ||
6979
+ (this.isUserScrolled && this.isNewMessageSentByUser)) {
6980
+ this.isNewMessageSentByUser = false;
6981
+ this.jumpToLatestMessage();
6719
6982
  }
6720
6983
  }
6721
6984
  ngOnDestroy() {
6722
- var _a, _b;
6985
+ var _a;
6723
6986
  this.subscriptions.forEach((s) => s.unsubscribe());
6724
6987
  (_a = this.newMessageSubscription) === null || _a === void 0 ? void 0 : _a.unsubscribe();
6725
6988
  if (this.scrollEndTimeout) {
@@ -6731,20 +6994,24 @@ class MessageListComponent {
6731
6994
  if (this.jumpToLatestButtonVisibilityTimeout) {
6732
6995
  clearTimeout(this.jumpToLatestButtonVisibilityTimeout);
6733
6996
  }
6734
- if (this.messageRemoveTimeout) {
6735
- clearTimeout(this.messageRemoveTimeout);
6736
- }
6737
- (_b = this.removeOldMessagesSubscription) === null || _b === void 0 ? void 0 : _b.unsubscribe();
6997
+ this.disposeVirtualizedList();
6738
6998
  }
6739
- trackByMessageId(index, item) {
6999
+ trackByMessageId(_, item) {
6740
7000
  return item.id;
6741
7001
  }
6742
- trackByUserId(index, user) {
7002
+ trackByUserId(_, user) {
6743
7003
  return user.id;
6744
7004
  }
6745
7005
  jumpToLatestMessage() {
6746
7006
  var _a;
6747
- void this.channelService.jumpToMessage('latest', this.mode === 'thread' ? (_a = this.parentMessage) === null || _a === void 0 ? void 0 : _a.id : undefined);
7007
+ if (this.isLatestMessageInList) {
7008
+ this.direction === 'bottom-to-top'
7009
+ ? this.scrollToBottom()
7010
+ : this.scrollToTop();
7011
+ }
7012
+ else {
7013
+ void this.channelService.jumpToMessage('latest', this.mode === 'thread' ? (_a = this.parentMessage) === null || _a === void 0 ? void 0 : _a.id : undefined);
7014
+ }
6748
7015
  }
6749
7016
  scrollToBottom() {
6750
7017
  this.scrollContainer.nativeElement.scrollTop =
@@ -6757,7 +7024,7 @@ class MessageListComponent {
6757
7024
  this.scrollContainer.nativeElement.scrollTop = 0;
6758
7025
  }
6759
7026
  scrolled() {
6760
- var _a, _b;
7027
+ var _a;
6761
7028
  if (this.scrollContainer.nativeElement.scrollHeight ===
6762
7029
  this.scrollContainer.nativeElement.clientHeight) {
6763
7030
  if (this.isJumpToLatestButtonVisible) {
@@ -6768,8 +7035,7 @@ class MessageListComponent {
6768
7035
  return;
6769
7036
  }
6770
7037
  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}` });
7038
+ let scrollPosition = this.getScrollPosition();
6773
7039
  const isUserScrolled = (this.direction === 'bottom-to-top'
6774
7040
  ? scrollPosition !== 'bottom'
6775
7041
  : scrollPosition !== 'top') || !this.isLatestMessageInList;
@@ -6798,31 +7064,31 @@ class MessageListComponent {
6798
7064
  }
6799
7065
  }, 100);
6800
7066
  }
6801
- if (this.shouldLoadMoreMessages(scrollPosition)) {
7067
+ const prevScrollPosition = this.scrollPosition$.getValue();
7068
+ if (this.direction === 'top-to-bottom') {
7069
+ if (scrollPosition === 'top') {
7070
+ scrollPosition = 'bottom';
7071
+ }
7072
+ else if (scrollPosition === 'bottom') {
7073
+ scrollPosition = 'top';
7074
+ }
7075
+ }
7076
+ if (prevScrollPosition !== scrollPosition && !this.isJumpingToMessage) {
7077
+ if (scrollPosition === 'top' || scrollPosition === 'bottom') {
7078
+ (_a = this.virtualizedList) === null || _a === void 0 ? void 0 : _a.virtualizedItems$.pipe(take(1)).subscribe((items) => {
7079
+ var _a, _b, _c, _d;
7080
+ this.messageIdToAnchorTo =
7081
+ scrollPosition === 'top'
7082
+ ? (_a = items[0]) === null || _a === void 0 ? void 0 : _a.id
7083
+ : (_b = items[items.length - 1]) === null || _b === void 0 ? void 0 : _b.id;
7084
+ this.anchorMessageTopOffset = (_d = (_c = document
7085
+ .getElementById(this.messageIdToAnchorTo)) === null || _c === void 0 ? void 0 : _c.getBoundingClientRect()) === null || _d === void 0 ? void 0 : _d.top;
7086
+ });
7087
+ }
6802
7088
  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();
7089
+ this.scrollPosition$.next(scrollPosition);
6823
7090
  });
6824
7091
  }
6825
- this.prevScrollTop = this.scrollContainer.nativeElement.scrollTop;
6826
7092
  }
6827
7093
  jumpToFirstUnreadMessage() {
6828
7094
  if (!this.lastReadMessageId) {
@@ -6865,9 +7131,19 @@ class MessageListComponent {
6865
7131
  : this.emptyThreadMessageListTemplate;
6866
7132
  }
6867
7133
  preserveScrollbarPosition() {
6868
- this.scrollContainer.nativeElement.scrollTop =
6869
- (this.prevScrollTop || 0) +
6870
- (this.scrollContainer.nativeElement.scrollHeight - this.containerHeight);
7134
+ var _a;
7135
+ if (!this.messageIdToAnchorTo) {
7136
+ return;
7137
+ }
7138
+ const messageToAlignTo = document.getElementById(this.messageIdToAnchorTo);
7139
+ this.messageIdToAnchorTo = undefined;
7140
+ this.scrollContainer.nativeElement.scrollTop +=
7141
+ (((_a = messageToAlignTo === null || messageToAlignTo === void 0 ? void 0 : messageToAlignTo.getBoundingClientRect()) === null || _a === void 0 ? void 0 : _a.top) || 0) -
7142
+ (this.anchorMessageTopOffset || 0);
7143
+ this.anchorMessageTopOffset = undefined;
7144
+ if (this.isSafari) {
7145
+ this.forceRepaintSubject.next();
7146
+ }
6871
7147
  }
6872
7148
  forceRepaint() {
6873
7149
  // Solves the issue of empty screen on Safari when scrolling
@@ -6876,13 +7152,10 @@ class MessageListComponent {
6876
7152
  this.scrollContainer.nativeElement.style.display = '';
6877
7153
  }
6878
7154
  getScrollPosition() {
6879
- var _a, _b;
7155
+ var _a;
6880
7156
  let position = 'middle';
6881
7157
  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))) {
7158
+ (((_a = this.parentMessageElement) === null || _a === void 0 ? void 0 : _a.nativeElement.clientHeight) || 0)) {
6886
7159
  position = 'top';
6887
7160
  }
6888
7161
  else if (Math.ceil(this.scrollContainer.nativeElement.scrollTop) +
@@ -6893,24 +7166,24 @@ class MessageListComponent {
6893
7166
  }
6894
7167
  return position;
6895
7168
  }
6896
- shouldLoadMoreMessages(scrollPosition) {
6897
- return (scrollPosition !== 'middle' &&
6898
- !this.highlightedMessageId &&
6899
- !this.isLoading);
6900
- }
6901
7169
  setMessages$() {
6902
7170
  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;
7171
+ this.disposeVirtualizedList();
7172
+ this.virtualizedList = new VirtualizedMessageListService(this.mode, this.scrollPosition$, this.channelService);
7173
+ this.queryStateSubscription = this.virtualizedList.queryState$.subscribe((queryState) => {
7174
+ let mappedState = 'idle';
7175
+ if (queryState.state.includes('loading')) {
7176
+ mappedState = queryState.state || 'loading-bottom';
7177
+ }
7178
+ if (mappedState !== this.loadingState) {
7179
+ this.loadingState = mappedState;
7180
+ if (this.isViewInited) {
7181
+ this.cdRef.detectChanges();
7182
+ }
6909
7183
  }
7184
+ });
7185
+ this.messages$ = this.virtualizedList.virtualizedItems$.pipe(tap((messages) => {
6910
7186
  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
7187
  this.resetScrollState();
6915
7188
  return;
6916
7189
  }
@@ -6918,21 +7191,6 @@ class MessageListComponent {
6918
7191
  // cdRef.detectChanges() isn't enough here, test will fail
6919
7192
  setTimeout(() => (this.isEmpty = false), 0);
6920
7193
  }
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
7194
  }), tap((messages) => {
6937
7195
  var _a;
6938
7196
  if (this.isJumpingToLatestUnreadMessage &&
@@ -6952,82 +7210,101 @@ class MessageListComponent {
6952
7210
  m.status !== 'sending';
6953
7211
  })) === null || _a === void 0 ? void 0 : _a.id);
6954
7212
  }), tap((messages) => {
7213
+ var _a, _b;
7214
+ const latestMessageInList = messages[messages.length - 1];
7215
+ const channel = this.channelService.activeChannel;
7216
+ const messagesFromState = (this.mode === 'main'
7217
+ ? channel === null || channel === void 0 ? void 0 : channel.state.latestMessages
7218
+ : channel === null || channel === void 0 ? void 0 : channel.state.threads[((_a = this.parentMessage) === null || _a === void 0 ? void 0 : _a.id) || '']) || [];
6955
7219
  this.isLatestMessageInList =
6956
- !this.latestMessage ||
6957
- messages.length === 0 ||
6958
- messages[messages.length - 1].id === this.latestMessage.id ||
6959
- this.mode === 'thread';
7220
+ !latestMessageInList ||
7221
+ latestMessageInList.cid !== (channel === null || channel === void 0 ? void 0 : channel.cid) ||
7222
+ (latestMessageInList === null || latestMessageInList === void 0 ? void 0 : latestMessageInList.id) ===
7223
+ ((_b = messagesFromState[messagesFromState.length - 1]) === null || _b === void 0 ? void 0 : _b.id);
6960
7224
  if (!this.isLatestMessageInList) {
6961
7225
  this.isUserScrolled = true;
6962
7226
  }
6963
- }), map((messages) => this.direction === 'bottom-to-top' ? messages : [...messages].reverse()), tap((messages) => {
7227
+ }), map((messages) => {
7228
+ return this.direction === 'bottom-to-top'
7229
+ ? messages
7230
+ : [...messages].reverse();
7231
+ }), tap((messages) => {
6964
7232
  this.groupStyles = messages.map((m, i) => getGroupStyles(m, messages[i - 1], messages[i + 1], {
6965
7233
  lastReadMessageId: this.lastReadMessageId,
6966
7234
  }));
6967
7235
  this.isNextMessageOnSeparateDate = messages.map((m, i) => this.checkIfOnSeparateDates(m, messages[i + 1]));
6968
7236
  }), 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);
7237
+ if ((_a = this.virtualizedList) === null || _a === void 0 ? void 0 : _a.jumpToItem$) {
7238
+ this.jumpToItemSubscription = this.virtualizedList.jumpToItem$
7239
+ .pipe(filter((jumpToMessage) => { var _a; return !!((_a = jumpToMessage.item) === null || _a === void 0 ? void 0 : _a.id); }))
7240
+ .subscribe((jumpToMessage) => {
7241
+ var _a;
7242
+ let messageId = (_a = jumpToMessage.item) === null || _a === void 0 ? void 0 : _a.id;
7243
+ if (messageId) {
7244
+ if (this.isJumpingToLatestUnreadMessage) {
7245
+ messageId = this.firstUnreadMessageId || messageId;
7246
+ }
7247
+ if (jumpToMessage.position !== 'bottom') {
7248
+ this.highlightedMessageId = messageId;
7249
+ }
7250
+ else if (this.direction === 'top-to-bottom') {
7251
+ jumpToMessage.position = 'top';
7252
+ }
7253
+ this.isJumpingToMessage = true;
7254
+ this.scrollMessageIntoView({
7255
+ messageId: this.firstUnreadMessageId || messageId,
7256
+ position: jumpToMessage.position || 'middle',
7257
+ });
6999
7258
  }
7000
- }
7001
- });
7259
+ });
7260
+ }
7002
7261
  }
7003
7262
  resetScrollState() {
7004
7263
  this.isEmpty = true;
7005
- this.latestMessage = undefined;
7006
- this.hasNewMessages = true;
7007
7264
  this.isUserScrolled = false;
7008
- this.containerHeight = undefined;
7009
- this.olderMassagesLoaded = false;
7010
- this.oldestMessage = undefined;
7265
+ this.messageIdToAnchorTo = undefined;
7266
+ this.anchorMessageTopOffset = undefined;
7011
7267
  this.newMessageCountWhileBeingScrolled = 0;
7012
- this.prevScrollTop = undefined;
7013
- this.isNewMessageSentByUser = undefined;
7268
+ this.isNewMessageSentByUser = false;
7014
7269
  this.isLatestMessageInList = true;
7270
+ this.isJumpingToMessage = false;
7271
+ this.scrollPosition$.next('bottom');
7272
+ this.loadingState = 'idle';
7273
+ }
7274
+ disposeVirtualizedList() {
7275
+ var _a, _b, _c;
7276
+ (_a = this.virtualizedList) === null || _a === void 0 ? void 0 : _a.dispose();
7277
+ (_b = this.jumpToItemSubscription) === null || _b === void 0 ? void 0 : _b.unsubscribe();
7278
+ (_c = this.queryStateSubscription) === null || _c === void 0 ? void 0 : _c.unsubscribe();
7015
7279
  }
7016
7280
  get usersTyping$() {
7017
7281
  return this.mode === 'thread'
7018
7282
  ? this.usersTypingInThread$
7019
7283
  : this.usersTypingInChannel$;
7020
7284
  }
7021
- scrollMessageIntoView(messageId, withRetry = true) {
7022
- const element = document.getElementById(messageId);
7285
+ scrollMessageIntoView(options, withRetry = true) {
7286
+ const element = document.getElementById(options.messageId);
7023
7287
  if (!element && withRetry) {
7024
7288
  // 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));
7289
+ setTimeout(() => this.scrollMessageIntoView(options, false));
7026
7290
  }
7027
7291
  else if (element) {
7292
+ const blockMapping = {
7293
+ top: 'start',
7294
+ bottom: 'end',
7295
+ middle: 'center',
7296
+ };
7028
7297
  element.scrollIntoView({
7029
- block: 'center',
7298
+ block: blockMapping[options.position],
7030
7299
  });
7300
+ if (options.position !== 'middle') {
7301
+ options.position === 'bottom'
7302
+ ? this.scrollToBottom()
7303
+ : this.scrollToTop();
7304
+ }
7305
+ setTimeout(() => {
7306
+ this.isJumpingToMessage = false;
7307
+ }, 0);
7031
7308
  setTimeout(() => {
7032
7309
  this.highlightedMessageId = undefined;
7033
7310
  this.firstUnreadMessageId = undefined;
@@ -7036,40 +7313,27 @@ class MessageListComponent {
7036
7313
  }, 1000);
7037
7314
  }
7038
7315
  }
7039
- scrollToLatestMessage(withRetry = true) {
7040
- if (document.getElementById(this.latestMessage.id)) {
7041
- this.direction === 'bottom-to-top'
7042
- ? this.scrollToBottom()
7043
- : this.scrollToTop();
7316
+ newMessageReceived(message) {
7317
+ var _a, _b, _c, _d;
7318
+ if ((this.mode === 'main' && message.parent_id) ||
7319
+ (this.mode === 'thread' && message.parent_id !== ((_a = this.parentMessage) === null || _a === void 0 ? void 0 : _a.id))) {
7320
+ return;
7044
7321
  }
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);
7322
+ 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);
7323
+ let shouldDetectChanges = false;
7324
+ if (!this.isNewMessageSentByUser && isNewMessageSentByCurrentUser) {
7325
+ this.isNewMessageSentByUser = true;
7326
+ shouldDetectChanges = true;
7048
7327
  }
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
- }
7328
+ if (this.isUserScrolled) {
7329
+ this.newMessageCountWhileBeingScrolled++;
7330
+ shouldDetectChanges = true;
7331
+ }
7332
+ if (!this.isNewMessageSentByUser && this.unreadCount !== undefined) {
7333
+ this.unreadCount++;
7334
+ shouldDetectChanges = true;
7335
+ }
7336
+ if (shouldDetectChanges && this.isViewInited) {
7073
7337
  this.cdRef.detectChanges();
7074
7338
  }
7075
7339
  }
@@ -7081,10 +7345,10 @@ class MessageListComponent {
7081
7345
  }
7082
7346
  }
7083
7347
  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 });
7348
+ 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
7349
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: MessageListComponent, decorators: [{
7086
7350
  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" }]
7351
+ 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
7352
  }], ctorParameters: function () { return [{ type: ChannelService }, { type: ChatClientService }, { type: CustomTemplatesService }, { type: DateParserService }, { type: i0.NgZone }, { type: i0.ChangeDetectorRef }]; }, propDecorators: { mode: [{
7089
7353
  type: Input
7090
7354
  }], direction: [{
@@ -7103,8 +7367,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImpor
7103
7367
  type: Input
7104
7368
  }], displayLoadingIndicator: [{
7105
7369
  type: Input
7106
- }], limitNumberOfMessagesInList: [{
7107
- type: Input
7108
7370
  }], scrollContainer: [{
7109
7371
  type: ViewChild,
7110
7372
  args: ['scrollContainer']
@@ -7351,5 +7613,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImpor
7351
7613
  * Generated bundle index. Do not edit.
7352
7614
  */
7353
7615
 
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 };
7616
+ 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
7617
  //# sourceMappingURL=stream-chat-angular.mjs.map