stream-chat-angular 2.8.2 → 2.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/assets/i18n/en.d.ts +2 -0
  2. package/assets/version.d.ts +1 -1
  3. package/bundles/stream-chat-angular.umd.js +804 -338
  4. package/bundles/stream-chat-angular.umd.js.map +1 -1
  5. package/esm2015/assets/i18n/en.js +3 -1
  6. package/esm2015/assets/version.js +2 -2
  7. package/esm2015/lib/attachment-list/attachment-list.component.js +21 -8
  8. package/esm2015/lib/channel/channel.component.js +3 -2
  9. package/esm2015/lib/channel.service.js +191 -38
  10. package/esm2015/lib/chat-client.service.js +6 -1
  11. package/esm2015/lib/icon/icon.component.js +2 -2
  12. package/esm2015/lib/message/message.component.js +67 -42
  13. package/esm2015/lib/message-actions-box/message-actions-box.component.js +20 -9
  14. package/esm2015/lib/message-input/autocomplete-textarea/autocomplete-textarea.component.js +35 -9
  15. package/esm2015/lib/message-input/message-input-config.service.js +1 -1
  16. package/esm2015/lib/message-input/message-input.component.js +69 -13
  17. package/esm2015/lib/message-input/textarea.directive.js +10 -2
  18. package/esm2015/lib/message-input/textarea.interface.js +1 -1
  19. package/esm2015/lib/message-list/message-list.component.js +130 -55
  20. package/esm2015/lib/message-preview.js +4 -2
  21. package/esm2015/lib/stream-chat.module.js +8 -3
  22. package/esm2015/lib/thread/thread.component.js +37 -0
  23. package/esm2015/lib/types.js +1 -1
  24. package/esm2015/public-api.js +2 -1
  25. package/fesm2015/stream-chat-angular.js +659 -250
  26. package/fesm2015/stream-chat-angular.js.map +1 -1
  27. package/lib/attachment-list/attachment-list.component.d.ts +8 -3
  28. package/lib/channel/channel.component.d.ts +2 -1
  29. package/lib/channel.service.d.ts +23 -7
  30. package/lib/chat-client.service.d.ts +1 -0
  31. package/lib/icon/icon.component.d.ts +1 -1
  32. package/lib/message/message.component.d.ts +26 -8
  33. package/lib/message-actions-box/message-actions-box.component.d.ts +4 -1
  34. package/lib/message-input/autocomplete-textarea/autocomplete-textarea.component.d.ts +6 -3
  35. package/lib/message-input/message-input-config.service.d.ts +2 -1
  36. package/lib/message-input/message-input.component.d.ts +9 -2
  37. package/lib/message-input/textarea.directive.d.ts +3 -2
  38. package/lib/message-input/textarea.interface.d.ts +2 -1
  39. package/lib/message-list/message-list.component.d.ts +21 -7
  40. package/lib/message-preview.d.ts +1 -1
  41. package/lib/stream-chat.module.d.ts +5 -4
  42. package/lib/thread/thread.component.d.ts +18 -0
  43. package/lib/types.d.ts +7 -1
  44. package/package.json +2 -11
  45. package/public-api.d.ts +1 -0
  46. package/src/assets/i18n/en.ts +4 -0
  47. package/src/assets/version.ts +1 -1
@@ -1,22 +1,22 @@
1
1
  import { __awaiter } from 'tslib';
2
2
  import * as i0 from '@angular/core';
3
3
  import { Injectable, Component, Input, InjectionToken, EventEmitter, Directive, Output, Inject, ViewChild, HostBinding, NgModule } from '@angular/core';
4
- import { BehaviorSubject, ReplaySubject, of, Subject } from 'rxjs';
4
+ import { BehaviorSubject, ReplaySubject, combineLatest, Subject, of } from 'rxjs';
5
5
  import { StreamChat } from 'stream-chat';
6
- import { map, first, catchError, startWith, distinctUntilChanged, filter, debounceTime, tap } from 'rxjs/operators';
6
+ import { map, shareReplay, filter, first, catchError, startWith, distinctUntilChanged, debounceTime, tap } from 'rxjs/operators';
7
7
  import { v4 } from 'uuid';
8
- import * as i10 from '@ngx-translate/core';
8
+ import * as i1 from '@ngx-translate/core';
9
9
  import { TranslateModule } from '@ngx-translate/core';
10
- import * as i3 from '@angular/common';
10
+ import * as i6 from '@angular/common';
11
11
  import { CommonModule } from '@angular/common';
12
+ import prettybytes from 'pretty-bytes';
12
13
  import Dayjs from 'dayjs';
13
14
  import calendar from 'dayjs/plugin/calendar';
14
- import prettybytes from 'pretty-bytes';
15
15
  import transliterate from '@stream-io/transliterate';
16
16
  import * as i5 from 'angular-mentions';
17
17
  import { MentionModule } from 'angular-mentions';
18
18
 
19
- const version = '2.8.2';
19
+ const version = '2.12.0';
20
20
 
21
21
  class NotificationService {
22
22
  constructor() {
@@ -105,6 +105,11 @@ class ChatClientService {
105
105
  });
106
106
  });
107
107
  }
108
+ disconnectUser() {
109
+ return __awaiter(this, void 0, void 0, function* () {
110
+ yield this.chatClient.disconnectUser();
111
+ });
112
+ }
108
113
  getAppSettings() {
109
114
  return __awaiter(this, void 0, void 0, function* () {
110
115
  if (this.appSettingsSubject.getValue()) {
@@ -144,7 +149,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.5", ngImpor
144
149
  }]
145
150
  }], ctorParameters: function () { return [{ type: i0.NgZone }, { type: NotificationService }]; } });
146
151
 
147
- const createMessagePreview = (user, text, attachments, mentionedUsers) => {
152
+ const createMessagePreview = (user, text, attachments = [], mentionedUsers = [], parentId = undefined, quotedMessageId = undefined) => {
148
153
  const clientSideId = `${user.id}-${v4()}`;
149
154
  return {
150
155
  __html: text,
@@ -158,6 +163,8 @@ const createMessagePreview = (user, text, attachments, mentionedUsers) => {
158
163
  user,
159
164
  attachments,
160
165
  mentioned_users: mentionedUsers,
166
+ parent_id: parentId,
167
+ quoted_message_id: quotedMessageId,
161
168
  };
162
169
  };
163
170
 
@@ -183,31 +190,52 @@ class ChannelService {
183
190
  this.activeChannelMessagesSubject = new BehaviorSubject([]);
184
191
  this.hasMoreChannelsSubject = new ReplaySubject(1);
185
192
  this.activeChannelSubscriptions = [];
193
+ this.activeParentMessageIdSubject = new BehaviorSubject(undefined);
194
+ this.activeThreadMessagesSubject = new BehaviorSubject([]);
195
+ this.messagePageSize = 25;
196
+ this.messageToQuoteSubject = new BehaviorSubject(undefined);
186
197
  this.channelListSetter = (channels) => {
187
198
  this.channelsSubject.next(channels);
188
199
  };
189
200
  this.messageListSetter = (messages) => {
190
201
  this.activeChannelMessagesSubject.next(messages);
191
202
  };
203
+ this.threadListSetter = (messages) => {
204
+ this.activeThreadMessagesSubject.next(messages);
205
+ };
206
+ this.parentMessageSetter = (message) => {
207
+ this.activeParentMessageIdSubject.next(message === null || message === void 0 ? void 0 : message.id);
208
+ };
192
209
  this.channels$ = this.channelsSubject.asObservable();
193
210
  this.activeChannel$ = this.activeChannelSubject.asObservable();
194
211
  this.activeChannelMessages$ = this.activeChannelMessagesSubject.pipe(map((messages) => {
195
212
  const channel = this.activeChannelSubject.getValue();
196
- return messages.map((message) => {
197
- if (this.isStreamMessage(message) &&
198
- this.isFormatMessageResponse(message)) {
199
- return message;
200
- }
201
- else if (this.isFormatMessageResponse(message)) {
202
- return Object.assign(Object.assign({}, message), { readBy: getReadBy(message, channel) });
203
- }
204
- else {
205
- const formatMessage = this.formatMessage(message);
206
- return Object.assign(Object.assign({}, formatMessage), { readBy: getReadBy(formatMessage, channel) });
207
- }
208
- });
213
+ return messages.map((message) => this.transformToStreamMessage(message, channel));
209
214
  }));
210
215
  this.hasMoreChannels$ = this.hasMoreChannelsSubject.asObservable();
216
+ this.activeParentMessageId$ =
217
+ this.activeParentMessageIdSubject.asObservable();
218
+ this.activeThreadMessages$ = this.activeThreadMessagesSubject.pipe(map((messages) => {
219
+ const channel = this.activeChannelSubject.getValue();
220
+ return messages.map((message) => this.transformToStreamMessage(message, channel));
221
+ }));
222
+ this.activeParentMessage$ = combineLatest([
223
+ this.activeChannelMessages$,
224
+ this.activeParentMessageId$,
225
+ ]).pipe(map(([messages, parentMessageId]) => {
226
+ if (!parentMessageId) {
227
+ return undefined;
228
+ }
229
+ else {
230
+ return messages.find((m) => m.id === parentMessageId);
231
+ }
232
+ }), shareReplay());
233
+ this.messageToQuote$ = this.messageToQuoteSubject.asObservable();
234
+ this.chatClientService.connectionState$
235
+ .pipe(filter((s) => s === 'online'))
236
+ .subscribe(() => {
237
+ void this.setAsActiveParentMessage(undefined);
238
+ });
211
239
  }
212
240
  setAsActiveChannel(channel) {
213
241
  const prevActiveChannel = this.activeChannelSubject.getValue();
@@ -221,25 +249,66 @@ class ChannelService {
221
249
  void channel.markRead();
222
250
  }
223
251
  this.activeChannelMessagesSubject.next([...channel.state.messages]);
252
+ this.activeParentMessageIdSubject.next(undefined);
253
+ this.activeThreadMessagesSubject.next([]);
254
+ this.messageToQuoteSubject.next(undefined);
224
255
  }
256
+ setAsActiveParentMessage(message) {
257
+ var _a;
258
+ return __awaiter(this, void 0, void 0, function* () {
259
+ const messageToQuote = this.messageToQuoteSubject.getValue();
260
+ if (messageToQuote && !!messageToQuote.parent_id) {
261
+ this.messageToQuoteSubject.next(undefined);
262
+ }
263
+ if (!message) {
264
+ this.activeParentMessageIdSubject.next(undefined);
265
+ this.activeThreadMessagesSubject.next([]);
266
+ }
267
+ else {
268
+ this.activeParentMessageIdSubject.next(message.id);
269
+ const activeChannel = this.activeChannelSubject.getValue();
270
+ const result = yield (activeChannel === null || activeChannel === void 0 ? void 0 : activeChannel.getReplies(message.id, {
271
+ limit: (_a = this.options) === null || _a === void 0 ? void 0 : _a.message_limit,
272
+ }));
273
+ this.activeThreadMessagesSubject.next((result === null || result === void 0 ? void 0 : result.messages) || []);
274
+ }
275
+ });
276
+ }
277
+ // load more thread replies
225
278
  loadMoreMessages() {
226
- var _a, _b, _c, _d;
279
+ var _a, _b, _c, _d, _e;
227
280
  return __awaiter(this, void 0, void 0, function* () {
228
281
  const activeChnannel = this.activeChannelSubject.getValue();
229
282
  const lastMessageId = (_a = this.activeChannelMessagesSubject.getValue()[0]) === null || _a === void 0 ? void 0 : _a.id;
230
283
  yield (activeChnannel === null || activeChnannel === void 0 ? void 0 : activeChnannel.query({
231
- messages: { limit: 25, id_lt: lastMessageId },
284
+ messages: { limit: (_b = this.options) === null || _b === void 0 ? void 0 : _b.message_limit, id_lt: lastMessageId },
232
285
  members: { limit: 0 },
233
286
  watchers: { limit: 0 },
234
287
  }));
235
- if (((_b = activeChnannel === null || activeChnannel === void 0 ? void 0 : activeChnannel.data) === null || _b === void 0 ? void 0 : _b.id) ===
236
- ((_d = (_c = this.activeChannelSubject.getValue()) === null || _c === void 0 ? void 0 : _c.data) === null || _d === void 0 ? void 0 : _d.id)) {
288
+ if (((_c = activeChnannel === null || activeChnannel === void 0 ? void 0 : activeChnannel.data) === null || _c === void 0 ? void 0 : _c.id) ===
289
+ ((_e = (_d = this.activeChannelSubject.getValue()) === null || _d === void 0 ? void 0 : _d.data) === null || _e === void 0 ? void 0 : _e.id)) {
237
290
  this.activeChannelMessagesSubject.next([
238
291
  ...activeChnannel.state.messages,
239
292
  ]);
240
293
  }
241
294
  });
242
295
  }
296
+ loadMoreThreadReplies() {
297
+ var _a, _b;
298
+ return __awaiter(this, void 0, void 0, function* () {
299
+ const activeChnannel = this.activeChannelSubject.getValue();
300
+ const parentMessageId = this.activeParentMessageIdSubject.getValue();
301
+ if (!parentMessageId) {
302
+ return;
303
+ }
304
+ const lastMessageId = (_a = this.activeThreadMessagesSubject.getValue()[0]) === null || _a === void 0 ? void 0 : _a.id;
305
+ yield (activeChnannel === null || activeChnannel === void 0 ? void 0 : activeChnannel.getReplies(parentMessageId, {
306
+ limit: (_b = this.options) === null || _b === void 0 ? void 0 : _b.message_limit,
307
+ id_lt: lastMessageId,
308
+ }));
309
+ this.activeThreadMessagesSubject.next((activeChnannel === null || activeChnannel === void 0 ? void 0 : activeChnannel.state.threads[parentMessageId]) || []);
310
+ });
311
+ }
243
312
  init(filters, sort, options) {
244
313
  return __awaiter(this, void 0, void 0, function* () {
245
314
  this.filters = filters;
@@ -249,13 +318,21 @@ class ChannelService {
249
318
  state: true,
250
319
  presence: true,
251
320
  watch: true,
252
- message_limit: 25,
321
+ message_limit: this.messagePageSize,
253
322
  };
254
323
  this.sort = sort || { last_message_at: -1, updated_at: -1 };
255
324
  yield this.queryChannels();
256
325
  this.chatClientService.notification$.subscribe((notification) => void this.handleNotification(notification));
257
326
  });
258
327
  }
328
+ reset() {
329
+ this.activeChannelMessagesSubject.next([]);
330
+ this.activeChannelSubject.next(undefined);
331
+ this.activeParentMessageIdSubject.next(undefined);
332
+ this.activeThreadMessagesSubject.next([]);
333
+ this.channelsSubject.next(undefined);
334
+ this.selectMessageToQuote(undefined);
335
+ }
259
336
  loadMoreChannels() {
260
337
  return __awaiter(this, void 0, void 0, function* () {
261
338
  this.options.offset = this.channels.length;
@@ -277,9 +354,9 @@ class ChannelService {
277
354
  .getValue()) === null || _a === void 0 ? void 0 : _a.deleteReaction(messageId, reactionType));
278
355
  });
279
356
  }
280
- sendMessage(text, attachments = [], mentionedUsers = []) {
357
+ sendMessage(text, attachments = [], mentionedUsers = [], parentId = undefined, quotedMessageId = undefined) {
281
358
  return __awaiter(this, void 0, void 0, function* () {
282
- const preview = createMessagePreview(this.chatClientService.chatClient.user, text, attachments, mentionedUsers);
359
+ const preview = createMessagePreview(this.chatClientService.chatClient.user, text, attachments, mentionedUsers, parentId, quotedMessageId);
283
360
  const channel = this.activeChannelSubject.getValue();
284
361
  preview.readBy = [];
285
362
  channel.state.addMessageSorted(preview, true);
@@ -357,18 +434,64 @@ class ChannelService {
357
434
  }
358
435
  });
359
436
  }
437
+ sendAction(messageId, formData) {
438
+ return __awaiter(this, void 0, void 0, function* () {
439
+ const channel = this.activeChannelSubject.getValue();
440
+ const response = yield channel.sendAction(messageId, formData);
441
+ if (response === null || response === void 0 ? void 0 : response.message) {
442
+ channel.state.addMessageSorted(Object.assign(Object.assign({}, response.message), { status: 'received' }));
443
+ const isThreadReply = !!response.message.parent_id;
444
+ isThreadReply
445
+ ? this.activeThreadMessagesSubject.next([
446
+ ...channel.state.threads[response.message.parent_id],
447
+ ])
448
+ : this.activeChannelMessagesSubject.next([...channel.state.messages]);
449
+ }
450
+ else {
451
+ channel.state.removeMessage({ id: messageId });
452
+ if (this.activeChannelMessagesSubject
453
+ .getValue()
454
+ .find((m) => m.id === messageId)) {
455
+ this.activeChannelMessagesSubject.next([...channel.state.messages]);
456
+ }
457
+ else if (this.activeThreadMessagesSubject
458
+ .getValue()
459
+ .find((m) => m.id === messageId)) {
460
+ this.activeThreadMessagesSubject.next(channel.state.threads[this.activeParentMessageIdSubject.getValue()]);
461
+ }
462
+ }
463
+ });
464
+ }
465
+ selectMessageToQuote(message) {
466
+ this.messageToQuoteSubject.next(message);
467
+ }
360
468
  sendMessageRequest(preview) {
361
469
  var _a;
362
470
  return __awaiter(this, void 0, void 0, function* () {
363
471
  const channel = this.activeChannelSubject.getValue();
364
- this.activeChannelMessagesSubject.next([...channel.state.messages]);
472
+ const isThreadReply = !!preview.parent_id;
473
+ isThreadReply
474
+ ? this.activeThreadMessagesSubject.next([
475
+ ...channel.state.threads[preview.parent_id],
476
+ ])
477
+ : this.activeChannelMessagesSubject.next([...channel.state.messages]);
365
478
  try {
366
- yield channel.sendMessage({
479
+ const response = yield channel.sendMessage({
367
480
  text: preview.text,
368
481
  attachments: preview.attachments,
369
482
  mentioned_users: (_a = preview.mentioned_users) === null || _a === void 0 ? void 0 : _a.map((u) => u.id),
370
483
  id: preview.id,
484
+ parent_id: preview.parent_id,
485
+ quoted_message_id: preview.quoted_message_id,
371
486
  });
487
+ if (response === null || response === void 0 ? void 0 : response.message) {
488
+ channel.state.addMessageSorted(Object.assign(Object.assign({}, response.message), { status: 'received' }), true);
489
+ isThreadReply
490
+ ? this.activeThreadMessagesSubject.next([
491
+ ...channel.state.threads[preview.parent_id],
492
+ ])
493
+ : this.activeChannelMessagesSubject.next([...channel.state.messages]);
494
+ }
372
495
  }
373
496
  catch (error) {
374
497
  const stringError = JSON.stringify(error);
@@ -376,7 +499,11 @@ class ChannelService {
376
499
  ? JSON.parse(stringError)
377
500
  : {};
378
501
  channel.state.addMessageSorted(Object.assign(Object.assign({}, preview), { errorStatusCode: parsedError.status || undefined, status: 'failed' }), true);
379
- this.activeChannelMessagesSubject.next([...channel.state.messages]);
502
+ isThreadReply
503
+ ? this.activeThreadMessagesSubject.next([
504
+ ...channel.state.threads[preview.parent_id],
505
+ ])
506
+ : this.activeChannelMessagesSubject.next([...channel.state.messages]);
380
507
  }
381
508
  });
382
509
  }
@@ -454,9 +581,15 @@ class ChannelService {
454
581
  }
455
582
  }
456
583
  watchForActiveChannelEvents(channel) {
457
- this.activeChannelSubscriptions.push(channel.on('message.new', () => {
584
+ this.activeChannelSubscriptions.push(channel.on('message.new', (event) => {
458
585
  this.ngZone.run(() => {
459
- this.activeChannelMessagesSubject.next([...channel.state.messages]);
586
+ event.message && event.message.parent_id
587
+ ? this.activeThreadMessagesSubject.next([
588
+ ...channel.state.threads[event.message.parent_id],
589
+ ])
590
+ : this.activeChannelMessagesSubject.next([
591
+ ...channel.state.messages,
592
+ ]);
460
593
  this.activeChannel$.pipe(first()).subscribe((c) => {
461
594
  if (this.canSendReadEvents) {
462
595
  void (c === null || c === void 0 ? void 0 : c.markRead());
@@ -485,19 +618,27 @@ class ChannelService {
485
618
  }
486
619
  messageUpdated(event) {
487
620
  this.ngZone.run(() => {
488
- const messages = this.activeChannelMessagesSubject.getValue();
621
+ const isThreadReply = event.message && event.message.parent_id;
622
+ const messages = isThreadReply
623
+ ? this.activeThreadMessagesSubject.getValue()
624
+ : this.activeChannelMessagesSubject.getValue();
489
625
  const messageIndex = messages.findIndex((m) => { var _a; return m.id === ((_a = event.message) === null || _a === void 0 ? void 0 : _a.id); });
490
626
  if (messageIndex !== -1 && event.message) {
491
627
  messages[messageIndex] = event.message;
492
- this.activeChannelMessagesSubject.next([...messages]);
628
+ isThreadReply
629
+ ? this.activeThreadMessagesSubject.next([...messages])
630
+ : this.activeChannelMessagesSubject.next([...messages]);
493
631
  }
494
632
  });
495
633
  }
496
634
  messageReactionEventReceived(e) {
497
635
  this.ngZone.run(() => {
498
636
  var _a, _b, _c, _d;
637
+ const isThreadMessage = e.message && e.message.parent_id;
499
638
  let messages;
500
- this.activeChannelMessages$
639
+ (isThreadMessage
640
+ ? this.activeThreadMessages$
641
+ : this.activeChannelMessages$)
501
642
  .pipe(first())
502
643
  .subscribe((m) => (messages = m));
503
644
  const message = messages.find((m) => { var _a; return m.id === ((_a = e === null || e === void 0 ? void 0 : e.message) === null || _a === void 0 ? void 0 : _a.id); });
@@ -508,7 +649,9 @@ class ChannelService {
508
649
  message.reaction_scores = Object.assign({}, (_b = e.message) === null || _b === void 0 ? void 0 : _b.reaction_scores);
509
650
  message.latest_reactions = [...(((_c = e.message) === null || _c === void 0 ? void 0 : _c.latest_reactions) || [])];
510
651
  message.own_reactions = [...(((_d = e.message) === null || _d === void 0 ? void 0 : _d.own_reactions) || [])];
511
- this.activeChannelMessagesSubject.next([...messages]);
652
+ isThreadMessage
653
+ ? this.activeThreadMessagesSubject.next([...messages])
654
+ : this.activeChannelMessagesSubject.next([...messages]);
512
655
  });
513
656
  }
514
657
  formatMessage(message) {
@@ -556,7 +699,7 @@ class ChannelService {
556
699
  case 'message.new': {
557
700
  this.ngZone.run(() => {
558
701
  if (this.customNewMessageHandler) {
559
- this.customNewMessageHandler(event, channel, this.channelListSetter, this.messageListSetter);
702
+ this.customNewMessageHandler(event, channel, this.channelListSetter, this.messageListSetter, this.threadListSetter, this.parentMessageSetter);
560
703
  }
561
704
  else {
562
705
  this.handleNewMessage(event, channel);
@@ -567,7 +710,7 @@ class ChannelService {
567
710
  case 'channel.hidden': {
568
711
  this.ngZone.run(() => {
569
712
  if (this.customChannelHiddenHandler) {
570
- this.customChannelHiddenHandler(event, channel, this.channelListSetter, this.messageListSetter);
713
+ this.customChannelHiddenHandler(event, channel, this.channelListSetter, this.messageListSetter, this.threadListSetter, this.parentMessageSetter);
571
714
  }
572
715
  else {
573
716
  this.handleChannelHidden(event);
@@ -578,7 +721,7 @@ class ChannelService {
578
721
  case 'channel.deleted': {
579
722
  this.ngZone.run(() => {
580
723
  if (this.customChannelDeletedHandler) {
581
- this.customChannelDeletedHandler(event, channel, this.channelListSetter, this.messageListSetter);
724
+ this.customChannelDeletedHandler(event, channel, this.channelListSetter, this.messageListSetter, this.threadListSetter, this.parentMessageSetter);
582
725
  }
583
726
  else {
584
727
  this.handleChannelDeleted(event);
@@ -589,7 +732,7 @@ class ChannelService {
589
732
  case 'channel.visible': {
590
733
  this.ngZone.run(() => {
591
734
  if (this.customChannelVisibleHandler) {
592
- this.customChannelVisibleHandler(event, channel, this.channelListSetter, this.messageListSetter);
735
+ this.customChannelVisibleHandler(event, channel, this.channelListSetter, this.messageListSetter, this.threadListSetter, this.parentMessageSetter);
593
736
  }
594
737
  else {
595
738
  this.handleChannelVisible(event, channel);
@@ -600,7 +743,7 @@ class ChannelService {
600
743
  case 'channel.updated': {
601
744
  this.ngZone.run(() => {
602
745
  if (this.customChannelUpdatedHandler) {
603
- this.customChannelUpdatedHandler(event, channel, this.channelListSetter, this.messageListSetter);
746
+ this.customChannelUpdatedHandler(event, channel, this.channelListSetter, this.messageListSetter, this.threadListSetter, this.parentMessageSetter);
604
747
  }
605
748
  else {
606
749
  this.handleChannelUpdate(event);
@@ -611,7 +754,7 @@ class ChannelService {
611
754
  case 'channel.truncated': {
612
755
  this.ngZone.run(() => {
613
756
  if (this.customChannelTruncatedHandler) {
614
- this.customChannelTruncatedHandler(event, channel, this.channelListSetter, this.messageListSetter);
757
+ this.customChannelTruncatedHandler(event, channel, this.channelListSetter, this.messageListSetter, this.threadListSetter, this.parentMessageSetter);
615
758
  }
616
759
  else {
617
760
  this.handleChannelTruncate(event);
@@ -651,6 +794,7 @@ class ChannelService {
651
794
  }
652
795
  }
653
796
  }
797
+ // truncate active thread as well
654
798
  handleChannelTruncate(event) {
655
799
  var _a, _b;
656
800
  const channelIndex = this.channels.findIndex((c) => c.cid === event.channel.cid);
@@ -662,6 +806,8 @@ class ChannelService {
662
806
  channel.state.messages = [];
663
807
  this.activeChannelSubject.next(channel);
664
808
  this.activeChannelMessagesSubject.next([]);
809
+ this.activeParentMessageIdSubject.next(undefined);
810
+ this.activeThreadMessagesSubject.next([]);
665
811
  }
666
812
  }
667
813
  }
@@ -677,6 +823,20 @@ class ChannelService {
677
823
  const capabilites = (_a = channel.data) === null || _a === void 0 ? void 0 : _a.own_capabilities;
678
824
  return capabilites.indexOf('read-events') !== -1;
679
825
  }
826
+ transformToStreamMessage(message, channel) {
827
+ const isThreadMessage = !!message.parent_id;
828
+ if (this.isStreamMessage(message) &&
829
+ this.isFormatMessageResponse(message)) {
830
+ return message;
831
+ }
832
+ else if (this.isFormatMessageResponse(message)) {
833
+ return Object.assign(Object.assign({}, message), { readBy: isThreadMessage ? [] : getReadBy(message, channel) });
834
+ }
835
+ else {
836
+ const formatMessage = this.formatMessage(message);
837
+ return Object.assign(Object.assign({}, formatMessage), { readBy: isThreadMessage ? [] : getReadBy(formatMessage, channel) });
838
+ }
839
+ }
680
840
  }
681
841
  ChannelService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: ChannelService, deps: [{ token: ChatClientService }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Injectable });
682
842
  ChannelService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: ChannelService, providedIn: 'root' });
@@ -989,6 +1149,7 @@ const en = {
989
1149
  Flag: 'Flag',
990
1150
  'Message Failed': 'Message Failed',
991
1151
  'Message Failed · Unauthorized': 'Message Failed · Unauthorized',
1152
+ 'Message Failed · Click to try again': 'Message Failed · Click to try again',
992
1153
  'Message deleted': 'Message deleted',
993
1154
  'Message has been successfully flagged': 'Message has been successfully flagged',
994
1155
  'Message pinned': 'Message pinned',
@@ -1039,6 +1200,7 @@ const en = {
1039
1200
  test: 'success',
1040
1201
  'Sending links is not allowed in this conversation': 'Sending links is not allowed in this conversation',
1041
1202
  "You can't send messages in this channel": "You can't send messages in this channel",
1203
+ "You can't send thread replies in this channel": "You can't send thread replies in this channel",
1042
1204
  'Unsupported file type: {{type}}': 'Unsupported file type: {{type}}',
1043
1205
  },
1044
1206
  };
@@ -1054,14 +1216,14 @@ class StreamI18nService {
1054
1216
  this.translteService.setTranslation(lang, { streamChat: Object.assign(Object.assign({}, en.streamChat), overrides) }, true);
1055
1217
  }
1056
1218
  }
1057
- StreamI18nService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: StreamI18nService, deps: [{ token: i10.TranslateService }], target: i0.ɵɵFactoryTarget.Injectable });
1219
+ StreamI18nService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: StreamI18nService, deps: [{ token: i1.TranslateService }], target: i0.ɵɵFactoryTarget.Injectable });
1058
1220
  StreamI18nService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: StreamI18nService, providedIn: 'root' });
1059
1221
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: StreamI18nService, decorators: [{
1060
1222
  type: Injectable,
1061
1223
  args: [{
1062
1224
  providedIn: 'root',
1063
1225
  }]
1064
- }], ctorParameters: function () { return [{ type: i10.TranslateService }]; } });
1226
+ }], ctorParameters: function () { return [{ type: i1.TranslateService }]; } });
1065
1227
 
1066
1228
  class AvatarComponent {
1067
1229
  constructor() {
@@ -1075,7 +1237,7 @@ class AvatarComponent {
1075
1237
  }
1076
1238
  }
1077
1239
  AvatarComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: AvatarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1078
- AvatarComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.5", type: AvatarComponent, selector: "stream-avatar", inputs: { name: "name", imageUrl: "imageUrl", size: "size" }, ngImport: i0, template: "<div\n class=\"str-chat__avatar str-chat__avatar--circle\"\n title=\"{{ name }}\"\n [style]=\"{\n flexBasis: size + 'px',\n fontSize: size / 2 + 'px',\n height: size + 'px',\n lineHeight: size + 'px',\n width: size + 'px'\n }\"\n>\n <img\n *ngIf=\"imageUrl && !isError; else fallback\"\n class=\"str-chat__avatar-image str-chat__avatar-image{{\n isLoaded ? ' str-chat__avatar-image--loaded' : ''\n }}\"\n src=\"{{ imageUrl }}\"\n alt=\"{{ initials }}\"\n data-testid=\"avatar-img\"\n (load)=\"isLoaded = true\"\n (error)=\"isError = true\"\n [style]=\"{\n flexBasis: size + 'px',\n height: size + 'px',\n objectFit: 'cover',\n width: size + 'px'\n }\"\n />\n <ng-template #fallback>\n <div data-testid=\"fallback-img\" class=\"str-chat__avatar-fallback\">\n {{ initials }}\n </div>\n </ng-template>\n</div>\n", styles: [""], directives: [{ type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
1240
+ AvatarComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.5", type: AvatarComponent, selector: "stream-avatar", inputs: { name: "name", imageUrl: "imageUrl", size: "size" }, ngImport: i0, template: "<div\n class=\"str-chat__avatar str-chat__avatar--circle\"\n title=\"{{ name }}\"\n [style]=\"{\n flexBasis: size + 'px',\n fontSize: size / 2 + 'px',\n height: size + 'px',\n lineHeight: size + 'px',\n width: size + 'px'\n }\"\n>\n <img\n *ngIf=\"imageUrl && !isError; else fallback\"\n class=\"str-chat__avatar-image str-chat__avatar-image{{\n isLoaded ? ' str-chat__avatar-image--loaded' : ''\n }}\"\n src=\"{{ imageUrl }}\"\n alt=\"{{ initials }}\"\n data-testid=\"avatar-img\"\n (load)=\"isLoaded = true\"\n (error)=\"isError = true\"\n [style]=\"{\n flexBasis: size + 'px',\n height: size + 'px',\n objectFit: 'cover',\n width: size + 'px'\n }\"\n />\n <ng-template #fallback>\n <div data-testid=\"fallback-img\" class=\"str-chat__avatar-fallback\">\n {{ initials }}\n </div>\n </ng-template>\n</div>\n", styles: [""], directives: [{ type: i6.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
1079
1241
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: AvatarComponent, decorators: [{
1080
1242
  type: Component,
1081
1243
  args: [{
@@ -1095,7 +1257,7 @@ class IconComponent {
1095
1257
  constructor() { }
1096
1258
  }
1097
1259
  IconComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: IconComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1098
- IconComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.5", type: IconComponent, selector: "stream-icon", inputs: { icon: "icon", size: "size" }, ngImport: i0, template: "<svg\n data-testid=\"action-icon\"\n *ngIf=\"icon === 'action-icon'\"\n height=\"4\"\n viewBox=\"0 0 11 4\"\n width=\"11\"\n xmlns=\"http://www.w3.org/2000/svg\"\n>\n <path\n d=\"M1.5 3a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm4 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm4 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3z\"\n fillRule=\"nonzero\"\n />\n</svg>\n<svg\n *ngIf=\"icon === 'delivered-icon'\"\n height=\"16\"\n width=\"16\"\n xmlns=\"http://www.w3.org/2000/svg\"\n data-testid=\"delivered-icon\"\n>\n <path\n d=\"M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0zm3.72 6.633a.955.955 0 1 0-1.352-1.352L6.986 8.663 5.633 7.31A.956.956 0 1 0 4.28 8.663l2.029 2.028a.956.956 0 0 0 1.353 0l4.058-4.058z\"\n fill=\"#006CFF\"\n fillRule=\"evenodd\"\n />\n</svg>\n<svg\n *ngIf=\"icon === 'reaction-icon'\"\n height=\"12\"\n viewBox=\"0 0 12 12\"\n width=\"12\"\n xmlns=\"http://www.w3.org/2000/svg\"\n data-testid=\"reaction-icon\"\n>\n <g clipRule=\"evenodd\" fillRule=\"evenodd\">\n <path\n d=\"M6 1.2C3.3 1.2 1.2 3.3 1.2 6c0 2.7 2.1 4.8 4.8 4.8 2.7 0 4.8-2.1 4.8-4.8 0-2.7-2.1-4.8-4.8-4.8zM0 6c0-3.3 2.7-6 6-6s6 2.7 6 6-2.7 6-6 6-6-2.7-6-6z\"\n ></path>\n <path\n d=\"M5.4 4.5c0 .5-.4.9-.9.9s-.9-.4-.9-.9.4-.9.9-.9.9.4.9.9zM8.4 4.5c0 .5-.4.9-.9.9s-.9-.4-.9-.9.4-.9.9-.9.9.4.9.9zM3.3 6.7c.3-.2.6-.1.8.1.3.4.8.9 1.5 1 .6.2 1.4.1 2.4-1 .2-.2.6-.3.8 0 .2.2.3.6 0 .8-1.1 1.3-2.4 1.7-3.5 1.5-1-.2-1.8-.9-2.2-1.5-.2-.3-.1-.7.2-.9z\"\n ></path>\n </g>\n</svg>\n<svg\n data-testid=\"connection-error\"\n *ngIf=\"icon === 'connection-error'\"\n width=\"78px\"\n height=\"78px\"\n viewBox=\"0 0 78 78\"\n version=\"1.1\"\n xmlns=\"http://www.w3.org/2000/svg\"\n xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n>\n <!-- Generator: Sketch 52.6 (67491) - http://www.bohemiancoding.com/sketch -->\n <title>Combined Shape</title>\n <desc>Created with Sketch.</desc>\n <g\n id=\"Interactions\"\n stroke=\"none\"\n stroke-width=\"1\"\n fill=\"none\"\n fill-rule=\"evenodd\"\n >\n <g\n id=\"Connection-Error-_-Connectivity\"\n transform=\"translate(-270.000000, -30.000000)\"\n fill=\"#CF1F25\"\n >\n <g\n id=\"109-network-connection\"\n transform=\"translate(270.000000, 30.000000)\"\n >\n <path\n d=\"M66.4609744,11.414231 C81.6225232,26.5757798 81.6225232,51.157545 66.4609744,66.3188467 C51.2994256,81.4803954 26.7176604,81.4803954 11.5563587,66.3188467 C-3.60519004,51.1572979 -3.60519004,26.5755327 11.5563587,11.414231 C26.7179075,-3.74731776 51.2996727,-3.74731776 66.4609744,11.414231 Z M54.7853215,45.8823776 L54.7853215,40.5882574 C54.7853215,39.613638 53.9952341,38.8235506 53.0206147,38.8235506 L44.9576695,38.8235506 L41.428256,42.3529641 L51.255555,42.3529641 L51.255555,45.8823776 L54.7853215,45.8823776 Z M40.6659027,43.1153174 L37.8988425,45.8823776 L40.6659027,45.8823776 L40.6659027,43.1153174 Z M51.1764962,56.4702653 L58.2353232,56.4702653 C59.2099355,56.4702653 60.00003,55.6801708 60.00003,54.7055585 L60.00003,51.176145 C60.00003,50.2015327 59.2099355,49.4114382 58.2353232,49.4114382 L51.1764962,49.4114382 C50.2018839,49.4114382 49.4117894,50.2015327 49.4117894,51.176145 L49.4117894,54.7055585 C49.4117894,55.6801708 50.2018839,56.4702653 51.1764962,56.4702653 Z M35.2941353,56.4702653 L42.3529624,56.4702653 C43.3275746,56.4702653 44.1176691,55.6801708 44.1176691,54.7055585 L44.1176691,51.176145 C44.1176691,50.2015327 43.3275746,49.4114382 42.3529624,49.4114382 L35.2941353,49.4114382 C34.319523,49.4114382 33.5294285,50.2015327 33.5294285,51.176145 L33.5294285,54.7055585 C33.5294285,55.6801708 34.319523,56.4702653 35.2941353,56.4702653 Z M56.6964989,19.0874231 C56.007381,18.3985134 54.8903216,18.3985134 54.2012036,19.087423 L45.882376,27.4062507 L45.882376,19.4117761 C45.882376,18.4371568 45.0922885,17.6470693 44.1176692,17.6470693 L33.5294286,17.6470693 C32.5548092,17.6470694 31.7647218,18.4371568 31.7647218,19.4117761 L31.7647218,30.0000167 C31.7647219,30.9746363 32.5548092,31.7647237 33.5294285,31.7647237 L41.5239031,31.7647237 L34.4650761,38.8235508 L24.7058947,38.8235508 C23.7312753,38.8235508 22.9411879,39.6136382 22.9411879,40.5882575 L22.9411879,45.8823778 L26.4706014,45.8823778 L26.4706014,42.3529643 L30.9356624,42.3529643 L23.8768354,49.4117914 L19.4117743,49.4117914 C18.4371549,49.4117914 17.6470675,50.2018788 17.6470675,51.1764981 L17.6470675,54.7059117 C17.6504049,54.9674302 17.7129076,55.2248042 17.8298886,55.4587302 L16.4456526,56.8429662 C15.7446193,57.5200453 15.7252005,58.6372282 16.4022825,59.3382615 C17.0793616,60.0392948 18.1965445,60.0587136 18.8975778,59.3816316 C18.9122847,59.3674273 18.9267436,59.3529684 18.940948,59.3382615 L56.6964963,21.5830662 C57.3856425,20.8939094 57.3856425,19.7765747 56.6964963,19.0874179 Z\"\n id=\"Combined-Shape\"\n ></path>\n </g>\n </g>\n </g>\n</svg>\n<svg\n *ngIf=\"icon === 'send'\"\n data-testid=\"send\"\n height=\"17\"\n viewBox=\"0 0 18 17\"\n width=\"18\"\n xmlns=\"http://www.w3.org/2000/svg\"\n>\n <title translate>streamChat.Send</title>\n <path\n d=\"M0 17.015l17.333-8.508L0 0v6.617l12.417 1.89L0 10.397z\"\n fill=\"#006cff\"\n fillRule=\"evenodd\"\n />\n</svg>\n<svg\n *ngIf=\"icon === 'file-upload'\"\n data-testid=\"file-upload\"\n height=\"14\"\n width=\"14\"\n xmlns=\"http://www.w3.org/2000/svg\"\n>\n <title translate>streamChat.Attach files</title>\n <path\n d=\"M1.667.333h10.666c.737 0 1.334.597 1.334 1.334v10.666c0 .737-.597 1.334-1.334 1.334H1.667a1.333 1.333 0 0 1-1.334-1.334V1.667C.333.93.93.333 1.667.333zm2 1.334a1.667 1.667 0 1 0 0 3.333 1.667 1.667 0 0 0 0-3.333zm-2 9.333v1.333h10.666v-4l-2-2-4 4-2-2L1.667 11z\"\n fillRule=\"nonzero\"\n />\n</svg>\n<svg\n data-testid=\"retry\"\n *ngIf=\"icon === 'retry'\"\n width=\"22\"\n height=\"20\"\n viewBox=\"0 0 22 20\"\n xmlns=\"http://www.w3.org/2000/svg\"\n>\n <path\n d=\"M20 5.535V2a1 1 0 0 1 2 0v6a1 1 0 0 1-1 1h-6a1 1 0 0 1 0-2h3.638l-2.975-2.653a8 8 0 1 0 1.884 8.32 1 1 0 1 1 1.886.666A10 10 0 1 1 5.175 1.245c3.901-2.15 8.754-1.462 11.88 1.667L20 5.535z\"\n fill=\"#FFF\"\n fill-rule=\"nonzero\"\n />\n</svg>\n<svg\n *ngIf=\"icon === 'close'\"\n data-testid=\"close\"\n width=\"28\"\n height=\"28\"\n viewBox=\"0 0 28 28\"\n xmlns=\"http://www.w3.org/2000/svg\"\n xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n>\n <defs>\n <path\n d=\"M465 5c5.53 0 10 4.47 10 10s-4.47 10-10 10-10-4.47-10-10 4.47-10 10-10zm3.59 5L465 13.59 461.41 10 460 11.41l3.59 3.59-3.59 3.59 1.41 1.41 3.59-3.59 3.59 3.59 1.41-1.41-3.59-3.59 3.59-3.59-1.41-1.41z\"\n id=\"b\"\n />\n <filter\n x=\"-30%\"\n y=\"-30%\"\n width=\"160%\"\n height=\"160%\"\n filterUnits=\"objectBoundingBox\"\n id=\"a\"\n >\n <feOffset in=\"SourceAlpha\" result=\"shadowOffsetOuter1\" />\n <feGaussianBlur\n stdDeviation=\"2\"\n in=\"shadowOffsetOuter1\"\n result=\"shadowBlurOuter1\"\n />\n <feColorMatrix\n values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0\"\n in=\"shadowBlurOuter1\"\n />\n </filter>\n </defs>\n <g transform=\"translate(-451 -1)\" fill-rule=\"nonzero\" fill=\"none\">\n <use fill=\"#000\" filter=\"url(#a)\" xlink:href=\"#b\" />\n <use fill=\"#FFF\" fill-rule=\"evenodd\" xlink:href=\"#b\" />\n </g>\n</svg>\n<svg\n *ngIf=\"icon === 'file'\"\n data-testid=\"file\"\n className=\"rfu-file-icon--small fa-file-fallback\"\n [attr.height]=\"size || 20\"\n [attr.width]=\"size || 20\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 384 512\"\n>\n <path\n d=\"M369.9 97.9L286 14C277 5 264.8-.1 252.1-.1H48C21.5 0 0 21.5 0 48v416c0 26.5 21.5 48 48 48h288c26.5 0 48-21.5 48-48V131.9c0-12.7-5.1-25-14.1-34zM332.1 128H256V51.9l76.1 76.1zM48 464V48h160v104c0 13.3 10.7 24 24 24h104v288H48z\"\n fill=\"#414D54\"\n />\n</svg>\n", directives: [{ type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i10.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }] });
1260
+ IconComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.5", type: IconComponent, selector: "stream-icon", inputs: { icon: "icon", size: "size" }, ngImport: i0, template: "<svg\n data-testid=\"action-icon\"\n *ngIf=\"icon === 'action-icon'\"\n height=\"4\"\n viewBox=\"0 0 11 4\"\n width=\"11\"\n xmlns=\"http://www.w3.org/2000/svg\"\n>\n <path\n d=\"M1.5 3a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm4 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm4 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3z\"\n fillRule=\"nonzero\"\n />\n</svg>\n<svg\n *ngIf=\"icon === 'delivered-icon'\"\n height=\"16\"\n width=\"16\"\n xmlns=\"http://www.w3.org/2000/svg\"\n data-testid=\"delivered-icon\"\n>\n <path\n d=\"M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0zm3.72 6.633a.955.955 0 1 0-1.352-1.352L6.986 8.663 5.633 7.31A.956.956 0 1 0 4.28 8.663l2.029 2.028a.956.956 0 0 0 1.353 0l4.058-4.058z\"\n fill=\"#006CFF\"\n fillRule=\"evenodd\"\n />\n</svg>\n<svg\n *ngIf=\"icon === 'reaction-icon'\"\n height=\"12\"\n viewBox=\"0 0 12 12\"\n width=\"12\"\n xmlns=\"http://www.w3.org/2000/svg\"\n data-testid=\"reaction-icon\"\n>\n <g clipRule=\"evenodd\" fillRule=\"evenodd\">\n <path\n d=\"M6 1.2C3.3 1.2 1.2 3.3 1.2 6c0 2.7 2.1 4.8 4.8 4.8 2.7 0 4.8-2.1 4.8-4.8 0-2.7-2.1-4.8-4.8-4.8zM0 6c0-3.3 2.7-6 6-6s6 2.7 6 6-2.7 6-6 6-6-2.7-6-6z\"\n ></path>\n <path\n d=\"M5.4 4.5c0 .5-.4.9-.9.9s-.9-.4-.9-.9.4-.9.9-.9.9.4.9.9zM8.4 4.5c0 .5-.4.9-.9.9s-.9-.4-.9-.9.4-.9.9-.9.9.4.9.9zM3.3 6.7c.3-.2.6-.1.8.1.3.4.8.9 1.5 1 .6.2 1.4.1 2.4-1 .2-.2.6-.3.8 0 .2.2.3.6 0 .8-1.1 1.3-2.4 1.7-3.5 1.5-1-.2-1.8-.9-2.2-1.5-.2-.3-.1-.7.2-.9z\"\n ></path>\n </g>\n</svg>\n<svg\n data-testid=\"connection-error\"\n *ngIf=\"icon === 'connection-error'\"\n width=\"78px\"\n height=\"78px\"\n viewBox=\"0 0 78 78\"\n version=\"1.1\"\n xmlns=\"http://www.w3.org/2000/svg\"\n xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n>\n <!-- Generator: Sketch 52.6 (67491) - http://www.bohemiancoding.com/sketch -->\n <title>Combined Shape</title>\n <desc>Created with Sketch.</desc>\n <g\n id=\"Interactions\"\n stroke=\"none\"\n stroke-width=\"1\"\n fill=\"none\"\n fill-rule=\"evenodd\"\n >\n <g\n id=\"Connection-Error-_-Connectivity\"\n transform=\"translate(-270.000000, -30.000000)\"\n fill=\"#CF1F25\"\n >\n <g\n id=\"109-network-connection\"\n transform=\"translate(270.000000, 30.000000)\"\n >\n <path\n d=\"M66.4609744,11.414231 C81.6225232,26.5757798 81.6225232,51.157545 66.4609744,66.3188467 C51.2994256,81.4803954 26.7176604,81.4803954 11.5563587,66.3188467 C-3.60519004,51.1572979 -3.60519004,26.5755327 11.5563587,11.414231 C26.7179075,-3.74731776 51.2996727,-3.74731776 66.4609744,11.414231 Z M54.7853215,45.8823776 L54.7853215,40.5882574 C54.7853215,39.613638 53.9952341,38.8235506 53.0206147,38.8235506 L44.9576695,38.8235506 L41.428256,42.3529641 L51.255555,42.3529641 L51.255555,45.8823776 L54.7853215,45.8823776 Z M40.6659027,43.1153174 L37.8988425,45.8823776 L40.6659027,45.8823776 L40.6659027,43.1153174 Z M51.1764962,56.4702653 L58.2353232,56.4702653 C59.2099355,56.4702653 60.00003,55.6801708 60.00003,54.7055585 L60.00003,51.176145 C60.00003,50.2015327 59.2099355,49.4114382 58.2353232,49.4114382 L51.1764962,49.4114382 C50.2018839,49.4114382 49.4117894,50.2015327 49.4117894,51.176145 L49.4117894,54.7055585 C49.4117894,55.6801708 50.2018839,56.4702653 51.1764962,56.4702653 Z M35.2941353,56.4702653 L42.3529624,56.4702653 C43.3275746,56.4702653 44.1176691,55.6801708 44.1176691,54.7055585 L44.1176691,51.176145 C44.1176691,50.2015327 43.3275746,49.4114382 42.3529624,49.4114382 L35.2941353,49.4114382 C34.319523,49.4114382 33.5294285,50.2015327 33.5294285,51.176145 L33.5294285,54.7055585 C33.5294285,55.6801708 34.319523,56.4702653 35.2941353,56.4702653 Z M56.6964989,19.0874231 C56.007381,18.3985134 54.8903216,18.3985134 54.2012036,19.087423 L45.882376,27.4062507 L45.882376,19.4117761 C45.882376,18.4371568 45.0922885,17.6470693 44.1176692,17.6470693 L33.5294286,17.6470693 C32.5548092,17.6470694 31.7647218,18.4371568 31.7647218,19.4117761 L31.7647218,30.0000167 C31.7647219,30.9746363 32.5548092,31.7647237 33.5294285,31.7647237 L41.5239031,31.7647237 L34.4650761,38.8235508 L24.7058947,38.8235508 C23.7312753,38.8235508 22.9411879,39.6136382 22.9411879,40.5882575 L22.9411879,45.8823778 L26.4706014,45.8823778 L26.4706014,42.3529643 L30.9356624,42.3529643 L23.8768354,49.4117914 L19.4117743,49.4117914 C18.4371549,49.4117914 17.6470675,50.2018788 17.6470675,51.1764981 L17.6470675,54.7059117 C17.6504049,54.9674302 17.7129076,55.2248042 17.8298886,55.4587302 L16.4456526,56.8429662 C15.7446193,57.5200453 15.7252005,58.6372282 16.4022825,59.3382615 C17.0793616,60.0392948 18.1965445,60.0587136 18.8975778,59.3816316 C18.9122847,59.3674273 18.9267436,59.3529684 18.940948,59.3382615 L56.6964963,21.5830662 C57.3856425,20.8939094 57.3856425,19.7765747 56.6964963,19.0874179 Z\"\n id=\"Combined-Shape\"\n ></path>\n </g>\n </g>\n </g>\n</svg>\n<svg\n *ngIf=\"icon === 'send'\"\n data-testid=\"send\"\n height=\"17\"\n viewBox=\"0 0 18 17\"\n width=\"18\"\n xmlns=\"http://www.w3.org/2000/svg\"\n>\n <title translate>streamChat.Send</title>\n <path\n d=\"M0 17.015l17.333-8.508L0 0v6.617l12.417 1.89L0 10.397z\"\n fill=\"#006cff\"\n fillRule=\"evenodd\"\n />\n</svg>\n<svg\n *ngIf=\"icon === 'file-upload'\"\n data-testid=\"file-upload\"\n height=\"14\"\n width=\"14\"\n xmlns=\"http://www.w3.org/2000/svg\"\n>\n <title translate>streamChat.Attach files</title>\n <path\n d=\"M1.667.333h10.666c.737 0 1.334.597 1.334 1.334v10.666c0 .737-.597 1.334-1.334 1.334H1.667a1.333 1.333 0 0 1-1.334-1.334V1.667C.333.93.93.333 1.667.333zm2 1.334a1.667 1.667 0 1 0 0 3.333 1.667 1.667 0 0 0 0-3.333zm-2 9.333v1.333h10.666v-4l-2-2-4 4-2-2L1.667 11z\"\n fillRule=\"nonzero\"\n />\n</svg>\n<svg\n data-testid=\"retry\"\n *ngIf=\"icon === 'retry'\"\n width=\"22\"\n height=\"20\"\n viewBox=\"0 0 22 20\"\n xmlns=\"http://www.w3.org/2000/svg\"\n>\n <path\n d=\"M20 5.535V2a1 1 0 0 1 2 0v6a1 1 0 0 1-1 1h-6a1 1 0 0 1 0-2h3.638l-2.975-2.653a8 8 0 1 0 1.884 8.32 1 1 0 1 1 1.886.666A10 10 0 1 1 5.175 1.245c3.901-2.15 8.754-1.462 11.88 1.667L20 5.535z\"\n fill=\"#FFF\"\n fill-rule=\"nonzero\"\n />\n</svg>\n<svg\n *ngIf=\"icon === 'close'\"\n data-testid=\"close\"\n width=\"28\"\n height=\"28\"\n viewBox=\"0 0 28 28\"\n xmlns=\"http://www.w3.org/2000/svg\"\n xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n>\n <defs>\n <path\n d=\"M465 5c5.53 0 10 4.47 10 10s-4.47 10-10 10-10-4.47-10-10 4.47-10 10-10zm3.59 5L465 13.59 461.41 10 460 11.41l3.59 3.59-3.59 3.59 1.41 1.41 3.59-3.59 3.59 3.59 1.41-1.41-3.59-3.59 3.59-3.59-1.41-1.41z\"\n id=\"b\"\n />\n <filter\n x=\"-30%\"\n y=\"-30%\"\n width=\"160%\"\n height=\"160%\"\n filterUnits=\"objectBoundingBox\"\n id=\"a\"\n >\n <feOffset in=\"SourceAlpha\" result=\"shadowOffsetOuter1\" />\n <feGaussianBlur\n stdDeviation=\"2\"\n in=\"shadowOffsetOuter1\"\n result=\"shadowBlurOuter1\"\n />\n <feColorMatrix\n values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0\"\n in=\"shadowBlurOuter1\"\n />\n </filter>\n </defs>\n <g transform=\"translate(-451 -1)\" fill-rule=\"nonzero\" fill=\"none\">\n <use fill=\"#000\" filter=\"url(#a)\" xlink:href=\"#b\" />\n <use fill=\"#FFF\" fill-rule=\"evenodd\" xlink:href=\"#b\" />\n </g>\n</svg>\n<svg\n *ngIf=\"icon === 'file'\"\n data-testid=\"file\"\n className=\"rfu-file-icon--small fa-file-fallback\"\n [attr.height]=\"size || 20\"\n [attr.width]=\"size || 20\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 384 512\"\n>\n <path\n d=\"M369.9 97.9L286 14C277 5 264.8-.1 252.1-.1H48C21.5 0 0 21.5 0 48v416c0 26.5 21.5 48 48 48h288c26.5 0 48-21.5 48-48V131.9c0-12.7-5.1-25-14.1-34zM332.1 128H256V51.9l76.1 76.1zM48 464V48h160v104c0 13.3 10.7 24 24 24h104v288H48z\"\n fill=\"#414D54\"\n />\n</svg>\n<svg\n *ngIf=\"icon === 'reply'\"\n data-testid=\"reply\"\n height=\"15\"\n width=\"18\"\n xmlns=\"http://www.w3.org/2000/svg\"\n>\n <path\n d=\"M.56 10.946H.06l-.002-.498L.025.92a.5.5 0 1 1 1-.004l.032 9.029H9.06v-4l9 4.5-9 4.5v-4H.56z\"\n fillRule=\"nonzero\"\n />\n</svg>\n<svg\n height=\"10\"\n width=\"10\"\n xmlns=\"http://www.w3.org/2000/svg\"\n data-testid=\"close-no-outline\"\n *ngIf=\"icon === 'close-no-outline'\"\n>\n <path\n d=\"M9.916 1.027L8.973.084 5 4.058 1.027.084l-.943.943L4.058 5 .084 8.973l.943.943L5 5.942l3.973 3.974.943-.943L5.942 5z\"\n fillRule=\"evenodd\"\n />\n</svg>\n<svg\n height=\"10\"\n width=\"14\"\n xmlns=\"http://www.w3.org/2000/svg\"\n data-testid=\"reply-in-thread\"\n *ngIf=\"icon === 'reply-in-thread'\"\n>\n <path\n d=\"M8.516 3c4.78 0 4.972 6.5 4.972 6.5-1.6-2.906-2.847-3.184-4.972-3.184v2.872L3.772 4.994 8.516.5V3zM.484 5l4.5-4.237v1.78L2.416 5l2.568 2.125v1.828L.484 5z\"\n fillRule=\"evenodd\"\n />\n</svg>\n", directives: [{ type: i6.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i1.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }] });
1099
1261
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: IconComponent, decorators: [{
1100
1262
  type: Component,
1101
1263
  args: [{
@@ -1116,7 +1278,7 @@ class LoadingIndicatorComponent {
1116
1278
  }
1117
1279
  }
1118
1280
  LoadingIndicatorComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: LoadingIndicatorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1119
- LoadingIndicatorComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.5", type: LoadingIndicatorComponent, selector: "stream-loading-indicator", inputs: { size: "size", color: "color" }, ngImport: i0, template: "<div class=\"str-chat__loading-indicator\">\n <svg\n [attr.height]=\"size\"\n viewBox=\"0 0 30 30\"\n [attr.width]=\"size\"\n data-testid=\"loading-indicator\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <defs>\n <linearGradient id=\"a\" x1=\"50%\" x2=\"50%\" y1=\"0%\" y2=\"100%\">\n <stop offset=\"0%\" stop-color=\"#FFF\" stop-opacity=\"0\" />\n <stop\n data-testid=\"stop-color\"\n offset=\"100%\"\n [attr.stop-color]=\"color\"\n stop-opacity=\"1\"\n [ngStyle]=\"{ stopColor: color }\"\n />\n </linearGradient>\n </defs>\n <path\n d=\"M2.518 23.321l1.664-1.11A12.988 12.988 0 0 0 15 28c7.18 0 13-5.82 13-13S22.18 2 15 2V0c8.284 0 15 6.716 15 15 0 8.284-6.716 15-15 15-5.206 0-9.792-2.652-12.482-6.679z\"\n fill=\"url(#a)\"\n fillRule=\"evenodd\"\n />\n </svg>\n</div>\n", directives: [{ type: i3.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }] });
1281
+ LoadingIndicatorComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.5", type: LoadingIndicatorComponent, selector: "stream-loading-indicator", inputs: { size: "size", color: "color" }, ngImport: i0, template: "<div class=\"str-chat__loading-indicator\">\n <svg\n [attr.height]=\"size\"\n viewBox=\"0 0 30 30\"\n [attr.width]=\"size\"\n data-testid=\"loading-indicator\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <defs>\n <linearGradient id=\"a\" x1=\"50%\" x2=\"50%\" y1=\"0%\" y2=\"100%\">\n <stop offset=\"0%\" stop-color=\"#FFF\" stop-opacity=\"0\" />\n <stop\n data-testid=\"stop-color\"\n offset=\"100%\"\n [attr.stop-color]=\"color\"\n stop-opacity=\"1\"\n [ngStyle]=\"{ stopColor: color }\"\n />\n </linearGradient>\n </defs>\n <path\n d=\"M2.518 23.321l1.664-1.11A12.988 12.988 0 0 0 15 28c7.18 0 13-5.82 13-13S22.18 2 15 2V0c8.284 0 15 6.716 15 15 0 8.284-6.716 15-15 15-5.206 0-9.792-2.652-12.482-6.679z\"\n fill=\"url(#a)\"\n fillRule=\"evenodd\"\n />\n </svg>\n</div>\n", directives: [{ type: i6.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }] });
1120
1282
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: LoadingIndicatorComponent, decorators: [{
1121
1283
  type: Component,
1122
1284
  args: [{
@@ -1158,6 +1320,8 @@ class TextareaDirective {
1158
1320
  this.componentRef.instance.areMentionsEnabled = this.areMentionsEnabled;
1159
1321
  this.componentRef.instance.mentionAutocompleteItemTemplate =
1160
1322
  this.mentionAutocompleteItemTemplate;
1323
+ this.componentRef.instance.commandAutocompleteItemTemplate =
1324
+ this.commandAutocompleteItemTemplate;
1161
1325
  this.componentRef.instance.mentionScope = this.mentionScope;
1162
1326
  this.componentRef.instance.value = this.value;
1163
1327
  }
@@ -1169,6 +1333,10 @@ class TextareaDirective {
1169
1333
  this.componentRef.instance.mentionAutocompleteItemTemplate =
1170
1334
  this.mentionAutocompleteItemTemplate;
1171
1335
  }
1336
+ if (changes.commandAutocompleteItemTemplate) {
1337
+ this.componentRef.instance.commandAutocompleteItemTemplate =
1338
+ this.commandAutocompleteItemTemplate;
1339
+ }
1172
1340
  if (changes.mentionScope) {
1173
1341
  this.componentRef.instance.mentionScope = this.mentionScope;
1174
1342
  }
@@ -1184,7 +1352,7 @@ class TextareaDirective {
1184
1352
  }
1185
1353
  }
1186
1354
  TextareaDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: TextareaDirective, deps: [{ token: i0.ViewContainerRef }], target: i0.ɵɵFactoryTarget.Directive });
1187
- TextareaDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "12.0.0", version: "12.2.5", type: TextareaDirective, selector: "[streamTextarea]", inputs: { componentRef: "componentRef", areMentionsEnabled: "areMentionsEnabled", mentionAutocompleteItemTemplate: "mentionAutocompleteItemTemplate", mentionScope: "mentionScope", value: "value" }, outputs: { valueChange: "valueChange", send: "send", userMentions: "userMentions" }, usesOnChanges: true, ngImport: i0 });
1355
+ TextareaDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "12.0.0", version: "12.2.5", type: TextareaDirective, selector: "[streamTextarea]", inputs: { componentRef: "componentRef", areMentionsEnabled: "areMentionsEnabled", mentionAutocompleteItemTemplate: "mentionAutocompleteItemTemplate", mentionScope: "mentionScope", commandAutocompleteItemTemplate: "commandAutocompleteItemTemplate", value: "value" }, outputs: { valueChange: "valueChange", send: "send", userMentions: "userMentions" }, usesOnChanges: true, ngImport: i0 });
1188
1356
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: TextareaDirective, decorators: [{
1189
1357
  type: Directive,
1190
1358
  args: [{
@@ -1198,6 +1366,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.5", ngImpor
1198
1366
  type: Input
1199
1367
  }], mentionScope: [{
1200
1368
  type: Input
1369
+ }], commandAutocompleteItemTemplate: [{
1370
+ type: Input
1201
1371
  }], value: [{
1202
1372
  type: Input
1203
1373
  }], valueChange: [{
@@ -1225,6 +1395,90 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.5", ngImpor
1225
1395
  }]
1226
1396
  }], ctorParameters: function () { return []; } });
1227
1397
 
1398
+ class ImageLoadService {
1399
+ constructor() {
1400
+ this.imageLoad$ = new Subject();
1401
+ }
1402
+ }
1403
+ ImageLoadService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: ImageLoadService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1404
+ ImageLoadService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: ImageLoadService, providedIn: 'root' });
1405
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: ImageLoadService, decorators: [{
1406
+ type: Injectable,
1407
+ args: [{
1408
+ providedIn: 'root',
1409
+ }]
1410
+ }], ctorParameters: function () { return []; } });
1411
+
1412
+ class AttachmentListComponent {
1413
+ constructor(imageLoadService, channelService) {
1414
+ this.imageLoadService = imageLoadService;
1415
+ this.channelService = channelService;
1416
+ this.attachments = [];
1417
+ this.orderedAttachments = [];
1418
+ }
1419
+ ngOnChanges() {
1420
+ this.orderedAttachments = [
1421
+ ...this.attachments.filter((a) => this.isImage(a)),
1422
+ ...this.attachments.filter((a) => this.isFile(a)),
1423
+ ...this.attachments.filter((a) => this.isCard(a)),
1424
+ ];
1425
+ }
1426
+ trackById(index) {
1427
+ return index;
1428
+ }
1429
+ isImage(attachment) {
1430
+ return isImageAttachment(attachment);
1431
+ }
1432
+ isFile(attachment) {
1433
+ return attachment.type === 'file';
1434
+ }
1435
+ isCard(attachment) {
1436
+ return (!attachment.type ||
1437
+ (attachment.type === 'image' && !this.isImage(attachment)) ||
1438
+ attachment.type === 'giphy');
1439
+ }
1440
+ imageLoaded() {
1441
+ this.imageLoadService.imageLoad$.next();
1442
+ }
1443
+ hasFileSize(attachment) {
1444
+ return (attachment.file_size && Number.isFinite(Number(attachment.file_size)));
1445
+ }
1446
+ getFileSize(attachment) {
1447
+ return prettybytes(attachment.file_size);
1448
+ }
1449
+ trimUrl(url) {
1450
+ if (url !== undefined && url !== null) {
1451
+ const [trimmedUrl] = url
1452
+ .replace(/^(?:https?:\/\/)?(?:www\.)?/i, '')
1453
+ .split('/');
1454
+ return trimmedUrl;
1455
+ }
1456
+ return null;
1457
+ }
1458
+ sendAction(action) {
1459
+ void this.channelService.sendAction(this.messageId, {
1460
+ [action.name]: action.value,
1461
+ });
1462
+ }
1463
+ trackByActionValue(_, item) {
1464
+ return item.value;
1465
+ }
1466
+ }
1467
+ AttachmentListComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: AttachmentListComponent, deps: [{ token: ImageLoadService }, { token: ChannelService }], target: i0.ɵɵFactoryTarget.Component });
1468
+ AttachmentListComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.5", type: AttachmentListComponent, selector: "stream-attachment-list", inputs: { messageId: "messageId", attachments: "attachments" }, usesOnChanges: true, ngImport: i0, template: "<ng-container *ngFor=\"let attachment of orderedAttachments; trackBy: trackById\">\n <div\n data-testclass=\"attachment-container\"\n class=\"str-chat__message-attachment str-chat__message-attachment--{{\n attachment.type\n }}\"\n [class.str-chat__message-attachment--card]=\"isCard(attachment)\"\n [class.str-chat-angular__message-attachment-file-single]=\"\n isFile(attachment)\n \"\n >\n <img\n *ngIf=\"isImage(attachment)\"\n class=\"str-chat__message-attachment--img\"\n data-testclass=\"image\"\n [src]=\"attachment.img_url || attachment.thumb_url || attachment.image_url\"\n [alt]=\"attachment?.fallback\"\n (load)=\"imageLoaded()\"\n />\n <div\n *ngIf=\"isFile(attachment)\"\n class=\"\n str-chat__message-attachment-file--item\n str-chat-angular__message-attachment-file-single\n \"\n >\n <stream-icon icon=\"file\" [size]=\"30\"></stream-icon>\n <div class=\"str-chat__message-attachment-file--item-text\">\n <a\n data-testclass=\"file-link\"\n download\n href=\"{{ attachment.asset_url }}\"\n target=\"_blank\"\n >\n {{ attachment.title }}\n </a>\n <span data-testclass=\"size\" *ngIf=\"hasFileSize(attachment)\">{{\n getFileSize(attachment)\n }}</span>\n </div>\n </div>\n <div\n *ngIf=\"isCard(attachment)\"\n class=\"str-chat__message-attachment-card str-chat__message-attachment-card--{{\n attachment.type\n }}\"\n >\n <div\n *ngIf=\"attachment.image_url || attachment.thumb_url\"\n class=\"str-chat__message-attachment-card--header\"\n >\n <img\n data-testclass=\"card-img\"\n alt=\"{{ attachment.image_url || attachment.thumb_url }}\"\n src=\"{{ attachment.image_url || attachment.thumb_url }}\"\n />\n </div>\n <div class=\"str-chat__message-attachment-card--content\">\n <div class=\"str-chat__message-attachment-card--flex\">\n <div\n *ngIf=\"attachment.title\"\n data-testclass=\"card-title\"\n class=\"str-chat__message-attachment-card--title\"\n >\n {{ attachment.title }}\n </div>\n <div\n *ngIf=\"attachment.text\"\n class=\"str-chat__message-attachment-card--text\"\n data-testclass=\"card-text\"\n >\n {{ attachment.text }}\n </div>\n <a\n class=\"str-chat__message-attachment-card--url\"\n *ngIf=\"attachment.title_link || attachment.og_scrape_url\"\n data-testclass=\"url-link\"\n noopener\n noreferrer\n href=\"{{ attachment.title_link || attachment.og_scrape_url }}\"\n target=\"_blank\"\n >\n {{ trimUrl(attachment.title_link || attachment.og_scrape_url) }}\n </a>\n </div>\n </div>\n </div>\n <div\n class=\"str-chat__message-attachment-actions\"\n *ngIf=\"attachment.actions && attachment.actions.length > 0\"\n >\n <div class=\"str-chat__message-attachment-actions-form\">\n <button\n *ngFor=\"let action of attachment.actions; trackBy: trackByActionValue\"\n class=\"str-chat__message-attachment-actions-button str-chat__message-attachment-actions-button--{{\n action.style\n }}\"\n data-testclass=\"attachment-action\"\n (click)=\"sendAction(action)\"\n (keyup.enter)=\"sendAction(action)\"\n >\n {{ action.text }}\n </button>\n </div>\n </div>\n </div>\n</ng-container>\n", components: [{ type: IconComponent, selector: "stream-icon", inputs: ["icon", "size"] }], directives: [{ type: i6.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { type: i6.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
1469
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: AttachmentListComponent, decorators: [{
1470
+ type: Component,
1471
+ args: [{
1472
+ selector: 'stream-attachment-list',
1473
+ templateUrl: './attachment-list.component.html',
1474
+ styles: [],
1475
+ }]
1476
+ }], ctorParameters: function () { return [{ type: ImageLoadService }, { type: ChannelService }]; }, propDecorators: { messageId: [{
1477
+ type: Input
1478
+ }], attachments: [{
1479
+ type: Input
1480
+ }] } });
1481
+
1228
1482
  class AttachmentPreviewListComponent {
1229
1483
  constructor(attachmentService) {
1230
1484
  this.attachmentService = attachmentService;
@@ -1245,7 +1499,7 @@ class AttachmentPreviewListComponent {
1245
1499
  }
1246
1500
  }
1247
1501
  AttachmentPreviewListComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: AttachmentPreviewListComponent, deps: [{ token: AttachmentService }], target: i0.ɵɵFactoryTarget.Component });
1248
- AttachmentPreviewListComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.5", type: AttachmentPreviewListComponent, selector: "stream-attachment-preview-list", ngImport: i0, template: "<div class=\"rfu-image-previewer\" *ngIf=\"(attachmentUploads$ | async)?.length\">\n <ng-container\n *ngFor=\"\n let attachmentUpload of attachmentUploads$ | async;\n trackBy: trackByFile\n \"\n >\n <div\n *ngIf=\"attachmentUpload.type === 'image'\"\n class=\"rfu-image-previewer__image\"\n [class.rfu-image-previewer__image--loaded]=\"\n attachmentUpload.state === 'success'\n \"\n data-testclass=\"attachment-image-preview\"\n >\n <div\n *ngIf=\"attachmentUpload.state === 'error'\"\n class=\"rfu-image-previewer__retry\"\n (click)=\"retryAttachmentUpload(attachmentUpload.file)\"\n (keyup.enter)=\"retryAttachmentUpload(attachmentUpload.file)\"\n data-testclass=\"upload-error\"\n >\n <stream-icon icon=\"retry\"></stream-icon>\n </div>\n <div class=\"rfu-thumbnail__wrapper\" style=\"width: 100; height: 100\">\n <div class=\"rfu-thumbnail__overlay\">\n <div\n class=\"rfu-icon-button\"\n data-testclass=\"delete-attachment\"\n role=\"button\"\n (click)=\"deleteAttachment(attachmentUpload)\"\n (keyup.enter)=\"deleteAttachment(attachmentUpload)\"\n >\n <stream-icon icon=\"close\"></stream-icon>\n </div>\n </div>\n <img\n *ngIf=\"attachmentUpload.url || attachmentUpload.previewUri\"\n src=\"{{\n attachmentUpload.url\n ? attachmentUpload.url\n : attachmentUpload.previewUri\n }}\"\n alt=\"attachmentUpload.file.name\"\n class=\"rfu-thumbnail__image\"\n data-testclass=\"attachment-image\"\n />\n </div>\n <stream-loading-indicator\n data-testclass=\"loading-indicator\"\n color=\"rgba(255,255,255,0.7)\"\n *ngIf=\"attachmentUpload.state === 'uploading'\"\n ></stream-loading-indicator>\n </div>\n <div\n class=\"rfu-file-previewer\"\n *ngIf=\"attachmentUpload.type === 'file'\"\n data-testclass=\"attachment-file-preview\"\n >\n <ol>\n <li\n class=\"rfu-file-previewer__file\"\n [class.rfu-file-previewer__file--uploading]=\"\n attachmentUpload.state === 'uploading'\n \"\n [class.rfu-file-previewer__file--failed]=\"\n attachmentUpload.state === 'error'\n \"\n >\n <stream-icon icon=\"file\"></stream-icon>\n\n <a\n data-testclass=\"file-download-link\"\n href=\"{{ attachmentUpload.url }}\"\n (click)=\"attachmentUpload.url ? null : $event.preventDefault()\"\n (keyup.enter)=\"\n attachmentUpload.url ? null : $event.preventDefault()\n \"\n download\n >\n {{ attachmentUpload.file.name }}\n <ng-container *ngIf=\"attachmentUpload.state === 'error'\">\n <div\n data-testclass=\"file-upload-retry\"\n class=\"rfu-file-previewer__failed\"\n (click)=\"retryAttachmentUpload(attachmentUpload.file)\"\n (keyup.enter)=\"retryAttachmentUpload(attachmentUpload.file)\"\n translate\n >\n streamChat.failed\n </div>\n <div\n class=\"rfu-file-previewer__retry\"\n (click)=\"retryAttachmentUpload(attachmentUpload.file)\"\n (keyup.enter)=\"retryAttachmentUpload(attachmentUpload.file)\"\n translate\n >\n streamChat.retry\n </div>\n </ng-container>\n </a>\n\n <span\n data-testclass=\"file-delete\"\n class=\"rfu-file-previewer__close-button\"\n (click)=\"deleteAttachment(attachmentUpload)\"\n (keyup.enter)=\"deleteAttachment(attachmentUpload)\"\n >\n \u2718\n </span>\n <div\n *ngIf=\"attachmentUpload.state === 'uploading'\"\n class=\"rfu-file-previewer__loading-indicator\"\n >\n <stream-loading-indicator></stream-loading-indicator>\n </div>\n </li>\n </ol>\n </div>\n </ng-container>\n</div>\n", components: [{ type: IconComponent, selector: "stream-icon", inputs: ["icon", "size"] }, { type: LoadingIndicatorComponent, selector: "stream-loading-indicator", inputs: ["size", "color"] }], directives: [{ type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i3.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { type: i10.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }], pipes: { "async": i3.AsyncPipe } });
1502
+ AttachmentPreviewListComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.5", type: AttachmentPreviewListComponent, selector: "stream-attachment-preview-list", ngImport: i0, template: "<div class=\"rfu-image-previewer\" *ngIf=\"(attachmentUploads$ | async)?.length\">\n <ng-container\n *ngFor=\"\n let attachmentUpload of attachmentUploads$ | async;\n trackBy: trackByFile\n \"\n >\n <div\n *ngIf=\"attachmentUpload.type === 'image'\"\n class=\"rfu-image-previewer__image\"\n [class.rfu-image-previewer__image--loaded]=\"\n attachmentUpload.state === 'success'\n \"\n data-testclass=\"attachment-image-preview\"\n >\n <div\n *ngIf=\"attachmentUpload.state === 'error'\"\n class=\"rfu-image-previewer__retry\"\n (click)=\"retryAttachmentUpload(attachmentUpload.file)\"\n (keyup.enter)=\"retryAttachmentUpload(attachmentUpload.file)\"\n data-testclass=\"upload-error\"\n >\n <stream-icon icon=\"retry\"></stream-icon>\n </div>\n <div class=\"rfu-thumbnail__wrapper\" style=\"width: 100; height: 100\">\n <div class=\"rfu-thumbnail__overlay\">\n <div\n class=\"rfu-icon-button\"\n data-testclass=\"delete-attachment\"\n role=\"button\"\n (click)=\"deleteAttachment(attachmentUpload)\"\n (keyup.enter)=\"deleteAttachment(attachmentUpload)\"\n >\n <stream-icon icon=\"close\"></stream-icon>\n </div>\n </div>\n <img\n *ngIf=\"attachmentUpload.url || attachmentUpload.previewUri\"\n src=\"{{\n attachmentUpload.url\n ? attachmentUpload.url\n : attachmentUpload.previewUri\n }}\"\n alt=\"attachmentUpload.file.name\"\n class=\"rfu-thumbnail__image\"\n data-testclass=\"attachment-image\"\n />\n </div>\n <stream-loading-indicator\n data-testclass=\"loading-indicator\"\n color=\"rgba(255,255,255,0.7)\"\n *ngIf=\"attachmentUpload.state === 'uploading'\"\n ></stream-loading-indicator>\n </div>\n <div\n class=\"rfu-file-previewer\"\n *ngIf=\"attachmentUpload.type === 'file'\"\n data-testclass=\"attachment-file-preview\"\n >\n <ol>\n <li\n class=\"rfu-file-previewer__file\"\n [class.rfu-file-previewer__file--uploading]=\"\n attachmentUpload.state === 'uploading'\n \"\n [class.rfu-file-previewer__file--failed]=\"\n attachmentUpload.state === 'error'\n \"\n >\n <stream-icon icon=\"file\"></stream-icon>\n\n <a\n data-testclass=\"file-download-link\"\n href=\"{{ attachmentUpload.url }}\"\n (click)=\"attachmentUpload.url ? null : $event.preventDefault()\"\n (keyup.enter)=\"\n attachmentUpload.url ? null : $event.preventDefault()\n \"\n download\n >\n {{ attachmentUpload.file.name }}\n <ng-container *ngIf=\"attachmentUpload.state === 'error'\">\n <div\n data-testclass=\"file-upload-retry\"\n class=\"rfu-file-previewer__failed\"\n (click)=\"retryAttachmentUpload(attachmentUpload.file)\"\n (keyup.enter)=\"retryAttachmentUpload(attachmentUpload.file)\"\n translate\n >\n streamChat.failed\n </div>\n <div\n class=\"rfu-file-previewer__retry\"\n (click)=\"retryAttachmentUpload(attachmentUpload.file)\"\n (keyup.enter)=\"retryAttachmentUpload(attachmentUpload.file)\"\n translate\n >\n streamChat.retry\n </div>\n </ng-container>\n </a>\n\n <span\n data-testclass=\"file-delete\"\n class=\"rfu-file-previewer__close-button\"\n (click)=\"deleteAttachment(attachmentUpload)\"\n (keyup.enter)=\"deleteAttachment(attachmentUpload)\"\n >\n \u2718\n </span>\n <div\n *ngIf=\"attachmentUpload.state === 'uploading'\"\n class=\"rfu-file-previewer__loading-indicator\"\n >\n <stream-loading-indicator></stream-loading-indicator>\n </div>\n </li>\n </ol>\n </div>\n </ng-container>\n</div>\n", components: [{ type: IconComponent, selector: "stream-icon", inputs: ["icon", "size"] }, { type: LoadingIndicatorComponent, selector: "stream-loading-indicator", inputs: ["size", "color"] }], directives: [{ type: i6.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i6.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { type: i1.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }], pipes: { "async": i6.AsyncPipe } });
1249
1503
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: AttachmentPreviewListComponent, decorators: [{
1250
1504
  type: Component,
1251
1505
  args: [{
@@ -1265,6 +1519,7 @@ class MessageInputComponent {
1265
1519
  this.componentFactoryResolver = componentFactoryResolver;
1266
1520
  this.cdRef = cdRef;
1267
1521
  this.chatClient = chatClient;
1522
+ this.mode = 'main';
1268
1523
  this.messageUpdate = new EventEmitter();
1269
1524
  this.textareaValue = '';
1270
1525
  this.mentionedUsers = [];
@@ -1285,14 +1540,19 @@ class MessageInputComponent {
1285
1540
  this.isFileUploadAuthorized =
1286
1541
  capabilities.indexOf('upload-file') !== -1;
1287
1542
  this.canSendLinks = capabilities.indexOf('send-links') !== -1;
1288
- this.canSendMessages = capabilities.indexOf('send-message') !== -1;
1289
- if (this.isViewInited) {
1290
- this.cdRef.detectChanges();
1291
- this.initTextarea();
1292
- }
1543
+ this.channel = channel;
1544
+ this.setCanSendMessages();
1293
1545
  }
1294
1546
  }));
1295
1547
  this.subscriptions.push(this.chatClient.appSettings$.subscribe((appSettings) => (this.appSettings = appSettings)));
1548
+ this.subscriptions.push(this.channelService.messageToQuote$.subscribe((m) => {
1549
+ const isThreadReply = m && m.parent_id;
1550
+ if ((this.mode === 'thread' && isThreadReply) ||
1551
+ (this.mode === 'thread' && this.quotedMessage && !m) ||
1552
+ (this.mode === 'main' && !isThreadReply)) {
1553
+ this.quotedMessage = m;
1554
+ }
1555
+ }));
1296
1556
  this.attachmentUploads$ = this.attachmentService.attachmentUploads$;
1297
1557
  this.isFileUploadEnabled = this.configService.isFileUploadEnabled;
1298
1558
  this.acceptedFileTypes = this.configService.acceptedFileTypes;
@@ -1302,6 +1562,8 @@ class MessageInputComponent {
1302
1562
  this.mentionAutocompleteItemTemplate =
1303
1563
  this.configService.mentionAutocompleteItemTemplate;
1304
1564
  this.mentionScope = this.configService.mentionScope;
1565
+ this.commandAutocompleteItemTemplate =
1566
+ this.configService.commandAutocompleteItemTemplate;
1305
1567
  }
1306
1568
  ngAfterViewInit() {
1307
1569
  this.isViewInited = true;
@@ -1332,14 +1594,22 @@ class MessageInputComponent {
1332
1594
  this.configService.mentionAutocompleteItemTemplate =
1333
1595
  this.mentionAutocompleteItemTemplate;
1334
1596
  }
1597
+ if (changes.commandAutocompleteItemTemplate) {
1598
+ this.configService.commandAutocompleteItemTemplate =
1599
+ this.commandAutocompleteItemTemplate;
1600
+ }
1335
1601
  if (changes.mentionScope) {
1336
1602
  this.configService.mentionScope = this.mentionScope;
1337
1603
  }
1604
+ if (changes.mode) {
1605
+ this.setCanSendMessages();
1606
+ }
1338
1607
  }
1339
1608
  ngOnDestroy() {
1340
1609
  this.subscriptions.forEach((s) => s.unsubscribe());
1341
1610
  }
1342
1611
  messageSent() {
1612
+ var _a;
1343
1613
  return __awaiter(this, void 0, void 0, function* () {
1344
1614
  let attachmentUploadInProgressCounter;
1345
1615
  this.attachmentService.attachmentUploadInProgressCounter$
@@ -1364,10 +1634,16 @@ class MessageInputComponent {
1364
1634
  if (!this.isUpdate) {
1365
1635
  this.textareaValue = '';
1366
1636
  }
1637
+ let parentMessageId = undefined;
1638
+ if (this.mode === 'thread') {
1639
+ this.channelService.activeParentMessageId$
1640
+ .pipe(first())
1641
+ .subscribe((id) => (parentMessageId = id));
1642
+ }
1367
1643
  try {
1368
1644
  yield (this.isUpdate
1369
1645
  ? this.channelService.updateMessage(Object.assign(Object.assign({}, this.message), { text: text, attachments: attachments }))
1370
- : this.channelService.sendMessage(text, attachments, this.mentionedUsers));
1646
+ : this.channelService.sendMessage(text, attachments, this.mentionedUsers, parentMessageId, (_a = this.quotedMessage) === null || _a === void 0 ? void 0 : _a.id));
1371
1647
  this.messageUpdate.emit();
1372
1648
  if (!this.isUpdate) {
1373
1649
  this.attachmentService.resetAttachmentUploads();
@@ -1378,6 +1654,9 @@ class MessageInputComponent {
1378
1654
  this.notificationService.addTemporaryNotification('streamChat.Edit message request failed');
1379
1655
  }
1380
1656
  }
1657
+ if (this.quotedMessage) {
1658
+ this.deselectMessageToQuote();
1659
+ }
1381
1660
  });
1382
1661
  }
1383
1662
  get containsLinks() {
@@ -1387,6 +1666,13 @@ class MessageInputComponent {
1387
1666
  var _a;
1388
1667
  return this.acceptedFileTypes ? (_a = this.acceptedFileTypes) === null || _a === void 0 ? void 0 : _a.join(',') : '';
1389
1668
  }
1669
+ get quotedMessageAttachments() {
1670
+ var _a;
1671
+ const originalAttachments = (_a = this.quotedMessage) === null || _a === void 0 ? void 0 : _a.attachments;
1672
+ return originalAttachments && originalAttachments.length
1673
+ ? [originalAttachments[0]]
1674
+ : [];
1675
+ }
1390
1676
  filesSelected(fileList) {
1391
1677
  return __awaiter(this, void 0, void 0, function* () {
1392
1678
  if (!(yield this.areAttachemntsValid(fileList))) {
@@ -1396,6 +1682,9 @@ class MessageInputComponent {
1396
1682
  this.clearFileInput();
1397
1683
  });
1398
1684
  }
1685
+ deselectMessageToQuote() {
1686
+ this.channelService.selectMessageToQuote(undefined);
1687
+ }
1399
1688
  clearFileInput() {
1400
1689
  this.fileInput.nativeElement.value = '';
1401
1690
  }
@@ -1461,9 +1750,24 @@ class MessageInputComponent {
1461
1750
  return isValid;
1462
1751
  });
1463
1752
  }
1753
+ setCanSendMessages() {
1754
+ var _a, _b;
1755
+ const capabilities = (_b = (_a = this.channel) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.own_capabilities;
1756
+ if (!capabilities) {
1757
+ this.canSendMessages = false;
1758
+ }
1759
+ else {
1760
+ this.canSendMessages =
1761
+ capabilities.indexOf(this.mode === 'main' ? 'send-message' : 'send-reply') !== -1;
1762
+ }
1763
+ if (this.isViewInited) {
1764
+ this.cdRef.detectChanges();
1765
+ this.initTextarea();
1766
+ }
1767
+ }
1464
1768
  }
1465
1769
  MessageInputComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: MessageInputComponent, deps: [{ token: ChannelService }, { token: NotificationService }, { token: AttachmentService }, { token: MessageInputConfigService }, { token: textareaInjectionToken }, { token: i0.ComponentFactoryResolver }, { token: i0.ChangeDetectorRef }, { token: ChatClientService }], target: i0.ɵɵFactoryTarget.Component });
1466
- MessageInputComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.5", type: MessageInputComponent, selector: "stream-message-input", inputs: { isFileUploadEnabled: "isFileUploadEnabled", areMentionsEnabled: "areMentionsEnabled", mentionScope: "mentionScope", mentionAutocompleteItemTemplate: "mentionAutocompleteItemTemplate", acceptedFileTypes: "acceptedFileTypes", isMultipleFileUploadEnabled: "isMultipleFileUploadEnabled", message: "message" }, outputs: { messageUpdate: "messageUpdate" }, providers: [AttachmentService], viewQueries: [{ propertyName: "fileInput", first: true, predicate: ["fileInput"], descendants: true }, { propertyName: "textareaAnchor", first: true, predicate: TextareaDirective, descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div\n class=\"str-chat__input-flat\"\n [class.str-chat__input-flat-has-attachments]=\"\n (attachmentUploads$ | async)!.length > 0\n \"\n>\n <div class=\"str-chat__input-flat-wrapper\">\n <div class=\"str-chat__input-flat--textarea-wrapper\">\n <stream-attachment-preview-list\n class=\"rfu-image-previewer-angular-host\"\n ></stream-attachment-preview-list>\n <div class=\"rta str-chat__textarea\">\n <ng-template\n *ngIf=\"canSendMessages; else notAllowed\"\n streamTextarea\n [(value)]=\"textareaValue\"\n (send)=\"messageSent()\"\n [componentRef]=\"textareaRef\"\n (userMentions)=\"mentionedUsers = $event\"\n [areMentionsEnabled]=\"areMentionsEnabled\"\n [mentionAutocompleteItemTemplate]=\"mentionAutocompleteItemTemplate\"\n [mentionScope]=\"mentionScope\"\n ></ng-template>\n <ng-template #notAllowed>\n <textarea\n disabled\n rows=\"1\"\n [value]=\"\n 'streamChat.You can\\'t send messages in this channel' | translate\n \"\n class=\"rta__textarea str-chat__textarea__textarea\"\n ></textarea>\n </ng-template>\n </div>\n <div\n *ngIf=\"isFileUploadEnabled && isFileUploadAuthorized && canSendMessages\"\n class=\"str-chat__fileupload-wrapper\"\n data-testid=\"file-upload-button\"\n >\n <div class=\"str-chat__tooltip\">\n {{ \"streamChat.Attach files\" | translate }}\n </div>\n <div class=\"rfu-file-upload-button\">\n <label>\n <input\n #fileInput\n type=\"file\"\n class=\"rfu-file-input\"\n data-testid=\"file-input\"\n [accept]=\"accept\"\n [multiple]=\"isMultipleFileUploadEnabled\"\n (change)=\"filesSelected(fileInput.files)\"\n />\n <span class=\"str-chat__input-flat-fileupload\">\n <stream-icon icon=\"file-upload\"></stream-icon>\n </span>\n </label>\n </div>\n </div>\n </div>\n <button\n *ngIf=\"canSendMessages\"\n data-testid=\"send-button\"\n class=\"str-chat__send-button\"\n (click)=\"messageSent()\"\n (keyup.enter)=\"messageSent()\"\n >\n <stream-icon icon=\"send\"></stream-icon>\n </button>\n </div>\n</div>\n", components: [{ type: AttachmentPreviewListComponent, selector: "stream-attachment-preview-list" }, { type: IconComponent, selector: "stream-icon", inputs: ["icon", "size"] }], directives: [{ type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: TextareaDirective, selector: "[streamTextarea]", inputs: ["componentRef", "areMentionsEnabled", "mentionAutocompleteItemTemplate", "mentionScope", "value"], outputs: ["valueChange", "send", "userMentions"] }], pipes: { "async": i3.AsyncPipe, "translate": i10.TranslatePipe } });
1770
+ MessageInputComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.5", type: MessageInputComponent, selector: "stream-message-input", inputs: { isFileUploadEnabled: "isFileUploadEnabled", areMentionsEnabled: "areMentionsEnabled", mentionScope: "mentionScope", mentionAutocompleteItemTemplate: "mentionAutocompleteItemTemplate", commandAutocompleteItemTemplate: "commandAutocompleteItemTemplate", mode: "mode", acceptedFileTypes: "acceptedFileTypes", isMultipleFileUploadEnabled: "isMultipleFileUploadEnabled", message: "message" }, outputs: { messageUpdate: "messageUpdate" }, providers: [AttachmentService], viewQueries: [{ propertyName: "fileInput", first: true, predicate: ["fileInput"], descendants: true }, { propertyName: "textareaAnchor", first: true, predicate: TextareaDirective, descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div\n class=\"{{\n mode === 'main' ? 'str-chat__input-flat' : 'str-chat__small-message-input'\n }}\"\n [class.str-chat__input-flat-has-attachments]=\"\n (attachmentUploads$ | async)!.length > 0\n \"\n [class.str-chat__input-flat-quoted]=\"!!quotedMessage\"\n>\n <div\n data-testid=\"quoted-message-container\"\n class=\"quoted-message-preview\"\n *ngIf=\"quotedMessage\"\n >\n <div class=\"quoted-message-preview-header\">\n <div>{{ \"streamChat.Reply to Message\" | translate }}</div>\n <button\n class=\"str-chat__square-button\"\n data-testid=\"remove-quote\"\n (click)=\"deselectMessageToQuote()\"\n (keyup.enter)=\"deselectMessageToQuote()\"\n >\n <stream-icon\n icon=\"close-no-outline\"\n style=\"font-size: 10px; line-height: 10px\"\n ></stream-icon>\n </button>\n </div>\n <div class=\"quoted-message-preview-content\">\n <stream-avatar\n data-testid=\"qouted-message-avatar\"\n class=\"str-chat-angular__avatar-host\"\n [imageUrl]=\"quotedMessage?.user?.image\"\n [name]=\"quotedMessage?.user?.name || quotedMessage?.user?.id\"\n [size]=\"20\"\n ></stream-avatar>\n <div class=\"quoted-message-preview-content-inner\">\n <stream-attachment-list\n *ngIf=\"\n quotedMessage?.attachments && quotedMessage?.attachments?.length\n \"\n [attachments]=\"quotedMessageAttachments\"\n [messageId]=\"quotedMessage?.id\"\n ></stream-attachment-list>\n <div\n data-testid=\"quoted-message-text\"\n [innerHTML]=\"quotedMessage?.html || quotedMessage?.text\"\n ></div>\n </div>\n </div>\n </div>\n <div class=\"str-chat__input-flat-wrapper\" style=\"width: 100%\">\n <div\n class=\"{{\n mode === 'main'\n ? 'str-chat__input-flat--textarea-wrapper'\n : 'str-chat__small-message-input--textarea-wrapper'\n }}\"\n >\n <stream-attachment-preview-list\n class=\"rfu-image-previewer-angular-host\"\n ></stream-attachment-preview-list>\n <div class=\"rta str-chat__textarea\">\n <ng-template\n *ngIf=\"canSendMessages; else notAllowed\"\n streamTextarea\n [(value)]=\"textareaValue\"\n (send)=\"messageSent()\"\n [componentRef]=\"textareaRef\"\n (userMentions)=\"mentionedUsers = $event\"\n [areMentionsEnabled]=\"areMentionsEnabled\"\n [mentionAutocompleteItemTemplate]=\"mentionAutocompleteItemTemplate\"\n [commandAutocompleteItemTemplate]=\"commandAutocompleteItemTemplate\"\n [mentionScope]=\"mentionScope\"\n ></ng-template>\n <ng-template #notAllowed>\n <textarea\n disabled\n rows=\"1\"\n [value]=\"\n (mode === 'thread'\n ? 'You can\\'t send thread replies in this channel'\n : 'streamChat.You can\\'t send messages in this channel'\n ) | translate\n \"\n class=\"rta__textarea str-chat__textarea__textarea\"\n ></textarea>\n </ng-template>\n </div>\n <div\n *ngIf=\"isFileUploadEnabled && isFileUploadAuthorized && canSendMessages\"\n class=\"str-chat__fileupload-wrapper\"\n data-testid=\"file-upload-button\"\n >\n <div class=\"str-chat__tooltip\">\n {{ \"streamChat.Attach files\" | translate }}\n </div>\n <div class=\"rfu-file-upload-button\">\n <label>\n <input\n #fileInput\n type=\"file\"\n class=\"rfu-file-input\"\n data-testid=\"file-input\"\n [accept]=\"accept\"\n [multiple]=\"isMultipleFileUploadEnabled\"\n (change)=\"filesSelected(fileInput.files)\"\n />\n <span class=\"str-chat__input-flat-fileupload\">\n <stream-icon icon=\"file-upload\"></stream-icon>\n </span>\n </label>\n </div>\n </div>\n </div>\n <button\n *ngIf=\"canSendMessages\"\n data-testid=\"send-button\"\n class=\"str-chat__send-button\"\n (click)=\"messageSent()\"\n (keyup.enter)=\"messageSent()\"\n >\n <stream-icon icon=\"send\"></stream-icon>\n </button>\n </div>\n</div>\n", components: [{ type: IconComponent, selector: "stream-icon", inputs: ["icon", "size"] }, { type: AvatarComponent, selector: "stream-avatar", inputs: ["name", "imageUrl", "size"] }, { type: AttachmentListComponent, selector: "stream-attachment-list", inputs: ["messageId", "attachments"] }, { type: AttachmentPreviewListComponent, selector: "stream-attachment-preview-list" }], directives: [{ type: i6.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: TextareaDirective, selector: "[streamTextarea]", inputs: ["componentRef", "areMentionsEnabled", "mentionAutocompleteItemTemplate", "mentionScope", "commandAutocompleteItemTemplate", "value"], outputs: ["valueChange", "send", "userMentions"] }], pipes: { "async": i6.AsyncPipe, "translate": i1.TranslatePipe } });
1467
1771
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: MessageInputComponent, decorators: [{
1468
1772
  type: Component,
1469
1773
  args: [{
@@ -1483,6 +1787,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.5", ngImpor
1483
1787
  type: Input
1484
1788
  }], mentionAutocompleteItemTemplate: [{
1485
1789
  type: Input
1790
+ }], commandAutocompleteItemTemplate: [{
1791
+ type: Input
1792
+ }], mode: [{
1793
+ type: Input
1486
1794
  }], acceptedFileTypes: [{
1487
1795
  type: Input
1488
1796
  }], isMultipleFileUploadEnabled: [{
@@ -1541,7 +1849,7 @@ class ModalComponent {
1541
1849
  }
1542
1850
  }
1543
1851
  ModalComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: ModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1544
- ModalComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.5", type: ModalComponent, selector: "stream-modal", inputs: { isOpen: "isOpen" }, outputs: { isOpenChange: "isOpenChange" }, viewQueries: [{ propertyName: "content", first: true, predicate: ["content"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div\n data-testid=\"modal\"\n class=\"str-chat__modal str-chat__modal--{{ isOpen ? 'open' : 'close' }}\"\n>\n <div\n data-testid=\"close\"\n class=\"str-chat__modal__close-button\"\n (click)=\"close()\"\n (keyup.enter)=\"close()\"\n translate\n >\n streamChat.Close\n <stream-icon icon=\"close\"></stream-icon>\n </div>\n <div class=\"str-chat__modal__inner\" #content>\n <ng-content></ng-content>\n </div>\n</div>\n", components: [{ type: IconComponent, selector: "stream-icon", inputs: ["icon", "size"] }], directives: [{ type: i10.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }] });
1852
+ ModalComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.5", type: ModalComponent, selector: "stream-modal", inputs: { isOpen: "isOpen" }, outputs: { isOpenChange: "isOpenChange" }, viewQueries: [{ propertyName: "content", first: true, predicate: ["content"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div\n data-testid=\"modal\"\n class=\"str-chat__modal str-chat__modal--{{ isOpen ? 'open' : 'close' }}\"\n>\n <div\n data-testid=\"close\"\n class=\"str-chat__modal__close-button\"\n (click)=\"close()\"\n (keyup.enter)=\"close()\"\n translate\n >\n streamChat.Close\n <stream-icon icon=\"close\"></stream-icon>\n </div>\n <div class=\"str-chat__modal__inner\" #content>\n <ng-content></ng-content>\n </div>\n</div>\n", components: [{ type: IconComponent, selector: "stream-icon", inputs: ["icon", "size"] }], directives: [{ type: i1.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }] });
1545
1853
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: ModalComponent, decorators: [{
1546
1854
  type: Component,
1547
1855
  args: [{
@@ -1584,7 +1892,7 @@ class NotificationListComponent {
1584
1892
  }
1585
1893
  }
1586
1894
  NotificationListComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: NotificationListComponent, deps: [{ token: NotificationService }], target: i0.ɵɵFactoryTarget.Component });
1587
- NotificationListComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.5", type: NotificationListComponent, selector: "stream-notification-list", ngImport: i0, template: "<div class=\"str-chat__list-notifications\">\n <stream-notification\n *ngFor=\"let notification of notifications$ | async; trackBy: trackByItem\"\n [type]=\"notification.type\"\n ><div data-testclass=\"notification-content\">\n {{ notification.text | translate: notification.translateParams }}\n </div></stream-notification\n >\n</div>\n", components: [{ type: NotificationComponent, selector: "stream-notification", inputs: ["type"] }], directives: [{ type: i3.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }], pipes: { "async": i3.AsyncPipe, "translate": i10.TranslatePipe } });
1895
+ NotificationListComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.5", type: NotificationListComponent, selector: "stream-notification-list", ngImport: i0, template: "<div class=\"str-chat__list-notifications\">\n <stream-notification\n *ngFor=\"let notification of notifications$ | async; trackBy: trackByItem\"\n [type]=\"notification.type\"\n ><div data-testclass=\"notification-content\">\n {{ notification.text | translate: notification.translateParams }}\n </div></stream-notification\n >\n</div>\n", components: [{ type: NotificationComponent, selector: "stream-notification", inputs: ["type"] }], directives: [{ type: i6.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }], pipes: { "async": i6.AsyncPipe, "translate": i1.TranslatePipe } });
1588
1896
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: NotificationListComponent, decorators: [{
1589
1897
  type: Component,
1590
1898
  args: [{
@@ -1635,21 +1943,32 @@ class MessageActionsBoxComponent {
1635
1943
  }
1636
1944
  }
1637
1945
  get isQuoteVisible() {
1638
- return this.enabledActions.indexOf('quote') !== -1;
1946
+ var _a;
1947
+ return ((this.enabledActions.indexOf('quote') !== -1 ||
1948
+ this.enabledActions.indexOf('quote-message') !== -1) &&
1949
+ !((_a = this.message) === null || _a === void 0 ? void 0 : _a.quoted_message));
1639
1950
  }
1640
1951
  get isEditVisible() {
1641
- return ((this.enabledActions.indexOf('edit') !== -1 && this.isMine) ||
1642
- this.enabledActions.indexOf('edit-any') !== -1);
1952
+ return (((this.enabledActions.indexOf('edit') !== -1 ||
1953
+ this.enabledActions.indexOf('update-own-message') !== -1) &&
1954
+ this.isMine) ||
1955
+ this.enabledActions.indexOf('edit-any') !== -1 ||
1956
+ this.enabledActions.indexOf('update-any-message') !== -1);
1643
1957
  }
1644
1958
  get isDeleteVisible() {
1645
- return ((this.enabledActions.indexOf('delete') !== -1 && this.isMine) ||
1646
- this.enabledActions.indexOf('delete-any') !== -1);
1959
+ return (((this.enabledActions.indexOf('delete') !== -1 ||
1960
+ this.enabledActions.indexOf('delete-own-message') !== -1) &&
1961
+ this.isMine) ||
1962
+ this.enabledActions.indexOf('delete-any') !== -1 ||
1963
+ this.enabledActions.indexOf('delete-any-message') !== -1);
1647
1964
  }
1648
1965
  get isMuteVisible() {
1649
1966
  return this.enabledActions.indexOf('mute') !== -1;
1650
1967
  }
1651
1968
  get isFlagVisible() {
1652
- return this.enabledActions.indexOf('flag') !== -1 && !this.isMine;
1969
+ return ((this.enabledActions.indexOf('flag') !== -1 ||
1970
+ this.enabledActions.indexOf('flag-message') !== -1) &&
1971
+ !this.isMine);
1653
1972
  }
1654
1973
  get isPinVisible() {
1655
1974
  return this.enabledActions.indexOf('pin') !== -1;
@@ -1672,7 +1991,7 @@ class MessageActionsBoxComponent {
1672
1991
  alert('Feature not yet implemented');
1673
1992
  }
1674
1993
  quoteClicked() {
1675
- alert('Feature not yet implemented');
1994
+ this.channelService.selectMessageToQuote(this.message);
1676
1995
  }
1677
1996
  editClicked() {
1678
1997
  this.isEditing.emit(true);
@@ -1694,7 +2013,7 @@ class MessageActionsBoxComponent {
1694
2013
  }
1695
2014
  }
1696
2015
  MessageActionsBoxComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: MessageActionsBoxComponent, deps: [{ token: ChatClientService }, { token: NotificationService }, { token: ChannelService }], target: i0.ɵɵFactoryTarget.Component });
1697
- MessageActionsBoxComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.5", type: MessageActionsBoxComponent, selector: "stream-message-actions-box", inputs: { messageInputTemplate: "messageInputTemplate", isOpen: "isOpen", isMine: "isMine", message: "message", enabledActions: "enabledActions" }, outputs: { displayedActionsCount: "displayedActionsCount", isEditing: "isEditing" }, viewQueries: [{ propertyName: "messageInput", first: true, predicate: MessageInputComponent, descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div\n data-testid=\"action-box\"\n class=\"str-chat__message-actions-box\"\n [class.str-chat__message-actions-box--open]=\"isOpen\"\n [class.str-chat__message-actions-box--mine]=\"isMine\"\n>\n <ul class=\"str-chat__message-actions-list\">\n <button\n data-testid=\"quote-action\"\n *ngIf=\"isQuoteVisible\"\n (click)=\"quoteClicked()\"\n >\n <li class=\"str-chat__message-actions-list-item\">\n {{ \"streamChat.Reply\" | translate }}\n </li>\n </button>\n <button\n data-testid=\"pin-action\"\n *ngIf=\"isPinVisible\"\n (click)=\"pinClicked()\"\n >\n <li class=\"str-chat__message-actions-list-item\">\n {{\n (message?.pinned ? \"streamChat.Unpin\" : \"streamChat.Pin\") | translate\n }}\n </li>\n </button>\n <button\n data-testid=\"flag-action\"\n *ngIf=\"isFlagVisible\"\n (click)=\"flagClicked()\"\n >\n <li class=\"str-chat__message-actions-list-item\">\n {{ \"streamChat.Flag\" | translate }}\n </li>\n </button>\n <button\n data-testid=\"mute-action\"\n *ngIf=\"isMuteVisible\"\n (click)=\"muteClicked()\"\n >\n <li class=\"str-chat__message-actions-list-item\">\n {{ \"streamChat.Mute\" | translate }}\n </li>\n </button>\n <button\n data-testid=\"edit-action\"\n *ngIf=\"isEditVisible\"\n (click)=\"editClicked()\"\n >\n <li class=\"str-chat__message-actions-list-item\">\n {{ \"streamChat.Edit Message\" | translate }}\n </li>\n </button>\n <button\n data-testid=\"delete-action\"\n *ngIf=\"isDeleteVisible\"\n (click)=\"deleteClicked()\"\n >\n <li class=\"str-chat__message-actions-list-item\">\n {{ \"streamChat.Delete\" | translate }}\n </li>\n </button>\n </ul>\n</div>\n\n<stream-modal\n [isOpen]=\"isEditModalOpen\"\n (isOpenChange)=\"\n isEditModalOpen = $event; isEditModalOpen ? '' : modalClosed()\n \"\n>\n <div class=\"str-chat__edit-message-form\" *ngIf=\"isEditModalOpen\">\n <ng-container *ngIf=\"messageInputTemplate; else defaultInput\">\n <ng-container\n *ngTemplateOutlet=\"\n messageInputTemplate;\n context: {\n message: message,\n messageUpdateHandler: modalClosed\n }\n \"\n ></ng-container>\n </ng-container>\n <ng-template #defaultInput>\n <stream-message-input\n [message]=\"message\"\n (messageUpdate)=\"modalClosed()\"\n ></stream-message-input>\n </ng-template>\n <stream-notification-list></stream-notification-list>\n <div\n class=\"\n str-chat__message-team-form-footer\n str-chat__message-team-form-footer-angular\n \"\n >\n <div class=\"str-chat__edit-message-form-options\">\n <button translate data-testid=\"cancel-button\" (click)=\"modalClosed()\">\n streamChat.Cancel\n </button>\n <button\n type=\"submit\"\n translate\n data-testid=\"send-button\"\n (click)=\"sendClicked()\"\n (keyup.enter)=\"sendClicked()\"\n >\n streamChat.Send\n </button>\n </div>\n </div>\n </div>\n</stream-modal>\n", components: [{ type: ModalComponent, selector: "stream-modal", inputs: ["isOpen"], outputs: ["isOpenChange"] }, { type: MessageInputComponent, selector: "stream-message-input", inputs: ["isFileUploadEnabled", "areMentionsEnabled", "mentionScope", "mentionAutocompleteItemTemplate", "acceptedFileTypes", "isMultipleFileUploadEnabled", "message"], outputs: ["messageUpdate"] }, { type: NotificationListComponent, selector: "stream-notification-list" }], directives: [{ type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i3.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet"] }, { type: i10.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }], pipes: { "translate": i10.TranslatePipe } });
2016
+ MessageActionsBoxComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.5", type: MessageActionsBoxComponent, selector: "stream-message-actions-box", inputs: { messageInputTemplate: "messageInputTemplate", isOpen: "isOpen", isMine: "isMine", message: "message", enabledActions: "enabledActions" }, outputs: { displayedActionsCount: "displayedActionsCount", isEditing: "isEditing" }, viewQueries: [{ propertyName: "messageInput", first: true, predicate: MessageInputComponent, descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div\n data-testid=\"action-box\"\n class=\"str-chat__message-actions-box\"\n [class.str-chat__message-actions-box--open]=\"isOpen\"\n [class.str-chat__message-actions-box--mine]=\"isMine\"\n>\n <ul class=\"str-chat__message-actions-list\">\n <button\n data-testid=\"quote-action\"\n *ngIf=\"isQuoteVisible\"\n (click)=\"quoteClicked()\"\n >\n <li class=\"str-chat__message-actions-list-item\">\n {{ \"streamChat.Reply\" | translate }}\n </li>\n </button>\n <button\n data-testid=\"pin-action\"\n *ngIf=\"isPinVisible\"\n (click)=\"pinClicked()\"\n >\n <li class=\"str-chat__message-actions-list-item\">\n {{\n (message?.pinned ? \"streamChat.Unpin\" : \"streamChat.Pin\") | translate\n }}\n </li>\n </button>\n <button\n data-testid=\"flag-action\"\n *ngIf=\"isFlagVisible\"\n (click)=\"flagClicked()\"\n >\n <li class=\"str-chat__message-actions-list-item\">\n {{ \"streamChat.Flag\" | translate }}\n </li>\n </button>\n <button\n data-testid=\"mute-action\"\n *ngIf=\"isMuteVisible\"\n (click)=\"muteClicked()\"\n >\n <li class=\"str-chat__message-actions-list-item\">\n {{ \"streamChat.Mute\" | translate }}\n </li>\n </button>\n <button\n data-testid=\"edit-action\"\n *ngIf=\"isEditVisible\"\n (click)=\"editClicked()\"\n >\n <li class=\"str-chat__message-actions-list-item\">\n {{ \"streamChat.Edit Message\" | translate }}\n </li>\n </button>\n <button\n data-testid=\"delete-action\"\n *ngIf=\"isDeleteVisible\"\n (click)=\"deleteClicked()\"\n >\n <li class=\"str-chat__message-actions-list-item\">\n {{ \"streamChat.Delete\" | translate }}\n </li>\n </button>\n </ul>\n</div>\n\n<stream-modal\n [isOpen]=\"isEditModalOpen\"\n (isOpenChange)=\"\n isEditModalOpen = $event; isEditModalOpen ? '' : modalClosed()\n \"\n>\n <div class=\"str-chat__edit-message-form\" *ngIf=\"isEditModalOpen\">\n <ng-container *ngIf=\"messageInputTemplate; else defaultInput\">\n <ng-container\n *ngTemplateOutlet=\"\n messageInputTemplate;\n context: {\n message: message,\n messageUpdateHandler: modalClosed\n }\n \"\n ></ng-container>\n </ng-container>\n <ng-template #defaultInput>\n <stream-message-input\n [message]=\"message\"\n (messageUpdate)=\"modalClosed()\"\n ></stream-message-input>\n </ng-template>\n <stream-notification-list></stream-notification-list>\n <div\n class=\"\n str-chat__message-team-form-footer\n str-chat__message-team-form-footer-angular\n \"\n >\n <div class=\"str-chat__edit-message-form-options\">\n <button translate data-testid=\"cancel-button\" (click)=\"modalClosed()\">\n streamChat.Cancel\n </button>\n <button\n type=\"submit\"\n translate\n data-testid=\"send-button\"\n (click)=\"sendClicked()\"\n (keyup.enter)=\"sendClicked()\"\n >\n streamChat.Send\n </button>\n </div>\n </div>\n </div>\n</stream-modal>\n", components: [{ type: ModalComponent, selector: "stream-modal", inputs: ["isOpen"], outputs: ["isOpenChange"] }, { type: MessageInputComponent, selector: "stream-message-input", inputs: ["isFileUploadEnabled", "areMentionsEnabled", "mentionScope", "mentionAutocompleteItemTemplate", "commandAutocompleteItemTemplate", "mode", "acceptedFileTypes", "isMultipleFileUploadEnabled", "message"], outputs: ["messageUpdate"] }, { type: NotificationListComponent, selector: "stream-notification-list" }], directives: [{ type: i6.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i6.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet"] }, { type: i1.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }], pipes: { "translate": i1.TranslatePipe } });
1698
2017
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: MessageActionsBoxComponent, decorators: [{
1699
2018
  type: Component,
1700
2019
  args: [{
@@ -1727,10 +2046,11 @@ class ChannelComponent {
1727
2046
  this.subscriptions = [];
1728
2047
  this.isError$ = this.channelService.channels$.pipe(map(() => false), catchError(() => of(true)), startWith(false));
1729
2048
  this.isInitializing$ = this.channelService.channels$.pipe(map((channels) => !channels), catchError(() => of(false)));
2049
+ this.isActiveThread$ = this.channelService.activeParentMessageId$.pipe(map((id) => !!id));
1730
2050
  }
1731
2051
  }
1732
2052
  ChannelComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: ChannelComponent, deps: [{ token: ChannelService }], target: i0.ɵɵFactoryTarget.Component });
1733
- ChannelComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.5", type: ChannelComponent, selector: "stream-channel", ngImport: i0, template: "<div\n *ngIf=\"(isError$ | async) === false && (isInitializing$ | async) === false\"\n class=\"str-chat str-chat-channel messaging\"\n>\n <div class=\"str-chat__container\">\n <div class=\"str-chat__main-panel\">\n <ng-content></ng-content>\n </div>\n </div>\n</div>\n", directives: [{ type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }], pipes: { "async": i3.AsyncPipe } });
2053
+ ChannelComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.5", type: ChannelComponent, selector: "stream-channel", ngImport: i0, template: "<div\n *ngIf=\"(isError$ | async) === false && (isInitializing$ | async) === false\"\n class=\"str-chat str-chat-channel messaging\"\n>\n <div class=\"str-chat__container\">\n <div class=\"str-chat__main-panel\">\n <ng-content></ng-content>\n </div>\n <ng-content\n *ngIf=\"isActiveThread$ | async\"\n select='[name=\"thread\"]'\n ></ng-content>\n </div>\n</div>\n", directives: [{ type: i6.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }], pipes: { "async": i6.AsyncPipe } });
1734
2054
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: ChannelComponent, decorators: [{
1735
2055
  type: Component,
1736
2056
  args: [{
@@ -1830,7 +2150,7 @@ class ChannelHeaderComponent {
1830
2150
  }
1831
2151
  }
1832
2152
  ChannelHeaderComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: ChannelHeaderComponent, deps: [{ token: ChannelService }, { token: ChannelListToggleService }], target: i0.ɵɵFactoryTarget.Component });
1833
- ChannelHeaderComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.5", type: ChannelHeaderComponent, selector: "stream-channel-header", ngImport: i0, template: "<div class=\"str-chat__header-livestream\">\n <div\n class=\"str-chat__header-hamburger\"\n (click)=\"toggleMenu($event)\"\n (keyup.enter)=\"toggleMenu($event)\"\n >\n <span class=\"str-chat__header-hamburger--line\"></span>\n <span class=\"str-chat__header-hamburger--line\"></span>\n <span class=\"str-chat__header-hamburger--line\"></span>\n </div>\n <stream-avatar\n imageUrl=\"{{ activeChannel?.data?.image }}\"\n name=\"{{ activeChannel?.data?.name }}\"\n ></stream-avatar>\n <div class=\"str-chat__header-livestream-left\">\n <p data-testid=\"name\" class=\"str-chat__header-livestream-left--title\">\n {{ activeChannel?.data?.name }}\n </p>\n <p data-testid=\"info\" class=\"str-chat__header-livestream-left--members\">\n {{'streamChat.{{ memberCount }} members' | translate:memberCountParam}}\n {{canReceiveConnectEvents ? ('streamChat.{{ watcherCount }} online' |\n translate:watcherCountParam) : ''}}\n </p>\n </div>\n</div>\n", components: [{ type: AvatarComponent, selector: "stream-avatar", inputs: ["name", "imageUrl", "size"] }], pipes: { "translate": i10.TranslatePipe } });
2153
+ ChannelHeaderComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.5", type: ChannelHeaderComponent, selector: "stream-channel-header", ngImport: i0, template: "<div class=\"str-chat__header-livestream\">\n <div\n class=\"str-chat__header-hamburger\"\n (click)=\"toggleMenu($event)\"\n (keyup.enter)=\"toggleMenu($event)\"\n >\n <span class=\"str-chat__header-hamburger--line\"></span>\n <span class=\"str-chat__header-hamburger--line\"></span>\n <span class=\"str-chat__header-hamburger--line\"></span>\n </div>\n <stream-avatar\n imageUrl=\"{{ activeChannel?.data?.image }}\"\n name=\"{{ activeChannel?.data?.name }}\"\n ></stream-avatar>\n <div class=\"str-chat__header-livestream-left\">\n <p data-testid=\"name\" class=\"str-chat__header-livestream-left--title\">\n {{ activeChannel?.data?.name }}\n </p>\n <p data-testid=\"info\" class=\"str-chat__header-livestream-left--members\">\n {{'streamChat.{{ memberCount }} members' | translate:memberCountParam}}\n {{canReceiveConnectEvents ? ('streamChat.{{ watcherCount }} online' |\n translate:watcherCountParam) : ''}}\n </p>\n </div>\n</div>\n", components: [{ type: AvatarComponent, selector: "stream-avatar", inputs: ["name", "imageUrl", "size"] }], pipes: { "translate": i1.TranslatePipe } });
1834
2154
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: ChannelHeaderComponent, decorators: [{
1835
2155
  type: Component,
1836
2156
  args: [{
@@ -1915,7 +2235,7 @@ class ChannelPreviewComponent {
1915
2235
  }
1916
2236
  }
1917
2237
  ChannelPreviewComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: ChannelPreviewComponent, deps: [{ token: ChannelService }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component });
1918
- ChannelPreviewComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.5", type: ChannelPreviewComponent, selector: "stream-channel-preview", inputs: { channel: "channel" }, ngImport: i0, template: "<button\n class=\"str-chat__channel-preview-messenger\"\n [class.str-chat__channel-preview-messenger--active]=\"isActive\"\n [class.str-chat__channel-preview-messenger--unread]=\"isUnread\"\n (click)=\"setAsActiveChannel()\"\n data-testid=\"channel-preview-container\"\n>\n <div class=\"str-chat__channel-preview-messenger--left\">\n <stream-avatar\n imageUrl=\"{{ avatarImage }}\"\n name=\"{{ avatarName }}\"\n [size]=\"40\"\n ></stream-avatar>\n </div>\n <div class=\"str-chat__channel-preview-messenger--right\">\n <div class=\"str-chat__channel-preview-messenger--name\">\n <span data-testid=\"channel-preview-title\">{{ title }}</span>\n </div>\n <div\n data-testid=\"latest-message\"\n class=\"str-chat__channel-preview-messenger--last-message\"\n >\n {{ latestMessage | translate }}\n </div>\n </div>\n</button>\n", components: [{ type: AvatarComponent, selector: "stream-avatar", inputs: ["name", "imageUrl", "size"] }], pipes: { "translate": i10.TranslatePipe } });
2238
+ ChannelPreviewComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.5", type: ChannelPreviewComponent, selector: "stream-channel-preview", inputs: { channel: "channel" }, ngImport: i0, template: "<button\n class=\"str-chat__channel-preview-messenger\"\n [class.str-chat__channel-preview-messenger--active]=\"isActive\"\n [class.str-chat__channel-preview-messenger--unread]=\"isUnread\"\n (click)=\"setAsActiveChannel()\"\n data-testid=\"channel-preview-container\"\n>\n <div class=\"str-chat__channel-preview-messenger--left\">\n <stream-avatar\n imageUrl=\"{{ avatarImage }}\"\n name=\"{{ avatarName }}\"\n [size]=\"40\"\n ></stream-avatar>\n </div>\n <div class=\"str-chat__channel-preview-messenger--right\">\n <div class=\"str-chat__channel-preview-messenger--name\">\n <span data-testid=\"channel-preview-title\">{{ title }}</span>\n </div>\n <div\n data-testid=\"latest-message\"\n class=\"str-chat__channel-preview-messenger--last-message\"\n >\n {{ latestMessage | translate }}\n </div>\n </div>\n</button>\n", components: [{ type: AvatarComponent, selector: "stream-avatar", inputs: ["name", "imageUrl", "size"] }], pipes: { "translate": i1.TranslatePipe } });
1919
2239
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: ChannelPreviewComponent, decorators: [{
1920
2240
  type: Component,
1921
2241
  args: [{
@@ -1956,7 +2276,7 @@ class ChannelListComponent {
1956
2276
  }
1957
2277
  }
1958
2278
  ChannelListComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: ChannelListComponent, deps: [{ token: ChannelService }, { token: ChannelListToggleService }], target: i0.ɵɵFactoryTarget.Component });
1959
- ChannelListComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.5", type: ChannelListComponent, selector: "stream-channel-list", inputs: { customChannelPreviewTemplate: "customChannelPreviewTemplate" }, viewQueries: [{ propertyName: "container", first: true, predicate: ["container"], descendants: true }], ngImport: i0, template: "<div\n #container\n data-testid=\"channel-list-container\"\n class=\"str-chat str-chat-channel-list messaging\"\n [class.str-chat-channel-list--open]=\"(isOpen$ | async) === true\"\n>\n <div\n *ngIf=\"\n (isError$ | async) === false && (isInitializing$ | async) === false;\n else statusIndicator\n \"\n class=\"str-chat__channel-list-messenger\"\n >\n <div class=\"str-chat__channel-list-messenger__main\">\n <p\n data-testid=\"empty-channel-list-indicator\"\n *ngIf=\"!(channels$ | async)?.length\"\n >\n {{ \"streamChat.You have no channels currently\" | translate }}\n </p>\n <ng-container\n *ngFor=\"let channel of channels$ | async; trackBy: trackByChannelId\"\n >\n <ng-container\n *ngIf=\"customChannelPreviewTemplate; else defaultTemplate\"\n >\n <div (click)=\"channelSelected()\" (keyup.enter)=\"channelSelected()\">\n <ng-container\n *ngTemplateOutlet=\"\n customChannelPreviewTemplate;\n context: { channel: channel }\n \"\n ></ng-container>\n </div>\n </ng-container>\n <ng-template #defaultTemplate>\n <stream-channel-preview\n data-testclass=\"channel-preview\"\n [channel]=\"channel\"\n (click)=\"channelSelected()\"\n (keyup.enter)=\"channelSelected()\"\n ></stream-channel-preview>\n </ng-template>\n </ng-container>\n <div\n *ngIf=\"hasMoreChannels$ | async\"\n class=\"str-chat__load-more-button\"\n (click)=\"loadMoreChannels()\"\n (keyup.enter)=\"loadMoreChannels()\"\n data-testid=\"load-more\"\n >\n <button\n class=\"str-chat__load-more-button__button\"\n data-testid=\"load-more-button\"\n [disabled]=\"isLoadingMoreChannels\"\n >\n <span *ngIf=\"!isLoadingMoreChannels; else loadingIndicator\">{{\n \"Load more\" | translate\n }}</span>\n <ng-template #loadingIndicator\n ><stream-loading-indicator></stream-loading-indicator\n ></ng-template>\n </button>\n </div>\n </div>\n </div>\n</div>\n\n<ng-template #statusIndicator>\n <ng-container *ngIf=\"isError$ | async\">\n <ng-container *ngTemplateOutlet=\"chatDown\"></ng-container>\n </ng-container>\n <ng-container *ngIf=\"isInitializing$ | async\">\n <ng-container *ngTemplateOutlet=\"loadingChannels\"></ng-container>\n </ng-container>\n</ng-template>\n\n<ng-template #chatDown>\n <div data-testid=\"chatdown-container\" class=\"str-chat__down\">\n <ng-container *ngTemplateOutlet=\"loadingChannels\"></ng-container>\n <div class=\"str-chat__down-main\">\n <stream-icon icon=\"connection-error\"></stream-icon>\n <h1>{{ \"streamChat.Connection error\" | translate }}</h1>\n <h3>\n {{\n \"streamChat.Error connecting to chat, refresh the page to try again.\"\n | translate\n }}\n </h3>\n </div>\n </div>\n</ng-template>\n\n<ng-template #loadingChannels>\n <div data-testid=\"loading-indicator\" class=\"str-chat__loading-channels\">\n <ng-container *ngTemplateOutlet=\"loadingChannel\"></ng-container>\n <ng-container *ngTemplateOutlet=\"loadingChannel\"></ng-container>\n <ng-container *ngTemplateOutlet=\"loadingChannel\"></ng-container>\n </div>\n</ng-template>\n\n<ng-template #loadingChannel>\n <div class=\"str-chat__loading-channels-item\">\n <div class=\"str-chat__loading-channels-avatar\"></div>\n <div class=\"str-chat__loading-channels-meta\">\n <div class=\"str-chat__loading-channels-username\"></div>\n <div class=\"str-chat__loading-channels-status\"></div>\n </div>\n </div>\n</ng-template>\n", components: [{ type: ChannelPreviewComponent, selector: "stream-channel-preview", inputs: ["channel"] }, { type: LoadingIndicatorComponent, selector: "stream-loading-indicator", inputs: ["size", "color"] }, { type: IconComponent, selector: "stream-icon", inputs: ["icon", "size"] }], directives: [{ type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i3.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { type: i3.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet"] }], pipes: { "async": i3.AsyncPipe, "translate": i10.TranslatePipe } });
2279
+ ChannelListComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.5", type: ChannelListComponent, selector: "stream-channel-list", inputs: { customChannelPreviewTemplate: "customChannelPreviewTemplate" }, viewQueries: [{ propertyName: "container", first: true, predicate: ["container"], descendants: true }], ngImport: i0, template: "<div\n #container\n data-testid=\"channel-list-container\"\n class=\"str-chat str-chat-channel-list messaging\"\n [class.str-chat-channel-list--open]=\"(isOpen$ | async) === true\"\n>\n <div\n *ngIf=\"\n (isError$ | async) === false && (isInitializing$ | async) === false;\n else statusIndicator\n \"\n class=\"str-chat__channel-list-messenger\"\n >\n <div class=\"str-chat__channel-list-messenger__main\">\n <p\n data-testid=\"empty-channel-list-indicator\"\n *ngIf=\"!(channels$ | async)?.length\"\n >\n {{ \"streamChat.You have no channels currently\" | translate }}\n </p>\n <ng-container\n *ngFor=\"let channel of channels$ | async; trackBy: trackByChannelId\"\n >\n <ng-container\n *ngIf=\"customChannelPreviewTemplate; else defaultTemplate\"\n >\n <div (click)=\"channelSelected()\" (keyup.enter)=\"channelSelected()\">\n <ng-container\n *ngTemplateOutlet=\"\n customChannelPreviewTemplate;\n context: { channel: channel }\n \"\n ></ng-container>\n </div>\n </ng-container>\n <ng-template #defaultTemplate>\n <stream-channel-preview\n data-testclass=\"channel-preview\"\n [channel]=\"channel\"\n (click)=\"channelSelected()\"\n (keyup.enter)=\"channelSelected()\"\n ></stream-channel-preview>\n </ng-template>\n </ng-container>\n <div\n *ngIf=\"hasMoreChannels$ | async\"\n class=\"str-chat__load-more-button\"\n (click)=\"loadMoreChannels()\"\n (keyup.enter)=\"loadMoreChannels()\"\n data-testid=\"load-more\"\n >\n <button\n class=\"str-chat__load-more-button__button\"\n data-testid=\"load-more-button\"\n [disabled]=\"isLoadingMoreChannels\"\n >\n <span *ngIf=\"!isLoadingMoreChannels; else loadingIndicator\">{{\n \"Load more\" | translate\n }}</span>\n <ng-template #loadingIndicator\n ><stream-loading-indicator></stream-loading-indicator\n ></ng-template>\n </button>\n </div>\n </div>\n </div>\n</div>\n\n<ng-template #statusIndicator>\n <ng-container *ngIf=\"isError$ | async\">\n <ng-container *ngTemplateOutlet=\"chatDown\"></ng-container>\n </ng-container>\n <ng-container *ngIf=\"isInitializing$ | async\">\n <ng-container *ngTemplateOutlet=\"loadingChannels\"></ng-container>\n </ng-container>\n</ng-template>\n\n<ng-template #chatDown>\n <div data-testid=\"chatdown-container\" class=\"str-chat__down\">\n <ng-container *ngTemplateOutlet=\"loadingChannels\"></ng-container>\n <div class=\"str-chat__down-main\">\n <stream-icon icon=\"connection-error\"></stream-icon>\n <h1>{{ \"streamChat.Connection error\" | translate }}</h1>\n <h3>\n {{\n \"streamChat.Error connecting to chat, refresh the page to try again.\"\n | translate\n }}\n </h3>\n </div>\n </div>\n</ng-template>\n\n<ng-template #loadingChannels>\n <div data-testid=\"loading-indicator\" class=\"str-chat__loading-channels\">\n <ng-container *ngTemplateOutlet=\"loadingChannel\"></ng-container>\n <ng-container *ngTemplateOutlet=\"loadingChannel\"></ng-container>\n <ng-container *ngTemplateOutlet=\"loadingChannel\"></ng-container>\n </div>\n</ng-template>\n\n<ng-template #loadingChannel>\n <div class=\"str-chat__loading-channels-item\">\n <div class=\"str-chat__loading-channels-avatar\"></div>\n <div class=\"str-chat__loading-channels-meta\">\n <div class=\"str-chat__loading-channels-username\"></div>\n <div class=\"str-chat__loading-channels-status\"></div>\n </div>\n </div>\n</ng-template>\n", components: [{ type: ChannelPreviewComponent, selector: "stream-channel-preview", inputs: ["channel"] }, { type: LoadingIndicatorComponent, selector: "stream-loading-indicator", inputs: ["size", "color"] }, { type: IconComponent, selector: "stream-icon", inputs: ["icon", "size"] }], directives: [{ type: i6.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i6.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { type: i6.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet"] }], pipes: { "async": i6.AsyncPipe, "translate": i1.TranslatePipe } });
1960
2280
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: ChannelListComponent, decorators: [{
1961
2281
  type: Component,
1962
2282
  args: [{
@@ -2107,7 +2427,7 @@ class MessageReactionsComponent {
2107
2427
  }
2108
2428
  }
2109
2429
  MessageReactionsComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: MessageReactionsComponent, deps: [{ token: i0.ChangeDetectorRef }, { token: ChannelService }], target: i0.ɵɵFactoryTarget.Component });
2110
- MessageReactionsComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.5", type: MessageReactionsComponent, selector: "stream-message-reactions", inputs: { messageId: "messageId", messageReactionCounts: "messageReactionCounts", isSelectorOpen: "isSelectorOpen", latestReactions: "latestReactions", ownReactions: "ownReactions" }, outputs: { isSelectorOpenChange: "isSelectorOpenChange" }, viewQueries: [{ propertyName: "selectorContainer", first: true, predicate: ["selectorContainer"], descendants: true }, { propertyName: "selectorTooltip", first: true, predicate: ["selectorTooltip"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div\n *ngIf=\"existingReactions.length > 0 && !isSelectorOpen\"\n class=\"str-chat__reaction-list\"\n [class.str-chat__reaction-list--reverse]=\"true\"\n data-testid=\"reaction-list\"\n>\n <ul>\n <li\n *ngFor=\"\n let reactionType of existingReactions;\n trackBy: trackByMessageReaction\n \"\n data-testclass=\"emoji\"\n >\n <span class=\"emoji\">\n {{ getEmojiByReaction(reactionType) }}\n </span>\n &nbsp;\n </li>\n <li>\n <span\n data-testid=\"reactions-count\"\n class=\"str-chat__reaction-list--counter\"\n >{{ reactionsCount }}</span\n >\n </li>\n </ul>\n</div>\n\n<div\n #selectorContainer\n class=\"str-chat__reaction-selector\"\n *ngIf=\"isSelectorOpen\"\n data-testid=\"reaction-selector\"\n>\n <div\n *ngIf=\"tooltipText\"\n data-testid=\"tooltip\"\n #selectorTooltip\n class=\"str-chat__reaction-selector-tooltip\"\n [ngStyle]=\"{\n left: tooltipPositions?.tooltip + 'px',\n visibility: tooltipPositions ? 'visible' : 'hidden'\n }\"\n >\n <div\n class=\"arrow\"\n [ngStyle]=\"{ left: tooltipPositions?.arrow + 'px' }\"\n ></div>\n <span class=\"latest-user-username\">\n {{ tooltipText }}\n </span>\n </div>\n <ul class=\"str-chat__message-reactions-list\">\n <li\n class=\"str-chat__message-reactions-list-item\"\n *ngFor=\"\n let reactionType of reactionOptions;\n trackBy: trackByMessageReaction\n \"\n data-testclass=\"emoji-option\"\n (click)=\"react(reactionType)\"\n (keyup.enter)=\"react(reactionType)\"\n >\n <div\n *ngIf=\"getLatestUserByReaction(reactionType) as user\"\n class=\"latest-user\"\n (click)=\"hideTooltip()\"\n (keyup.enter)=\"hideTooltip()\"\n (mouseenter)=\"showTooltip($event, reactionType)\"\n (mouseleave)=\"hideTooltip()\"\n attr.data-testid=\"{{ reactionType }}-last-user\"\n >\n <stream-avatar\n attr.data-testid=\"{{ reactionType }}-avatar\"\n [imageUrl]=\"user.image\"\n [name]=\"user.name || user.id\"\n [size]=\"20\"\n ></stream-avatar>\n </div>\n <span class=\"emoji\" style=\"width: 20px; height: 20px; top: 10px\">\n {{ getEmojiByReaction(reactionType) }}\n </span>\n <span\n *ngIf=\"\n messageReactionCounts[reactionType] &&\n messageReactionCounts[reactionType]! > 0\n \"\n class=\"str-chat__message-reactions-list-item__count\"\n attr.data-testid=\"{{ reactionType }}-reaction-count\"\n >\n {{ messageReactionCounts[reactionType] }}\n </span>\n </li>\n </ul>\n</div>\n", styles: [".emoji {position: relative; display: inline-block; }"], components: [{ type: AvatarComponent, selector: "stream-avatar", inputs: ["name", "imageUrl", "size"] }], directives: [{ type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i3.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { type: i3.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }] });
2430
+ MessageReactionsComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.5", type: MessageReactionsComponent, selector: "stream-message-reactions", inputs: { messageId: "messageId", messageReactionCounts: "messageReactionCounts", isSelectorOpen: "isSelectorOpen", latestReactions: "latestReactions", ownReactions: "ownReactions" }, outputs: { isSelectorOpenChange: "isSelectorOpenChange" }, viewQueries: [{ propertyName: "selectorContainer", first: true, predicate: ["selectorContainer"], descendants: true }, { propertyName: "selectorTooltip", first: true, predicate: ["selectorTooltip"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div\n *ngIf=\"existingReactions.length > 0 && !isSelectorOpen\"\n class=\"str-chat__reaction-list\"\n [class.str-chat__reaction-list--reverse]=\"true\"\n data-testid=\"reaction-list\"\n>\n <ul>\n <li\n *ngFor=\"\n let reactionType of existingReactions;\n trackBy: trackByMessageReaction\n \"\n data-testclass=\"emoji\"\n >\n <span class=\"emoji\">\n {{ getEmojiByReaction(reactionType) }}\n </span>\n &nbsp;\n </li>\n <li>\n <span\n data-testid=\"reactions-count\"\n class=\"str-chat__reaction-list--counter\"\n >{{ reactionsCount }}</span\n >\n </li>\n </ul>\n</div>\n\n<div\n #selectorContainer\n class=\"str-chat__reaction-selector\"\n *ngIf=\"isSelectorOpen\"\n data-testid=\"reaction-selector\"\n>\n <div\n *ngIf=\"tooltipText\"\n data-testid=\"tooltip\"\n #selectorTooltip\n class=\"str-chat__reaction-selector-tooltip\"\n [ngStyle]=\"{\n left: tooltipPositions?.tooltip + 'px',\n visibility: tooltipPositions ? 'visible' : 'hidden'\n }\"\n >\n <div\n class=\"arrow\"\n [ngStyle]=\"{ left: tooltipPositions?.arrow + 'px' }\"\n ></div>\n <span class=\"latest-user-username\">\n {{ tooltipText }}\n </span>\n </div>\n <ul class=\"str-chat__message-reactions-list\">\n <li\n class=\"str-chat__message-reactions-list-item\"\n *ngFor=\"\n let reactionType of reactionOptions;\n trackBy: trackByMessageReaction\n \"\n data-testclass=\"emoji-option\"\n (click)=\"react(reactionType)\"\n (keyup.enter)=\"react(reactionType)\"\n >\n <div\n *ngIf=\"getLatestUserByReaction(reactionType) as user\"\n class=\"latest-user\"\n (click)=\"hideTooltip()\"\n (keyup.enter)=\"hideTooltip()\"\n (mouseenter)=\"showTooltip($event, reactionType)\"\n (mouseleave)=\"hideTooltip()\"\n attr.data-testid=\"{{ reactionType }}-last-user\"\n >\n <stream-avatar\n attr.data-testid=\"{{ reactionType }}-avatar\"\n [imageUrl]=\"user.image\"\n [name]=\"user.name || user.id\"\n [size]=\"20\"\n ></stream-avatar>\n </div>\n <span class=\"emoji\" style=\"width: 20px; height: 20px; top: 10px\">\n {{ getEmojiByReaction(reactionType) }}\n </span>\n <span\n *ngIf=\"\n messageReactionCounts[reactionType] &&\n messageReactionCounts[reactionType]! > 0\n \"\n class=\"str-chat__message-reactions-list-item__count\"\n attr.data-testid=\"{{ reactionType }}-reaction-count\"\n >\n {{ messageReactionCounts[reactionType] }}\n </span>\n </li>\n </ul>\n</div>\n", styles: [".emoji {position: relative; display: inline-block; }"], components: [{ type: AvatarComponent, selector: "stream-avatar", inputs: ["name", "imageUrl", "size"] }], directives: [{ type: i6.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i6.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { type: i6.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }] });
2111
2431
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: MessageReactionsComponent, decorators: [{
2112
2432
  type: Component,
2113
2433
  args: [{
@@ -2135,83 +2455,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.5", ngImpor
2135
2455
  args: ['selectorTooltip']
2136
2456
  }] } });
2137
2457
 
2138
- class ImageLoadService {
2139
- constructor() {
2140
- this.imageLoad$ = new Subject();
2141
- }
2142
- }
2143
- ImageLoadService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: ImageLoadService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2144
- ImageLoadService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: ImageLoadService, providedIn: 'root' });
2145
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: ImageLoadService, decorators: [{
2146
- type: Injectable,
2147
- args: [{
2148
- providedIn: 'root',
2149
- }]
2150
- }], ctorParameters: function () { return []; } });
2151
-
2152
- class AttachmentListComponent {
2153
- constructor(imageLoadService) {
2154
- this.imageLoadService = imageLoadService;
2155
- this.attachments = [];
2156
- this.orderedAttachments = [];
2157
- }
2158
- ngOnChanges() {
2159
- this.orderedAttachments = [
2160
- ...this.attachments.filter((a) => this.isImage(a)),
2161
- ...this.attachments.filter((a) => this.isFile(a)),
2162
- ...this.attachments.filter((a) => this.isCard(a)),
2163
- ];
2164
- }
2165
- trackById(index) {
2166
- return index;
2167
- }
2168
- isImage(attachment) {
2169
- return isImageAttachment(attachment);
2170
- }
2171
- isFile(attachment) {
2172
- return attachment.type === 'file';
2173
- }
2174
- isCard(attachment) {
2175
- return (!attachment.type ||
2176
- (attachment.type === 'image' && !this.isImage(attachment)));
2177
- }
2178
- imageLoaded() {
2179
- this.imageLoadService.imageLoad$.next();
2180
- }
2181
- hasFileSize(attachment) {
2182
- return (attachment.file_size && Number.isFinite(Number(attachment.file_size)));
2183
- }
2184
- getFileSize(attachment) {
2185
- return prettybytes(attachment.file_size);
2186
- }
2187
- trimUrl(url) {
2188
- if (url !== undefined && url !== null) {
2189
- const [trimmedUrl] = url
2190
- .replace(/^(?:https?:\/\/)?(?:www\.)?/i, '')
2191
- .split('/');
2192
- return trimmedUrl;
2193
- }
2194
- return null;
2195
- }
2196
- }
2197
- AttachmentListComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: AttachmentListComponent, deps: [{ token: ImageLoadService }], target: i0.ɵɵFactoryTarget.Component });
2198
- AttachmentListComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.5", type: AttachmentListComponent, selector: "stream-attachment-list", inputs: { attachments: "attachments" }, usesOnChanges: true, ngImport: i0, template: "<ng-container *ngFor=\"let attachment of orderedAttachments; trackBy: trackById\">\n <div\n data-testclass=\"attachment-container\"\n class=\"str-chat__message-attachment str-chat__message-attachment--{{\n attachment.type\n }}\"\n [class.str-chat__message-attachment--card]=\"isCard(attachment)\"\n [class.str-chat-angular__message-attachment-file-single]=\"\n isFile(attachment)\n \"\n >\n <img\n *ngIf=\"isImage(attachment)\"\n class=\"str-chat__message-attachment--img\"\n data-testclass=\"image\"\n [src]=\"attachment.img_url || attachment.thumb_url || attachment.image_url\"\n [alt]=\"attachment?.fallback\"\n (load)=\"imageLoaded()\"\n />\n <div\n *ngIf=\"isFile(attachment)\"\n class=\"\n str-chat__message-attachment-file--item\n str-chat-angular__message-attachment-file-single\n \"\n >\n <stream-icon icon=\"file\" [size]=\"30\"></stream-icon>\n <div class=\"str-chat__message-attachment-file--item-text\">\n <a\n data-testclass=\"file-link\"\n download\n href=\"{{ attachment.asset_url }}\"\n target=\"_blank\"\n >\n {{ attachment.title }}\n </a>\n <span data-testclass=\"size\" *ngIf=\"hasFileSize(attachment)\">{{\n getFileSize(attachment)\n }}</span>\n </div>\n </div>\n <div\n *ngIf=\"isCard(attachment)\"\n class=\"str-chat__message-attachment-card str-chat__message-attachment-card--{{\n attachment.type\n }}\"\n >\n <div\n *ngIf=\"attachment.image_url || attachment.thumb_url\"\n class=\"str-chat__message-attachment-card--header\"\n >\n <img\n data-testclass=\"card-img\"\n alt=\"{{ attachment.image_url || attachment.thumb_url }}\"\n src=\"{{ attachment.image_url || attachment.thumb_url }}\"\n />\n </div>\n <div class=\"str-chat__message-attachment-card--content\">\n <div class=\"str-chat__message-attachment-card--flex\">\n <div\n *ngIf=\"attachment.title\"\n data-testclass=\"card-title\"\n class=\"str-chat__message-attachment-card--title\"\n >\n {{ attachment.title }}\n </div>\n <div\n *ngIf=\"attachment.text\"\n class=\"str-chat__message-attachment-card--text\"\n data-testclass=\"card-text\"\n >\n {{ attachment.text }}\n </div>\n <a\n class=\"str-chat__message-attachment-card--url\"\n *ngIf=\"attachment.title_link || attachment.og_scrape_url\"\n data-testclass=\"url-link\"\n noopener\n noreferrer\n href=\"{{ attachment.title_link || attachment.og_scrape_url }}\"\n target=\"_blank\"\n >\n {{ trimUrl(attachment.title_link || attachment.og_scrape_url) }}\n </a>\n </div>\n </div>\n </div>\n </div>\n</ng-container>\n", components: [{ type: IconComponent, selector: "stream-icon", inputs: ["icon", "size"] }], directives: [{ type: i3.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
2199
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: AttachmentListComponent, decorators: [{
2200
- type: Component,
2201
- args: [{
2202
- selector: 'stream-attachment-list',
2203
- templateUrl: './attachment-list.component.html',
2204
- styles: [],
2205
- }]
2206
- }], ctorParameters: function () { return [{ type: ImageLoadService }]; }, propDecorators: { attachments: [{
2207
- type: Input
2208
- }] } });
2209
-
2210
2458
  class MessageComponent {
2211
2459
  constructor(chatClientService, channelService) {
2212
2460
  this.chatClientService = chatClientService;
2213
2461
  this.channelService = channelService;
2214
2462
  this.enabledMessageActions = [];
2463
+ this.mode = 'main';
2215
2464
  this.isActionBoxOpen = false;
2216
2465
  this.isReactionSelectorOpen = false;
2217
2466
  this.isPressedOnMobile = false;
@@ -2220,46 +2469,8 @@ class MessageComponent {
2220
2469
  this.user = this.chatClientService.chatClient.user;
2221
2470
  }
2222
2471
  ngOnChanges(changes) {
2223
- var _a, _b;
2224
2472
  if (changes.message) {
2225
- let content = ((_a = this.message) === null || _a === void 0 ? void 0 : _a.html) || ((_b = this.message) === null || _b === void 0 ? void 0 : _b.text);
2226
- if (!content) {
2227
- this.messageTextParts = [];
2228
- }
2229
- else {
2230
- // Backend will wrap HTML content with <p></p>\n
2231
- if (content.startsWith('<p>')) {
2232
- content = content.replace('<p>', '');
2233
- }
2234
- if (content.endsWith('</p>\n')) {
2235
- content = content.replace('</p>\n', '');
2236
- }
2237
- if (!this.message.mentioned_users ||
2238
- this.message.mentioned_users.length === 0) {
2239
- this.messageTextParts = [{ content, type: 'text' }];
2240
- }
2241
- else {
2242
- this.messageTextParts = [];
2243
- let text = content;
2244
- this.message.mentioned_users.forEach((user) => {
2245
- const mention = `@${user.name || user.id}`;
2246
- const precedingText = text.substring(0, text.indexOf(mention));
2247
- this.messageTextParts.push({
2248
- content: precedingText,
2249
- type: 'text',
2250
- });
2251
- this.messageTextParts.push({
2252
- content: mention,
2253
- type: 'mention',
2254
- user,
2255
- });
2256
- text = text.replace(precedingText + mention, '');
2257
- });
2258
- if (text) {
2259
- this.messageTextParts.push({ content: text, type: 'text' });
2260
- }
2261
- }
2262
- }
2473
+ this.createMessageParts();
2263
2474
  }
2264
2475
  }
2265
2476
  get isSentByCurrentUser() {
@@ -2301,7 +2512,8 @@ class MessageComponent {
2301
2512
  this.message.type === 'system' ||
2302
2513
  this.message.type === 'ephemeral' ||
2303
2514
  this.message.status === 'failed' ||
2304
- this.message.status === 'sending');
2515
+ this.message.status === 'sending' ||
2516
+ (this.mode === 'thread' && !this.message.parent_id));
2305
2517
  }
2306
2518
  get hasAttachment() {
2307
2519
  var _a;
@@ -2312,6 +2524,21 @@ class MessageComponent {
2312
2524
  return (!!((_a = this.message) === null || _a === void 0 ? void 0 : _a.reaction_counts) &&
2313
2525
  Object.keys(this.message.reaction_counts).length > 0);
2314
2526
  }
2527
+ get replyCountParam() {
2528
+ var _a;
2529
+ return { replyCount: (_a = this.message) === null || _a === void 0 ? void 0 : _a.reply_count };
2530
+ }
2531
+ get canDisplayReadStatus() {
2532
+ return (this.canReceiveReadEvents !== false &&
2533
+ this.enabledMessageActions.indexOf('read-events') !== -1);
2534
+ }
2535
+ get quotedMessageAttachments() {
2536
+ var _a, _b;
2537
+ const originalAttachments = (_b = (_a = this.message) === null || _a === void 0 ? void 0 : _a.quoted_message) === null || _b === void 0 ? void 0 : _b.attachments;
2538
+ return originalAttachments && originalAttachments.length
2539
+ ? [originalAttachments[0]]
2540
+ : [];
2541
+ }
2315
2542
  resendMessage() {
2316
2543
  void this.channelService.resendMessage(this.message);
2317
2544
  }
@@ -2333,9 +2560,53 @@ class MessageComponent {
2333
2560
  };
2334
2561
  window.addEventListener('click', eventHandler);
2335
2562
  }
2563
+ setAsActiveParentMessage() {
2564
+ void this.channelService.setAsActiveParentMessage(this.message);
2565
+ }
2566
+ createMessageParts() {
2567
+ var _a, _b;
2568
+ let content = ((_a = this.message) === null || _a === void 0 ? void 0 : _a.html) || ((_b = this.message) === null || _b === void 0 ? void 0 : _b.text);
2569
+ if (!content) {
2570
+ this.messageTextParts = [];
2571
+ }
2572
+ else {
2573
+ // Backend will wrap HTML content with <p></p>\n
2574
+ if (content.startsWith('<p>')) {
2575
+ content = content.replace('<p>', '');
2576
+ }
2577
+ if (content.endsWith('</p>\n')) {
2578
+ content = content.replace('</p>\n', '');
2579
+ }
2580
+ if (!this.message.mentioned_users ||
2581
+ this.message.mentioned_users.length === 0) {
2582
+ this.messageTextParts = [{ content, type: 'text' }];
2583
+ }
2584
+ else {
2585
+ this.messageTextParts = [];
2586
+ let text = content;
2587
+ this.message.mentioned_users.forEach((user) => {
2588
+ const mention = `@${user.name || user.id}`;
2589
+ const precedingText = text.substring(0, text.indexOf(mention));
2590
+ this.messageTextParts.push({
2591
+ content: precedingText,
2592
+ type: 'text',
2593
+ });
2594
+ this.messageTextParts.push({
2595
+ content: mention,
2596
+ type: 'mention',
2597
+ user,
2598
+ });
2599
+ text = text.replace(precedingText + mention, '');
2600
+ });
2601
+ if (text) {
2602
+ this.messageTextParts.push({ content: text, type: 'text' });
2603
+ }
2604
+ }
2605
+ }
2606
+ }
2336
2607
  }
2337
2608
  MessageComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: MessageComponent, deps: [{ token: ChatClientService }, { token: ChannelService }], target: i0.ɵɵFactoryTarget.Component });
2338
- MessageComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.5", type: MessageComponent, selector: "stream-message", inputs: { messageInputTemplate: "messageInputTemplate", mentionTemplate: "mentionTemplate", message: "message", enabledMessageActions: "enabledMessageActions", areReactionsEnabled: "areReactionsEnabled", canReactToMessage: "canReactToMessage", isLastSentMessage: "isLastSentMessage", canReceiveReadEvents: "canReceiveReadEvents" }, viewQueries: [{ propertyName: "container", first: true, predicate: ["container"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div\n #container\n class=\"str-chat__message-simple str-chat__message str-chat__message--{{\n message?.type\n }} str-chat__message--{{ message?.status }} {{\n message?.text ? 'str-chat__message--has-text' : 'has-no-text'\n }}\"\n [class.str-chat__message--me]=\"isSentByCurrentUser\"\n [class.str-chat__message-simple--me]=\"isSentByCurrentUser\"\n [class.mobile-press]=\"isPressedOnMobile\"\n [class.str-chat__message--has-attachment]=\"hasAttachment\"\n [class.str-chat__message--with-reactions]=\"\n areReactionsEnabled && hasReactions\n \"\n data-testid=\"message-container\"\n>\n <ng-container *ngIf=\"!message?.deleted_at; else deletedMessage\">\n <ng-container *ngIf=\"message?.type !== 'system'; else systemMessage\">\n <ng-container\n *ngIf=\"\n isSentByCurrentUser &&\n ((isLastSentMessage && message?.status === 'received') ||\n message?.status === 'sending')\n \"\n >\n <ng-container *ngIf=\"message?.status === 'sending'; else sentStatus\">\n <ng-container *ngTemplateOutlet=\"sendingStatus\"></ng-container>\n </ng-container>\n <ng-template #sentStatus>\n <ng-container\n *ngIf=\"\n isMessageDeliveredAndRead && canReceiveReadEvents;\n else deliveredStatus\n \"\n >\n <ng-container *ngTemplateOutlet=\"readStatus\"></ng-container>\n </ng-container>\n </ng-template>\n </ng-container>\n <stream-avatar\n data-testid=\"avatar\"\n class=\"str-chat-angular__avatar-host\"\n [imageUrl]=\"message?.user?.image\"\n [name]=\"message?.user?.name || message?.user?.id\"\n ></stream-avatar>\n <div class=\"str-chat__message-inner\">\n <div\n class=\"str-chat__message-simple__actions\"\n data-testid=\"message-options\"\n *ngIf=\"areOptionsVisible\"\n >\n <div\n data-testid=\"message-actions-container\"\n class=\"\n str-chat__message-simple__actions__action\n str-chat__message-simple__actions__action--options\n \"\n [class.str-chat-angular__message-simple__actions__action--options--editing]=\"\n isEditing\n \"\n >\n <stream-message-actions-box\n [isOpen]=\"isActionBoxOpen\"\n [isMine]=\"isSentByCurrentUser\"\n [enabledActions]=\"enabledMessageActions\"\n [message]=\"message\"\n (displayedActionsCount)=\"visibleMessageActionsCount = $event\"\n (isEditing)=\"isEditing = $event; isActionBoxOpen = !isEditing\"\n [messageInputTemplate]=\"messageInputTemplate\"\n ></stream-message-actions-box>\n <stream-icon\n *ngIf=\"visibleMessageActionsCount > 0\"\n data-testid=\"action-icon\"\n icon=\"action-icon\"\n (keyup.enter)=\"isActionBoxOpen = !isActionBoxOpen\"\n (click)=\"isActionBoxOpen = !isActionBoxOpen\"\n ></stream-icon>\n </div>\n <div\n *ngIf=\"areReactionsEnabled && canReactToMessage\"\n class=\"\n str-chat__message-simple__actions__action\n str-chat__message-simple__actions__action--reactions\n \"\n data-testid=\"reaction-icon\"\n (click)=\"isReactionSelectorOpen = !isReactionSelectorOpen\"\n (keyup.enter)=\"isReactionSelectorOpen = !isReactionSelectorOpen\"\n >\n <stream-icon icon=\"reaction-icon\"></stream-icon>\n </div>\n </div>\n <stream-message-reactions\n *ngIf=\"areReactionsEnabled\"\n [messageReactionCounts]=\"message?.reaction_counts || {}\"\n [latestReactions]=\"message?.latest_reactions || []\"\n [(isSelectorOpen)]=\"isReactionSelectorOpen\"\n [messageId]=\"message?.id\"\n [ownReactions]=\"message?.own_reactions || []\"\n ></stream-message-reactions>\n <stream-attachment-list\n *ngIf=\"hasAttachment\"\n [attachments]=\"message!.attachments!\"\n ></stream-attachment-list>\n <div class=\"str-chat__message-text\" *ngIf=\"message?.text\">\n <div\n data-testid=\"inner-message\"\n class=\"\n str-chat__message-text-inner str-chat__message-simple-text-inner\n \"\n [class.str-chat__message-light-text-inner--has-attachment]=\"\n hasAttachment\n \"\n (click)=\"\n message?.status === 'failed' && message?.errorStatusCode !== 403\n ? resendMessage()\n : undefined\n \"\n (keyup.enter)=\"\n message?.status === 'failed' && message?.errorStatusCode !== 403\n ? resendMessage()\n : undefined\n \"\n >\n <div\n data-testid=\"client-error-message\"\n *ngIf=\"message?.type === 'error'\"\n class=\"str-chat__simple-message--error-message\"\n >\n {{ \"streamChat.Error \u00B7 Unsent\" | translate }}\n </div>\n <div\n data-testid=\"error-message\"\n *ngIf=\"message?.status === 'failed'\"\n class=\"str-chat__simple-message--error-message\"\n >\n {{\n (message?.errorStatusCode === 403\n ? \"streamChat.Message Failed \u00B7 Unauthorized\"\n : \"streamChat.Message Failed \u00B7 Click to try again\"\n ) | translate\n }}\n </div>\n <div\n (click)=\"textClicked()\"\n (keyup.enter)=\"textClicked()\"\n data-testid=\"text\"\n >\n <p>\n <!-- eslint-disable-next-line @angular-eslint/template/use-track-by-function -->\n <ng-container *ngFor=\"let part of messageTextParts\">\n <span\n *ngIf=\"part.type === 'text'; else mention\"\n [innerHTML]=\"part.content\"\n ></span>\n <ng-template #mention>\n <ng-container *ngIf=\"mentionTemplate; else defaultMention\">\n <ng-container\n *ngTemplateOutlet=\"\n mentionTemplate;\n context: { user: part.user! }\n \"\n ></ng-container>\n </ng-container>\n <ng-template #defaultMention>\n <b>{{ part.content }}</b>\n </ng-template>\n </ng-template>\n </ng-container>\n </p>\n </div>\n </div>\n </div>\n <div class=\"str-chat__message-data str-chat__message-simple-data\">\n <span\n data-testid=\"sender\"\n *ngIf=\"!isSentByCurrentUser\"\n class=\"str-chat__message-simple-name\"\n >\n {{ message?.user?.name ? message?.user?.name : message?.user?.id }}\n </span>\n <span data-testid=\"date\" class=\"str-chat__message-simple-timestamp\">\n {{ parsedDate }}\n </span>\n </div>\n </div>\n </ng-container>\n </ng-container>\n</div>\n\n<ng-template #sendingStatus>\n <span\n class=\"\n str-chat__message-simple-status str-chat__message-simple-status-angular\n \"\n data-testid=\"sending-indicator\"\n >\n <div class=\"str-chat__tooltip\">\n {{ \"streamChat.Sending...\" | translate }}\n </div>\n <stream-loading-indicator\n data-testid=\"loading-indicator\"\n ></stream-loading-indicator>\n </span>\n</ng-template>\n<ng-template #readStatus>\n <span\n class=\"\n str-chat__message-simple-status str-chat__message-simple-status-angular\n \"\n data-testid=\"read-indicator\"\n >\n <div class=\"str-chat__tooltip\" data-testid=\"read-by-tooltip\">\n {{ readByText }}\n </div>\n <stream-avatar\n class=\"str-chat-angular__avatar-host\"\n data-test-id=\"last-read-user-avatar\"\n [size]=\"15\"\n [imageUrl]=\"lastReadUser?.image\"\n [name]=\"lastReadUser?.name || lastReadUser?.id\"\n ></stream-avatar>\n <span\n data-test-id=\"read-by-length\"\n *ngIf=\"isReadByMultipleUsers\"\n class=\"str-chat__message-simple-status-number\"\n >\n {{ (message?.readBy)!.length }}\n </span>\n </span>\n</ng-template>\n<ng-template #deliveredStatus>\n <span\n class=\"\n str-chat__message-simple-status str-chat__message-simple-status-angular\n \"\n data-testid=\"delivered-indicator\"\n >\n <div class=\"str-chat__tooltip\">\n {{ \"streamChat.Delivered\" | translate }}\n </div>\n <stream-icon\n data-testid=\"delivered-icon\"\n icon=\"delivered-icon\"\n ></stream-icon>\n </span>\n</ng-template>\n\n<ng-template #deletedMessage>\n <div data-testid=\"message-deleted-component\">\n <div class=\"str-chat__message--deleted-inner\" translate>\n streamChat.This message was deleted...\n </div>\n </div>\n</ng-template>\n\n<ng-template #systemMessage>\n <div data-testid=\"system-message\" class=\"str-chat__message--system\">\n <div class=\"str-chat__message--system__text\">\n <div class=\"str-chat__message--system__line\"></div>\n <p>{{ message?.text }}</p>\n <div class=\"str-chat__message--system__line\"></div>\n </div>\n <div class=\"str-chat__message--system__date\">\n {{ parsedDate }}\n </div>\n </div>\n</ng-template>\n", components: [{ type: AvatarComponent, selector: "stream-avatar", inputs: ["name", "imageUrl", "size"] }, { type: MessageActionsBoxComponent, selector: "stream-message-actions-box", inputs: ["messageInputTemplate", "isOpen", "isMine", "message", "enabledActions"], outputs: ["displayedActionsCount", "isEditing"] }, { type: IconComponent, selector: "stream-icon", inputs: ["icon", "size"] }, { type: MessageReactionsComponent, selector: "stream-message-reactions", inputs: ["messageId", "messageReactionCounts", "isSelectorOpen", "latestReactions", "ownReactions"], outputs: ["isSelectorOpenChange"] }, { type: AttachmentListComponent, selector: "stream-attachment-list", inputs: ["attachments"] }, { type: LoadingIndicatorComponent, selector: "stream-loading-indicator", inputs: ["size", "color"] }], directives: [{ type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i3.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet"] }, { type: i3.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { type: i10.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }], pipes: { "translate": i10.TranslatePipe } });
2609
+ MessageComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.5", type: MessageComponent, selector: "stream-message", inputs: { messageInputTemplate: "messageInputTemplate", mentionTemplate: "mentionTemplate", message: "message", enabledMessageActions: "enabledMessageActions", areReactionsEnabled: "areReactionsEnabled", canReactToMessage: "canReactToMessage", isLastSentMessage: "isLastSentMessage", canReceiveReadEvents: "canReceiveReadEvents", mode: "mode" }, viewQueries: [{ propertyName: "container", first: true, predicate: ["container"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div\n #container\n class=\"str-chat__message-simple str-chat__message str-chat__message--{{\n message?.type\n }} str-chat__message--{{ message?.status }} {{\n message?.text ? 'str-chat__message--has-text' : 'has-no-text'\n }}\"\n [class.str-chat__message--me]=\"isSentByCurrentUser\"\n [class.str-chat__message-simple--me]=\"isSentByCurrentUser\"\n [class.mobile-press]=\"isPressedOnMobile\"\n [class.str-chat__message--has-attachment]=\"hasAttachment\"\n [class.str-chat__message--with-reactions]=\"\n areReactionsEnabled !== false && hasReactions\n \"\n data-testid=\"message-container\"\n>\n <ng-container *ngIf=\"!message?.deleted_at; else deletedMessage\">\n <ng-container *ngIf=\"message?.type !== 'system'; else systemMessage\">\n <ng-container\n *ngIf=\"\n isSentByCurrentUser &&\n ((isLastSentMessage && message?.status === 'received') ||\n message?.status === 'sending')\n \"\n >\n <ng-container *ngIf=\"message?.status === 'sending'; else sentStatus\">\n <ng-container *ngTemplateOutlet=\"sendingStatus\"></ng-container>\n </ng-container>\n <ng-template #sentStatus>\n <ng-container\n *ngIf=\"\n mode === 'main' &&\n isMessageDeliveredAndRead &&\n canDisplayReadStatus;\n else deliveredStatus\n \"\n >\n <ng-container *ngTemplateOutlet=\"readStatus\"></ng-container>\n </ng-container>\n </ng-template>\n </ng-container>\n <stream-avatar\n data-testid=\"avatar\"\n class=\"str-chat-angular__avatar-host\"\n [imageUrl]=\"message?.user?.image\"\n [name]=\"message?.user?.name || message?.user?.id\"\n ></stream-avatar>\n <div class=\"str-chat__message-inner\">\n <div\n class=\"str-chat__message-simple__actions\"\n data-testid=\"message-options\"\n *ngIf=\"areOptionsVisible\"\n >\n <div\n data-testid=\"message-actions-container\"\n class=\"\n str-chat__message-simple__actions__action\n str-chat__message-simple__actions__action--options\n \"\n [class.str-chat-angular__message-simple__actions__action--options--editing]=\"\n isEditing\n \"\n >\n <stream-message-actions-box\n [isOpen]=\"isActionBoxOpen\"\n [isMine]=\"isSentByCurrentUser\"\n [enabledActions]=\"enabledMessageActions\"\n [message]=\"message\"\n (displayedActionsCount)=\"visibleMessageActionsCount = $event\"\n (isEditing)=\"isEditing = $event; isActionBoxOpen = !isEditing\"\n [messageInputTemplate]=\"messageInputTemplate\"\n ></stream-message-actions-box>\n <stream-icon\n *ngIf=\"visibleMessageActionsCount > 0\"\n data-testid=\"action-icon\"\n icon=\"action-icon\"\n (keyup.enter)=\"isActionBoxOpen = !isActionBoxOpen\"\n (click)=\"isActionBoxOpen = !isActionBoxOpen\"\n ></stream-icon>\n </div>\n <!-- eslint-disable @angular-eslint/template/conditional-complexity -->\n <div\n *ngIf=\"\n enabledMessageActions.indexOf('send-reply') !== -1 &&\n mode === 'main'\n \"\n class=\"\n str-chat__message-simple__actions__action\n str-chat__message-simple__actions__action--thread\n \"\n data-testid=\"reply-in-thread\"\n (click)=\"setAsActiveParentMessage()\"\n (keyup.enter)=\"setAsActiveParentMessage()\"\n >\n <stream-icon icon=\"reply-in-thread\"></stream-icon>\n </div>\n <div\n *ngIf=\"\n areReactionsEnabled !== false &&\n canReactToMessage !== false &&\n enabledMessageActions.indexOf('send-reaction') !== -1\n \"\n class=\"\n str-chat__message-simple__actions__action\n str-chat__message-simple__actions__action--reactions\n \"\n data-testid=\"reaction-icon\"\n (click)=\"isReactionSelectorOpen = !isReactionSelectorOpen\"\n (keyup.enter)=\"isReactionSelectorOpen = !isReactionSelectorOpen\"\n >\n <stream-icon icon=\"reaction-icon\"></stream-icon>\n </div>\n </div>\n <!-- eslint-enable @angular-eslint/template/conditional-complexity -->\n <stream-message-reactions\n *ngIf=\"areReactionsEnabled !== false\"\n [messageReactionCounts]=\"message?.reaction_counts || {}\"\n [latestReactions]=\"message?.latest_reactions || []\"\n [(isSelectorOpen)]=\"isReactionSelectorOpen\"\n [messageId]=\"message?.id\"\n [ownReactions]=\"message?.own_reactions || []\"\n ></stream-message-reactions>\n <stream-attachment-list\n *ngIf=\"hasAttachment && !message?.quoted_message\"\n [attachments]=\"message!.attachments!\"\n [messageId]=\"message!.id\"\n ></stream-attachment-list>\n <div class=\"str-chat__message-text\" *ngIf=\"message?.text\">\n <div\n data-testid=\"inner-message\"\n class=\"\n str-chat__message-text-inner str-chat__message-simple-text-inner\n \"\n [class.str-chat__message-light-text-inner--has-attachment]=\"\n hasAttachment\n \"\n (click)=\"\n message?.status === 'failed' && message?.errorStatusCode !== 403\n ? resendMessage()\n : undefined\n \"\n (keyup.enter)=\"\n message?.status === 'failed' && message?.errorStatusCode !== 403\n ? resendMessage()\n : undefined\n \"\n >\n <ng-container *ngTemplateOutlet=\"quotedMessage\"></ng-container>\n <stream-attachment-list\n *ngIf=\"hasAttachment && message?.quoted_message\"\n [attachments]=\"message!.attachments!\"\n [messageId]=\"message!.id\"\n ></stream-attachment-list>\n <div\n data-testid=\"client-error-message\"\n *ngIf=\"message?.type === 'error'\"\n class=\"str-chat__simple-message--error-message\"\n >\n {{ \"streamChat.Error \u00B7 Unsent\" | translate }}\n </div>\n <div\n data-testid=\"error-message\"\n *ngIf=\"message?.status === 'failed'\"\n class=\"str-chat__simple-message--error-message\"\n >\n {{\n (message?.errorStatusCode === 403\n ? \"streamChat.Message Failed \u00B7 Unauthorized\"\n : \"streamChat.Message Failed \u00B7 Click to try again\"\n ) | translate\n }}\n </div>\n <div\n (click)=\"textClicked()\"\n (keyup.enter)=\"textClicked()\"\n data-testid=\"text\"\n >\n <p>\n <!-- eslint-disable-next-line @angular-eslint/template/use-track-by-function -->\n <ng-container *ngFor=\"let part of messageTextParts\">\n <span\n *ngIf=\"part.type === 'text'; else mention\"\n [innerHTML]=\"part.content\"\n ></span>\n <ng-template #mention>\n <ng-container *ngIf=\"mentionTemplate; else defaultMention\">\n <ng-container\n *ngTemplateOutlet=\"\n mentionTemplate;\n context: { user: part.user! }\n \"\n ></ng-container>\n </ng-container>\n <ng-template #defaultMention>\n <b>{{ part.content }}</b>\n </ng-template>\n </ng-template>\n </ng-container>\n </p>\n </div>\n </div>\n </div>\n <div class=\"str-chat__message-simple-reply-button\">\n <button\n *ngIf=\"\n !!message?.reply_count &&\n mode !== 'thread' &&\n enabledMessageActions.indexOf('send-reply') !== -1\n \"\n class=\"str-chat__message-replies-count-button\"\n data-testid=\"reply-count-button\"\n (click)=\"setAsActiveParentMessage()\"\n >\n <stream-icon icon=\"reply\"></stream-icon>\n {{message?.reply_count === 1 ? ('streamChat.1 reply' | translate) : ('streamChat.{{ replyCount }}\n replies' | translate:replyCountParam)}}\n </button>\n </div>\n <div class=\"str-chat__message-data str-chat__message-simple-data\">\n <span\n data-testid=\"sender\"\n *ngIf=\"!isSentByCurrentUser\"\n class=\"str-chat__message-simple-name\"\n >\n {{ message?.user?.name ? message?.user?.name : message?.user?.id }}\n </span>\n <span data-testid=\"date\" class=\"str-chat__message-simple-timestamp\">\n {{ parsedDate }}\n </span>\n </div>\n </div>\n </ng-container>\n </ng-container>\n</div>\n\n<ng-template #sendingStatus>\n <span\n class=\"\n str-chat__message-simple-status str-chat__message-simple-status-angular\n \"\n data-testid=\"sending-indicator\"\n >\n <div class=\"str-chat__tooltip\">\n {{ \"streamChat.Sending...\" | translate }}\n </div>\n <stream-loading-indicator\n data-testid=\"loading-indicator\"\n ></stream-loading-indicator>\n </span>\n</ng-template>\n<ng-template #readStatus>\n <span\n class=\"\n str-chat__message-simple-status str-chat__message-simple-status-angular\n \"\n data-testid=\"read-indicator\"\n >\n <div class=\"str-chat__tooltip\" data-testid=\"read-by-tooltip\">\n {{ readByText }}\n </div>\n <stream-avatar\n class=\"str-chat-angular__avatar-host\"\n data-test-id=\"last-read-user-avatar\"\n [size]=\"15\"\n [imageUrl]=\"lastReadUser?.image\"\n [name]=\"lastReadUser?.name || lastReadUser?.id\"\n ></stream-avatar>\n <span\n data-test-id=\"read-by-length\"\n *ngIf=\"isReadByMultipleUsers\"\n class=\"str-chat__message-simple-status-number\"\n >\n {{ (message?.readBy)!.length }}\n </span>\n </span>\n</ng-template>\n<ng-template #deliveredStatus>\n <span\n *ngIf=\"mode === 'main'\"\n class=\"\n str-chat__message-simple-status str-chat__message-simple-status-angular\n \"\n data-testid=\"delivered-indicator\"\n >\n <div class=\"str-chat__tooltip\">\n {{ \"streamChat.Delivered\" | translate }}\n </div>\n <stream-icon\n data-testid=\"delivered-icon\"\n icon=\"delivered-icon\"\n ></stream-icon>\n </span>\n</ng-template>\n\n<ng-template #deletedMessage>\n <div data-testid=\"message-deleted-component\">\n <div class=\"str-chat__message--deleted-inner\" translate>\n streamChat.This message was deleted...\n </div>\n </div>\n</ng-template>\n\n<ng-template #systemMessage>\n <div data-testid=\"system-message\" class=\"str-chat__message--system\">\n <div class=\"str-chat__message--system__text\">\n <div class=\"str-chat__message--system__line\"></div>\n <p>{{ message?.text }}</p>\n <div class=\"str-chat__message--system__line\"></div>\n </div>\n <div class=\"str-chat__message--system__date\">\n {{ parsedDate }}\n </div>\n </div>\n</ng-template>\n\n<ng-template #quotedMessage>\n <div\n *ngIf=\"message?.quoted_message\"\n class=\"quoted-message\"\n data-testid=\"quoted-message-container\"\n [class.mine]=\"isSentByCurrentUser\"\n >\n <stream-avatar\n data-testid=\"qouted-message-avatar\"\n class=\"str-chat-angular__avatar-host\"\n [imageUrl]=\"message?.quoted_message?.user?.image\"\n [name]=\"\n message?.quoted_message?.user?.name || message?.quoted_message?.user?.id\n \"\n [size]=\"20\"\n ></stream-avatar>\n <div class=\"quoted-message-inner\">\n <stream-attachment-list\n *ngIf=\"\n message?.quoted_message?.attachments &&\n message?.quoted_message?.attachments?.length\n \"\n [attachments]=\"quotedMessageAttachments\"\n [messageId]=\"message?.quoted_message?.id\"\n ></stream-attachment-list>\n <div\n data-testid=\"quoted-message-text\"\n [innerHTML]=\"\n message?.quoted_message?.html || message?.quoted_message?.text\n \"\n ></div>\n </div>\n </div>\n</ng-template>\n", components: [{ type: AvatarComponent, selector: "stream-avatar", inputs: ["name", "imageUrl", "size"] }, { type: MessageActionsBoxComponent, selector: "stream-message-actions-box", inputs: ["messageInputTemplate", "isOpen", "isMine", "message", "enabledActions"], outputs: ["displayedActionsCount", "isEditing"] }, { type: IconComponent, selector: "stream-icon", inputs: ["icon", "size"] }, { type: MessageReactionsComponent, selector: "stream-message-reactions", inputs: ["messageId", "messageReactionCounts", "isSelectorOpen", "latestReactions", "ownReactions"], outputs: ["isSelectorOpenChange"] }, { type: AttachmentListComponent, selector: "stream-attachment-list", inputs: ["messageId", "attachments"] }, { type: LoadingIndicatorComponent, selector: "stream-loading-indicator", inputs: ["size", "color"] }], directives: [{ type: i6.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i6.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet"] }, { type: i6.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { type: i1.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }], pipes: { "translate": i1.TranslatePipe } });
2339
2610
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: MessageComponent, decorators: [{
2340
2611
  type: Component,
2341
2612
  args: [{
@@ -2359,6 +2630,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.5", ngImpor
2359
2630
  type: Input
2360
2631
  }], canReceiveReadEvents: [{
2361
2632
  type: Input
2633
+ }], mode: [{
2634
+ type: Input
2362
2635
  }], container: [{
2363
2636
  type: ViewChild,
2364
2637
  args: ['container']
@@ -2382,7 +2655,7 @@ class TextareaComponent {
2382
2655
  }
2383
2656
  }
2384
2657
  TextareaComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: TextareaComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2385
- TextareaComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.5", type: TextareaComponent, selector: "stream-textarea", inputs: { value: "value" }, outputs: { valueChange: "valueChange", send: "send" }, host: { properties: { "class": "this.class" } }, viewQueries: [{ propertyName: "messageInput", first: true, predicate: ["input"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<textarea\n [value]=\"value || ''\"\n autofocus\n data-testid=\"textarea\"\n #input\n placeholder=\"{{ 'streamChat.Type your message' | translate }}\"\n class=\"rta__textarea str-chat__textarea__textarea\"\n rows=\"1\"\n (input)=\"inputChanged()\"\n (keydown.enter)=\"sent($event)\"\n></textarea>\n", pipes: { "translate": i10.TranslatePipe } });
2658
+ TextareaComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.5", type: TextareaComponent, selector: "stream-textarea", inputs: { value: "value" }, outputs: { valueChange: "valueChange", send: "send" }, host: { properties: { "class": "this.class" } }, viewQueries: [{ propertyName: "messageInput", first: true, predicate: ["input"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<textarea\n [value]=\"value || ''\"\n autofocus\n data-testid=\"textarea\"\n #input\n placeholder=\"{{ 'streamChat.Type your message' | translate }}\"\n class=\"rta__textarea str-chat__textarea__textarea\"\n rows=\"1\"\n (input)=\"inputChanged()\"\n (keydown.enter)=\"sent($event)\"\n></textarea>\n", pipes: { "translate": i1.TranslatePipe } });
2386
2659
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: TextareaComponent, decorators: [{
2387
2660
  type: Component,
2388
2661
  args: [{
@@ -2428,8 +2701,9 @@ class AutocompleteTextareaComponent {
2428
2701
  this.valueChange = new EventEmitter();
2429
2702
  this.send = new EventEmitter();
2430
2703
  this.userMentions = new EventEmitter();
2431
- this.labelKey = 'autocompleteLabel';
2704
+ this.autocompleteKey = 'autocompleteLabel';
2432
2705
  this.mentionTriggerChar = '@';
2706
+ this.commandTriggerChar = '/';
2433
2707
  this.autocompleteConfig = {
2434
2708
  mentions: [],
2435
2709
  };
@@ -2438,7 +2712,15 @@ class AutocompleteTextareaComponent {
2438
2712
  this.userMentionConfig = {
2439
2713
  triggerChar: this.mentionTriggerChar,
2440
2714
  dropUp: true,
2441
- labelKey: this.labelKey,
2715
+ labelKey: this.autocompleteKey,
2716
+ returnTrigger: true,
2717
+ mentionFilter: (searchString, items) => this.filter(searchString, items),
2718
+ mentionSelect: (item, triggerChar) => this.itemSelectedFromAutocompleteList(item, triggerChar),
2719
+ };
2720
+ this.slashCommandConfig = {
2721
+ triggerChar: this.commandTriggerChar,
2722
+ dropUp: true,
2723
+ labelKey: 'name',
2442
2724
  returnTrigger: true,
2443
2725
  mentionFilter: (searchString, items) => this.filter(searchString, items),
2444
2726
  mentionSelect: (item, triggerChar) => this.itemSelectedFromAutocompleteList(item, triggerChar),
@@ -2451,20 +2733,30 @@ class AutocompleteTextareaComponent {
2451
2733
  void this.updateMentionOptions(searchTerm);
2452
2734
  }
2453
2735
  });
2454
- this.subscriptions.push(this.channelService.activeChannel$.subscribe(() => {
2736
+ this.subscriptions.push(this.channelService.activeChannel$.subscribe((channel) => {
2737
+ var _a;
2738
+ const commands = ((_a = channel === null || channel === void 0 ? void 0 : channel.getConfig()) === null || _a === void 0 ? void 0 : _a.commands) || [];
2739
+ this.slashCommandConfig.items = commands.map((c) => (Object.assign(Object.assign({}, c), { [this.autocompleteKey]: c.name, type: 'command' })));
2455
2740
  this.mentionedUsers = [];
2456
2741
  this.userMentions.next([...this.mentionedUsers]);
2457
2742
  void this.updateMentionOptions(this.searchTerm$.getValue());
2458
2743
  }));
2744
+ this.autocompleteConfig.mentions = [
2745
+ this.userMentionConfig,
2746
+ this.slashCommandConfig,
2747
+ ];
2459
2748
  }
2460
2749
  ngOnChanges(changes) {
2461
2750
  if (changes.areMentionsEnabled) {
2462
2751
  if (this.areMentionsEnabled) {
2463
- this.autocompleteConfig.mentions = [this.userMentionConfig];
2752
+ this.autocompleteConfig.mentions = [
2753
+ this.userMentionConfig,
2754
+ this.slashCommandConfig,
2755
+ ];
2464
2756
  this.autocompleteConfig = Object.assign({}, this.autocompleteConfig);
2465
2757
  }
2466
2758
  else {
2467
- this.autocompleteConfig.mentions = [];
2759
+ this.autocompleteConfig.mentions = [this.slashCommandConfig];
2468
2760
  this.autocompleteConfig = Object.assign({}, this.autocompleteConfig);
2469
2761
  }
2470
2762
  }
@@ -2480,7 +2772,9 @@ class AutocompleteTextareaComponent {
2480
2772
  this.mentionedUsers.push((item.user ? item.user : item));
2481
2773
  this.userMentions.next([...this.mentionedUsers]);
2482
2774
  }
2483
- return triggerChar + item.autocompleteLabel;
2775
+ return (triggerChar +
2776
+ item.autocompleteLabel +
2777
+ (triggerChar === this.commandTriggerChar ? ' ' : ''));
2484
2778
  }
2485
2779
  autcompleteSearchTermChanged(searchTerm) {
2486
2780
  if (searchTerm === this.mentionTriggerChar) {
@@ -2524,7 +2818,10 @@ class AutocompleteTextareaComponent {
2524
2818
  return Object.assign(Object.assign({}, i), { autocompleteLabel: user.name || user.id, type: 'mention' });
2525
2819
  }));
2526
2820
  this.userMentionConfig.items = items;
2527
- this.autocompleteConfig.mentions = [this.userMentionConfig];
2821
+ this.autocompleteConfig.mentions = [
2822
+ this.userMentionConfig,
2823
+ this.slashCommandConfig,
2824
+ ];
2528
2825
  this.autocompleteConfig = Object.assign({}, this.autocompleteConfig);
2529
2826
  });
2530
2827
  }
@@ -2543,7 +2840,7 @@ class AutocompleteTextareaComponent {
2543
2840
  }
2544
2841
  }
2545
2842
  AutocompleteTextareaComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: AutocompleteTextareaComponent, deps: [{ token: ChannelService }, { token: ChatClientService }, { token: TransliterationService }], target: i0.ɵɵFactoryTarget.Component });
2546
- AutocompleteTextareaComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.5", type: AutocompleteTextareaComponent, selector: "stream-autocomplete-textarea", inputs: { value: "value", areMentionsEnabled: "areMentionsEnabled", mentionAutocompleteItemTemplate: "mentionAutocompleteItemTemplate", mentionScope: "mentionScope" }, outputs: { valueChange: "valueChange", send: "send", userMentions: "userMentions" }, host: { properties: { "class": "this.class" } }, viewQueries: [{ propertyName: "messageInput", first: true, predicate: ["input"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<textarea\n [value]=\"value || ''\"\n autofocus\n data-testid=\"textarea\"\n #input\n placeholder=\"{{ 'streamChat.Type your message' | translate }}\"\n class=\"rta__textarea str-chat__textarea__textarea\"\n rows=\"1\"\n (input)=\"inputChanged()\"\n (keydown.enter)=\"sent($event)\"\n [mentionConfig]=\"autocompleteConfig\"\n (searchTerm)=\"autcompleteSearchTermChanged($event)\"\n [mentionListTemplate]=\"autocompleteItem\"\n (blur)=\"inputLeft()\"\n></textarea>\n<ng-template #autocompleteItem let-item=\"item\">\n <div class=\"rta rta__item str-chat__emojisearch__item\" [ngSwitch]=\"item.type\">\n <div class=\"rta__entity\" *ngSwitchCase=\"'mention'\">\n <ng-container\n *ngTemplateOutlet=\"\n mentionAutocompleteItemTemplate || defaultMentionTemplate;\n context: { item: item }\n \"\n ></ng-container>\n </div>\n </div>\n</ng-template>\n\n<ng-template #defaultMentionTemplate let-item=\"item\">\n <div class=\"str-chat__user-item\">\n <stream-avatar\n data-testclass=\"avatar\"\n class=\"str-chat__avatar str-chat__avatar--circle\"\n style=\"height: 20px\"\n [size]=\"20\"\n [imageUrl]=\"item.image || item.user?.image\"\n [name]=\"item.autocompleteLabel\"\n ></stream-avatar>\n <span data-testclass=\"username\" class=\"str-chat__user-item--name\">{{\n item.autocompleteLabel\n }}</span>\n </div>\n</ng-template>\n", components: [{ type: AvatarComponent, selector: "stream-avatar", inputs: ["name", "imageUrl", "size"] }], directives: [{ type: i5.MentionDirective, selector: "[mention], [mentionConfig]", inputs: ["mentionConfig", "mention", "mentionListTemplate"], outputs: ["searchTerm", "itemSelected", "opened", "closed"] }, { type: i3.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { type: i3.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { type: i3.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet"] }], pipes: { "translate": i10.TranslatePipe } });
2843
+ AutocompleteTextareaComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.5", type: AutocompleteTextareaComponent, selector: "stream-autocomplete-textarea", inputs: { value: "value", areMentionsEnabled: "areMentionsEnabled", mentionAutocompleteItemTemplate: "mentionAutocompleteItemTemplate", commandAutocompleteItemTemplate: "commandAutocompleteItemTemplate", mentionScope: "mentionScope" }, outputs: { valueChange: "valueChange", send: "send", userMentions: "userMentions" }, host: { properties: { "class": "this.class" } }, viewQueries: [{ propertyName: "messageInput", first: true, predicate: ["input"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<textarea\n [value]=\"value || ''\"\n autofocus\n data-testid=\"textarea\"\n #input\n placeholder=\"{{ 'streamChat.Type your message' | translate }}\"\n class=\"rta__textarea str-chat__textarea__textarea\"\n rows=\"1\"\n (input)=\"inputChanged()\"\n (keydown.enter)=\"sent($event)\"\n [mentionConfig]=\"autocompleteConfig\"\n (searchTerm)=\"autcompleteSearchTermChanged($event)\"\n [mentionListTemplate]=\"autocompleteItem\"\n (blur)=\"inputLeft()\"\n></textarea>\n<ng-template #autocompleteItem let-item=\"item\">\n <div class=\"rta rta__item str-chat__emojisearch__item\" [ngSwitch]=\"item.type\">\n <div class=\"rta__entity\" *ngSwitchCase=\"'mention'\">\n <ng-container\n *ngTemplateOutlet=\"\n mentionAutocompleteItemTemplate || defaultMentionTemplate;\n context: { item: item }\n \"\n ></ng-container>\n </div>\n <div class=\"rta__entity\" *ngSwitchCase=\"'command'\">\n <ng-container\n *ngTemplateOutlet=\"\n commandAutocompleteItemTemplate || defaultCommandTemplate;\n context: { item: item }\n \"\n ></ng-container>\n </div>\n </div>\n</ng-template>\n\n<ng-template #defaultCommandTemplate let-item=\"item\">\n <div class=\"str-chat__slash-command\">\n <span class=\"str-chat__slash-command-header\">\n <strong data-testclass=\"command-name\">{{ item.name }}</strong>\n {{ item.args }}\n </span>\n <br />\n <span class=\"str-chat__slash-command-description\">{{\n item.description\n }}</span>\n </div>\n</ng-template>\n\n<ng-template #defaultMentionTemplate let-item=\"item\">\n <div class=\"str-chat__user-item\">\n <stream-avatar\n data-testclass=\"avatar\"\n class=\"str-chat__avatar str-chat__avatar--circle\"\n style=\"height: 20px\"\n [size]=\"20\"\n [imageUrl]=\"item.image || item.user?.image\"\n [name]=\"item.autocompleteLabel\"\n ></stream-avatar>\n <span data-testclass=\"username\" class=\"str-chat__user-item--name\">{{\n item.autocompleteLabel\n }}</span>\n </div>\n</ng-template>\n", components: [{ type: AvatarComponent, selector: "stream-avatar", inputs: ["name", "imageUrl", "size"] }], directives: [{ type: i5.MentionDirective, selector: "[mention], [mentionConfig]", inputs: ["mentionConfig", "mention", "mentionListTemplate"], outputs: ["searchTerm", "itemSelected", "opened", "closed"] }, { type: i6.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { type: i6.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { type: i6.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet"] }], pipes: { "translate": i1.TranslatePipe } });
2547
2844
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: AutocompleteTextareaComponent, decorators: [{
2548
2845
  type: Component,
2549
2846
  args: [{
@@ -2559,6 +2856,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.5", ngImpor
2559
2856
  type: Input
2560
2857
  }], mentionAutocompleteItemTemplate: [{
2561
2858
  type: Input
2859
+ }], commandAutocompleteItemTemplate: [{
2860
+ type: Input
2562
2861
  }], mentionScope: [{
2563
2862
  type: Input
2564
2863
  }], valueChange: [{
@@ -2617,30 +2916,37 @@ class MessageListComponent {
2617
2916
  this.channelService = channelService;
2618
2917
  this.chatClientService = chatClientService;
2619
2918
  this.imageLoadService = imageLoadService;
2620
- this.areReactionsEnabled = true;
2919
+ /**
2920
+ * @deprecated https://getstream.io/chat/docs/sdk/angular/components/message_list/#caution-arereactionsenabled-deprecated
2921
+ */
2922
+ this.areReactionsEnabled = undefined;
2923
+ /**
2924
+ * @deprecated https://getstream.io/chat/docs/sdk/angular/components/message_list/#caution-enabledmessageactions-deprecated
2925
+ */
2621
2926
  /* eslint-disable-next-line @angular-eslint/no-input-rename */
2622
- this.enabledMessageActionsInput = ['flag', 'edit', 'edit-any', 'delete', 'delete-any'];
2927
+ this.enabledMessageActionsInput = undefined;
2928
+ this.mode = 'main';
2623
2929
  this.enabledMessageActions = [];
2624
2930
  this.class = 'str-chat-angular__main-panel-inner str-chat-angular__message-list-host';
2625
2931
  this.unreadMessageCount = 0;
2626
2932
  this.groupStyles = [];
2627
2933
  this.authorizedMessageActions = ['flag'];
2628
2934
  this.isUserScrolledUpThreshold = 300;
2629
- this.channelService.activeChannel$.subscribe((channel) => {
2935
+ this.subscriptions = [];
2936
+ this.subscriptions.push(this.channelService.activeChannel$.subscribe((channel) => {
2630
2937
  var _a;
2631
- this.latestMessageDate = undefined;
2632
- this.hasNewMessages = true;
2633
- this.isUserScrolledUp = false;
2634
- this.containerHeight = undefined;
2635
- this.olderMassagesLoaded = false;
2636
- this.oldestMessageDate = undefined;
2637
- this.unreadMessageCount = 0;
2638
- this.isNewMessageSentByUser = undefined;
2938
+ this.resetScrollState();
2639
2939
  const capabilites = (_a = channel === null || channel === void 0 ? void 0 : channel.data) === null || _a === void 0 ? void 0 : _a.own_capabilities;
2640
2940
  if (capabilites) {
2641
2941
  this.canReactToMessage = capabilites.indexOf('send-reaction') !== -1;
2642
2942
  this.canReceiveReadEvents = capabilites.indexOf('read-events') !== -1;
2643
2943
  this.authorizedMessageActions = [];
2944
+ if (this.canReactToMessage) {
2945
+ this.authorizedMessageActions.push('send-reaction');
2946
+ }
2947
+ if (this.canReceiveReadEvents) {
2948
+ this.authorizedMessageActions.push('read-events');
2949
+ }
2644
2950
  if (capabilites.indexOf('flag-message') !== -1) {
2645
2951
  this.authorizedMessageActions.push('flag');
2646
2952
  }
@@ -2658,47 +2964,16 @@ class MessageListComponent {
2658
2964
  this.authorizedMessageActions.push('delete');
2659
2965
  this.authorizedMessageActions.push('delete-any');
2660
2966
  }
2661
- this.setEnabledActions();
2662
- }
2663
- });
2664
- this.messages$ = this.channelService.activeChannelMessages$.pipe(tap((messages) => {
2665
- var _a, _b, _c, _d, _e;
2666
- if (messages.length === 0) {
2667
- return;
2668
- }
2669
- const currentLatestMessageDate = messages[messages.length - 1].created_at;
2670
- if (!this.latestMessageDate ||
2671
- ((_a = this.latestMessageDate) === null || _a === void 0 ? void 0 : _a.getTime()) < currentLatestMessageDate.getTime()) {
2672
- this.latestMessageDate = currentLatestMessageDate;
2673
- this.hasNewMessages = true;
2674
- this.isNewMessageSentByUser =
2675
- ((_b = messages[messages.length - 1].user) === null || _b === void 0 ? void 0 : _b.id) ===
2676
- ((_d = (_c = this.chatClientService.chatClient) === null || _c === void 0 ? void 0 : _c.user) === null || _d === void 0 ? void 0 : _d.id);
2677
- if (this.isUserScrolledUp) {
2678
- this.unreadMessageCount++;
2967
+ if (capabilites.indexOf('send-reply') !== -1) {
2968
+ this.authorizedMessageActions.push('send-reply');
2679
2969
  }
2970
+ if (capabilites.indexOf('quote-message') !== -1) {
2971
+ this.authorizedMessageActions.push('quote-message');
2972
+ }
2973
+ this.setEnabledActions();
2680
2974
  }
2681
- const currentOldestMessageDate = messages[0].created_at;
2682
- if (!this.oldestMessageDate) {
2683
- this.oldestMessageDate = currentOldestMessageDate;
2684
- }
2685
- else if (((_e = this.oldestMessageDate) === null || _e === void 0 ? void 0 : _e.getTime()) > currentOldestMessageDate.getTime()) {
2686
- this.oldestMessageDate = currentOldestMessageDate;
2687
- this.olderMassagesLoaded = true;
2688
- }
2689
- }), tap((messages) => {
2690
- this.groupStyles = messages.map((m, i) => getGroupStyles(m, messages[i - 1], messages[i + 1]));
2691
- }), tap((messages) => {
2692
- var _a;
2693
- return (this.lastSentMessageId = (_a = [...messages]
2694
- .reverse()
2695
- .find((m) => {
2696
- var _a, _b, _c;
2697
- return ((_a = m.user) === null || _a === void 0 ? void 0 : _a.id) === ((_c = (_b = this.chatClientService.chatClient) === null || _b === void 0 ? void 0 : _b.user) === null || _c === void 0 ? void 0 : _c.id) &&
2698
- m.status !== 'sending';
2699
- })) === null || _a === void 0 ? void 0 : _a.id);
2700
2975
  }));
2701
- this.imageLoadService.imageLoad$.subscribe(() => {
2976
+ this.subscriptions.push(this.imageLoadService.imageLoad$.subscribe(() => {
2702
2977
  if (!this.isUserScrolledUp) {
2703
2978
  this.scrollToBottom();
2704
2979
  // Hacky and unreliable workaround to scroll down after loaded images move the scrollbar
@@ -2706,12 +2981,27 @@ class MessageListComponent {
2706
2981
  this.scrollToBottom();
2707
2982
  }, 300);
2708
2983
  }
2709
- });
2984
+ }));
2985
+ this.subscriptions.push(this.channelService.activeParentMessage$.subscribe((message) => {
2986
+ if (message &&
2987
+ this.parentMessage &&
2988
+ message.id !== this.parentMessage.id &&
2989
+ this.mode === 'thread') {
2990
+ this.resetScrollState();
2991
+ }
2992
+ this.parentMessage = message;
2993
+ }));
2994
+ }
2995
+ ngOnInit() {
2996
+ this.setMessages$();
2710
2997
  }
2711
2998
  ngOnChanges(changes) {
2712
2999
  if (changes.enabledMessageActionsInput) {
2713
3000
  this.setEnabledActions();
2714
3001
  }
3002
+ if (changes.mode) {
3003
+ this.setMessages$();
3004
+ }
2715
3005
  }
2716
3006
  ngAfterViewChecked() {
2717
3007
  if (this.hasNewMessages) {
@@ -2730,6 +3020,15 @@ class MessageListComponent {
2730
3020
  this.containerHeight = this.scrollContainer.nativeElement.scrollHeight;
2731
3021
  this.olderMassagesLoaded = false;
2732
3022
  }
3023
+ else if (this.containerHeight !== undefined &&
3024
+ this.containerHeight < this.scrollContainer.nativeElement.scrollHeight &&
3025
+ !this.isUserScrolledUp) {
3026
+ this.scrollToBottom();
3027
+ this.containerHeight = this.scrollContainer.nativeElement.scrollHeight;
3028
+ }
3029
+ }
3030
+ ngOnDestroy() {
3031
+ this.subscriptions.forEach((s) => s.unsubscribe());
2733
3032
  }
2734
3033
  trackByMessageId(index, item) {
2735
3034
  return item.id;
@@ -2739,6 +3038,7 @@ class MessageListComponent {
2739
3038
  this.scrollContainer.nativeElement.scrollHeight;
2740
3039
  }
2741
3040
  scrolled() {
3041
+ var _a, _b;
2742
3042
  this.isUserScrolledUp =
2743
3043
  this.scrollContainer.nativeElement.scrollHeight -
2744
3044
  (this.scrollContainer.nativeElement.scrollTop +
@@ -2747,20 +3047,36 @@ class MessageListComponent {
2747
3047
  if (!this.isUserScrolledUp) {
2748
3048
  this.unreadMessageCount = 0;
2749
3049
  }
2750
- if (this.scrollContainer.nativeElement.scrollTop === 0) {
3050
+ if (this.scrollContainer.nativeElement.scrollTop <=
3051
+ (((_a = this.parentMessageElement) === null || _a === void 0 ? void 0 : _a.nativeElement.clientHeight) || 0) &&
3052
+ (this.prevScrollTop === undefined ||
3053
+ this.prevScrollTop >
3054
+ (((_b = this.parentMessageElement) === null || _b === void 0 ? void 0 : _b.nativeElement.clientHeight) || 0))) {
2751
3055
  this.containerHeight = this.scrollContainer.nativeElement.scrollHeight;
2752
- void this.channelService.loadMoreMessages();
3056
+ this.mode === 'main'
3057
+ ? void this.channelService.loadMoreMessages()
3058
+ : void this.channelService.loadMoreThreadReplies();
2753
3059
  }
3060
+ this.prevScrollTop = this.scrollContainer.nativeElement.scrollTop;
2754
3061
  }
2755
3062
  preserveScrollbarPosition() {
2756
3063
  this.scrollContainer.nativeElement.scrollTop =
2757
- this.scrollContainer.nativeElement.scrollHeight - this.containerHeight;
3064
+ (this.prevScrollTop || 0) +
3065
+ (this.scrollContainer.nativeElement.scrollHeight - this.containerHeight);
2758
3066
  }
2759
3067
  setEnabledActions() {
2760
3068
  this.enabledMessageActions = [];
2761
3069
  if (!this.enabledMessageActionsInput) {
3070
+ this.enabledMessageActions = this.authorizedMessageActions;
2762
3071
  return;
2763
3072
  }
3073
+ this.enabledMessageActionsInput = [
3074
+ ...this.enabledMessageActionsInput,
3075
+ 'send-reaction',
3076
+ 'read-events',
3077
+ 'send-reply',
3078
+ 'quote-message',
3079
+ ];
2764
3080
  this.enabledMessageActionsInput.forEach((action) => {
2765
3081
  const isAuthorized = this.authorizedMessageActions.indexOf(action) !== -1;
2766
3082
  if (isAuthorized) {
@@ -2768,9 +3084,61 @@ class MessageListComponent {
2768
3084
  }
2769
3085
  });
2770
3086
  }
3087
+ setMessages$() {
3088
+ this.messages$ = (this.mode === 'main'
3089
+ ? this.channelService.activeChannelMessages$
3090
+ : this.channelService.activeThreadMessages$).pipe(tap((messages) => {
3091
+ var _a, _b, _c, _d, _e;
3092
+ if (messages.length === 0) {
3093
+ return;
3094
+ }
3095
+ const currentLatestMessageDate = messages[messages.length - 1].created_at;
3096
+ if (!this.latestMessageDate ||
3097
+ ((_a = this.latestMessageDate) === null || _a === void 0 ? void 0 : _a.getTime()) < currentLatestMessageDate.getTime()) {
3098
+ this.latestMessageDate = currentLatestMessageDate;
3099
+ this.hasNewMessages = true;
3100
+ this.isNewMessageSentByUser =
3101
+ ((_b = messages[messages.length - 1].user) === null || _b === void 0 ? void 0 : _b.id) ===
3102
+ ((_d = (_c = this.chatClientService.chatClient) === null || _c === void 0 ? void 0 : _c.user) === null || _d === void 0 ? void 0 : _d.id);
3103
+ if (this.isUserScrolledUp) {
3104
+ this.unreadMessageCount++;
3105
+ }
3106
+ }
3107
+ const currentOldestMessageDate = messages[0].created_at;
3108
+ if (!this.oldestMessageDate) {
3109
+ this.oldestMessageDate = currentOldestMessageDate;
3110
+ }
3111
+ else if (((_e = this.oldestMessageDate) === null || _e === void 0 ? void 0 : _e.getTime()) > currentOldestMessageDate.getTime()) {
3112
+ this.oldestMessageDate = currentOldestMessageDate;
3113
+ this.olderMassagesLoaded = true;
3114
+ }
3115
+ }), tap((messages) => {
3116
+ this.groupStyles = messages.map((m, i) => getGroupStyles(m, messages[i - 1], messages[i + 1]));
3117
+ }), tap((messages) => {
3118
+ var _a;
3119
+ return (this.lastSentMessageId = (_a = [...messages]
3120
+ .reverse()
3121
+ .find((m) => {
3122
+ var _a, _b, _c;
3123
+ return ((_a = m.user) === null || _a === void 0 ? void 0 : _a.id) === ((_c = (_b = this.chatClientService.chatClient) === null || _b === void 0 ? void 0 : _b.user) === null || _c === void 0 ? void 0 : _c.id) &&
3124
+ m.status !== 'sending';
3125
+ })) === null || _a === void 0 ? void 0 : _a.id);
3126
+ }));
3127
+ }
3128
+ resetScrollState() {
3129
+ this.latestMessageDate = undefined;
3130
+ this.hasNewMessages = true;
3131
+ this.isUserScrolledUp = false;
3132
+ this.containerHeight = undefined;
3133
+ this.olderMassagesLoaded = false;
3134
+ this.oldestMessageDate = undefined;
3135
+ this.unreadMessageCount = 0;
3136
+ this.prevScrollTop = undefined;
3137
+ this.isNewMessageSentByUser = undefined;
3138
+ }
2771
3139
  }
2772
3140
  MessageListComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: MessageListComponent, deps: [{ token: ChannelService }, { token: ChatClientService }, { token: ImageLoadService }], target: i0.ɵɵFactoryTarget.Component });
2773
- MessageListComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.5", type: MessageListComponent, selector: "stream-message-list", inputs: { messageTemplate: "messageTemplate", messageInputTemplate: "messageInputTemplate", mentionTemplate: "mentionTemplate", areReactionsEnabled: "areReactionsEnabled", enabledMessageActionsInput: ["enabledMessageActions", "enabledMessageActionsInput"] }, host: { properties: { "class": "this.class" } }, viewQueries: [{ propertyName: "scrollContainer", first: true, predicate: ["scrollContainer"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div\n #scrollContainer\n data-testid=\"scroll-container\"\n class=\"str-chat__list\"\n (scroll)=\"scrolled()\"\n>\n <div class=\"str-chat__reverse-infinite-scroll\">\n <ul class=\"str-chat__ul\">\n <li\n data-testclass=\"message\"\n *ngFor=\"\n let message of messages$ | async;\n let i = index;\n trackBy: trackByMessageId\n \"\n class=\"str-chat__li str-chat__li--{{ groupStyles[i] }}\"\n >\n <ng-container *ngIf=\"messageTemplate; else defaultMessageTemplate\">\n <ng-container\n *ngTemplateOutlet=\"\n messageTemplate;\n context: {\n message: message,\n areReactionsEnabled: areReactionsEnabled,\n canReactToMessage: canReactToMessage,\n lastSentMessageId: !!(\n lastSentMessageId && message?.id === lastSentMessageId\n ),\n canReceiveReadEvents: canReceiveReadEvents,\n messageInputTemplate: messageInputTemplate,\n mentionTemplate: mentionTemplate\n }\n \"\n ></ng-container>\n </ng-container>\n <ng-template #defaultMessageTemplate>\n <stream-message\n [message]=\"message\"\n [areReactionsEnabled]=\"areReactionsEnabled\"\n [canReactToMessage]=\"canReactToMessage\"\n [isLastSentMessage]=\"\n !!(lastSentMessageId && message?.id === lastSentMessageId)\n \"\n [enabledMessageActions]=\"enabledMessageActions\"\n [canReceiveReadEvents]=\"canReceiveReadEvents\"\n [messageInputTemplate]=\"messageInputTemplate\"\n [mentionTemplate]=\"mentionTemplate\"\n ></stream-message>\n </ng-template>\n </li>\n </ul>\n </div>\n</div>\n<div class=\"str-chat__list-notifications\">\n <button\n data-testid=\"scroll-to-bottom\"\n *ngIf=\"isUserScrolledUp\"\n class=\"\n str-chat__message-notification\n str-chat__message-notification-right\n str-chat__message-notification-scroll-down\n \"\n (keyup.enter)=\"scrollToBottom()\"\n (click)=\"scrollToBottom()\"\n >\n <div\n *ngIf=\"unreadMessageCount > 0\"\n class=\"\n str-chat__message-notification\n str-chat__message-notification-scroll-down-unread-count\n \"\n >\n {{ unreadMessageCount }}\n </div>\n </button>\n</div>\n", components: [{ type: MessageComponent, selector: "stream-message", inputs: ["messageInputTemplate", "mentionTemplate", "message", "enabledMessageActions", "areReactionsEnabled", "canReactToMessage", "isLastSentMessage", "canReceiveReadEvents"] }], directives: [{ type: i3.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i3.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet"] }], pipes: { "async": i3.AsyncPipe } });
3141
+ MessageListComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.5", type: MessageListComponent, selector: "stream-message-list", inputs: { messageTemplate: "messageTemplate", messageInputTemplate: "messageInputTemplate", mentionTemplate: "mentionTemplate", areReactionsEnabled: "areReactionsEnabled", enabledMessageActionsInput: ["enabledMessageActions", "enabledMessageActionsInput"], mode: "mode" }, 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: "<div\n #scrollContainer\n data-testid=\"scroll-container\"\n class=\"str-chat__list\"\n (scroll)=\"scrolled()\"\n>\n <div class=\"str-chat__reverse-infinite-scroll\">\n <ul class=\"str-chat__ul\">\n <li\n #parentMessageElement\n *ngIf=\"mode === 'thread'\"\n data-testid=\"parent-message\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n messageTemplateContainer;\n context: { message: parentMessage }\n \"\n ></ng-container>\n <div class=\"str-chat__thread-start\" translate>\n streamChat.Start of a new thread\n </div>\n </li>\n <li\n data-testclass=\"message\"\n *ngFor=\"\n let message of messages$ | async;\n let i = index;\n trackBy: trackByMessageId\n \"\n class=\"str-chat__li str-chat__li--{{ groupStyles[i] }}\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n messageTemplateContainer;\n context: { message: message }\n \"\n ></ng-container>\n </li>\n </ul>\n </div>\n</div>\n<div class=\"str-chat__list-notifications\">\n <button\n data-testid=\"scroll-to-bottom\"\n *ngIf=\"isUserScrolledUp\"\n class=\"\n str-chat__message-notification\n str-chat__message-notification-right\n str-chat__message-notification-scroll-down\n \"\n (keyup.enter)=\"scrollToBottom()\"\n (click)=\"scrollToBottom()\"\n >\n <div\n *ngIf=\"unreadMessageCount > 0\"\n class=\"\n str-chat__message-notification\n str-chat__message-notification-scroll-down-unread-count\n \"\n >\n {{ unreadMessageCount }}\n </div>\n </button>\n</div>\n\n<ng-template #messageTemplateContainer let-message=\"message\">\n <ng-container *ngIf=\"messageTemplate; else defaultMessageTemplate\">\n <ng-container\n *ngTemplateOutlet=\"\n messageTemplate;\n context: {\n message: message,\n areReactionsEnabled: areReactionsEnabled,\n canReactToMessage: canReactToMessage,\n lastSentMessageId: !!(\n lastSentMessageId && message?.id === lastSentMessageId\n ),\n canReceiveReadEvents: canReceiveReadEvents,\n messageInputTemplate: messageInputTemplate,\n mentionTemplate: mentionTemplate,\n mode: mode\n }\n \"\n ></ng-container>\n </ng-container>\n <ng-template #defaultMessageTemplate>\n <stream-message\n [message]=\"message\"\n [areReactionsEnabled]=\"areReactionsEnabled\"\n [canReactToMessage]=\"canReactToMessage\"\n [isLastSentMessage]=\"\n !!(lastSentMessageId && message?.id === lastSentMessageId)\n \"\n [enabledMessageActions]=\"enabledMessageActions\"\n [canReceiveReadEvents]=\"canReceiveReadEvents\"\n [messageInputTemplate]=\"messageInputTemplate\"\n [mentionTemplate]=\"mentionTemplate\"\n [mode]=\"mode\"\n ></stream-message>\n </ng-template>\n</ng-template>\n", components: [{ type: MessageComponent, selector: "stream-message", inputs: ["messageInputTemplate", "mentionTemplate", "message", "enabledMessageActions", "areReactionsEnabled", "canReactToMessage", "isLastSentMessage", "canReceiveReadEvents", "mode"] }], directives: [{ type: i6.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i6.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet"] }, { type: i1.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { type: i6.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }], pipes: { "async": i6.AsyncPipe } });
2774
3142
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: MessageListComponent, decorators: [{
2775
3143
  type: Component,
2776
3144
  args: [{
@@ -2789,12 +3157,49 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.5", ngImpor
2789
3157
  }], enabledMessageActionsInput: [{
2790
3158
  type: Input,
2791
3159
  args: ['enabledMessageActions']
3160
+ }], mode: [{
3161
+ type: Input
2792
3162
  }], class: [{
2793
3163
  type: HostBinding,
2794
3164
  args: ['class']
2795
3165
  }], scrollContainer: [{
2796
3166
  type: ViewChild,
2797
3167
  args: ['scrollContainer']
3168
+ }], parentMessageElement: [{
3169
+ type: ViewChild,
3170
+ args: ['parentMessageElement']
3171
+ }] } });
3172
+
3173
+ class ThreadComponent {
3174
+ constructor(channelService) {
3175
+ this.channelService = channelService;
3176
+ this.class = 'str-chat__thread';
3177
+ this.subscriptions = [];
3178
+ this.subscriptions.push(this.channelService.activeParentMessage$.subscribe((parentMessage) => (this.parentMessage = parentMessage)));
3179
+ }
3180
+ ngOnDestroy() {
3181
+ this.subscriptions.forEach((s) => s.unsubscribe());
3182
+ }
3183
+ get replyCountParam() {
3184
+ var _a;
3185
+ return { replyCount: (_a = this.parentMessage) === null || _a === void 0 ? void 0 : _a.reply_count };
3186
+ }
3187
+ closeThread() {
3188
+ void this.channelService.setAsActiveParentMessage(undefined);
3189
+ }
3190
+ }
3191
+ ThreadComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: ThreadComponent, deps: [{ token: ChannelService }], target: i0.ɵɵFactoryTarget.Component });
3192
+ ThreadComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.5", type: ThreadComponent, selector: "stream-thread", host: { properties: { "class": "this.class" } }, ngImport: i0, template: "<div class=\"str-chat__thread-header\">\n <div class=\"str-chat__thread-header-details\">\n <strong translate>streamChat.Thread</strong>\n <small data-testid=\"reply-count\">\n {{parentMessage?.reply_count === 1 ? ('streamChat.1 reply' | translate) : ('streamChat.{{ replyCount }}\n replies' | translate:replyCountParam)}}\n </small>\n </div>\n <button\n class=\"str-chat__square-button\"\n data-testid=\"close-button\"\n (click)=\"closeThread()\"\n >\n <stream-icon icon=\"close-no-outline\"></stream-icon>\n </button>\n</div>\n<ng-content select='[name=\"thread-message-list\"]'></ng-content>\n<div class=\"str-chat__small-message-input__wrapper\">\n <ng-content select='[name=\"thread-message-input\"]'></ng-content>\n</div>\n", components: [{ type: IconComponent, selector: "stream-icon", inputs: ["icon", "size"] }], directives: [{ type: i1.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }], pipes: { "translate": i1.TranslatePipe } });
3193
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: ThreadComponent, decorators: [{
3194
+ type: Component,
3195
+ args: [{
3196
+ selector: 'stream-thread',
3197
+ templateUrl: './thread.component.html',
3198
+ styles: [],
3199
+ }]
3200
+ }], ctorParameters: function () { return [{ type: ChannelService }]; }, propDecorators: { class: [{
3201
+ type: HostBinding,
3202
+ args: ['class']
2798
3203
  }] } });
2799
3204
 
2800
3205
  class StreamAvatarModule {
@@ -2830,7 +3235,8 @@ StreamChatModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", versio
2830
3235
  NotificationListComponent,
2831
3236
  AttachmentPreviewListComponent,
2832
3237
  ModalComponent,
2833
- TextareaDirective], imports: [CommonModule, TranslateModule, StreamAvatarModule], exports: [ChannelComponent,
3238
+ TextareaDirective,
3239
+ ThreadComponent], imports: [CommonModule, TranslateModule, StreamAvatarModule], exports: [ChannelComponent,
2834
3240
  ChannelHeaderComponent,
2835
3241
  ChannelListComponent,
2836
3242
  ChannelPreviewComponent,
@@ -2846,7 +3252,8 @@ StreamChatModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", versio
2846
3252
  NotificationListComponent,
2847
3253
  AttachmentPreviewListComponent,
2848
3254
  ModalComponent,
2849
- StreamAvatarModule] });
3255
+ StreamAvatarModule,
3256
+ ThreadComponent] });
2850
3257
  StreamChatModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: StreamChatModule, imports: [[CommonModule, TranslateModule, StreamAvatarModule], StreamAvatarModule] });
2851
3258
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: StreamChatModule, decorators: [{
2852
3259
  type: NgModule,
@@ -2869,6 +3276,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.5", ngImpor
2869
3276
  AttachmentPreviewListComponent,
2870
3277
  ModalComponent,
2871
3278
  TextareaDirective,
3279
+ ThreadComponent,
2872
3280
  ],
2873
3281
  imports: [CommonModule, TranslateModule, StreamAvatarModule],
2874
3282
  exports: [
@@ -2889,6 +3297,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.5", ngImpor
2889
3297
  AttachmentPreviewListComponent,
2890
3298
  ModalComponent,
2891
3299
  StreamAvatarModule,
3300
+ ThreadComponent,
2892
3301
  ],
2893
3302
  }]
2894
3303
  }] });
@@ -2951,5 +3360,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.5", ngImpor
2951
3360
  * Generated bundle index. Do not edit.
2952
3361
  */
2953
3362
 
2954
- export { AttachmentListComponent, AttachmentPreviewListComponent, AttachmentService, AutocompleteTextareaComponent, AvatarComponent, ChannelComponent, ChannelHeaderComponent, ChannelListComponent, ChannelListToggleService, ChannelPreviewComponent, ChannelService, ChatClientService, IconComponent, ImageLoadService, LoadingIndicatorComponent, MessageActionsBoxComponent, MessageComponent, MessageInputComponent, MessageInputConfigService, MessageListComponent, MessageReactionsComponent, ModalComponent, NotificationComponent, NotificationListComponent, NotificationService, StreamAutocompleteTextareaModule, StreamAvatarModule, StreamChatModule, StreamI18nService, StreamTextareaModule, TextareaComponent, TextareaDirective, ThemeService, TransliterationService, createMessagePreview, getDeviceWidth, getGroupStyles, getReadBy, getReadByText, isImageAttachment, isImageFile, parseDate, textareaInjectionToken };
3363
+ export { AttachmentListComponent, AttachmentPreviewListComponent, AttachmentService, AutocompleteTextareaComponent, AvatarComponent, ChannelComponent, ChannelHeaderComponent, ChannelListComponent, ChannelListToggleService, ChannelPreviewComponent, ChannelService, ChatClientService, IconComponent, ImageLoadService, LoadingIndicatorComponent, MessageActionsBoxComponent, MessageComponent, MessageInputComponent, MessageInputConfigService, MessageListComponent, MessageReactionsComponent, ModalComponent, NotificationComponent, NotificationListComponent, NotificationService, StreamAutocompleteTextareaModule, StreamAvatarModule, StreamChatModule, StreamI18nService, StreamTextareaModule, TextareaComponent, TextareaDirective, ThemeService, ThreadComponent, TransliterationService, createMessagePreview, getDeviceWidth, getGroupStyles, getReadBy, getReadByText, isImageAttachment, isImageFile, parseDate, textareaInjectionToken };
2955
3364
  //# sourceMappingURL=stream-chat-angular.js.map