stream-chat-angular 5.3.2 → 5.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/assets/i18n/en.d.ts +4 -0
  2. package/assets/version.d.ts +1 -1
  3. package/esm2020/assets/i18n/en.mjs +5 -1
  4. package/esm2020/assets/version.mjs +2 -2
  5. package/esm2020/lib/attachment-list/attachment-list.component.mjs +4 -4
  6. package/esm2020/lib/attachment-preview-list/attachment-preview-list.component.mjs +5 -5
  7. package/esm2020/lib/attachment.service.mjs +60 -10
  8. package/esm2020/lib/channel-list/channel-list.component.mjs +3 -3
  9. package/esm2020/lib/channel-preview/channel-preview.component.mjs +1 -1
  10. package/esm2020/lib/file-utils.mjs +35 -0
  11. package/esm2020/lib/format-duration.mjs +16 -0
  12. package/esm2020/lib/icon/icon-placeholder/icon-placeholder.component.mjs +28 -0
  13. package/esm2020/lib/icon/icon.component.mjs +1 -1
  14. package/esm2020/lib/icon/icon.module.mjs +37 -0
  15. package/esm2020/lib/{loading-indicator → icon/loading-indicator}/loading-indicator.component.mjs +1 -1
  16. package/esm2020/lib/{loading-indicator-placeholder → icon/loading-indicator-placeholder}/loading-indicator-placeholder.component.mjs +2 -2
  17. package/esm2020/lib/is-safari.mjs +2 -0
  18. package/esm2020/lib/message/message.component.mjs +6 -6
  19. package/esm2020/lib/message-input/message-input-config.service.mjs +6 -1
  20. package/esm2020/lib/message-input/message-input.component.mjs +57 -14
  21. package/esm2020/lib/message-input/voice-recorder.service.mjs +27 -0
  22. package/esm2020/lib/message-list/message-list.component.mjs +9 -9
  23. package/esm2020/lib/modal/modal.component.mjs +1 -1
  24. package/esm2020/lib/paginated-list/paginated-list.component.mjs +1 -1
  25. package/esm2020/lib/stream-chat.module.mjs +21 -35
  26. package/esm2020/lib/thread/thread.component.mjs +1 -1
  27. package/esm2020/lib/types.mjs +1 -1
  28. package/esm2020/lib/voice-recorder/amplitude-recorder.service.mjs +119 -0
  29. package/esm2020/lib/voice-recorder/audio-recorder.service.mjs +79 -0
  30. package/esm2020/lib/voice-recorder/media-recorder.mjs +190 -0
  31. package/esm2020/lib/voice-recorder/mp3-transcoder.mjs +61 -0
  32. package/esm2020/lib/voice-recorder/transcoder.service.mjs +121 -0
  33. package/esm2020/lib/voice-recorder/voice-recorder-wavebar/voice-recorder-wavebar.component.mjs +35 -0
  34. package/esm2020/lib/voice-recorder/voice-recorder.component.mjs +80 -0
  35. package/esm2020/lib/voice-recorder/voice-recorder.module.mjs +34 -0
  36. package/esm2020/lib/voice-recording/voice-recording-wavebar/voice-recording-wavebar.component.mjs +4 -75
  37. package/esm2020/lib/voice-recording/voice-recording.component.mjs +4 -15
  38. package/esm2020/lib/voice-recording/voice-recording.module.mjs +21 -0
  39. package/esm2020/lib/wave-form-sampler.mjs +72 -0
  40. package/esm2020/public-api.mjs +18 -5
  41. package/fesm2015/stream-chat-angular.mjs +1055 -145
  42. package/fesm2015/stream-chat-angular.mjs.map +1 -1
  43. package/fesm2020/stream-chat-angular.mjs +1006 -140
  44. package/fesm2020/stream-chat-angular.mjs.map +1 -1
  45. package/lib/attachment.service.d.ts +7 -1
  46. package/lib/file-utils.d.ts +9 -0
  47. package/lib/format-duration.d.ts +1 -0
  48. package/lib/{icon-placeholder → icon/icon-placeholder}/icon-placeholder.component.d.ts +3 -3
  49. package/lib/icon/icon.component.d.ts +1 -1
  50. package/lib/icon/icon.module.d.ts +11 -0
  51. package/lib/{loading-indicator-placeholder → icon/loading-indicator-placeholder}/loading-indicator-placeholder.component.d.ts +1 -1
  52. package/lib/is-safari.d.ts +1 -0
  53. package/lib/message-input/message-input-config.service.d.ts +5 -0
  54. package/lib/message-input/message-input.component.d.ts +19 -5
  55. package/lib/message-input/voice-recorder.service.d.ts +19 -0
  56. package/lib/message-list/message-list.component.d.ts +0 -1
  57. package/lib/stream-chat.module.d.ts +20 -24
  58. package/lib/types.d.ts +11 -1
  59. package/lib/voice-recorder/amplitude-recorder.service.d.ts +71 -0
  60. package/lib/voice-recorder/audio-recorder.service.d.ts +46 -0
  61. package/lib/voice-recorder/media-recorder.d.ts +46 -0
  62. package/lib/voice-recorder/mp3-transcoder.d.ts +1 -0
  63. package/lib/voice-recorder/transcoder.service.d.ts +40 -0
  64. package/lib/voice-recorder/voice-recorder-wavebar/voice-recorder-wavebar.component.d.ts +21 -0
  65. package/lib/voice-recorder/voice-recorder.component.d.ts +30 -0
  66. package/lib/voice-recorder/voice-recorder.module.d.ts +12 -0
  67. package/lib/voice-recording/voice-recording-wavebar/voice-recording-wavebar.component.d.ts +0 -7
  68. package/lib/voice-recording/voice-recording.component.d.ts +0 -1
  69. package/lib/voice-recording/voice-recording.module.d.ts +11 -0
  70. package/lib/wave-form-sampler.d.ts +1 -0
  71. package/package.json +8 -1
  72. package/public-api.d.ts +17 -4
  73. package/src/assets/assets/icons/stream-chat-icons.eot +0 -0
  74. package/src/assets/assets/icons/stream-chat-icons.svg +4 -0
  75. package/src/assets/assets/icons/stream-chat-icons.ttf +0 -0
  76. package/src/assets/assets/icons/stream-chat-icons.woff +0 -0
  77. package/src/assets/assets/icons/stream-chat-icons.woff2 +0 -0
  78. package/src/assets/i18n/en.ts +6 -0
  79. package/src/assets/styles/css/index.css +1 -1
  80. package/src/assets/styles/css/index.layout.css +1 -1
  81. package/src/assets/styles/scss/AudioRecorder/AudioRecorder-layout.scss +64 -14
  82. package/src/assets/styles/scss/AudioRecorder/AudioRecorder-theme.scss +11 -1
  83. package/src/assets/styles/scss/Icon/Icon-layout.scss +6 -1
  84. package/src/assets/styles/scss/MessageInput/MessageInput-layout.scss +1 -0
  85. package/src/assets/styles/scss/MessageInput/MessageInput-theme.scss +1 -0
  86. package/src/assets/version.ts +1 -1
  87. package/esm2020/lib/icon-placeholder/icon-placeholder.component.mjs +0 -28
  88. package/esm2020/lib/is-image-file.mjs +0 -5
  89. package/lib/is-image-file.d.ts +0 -1
  90. /package/lib/{loading-indicator → icon/loading-indicator}/loading-indicator.component.d.ts +0 -0
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Injectable, Component, Input, EventEmitter, Output, ViewChild, HostBinding, TemplateRef, ContentChild, ChangeDetectionStrategy, InjectionToken, Directive, Inject, NgModule } from '@angular/core';
2
+ import { Injectable, Component, Input, EventEmitter, Output, ViewChild, HostBinding, TemplateRef, ContentChild, ChangeDetectionStrategy, InjectionToken, Directive, NgModule, Inject, Optional } from '@angular/core';
3
3
  import { BehaviorSubject, ReplaySubject, combineLatest, take as take$1, Subject, timer, merge, switchMap, distinctUntilChanged, pairwise, filter as filter$1, of, map as map$1 } from 'rxjs';
4
4
  import { StreamChat } from 'stream-chat';
5
5
  import { take, shareReplay, map, first, filter, tap, debounceTime, throttleTime } from 'rxjs/operators';
@@ -15,11 +15,13 @@ import emojiRegex from 'emoji-regex';
15
15
  import * as i8 from 'ngx-float-ui';
16
16
  import { NgxFloatUiModule } from 'ngx-float-ui';
17
17
  import prettybytes from 'pretty-bytes';
18
+ import fixWebmDuration from 'fix-webm-duration';
19
+ import { NgModel } from '@angular/forms';
18
20
  import transliterate from '@stream-io/transliterate';
19
21
  import * as i8$1 from 'angular-mentions';
20
22
  import { MentionModule } from 'angular-mentions';
21
23
 
22
- const version = '5.3.2';
24
+ const version = '5.4.1';
23
25
 
24
26
  /**
25
27
  * The `NotificationService` can be used to add or remove notifications. By default the [`NotificationList`](../components/NotificationListComponent.mdx) component displays the currently active notifications.
@@ -1983,6 +1985,36 @@ const isImageFile = (file) => {
1983
1985
  // photoshop files begin with 'image/'
1984
1986
  return file.type.startsWith('image/') && !file.type.endsWith('.photoshop');
1985
1987
  };
1988
+ const readBlobAsArrayBuffer = (blob) => new Promise((resolve, reject) => {
1989
+ const fileReader = new FileReader();
1990
+ fileReader.onload = () => {
1991
+ resolve(fileReader.result);
1992
+ };
1993
+ fileReader.onerror = () => {
1994
+ reject(fileReader.error);
1995
+ };
1996
+ fileReader.readAsArrayBuffer(blob);
1997
+ });
1998
+ const createFileFromBlobs = ({ blobsArray, fileName, mimeType, }) => {
1999
+ const concatenatedBlob = new Blob(blobsArray, { type: mimeType });
2000
+ return new File([concatenatedBlob], fileName, {
2001
+ type: concatenatedBlob.type,
2002
+ });
2003
+ };
2004
+ const getExtensionFromMimeType = (mimeType) => {
2005
+ const match = mimeType.match(/\/([^/;]+)/);
2006
+ return match && match[1];
2007
+ };
2008
+ const createUriFromBlob = (blob) => {
2009
+ return new Promise((resolve, reject) => {
2010
+ const reader = new FileReader();
2011
+ reader.onload = (event) => {
2012
+ resolve(event.target?.result ?? undefined);
2013
+ };
2014
+ reader.onerror = (e) => reject(e);
2015
+ reader.readAsDataURL(blob);
2016
+ });
2017
+ };
1986
2018
 
1987
2019
  const isImageAttachment = (attachment) => {
1988
2020
  return (attachment.type === 'image' &&
@@ -2013,6 +2045,35 @@ class AttachmentService {
2013
2045
  resetAttachmentUploads() {
2014
2046
  this.attachmentUploadsSubject.next([]);
2015
2047
  }
2048
+ /**
2049
+ * Upload a voice recording
2050
+ * @param audioRecording
2051
+ * @returns A promise with true or false. If false is returned the upload was canceled because of a client side error. The error is emitted via the `NotificationService`.
2052
+ */
2053
+ async uploadVoiceRecording(audioRecording) {
2054
+ if (!(await this.areAttachmentsHaveValidExtension([audioRecording.recording]))) {
2055
+ return false;
2056
+ }
2057
+ if (!(await this.areAttachmentsHaveValidSize([audioRecording.recording]))) {
2058
+ return false;
2059
+ }
2060
+ const upload = {
2061
+ file: audioRecording.recording,
2062
+ previewUri: audioRecording.asset_url,
2063
+ extraData: {
2064
+ duration: audioRecording.duration,
2065
+ waveform_data: audioRecording.waveform_data,
2066
+ },
2067
+ state: 'uploading',
2068
+ type: 'voiceRecording',
2069
+ };
2070
+ this.attachmentUploadsSubject.next([
2071
+ ...this.attachmentUploadsSubject.getValue(),
2072
+ upload,
2073
+ ]);
2074
+ await this.uploadAttachments([upload]);
2075
+ return true;
2076
+ }
2016
2077
  /**
2017
2078
  * Uploads the selected files, and creates preview for image files. The result is propagated throught the `attachmentUploads$` stream.
2018
2079
  * @param fileList The files selected by the user, if you have Blobs instead of Files, you can convert them with this method: https://developer.mozilla.org/en-US/docs/Web/API/File/File
@@ -2043,7 +2104,7 @@ class AttachmentService {
2043
2104
  dataFiles.push(file);
2044
2105
  }
2045
2106
  });
2046
- imageFiles.forEach((f) => this.createPreview(f));
2107
+ imageFiles.forEach((f) => void this.createPreview(f));
2047
2108
  const newUploads = [
2048
2109
  ...imageFiles.map((file) => ({
2049
2110
  file,
@@ -2129,7 +2190,7 @@ class AttachmentService {
2129
2190
  return attachmentUploads
2130
2191
  .filter((r) => r.state === 'success')
2131
2192
  .map((r) => {
2132
- const attachment = {
2193
+ let attachment = {
2133
2194
  type: r.type,
2134
2195
  };
2135
2196
  if (r.fromAttachment) {
@@ -2147,6 +2208,9 @@ class AttachmentService {
2147
2208
  attachment.file_size = r.file?.size;
2148
2209
  attachment.thumb_url = r.thumb_url;
2149
2210
  }
2211
+ if (r.extraData) {
2212
+ attachment = { ...attachment, ...r.extraData };
2213
+ }
2150
2214
  }
2151
2215
  return attachment;
2152
2216
  });
@@ -2186,6 +2250,22 @@ class AttachmentService {
2186
2250
  fromAttachment: attachment,
2187
2251
  });
2188
2252
  }
2253
+ else if (attachment.type === 'voiceRecording') {
2254
+ attachmentUploads.push({
2255
+ url: attachment.asset_url,
2256
+ state: 'success',
2257
+ file: {
2258
+ name: attachment.title,
2259
+ size: attachment.file_size,
2260
+ type: attachment.mime_type,
2261
+ },
2262
+ type: 'voiceRecording',
2263
+ extraData: {
2264
+ duration: attachment.duration,
2265
+ waveform_data: attachment.waveform_data,
2266
+ },
2267
+ });
2268
+ }
2189
2269
  });
2190
2270
  if (attachmentUploads.length > 0) {
2191
2271
  this.attachmentUploadsSubject.next([
@@ -2194,18 +2274,20 @@ class AttachmentService {
2194
2274
  ]);
2195
2275
  }
2196
2276
  }
2197
- createPreview(file) {
2198
- const reader = new FileReader();
2199
- reader.onload = (event) => {
2277
+ async createPreview(file) {
2278
+ try {
2279
+ const uri = await createUriFromBlob(file);
2200
2280
  const attachmentUploads = this.attachmentUploadsSubject.getValue();
2201
2281
  const upload = attachmentUploads.find((upload) => upload.file === file);
2202
2282
  if (!upload) {
2203
2283
  return;
2204
2284
  }
2205
- upload.previewUri = event.target?.result || undefined;
2285
+ upload.previewUri = uri;
2206
2286
  this.attachmentUploadsSubject.next([...attachmentUploads]);
2207
- };
2208
- reader.readAsDataURL(file);
2287
+ }
2288
+ catch (e) {
2289
+ this.chatClientService?.chatClient?.logger('error', e instanceof Error ? e.message : `Can't create image preview`, { error: e, tag: ['AttachmentService'] });
2290
+ }
2209
2291
  }
2210
2292
  async uploadAttachments(uploads) {
2211
2293
  this.attachmentUploadInProgressCounterSubject.next(this.attachmentUploadInProgressCounterSubject.getValue() + 1);
@@ -2627,6 +2709,10 @@ const en = {
2627
2709
  Edited: 'Edited',
2628
2710
  'Error playing audio': 'Error playing audio',
2629
2711
  'Copy text': 'Copy text',
2712
+ 'Please grant permission to use microhpone': 'Please grant permission to use microhpone',
2713
+ 'Error starting recording': 'Error starting recording',
2714
+ 'An error has occurred during recording': 'An error has occurred during recording',
2715
+ 'Media recording not supported': 'Media recording not supported',
2630
2716
  },
2631
2717
  };
2632
2718
 
@@ -4232,12 +4318,100 @@ class ChannelListComponent {
4232
4318
  }
4233
4319
  }
4234
4320
  ChannelListComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: ChannelListComponent, deps: [{ token: ChannelService }, { token: CustomTemplatesService }, { token: ThemeService }], target: i0.ɵɵFactoryTarget.Component });
4235
- ChannelListComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.0.4", type: ChannelListComponent, selector: "stream-channel-list", ngImport: i0, template: "<div\n #container\n data-testid=\"channel-list-container\"\n class=\"str-chat str-chat-angular__channel-list str-chat__channel-list str-chat-channel-list messaging str-chat__theme-{{\n theme$ | async\n }}\"\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 <ng-content select=\"[channel-list-top]\"></ng-content>\n <div\n *ngIf=\"!(channels$ | async)?.length\"\n class=\"str-chat__channel-list-empty\"\n >\n <stream-icon icon=\"chat-bubble\"></stream-icon>\n <p data-testid=\"empty-channel-list-indicator\">\n {{ \"streamChat.You have no channels currently\" | translate }}\n </p>\n </div>\n <p\n *ngIf=\"!(channels$ | async)?.length\"\n class=\"str-chat__channel-list-empty-v1\"\n data-testid=\"empty-channel-list-indicator\"\n >\n {{ \"streamChat.You have no channels currently\" | translate }}\n </p>\n <stream-paginated-list\n [items]=\"(channels$ | async) ?? []\"\n [hasMore]=\"(hasMoreChannels$ | async) ?? false\"\n [isLoading]=\"isLoadingMoreChannels\"\n (loadMore)=\"loadMoreChannels()\"\n [trackBy]=\"trackByChannelId\"\n >\n <ng-template let-channel=\"item\">\n <ng-template #defaultTemplate let-channelInput=\"channel\">\n <stream-channel-preview\n data-testclass=\"channel-preview\"\n [channel]=\"channelInput\"\n ></stream-channel-preview>\n </ng-template>\n <div>\n <ng-container\n *ngTemplateOutlet=\"\n customChannelPreviewTemplate || defaultTemplate;\n context: { channel: channel }\n \"\n ></ng-container>\n </div>\n </ng-template>\n </stream-paginated-list>\n <ng-content select=\"[channel-list-bottom]\"></ng-content>\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>\n</ng-template>\n\n<ng-template #loadingChannels>\n <div\n data-testid=\"loading-indicator-full-size\"\n class=\"str-chat__loading-channels\"\n >\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\n class=\"str-chat__loading-channels-item str-chat__channel-preview-loading\"\n >\n <div class=\"str-chat__loading-channels-avatar\"></div>\n <div\n class=\"str-chat__loading-channels-meta str-chat__channel-preview-end-loading\"\n >\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", dependencies: [{ kind: "directive", type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i4.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: ChannelPreviewComponent, selector: "stream-channel-preview", inputs: ["channel"] }, { kind: "component", type: IconComponent, selector: "stream-icon", inputs: ["icon"] }, { kind: "component", type: PaginatedListComponent, selector: "stream-paginated-list", inputs: ["items", "isLoading", "hasMore", "trackBy"], outputs: ["loadMore"] }, { kind: "pipe", type: i4.AsyncPipe, name: "async" }, { kind: "pipe", type: i10.TranslatePipe, name: "translate" }] });
4321
+ ChannelListComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.0.4", type: ChannelListComponent, selector: "stream-channel-list", ngImport: i0, template: "<div\n #container\n data-testid=\"channel-list-container\"\n class=\"str-chat str-chat-angular__channel-list str-chat__channel-list str-chat-channel-list messaging str-chat__theme-{{\n theme$ | async\n }}\"\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 <ng-content select=\"[channel-list-top]\"></ng-content>\n <div\n *ngIf=\"!(channels$ | async)?.length\"\n class=\"str-chat__channel-list-empty\"\n >\n <stream-icon icon=\"chat-bubble\"></stream-icon>\n <p data-testid=\"empty-channel-list-indicator\">\n {{ \"streamChat.You have no channels currently\" | translate }}\n </p>\n </div>\n <p\n *ngIf=\"!(channels$ | async)?.length\"\n class=\"str-chat__channel-list-empty-v1\"\n data-testid=\"empty-channel-list-indicator\"\n >\n {{ \"streamChat.You have no channels currently\" | translate }}\n </p>\n <stream-paginated-list\n [items]=\"(channels$ | async) ?? []\"\n [hasMore]=\"(hasMoreChannels$ | async) ?? false\"\n [isLoading]=\"isLoadingMoreChannels\"\n (loadMore)=\"loadMoreChannels()\"\n [trackBy]=\"trackByChannelId\"\n >\n <ng-template let-channel=\"item\">\n <ng-template #defaultTemplate let-channelInput=\"channel\">\n <stream-channel-preview\n data-testclass=\"channel-preview\"\n [channel]=\"channelInput\"\n ></stream-channel-preview>\n </ng-template>\n <div>\n <ng-container\n *ngTemplateOutlet=\"\n customChannelPreviewTemplate || defaultTemplate;\n context: { channel: channel }\n \"\n ></ng-container>\n </div>\n </ng-template>\n </stream-paginated-list>\n <ng-content select=\"[channel-list-bottom]\"></ng-content>\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>\n</ng-template>\n\n<ng-template #loadingChannels>\n <div\n data-testid=\"loading-indicator-full-size\"\n class=\"str-chat__loading-channels\"\n >\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\n class=\"str-chat__loading-channels-item str-chat__channel-preview-loading\"\n >\n <div class=\"str-chat__loading-channels-avatar\"></div>\n <div\n class=\"str-chat__loading-channels-meta str-chat__channel-preview-end-loading\"\n >\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", dependencies: [{ kind: "directive", type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i4.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: IconComponent, selector: "stream-icon", inputs: ["icon"] }, { kind: "component", type: ChannelPreviewComponent, selector: "stream-channel-preview", inputs: ["channel"] }, { kind: "component", type: PaginatedListComponent, selector: "stream-paginated-list", inputs: ["items", "isLoading", "hasMore", "trackBy"], outputs: ["loadMore"] }, { kind: "pipe", type: i4.AsyncPipe, name: "async" }, { kind: "pipe", type: i10.TranslatePipe, name: "translate" }] });
4236
4322
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: ChannelListComponent, decorators: [{
4237
4323
  type: Component,
4238
4324
  args: [{ selector: 'stream-channel-list', template: "<div\n #container\n data-testid=\"channel-list-container\"\n class=\"str-chat str-chat-angular__channel-list str-chat__channel-list str-chat-channel-list messaging str-chat__theme-{{\n theme$ | async\n }}\"\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 <ng-content select=\"[channel-list-top]\"></ng-content>\n <div\n *ngIf=\"!(channels$ | async)?.length\"\n class=\"str-chat__channel-list-empty\"\n >\n <stream-icon icon=\"chat-bubble\"></stream-icon>\n <p data-testid=\"empty-channel-list-indicator\">\n {{ \"streamChat.You have no channels currently\" | translate }}\n </p>\n </div>\n <p\n *ngIf=\"!(channels$ | async)?.length\"\n class=\"str-chat__channel-list-empty-v1\"\n data-testid=\"empty-channel-list-indicator\"\n >\n {{ \"streamChat.You have no channels currently\" | translate }}\n </p>\n <stream-paginated-list\n [items]=\"(channels$ | async) ?? []\"\n [hasMore]=\"(hasMoreChannels$ | async) ?? false\"\n [isLoading]=\"isLoadingMoreChannels\"\n (loadMore)=\"loadMoreChannels()\"\n [trackBy]=\"trackByChannelId\"\n >\n <ng-template let-channel=\"item\">\n <ng-template #defaultTemplate let-channelInput=\"channel\">\n <stream-channel-preview\n data-testclass=\"channel-preview\"\n [channel]=\"channelInput\"\n ></stream-channel-preview>\n </ng-template>\n <div>\n <ng-container\n *ngTemplateOutlet=\"\n customChannelPreviewTemplate || defaultTemplate;\n context: { channel: channel }\n \"\n ></ng-container>\n </div>\n </ng-template>\n </stream-paginated-list>\n <ng-content select=\"[channel-list-bottom]\"></ng-content>\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>\n</ng-template>\n\n<ng-template #loadingChannels>\n <div\n data-testid=\"loading-indicator-full-size\"\n class=\"str-chat__loading-channels\"\n >\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\n class=\"str-chat__loading-channels-item str-chat__channel-preview-loading\"\n >\n <div class=\"str-chat__loading-channels-avatar\"></div>\n <div\n class=\"str-chat__loading-channels-meta str-chat__channel-preview-end-loading\"\n >\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" }]
4239
4325
  }], ctorParameters: function () { return [{ type: ChannelService }, { type: CustomTemplatesService }, { type: ThemeService }]; } });
4240
4326
 
4327
+ const formatDuration = (durationInSeconds) => {
4328
+ if (durationInSeconds === undefined || durationInSeconds <= 0)
4329
+ return '00:00';
4330
+ const [hours, hoursLeftover] = divMod$1(durationInSeconds, 3600);
4331
+ const [minutes, seconds] = divMod$1(hoursLeftover, 60);
4332
+ const roundedSeconds = Math.ceil(seconds);
4333
+ const prependHrsZero = hours.toString().length === 1 ? '0' : '';
4334
+ const prependMinZero = minutes.toString().length === 1 ? '0' : '';
4335
+ const prependSecZero = roundedSeconds.toString().length === 1 ? '0' : '';
4336
+ const minSec = `${prependMinZero}${minutes}:${prependSecZero}${roundedSeconds}`;
4337
+ return hours ? `${prependHrsZero}${hours}:` + minSec : minSec;
4338
+ };
4339
+ const divMod$1 = (num, divisor) => {
4340
+ return [Math.floor(num / divisor), num % divisor];
4341
+ };
4342
+
4343
+ const resampleWaveForm = (waveFormData, sampleSize) => {
4344
+ return waveFormData.length > sampleSize
4345
+ ? downsample(waveFormData, sampleSize)
4346
+ : upsample(waveFormData, sampleSize);
4347
+ };
4348
+ const downsample = (waveFormData, sampleSize) => {
4349
+ if (waveFormData.length <= sampleSize) {
4350
+ return waveFormData;
4351
+ }
4352
+ if (sampleSize === 1)
4353
+ return [mean(waveFormData)];
4354
+ const result = [];
4355
+ // bucket size adjusted due to the fact that the first and the last item in the original data array is kept in target output
4356
+ const bucketSize = (waveFormData.length - 2) / (sampleSize - 2);
4357
+ let lastSelectedPointIndex = 0;
4358
+ result.push(waveFormData[lastSelectedPointIndex]); // Always add the first point
4359
+ let maxAreaPoint, maxArea, triangleArea;
4360
+ for (let bucketIndex = 1; bucketIndex < sampleSize - 1; bucketIndex++) {
4361
+ const previousBucketRefPoint = waveFormData[lastSelectedPointIndex];
4362
+ const nextBucketMean = getNextBucketMean(waveFormData, bucketIndex, bucketSize);
4363
+ const currentBucketStartIndex = Math.floor((bucketIndex - 1) * bucketSize) + 1;
4364
+ const nextBucketStartIndex = Math.floor(bucketIndex * bucketSize) + 1;
4365
+ const countUnitsBetweenAtoC = 1 + nextBucketStartIndex - currentBucketStartIndex;
4366
+ maxArea = triangleArea = -1;
4367
+ for (let currentPointIndex = currentBucketStartIndex; currentPointIndex < nextBucketStartIndex; currentPointIndex++) {
4368
+ const countUnitsBetweenAtoB = Math.abs(currentPointIndex - currentBucketStartIndex) + 1;
4369
+ const countUnitsBetweenBtoC = countUnitsBetweenAtoC - countUnitsBetweenAtoB;
4370
+ const currentPointValue = waveFormData[currentPointIndex];
4371
+ triangleArea = triangleAreaHeron(triangleBase(Math.abs(previousBucketRefPoint - currentPointValue), countUnitsBetweenAtoB), triangleBase(Math.abs(currentPointValue - nextBucketMean), countUnitsBetweenBtoC), triangleBase(Math.abs(previousBucketRefPoint - nextBucketMean), countUnitsBetweenAtoC));
4372
+ if (triangleArea > maxArea) {
4373
+ maxArea = triangleArea;
4374
+ maxAreaPoint = waveFormData[currentPointIndex];
4375
+ lastSelectedPointIndex = currentPointIndex;
4376
+ }
4377
+ }
4378
+ if (typeof maxAreaPoint !== 'undefined')
4379
+ result.push(maxAreaPoint);
4380
+ }
4381
+ result.push(waveFormData[waveFormData.length - 1]); // Always add the last point
4382
+ return result;
4383
+ };
4384
+ const upsample = (waveFormData, sampleSize) => {
4385
+ if (sampleSize === waveFormData.length)
4386
+ return waveFormData;
4387
+ // eslint-disable-next-line prefer-const
4388
+ let [bucketSize, remainder] = divMod(sampleSize, waveFormData.length);
4389
+ const result = [];
4390
+ for (let i = 0; i < waveFormData.length; i++) {
4391
+ const extra = remainder && remainder-- ? 1 : 0;
4392
+ result.push(...Array(bucketSize + extra).fill(waveFormData[i]));
4393
+ }
4394
+ return result;
4395
+ };
4396
+ const getNextBucketMean = (data, currentBucketIndex, bucketSize) => {
4397
+ const nextBucketStartIndex = Math.floor(currentBucketIndex * bucketSize) + 1;
4398
+ let nextNextBucketStartIndex = Math.floor((currentBucketIndex + 1) * bucketSize) + 1;
4399
+ nextNextBucketStartIndex =
4400
+ nextNextBucketStartIndex < data.length
4401
+ ? nextNextBucketStartIndex
4402
+ : data.length;
4403
+ return mean(data.slice(nextBucketStartIndex, nextNextBucketStartIndex));
4404
+ };
4405
+ const mean = (values) => values.reduce((acc, value) => acc + value, 0) / values.length;
4406
+ const triangleAreaHeron = (a, b, c) => {
4407
+ const s = (a + b + c) / 2;
4408
+ return Math.sqrt(s * (s - a) * (s - b) * (s - c));
4409
+ };
4410
+ const triangleBase = (a, b) => Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2));
4411
+ const divMod = (num, divisor) => {
4412
+ return [Math.floor(num / divisor), num % divisor];
4413
+ };
4414
+
4241
4415
  /**
4242
4416
  * This component can be used to visualize the wave bar of a voice recording
4243
4417
  */
@@ -4254,36 +4428,6 @@ class VoiceRecordingWavebarComponent {
4254
4428
  this.isDragging = false;
4255
4429
  this.sampleSize = 40;
4256
4430
  this.isViewInited = false;
4257
- this.upsample = () => {
4258
- if (this.sampleSize === this.waveFormData.length)
4259
- return this.waveFormData;
4260
- // eslint-disable-next-line prefer-const
4261
- let [bucketSize, remainder] = this.divMod(this.sampleSize, this.waveFormData.length);
4262
- const result = [];
4263
- for (let i = 0; i < this.waveFormData.length; i++) {
4264
- const extra = remainder && remainder-- ? 1 : 0;
4265
- result.push(...Array(bucketSize + extra).fill(this.waveFormData[i]));
4266
- }
4267
- return result;
4268
- };
4269
- this.getNextBucketMean = (data, currentBucketIndex, bucketSize) => {
4270
- const nextBucketStartIndex = Math.floor(currentBucketIndex * bucketSize) + 1;
4271
- let nextNextBucketStartIndex = Math.floor((currentBucketIndex + 1) * bucketSize) + 1;
4272
- nextNextBucketStartIndex =
4273
- nextNextBucketStartIndex < data.length
4274
- ? nextNextBucketStartIndex
4275
- : data.length;
4276
- return this.mean(data.slice(nextBucketStartIndex, nextNextBucketStartIndex));
4277
- };
4278
- this.mean = (values) => values.reduce((acc, value) => acc + value, 0) / values.length;
4279
- this.triangleAreaHeron = (a, b, c) => {
4280
- const s = (a + b + c) / 2;
4281
- return Math.sqrt(s * (s - a) * (s - b) * (s - c));
4282
- };
4283
- this.triangleBase = (a, b) => Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2));
4284
- this.divMod = (num, divisor) => {
4285
- return [Math.floor(num / divisor), num % divisor];
4286
- };
4287
4431
  }
4288
4432
  ngOnInit() {
4289
4433
  this.containerSizeChanged();
@@ -4297,10 +4441,7 @@ class VoiceRecordingWavebarComponent {
4297
4441
  }
4298
4442
  ngOnChanges(changes) {
4299
4443
  if (changes.waveFormData) {
4300
- this.resampledWaveFormData =
4301
- this.waveFormData.length > this.sampleSize
4302
- ? this.downsample()
4303
- : this.upsample();
4444
+ this.resampledWaveFormData = resampleWaveForm(this.waveFormData, this.sampleSize);
4304
4445
  }
4305
4446
  if (changes.audioElement) {
4306
4447
  this.ngZone.runOutsideAngular(() => {
@@ -4354,10 +4495,7 @@ class VoiceRecordingWavebarComponent {
4354
4495
  sampleSize !== Infinity) {
4355
4496
  this.ngZone.run(() => {
4356
4497
  this.sampleSize = sampleSize;
4357
- this.resampledWaveFormData =
4358
- this.waveFormData.length > this.sampleSize
4359
- ? this.downsample()
4360
- : this.upsample();
4498
+ this.resampledWaveFormData = resampleWaveForm(this.waveFormData, this.sampleSize);
4361
4499
  if (this.isViewInited) {
4362
4500
  this.cdRef.detectChanges();
4363
4501
  }
@@ -4365,42 +4503,6 @@ class VoiceRecordingWavebarComponent {
4365
4503
  }
4366
4504
  }
4367
4505
  }
4368
- downsample() {
4369
- if (this.waveFormData.length <= this.sampleSize) {
4370
- return this.waveFormData;
4371
- }
4372
- if (this.sampleSize === 1)
4373
- return [this.mean(this.waveFormData)];
4374
- const result = [];
4375
- // bucket size adjusted due to the fact that the first and the last item in the original data array is kept in target output
4376
- const bucketSize = (this.waveFormData.length - 2) / (this.sampleSize - 2);
4377
- let lastSelectedPointIndex = 0;
4378
- result.push(this.waveFormData[lastSelectedPointIndex]); // Always add the first point
4379
- let maxAreaPoint, maxArea, triangleArea;
4380
- for (let bucketIndex = 1; bucketIndex < this.sampleSize - 1; bucketIndex++) {
4381
- const previousBucketRefPoint = this.waveFormData[lastSelectedPointIndex];
4382
- const nextBucketMean = this.getNextBucketMean(this.waveFormData, bucketIndex, bucketSize);
4383
- const currentBucketStartIndex = Math.floor((bucketIndex - 1) * bucketSize) + 1;
4384
- const nextBucketStartIndex = Math.floor(bucketIndex * bucketSize) + 1;
4385
- const countUnitsBetweenAtoC = 1 + nextBucketStartIndex - currentBucketStartIndex;
4386
- maxArea = triangleArea = -1;
4387
- for (let currentPointIndex = currentBucketStartIndex; currentPointIndex < nextBucketStartIndex; currentPointIndex++) {
4388
- const countUnitsBetweenAtoB = Math.abs(currentPointIndex - currentBucketStartIndex) + 1;
4389
- const countUnitsBetweenBtoC = countUnitsBetweenAtoC - countUnitsBetweenAtoB;
4390
- const currentPointValue = this.waveFormData[currentPointIndex];
4391
- triangleArea = this.triangleAreaHeron(this.triangleBase(Math.abs(previousBucketRefPoint - currentPointValue), countUnitsBetweenAtoB), this.triangleBase(Math.abs(currentPointValue - nextBucketMean), countUnitsBetweenBtoC), this.triangleBase(Math.abs(previousBucketRefPoint - nextBucketMean), countUnitsBetweenAtoC));
4392
- if (triangleArea > maxArea) {
4393
- maxArea = triangleArea;
4394
- maxAreaPoint = this.waveFormData[currentPointIndex];
4395
- lastSelectedPointIndex = currentPointIndex;
4396
- }
4397
- }
4398
- if (typeof maxAreaPoint !== 'undefined')
4399
- result.push(maxAreaPoint);
4400
- }
4401
- result.push(this.waveFormData[this.waveFormData.length - 1]); // Always add the last point
4402
- return result;
4403
- }
4404
4506
  }
4405
4507
  VoiceRecordingWavebarComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: VoiceRecordingWavebarComponent, deps: [{ token: i0.NgZone }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
4406
4508
  VoiceRecordingWavebarComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.0.4", type: VoiceRecordingWavebarComponent, selector: "stream-voice-recording-wavebar", inputs: { audioElement: "audioElement", waveFormData: "waveFormData", duration: "duration" }, viewQueries: [{ propertyName: "container", first: true, predicate: ["container"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: "<!--eslint-disable @angular-eslint/template/click-events-have-key-events-->\n<div\n #container\n class=\"str-chat__wave-progress-bar__track\"\n data-testid=\"wave-progress-bar-track\"\n role=\"progressbar\"\n (mousedown)=\"isDragging = true\"\n (mouseup)=\"isDragging = false\"\n (mouseleave)=\"isDragging = false\"\n (mousemove)=\"isDragging ? seek($event) : null\"\n (click)=\"seek($event)\"\n>\n <!--eslint-enable @angular-eslint/template/click-events-have-key-events-->\n <div\n *ngFor=\"\n let dataPoint of resampledWaveFormData;\n let i = index;\n trackBy: trackByIndex\n \"\n class=\"str-chat__wave-progress-bar__amplitude-bar\"\n [class.str-chat__wave-progress-bar__amplitude-bar--active]=\"\n progress > i / resampledWaveFormData.length\n \"\n [style.--str-chat__wave-progress-bar__amplitude-bar-height]=\"\n dataPoint ? dataPoint * 100 + '%' : '0%'\n \"\n ></div>\n <div\n class=\"str-chat__wave-progress-bar__progress-indicator\"\n data-testid=\"wave-progress-bar-progress-indicator\"\n [ngStyle]=\"{ 'inset-inline-start': progress * 100 + '%' }\"\n ></div>\n</div>\n", dependencies: [{ kind: "directive", type: i4.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i4.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }] });
@@ -4479,16 +4581,7 @@ class VoiceRecordingComponent {
4479
4581
  this.audioElement.nativeElement.playbackRate = playbackRate;
4480
4582
  }
4481
4583
  getFormattedDuration(duration) {
4482
- if (duration === undefined || duration <= 0)
4483
- return '00:00';
4484
- const [hours, hoursLeftover] = this.divMod(duration, 3600);
4485
- const [minutes, seconds] = this.divMod(hoursLeftover, 60);
4486
- const roundedSeconds = Math.ceil(seconds);
4487
- const prependHrsZero = hours.toString().length === 1 ? '0' : '';
4488
- const prependMinZero = minutes.toString().length === 1 ? '0' : '';
4489
- const prependSecZero = roundedSeconds.toString().length === 1 ? '0' : '';
4490
- const minSec = `${prependMinZero}${minutes}:${prependSecZero}${roundedSeconds}`;
4491
- return hours ? `${prependHrsZero}${hours}:` + minSec : minSec;
4584
+ return formatDuration(duration);
4492
4585
  }
4493
4586
  getFileSize() {
4494
4587
  if (this.attachment?.file_size === undefined ||
@@ -4497,9 +4590,6 @@ class VoiceRecordingComponent {
4497
4590
  }
4498
4591
  return prettybytes(Number(this.attachment.file_size || 0));
4499
4592
  }
4500
- divMod(num, divisor) {
4501
- return [Math.floor(num / divisor), num % divisor];
4502
- }
4503
4593
  }
4504
4594
  VoiceRecordingComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: VoiceRecordingComponent, deps: [{ token: i0.NgZone }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
4505
4595
  VoiceRecordingComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.0.4", type: VoiceRecordingComponent, selector: "stream-voice-recording", inputs: { attachment: "attachment" }, viewQueries: [{ propertyName: "audioElement", first: true, predicate: ["audioElement"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div\n class=\"str-chat__message-attachment__voice-recording-widget\"\n data-testid=\"voice-recording-widget\"\n [class.str-chat__message-attachment__voice-recording-widget--error]=\"isError\"\n>\n <!-- Empty event handlers to trigger change detection -->\n <audio\n #audioElement\n (play)=\"(null)\"\n (pause)=\"(null)\"\n (ended)=\"(null)\"\n (error)=\"isError = true\"\n (abort)=\"isError = true\"\n >\n <source\n data-testid=\"audio-source\"\n [src]=\"attachment?.asset_url\"\n [type]=\"attachment?.mime_type\"\n />\n </audio>\n <button\n class=\"str-chat__message-attachment-audio-widget--play-button\"\n data-testid=\"play-button\"\n (click)=\"togglePlay()\"\n >\n <stream-icon-placeholder\n [icon]=\"audioElement?.paused ? 'play' : 'pause'\"\n ></stream-icon-placeholder>\n </button>\n <div class=\"str-chat__message-attachment__voice-recording-widget__metadata\">\n <div class=\"str-chat__message-attachment-voice-recording-widget--first-row\">\n <div\n class=\"str-chat__message-attachment__voice-recording-widget__title\"\n data-testid=\"voice-recording-title\"\n [title]=\"attachment?.title\"\n >\n {{ attachment?.title }}\n </div>\n </div>\n\n <ng-container *ngIf=\"isError; else state\">\n <div\n class=\"str-chat__message-attachment__voice-recording-widget__error-message\"\n >\n <stream-icon-placeholder icon=\"error\"></stream-icon-placeholder>\n <span data-testid=\"error-message\">{{\n \"streamChat.Error playing audio\" | translate\n }}</span>\n </div>\n </ng-container>\n <ng-template #state>\n <div\n class=\"str-chat__message-attachment__voice-recording-widget__audio-state\"\n >\n <div\n class=\"str-chat__message-attachment__voice-recording-widget__timer\"\n >\n <span\n *ngIf=\"!!attachment?.duration; else fileSizeTemplate\"\n data-testid=\"duration\"\n >\n {{\n secondsElapsed > 0 || !audioElement.paused\n ? secondsElapsedFormatted\n : durationFormatted\n }}</span\n >\n <ng-template #fileSizeTemplate>\n <span\n class=\"str-chat__message-attachment-file--item-size\"\n data-testid=\"file-size-indicator\"\n >\n {{ fileSize }}\n </span>\n </ng-template>\n </div>\n <stream-voice-recording-wavebar\n *ngIf=\"attachment?.waveform_data && attachment?.duration\"\n [waveFormData]=\"attachment?.waveform_data || []\"\n [duration]=\"attachment?.duration\"\n [audioElement]=\"audioElement\"\n ></stream-voice-recording-wavebar>\n </div>\n </ng-template>\n </div>\n <div\n class=\"str-chat__message-attachment__voice-recording-widget__right-section\"\n >\n <button\n *ngIf=\"!audioElement?.paused; else fileIcon\"\n class=\"str-chat__message_attachment__playback-rate-button\"\n data-testid=\"playback-rate-button\"\n (click)=\"setPlaybackRate()\"\n >\n {{ audioElement?.playbackRate | number : \"1.1-1\" }}x\n </button>\n <ng-template #fileIcon>\n <stream-icon-placeholder\n class=\"str-chat__attachment-type-icon\"\n icon=\"audio-file\"\n ></stream-icon-placeholder>\n </ng-template>\n </div>\n</div>\n", dependencies: [{ kind: "directive", type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: IconPlaceholderComponent, selector: "stream-icon-placeholder", inputs: ["icon"] }, { kind: "component", type: VoiceRecordingWavebarComponent, selector: "stream-voice-recording-wavebar", inputs: ["audioElement", "waveFormData", "duration"] }, { kind: "pipe", type: i4.DecimalPipe, name: "number" }, { kind: "pipe", type: i10.TranslatePipe, name: "translate" }] });
@@ -4685,7 +4775,7 @@ class AttachmentListComponent {
4685
4775
  }
4686
4776
  }
4687
4777
  AttachmentListComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: AttachmentListComponent, deps: [{ token: CustomTemplatesService }, { token: ChannelService }, { token: AttachmentConfigurationService }], target: i0.ɵɵFactoryTarget.Component });
4688
- AttachmentListComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.0.4", type: AttachmentListComponent, selector: "stream-attachment-list", inputs: { messageId: "messageId", parentMessageId: "parentMessageId", attachments: "attachments" }, outputs: { imageModalStateChange: "imageModalStateChange" }, host: { properties: { "class": "this.class" } }, viewQueries: [{ propertyName: "modalContent", first: true, predicate: ["modalContent"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: "<div *ngIf=\"orderedAttachments.length > 0\" class=\"str-chat__attachment-list\">\n <ng-container\n *ngFor=\"let attachment of orderedAttachments; trackBy: trackByUrl\"\n >\n <div\n data-testclass=\"attachment-container\"\n class=\"str-chat__message-attachment str-chat__message-attachment--{{\n attachment.type\n }} str-chat__message-attachment-dynamic-size\"\n [class.str-chat__message-attachment--card]=\"isCard(attachment)\"\n [class.str-chat-angular__message-attachment-file-single]=\"\n isFile(attachment)\n \"\n [class.str-chat__message-attachment--voice-recording]=\"\n isVoiceMessage(attachment)\n \"\n [class.str-chat__message-attachment-with-actions]=\"\n attachment.actions && attachment.actions.length > 0\n \"\n [class.str-chat__message-attachment--svg-image]=\"isSvg(attachment)\"\n >\n <ng-container *ngIf=\"isImage(attachment)\">\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.imageAttachmentTemplate$ | async) ||\n defaultImage;\n context: getAttachmentContext(attachment)\n \"\n ></ng-container>\n <ng-template #defaultImage let-attachmentContext=\"attachment\">\n <img\n #imgElement\n class=\"str-chat__message-attachment--img\"\n data-testclass=\"image\"\n [src]=\"\n getImageAttachmentConfiguration(\n attachmentContext,\n 'single',\n imgElement\n ).url\n \"\n [alt]=\"attachmentContext?.fallback\"\n [ngStyle]=\"{\n height: getImageAttachmentConfiguration(\n attachmentContext,\n 'single',\n imgElement\n ).height,\n width: getImageAttachmentConfiguration(\n attachmentContext,\n 'single',\n imgElement\n ).width,\n '--original-height': getImageAttachmentConfiguration(\n attachmentContext,\n 'single',\n imgElement\n ).originalHeight,\n '--original-width': getImageAttachmentConfiguration(\n attachmentContext,\n 'single',\n imgElement\n ).originalWidth\n }\"\n (click)=\"openImageModal([attachmentContext])\"\n (keyup.enter)=\"openImageModal([attachmentContext])\"\n />\n </ng-template>\n </ng-container>\n <ng-container *ngIf=\"isGallery(attachment)\">\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.galleryAttachmentTemplate$ | async) ||\n defaultGallery;\n context: getAttachmentContext(attachment)\n \"\n ></ng-container>\n <ng-template #defaultGallery let-attachmentContext=\"attachment\">\n <div\n class=\"str-chat__gallery\"\n data-testid=\"image-gallery\"\n [class.str-chat__gallery--square]=\"\n (attachmentContext?.images)!.length > 3\n \"\n [class.str-chat__gallery-two-rows]=\"\n (attachmentContext?.images)!.length > 2\n \"\n >\n <ng-container\n *ngFor=\"\n let galleryImage of attachmentContext.images;\n let index = index;\n let isLast = last;\n trackBy: trackByImageUrl\n \"\n >\n <button\n *ngIf=\"index < 3 || (index === 3 && isLast)\"\n class=\"str-chat__gallery-image\"\n data-testclass=\"gallery-image\"\n [class.str-chat__message-attachment--svg-image]=\"\n isSvg(galleryImage)\n \"\n (click)=\"openImageModal(attachmentContext.images!, index)\"\n (keyup.enter)=\"openImageModal(attachmentContext.images!, index)\"\n >\n <img\n #imgElement\n fetchpriority=\"low\"\n loading=\"lazy\"\n [src]=\"\n getImageAttachmentConfiguration(\n galleryImage,\n 'gallery',\n imgElement\n ).url\n \"\n [alt]=\"galleryImage.fallback\"\n [style.--original-height]=\"\n getImageAttachmentConfiguration(\n galleryImage,\n 'gallery',\n imgElement\n ).originalHeight\n \"\n [style.--original-width]=\"\n getImageAttachmentConfiguration(\n galleryImage,\n 'gallery',\n imgElement\n ).originalWidth\n \"\n [ngStyle]=\"{\n height: getImageAttachmentConfiguration(\n galleryImage,\n 'gallery',\n imgElement\n ).height,\n width: getImageAttachmentConfiguration(\n galleryImage,\n 'gallery',\n imgElement\n ).width\n }\"\n />\n </button>\n <button\n *ngIf=\"index === 3 && !isLast\"\n #element\n class=\"str-chat__gallery-placeholder\"\n data-testclass=\"gallery-image\"\n data-testid=\"more-image-button\"\n [class.str-chat__message-attachment--svg-image]=\"\n isSvg(galleryImage)\n \"\n [ngStyle]=\"{\n 'background-image':\n 'url(' +\n getImageAttachmentConfiguration(\n galleryImage,\n 'gallery',\n element\n ).url +\n ')',\n height: getImageAttachmentConfiguration(\n galleryImage,\n 'gallery',\n element\n ).height,\n width: getImageAttachmentConfiguration(\n galleryImage,\n 'gallery',\n element\n ).width,\n '--original-height': getImageAttachmentConfiguration(\n galleryImage,\n 'gallery',\n element\n ).originalHeight,\n '--original-width': getImageAttachmentConfiguration(\n galleryImage,\n 'gallery',\n element\n ).originalWidth\n }\"\n (click)=\"openImageModal(attachmentContext.images!, index)\"\n (keyup.enter)=\"openImageModal(attachmentContext.images!, index)\"\n >\n <p\n [innerHTML]=\"\n 'streamChat.{{ imageCount }} more'\n | translate\n : { imageCount: attachmentContext!.images!.length - 4 }\n \"\n ></p>\n </button>\n </ng-container>\n </div>\n </ng-template>\n </ng-container>\n <ng-container *ngIf=\"isVideo(attachment)\">\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.videoAttachmentTemplate$ | async) ||\n defaultVideo;\n context: getAttachmentContext(attachment)\n \"\n ></ng-container>\n <ng-template #defaultVideo let-attachmentContext=\"attachment\">\n <div\n class=\"str-chat__player-wrapper\"\n data-testclass=\"video-attachment-parent\"\n [style.--original-height]=\"\n getVideoAttachmentConfiguration(attachmentContext, videoElement)\n .originalHeight\n \"\n [style.--original-width]=\"\n getVideoAttachmentConfiguration(attachmentContext, videoElement)\n .originalWidth\n \"\n [ngStyle]=\"{\n height: getVideoAttachmentConfiguration(\n attachmentContext,\n videoElement\n ).height,\n width: getVideoAttachmentConfiguration(\n attachmentContext,\n videoElement\n ).width\n }\"\n >\n <video\n #videoElement\n class=\"str-chat__video-angular\"\n controls\n data-testclass=\"video-attachment\"\n [src]=\"\n getVideoAttachmentConfiguration(attachmentContext, videoElement)\n .url\n \"\n [poster]=\"\n getVideoAttachmentConfiguration(attachmentContext, videoElement)\n .thumbUrl\n \"\n ></video>\n </div>\n </ng-template>\n </ng-container>\n <ng-container *ngIf=\"isFile(attachment)\">\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.fileAttachmentTemplate$ | async) ||\n defaultFile;\n context: getAttachmentContext(attachment)\n \"\n ></ng-container>\n <ng-template #defaultFile let-attachmentContext=\"attachment\">\n <div\n class=\"str-chat__message-attachment-file--item str-chat-angular__message-attachment-file-single\"\n >\n <stream-icon-placeholder\n class=\"str-chat__attachment-type-icon\"\n icon=\"unspecified-filetype\"\n ></stream-icon-placeholder>\n <div class=\"str-chat__message-attachment-file--item-text\">\n <a\n class=\"str-chat__message-attachment-file--item-first-row\"\n data-testclass=\"file-link\"\n target=\"_blank\"\n href=\"{{ attachmentContext.asset_url }}\"\n >\n <div\n data-testclass=\"file-title\"\n class=\"str-chat__message-attachment-file--item-name\"\n >\n {{ attachmentContext.title }}\n </div>\n <stream-icon-placeholder\n class=\"str-chat__message-attachment-download-icon\"\n icon=\"download\"\n ></stream-icon-placeholder>\n </a>\n <span\n *ngIf=\"hasFileSize(attachmentContext)\"\n class=\"str-chat__message-attachment-file--item-size\"\n data-testclass=\"size\"\n >{{ getFileSize(attachmentContext) }}</span\n >\n </div>\n </div>\n </ng-template>\n </ng-container>\n <ng-container *ngIf=\"isVoiceMessage(attachment)\">\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.voiceRecordingAttachmentTemplate$\n | async) || defaultRecording;\n context: getAttachmentContext(attachment)\n \"\n ></ng-container>\n <ng-template #defaultRecording>\n <stream-voice-recording\n data-testclass=\"voice-recording\"\n [attachment]=\"attachment\"\n ></stream-voice-recording>\n </ng-template>\n </ng-container>\n <ng-container\n *ngIf=\"\n isCard(attachment) &&\n getCardAttachmentConfiguration(attachment) as attachmentConfiguration\n \"\n >\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.cardAttachmentTemplate$ | async) ||\n defaultCard;\n context: getAttachmentContext(attachment)\n \"\n ></ng-container>\n <ng-template #defaultCard let-attachmentContext=\"attachment\">\n <div\n class=\"str-chat__message-attachment-card str-chat__message-attachment-card--{{\n attachmentContext.type\n }}\"\n >\n <div\n *ngIf=\"attachmentConfiguration.url\"\n class=\"str-chat__message-attachment-card--header\"\n >\n <img\n fetchpriority=\"low\"\n loading=\"lazy\"\n data-testclass=\"card-img\"\n alt=\"{{ attachmentConfiguration.url }}\"\n src=\"{{ attachmentConfiguration.url }}\"\n [ngStyle]=\"{\n height: attachmentConfiguration.height,\n width: attachmentConfiguration.width\n }\"\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=\"attachmentContext.title\"\n data-testclass=\"card-title\"\n class=\"str-chat__message-attachment-card--title\"\n >\n {{ attachmentContext.title }}\n </div>\n <div\n *ngIf=\"attachmentContext.text\"\n class=\"str-chat__message-attachment-card--text\"\n data-testclass=\"card-text\"\n >\n {{ attachmentContext.text }}\n </div>\n <a\n *ngIf=\"\n attachmentContext.title_link ||\n attachmentContext.og_scrape_url\n \"\n class=\"str-chat__message-attachment-card--url\"\n data-testclass=\"url-link\"\n noopener\n noreferrer\n target=\"_blank\"\n href=\"{{\n attachmentContext.title_link ||\n attachmentContext.og_scrape_url\n }}\"\n >\n {{\n trimUrl(\n attachmentContext.title_link ||\n attachmentContext.og_scrape_url\n )\n }}\n </a>\n </div>\n </div>\n </div>\n </ng-template>\n </ng-container>\n <ng-container *ngIf=\"attachment.actions && attachment.actions.length > 0\">\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.attachmentActionsTemplate$ | async) ||\n defaultActions;\n context: getAttachmentContext(attachment)\n \"\n ></ng-container>\n <ng-template #defaultActions let-attachmentContext=\"attachment\">\n <div class=\"str-chat__message-attachment-actions\">\n <div class=\"str-chat__message-attachment-actions-form\">\n <button\n *ngFor=\"\n let action of attachmentContext.actions;\n trackBy: trackByActionValue\n \"\n data-testclass=\"attachment-action\"\n class=\"str-chat__message-attachment-actions-button str-chat__message-attachment-actions-button--{{\n action.style\n }}\"\n (click)=\"sendAction(action)\"\n (keyup.enter)=\"sendAction(action)\"\n >\n {{ action.text }}\n </button>\n </div>\n </div>\n </ng-template>\n </ng-container>\n </div>\n </ng-container>\n\n <ng-container *ngIf=\"imagesToView && imagesToView.length > 0\">\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.modalTemplate$ | async) || defaultModal;\n context: getModalContext()\n \"\n ></ng-container>\n </ng-container>\n</div>\n\n<ng-template\n #defaultModal\n let-isOpen=\"isOpen\"\n let-isOpenChangeHandler=\"isOpenChangeHandler\"\n let-content=\"content\"\n>\n <stream-modal\n class=\"stream-chat-angular__image-modal-host\"\n [isOpen]=\"isOpen\"\n [content]=\"content\"\n (isOpenChange)=\"isOpenChangeHandler($event)\"\n >\n </stream-modal>\n</ng-template>\n\n<ng-template #modalContent>\n <div class=\"stream-chat-angular__image-modal str-chat__image-carousel\">\n <img\n #imgElement\n class=\"stream-chat-angular__image-modal-image str-chat__image-carousel-image\"\n data-testid=\"modal-image\"\n [src]=\"\n getCarouselImageAttachmentConfiguration(\n imagesToView[imagesToViewCurrentIndex],\n imgElement\n ).url\n \"\n [style.--original-height]=\"\n getCarouselImageAttachmentConfiguration(\n imagesToView[imagesToViewCurrentIndex],\n imgElement\n ).originalHeight\n \"\n [style.--original-width]=\"\n getCarouselImageAttachmentConfiguration(\n imagesToView[imagesToViewCurrentIndex],\n imgElement\n ).originalWidth\n \"\n [alt]=\"imagesToView[imagesToViewCurrentIndex].fallback\"\n [ngStyle]=\"{\n width: getCarouselImageAttachmentConfiguration(\n imagesToView[imagesToViewCurrentIndex],\n imgElement\n ).width,\n height: getCarouselImageAttachmentConfiguration(\n imagesToView[imagesToViewCurrentIndex],\n imgElement\n ).height\n }\"\n />\n <div>\n <button\n class=\"stream-chat-angular__image-modal-stepper str-chat__image-carousel-stepper str-chat__image-carousel-stepper-prev\"\n data-testid=\"image-modal-prev\"\n type=\"button\"\n [ngStyle]=\"{\n visibility: isImageModalPrevButtonVisible ? 'visible' : 'hidden'\n }\"\n (click)=\"stepImages(-1)\"\n (keyup.enter)=\"stepImages(-1)\"\n >\n <stream-icon-placeholder icon=\"arrow-left\"></stream-icon-placeholder>\n </button>\n <button\n class=\"stream-chat-angular__image-modal-stepper str-chat__image-carousel-stepper str-chat__image-carousel-stepper-next\"\n type=\"button\"\n data-testid=\"image-modal-next\"\n [ngStyle]=\"{\n visibility: isImageModalNextButtonVisible ? 'visible' : 'hidden'\n }\"\n (click)=\"stepImages(1)\"\n (keyup.enter)=\"stepImages(1)\"\n >\n <stream-icon-placeholder icon=\"arrow-right\"></stream-icon-placeholder>\n </button>\n </div>\n </div>\n</ng-template>\n", dependencies: [{ kind: "directive", type: i4.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i4.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i4.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: ModalComponent, selector: "stream-modal", inputs: ["isOpen", "content"], outputs: ["isOpenChange"] }, { kind: "component", type: IconPlaceholderComponent, selector: "stream-icon-placeholder", inputs: ["icon"] }, { kind: "component", type: VoiceRecordingComponent, selector: "stream-voice-recording", inputs: ["attachment"] }, { kind: "pipe", type: i4.AsyncPipe, name: "async" }, { kind: "pipe", type: i10.TranslatePipe, name: "translate" }] });
4778
+ AttachmentListComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.0.4", type: AttachmentListComponent, selector: "stream-attachment-list", inputs: { messageId: "messageId", parentMessageId: "parentMessageId", attachments: "attachments" }, outputs: { imageModalStateChange: "imageModalStateChange" }, host: { properties: { "class": "this.class" } }, viewQueries: [{ propertyName: "modalContent", first: true, predicate: ["modalContent"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: "<div *ngIf=\"orderedAttachments.length > 0\" class=\"str-chat__attachment-list\">\n <ng-container\n *ngFor=\"let attachment of orderedAttachments; trackBy: trackByUrl\"\n >\n <div\n data-testclass=\"attachment-container\"\n class=\"str-chat__message-attachment str-chat__message-attachment--{{\n attachment.type\n }} str-chat__message-attachment-dynamic-size\"\n [class.str-chat__message-attachment--card]=\"isCard(attachment)\"\n [class.str-chat-angular__message-attachment-file-single]=\"\n isFile(attachment)\n \"\n [class.str-chat__message-attachment--voice-recording]=\"\n isVoiceMessage(attachment)\n \"\n [class.str-chat__message-attachment-with-actions]=\"\n attachment.actions && attachment.actions.length > 0\n \"\n [class.str-chat__message-attachment--svg-image]=\"isSvg(attachment)\"\n >\n <ng-container *ngIf=\"isImage(attachment)\">\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.imageAttachmentTemplate$ | async) ||\n defaultImage;\n context: getAttachmentContext(attachment)\n \"\n ></ng-container>\n <ng-template #defaultImage let-attachmentContext=\"attachment\">\n <img\n #imgElement\n class=\"str-chat__message-attachment--img\"\n data-testclass=\"image\"\n [src]=\"\n getImageAttachmentConfiguration(\n attachmentContext,\n 'single',\n imgElement\n ).url\n \"\n [alt]=\"attachmentContext?.fallback\"\n [ngStyle]=\"{\n height: getImageAttachmentConfiguration(\n attachmentContext,\n 'single',\n imgElement\n ).height,\n width: getImageAttachmentConfiguration(\n attachmentContext,\n 'single',\n imgElement\n ).width,\n '--original-height': getImageAttachmentConfiguration(\n attachmentContext,\n 'single',\n imgElement\n ).originalHeight,\n '--original-width': getImageAttachmentConfiguration(\n attachmentContext,\n 'single',\n imgElement\n ).originalWidth\n }\"\n (click)=\"openImageModal([attachmentContext])\"\n (keyup.enter)=\"openImageModal([attachmentContext])\"\n />\n </ng-template>\n </ng-container>\n <ng-container *ngIf=\"isGallery(attachment)\">\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.galleryAttachmentTemplate$ | async) ||\n defaultGallery;\n context: getAttachmentContext(attachment)\n \"\n ></ng-container>\n <ng-template #defaultGallery let-attachmentContext=\"attachment\">\n <div\n class=\"str-chat__gallery\"\n data-testid=\"image-gallery\"\n [class.str-chat__gallery--square]=\"\n (attachmentContext?.images)!.length > 3\n \"\n [class.str-chat__gallery-two-rows]=\"\n (attachmentContext?.images)!.length > 2\n \"\n >\n <ng-container\n *ngFor=\"\n let galleryImage of attachmentContext.images;\n let index = index;\n let isLast = last;\n trackBy: trackByImageUrl\n \"\n >\n <button\n *ngIf=\"index < 3 || (index === 3 && isLast)\"\n class=\"str-chat__gallery-image\"\n data-testclass=\"gallery-image\"\n [class.str-chat__message-attachment--svg-image]=\"\n isSvg(galleryImage)\n \"\n (click)=\"openImageModal(attachmentContext.images!, index)\"\n (keyup.enter)=\"openImageModal(attachmentContext.images!, index)\"\n >\n <img\n #imgElement\n fetchpriority=\"low\"\n loading=\"lazy\"\n [src]=\"\n getImageAttachmentConfiguration(\n galleryImage,\n 'gallery',\n imgElement\n ).url\n \"\n [alt]=\"galleryImage.fallback\"\n [style.--original-height]=\"\n getImageAttachmentConfiguration(\n galleryImage,\n 'gallery',\n imgElement\n ).originalHeight\n \"\n [style.--original-width]=\"\n getImageAttachmentConfiguration(\n galleryImage,\n 'gallery',\n imgElement\n ).originalWidth\n \"\n [ngStyle]=\"{\n height: getImageAttachmentConfiguration(\n galleryImage,\n 'gallery',\n imgElement\n ).height,\n width: getImageAttachmentConfiguration(\n galleryImage,\n 'gallery',\n imgElement\n ).width\n }\"\n />\n </button>\n <button\n *ngIf=\"index === 3 && !isLast\"\n #element\n class=\"str-chat__gallery-placeholder\"\n data-testclass=\"gallery-image\"\n data-testid=\"more-image-button\"\n [class.str-chat__message-attachment--svg-image]=\"\n isSvg(galleryImage)\n \"\n [ngStyle]=\"{\n 'background-image':\n 'url(' +\n getImageAttachmentConfiguration(\n galleryImage,\n 'gallery',\n element\n ).url +\n ')',\n height: getImageAttachmentConfiguration(\n galleryImage,\n 'gallery',\n element\n ).height,\n width: getImageAttachmentConfiguration(\n galleryImage,\n 'gallery',\n element\n ).width,\n '--original-height': getImageAttachmentConfiguration(\n galleryImage,\n 'gallery',\n element\n ).originalHeight,\n '--original-width': getImageAttachmentConfiguration(\n galleryImage,\n 'gallery',\n element\n ).originalWidth\n }\"\n (click)=\"openImageModal(attachmentContext.images!, index)\"\n (keyup.enter)=\"openImageModal(attachmentContext.images!, index)\"\n >\n <p\n [innerHTML]=\"\n 'streamChat.{{ imageCount }} more'\n | translate\n : { imageCount: attachmentContext!.images!.length - 4 }\n \"\n ></p>\n </button>\n </ng-container>\n </div>\n </ng-template>\n </ng-container>\n <ng-container *ngIf=\"isVideo(attachment)\">\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.videoAttachmentTemplate$ | async) ||\n defaultVideo;\n context: getAttachmentContext(attachment)\n \"\n ></ng-container>\n <ng-template #defaultVideo let-attachmentContext=\"attachment\">\n <div\n class=\"str-chat__player-wrapper\"\n data-testclass=\"video-attachment-parent\"\n [style.--original-height]=\"\n getVideoAttachmentConfiguration(attachmentContext, videoElement)\n .originalHeight\n \"\n [style.--original-width]=\"\n getVideoAttachmentConfiguration(attachmentContext, videoElement)\n .originalWidth\n \"\n [ngStyle]=\"{\n height: getVideoAttachmentConfiguration(\n attachmentContext,\n videoElement\n ).height,\n width: getVideoAttachmentConfiguration(\n attachmentContext,\n videoElement\n ).width\n }\"\n >\n <video\n #videoElement\n class=\"str-chat__video-angular\"\n controls\n data-testclass=\"video-attachment\"\n [src]=\"\n getVideoAttachmentConfiguration(attachmentContext, videoElement)\n .url\n \"\n [poster]=\"\n getVideoAttachmentConfiguration(attachmentContext, videoElement)\n .thumbUrl\n \"\n ></video>\n </div>\n </ng-template>\n </ng-container>\n <ng-container *ngIf=\"isFile(attachment)\">\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.fileAttachmentTemplate$ | async) ||\n defaultFile;\n context: getAttachmentContext(attachment)\n \"\n ></ng-container>\n <ng-template #defaultFile let-attachmentContext=\"attachment\">\n <div\n class=\"str-chat__message-attachment-file--item str-chat-angular__message-attachment-file-single\"\n >\n <stream-icon-placeholder\n class=\"str-chat__attachment-type-icon\"\n icon=\"unspecified-filetype\"\n ></stream-icon-placeholder>\n <div class=\"str-chat__message-attachment-file--item-text\">\n <a\n class=\"str-chat__message-attachment-file--item-first-row\"\n data-testclass=\"file-link\"\n target=\"_blank\"\n href=\"{{ attachmentContext.asset_url }}\"\n >\n <div\n data-testclass=\"file-title\"\n class=\"str-chat__message-attachment-file--item-name\"\n >\n {{ attachmentContext.title }}\n </div>\n <stream-icon-placeholder\n class=\"str-chat__message-attachment-download-icon\"\n icon=\"download\"\n ></stream-icon-placeholder>\n </a>\n <span\n *ngIf=\"hasFileSize(attachmentContext)\"\n class=\"str-chat__message-attachment-file--item-size\"\n data-testclass=\"size\"\n >{{ getFileSize(attachmentContext) }}</span\n >\n </div>\n </div>\n </ng-template>\n </ng-container>\n <ng-container *ngIf=\"isVoiceMessage(attachment)\">\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.voiceRecordingAttachmentTemplate$\n | async) || defaultRecording;\n context: getAttachmentContext(attachment)\n \"\n ></ng-container>\n <ng-template #defaultRecording>\n <stream-voice-recording\n data-testclass=\"voice-recording\"\n [attachment]=\"attachment\"\n ></stream-voice-recording>\n </ng-template>\n </ng-container>\n <ng-container\n *ngIf=\"\n isCard(attachment) &&\n getCardAttachmentConfiguration(attachment) as attachmentConfiguration\n \"\n >\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.cardAttachmentTemplate$ | async) ||\n defaultCard;\n context: getAttachmentContext(attachment)\n \"\n ></ng-container>\n <ng-template #defaultCard let-attachmentContext=\"attachment\">\n <div\n class=\"str-chat__message-attachment-card str-chat__message-attachment-card--{{\n attachmentContext.type\n }}\"\n >\n <div\n *ngIf=\"attachmentConfiguration.url\"\n class=\"str-chat__message-attachment-card--header\"\n >\n <img\n fetchpriority=\"low\"\n loading=\"lazy\"\n data-testclass=\"card-img\"\n alt=\"{{ attachmentConfiguration.url }}\"\n src=\"{{ attachmentConfiguration.url }}\"\n [ngStyle]=\"{\n height: attachmentConfiguration.height,\n width: attachmentConfiguration.width\n }\"\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=\"attachmentContext.title\"\n data-testclass=\"card-title\"\n class=\"str-chat__message-attachment-card--title\"\n >\n {{ attachmentContext.title }}\n </div>\n <div\n *ngIf=\"attachmentContext.text\"\n class=\"str-chat__message-attachment-card--text\"\n data-testclass=\"card-text\"\n >\n {{ attachmentContext.text }}\n </div>\n <a\n *ngIf=\"\n attachmentContext.title_link ||\n attachmentContext.og_scrape_url\n \"\n class=\"str-chat__message-attachment-card--url\"\n data-testclass=\"url-link\"\n noopener\n noreferrer\n target=\"_blank\"\n href=\"{{\n attachmentContext.title_link ||\n attachmentContext.og_scrape_url\n }}\"\n >\n {{\n trimUrl(\n attachmentContext.title_link ||\n attachmentContext.og_scrape_url\n )\n }}\n </a>\n </div>\n </div>\n </div>\n </ng-template>\n </ng-container>\n <ng-container *ngIf=\"attachment.actions && attachment.actions.length > 0\">\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.attachmentActionsTemplate$ | async) ||\n defaultActions;\n context: getAttachmentContext(attachment)\n \"\n ></ng-container>\n <ng-template #defaultActions let-attachmentContext=\"attachment\">\n <div class=\"str-chat__message-attachment-actions\">\n <div class=\"str-chat__message-attachment-actions-form\">\n <button\n *ngFor=\"\n let action of attachmentContext.actions;\n trackBy: trackByActionValue\n \"\n data-testclass=\"attachment-action\"\n class=\"str-chat__message-attachment-actions-button str-chat__message-attachment-actions-button--{{\n action.style\n }}\"\n (click)=\"sendAction(action)\"\n (keyup.enter)=\"sendAction(action)\"\n >\n {{ action.text }}\n </button>\n </div>\n </div>\n </ng-template>\n </ng-container>\n </div>\n </ng-container>\n\n <ng-container *ngIf=\"imagesToView && imagesToView.length > 0\">\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.modalTemplate$ | async) || defaultModal;\n context: getModalContext()\n \"\n ></ng-container>\n </ng-container>\n</div>\n\n<ng-template\n #defaultModal\n let-isOpen=\"isOpen\"\n let-isOpenChangeHandler=\"isOpenChangeHandler\"\n let-content=\"content\"\n>\n <stream-modal\n class=\"stream-chat-angular__image-modal-host\"\n [isOpen]=\"isOpen\"\n [content]=\"content\"\n (isOpenChange)=\"isOpenChangeHandler($event)\"\n >\n </stream-modal>\n</ng-template>\n\n<ng-template #modalContent>\n <div class=\"stream-chat-angular__image-modal str-chat__image-carousel\">\n <img\n #imgElement\n class=\"stream-chat-angular__image-modal-image str-chat__image-carousel-image\"\n data-testid=\"modal-image\"\n [src]=\"\n getCarouselImageAttachmentConfiguration(\n imagesToView[imagesToViewCurrentIndex],\n imgElement\n ).url\n \"\n [style.--original-height]=\"\n getCarouselImageAttachmentConfiguration(\n imagesToView[imagesToViewCurrentIndex],\n imgElement\n ).originalHeight\n \"\n [style.--original-width]=\"\n getCarouselImageAttachmentConfiguration(\n imagesToView[imagesToViewCurrentIndex],\n imgElement\n ).originalWidth\n \"\n [alt]=\"imagesToView[imagesToViewCurrentIndex].fallback\"\n [ngStyle]=\"{\n width: getCarouselImageAttachmentConfiguration(\n imagesToView[imagesToViewCurrentIndex],\n imgElement\n ).width,\n height: getCarouselImageAttachmentConfiguration(\n imagesToView[imagesToViewCurrentIndex],\n imgElement\n ).height\n }\"\n />\n <div>\n <button\n class=\"stream-chat-angular__image-modal-stepper str-chat__image-carousel-stepper str-chat__image-carousel-stepper-prev\"\n data-testid=\"image-modal-prev\"\n type=\"button\"\n [ngStyle]=\"{\n visibility: isImageModalPrevButtonVisible ? 'visible' : 'hidden'\n }\"\n (click)=\"stepImages(-1)\"\n (keyup.enter)=\"stepImages(-1)\"\n >\n <stream-icon-placeholder icon=\"arrow-left\"></stream-icon-placeholder>\n </button>\n <button\n class=\"stream-chat-angular__image-modal-stepper str-chat__image-carousel-stepper str-chat__image-carousel-stepper-next\"\n type=\"button\"\n data-testid=\"image-modal-next\"\n [ngStyle]=\"{\n visibility: isImageModalNextButtonVisible ? 'visible' : 'hidden'\n }\"\n (click)=\"stepImages(1)\"\n (keyup.enter)=\"stepImages(1)\"\n >\n <stream-icon-placeholder icon=\"arrow-right\"></stream-icon-placeholder>\n </button>\n </div>\n </div>\n</ng-template>\n", dependencies: [{ kind: "directive", type: i4.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i4.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i4.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: VoiceRecordingComponent, selector: "stream-voice-recording", inputs: ["attachment"] }, { kind: "component", type: IconPlaceholderComponent, selector: "stream-icon-placeholder", inputs: ["icon"] }, { kind: "component", type: ModalComponent, selector: "stream-modal", inputs: ["isOpen", "content"], outputs: ["isOpenChange"] }, { kind: "pipe", type: i4.AsyncPipe, name: "async" }, { kind: "pipe", type: i10.TranslatePipe, name: "translate" }] });
4689
4779
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: AttachmentListComponent, decorators: [{
4690
4780
  type: Component,
4691
4781
  args: [{ selector: 'stream-attachment-list', template: "<div *ngIf=\"orderedAttachments.length > 0\" class=\"str-chat__attachment-list\">\n <ng-container\n *ngFor=\"let attachment of orderedAttachments; trackBy: trackByUrl\"\n >\n <div\n data-testclass=\"attachment-container\"\n class=\"str-chat__message-attachment str-chat__message-attachment--{{\n attachment.type\n }} str-chat__message-attachment-dynamic-size\"\n [class.str-chat__message-attachment--card]=\"isCard(attachment)\"\n [class.str-chat-angular__message-attachment-file-single]=\"\n isFile(attachment)\n \"\n [class.str-chat__message-attachment--voice-recording]=\"\n isVoiceMessage(attachment)\n \"\n [class.str-chat__message-attachment-with-actions]=\"\n attachment.actions && attachment.actions.length > 0\n \"\n [class.str-chat__message-attachment--svg-image]=\"isSvg(attachment)\"\n >\n <ng-container *ngIf=\"isImage(attachment)\">\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.imageAttachmentTemplate$ | async) ||\n defaultImage;\n context: getAttachmentContext(attachment)\n \"\n ></ng-container>\n <ng-template #defaultImage let-attachmentContext=\"attachment\">\n <img\n #imgElement\n class=\"str-chat__message-attachment--img\"\n data-testclass=\"image\"\n [src]=\"\n getImageAttachmentConfiguration(\n attachmentContext,\n 'single',\n imgElement\n ).url\n \"\n [alt]=\"attachmentContext?.fallback\"\n [ngStyle]=\"{\n height: getImageAttachmentConfiguration(\n attachmentContext,\n 'single',\n imgElement\n ).height,\n width: getImageAttachmentConfiguration(\n attachmentContext,\n 'single',\n imgElement\n ).width,\n '--original-height': getImageAttachmentConfiguration(\n attachmentContext,\n 'single',\n imgElement\n ).originalHeight,\n '--original-width': getImageAttachmentConfiguration(\n attachmentContext,\n 'single',\n imgElement\n ).originalWidth\n }\"\n (click)=\"openImageModal([attachmentContext])\"\n (keyup.enter)=\"openImageModal([attachmentContext])\"\n />\n </ng-template>\n </ng-container>\n <ng-container *ngIf=\"isGallery(attachment)\">\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.galleryAttachmentTemplate$ | async) ||\n defaultGallery;\n context: getAttachmentContext(attachment)\n \"\n ></ng-container>\n <ng-template #defaultGallery let-attachmentContext=\"attachment\">\n <div\n class=\"str-chat__gallery\"\n data-testid=\"image-gallery\"\n [class.str-chat__gallery--square]=\"\n (attachmentContext?.images)!.length > 3\n \"\n [class.str-chat__gallery-two-rows]=\"\n (attachmentContext?.images)!.length > 2\n \"\n >\n <ng-container\n *ngFor=\"\n let galleryImage of attachmentContext.images;\n let index = index;\n let isLast = last;\n trackBy: trackByImageUrl\n \"\n >\n <button\n *ngIf=\"index < 3 || (index === 3 && isLast)\"\n class=\"str-chat__gallery-image\"\n data-testclass=\"gallery-image\"\n [class.str-chat__message-attachment--svg-image]=\"\n isSvg(galleryImage)\n \"\n (click)=\"openImageModal(attachmentContext.images!, index)\"\n (keyup.enter)=\"openImageModal(attachmentContext.images!, index)\"\n >\n <img\n #imgElement\n fetchpriority=\"low\"\n loading=\"lazy\"\n [src]=\"\n getImageAttachmentConfiguration(\n galleryImage,\n 'gallery',\n imgElement\n ).url\n \"\n [alt]=\"galleryImage.fallback\"\n [style.--original-height]=\"\n getImageAttachmentConfiguration(\n galleryImage,\n 'gallery',\n imgElement\n ).originalHeight\n \"\n [style.--original-width]=\"\n getImageAttachmentConfiguration(\n galleryImage,\n 'gallery',\n imgElement\n ).originalWidth\n \"\n [ngStyle]=\"{\n height: getImageAttachmentConfiguration(\n galleryImage,\n 'gallery',\n imgElement\n ).height,\n width: getImageAttachmentConfiguration(\n galleryImage,\n 'gallery',\n imgElement\n ).width\n }\"\n />\n </button>\n <button\n *ngIf=\"index === 3 && !isLast\"\n #element\n class=\"str-chat__gallery-placeholder\"\n data-testclass=\"gallery-image\"\n data-testid=\"more-image-button\"\n [class.str-chat__message-attachment--svg-image]=\"\n isSvg(galleryImage)\n \"\n [ngStyle]=\"{\n 'background-image':\n 'url(' +\n getImageAttachmentConfiguration(\n galleryImage,\n 'gallery',\n element\n ).url +\n ')',\n height: getImageAttachmentConfiguration(\n galleryImage,\n 'gallery',\n element\n ).height,\n width: getImageAttachmentConfiguration(\n galleryImage,\n 'gallery',\n element\n ).width,\n '--original-height': getImageAttachmentConfiguration(\n galleryImage,\n 'gallery',\n element\n ).originalHeight,\n '--original-width': getImageAttachmentConfiguration(\n galleryImage,\n 'gallery',\n element\n ).originalWidth\n }\"\n (click)=\"openImageModal(attachmentContext.images!, index)\"\n (keyup.enter)=\"openImageModal(attachmentContext.images!, index)\"\n >\n <p\n [innerHTML]=\"\n 'streamChat.{{ imageCount }} more'\n | translate\n : { imageCount: attachmentContext!.images!.length - 4 }\n \"\n ></p>\n </button>\n </ng-container>\n </div>\n </ng-template>\n </ng-container>\n <ng-container *ngIf=\"isVideo(attachment)\">\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.videoAttachmentTemplate$ | async) ||\n defaultVideo;\n context: getAttachmentContext(attachment)\n \"\n ></ng-container>\n <ng-template #defaultVideo let-attachmentContext=\"attachment\">\n <div\n class=\"str-chat__player-wrapper\"\n data-testclass=\"video-attachment-parent\"\n [style.--original-height]=\"\n getVideoAttachmentConfiguration(attachmentContext, videoElement)\n .originalHeight\n \"\n [style.--original-width]=\"\n getVideoAttachmentConfiguration(attachmentContext, videoElement)\n .originalWidth\n \"\n [ngStyle]=\"{\n height: getVideoAttachmentConfiguration(\n attachmentContext,\n videoElement\n ).height,\n width: getVideoAttachmentConfiguration(\n attachmentContext,\n videoElement\n ).width\n }\"\n >\n <video\n #videoElement\n class=\"str-chat__video-angular\"\n controls\n data-testclass=\"video-attachment\"\n [src]=\"\n getVideoAttachmentConfiguration(attachmentContext, videoElement)\n .url\n \"\n [poster]=\"\n getVideoAttachmentConfiguration(attachmentContext, videoElement)\n .thumbUrl\n \"\n ></video>\n </div>\n </ng-template>\n </ng-container>\n <ng-container *ngIf=\"isFile(attachment)\">\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.fileAttachmentTemplate$ | async) ||\n defaultFile;\n context: getAttachmentContext(attachment)\n \"\n ></ng-container>\n <ng-template #defaultFile let-attachmentContext=\"attachment\">\n <div\n class=\"str-chat__message-attachment-file--item str-chat-angular__message-attachment-file-single\"\n >\n <stream-icon-placeholder\n class=\"str-chat__attachment-type-icon\"\n icon=\"unspecified-filetype\"\n ></stream-icon-placeholder>\n <div class=\"str-chat__message-attachment-file--item-text\">\n <a\n class=\"str-chat__message-attachment-file--item-first-row\"\n data-testclass=\"file-link\"\n target=\"_blank\"\n href=\"{{ attachmentContext.asset_url }}\"\n >\n <div\n data-testclass=\"file-title\"\n class=\"str-chat__message-attachment-file--item-name\"\n >\n {{ attachmentContext.title }}\n </div>\n <stream-icon-placeholder\n class=\"str-chat__message-attachment-download-icon\"\n icon=\"download\"\n ></stream-icon-placeholder>\n </a>\n <span\n *ngIf=\"hasFileSize(attachmentContext)\"\n class=\"str-chat__message-attachment-file--item-size\"\n data-testclass=\"size\"\n >{{ getFileSize(attachmentContext) }}</span\n >\n </div>\n </div>\n </ng-template>\n </ng-container>\n <ng-container *ngIf=\"isVoiceMessage(attachment)\">\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.voiceRecordingAttachmentTemplate$\n | async) || defaultRecording;\n context: getAttachmentContext(attachment)\n \"\n ></ng-container>\n <ng-template #defaultRecording>\n <stream-voice-recording\n data-testclass=\"voice-recording\"\n [attachment]=\"attachment\"\n ></stream-voice-recording>\n </ng-template>\n </ng-container>\n <ng-container\n *ngIf=\"\n isCard(attachment) &&\n getCardAttachmentConfiguration(attachment) as attachmentConfiguration\n \"\n >\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.cardAttachmentTemplate$ | async) ||\n defaultCard;\n context: getAttachmentContext(attachment)\n \"\n ></ng-container>\n <ng-template #defaultCard let-attachmentContext=\"attachment\">\n <div\n class=\"str-chat__message-attachment-card str-chat__message-attachment-card--{{\n attachmentContext.type\n }}\"\n >\n <div\n *ngIf=\"attachmentConfiguration.url\"\n class=\"str-chat__message-attachment-card--header\"\n >\n <img\n fetchpriority=\"low\"\n loading=\"lazy\"\n data-testclass=\"card-img\"\n alt=\"{{ attachmentConfiguration.url }}\"\n src=\"{{ attachmentConfiguration.url }}\"\n [ngStyle]=\"{\n height: attachmentConfiguration.height,\n width: attachmentConfiguration.width\n }\"\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=\"attachmentContext.title\"\n data-testclass=\"card-title\"\n class=\"str-chat__message-attachment-card--title\"\n >\n {{ attachmentContext.title }}\n </div>\n <div\n *ngIf=\"attachmentContext.text\"\n class=\"str-chat__message-attachment-card--text\"\n data-testclass=\"card-text\"\n >\n {{ attachmentContext.text }}\n </div>\n <a\n *ngIf=\"\n attachmentContext.title_link ||\n attachmentContext.og_scrape_url\n \"\n class=\"str-chat__message-attachment-card--url\"\n data-testclass=\"url-link\"\n noopener\n noreferrer\n target=\"_blank\"\n href=\"{{\n attachmentContext.title_link ||\n attachmentContext.og_scrape_url\n }}\"\n >\n {{\n trimUrl(\n attachmentContext.title_link ||\n attachmentContext.og_scrape_url\n )\n }}\n </a>\n </div>\n </div>\n </div>\n </ng-template>\n </ng-container>\n <ng-container *ngIf=\"attachment.actions && attachment.actions.length > 0\">\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.attachmentActionsTemplate$ | async) ||\n defaultActions;\n context: getAttachmentContext(attachment)\n \"\n ></ng-container>\n <ng-template #defaultActions let-attachmentContext=\"attachment\">\n <div class=\"str-chat__message-attachment-actions\">\n <div class=\"str-chat__message-attachment-actions-form\">\n <button\n *ngFor=\"\n let action of attachmentContext.actions;\n trackBy: trackByActionValue\n \"\n data-testclass=\"attachment-action\"\n class=\"str-chat__message-attachment-actions-button str-chat__message-attachment-actions-button--{{\n action.style\n }}\"\n (click)=\"sendAction(action)\"\n (keyup.enter)=\"sendAction(action)\"\n >\n {{ action.text }}\n </button>\n </div>\n </div>\n </ng-template>\n </ng-container>\n </div>\n </ng-container>\n\n <ng-container *ngIf=\"imagesToView && imagesToView.length > 0\">\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.modalTemplate$ | async) || defaultModal;\n context: getModalContext()\n \"\n ></ng-container>\n </ng-container>\n</div>\n\n<ng-template\n #defaultModal\n let-isOpen=\"isOpen\"\n let-isOpenChangeHandler=\"isOpenChangeHandler\"\n let-content=\"content\"\n>\n <stream-modal\n class=\"stream-chat-angular__image-modal-host\"\n [isOpen]=\"isOpen\"\n [content]=\"content\"\n (isOpenChange)=\"isOpenChangeHandler($event)\"\n >\n </stream-modal>\n</ng-template>\n\n<ng-template #modalContent>\n <div class=\"stream-chat-angular__image-modal str-chat__image-carousel\">\n <img\n #imgElement\n class=\"stream-chat-angular__image-modal-image str-chat__image-carousel-image\"\n data-testid=\"modal-image\"\n [src]=\"\n getCarouselImageAttachmentConfiguration(\n imagesToView[imagesToViewCurrentIndex],\n imgElement\n ).url\n \"\n [style.--original-height]=\"\n getCarouselImageAttachmentConfiguration(\n imagesToView[imagesToViewCurrentIndex],\n imgElement\n ).originalHeight\n \"\n [style.--original-width]=\"\n getCarouselImageAttachmentConfiguration(\n imagesToView[imagesToViewCurrentIndex],\n imgElement\n ).originalWidth\n \"\n [alt]=\"imagesToView[imagesToViewCurrentIndex].fallback\"\n [ngStyle]=\"{\n width: getCarouselImageAttachmentConfiguration(\n imagesToView[imagesToViewCurrentIndex],\n imgElement\n ).width,\n height: getCarouselImageAttachmentConfiguration(\n imagesToView[imagesToViewCurrentIndex],\n imgElement\n ).height\n }\"\n />\n <div>\n <button\n class=\"stream-chat-angular__image-modal-stepper str-chat__image-carousel-stepper str-chat__image-carousel-stepper-prev\"\n data-testid=\"image-modal-prev\"\n type=\"button\"\n [ngStyle]=\"{\n visibility: isImageModalPrevButtonVisible ? 'visible' : 'hidden'\n }\"\n (click)=\"stepImages(-1)\"\n (keyup.enter)=\"stepImages(-1)\"\n >\n <stream-icon-placeholder icon=\"arrow-left\"></stream-icon-placeholder>\n </button>\n <button\n class=\"stream-chat-angular__image-modal-stepper str-chat__image-carousel-stepper str-chat__image-carousel-stepper-next\"\n type=\"button\"\n data-testid=\"image-modal-next\"\n [ngStyle]=\"{\n visibility: isImageModalNextButtonVisible ? 'visible' : 'hidden'\n }\"\n (click)=\"stepImages(1)\"\n (keyup.enter)=\"stepImages(1)\"\n >\n <stream-icon-placeholder icon=\"arrow-right\"></stream-icon-placeholder>\n </button>\n </div>\n </div>\n</ng-template>\n" }]
@@ -5380,7 +5470,7 @@ class MessageComponent {
5380
5470
  }
5381
5471
  }
5382
5472
  MessageComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: MessageComponent, deps: [{ token: ChatClientService }, { token: ChannelService }, { token: CustomTemplatesService }, { token: i0.ChangeDetectorRef }, { token: DateParserService }, { token: MessageService }, { token: MessageActionsService }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component });
5383
- MessageComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.0.4", type: MessageComponent, selector: "stream-message", inputs: { message: "message", enabledMessageActions: "enabledMessageActions", isLastSentMessage: "isLastSentMessage", mode: "mode", isHighlighted: "isHighlighted", scroll$: "scroll$" }, viewQueries: [{ propertyName: "messageMenuTrigger", first: true, predicate: ["messageMenuTrigger"], descendants: true }, { propertyName: "messageMenuFloat", first: true, predicate: ["messageMenuFloat"], descendants: true }, { propertyName: "messageTextElement", first: true, predicate: ["messageTextElement"], descendants: true }, { propertyName: "messageBubble", first: true, predicate: ["messageBubble"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div\n #container\n data-testid=\"message-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 }} str-chat__message-menu-{{ areMessageOptionsOpen ? 'opened' : 'closed' }}\"\n [class.str-chat__message--me]=\"isSentByCurrentUser\"\n [class.str-chat__message--other]=\"!isSentByCurrentUser\"\n [class.str-chat__message-simple--me]=\"isSentByCurrentUser\"\n [class.str-chat__message--has-attachment]=\"hasAttachment\"\n [class.str-chat__message--with-reactions]=\"hasReactions\"\n [class.str-chat__message--highlighted]=\"isHighlighted\"\n [class.str-chat__message-with-thread-link]=\"shouldDisplayThreadLink\"\n [class.str-chat__message-send-can-be-retried]=\"\n (message?.status === 'failed' && message?.errorStatusCode !== 403) ||\n (message?.type === 'error' && message?.moderation_details)\n \"\n [class.str-chat__message-with-touch-support]=\"hasTouchSupport\"\n [class.str-chat__message-without-touch-support]=\"!hasTouchSupport\"\n>\n <ng-container *ngIf=\"!message?.deleted_at; else deletedMessage\">\n <ng-container *ngIf=\"message?.type !== 'system'; else systemMessage\">\n <stream-avatar-placeholder\n data-testid=\"avatar\"\n class=\"str-chat-angular__avatar-host str-chat__message-sender-avatar\"\n type=\"user\"\n location=\"message-sender\"\n [imageUrl]=\"message?.user?.image\"\n [name]=\"message?.user?.name || message?.user?.id\"\n [user]=\"message?.user || undefined\"\n ></stream-avatar-placeholder>\n <div class=\"str-chat__message-inner\">\n <div\n *ngIf=\"!hasTouchSupport && areOptionsVisible\"\n class=\"str-chat__message-simple__actions str-chat__message-options\"\n data-testid=\"message-options\"\n [class.str-chat__message-actions-open]=\"areMessageOptionsOpen\"\n >\n <div\n #messageActionsToggle\n data-testid=\"message-actions-container\"\n class=\"str-chat__message-actions-container str-chat__message-simple__actions__action str-chat__message-simple__actions__action--options\"\n [floatUiLoose]=\"messageMenuFloat\"\n [looseTrigger]=\"\n messageActionsService.customActionClickHandler ? 'none' : 'click'\n \"\n [hideOnScroll]=\"false\"\n [hideOnClickOutside]=\"true\"\n [hideOnMouseLeave]=\"false\"\n [disableAnimation]=\"false\"\n [preventOverflow]=\"true\"\n [positionFixed]=\"true\"\n (onSHown)=\"areMessageOptionsOpen = true\"\n (onHidden)=\"areMessageOptionsOpen = false\"\n >\n <div\n *ngIf=\"visibleMessageActionsCount > 0\"\n class=\"str-chat__message-actions-box-button\"\n data-testid=\"message-options-button\"\n (click)=\"messageOptionsButtonClicked()\"\n (keyup.enter)=\"messageOptionsButtonClicked()\"\n >\n <stream-icon-placeholder\n icon=\"action\"\n class=\"str-chat__message-action-icon\"\n ></stream-icon-placeholder>\n </div>\n </div>\n </div>\n <div class=\"str-chat__message-reactions-host\">\n <ng-template\n #defaultMessageReactions\n let-messageReactionCounts=\"messageReactionCounts\"\n let-latestReactions=\"latestReactions\"\n let-messageId=\"messageId\"\n let-ownReactions=\"ownReactions\"\n let-messageReactionGroups=\"messageReactionGroups\"\n >\n <stream-message-reactions\n [messageReactionCounts]=\"messageReactionCounts\"\n [latestReactions]=\"latestReactions\"\n [messageId]=\"messageId\"\n [ownReactions]=\"ownReactions\"\n [messageReactionGroups]=\"messageReactionGroups\"\n ></stream-message-reactions>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.messageReactionsTemplate$ | async) ||\n defaultMessageReactions;\n context: getMessageReactionsContext()\n \"\n ></ng-container>\n </div>\n <float-ui-content #messageMenuFloat>\n <ng-template\n #defaultMessageActionsBox\n let-isMine=\"isMine\"\n let-messageInput=\"message\"\n let-enabledActions=\"enabledActions\"\n let-messageTextHtmlElement=\"messageTextHtmlElement\"\n >\n <stream-message-actions-box\n [isMine]=\"isMine\"\n [message]=\"messageInput\"\n [enabledActions]=\"enabledActions\"\n [messageTextHtmlElement]=\"messageTextHtmlElement\"\n (click)=\"messageActionsBoxClicked(messageMenuFloat)\"\n ></stream-message-actions-box>\n </ng-template>\n <ng-container>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.messageActionsBoxTemplate$ | async) ||\n defaultMessageActionsBox;\n context: getMessageActionsBoxContext()\n \"\n >\n </ng-container>\n </ng-container>\n </float-ui-content>\n <div\n class=\"str-chat__message-bubble str-chat-angular__message-bubble\"\n [class.str-chat-angular__message-bubble--attachment-modal-open]=\"\n imageAttachmentModalState === 'opened'\n \"\n data-testid=\"message-bubble\"\n [floatUiLoose]=\"messageMenuFloat\"\n #messageMenuTrigger=\"floatUiLoose\"\n #messageBubble\n looseTrigger=\"none\"\n [hideOnScroll]=\"false\"\n [hideOnClickOutside]=\"true\"\n [hideOnMouseLeave]=\"false\"\n [disableAnimation]=\"true\"\n [preventOverflow]=\"true\"\n (onShown)=\"areMessageOptionsOpen = true\"\n (onHidden)=\"areMessageOptionsOpen = false\"\n [positionFixed]=\"true\"\n >\n <ng-container *ngIf=\"hasAttachment && !message?.quoted_message\">\n <div class=\"str-chat__attachments-container\">\n <ng-container\n *ngTemplateOutlet=\"attachmentsTemplate\"\n ></ng-container>\n </div>\n </ng-container>\n <div\n *ngIf=\"message?.text || (message?.quoted_message && hasAttachment)\"\n class=\"str-chat__message-text\"\n tabindex=\"0\"\n [class.str-chat__message-text--pointer-cursor]=\"\n (message?.status === 'failed' &&\n message?.errorStatusCode !== 403) ||\n (this.message?.type === 'error' &&\n this.message?.moderation_details) ||\n message?.message_text_updated_at\n \"\n (click)=\"messageClicked()\"\n (keyup.enter)=\"messageClicked()\"\n >\n <div\n data-testid=\"inner-message\"\n class=\"str-chat__message-text-inner str-chat__message-simple-text-inner\"\n [class.str-chat__message-light-text-inner--has-attachment]=\"\n hasAttachment\n \"\n >\n <ng-container *ngTemplateOutlet=\"quotedMessage\"></ng-container>\n <ng-container *ngIf=\"hasAttachment && message?.quoted_message\">\n <ng-container\n *ngTemplateOutlet=\"attachmentsTemplate\"\n ></ng-container>\n </ng-container>\n <div\n *ngIf=\"message?.type === 'error'\"\n data-testid=\"client-error-message\"\n class=\"str-chat__simple-message--error-message str-chat__message--error-message\"\n >\n <ng-container *ngIf=\"!message?.moderation_details\">{{\n \"streamChat.Error \u00B7 Unsent\" | translate\n }}</ng-container>\n </div>\n <div\n *ngIf=\"message?.status === 'failed'\"\n data-testid=\"error-message\"\n class=\"str-chat__simple-message--error-message str-chat__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 #messageTextElement data-testid=\"text\">\n <p>\n <ng-container *ngIf=\"messageTextParts; else defaultContent\">\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-template #defaultMention let-content=\"content\">\n <span class=\"str-chat__message-mention\">{{\n content\n }}</span>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.mentionTemplate$ | async) ||\n defaultMention;\n context: getMentionContext(part)\n \"\n ></ng-container>\n </ng-template>\n </ng-container>\n </ng-container>\n <ng-template #defaultContent>\n <ng-container *ngIf=\"displayAs === 'text'; else asHTML\">\n {{ messageText || \"\" }}\n </ng-container>\n <ng-template #asHTML\n ><span\n data-testid=\"html-content\"\n [innerHTML]=\"messageText\"\n ></span\n ></ng-template>\n </ng-template>\n </p>\n </div>\n </div>\n </div>\n <stream-icon-placeholder\n class=\"str-chat__message-error-icon\"\n icon=\"error\"\n ></stream-icon-placeholder>\n </div>\n </div>\n <ng-container\n *ngTemplateOutlet=\"replyCountButton; context: { message: message }\"\n ></ng-container>\n\n <ng-container *ngTemplateOutlet=\"messageDateAndSender\"></ng-container>\n </ng-container>\n </ng-container>\n</div>\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 <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.systemMessageTemplate$ | async) ||\n defaultSystemMessage;\n context: getMessageContext()\n \"\n ></ng-container>\n <ng-template #defaultSystemMessage let-messageInput=\"message\">\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>{{ messageInput?.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</ng-template>\n\n<ng-template #quotedMessage>\n <div\n *ngIf=\"message?.quoted_message\"\n class=\"quoted-message str-chat__quoted-message-preview\"\n data-testid=\"quoted-message-container\"\n [class.mine]=\"isSentByCurrentUser\"\n (click)=\"\n jumpToMessage(\n (message?.quoted_message)!.id,\n message?.quoted_message?.parent_id\n )\n \"\n (keyup.enter)=\"\n jumpToMessage(\n (message?.quoted_message)!.id,\n message?.quoted_message?.parent_id\n )\n \"\n >\n <stream-avatar-placeholder\n data-testid=\"qouted-message-avatar\"\n class=\"str-chat-angular__avatar-host str-chat__message-sender-avatar\"\n type=\"user\"\n location=\"quoted-message-sender\"\n [imageUrl]=\"message?.quoted_message?.user?.image\"\n [name]=\"\n message?.quoted_message?.user?.name || message?.quoted_message?.user?.id\n \"\n [user]=\"message?.quoted_message?.user || undefined\"\n ></stream-avatar-placeholder>\n <div class=\"quoted-message-inner str-chat__quoted-message-bubble\">\n <ng-container\n *ngIf=\"\n message?.quoted_message?.attachments &&\n message?.quoted_message?.attachments?.length\n \"\n >\n <ng-template\n #defaultAttachments\n let-messageId=\"messageId\"\n let-attachments=\"attachments\"\n let-parentMessageId=\"parentMessageId\"\n let-imageModalStateChangeHandler=\"imageModalStateChangeHandler\"\n >\n <stream-attachment-list\n [messageId]=\"messageId\"\n [attachments]=\"attachments\"\n [parentMessageId]=\"parentMessageId\"\n (imageModalStateChange)=\"imageModalStateChangeHandler($event)\"\n ></stream-attachment-list>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.attachmentListTemplate$ | async) ||\n defaultAttachments;\n context: getQuotedMessageAttachmentListContext()\n \"\n ></ng-container>\n </ng-container>\n <div\n data-testid=\"quoted-message-text\"\n [innerHTML]=\"\n message?.quoted_message?.translation ||\n message?.quoted_message?.html ||\n message?.quoted_message?.text\n \"\n ></div>\n </div>\n </div>\n</ng-template>\n\n<ng-template #messageDateAndSender>\n <ng-container>\n <div\n *ngIf=\"shouldDisplayTranslationNotice\"\n class=\"str-chat__translation-notice\"\n data-testid=\"translation-notice\"\n >\n <button\n *ngIf=\"displayedMessageTextContent === 'translation'\"\n data-testid=\"see-original\"\n translate\n (click)=\"displayOriginalMessage()\"\n (keyup.enter)=\"displayOriginalMessage()\"\n >\n streamChat.See original (automatically translated)\n </button>\n <button\n *ngIf=\"displayedMessageTextContent === 'original'\"\n data-testid=\"see-translation\"\n translate\n (click)=\"displayTranslatedMessage()\"\n (keyup.enter)=\"displayTranslatedMessage()\"\n >\n streamChat.See translation\n </button>\n </div>\n <ng-container\n *ngIf=\"customTemplatesService.customMessageMetadataTemplate$ | async\"\n >\n <div class=\"str-chat__custom-message-metadata\">\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.customMessageMetadataTemplate$ | async)!;\n context: getMessageMetadataContext()\n \"\n ></ng-container>\n </div>\n </ng-container>\n <div\n class=\"str-chat__message-data str-chat__message-simple-data str-chat__message-metadata\"\n >\n <ng-container *ngTemplateOutlet=\"messageStatus\"></ng-container>\n\n <span\n *ngIf=\"!isSentByCurrentUser\"\n data-testid=\"sender\"\n class=\"str-chat__message-simple-name str-chat__message-sender-name\"\n >\n {{ message?.user?.name ? message?.user?.name : message?.user?.id }}\n </span>\n <span\n data-testid=\"date\"\n class=\"str-chat__message-simple-timestamp str-chat__message-simple-time\"\n >\n {{ parsedDate }}\n </span>\n <ng-container *ngIf=\"message?.message_text_updated_at\">\n <span\n data-testid=\"edited-flag\"\n class=\"str-chat__mesage-simple-edited\"\n translate\n >streamChat.Edited</span\n >\n <div\n data-testid=\"edited-timestamp\"\n class=\"str-chat__message-edited-timestamp\"\n [ngClass]=\"{\n 'str-chat__message-edited-timestamp--open': isEditedFlagOpened,\n 'str-chat__message-edited-timestamp--collapsed': !isEditedFlagOpened\n }\"\n >\n <span translate>streamChat.Edited</span>\n <time\n dateTime=\"{{ message?.message_text_updated_at }}\"\n title=\"{{ message?.message_text_updated_at }}\"\n >\n {{ pasedEditedDate }}\n </time>\n </div>\n </ng-container>\n </div>\n </ng-container>\n</ng-template>\n\n<ng-template #messageStatus>\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' && isMessageDeliveredAndRead && canDisplayReadStatus;\n else deliveredStatus\n \"\n >\n <ng-container *ngTemplateOutlet=\"readStatus\"></ng-container>\n </ng-container>\n </ng-template>\n <ng-template #deliveredStatus>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.deliveredStatusTemplate$ | async) ||\n defaultDeliveredStatus;\n context: getDeliveredStatusContext()\n \"\n ></ng-container>\n </ng-template>\n <ng-template #defaultDeliveredStatus>\n <span\n *ngIf=\"mode === 'main'\"\n class=\"str-chat__message-simple-status str-chat__message-simple-status-angular str-chat__message-status\"\n data-testid=\"delivered-indicator\"\n tabindex=\"0\"\n [floatUiLoose]=\"floatingContent\"\n loosePlacement=\"top\"\n [looseTrigger]=\"hasTouchSupport ? 'click' : 'hover'\"\n [disableAnimation]=\"true\"\n [hideOnClickOutside]=\"true\"\n [positionFixed]=\"true\"\n [preventOverflow]=\"true\"\n >\n <float-ui-content #floatingContent>\n <div class=\"str-chat__tooltip str-chat__tooltip-angular\">\n {{ \"streamChat.Delivered\" | translate }}\n </div>\n </float-ui-content>\n <stream-icon-placeholder\n data-testid=\"delivered-icon\"\n icon=\"delivered\"\n ></stream-icon-placeholder>\n </span>\n </ng-template>\n <ng-template #sendingStatus>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.sendingStatusTemplate$ | async) ||\n defaultSendingStatus;\n context: getSendingStatusContext()\n \"\n ></ng-container>\n </ng-template>\n <ng-template #defaultSendingStatus>\n <span\n class=\"str-chat__message-simple-status str-chat__message-simple-status-angular str-chat__message-status\"\n data-testid=\"sending-indicator\"\n tabindex=\"0\"\n [floatUiLoose]=\"floatingContent\"\n loosePlacement=\"top\"\n [looseTrigger]=\"hasTouchSupport ? 'click' : 'hover'\"\n [disableAnimation]=\"true\"\n [hideOnClickOutside]=\"true\"\n [positionFixed]=\"true\"\n [preventOverflow]=\"true\"\n >\n <float-ui-content #floatingContent>\n <div class=\"str-chat__tooltip str-chat__tooltip-angular\">\n {{ \"streamChat.Sending...\" | translate }}\n </div>\n </float-ui-content>\n <stream-loading-indicator-placeholder\n data-testid=\"loading-indicator\"\n ></stream-loading-indicator-placeholder>\n </span>\n </ng-template>\n <ng-template #readStatus>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.readStatusTemplate$ | async) ||\n defaultReadStatus;\n context: getReadStatusContext()\n \"\n ></ng-container>\n </ng-template>\n <ng-template #defaultReadStatus let-readByText=\"readByText\">\n <span\n class=\"str-chat__message-simple-status str-chat__message-simple-status-angular str-chat__message-status\"\n data-testid=\"read-indicator\"\n tabindex=\"0\"\n [floatUiLoose]=\"floatingContent\"\n loosePlacement=\"top\"\n [looseTrigger]=\"hasTouchSupport ? 'click' : 'hover'\"\n [disableAnimation]=\"true\"\n [hideOnClickOutside]=\"true\"\n [positionFixed]=\"true\"\n [preventOverflow]=\"true\"\n >\n <float-ui-content #floatingContent>\n <div\n class=\"str-chat__tooltip str-chat__tooltip-angular\"\n data-testid=\"read-by-tooltip\"\n >\n {{ readByText }}\n </div>\n </float-ui-content>\n <stream-icon-placeholder icon=\"read\"></stream-icon-placeholder>\n </span>\n </ng-template>\n </ng-container>\n</ng-template>\n\n<ng-template #replyCountButton>\n <div\n class=\"str-chat__message-simple-reply-button str-chat__message-replies-count-button-wrapper\"\n >\n <button\n *ngIf=\"shouldDisplayThreadLink\"\n class=\"str-chat__message-replies-count-button\"\n data-testid=\"reply-count-button\"\n (click)=\"setAsActiveParentMessage()\"\n >\n {{message?.reply_count === 1 ? ('streamChat.1 reply' | translate) : ('streamChat.{{ replyCount }}\n replies' | translate:replyCountParam)}}\n </button>\n </div>\n</ng-template>\n\n<ng-template #attachmentsTemplate>\n <ng-template\n #defaultAttachments\n let-messageId=\"messageId\"\n let-attachments=\"attachments\"\n let-parentMessageId=\"parentMessageId\"\n let-imageModalStateChangeHandler=\"imageModalStateChangeHandler\"\n >\n <stream-attachment-list\n [messageId]=\"messageId\"\n [attachments]=\"attachments\"\n [parentMessageId]=\"parentMessageId\"\n (imageModalStateChange)=\"imageModalStateChangeHandler($event)\"\n ></stream-attachment-list>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.attachmentListTemplate$ | async) ||\n defaultAttachments;\n context: getAttachmentListContext()\n \"\n ></ng-container>\n</ng-template>\n", dependencies: [{ kind: "directive", type: i4.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i4.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i4.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: i8.NgxFloatUiContentComponent, selector: "float-ui-content", exportAs: ["ngxFloatUiContent"] }, { kind: "directive", type: i8.NgxFloatUiLooseDirective, selector: "[floatUiLoose]", inputs: ["floatUiLoose", "loosePlacement", "looseTrigger"], exportAs: ["floatUiLoose"] }, { kind: "component", type: AvatarPlaceholderComponent, selector: "stream-avatar-placeholder", inputs: ["name", "imageUrl", "location", "channel", "user", "type", "initialsType", "showOnlineIndicator"] }, { kind: "directive", type: i10.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "component", type: MessageActionsBoxComponent, selector: "stream-message-actions-box", inputs: ["isMine", "message", "messageTextHtmlElement", "enabledActions"] }, { kind: "component", type: AttachmentListComponent, selector: "stream-attachment-list", inputs: ["messageId", "parentMessageId", "attachments"], outputs: ["imageModalStateChange"] }, { kind: "component", type: MessageReactionsComponent, selector: "stream-message-reactions", inputs: ["messageId", "messageReactionGroups", "messageReactionCounts", "latestReactions", "ownReactions"] }, { kind: "component", type: IconPlaceholderComponent, selector: "stream-icon-placeholder", inputs: ["icon"] }, { kind: "component", type: LoadingIndicatorPlaceholderComponent, selector: "stream-loading-indicator-placeholder" }, { kind: "pipe", type: i4.AsyncPipe, name: "async" }, { kind: "pipe", type: i10.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5473
+ MessageComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.0.4", type: MessageComponent, selector: "stream-message", inputs: { message: "message", enabledMessageActions: "enabledMessageActions", isLastSentMessage: "isLastSentMessage", mode: "mode", isHighlighted: "isHighlighted", scroll$: "scroll$" }, viewQueries: [{ propertyName: "messageMenuTrigger", first: true, predicate: ["messageMenuTrigger"], descendants: true }, { propertyName: "messageMenuFloat", first: true, predicate: ["messageMenuFloat"], descendants: true }, { propertyName: "messageTextElement", first: true, predicate: ["messageTextElement"], descendants: true }, { propertyName: "messageBubble", first: true, predicate: ["messageBubble"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div\n #container\n data-testid=\"message-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 }} str-chat__message-menu-{{ areMessageOptionsOpen ? 'opened' : 'closed' }}\"\n [class.str-chat__message--me]=\"isSentByCurrentUser\"\n [class.str-chat__message--other]=\"!isSentByCurrentUser\"\n [class.str-chat__message-simple--me]=\"isSentByCurrentUser\"\n [class.str-chat__message--has-attachment]=\"hasAttachment\"\n [class.str-chat__message--with-reactions]=\"hasReactions\"\n [class.str-chat__message--highlighted]=\"isHighlighted\"\n [class.str-chat__message-with-thread-link]=\"shouldDisplayThreadLink\"\n [class.str-chat__message-send-can-be-retried]=\"\n (message?.status === 'failed' && message?.errorStatusCode !== 403) ||\n (message?.type === 'error' && message?.moderation_details)\n \"\n [class.str-chat__message-with-touch-support]=\"hasTouchSupport\"\n [class.str-chat__message-without-touch-support]=\"!hasTouchSupport\"\n>\n <ng-container *ngIf=\"!message?.deleted_at; else deletedMessage\">\n <ng-container *ngIf=\"message?.type !== 'system'; else systemMessage\">\n <stream-avatar-placeholder\n data-testid=\"avatar\"\n class=\"str-chat-angular__avatar-host str-chat__message-sender-avatar\"\n type=\"user\"\n location=\"message-sender\"\n [imageUrl]=\"message?.user?.image\"\n [name]=\"message?.user?.name || message?.user?.id\"\n [user]=\"message?.user || undefined\"\n ></stream-avatar-placeholder>\n <div class=\"str-chat__message-inner\">\n <div\n *ngIf=\"!hasTouchSupport && areOptionsVisible\"\n class=\"str-chat__message-simple__actions str-chat__message-options\"\n data-testid=\"message-options\"\n [class.str-chat__message-actions-open]=\"areMessageOptionsOpen\"\n >\n <div\n #messageActionsToggle\n data-testid=\"message-actions-container\"\n class=\"str-chat__message-actions-container str-chat__message-simple__actions__action str-chat__message-simple__actions__action--options\"\n [floatUiLoose]=\"messageMenuFloat\"\n [looseTrigger]=\"\n messageActionsService.customActionClickHandler ? 'none' : 'click'\n \"\n [hideOnScroll]=\"false\"\n [hideOnClickOutside]=\"true\"\n [hideOnMouseLeave]=\"false\"\n [disableAnimation]=\"false\"\n [preventOverflow]=\"true\"\n [positionFixed]=\"true\"\n (onSHown)=\"areMessageOptionsOpen = true\"\n (onHidden)=\"areMessageOptionsOpen = false\"\n >\n <div\n *ngIf=\"visibleMessageActionsCount > 0\"\n class=\"str-chat__message-actions-box-button\"\n data-testid=\"message-options-button\"\n (click)=\"messageOptionsButtonClicked()\"\n (keyup.enter)=\"messageOptionsButtonClicked()\"\n >\n <stream-icon-placeholder\n icon=\"action\"\n class=\"str-chat__message-action-icon\"\n ></stream-icon-placeholder>\n </div>\n </div>\n </div>\n <div class=\"str-chat__message-reactions-host\">\n <ng-template\n #defaultMessageReactions\n let-messageReactionCounts=\"messageReactionCounts\"\n let-latestReactions=\"latestReactions\"\n let-messageId=\"messageId\"\n let-ownReactions=\"ownReactions\"\n let-messageReactionGroups=\"messageReactionGroups\"\n >\n <stream-message-reactions\n [messageReactionCounts]=\"messageReactionCounts\"\n [latestReactions]=\"latestReactions\"\n [messageId]=\"messageId\"\n [ownReactions]=\"ownReactions\"\n [messageReactionGroups]=\"messageReactionGroups\"\n ></stream-message-reactions>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.messageReactionsTemplate$ | async) ||\n defaultMessageReactions;\n context: getMessageReactionsContext()\n \"\n ></ng-container>\n </div>\n <float-ui-content #messageMenuFloat>\n <ng-template\n #defaultMessageActionsBox\n let-isMine=\"isMine\"\n let-messageInput=\"message\"\n let-enabledActions=\"enabledActions\"\n let-messageTextHtmlElement=\"messageTextHtmlElement\"\n >\n <stream-message-actions-box\n [isMine]=\"isMine\"\n [message]=\"messageInput\"\n [enabledActions]=\"enabledActions\"\n [messageTextHtmlElement]=\"messageTextHtmlElement\"\n (click)=\"messageActionsBoxClicked(messageMenuFloat)\"\n ></stream-message-actions-box>\n </ng-template>\n <ng-container>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.messageActionsBoxTemplate$ | async) ||\n defaultMessageActionsBox;\n context: getMessageActionsBoxContext()\n \"\n >\n </ng-container>\n </ng-container>\n </float-ui-content>\n <div\n class=\"str-chat__message-bubble str-chat-angular__message-bubble\"\n [class.str-chat-angular__message-bubble--attachment-modal-open]=\"\n imageAttachmentModalState === 'opened'\n \"\n data-testid=\"message-bubble\"\n [floatUiLoose]=\"messageMenuFloat\"\n #messageMenuTrigger=\"floatUiLoose\"\n #messageBubble\n looseTrigger=\"none\"\n [hideOnScroll]=\"false\"\n [hideOnClickOutside]=\"true\"\n [hideOnMouseLeave]=\"false\"\n [disableAnimation]=\"true\"\n [preventOverflow]=\"true\"\n (onShown)=\"areMessageOptionsOpen = true\"\n (onHidden)=\"areMessageOptionsOpen = false\"\n [positionFixed]=\"true\"\n >\n <ng-container *ngIf=\"hasAttachment && !message?.quoted_message\">\n <div class=\"str-chat__attachments-container\">\n <ng-container\n *ngTemplateOutlet=\"attachmentsTemplate\"\n ></ng-container>\n </div>\n </ng-container>\n <div\n *ngIf=\"message?.text || (message?.quoted_message && hasAttachment)\"\n class=\"str-chat__message-text\"\n tabindex=\"0\"\n [class.str-chat__message-text--pointer-cursor]=\"\n (message?.status === 'failed' &&\n message?.errorStatusCode !== 403) ||\n (this.message?.type === 'error' &&\n this.message?.moderation_details) ||\n message?.message_text_updated_at\n \"\n (click)=\"messageClicked()\"\n (keyup.enter)=\"messageClicked()\"\n >\n <div\n data-testid=\"inner-message\"\n class=\"str-chat__message-text-inner str-chat__message-simple-text-inner\"\n [class.str-chat__message-light-text-inner--has-attachment]=\"\n hasAttachment\n \"\n >\n <ng-container *ngTemplateOutlet=\"quotedMessage\"></ng-container>\n <ng-container *ngIf=\"hasAttachment && message?.quoted_message\">\n <ng-container\n *ngTemplateOutlet=\"attachmentsTemplate\"\n ></ng-container>\n </ng-container>\n <div\n *ngIf=\"message?.type === 'error'\"\n data-testid=\"client-error-message\"\n class=\"str-chat__simple-message--error-message str-chat__message--error-message\"\n >\n <ng-container *ngIf=\"!message?.moderation_details\">{{\n \"streamChat.Error \u00B7 Unsent\" | translate\n }}</ng-container>\n </div>\n <div\n *ngIf=\"message?.status === 'failed'\"\n data-testid=\"error-message\"\n class=\"str-chat__simple-message--error-message str-chat__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 #messageTextElement data-testid=\"text\">\n <p>\n <ng-container *ngIf=\"messageTextParts; else defaultContent\">\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-template #defaultMention let-content=\"content\">\n <span class=\"str-chat__message-mention\">{{\n content\n }}</span>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.mentionTemplate$ | async) ||\n defaultMention;\n context: getMentionContext(part)\n \"\n ></ng-container>\n </ng-template>\n </ng-container>\n </ng-container>\n <ng-template #defaultContent>\n <ng-container *ngIf=\"displayAs === 'text'; else asHTML\">\n {{ messageText || \"\" }}\n </ng-container>\n <ng-template #asHTML\n ><span\n data-testid=\"html-content\"\n [innerHTML]=\"messageText\"\n ></span\n ></ng-template>\n </ng-template>\n </p>\n </div>\n </div>\n </div>\n <stream-icon-placeholder\n class=\"str-chat__message-error-icon\"\n icon=\"error\"\n ></stream-icon-placeholder>\n </div>\n </div>\n <ng-container\n *ngTemplateOutlet=\"replyCountButton; context: { message: message }\"\n ></ng-container>\n\n <ng-container *ngTemplateOutlet=\"messageDateAndSender\"></ng-container>\n </ng-container>\n </ng-container>\n</div>\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 <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.systemMessageTemplate$ | async) ||\n defaultSystemMessage;\n context: getMessageContext()\n \"\n ></ng-container>\n <ng-template #defaultSystemMessage let-messageInput=\"message\">\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>{{ messageInput?.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</ng-template>\n\n<ng-template #quotedMessage>\n <div\n *ngIf=\"message?.quoted_message\"\n class=\"quoted-message str-chat__quoted-message-preview\"\n data-testid=\"quoted-message-container\"\n [class.mine]=\"isSentByCurrentUser\"\n (click)=\"\n jumpToMessage(\n (message?.quoted_message)!.id,\n message?.quoted_message?.parent_id\n )\n \"\n (keyup.enter)=\"\n jumpToMessage(\n (message?.quoted_message)!.id,\n message?.quoted_message?.parent_id\n )\n \"\n >\n <stream-avatar-placeholder\n data-testid=\"qouted-message-avatar\"\n class=\"str-chat-angular__avatar-host str-chat__message-sender-avatar\"\n type=\"user\"\n location=\"quoted-message-sender\"\n [imageUrl]=\"message?.quoted_message?.user?.image\"\n [name]=\"\n message?.quoted_message?.user?.name || message?.quoted_message?.user?.id\n \"\n [user]=\"message?.quoted_message?.user || undefined\"\n ></stream-avatar-placeholder>\n <div class=\"quoted-message-inner str-chat__quoted-message-bubble\">\n <ng-container\n *ngIf=\"\n message?.quoted_message?.attachments &&\n message?.quoted_message?.attachments?.length\n \"\n >\n <ng-template\n #defaultAttachments\n let-messageId=\"messageId\"\n let-attachments=\"attachments\"\n let-parentMessageId=\"parentMessageId\"\n let-imageModalStateChangeHandler=\"imageModalStateChangeHandler\"\n >\n <stream-attachment-list\n [messageId]=\"messageId\"\n [attachments]=\"attachments\"\n [parentMessageId]=\"parentMessageId\"\n (imageModalStateChange)=\"imageModalStateChangeHandler($event)\"\n ></stream-attachment-list>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.attachmentListTemplate$ | async) ||\n defaultAttachments;\n context: getQuotedMessageAttachmentListContext()\n \"\n ></ng-container>\n </ng-container>\n <div\n data-testid=\"quoted-message-text\"\n [innerHTML]=\"\n message?.quoted_message?.translation ||\n message?.quoted_message?.html ||\n message?.quoted_message?.text\n \"\n ></div>\n </div>\n </div>\n</ng-template>\n\n<ng-template #messageDateAndSender>\n <ng-container>\n <div\n *ngIf=\"shouldDisplayTranslationNotice\"\n class=\"str-chat__translation-notice\"\n data-testid=\"translation-notice\"\n >\n <button\n *ngIf=\"displayedMessageTextContent === 'translation'\"\n data-testid=\"see-original\"\n translate\n (click)=\"displayOriginalMessage()\"\n (keyup.enter)=\"displayOriginalMessage()\"\n >\n streamChat.See original (automatically translated)\n </button>\n <button\n *ngIf=\"displayedMessageTextContent === 'original'\"\n data-testid=\"see-translation\"\n translate\n (click)=\"displayTranslatedMessage()\"\n (keyup.enter)=\"displayTranslatedMessage()\"\n >\n streamChat.See translation\n </button>\n </div>\n <ng-container\n *ngIf=\"customTemplatesService.customMessageMetadataTemplate$ | async\"\n >\n <div class=\"str-chat__custom-message-metadata\">\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.customMessageMetadataTemplate$ | async)!;\n context: getMessageMetadataContext()\n \"\n ></ng-container>\n </div>\n </ng-container>\n <div\n class=\"str-chat__message-data str-chat__message-simple-data str-chat__message-metadata\"\n >\n <ng-container *ngTemplateOutlet=\"messageStatus\"></ng-container>\n\n <span\n *ngIf=\"!isSentByCurrentUser\"\n data-testid=\"sender\"\n class=\"str-chat__message-simple-name str-chat__message-sender-name\"\n >\n {{ message?.user?.name ? message?.user?.name : message?.user?.id }}\n </span>\n <span\n data-testid=\"date\"\n class=\"str-chat__message-simple-timestamp str-chat__message-simple-time\"\n >\n {{ parsedDate }}\n </span>\n <ng-container *ngIf=\"message?.message_text_updated_at\">\n <span\n data-testid=\"edited-flag\"\n class=\"str-chat__mesage-simple-edited\"\n translate\n >streamChat.Edited</span\n >\n <div\n data-testid=\"edited-timestamp\"\n class=\"str-chat__message-edited-timestamp\"\n [ngClass]=\"{\n 'str-chat__message-edited-timestamp--open': isEditedFlagOpened,\n 'str-chat__message-edited-timestamp--collapsed': !isEditedFlagOpened\n }\"\n >\n <span translate>streamChat.Edited</span>\n <time\n dateTime=\"{{ message?.message_text_updated_at }}\"\n title=\"{{ message?.message_text_updated_at }}\"\n >\n {{ pasedEditedDate }}\n </time>\n </div>\n </ng-container>\n </div>\n </ng-container>\n</ng-template>\n\n<ng-template #messageStatus>\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' && isMessageDeliveredAndRead && canDisplayReadStatus;\n else deliveredStatus\n \"\n >\n <ng-container *ngTemplateOutlet=\"readStatus\"></ng-container>\n </ng-container>\n </ng-template>\n <ng-template #deliveredStatus>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.deliveredStatusTemplate$ | async) ||\n defaultDeliveredStatus;\n context: getDeliveredStatusContext()\n \"\n ></ng-container>\n </ng-template>\n <ng-template #defaultDeliveredStatus>\n <span\n *ngIf=\"mode === 'main'\"\n class=\"str-chat__message-simple-status str-chat__message-simple-status-angular str-chat__message-status\"\n data-testid=\"delivered-indicator\"\n tabindex=\"0\"\n [floatUiLoose]=\"floatingContent\"\n loosePlacement=\"top\"\n [looseTrigger]=\"hasTouchSupport ? 'click' : 'hover'\"\n [disableAnimation]=\"true\"\n [hideOnClickOutside]=\"true\"\n [positionFixed]=\"true\"\n [preventOverflow]=\"true\"\n >\n <float-ui-content #floatingContent>\n <div class=\"str-chat__tooltip str-chat__tooltip-angular\">\n {{ \"streamChat.Delivered\" | translate }}\n </div>\n </float-ui-content>\n <stream-icon-placeholder\n data-testid=\"delivered-icon\"\n icon=\"delivered\"\n ></stream-icon-placeholder>\n </span>\n </ng-template>\n <ng-template #sendingStatus>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.sendingStatusTemplate$ | async) ||\n defaultSendingStatus;\n context: getSendingStatusContext()\n \"\n ></ng-container>\n </ng-template>\n <ng-template #defaultSendingStatus>\n <span\n class=\"str-chat__message-simple-status str-chat__message-simple-status-angular str-chat__message-status\"\n data-testid=\"sending-indicator\"\n tabindex=\"0\"\n [floatUiLoose]=\"floatingContent\"\n loosePlacement=\"top\"\n [looseTrigger]=\"hasTouchSupport ? 'click' : 'hover'\"\n [disableAnimation]=\"true\"\n [hideOnClickOutside]=\"true\"\n [positionFixed]=\"true\"\n [preventOverflow]=\"true\"\n >\n <float-ui-content #floatingContent>\n <div class=\"str-chat__tooltip str-chat__tooltip-angular\">\n {{ \"streamChat.Sending...\" | translate }}\n </div>\n </float-ui-content>\n <stream-loading-indicator-placeholder\n data-testid=\"loading-indicator\"\n ></stream-loading-indicator-placeholder>\n </span>\n </ng-template>\n <ng-template #readStatus>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.readStatusTemplate$ | async) ||\n defaultReadStatus;\n context: getReadStatusContext()\n \"\n ></ng-container>\n </ng-template>\n <ng-template #defaultReadStatus let-readByText=\"readByText\">\n <span\n class=\"str-chat__message-simple-status str-chat__message-simple-status-angular str-chat__message-status\"\n data-testid=\"read-indicator\"\n tabindex=\"0\"\n [floatUiLoose]=\"floatingContent\"\n loosePlacement=\"top\"\n [looseTrigger]=\"hasTouchSupport ? 'click' : 'hover'\"\n [disableAnimation]=\"true\"\n [hideOnClickOutside]=\"true\"\n [positionFixed]=\"true\"\n [preventOverflow]=\"true\"\n >\n <float-ui-content #floatingContent>\n <div\n class=\"str-chat__tooltip str-chat__tooltip-angular\"\n data-testid=\"read-by-tooltip\"\n >\n {{ readByText }}\n </div>\n </float-ui-content>\n <stream-icon-placeholder icon=\"read\"></stream-icon-placeholder>\n </span>\n </ng-template>\n </ng-container>\n</ng-template>\n\n<ng-template #replyCountButton>\n <div\n class=\"str-chat__message-simple-reply-button str-chat__message-replies-count-button-wrapper\"\n >\n <button\n *ngIf=\"shouldDisplayThreadLink\"\n class=\"str-chat__message-replies-count-button\"\n data-testid=\"reply-count-button\"\n (click)=\"setAsActiveParentMessage()\"\n >\n {{message?.reply_count === 1 ? ('streamChat.1 reply' | translate) : ('streamChat.{{ replyCount }}\n replies' | translate:replyCountParam)}}\n </button>\n </div>\n</ng-template>\n\n<ng-template #attachmentsTemplate>\n <ng-template\n #defaultAttachments\n let-messageId=\"messageId\"\n let-attachments=\"attachments\"\n let-parentMessageId=\"parentMessageId\"\n let-imageModalStateChangeHandler=\"imageModalStateChangeHandler\"\n >\n <stream-attachment-list\n [messageId]=\"messageId\"\n [attachments]=\"attachments\"\n [parentMessageId]=\"parentMessageId\"\n (imageModalStateChange)=\"imageModalStateChangeHandler($event)\"\n ></stream-attachment-list>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.attachmentListTemplate$ | async) ||\n defaultAttachments;\n context: getAttachmentListContext()\n \"\n ></ng-container>\n</ng-template>\n", dependencies: [{ kind: "directive", type: i4.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i4.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i4.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: i8.NgxFloatUiContentComponent, selector: "float-ui-content", exportAs: ["ngxFloatUiContent"] }, { kind: "directive", type: i8.NgxFloatUiLooseDirective, selector: "[floatUiLoose]", inputs: ["floatUiLoose", "loosePlacement", "looseTrigger"], exportAs: ["floatUiLoose"] }, { kind: "component", type: AvatarPlaceholderComponent, selector: "stream-avatar-placeholder", inputs: ["name", "imageUrl", "location", "channel", "user", "type", "initialsType", "showOnlineIndicator"] }, { kind: "directive", type: i10.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "component", type: IconPlaceholderComponent, selector: "stream-icon-placeholder", inputs: ["icon"] }, { kind: "component", type: LoadingIndicatorPlaceholderComponent, selector: "stream-loading-indicator-placeholder" }, { kind: "component", type: MessageActionsBoxComponent, selector: "stream-message-actions-box", inputs: ["isMine", "message", "messageTextHtmlElement", "enabledActions"] }, { kind: "component", type: AttachmentListComponent, selector: "stream-attachment-list", inputs: ["messageId", "parentMessageId", "attachments"], outputs: ["imageModalStateChange"] }, { kind: "component", type: MessageReactionsComponent, selector: "stream-message-reactions", inputs: ["messageId", "messageReactionGroups", "messageReactionCounts", "latestReactions", "ownReactions"] }, { kind: "pipe", type: i4.AsyncPipe, name: "async" }, { kind: "pipe", type: i10.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5384
5474
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: MessageComponent, decorators: [{
5385
5475
  type: Component,
5386
5476
  args: [{ selector: 'stream-message', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n #container\n data-testid=\"message-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 }} str-chat__message-menu-{{ areMessageOptionsOpen ? 'opened' : 'closed' }}\"\n [class.str-chat__message--me]=\"isSentByCurrentUser\"\n [class.str-chat__message--other]=\"!isSentByCurrentUser\"\n [class.str-chat__message-simple--me]=\"isSentByCurrentUser\"\n [class.str-chat__message--has-attachment]=\"hasAttachment\"\n [class.str-chat__message--with-reactions]=\"hasReactions\"\n [class.str-chat__message--highlighted]=\"isHighlighted\"\n [class.str-chat__message-with-thread-link]=\"shouldDisplayThreadLink\"\n [class.str-chat__message-send-can-be-retried]=\"\n (message?.status === 'failed' && message?.errorStatusCode !== 403) ||\n (message?.type === 'error' && message?.moderation_details)\n \"\n [class.str-chat__message-with-touch-support]=\"hasTouchSupport\"\n [class.str-chat__message-without-touch-support]=\"!hasTouchSupport\"\n>\n <ng-container *ngIf=\"!message?.deleted_at; else deletedMessage\">\n <ng-container *ngIf=\"message?.type !== 'system'; else systemMessage\">\n <stream-avatar-placeholder\n data-testid=\"avatar\"\n class=\"str-chat-angular__avatar-host str-chat__message-sender-avatar\"\n type=\"user\"\n location=\"message-sender\"\n [imageUrl]=\"message?.user?.image\"\n [name]=\"message?.user?.name || message?.user?.id\"\n [user]=\"message?.user || undefined\"\n ></stream-avatar-placeholder>\n <div class=\"str-chat__message-inner\">\n <div\n *ngIf=\"!hasTouchSupport && areOptionsVisible\"\n class=\"str-chat__message-simple__actions str-chat__message-options\"\n data-testid=\"message-options\"\n [class.str-chat__message-actions-open]=\"areMessageOptionsOpen\"\n >\n <div\n #messageActionsToggle\n data-testid=\"message-actions-container\"\n class=\"str-chat__message-actions-container str-chat__message-simple__actions__action str-chat__message-simple__actions__action--options\"\n [floatUiLoose]=\"messageMenuFloat\"\n [looseTrigger]=\"\n messageActionsService.customActionClickHandler ? 'none' : 'click'\n \"\n [hideOnScroll]=\"false\"\n [hideOnClickOutside]=\"true\"\n [hideOnMouseLeave]=\"false\"\n [disableAnimation]=\"false\"\n [preventOverflow]=\"true\"\n [positionFixed]=\"true\"\n (onSHown)=\"areMessageOptionsOpen = true\"\n (onHidden)=\"areMessageOptionsOpen = false\"\n >\n <div\n *ngIf=\"visibleMessageActionsCount > 0\"\n class=\"str-chat__message-actions-box-button\"\n data-testid=\"message-options-button\"\n (click)=\"messageOptionsButtonClicked()\"\n (keyup.enter)=\"messageOptionsButtonClicked()\"\n >\n <stream-icon-placeholder\n icon=\"action\"\n class=\"str-chat__message-action-icon\"\n ></stream-icon-placeholder>\n </div>\n </div>\n </div>\n <div class=\"str-chat__message-reactions-host\">\n <ng-template\n #defaultMessageReactions\n let-messageReactionCounts=\"messageReactionCounts\"\n let-latestReactions=\"latestReactions\"\n let-messageId=\"messageId\"\n let-ownReactions=\"ownReactions\"\n let-messageReactionGroups=\"messageReactionGroups\"\n >\n <stream-message-reactions\n [messageReactionCounts]=\"messageReactionCounts\"\n [latestReactions]=\"latestReactions\"\n [messageId]=\"messageId\"\n [ownReactions]=\"ownReactions\"\n [messageReactionGroups]=\"messageReactionGroups\"\n ></stream-message-reactions>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.messageReactionsTemplate$ | async) ||\n defaultMessageReactions;\n context: getMessageReactionsContext()\n \"\n ></ng-container>\n </div>\n <float-ui-content #messageMenuFloat>\n <ng-template\n #defaultMessageActionsBox\n let-isMine=\"isMine\"\n let-messageInput=\"message\"\n let-enabledActions=\"enabledActions\"\n let-messageTextHtmlElement=\"messageTextHtmlElement\"\n >\n <stream-message-actions-box\n [isMine]=\"isMine\"\n [message]=\"messageInput\"\n [enabledActions]=\"enabledActions\"\n [messageTextHtmlElement]=\"messageTextHtmlElement\"\n (click)=\"messageActionsBoxClicked(messageMenuFloat)\"\n ></stream-message-actions-box>\n </ng-template>\n <ng-container>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.messageActionsBoxTemplate$ | async) ||\n defaultMessageActionsBox;\n context: getMessageActionsBoxContext()\n \"\n >\n </ng-container>\n </ng-container>\n </float-ui-content>\n <div\n class=\"str-chat__message-bubble str-chat-angular__message-bubble\"\n [class.str-chat-angular__message-bubble--attachment-modal-open]=\"\n imageAttachmentModalState === 'opened'\n \"\n data-testid=\"message-bubble\"\n [floatUiLoose]=\"messageMenuFloat\"\n #messageMenuTrigger=\"floatUiLoose\"\n #messageBubble\n looseTrigger=\"none\"\n [hideOnScroll]=\"false\"\n [hideOnClickOutside]=\"true\"\n [hideOnMouseLeave]=\"false\"\n [disableAnimation]=\"true\"\n [preventOverflow]=\"true\"\n (onShown)=\"areMessageOptionsOpen = true\"\n (onHidden)=\"areMessageOptionsOpen = false\"\n [positionFixed]=\"true\"\n >\n <ng-container *ngIf=\"hasAttachment && !message?.quoted_message\">\n <div class=\"str-chat__attachments-container\">\n <ng-container\n *ngTemplateOutlet=\"attachmentsTemplate\"\n ></ng-container>\n </div>\n </ng-container>\n <div\n *ngIf=\"message?.text || (message?.quoted_message && hasAttachment)\"\n class=\"str-chat__message-text\"\n tabindex=\"0\"\n [class.str-chat__message-text--pointer-cursor]=\"\n (message?.status === 'failed' &&\n message?.errorStatusCode !== 403) ||\n (this.message?.type === 'error' &&\n this.message?.moderation_details) ||\n message?.message_text_updated_at\n \"\n (click)=\"messageClicked()\"\n (keyup.enter)=\"messageClicked()\"\n >\n <div\n data-testid=\"inner-message\"\n class=\"str-chat__message-text-inner str-chat__message-simple-text-inner\"\n [class.str-chat__message-light-text-inner--has-attachment]=\"\n hasAttachment\n \"\n >\n <ng-container *ngTemplateOutlet=\"quotedMessage\"></ng-container>\n <ng-container *ngIf=\"hasAttachment && message?.quoted_message\">\n <ng-container\n *ngTemplateOutlet=\"attachmentsTemplate\"\n ></ng-container>\n </ng-container>\n <div\n *ngIf=\"message?.type === 'error'\"\n data-testid=\"client-error-message\"\n class=\"str-chat__simple-message--error-message str-chat__message--error-message\"\n >\n <ng-container *ngIf=\"!message?.moderation_details\">{{\n \"streamChat.Error \u00B7 Unsent\" | translate\n }}</ng-container>\n </div>\n <div\n *ngIf=\"message?.status === 'failed'\"\n data-testid=\"error-message\"\n class=\"str-chat__simple-message--error-message str-chat__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 #messageTextElement data-testid=\"text\">\n <p>\n <ng-container *ngIf=\"messageTextParts; else defaultContent\">\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-template #defaultMention let-content=\"content\">\n <span class=\"str-chat__message-mention\">{{\n content\n }}</span>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.mentionTemplate$ | async) ||\n defaultMention;\n context: getMentionContext(part)\n \"\n ></ng-container>\n </ng-template>\n </ng-container>\n </ng-container>\n <ng-template #defaultContent>\n <ng-container *ngIf=\"displayAs === 'text'; else asHTML\">\n {{ messageText || \"\" }}\n </ng-container>\n <ng-template #asHTML\n ><span\n data-testid=\"html-content\"\n [innerHTML]=\"messageText\"\n ></span\n ></ng-template>\n </ng-template>\n </p>\n </div>\n </div>\n </div>\n <stream-icon-placeholder\n class=\"str-chat__message-error-icon\"\n icon=\"error\"\n ></stream-icon-placeholder>\n </div>\n </div>\n <ng-container\n *ngTemplateOutlet=\"replyCountButton; context: { message: message }\"\n ></ng-container>\n\n <ng-container *ngTemplateOutlet=\"messageDateAndSender\"></ng-container>\n </ng-container>\n </ng-container>\n</div>\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 <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.systemMessageTemplate$ | async) ||\n defaultSystemMessage;\n context: getMessageContext()\n \"\n ></ng-container>\n <ng-template #defaultSystemMessage let-messageInput=\"message\">\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>{{ messageInput?.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</ng-template>\n\n<ng-template #quotedMessage>\n <div\n *ngIf=\"message?.quoted_message\"\n class=\"quoted-message str-chat__quoted-message-preview\"\n data-testid=\"quoted-message-container\"\n [class.mine]=\"isSentByCurrentUser\"\n (click)=\"\n jumpToMessage(\n (message?.quoted_message)!.id,\n message?.quoted_message?.parent_id\n )\n \"\n (keyup.enter)=\"\n jumpToMessage(\n (message?.quoted_message)!.id,\n message?.quoted_message?.parent_id\n )\n \"\n >\n <stream-avatar-placeholder\n data-testid=\"qouted-message-avatar\"\n class=\"str-chat-angular__avatar-host str-chat__message-sender-avatar\"\n type=\"user\"\n location=\"quoted-message-sender\"\n [imageUrl]=\"message?.quoted_message?.user?.image\"\n [name]=\"\n message?.quoted_message?.user?.name || message?.quoted_message?.user?.id\n \"\n [user]=\"message?.quoted_message?.user || undefined\"\n ></stream-avatar-placeholder>\n <div class=\"quoted-message-inner str-chat__quoted-message-bubble\">\n <ng-container\n *ngIf=\"\n message?.quoted_message?.attachments &&\n message?.quoted_message?.attachments?.length\n \"\n >\n <ng-template\n #defaultAttachments\n let-messageId=\"messageId\"\n let-attachments=\"attachments\"\n let-parentMessageId=\"parentMessageId\"\n let-imageModalStateChangeHandler=\"imageModalStateChangeHandler\"\n >\n <stream-attachment-list\n [messageId]=\"messageId\"\n [attachments]=\"attachments\"\n [parentMessageId]=\"parentMessageId\"\n (imageModalStateChange)=\"imageModalStateChangeHandler($event)\"\n ></stream-attachment-list>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.attachmentListTemplate$ | async) ||\n defaultAttachments;\n context: getQuotedMessageAttachmentListContext()\n \"\n ></ng-container>\n </ng-container>\n <div\n data-testid=\"quoted-message-text\"\n [innerHTML]=\"\n message?.quoted_message?.translation ||\n message?.quoted_message?.html ||\n message?.quoted_message?.text\n \"\n ></div>\n </div>\n </div>\n</ng-template>\n\n<ng-template #messageDateAndSender>\n <ng-container>\n <div\n *ngIf=\"shouldDisplayTranslationNotice\"\n class=\"str-chat__translation-notice\"\n data-testid=\"translation-notice\"\n >\n <button\n *ngIf=\"displayedMessageTextContent === 'translation'\"\n data-testid=\"see-original\"\n translate\n (click)=\"displayOriginalMessage()\"\n (keyup.enter)=\"displayOriginalMessage()\"\n >\n streamChat.See original (automatically translated)\n </button>\n <button\n *ngIf=\"displayedMessageTextContent === 'original'\"\n data-testid=\"see-translation\"\n translate\n (click)=\"displayTranslatedMessage()\"\n (keyup.enter)=\"displayTranslatedMessage()\"\n >\n streamChat.See translation\n </button>\n </div>\n <ng-container\n *ngIf=\"customTemplatesService.customMessageMetadataTemplate$ | async\"\n >\n <div class=\"str-chat__custom-message-metadata\">\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.customMessageMetadataTemplate$ | async)!;\n context: getMessageMetadataContext()\n \"\n ></ng-container>\n </div>\n </ng-container>\n <div\n class=\"str-chat__message-data str-chat__message-simple-data str-chat__message-metadata\"\n >\n <ng-container *ngTemplateOutlet=\"messageStatus\"></ng-container>\n\n <span\n *ngIf=\"!isSentByCurrentUser\"\n data-testid=\"sender\"\n class=\"str-chat__message-simple-name str-chat__message-sender-name\"\n >\n {{ message?.user?.name ? message?.user?.name : message?.user?.id }}\n </span>\n <span\n data-testid=\"date\"\n class=\"str-chat__message-simple-timestamp str-chat__message-simple-time\"\n >\n {{ parsedDate }}\n </span>\n <ng-container *ngIf=\"message?.message_text_updated_at\">\n <span\n data-testid=\"edited-flag\"\n class=\"str-chat__mesage-simple-edited\"\n translate\n >streamChat.Edited</span\n >\n <div\n data-testid=\"edited-timestamp\"\n class=\"str-chat__message-edited-timestamp\"\n [ngClass]=\"{\n 'str-chat__message-edited-timestamp--open': isEditedFlagOpened,\n 'str-chat__message-edited-timestamp--collapsed': !isEditedFlagOpened\n }\"\n >\n <span translate>streamChat.Edited</span>\n <time\n dateTime=\"{{ message?.message_text_updated_at }}\"\n title=\"{{ message?.message_text_updated_at }}\"\n >\n {{ pasedEditedDate }}\n </time>\n </div>\n </ng-container>\n </div>\n </ng-container>\n</ng-template>\n\n<ng-template #messageStatus>\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' && isMessageDeliveredAndRead && canDisplayReadStatus;\n else deliveredStatus\n \"\n >\n <ng-container *ngTemplateOutlet=\"readStatus\"></ng-container>\n </ng-container>\n </ng-template>\n <ng-template #deliveredStatus>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.deliveredStatusTemplate$ | async) ||\n defaultDeliveredStatus;\n context: getDeliveredStatusContext()\n \"\n ></ng-container>\n </ng-template>\n <ng-template #defaultDeliveredStatus>\n <span\n *ngIf=\"mode === 'main'\"\n class=\"str-chat__message-simple-status str-chat__message-simple-status-angular str-chat__message-status\"\n data-testid=\"delivered-indicator\"\n tabindex=\"0\"\n [floatUiLoose]=\"floatingContent\"\n loosePlacement=\"top\"\n [looseTrigger]=\"hasTouchSupport ? 'click' : 'hover'\"\n [disableAnimation]=\"true\"\n [hideOnClickOutside]=\"true\"\n [positionFixed]=\"true\"\n [preventOverflow]=\"true\"\n >\n <float-ui-content #floatingContent>\n <div class=\"str-chat__tooltip str-chat__tooltip-angular\">\n {{ \"streamChat.Delivered\" | translate }}\n </div>\n </float-ui-content>\n <stream-icon-placeholder\n data-testid=\"delivered-icon\"\n icon=\"delivered\"\n ></stream-icon-placeholder>\n </span>\n </ng-template>\n <ng-template #sendingStatus>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.sendingStatusTemplate$ | async) ||\n defaultSendingStatus;\n context: getSendingStatusContext()\n \"\n ></ng-container>\n </ng-template>\n <ng-template #defaultSendingStatus>\n <span\n class=\"str-chat__message-simple-status str-chat__message-simple-status-angular str-chat__message-status\"\n data-testid=\"sending-indicator\"\n tabindex=\"0\"\n [floatUiLoose]=\"floatingContent\"\n loosePlacement=\"top\"\n [looseTrigger]=\"hasTouchSupport ? 'click' : 'hover'\"\n [disableAnimation]=\"true\"\n [hideOnClickOutside]=\"true\"\n [positionFixed]=\"true\"\n [preventOverflow]=\"true\"\n >\n <float-ui-content #floatingContent>\n <div class=\"str-chat__tooltip str-chat__tooltip-angular\">\n {{ \"streamChat.Sending...\" | translate }}\n </div>\n </float-ui-content>\n <stream-loading-indicator-placeholder\n data-testid=\"loading-indicator\"\n ></stream-loading-indicator-placeholder>\n </span>\n </ng-template>\n <ng-template #readStatus>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.readStatusTemplate$ | async) ||\n defaultReadStatus;\n context: getReadStatusContext()\n \"\n ></ng-container>\n </ng-template>\n <ng-template #defaultReadStatus let-readByText=\"readByText\">\n <span\n class=\"str-chat__message-simple-status str-chat__message-simple-status-angular str-chat__message-status\"\n data-testid=\"read-indicator\"\n tabindex=\"0\"\n [floatUiLoose]=\"floatingContent\"\n loosePlacement=\"top\"\n [looseTrigger]=\"hasTouchSupport ? 'click' : 'hover'\"\n [disableAnimation]=\"true\"\n [hideOnClickOutside]=\"true\"\n [positionFixed]=\"true\"\n [preventOverflow]=\"true\"\n >\n <float-ui-content #floatingContent>\n <div\n class=\"str-chat__tooltip str-chat__tooltip-angular\"\n data-testid=\"read-by-tooltip\"\n >\n {{ readByText }}\n </div>\n </float-ui-content>\n <stream-icon-placeholder icon=\"read\"></stream-icon-placeholder>\n </span>\n </ng-template>\n </ng-container>\n</ng-template>\n\n<ng-template #replyCountButton>\n <div\n class=\"str-chat__message-simple-reply-button str-chat__message-replies-count-button-wrapper\"\n >\n <button\n *ngIf=\"shouldDisplayThreadLink\"\n class=\"str-chat__message-replies-count-button\"\n data-testid=\"reply-count-button\"\n (click)=\"setAsActiveParentMessage()\"\n >\n {{message?.reply_count === 1 ? ('streamChat.1 reply' | translate) : ('streamChat.{{ replyCount }}\n replies' | translate:replyCountParam)}}\n </button>\n </div>\n</ng-template>\n\n<ng-template #attachmentsTemplate>\n <ng-template\n #defaultAttachments\n let-messageId=\"messageId\"\n let-attachments=\"attachments\"\n let-parentMessageId=\"parentMessageId\"\n let-imageModalStateChangeHandler=\"imageModalStateChangeHandler\"\n >\n <stream-attachment-list\n [messageId]=\"messageId\"\n [attachments]=\"attachments\"\n [parentMessageId]=\"parentMessageId\"\n (imageModalStateChange)=\"imageModalStateChangeHandler($event)\"\n ></stream-attachment-list>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n (customTemplatesService.attachmentListTemplate$ | async) ||\n defaultAttachments;\n context: getAttachmentListContext()\n \"\n ></ng-container>\n</ng-template>\n" }]
@@ -5518,6 +5608,30 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImpor
5518
5608
  }]
5519
5609
  }], ctorParameters: function () { return []; } });
5520
5610
 
5611
+ /**
5612
+ * The `VoiceRecorderService` provides a commincation outlet between the message input and voice recorder components.
5613
+ */
5614
+ class VoiceRecorderService {
5615
+ constructor() {
5616
+ /**
5617
+ * Use this property to get/set if the recording component should be visible
5618
+ */
5619
+ this.isRecorderVisible$ = new BehaviorSubject(false);
5620
+ /**
5621
+ * The audio recording that was created
5622
+ */
5623
+ this.recording$ = new BehaviorSubject(undefined);
5624
+ }
5625
+ }
5626
+ VoiceRecorderService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: VoiceRecorderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
5627
+ VoiceRecorderService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: VoiceRecorderService, providedIn: NgModule });
5628
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: VoiceRecorderService, decorators: [{
5629
+ type: Injectable,
5630
+ args: [{
5631
+ providedIn: NgModule,
5632
+ }]
5633
+ }], ctorParameters: function () { return []; } });
5634
+
5521
5635
  /**
5522
5636
  * The `MessageInputConfigService` is used to keep a consistent configuration among the different [`MessageInput`](../components/MessageInputComponent.mdx) components if your UI has more than one input component.
5523
5637
  */
@@ -5543,6 +5657,11 @@ class MessageInputConfigService {
5543
5657
  * In `desktop` mode the `Enter` key will trigger message sending, in `mobile` mode the `Enter` key will insert a new line to the message input.
5544
5658
  */
5545
5659
  this.inputMode = 'desktop';
5660
+ /**
5661
+ * If `true` the recording will be sent as a message immediately after the recording is completed.
5662
+ * If `false`, the recording will added to the attachment preview, and users can continue composing the message.
5663
+ */
5664
+ this.sendVoiceRecordingImmediately = true;
5546
5665
  }
5547
5666
  }
5548
5667
  MessageInputConfigService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: MessageInputConfigService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
@@ -5554,6 +5673,497 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImpor
5554
5673
  }]
5555
5674
  }], ctorParameters: function () { return []; } });
5556
5675
 
5676
+ const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
5677
+
5678
+ var MediaRecordingState;
5679
+ (function (MediaRecordingState) {
5680
+ MediaRecordingState["PAUSED"] = "paused";
5681
+ MediaRecordingState["RECORDING"] = "recording";
5682
+ MediaRecordingState["STOPPED"] = "stopped";
5683
+ MediaRecordingState["ERROR"] = "error";
5684
+ })(MediaRecordingState || (MediaRecordingState = {}));
5685
+ class MultimediaRecorder {
5686
+ constructor(notificationService, chatService, transcoder) {
5687
+ this.notificationService = notificationService;
5688
+ this.chatService = chatService;
5689
+ this.transcoder = transcoder;
5690
+ this.recordingSubject = new BehaviorSubject(undefined);
5691
+ this.recordedChunkDurations = [];
5692
+ this.recordingStateSubject = new BehaviorSubject(MediaRecordingState.STOPPED);
5693
+ this.generateRecordingTitle = (mimeType) => {
5694
+ if (this.customGenerateRecordingTitle) {
5695
+ return this.customGenerateRecordingTitle({ mimeType });
5696
+ }
5697
+ else {
5698
+ return `${this.mediaType}_recording_${new Date().toISOString()}.${getExtensionFromMimeType(mimeType)}`; // extension needed so that desktop Safari can play the asset
5699
+ }
5700
+ };
5701
+ this.handleErrorEvent = (e) => {
5702
+ /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */
5703
+ this.logError(e.error);
5704
+ this.recordingStateSubject.next(MediaRecordingState.ERROR);
5705
+ this.notificationService.addTemporaryNotification('streamChat.An error has occurred during recording');
5706
+ void this.stop({ cancel: true });
5707
+ };
5708
+ this.handleDataavailableEvent = (e) => {
5709
+ if (!e.data.size)
5710
+ return;
5711
+ void this.makeRecording(e.data);
5712
+ };
5713
+ this.recording$ = this.recordingSubject.asObservable();
5714
+ this.recordingState$ = this.recordingStateSubject.asObservable();
5715
+ }
5716
+ get durationMs() {
5717
+ return (this.recordedChunkDurations.reduce((acc, val) => acc + val, 0) +
5718
+ (this.startTime ? Date.now() - this.startTime : 0));
5719
+ }
5720
+ get mediaType() {
5721
+ return this.config.mimeType.split('/')?.[0] || 'unknown';
5722
+ }
5723
+ get isRecording() {
5724
+ return (this.recordingStateSubject.value === MediaRecordingState.RECORDING ||
5725
+ this.recordingStateSubject.value === MediaRecordingState.PAUSED);
5726
+ }
5727
+ async makeRecording(blob) {
5728
+ const { mimeType } = this.config;
5729
+ try {
5730
+ if (mimeType.includes('webm')) {
5731
+ // The browser does not include duration metadata with the recorded blob
5732
+ blob = await fixWebmDuration(blob, this.durationMs, {
5733
+ logger: () => null, // prevents polluting the browser console
5734
+ });
5735
+ }
5736
+ blob = await this.transcoder.transcode(blob);
5737
+ if (!blob)
5738
+ return;
5739
+ const file = createFileFromBlobs({
5740
+ blobsArray: [blob],
5741
+ fileName: this.generateRecordingTitle(blob.type),
5742
+ mimeType: blob.type,
5743
+ });
5744
+ const previewUrl = await createUriFromBlob(file);
5745
+ const extraData = this.enrichWithExtraData();
5746
+ this.recordingSubject.next({
5747
+ recording: file,
5748
+ duration: this.durationMs / 1000,
5749
+ asset_url: previewUrl,
5750
+ mime_type: mimeType,
5751
+ ...extraData,
5752
+ });
5753
+ return file;
5754
+ }
5755
+ catch (error) {
5756
+ this.logError(error);
5757
+ this.recordingStateSubject.next(MediaRecordingState.ERROR);
5758
+ return undefined;
5759
+ }
5760
+ }
5761
+ get recordingState() {
5762
+ return this.recordingStateSubject.value;
5763
+ }
5764
+ async start() {
5765
+ if ([MediaRecordingState.RECORDING, MediaRecordingState.PAUSED].includes(this.recordingStateSubject.value)) {
5766
+ return;
5767
+ }
5768
+ this.recordingSubject.next(undefined);
5769
+ // account for requirement on iOS as per this bug report: https://bugs.webkit.org/show_bug.cgi?id=252303
5770
+ if (!navigator.mediaDevices) {
5771
+ console.warn(`[Stream Chat] Media devices API missing, it's possible your app is not served from a secure context (https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts)`);
5772
+ const error = new Error('Media recording is not supported');
5773
+ this.logError(error);
5774
+ this.recordingStateSubject.next(MediaRecordingState.ERROR);
5775
+ this.notificationService.addTemporaryNotification(`streamChat.Media recording not supported`);
5776
+ return;
5777
+ }
5778
+ try {
5779
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
5780
+ this.mediaRecorder = new MediaRecorder(stream, this.config);
5781
+ this.mediaRecorder.addEventListener('dataavailable', this.handleDataavailableEvent);
5782
+ this.mediaRecorder.addEventListener('error', this.handleErrorEvent);
5783
+ this.startTime = new Date().getTime();
5784
+ this.mediaRecorder.start();
5785
+ this.recordingStateSubject.next(MediaRecordingState.RECORDING);
5786
+ }
5787
+ catch (error) {
5788
+ this.logError(error);
5789
+ void this.stop({ cancel: true });
5790
+ this.recordingStateSubject.next(MediaRecordingState.ERROR);
5791
+ const isNotAllowed = error.name?.includes('NotAllowedError');
5792
+ this.notificationService.addTemporaryNotification(isNotAllowed
5793
+ ? `streamChat.Please grant permission to use microhpone`
5794
+ : `streamChat.Error starting recording`);
5795
+ }
5796
+ }
5797
+ pause() {
5798
+ if (this.recordingStateSubject.value !== MediaRecordingState.RECORDING)
5799
+ return;
5800
+ if (this.startTime) {
5801
+ this.recordedChunkDurations.push(new Date().getTime() - this.startTime);
5802
+ this.startTime = undefined;
5803
+ }
5804
+ this.mediaRecorder?.pause();
5805
+ this.recordingStateSubject.next(MediaRecordingState.PAUSED);
5806
+ }
5807
+ resume() {
5808
+ if (this.recordingStateSubject.value !== MediaRecordingState.PAUSED)
5809
+ return;
5810
+ this.startTime = new Date().getTime();
5811
+ this.mediaRecorder?.resume();
5812
+ this.recordingStateSubject.next(MediaRecordingState.RECORDING);
5813
+ }
5814
+ async stop(options = { cancel: false }) {
5815
+ if (this.startTime) {
5816
+ this.recordedChunkDurations.push(new Date().getTime() - this.startTime);
5817
+ this.startTime = undefined;
5818
+ }
5819
+ let recording;
5820
+ this.mediaRecorder?.stop();
5821
+ try {
5822
+ if (!options.cancel &&
5823
+ this.recordingStateSubject.value !== MediaRecordingState.ERROR) {
5824
+ recording = await new Promise((resolve, reject) => {
5825
+ this.recording$.subscribe((r) => {
5826
+ if (r) {
5827
+ resolve(r);
5828
+ }
5829
+ });
5830
+ this.recordingState$.subscribe((s) => {
5831
+ if (s === MediaRecordingState.ERROR) {
5832
+ reject(new Error(`Recording couldn't be created`));
5833
+ }
5834
+ });
5835
+ });
5836
+ }
5837
+ }
5838
+ catch {
5839
+ this.notificationService.addTemporaryNotification('streamChat.An error has occurred during recording');
5840
+ }
5841
+ finally {
5842
+ this.recordedChunkDurations = [];
5843
+ this.startTime = undefined;
5844
+ this.mediaRecorder?.removeEventListener('dataavailable', this.handleDataavailableEvent);
5845
+ this.mediaRecorder?.removeEventListener('error', this.handleErrorEvent);
5846
+ if (this.mediaRecorder?.stream?.active) {
5847
+ this.mediaRecorder?.stream?.getTracks().forEach((track) => {
5848
+ track.stop();
5849
+ this.mediaRecorder?.stream?.removeTrack(track);
5850
+ });
5851
+ this.mediaRecorder = undefined;
5852
+ }
5853
+ this.recordingStateSubject.next(MediaRecordingState.STOPPED);
5854
+ }
5855
+ return recording;
5856
+ }
5857
+ logError(error) {
5858
+ this.chatService.chatClient?.logger('error', error.message, {
5859
+ error: error,
5860
+ tag: ['MediaRecorder'],
5861
+ });
5862
+ }
5863
+ }
5864
+
5865
+ const WAV_HEADER_LENGTH_BYTES = 44;
5866
+ const BYTES_PER_SAMPLE = 2;
5867
+ const RIFF_FILE_MAX_BYTES = 4294967295;
5868
+ const HEADER = {
5869
+ AUDIO_FORMAT: { offset: 20, value: 1 },
5870
+ BITS_PER_SAMPLE: { offset: 34, value: BYTES_PER_SAMPLE * 8 },
5871
+ BLOCK_ALIGN: { offset: 32 },
5872
+ BYTE_RATE: { offset: 28 },
5873
+ CHANNEL_COUNT: { offset: 22 },
5874
+ CHUNK_ID: { offset: 0, value: 0x52494646 },
5875
+ CHUNK_SIZE: { offset: 4 },
5876
+ FILE_FORMAT: { offset: 8, value: 0x57415645 },
5877
+ SAMPLE_RATE: { offset: 24 },
5878
+ SUBCHUNK1_ID: { offset: 12, value: 0x666d7420 },
5879
+ SUBCHUNK1_SIZE: { offset: 16, value: 16 },
5880
+ SUBCHUNK2_ID: { offset: 36, value: 0x64617461 },
5881
+ SUBCHUNK2_SIZE: { offset: 40 }, // actual audio data size
5882
+ };
5883
+ /**
5884
+ * The `TranscoderService` is used to transcibe audio recording to a format that's supported by all major browsers. The SDK uses this to create voice messages.
5885
+ *
5886
+ * If you want to use your own transcoder you can provide a `customTranscoder`.
5887
+ */
5888
+ class TranscoderService {
5889
+ constructor() {
5890
+ this.config = {
5891
+ sampleRate: 16000,
5892
+ };
5893
+ this.splitDataByChannel = (audioBuffer) => Array.from({ length: audioBuffer.numberOfChannels }, (_, i) => audioBuffer.getChannelData(i));
5894
+ }
5895
+ /**
5896
+ * The default transcoder will leave audio/mp4 files as is, and transcode webm files to wav. If you want to customize this, you can provide your own transcoder using the `customTranscoder` field
5897
+ * @param blob
5898
+ * @returns the transcoded file
5899
+ */
5900
+ async transcode(blob) {
5901
+ if (this.customTranscoder) {
5902
+ return this.customTranscoder(blob);
5903
+ }
5904
+ if (blob.type.includes('audio/mp4')) {
5905
+ return blob;
5906
+ }
5907
+ const audioBuffer = await this.renderAudio(await this.toAudioBuffer(blob), this.config.sampleRate);
5908
+ const numberOfSamples = audioBuffer.duration * this.config.sampleRate;
5909
+ const fileSizeBytes = numberOfSamples * audioBuffer.numberOfChannels * BYTES_PER_SAMPLE +
5910
+ WAV_HEADER_LENGTH_BYTES;
5911
+ const arrayBuffer = new ArrayBuffer(fileSizeBytes);
5912
+ this.writeWavHeader({
5913
+ arrayBuffer,
5914
+ channelCount: audioBuffer.numberOfChannels,
5915
+ sampleRate: this.config.sampleRate,
5916
+ });
5917
+ this.writeWavAudioData({
5918
+ arrayBuffer,
5919
+ dataByChannel: this.splitDataByChannel(audioBuffer),
5920
+ });
5921
+ return new Blob([arrayBuffer], { type: 'audio/wav' });
5922
+ }
5923
+ async renderAudio(audioBuffer, sampleRate) {
5924
+ const offlineAudioCtx = new OfflineAudioContext(audioBuffer.numberOfChannels, audioBuffer.duration * sampleRate, sampleRate);
5925
+ const source = offlineAudioCtx.createBufferSource();
5926
+ source.buffer = audioBuffer;
5927
+ source.connect(offlineAudioCtx.destination);
5928
+ source.start();
5929
+ return await offlineAudioCtx.startRendering();
5930
+ }
5931
+ async toAudioBuffer(blob) {
5932
+ const audioCtx = new AudioContext();
5933
+ const arrayBuffer = await readBlobAsArrayBuffer(blob);
5934
+ const decodedData = await audioCtx.decodeAudioData(arrayBuffer);
5935
+ if (audioCtx.state !== 'closed')
5936
+ await audioCtx.close();
5937
+ return decodedData;
5938
+ }
5939
+ writeWavAudioData({ arrayBuffer, dataByChannel, }) {
5940
+ const dataView = new DataView(arrayBuffer);
5941
+ const channelCount = dataByChannel.length;
5942
+ dataByChannel.forEach((channelData, channelIndex) => {
5943
+ let writeOffset = WAV_HEADER_LENGTH_BYTES + channelCount * channelIndex;
5944
+ channelData.forEach((float32Value) => {
5945
+ dataView.setInt16(writeOffset, float32Value < 0
5946
+ ? Math.max(-1, float32Value) * 32768
5947
+ : Math.min(1, float32Value) * 32767, true);
5948
+ writeOffset += channelCount * BYTES_PER_SAMPLE;
5949
+ });
5950
+ });
5951
+ }
5952
+ writeWavHeader({ arrayBuffer, channelCount, sampleRate, }) {
5953
+ const byteRate = sampleRate * channelCount * BYTES_PER_SAMPLE; // bytes/sec
5954
+ const blockAlign = channelCount * BYTES_PER_SAMPLE;
5955
+ const dataView = new DataView(arrayBuffer);
5956
+ /*
5957
+ * The maximum size of a RIFF file is 4294967295 bytes and since the header takes up 44 bytes there are 4294967251 bytes left for the
5958
+ * data chunk.
5959
+ */
5960
+ const dataChunkSize = Math.min(dataView.byteLength - WAV_HEADER_LENGTH_BYTES, RIFF_FILE_MAX_BYTES - WAV_HEADER_LENGTH_BYTES);
5961
+ dataView.setUint32(HEADER.CHUNK_ID.offset, HEADER.CHUNK_ID.value); // "RIFF"
5962
+ dataView.setUint32(HEADER.CHUNK_SIZE.offset, arrayBuffer.byteLength - 8, true); // adjustment for the first two headers - chunk id + file size
5963
+ dataView.setUint32(HEADER.FILE_FORMAT.offset, HEADER.FILE_FORMAT.value); // "WAVE"
5964
+ dataView.setUint32(HEADER.SUBCHUNK1_ID.offset, HEADER.SUBCHUNK1_ID.value); // "fmt "
5965
+ dataView.setUint32(HEADER.SUBCHUNK1_SIZE.offset, HEADER.SUBCHUNK1_SIZE.value, true);
5966
+ dataView.setUint16(HEADER.AUDIO_FORMAT.offset, HEADER.AUDIO_FORMAT.value, true);
5967
+ dataView.setUint16(HEADER.CHANNEL_COUNT.offset, channelCount, true);
5968
+ dataView.setUint32(HEADER.SAMPLE_RATE.offset, sampleRate, true);
5969
+ dataView.setUint32(HEADER.BYTE_RATE.offset, byteRate, true);
5970
+ dataView.setUint16(HEADER.BLOCK_ALIGN.offset, blockAlign, true);
5971
+ dataView.setUint16(HEADER.BITS_PER_SAMPLE.offset, HEADER.BITS_PER_SAMPLE.value, true);
5972
+ dataView.setUint32(HEADER.SUBCHUNK2_ID.offset, HEADER.SUBCHUNK2_ID.value); // "data"
5973
+ dataView.setUint32(HEADER.SUBCHUNK2_SIZE.offset, dataChunkSize, true);
5974
+ }
5975
+ }
5976
+ TranscoderService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: TranscoderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
5977
+ TranscoderService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: TranscoderService, providedIn: NgModule });
5978
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: TranscoderService, decorators: [{
5979
+ type: Injectable,
5980
+ args: [{ providedIn: NgModule }]
5981
+ }], ctorParameters: function () { return []; } });
5982
+
5983
+ const MAX_FREQUENCY_AMPLITUDE = 255;
5984
+ const rootMeanSquare = (values) => Math.sqrt(values.reduce((acc, val) => acc + Math.pow(val, 2), 0) / values.length);
5985
+ const DEFAULT_AMPLITUDE_RECORDER_CONFIG = {
5986
+ analyserConfig: {
5987
+ fftSize: 32,
5988
+ maxDecibels: 0,
5989
+ minDecibels: -100,
5990
+ },
5991
+ sampleCount: 100,
5992
+ samplingFrequencyMs: 60,
5993
+ };
5994
+ /**
5995
+ * The `AmplitudeRecorderService` is a utility service used to create amplitude values for voice recordings, making it possible to display a wave bar
5996
+ */
5997
+ class AmplitudeRecorderService {
5998
+ constructor(chatService) {
5999
+ this.chatService = chatService;
6000
+ this.config = DEFAULT_AMPLITUDE_RECORDER_CONFIG;
6001
+ this.amplitudesSubject = new BehaviorSubject([]);
6002
+ this.errorSubject = new BehaviorSubject(undefined);
6003
+ /**
6004
+ * Start amplitude recording for the given media stream
6005
+ * @param stream
6006
+ */
6007
+ this.start = (stream) => {
6008
+ this.stop();
6009
+ this.stream = stream;
6010
+ this.init();
6011
+ this.resume();
6012
+ };
6013
+ this.amplitudes$ = this.amplitudesSubject.asObservable();
6014
+ this.error$ = this.errorSubject.asObservable();
6015
+ }
6016
+ /**
6017
+ * The recorded amplitudes
6018
+ */
6019
+ get amplitudes() {
6020
+ return this.amplitudesSubject.value;
6021
+ }
6022
+ /**
6023
+ * Temporarily pause amplitude recording, recording can be resumed with `resume`
6024
+ */
6025
+ pause() {
6026
+ clearInterval(this.amplitudeSamplingInterval);
6027
+ this.amplitudeSamplingInterval = undefined;
6028
+ }
6029
+ /**
6030
+ * Resume amplited recording after it was pasued
6031
+ */
6032
+ resume() {
6033
+ this.amplitudeSamplingInterval = setInterval(() => {
6034
+ if (!this.analyserNode) {
6035
+ return;
6036
+ }
6037
+ const frequencyBins = new Uint8Array(this.analyserNode.frequencyBinCount);
6038
+ try {
6039
+ this.analyserNode.getByteFrequencyData(frequencyBins);
6040
+ }
6041
+ catch (e) {
6042
+ this.logError(e);
6043
+ this.errorSubject.next(e);
6044
+ return;
6045
+ }
6046
+ const normalizedSignalStrength = rootMeanSquare(frequencyBins) / MAX_FREQUENCY_AMPLITUDE;
6047
+ this.amplitudesSubject.next([
6048
+ ...this.amplitudesSubject.value,
6049
+ normalizedSignalStrength,
6050
+ ]);
6051
+ }, this.config.samplingFrequencyMs);
6052
+ }
6053
+ /**
6054
+ * Stop the amplitude recording and frees up used resources
6055
+ */
6056
+ stop() {
6057
+ if (!this.stream) {
6058
+ return;
6059
+ }
6060
+ this.stream = undefined;
6061
+ clearInterval(this.amplitudeSamplingInterval);
6062
+ this.amplitudeSamplingInterval = undefined;
6063
+ this.amplitudesSubject.next([]);
6064
+ this.errorSubject.next(undefined);
6065
+ this.microphone?.disconnect();
6066
+ this.analyserNode?.disconnect();
6067
+ if (this.audioContext?.state !== 'closed') {
6068
+ void this.audioContext?.close();
6069
+ }
6070
+ }
6071
+ init() {
6072
+ if (!this.stream) {
6073
+ return;
6074
+ }
6075
+ this.audioContext = new AudioContext();
6076
+ this.analyserNode = this.audioContext.createAnalyser();
6077
+ const { analyserConfig } = this.config;
6078
+ this.analyserNode.fftSize = analyserConfig.fftSize;
6079
+ this.analyserNode.maxDecibels = analyserConfig.maxDecibels;
6080
+ this.analyserNode.minDecibels = analyserConfig.minDecibels;
6081
+ this.microphone = this.audioContext.createMediaStreamSource(this.stream);
6082
+ this.microphone.connect(this.analyserNode);
6083
+ }
6084
+ logError(error) {
6085
+ this.chatService.chatClient?.logger('error', error.message, {
6086
+ error: error,
6087
+ tag: ['AmplitudeRecorderService'],
6088
+ });
6089
+ }
6090
+ }
6091
+ AmplitudeRecorderService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: AmplitudeRecorderService, deps: [{ token: ChatClientService }], target: i0.ɵɵFactoryTarget.Injectable });
6092
+ AmplitudeRecorderService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: AmplitudeRecorderService, providedIn: NgModule });
6093
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: AmplitudeRecorderService, decorators: [{
6094
+ type: Injectable,
6095
+ args: [{ providedIn: NgModule }]
6096
+ }], ctorParameters: function () { return [{ type: ChatClientService }]; } });
6097
+
6098
+ /**
6099
+ * The `AudioRecorderService` can record an audio file, the SDK uses this to record a voice message
6100
+ */
6101
+ class AudioRecorderService extends MultimediaRecorder {
6102
+ constructor(notificationService, chatService, transcoder, amplitudeRecorder) {
6103
+ super(notificationService, chatService, transcoder);
6104
+ this.amplitudeRecorder = amplitudeRecorder;
6105
+ /**
6106
+ * Due to browser restrictions the following config is used:
6107
+ * - In Safari we record in audio/mp4
6108
+ * - For all other browsers we use audio/webm (which is then transcoded to wav)
6109
+ */
6110
+ this.config = {
6111
+ mimeType: isSafari ? 'audio/mp4;codecs=mp4a.40.2' : 'audio/webm',
6112
+ };
6113
+ }
6114
+ enrichWithExtraData() {
6115
+ const waveformData = resampleWaveForm(this.amplitudeRecorder.amplitudes, this.amplitudeRecorder.config.sampleCount);
6116
+ return { waveform_data: waveformData };
6117
+ }
6118
+ /**
6119
+ * Start audio recording
6120
+ */
6121
+ async start() {
6122
+ const result = await super.start();
6123
+ if (this.mediaRecorder?.stream) {
6124
+ this.amplitudeRecorder.start(this.mediaRecorder?.stream);
6125
+ }
6126
+ return result;
6127
+ }
6128
+ /**
6129
+ * Pause audio recording, it can be restarted using `resume`
6130
+ */
6131
+ pause() {
6132
+ const result = super.pause();
6133
+ this.amplitudeRecorder.pause();
6134
+ return result;
6135
+ }
6136
+ /**
6137
+ * Resume a previously paused recording
6138
+ */
6139
+ resume() {
6140
+ const result = super.resume();
6141
+ this.amplitudeRecorder.resume();
6142
+ return result;
6143
+ }
6144
+ /**
6145
+ * Stop the recording and free up used resources
6146
+ * @param options
6147
+ * @param options.cancel if this is `true` no recording will be created, but resources will be freed
6148
+ * @returns the recording
6149
+ */
6150
+ async stop(options) {
6151
+ try {
6152
+ const result = await super.stop(options);
6153
+ return result;
6154
+ }
6155
+ finally {
6156
+ this.amplitudeRecorder.stop();
6157
+ }
6158
+ }
6159
+ }
6160
+ AudioRecorderService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: AudioRecorderService, deps: [{ token: NotificationService }, { token: ChatClientService }, { token: TranscoderService }, { token: AmplitudeRecorderService }], target: i0.ɵɵFactoryTarget.Injectable });
6161
+ AudioRecorderService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: AudioRecorderService, providedIn: NgModel });
6162
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: AudioRecorderService, decorators: [{
6163
+ type: Injectable,
6164
+ args: [{ providedIn: NgModel }]
6165
+ }], ctorParameters: function () { return [{ type: NotificationService }, { type: ChatClientService }, { type: TranscoderService }, { type: AmplitudeRecorderService }]; } });
6166
+
5557
6167
  /**
5558
6168
  * The `AttachmentPreviewList` component displays a preview of the attachments uploaded to a message. Users can delete attachments using the preview component, or retry upload if it failed previously.
5559
6169
  */
@@ -5579,10 +6189,10 @@ class AttachmentPreviewListComponent {
5579
6189
  }
5580
6190
  }
5581
6191
  AttachmentPreviewListComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: AttachmentPreviewListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5582
- AttachmentPreviewListComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.0.4", type: AttachmentPreviewListComponent, selector: "stream-attachment-preview-list", inputs: { attachmentUploads$: "attachmentUploads$" }, outputs: { retryAttachmentUpload: "retryAttachmentUpload", deleteAttachment: "deleteAttachment" }, ngImport: i0, template: "<div\n *ngIf=\"(attachmentUploads$ | async)?.length\"\n class=\"str-chat__attachment-preview-list\"\n>\n <div class=\"str-chat__attachment-list-scroll-container\">\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=\"str-chat__attachment-preview-image\"\n data-testclass=\"attachment-image-preview\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n deleteButton;\n context: { attachmentUpload: attachmentUpload }\n \"\n ></ng-container>\n <div\n *ngIf=\"attachmentUpload.state === 'uploading'\"\n class=\"str-chat__attachment-preview-image-loading\"\n >\n <stream-loading-indicator-placeholder\n data-testclass=\"loading-indicator\"\n ></stream-loading-indicator-placeholder>\n </div>\n <ng-container\n *ngTemplateOutlet=\"\n retryButton;\n context: { attachmentUpload: attachmentUpload }\n \"\n ></ng-container>\n <img\n *ngIf=\"attachmentUpload.url || attachmentUpload.previewUri\"\n class=\"str-chat__attachment-preview-thumbnail\"\n data-testclass=\"attachment-image\"\n src=\"{{\n attachmentUpload.url\n ? attachmentUpload.url\n : attachmentUpload.previewUri\n }}\"\n alt=\"{{ attachmentUpload.file.name }}\"\n />\n </div>\n <div\n *ngIf=\"\n attachmentUpload.type === 'file' || attachmentUpload.type === 'video'\n \"\n class=\"str-chat__attachment-preview-file\"\n data-testclass=\"attachment-file-preview\"\n >\n <stream-icon-placeholder\n class=\"str-chat__attachment-preview-file-icon\"\n icon=\"unspecified-filetype\"\n ></stream-icon-placeholder>\n\n <div class=\"str-chat__attachment-preview-file-end\">\n <div class=\"str-chat__attachment-preview-file-name\">\n {{ attachmentUpload.file.name }}\n </div>\n <a\n *ngIf=\"attachmentUpload.state === 'success'\"\n class=\"str-chat__attachment-preview-file-download\"\n data-testclass=\"file-download-link\"\n download\n href=\"{{ attachmentUpload.url }}\"\n (click)=\"attachmentUpload.url ? null : $event.preventDefault()\"\n (keyup.enter)=\"\n attachmentUpload.url ? null : $event.preventDefault()\n \"\n >\n <stream-icon-placeholder icon=\"download\"></stream-icon-placeholder>\n </a>\n <stream-loading-indicator-placeholder\n *ngIf=\"attachmentUpload.state === 'uploading'\"\n data-testclass=\"loading-indicator\"\n ></stream-loading-indicator-placeholder>\n </div>\n <ng-container\n *ngTemplateOutlet=\"\n deleteButton;\n context: { attachmentUpload: attachmentUpload }\n \"\n ></ng-container>\n <ng-container\n *ngTemplateOutlet=\"\n retryButton;\n context: { attachmentUpload: attachmentUpload }\n \"\n ></ng-container>\n </div>\n </ng-container>\n </div>\n</div>\n\n<ng-template #deleteButton let-attachmentUpload=\"attachmentUpload\">\n <div\n class=\"str-chat__attachment-preview-delete\"\n data-testclass=\"file-delete\"\n role=\"button\"\n (click)=\"attachmentDeleted(attachmentUpload)\"\n (keyup.enter)=\"attachmentDeleted(attachmentUpload)\"\n >\n <stream-icon-placeholder icon=\"close\"></stream-icon-placeholder>\n </div>\n</ng-template>\n\n<ng-template #retryButton let-attachmentUpload=\"attachmentUpload\">\n <div\n *ngIf=\"attachmentUpload.state === 'error'\"\n data-testclass=\"upload-retry\"\n class=\"str-chat__attachment-preview-error str-chat__attachment-preview-error-{{\n attachmentUpload.type === 'image' ? 'image' : 'file'\n }}\"\n (click)=\"attachmentUploadRetried(attachmentUpload.file)\"\n (keyup.enter)=\"attachmentUploadRetried(attachmentUpload.file)\"\n >\n <stream-icon-placeholder icon=\"retry\"></stream-icon-placeholder>\n </div>\n</ng-template>\n", dependencies: [{ kind: "directive", type: i4.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i4.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: IconPlaceholderComponent, selector: "stream-icon-placeholder", inputs: ["icon"] }, { kind: "component", type: LoadingIndicatorPlaceholderComponent, selector: "stream-loading-indicator-placeholder" }, { kind: "pipe", type: i4.AsyncPipe, name: "async" }] });
6192
+ AttachmentPreviewListComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.0.4", type: AttachmentPreviewListComponent, selector: "stream-attachment-preview-list", inputs: { attachmentUploads$: "attachmentUploads$" }, outputs: { retryAttachmentUpload: "retryAttachmentUpload", deleteAttachment: "deleteAttachment" }, ngImport: i0, template: "<div\n *ngIf=\"(attachmentUploads$ | async)?.length\"\n class=\"str-chat__attachment-preview-list\"\n>\n <div class=\"str-chat__attachment-list-scroll-container\">\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=\"str-chat__attachment-preview-image\"\n data-testclass=\"attachment-image-preview\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n deleteButton;\n context: { attachmentUpload: attachmentUpload }\n \"\n ></ng-container>\n <div\n *ngIf=\"attachmentUpload.state === 'uploading'\"\n class=\"str-chat__attachment-preview-image-loading\"\n >\n <stream-loading-indicator-placeholder\n data-testclass=\"loading-indicator\"\n ></stream-loading-indicator-placeholder>\n </div>\n <ng-container\n *ngTemplateOutlet=\"\n retryButton;\n context: { attachmentUpload: attachmentUpload }\n \"\n ></ng-container>\n <img\n *ngIf=\"attachmentUpload.url || attachmentUpload.previewUri\"\n class=\"str-chat__attachment-preview-thumbnail\"\n data-testclass=\"attachment-image\"\n src=\"{{\n attachmentUpload.url\n ? attachmentUpload.url\n : attachmentUpload.previewUri\n }}\"\n alt=\"{{ attachmentUpload.file.name }}\"\n />\n </div>\n <div\n *ngIf=\"\n attachmentUpload.type === 'file' ||\n attachmentUpload.type === 'video' ||\n attachmentUpload.type === 'voiceRecording'\n \"\n class=\"str-chat__attachment-preview-file str-chat__attachment-preview-type-{{\n attachmentUpload.type\n }}\"\n data-testclass=\"attachment-file-preview\"\n >\n <stream-icon-placeholder\n class=\"str-chat__attachment-preview-file-icon\"\n icon=\"unspecified-filetype\"\n ></stream-icon-placeholder>\n\n <div class=\"str-chat__attachment-preview-file-end\">\n <div\n class=\"str-chat__attachment-preview-file-name\"\n title=\"{{ attachmentUpload.file.name }}\"\n >\n {{ attachmentUpload.file.name }}\n </div>\n <a\n *ngIf=\"attachmentUpload.state === 'success'\"\n class=\"str-chat__attachment-preview-file-download\"\n data-testclass=\"file-download-link\"\n download\n href=\"{{ attachmentUpload.url }}\"\n (click)=\"attachmentUpload.url ? null : $event.preventDefault()\"\n (keyup.enter)=\"\n attachmentUpload.url ? null : $event.preventDefault()\n \"\n >\n <stream-icon-placeholder icon=\"download\"></stream-icon-placeholder>\n </a>\n <stream-loading-indicator-placeholder\n *ngIf=\"attachmentUpload.state === 'uploading'\"\n data-testclass=\"loading-indicator\"\n ></stream-loading-indicator-placeholder>\n </div>\n <ng-container\n *ngTemplateOutlet=\"\n deleteButton;\n context: { attachmentUpload: attachmentUpload }\n \"\n ></ng-container>\n <ng-container\n *ngTemplateOutlet=\"\n retryButton;\n context: { attachmentUpload: attachmentUpload }\n \"\n ></ng-container>\n </div>\n </ng-container>\n </div>\n</div>\n\n<ng-template #deleteButton let-attachmentUpload=\"attachmentUpload\">\n <div\n class=\"str-chat__attachment-preview-delete\"\n data-testclass=\"file-delete\"\n role=\"button\"\n (click)=\"attachmentDeleted(attachmentUpload)\"\n (keyup.enter)=\"attachmentDeleted(attachmentUpload)\"\n >\n <stream-icon-placeholder icon=\"close\"></stream-icon-placeholder>\n </div>\n</ng-template>\n\n<ng-template #retryButton let-attachmentUpload=\"attachmentUpload\">\n <div\n *ngIf=\"attachmentUpload.state === 'error'\"\n data-testclass=\"upload-retry\"\n class=\"str-chat__attachment-preview-error str-chat__attachment-preview-error-{{\n attachmentUpload.type === 'image' ? 'image' : 'file'\n }}\"\n (click)=\"attachmentUploadRetried(attachmentUpload.file)\"\n (keyup.enter)=\"attachmentUploadRetried(attachmentUpload.file)\"\n >\n <stream-icon-placeholder icon=\"retry\"></stream-icon-placeholder>\n </div>\n</ng-template>\n", dependencies: [{ kind: "directive", type: i4.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i4.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: IconPlaceholderComponent, selector: "stream-icon-placeholder", inputs: ["icon"] }, { kind: "component", type: LoadingIndicatorPlaceholderComponent, selector: "stream-loading-indicator-placeholder" }, { kind: "pipe", type: i4.AsyncPipe, name: "async" }] });
5583
6193
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: AttachmentPreviewListComponent, decorators: [{
5584
6194
  type: Component,
5585
- args: [{ selector: 'stream-attachment-preview-list', template: "<div\n *ngIf=\"(attachmentUploads$ | async)?.length\"\n class=\"str-chat__attachment-preview-list\"\n>\n <div class=\"str-chat__attachment-list-scroll-container\">\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=\"str-chat__attachment-preview-image\"\n data-testclass=\"attachment-image-preview\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n deleteButton;\n context: { attachmentUpload: attachmentUpload }\n \"\n ></ng-container>\n <div\n *ngIf=\"attachmentUpload.state === 'uploading'\"\n class=\"str-chat__attachment-preview-image-loading\"\n >\n <stream-loading-indicator-placeholder\n data-testclass=\"loading-indicator\"\n ></stream-loading-indicator-placeholder>\n </div>\n <ng-container\n *ngTemplateOutlet=\"\n retryButton;\n context: { attachmentUpload: attachmentUpload }\n \"\n ></ng-container>\n <img\n *ngIf=\"attachmentUpload.url || attachmentUpload.previewUri\"\n class=\"str-chat__attachment-preview-thumbnail\"\n data-testclass=\"attachment-image\"\n src=\"{{\n attachmentUpload.url\n ? attachmentUpload.url\n : attachmentUpload.previewUri\n }}\"\n alt=\"{{ attachmentUpload.file.name }}\"\n />\n </div>\n <div\n *ngIf=\"\n attachmentUpload.type === 'file' || attachmentUpload.type === 'video'\n \"\n class=\"str-chat__attachment-preview-file\"\n data-testclass=\"attachment-file-preview\"\n >\n <stream-icon-placeholder\n class=\"str-chat__attachment-preview-file-icon\"\n icon=\"unspecified-filetype\"\n ></stream-icon-placeholder>\n\n <div class=\"str-chat__attachment-preview-file-end\">\n <div class=\"str-chat__attachment-preview-file-name\">\n {{ attachmentUpload.file.name }}\n </div>\n <a\n *ngIf=\"attachmentUpload.state === 'success'\"\n class=\"str-chat__attachment-preview-file-download\"\n data-testclass=\"file-download-link\"\n download\n href=\"{{ attachmentUpload.url }}\"\n (click)=\"attachmentUpload.url ? null : $event.preventDefault()\"\n (keyup.enter)=\"\n attachmentUpload.url ? null : $event.preventDefault()\n \"\n >\n <stream-icon-placeholder icon=\"download\"></stream-icon-placeholder>\n </a>\n <stream-loading-indicator-placeholder\n *ngIf=\"attachmentUpload.state === 'uploading'\"\n data-testclass=\"loading-indicator\"\n ></stream-loading-indicator-placeholder>\n </div>\n <ng-container\n *ngTemplateOutlet=\"\n deleteButton;\n context: { attachmentUpload: attachmentUpload }\n \"\n ></ng-container>\n <ng-container\n *ngTemplateOutlet=\"\n retryButton;\n context: { attachmentUpload: attachmentUpload }\n \"\n ></ng-container>\n </div>\n </ng-container>\n </div>\n</div>\n\n<ng-template #deleteButton let-attachmentUpload=\"attachmentUpload\">\n <div\n class=\"str-chat__attachment-preview-delete\"\n data-testclass=\"file-delete\"\n role=\"button\"\n (click)=\"attachmentDeleted(attachmentUpload)\"\n (keyup.enter)=\"attachmentDeleted(attachmentUpload)\"\n >\n <stream-icon-placeholder icon=\"close\"></stream-icon-placeholder>\n </div>\n</ng-template>\n\n<ng-template #retryButton let-attachmentUpload=\"attachmentUpload\">\n <div\n *ngIf=\"attachmentUpload.state === 'error'\"\n data-testclass=\"upload-retry\"\n class=\"str-chat__attachment-preview-error str-chat__attachment-preview-error-{{\n attachmentUpload.type === 'image' ? 'image' : 'file'\n }}\"\n (click)=\"attachmentUploadRetried(attachmentUpload.file)\"\n (keyup.enter)=\"attachmentUploadRetried(attachmentUpload.file)\"\n >\n <stream-icon-placeholder icon=\"retry\"></stream-icon-placeholder>\n </div>\n</ng-template>\n" }]
6195
+ args: [{ selector: 'stream-attachment-preview-list', template: "<div\n *ngIf=\"(attachmentUploads$ | async)?.length\"\n class=\"str-chat__attachment-preview-list\"\n>\n <div class=\"str-chat__attachment-list-scroll-container\">\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=\"str-chat__attachment-preview-image\"\n data-testclass=\"attachment-image-preview\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n deleteButton;\n context: { attachmentUpload: attachmentUpload }\n \"\n ></ng-container>\n <div\n *ngIf=\"attachmentUpload.state === 'uploading'\"\n class=\"str-chat__attachment-preview-image-loading\"\n >\n <stream-loading-indicator-placeholder\n data-testclass=\"loading-indicator\"\n ></stream-loading-indicator-placeholder>\n </div>\n <ng-container\n *ngTemplateOutlet=\"\n retryButton;\n context: { attachmentUpload: attachmentUpload }\n \"\n ></ng-container>\n <img\n *ngIf=\"attachmentUpload.url || attachmentUpload.previewUri\"\n class=\"str-chat__attachment-preview-thumbnail\"\n data-testclass=\"attachment-image\"\n src=\"{{\n attachmentUpload.url\n ? attachmentUpload.url\n : attachmentUpload.previewUri\n }}\"\n alt=\"{{ attachmentUpload.file.name }}\"\n />\n </div>\n <div\n *ngIf=\"\n attachmentUpload.type === 'file' ||\n attachmentUpload.type === 'video' ||\n attachmentUpload.type === 'voiceRecording'\n \"\n class=\"str-chat__attachment-preview-file str-chat__attachment-preview-type-{{\n attachmentUpload.type\n }}\"\n data-testclass=\"attachment-file-preview\"\n >\n <stream-icon-placeholder\n class=\"str-chat__attachment-preview-file-icon\"\n icon=\"unspecified-filetype\"\n ></stream-icon-placeholder>\n\n <div class=\"str-chat__attachment-preview-file-end\">\n <div\n class=\"str-chat__attachment-preview-file-name\"\n title=\"{{ attachmentUpload.file.name }}\"\n >\n {{ attachmentUpload.file.name }}\n </div>\n <a\n *ngIf=\"attachmentUpload.state === 'success'\"\n class=\"str-chat__attachment-preview-file-download\"\n data-testclass=\"file-download-link\"\n download\n href=\"{{ attachmentUpload.url }}\"\n (click)=\"attachmentUpload.url ? null : $event.preventDefault()\"\n (keyup.enter)=\"\n attachmentUpload.url ? null : $event.preventDefault()\n \"\n >\n <stream-icon-placeholder icon=\"download\"></stream-icon-placeholder>\n </a>\n <stream-loading-indicator-placeholder\n *ngIf=\"attachmentUpload.state === 'uploading'\"\n data-testclass=\"loading-indicator\"\n ></stream-loading-indicator-placeholder>\n </div>\n <ng-container\n *ngTemplateOutlet=\"\n deleteButton;\n context: { attachmentUpload: attachmentUpload }\n \"\n ></ng-container>\n <ng-container\n *ngTemplateOutlet=\"\n retryButton;\n context: { attachmentUpload: attachmentUpload }\n \"\n ></ng-container>\n </div>\n </ng-container>\n </div>\n</div>\n\n<ng-template #deleteButton let-attachmentUpload=\"attachmentUpload\">\n <div\n class=\"str-chat__attachment-preview-delete\"\n data-testclass=\"file-delete\"\n role=\"button\"\n (click)=\"attachmentDeleted(attachmentUpload)\"\n (keyup.enter)=\"attachmentDeleted(attachmentUpload)\"\n >\n <stream-icon-placeholder icon=\"close\"></stream-icon-placeholder>\n </div>\n</ng-template>\n\n<ng-template #retryButton let-attachmentUpload=\"attachmentUpload\">\n <div\n *ngIf=\"attachmentUpload.state === 'error'\"\n data-testclass=\"upload-retry\"\n class=\"str-chat__attachment-preview-error str-chat__attachment-preview-error-{{\n attachmentUpload.type === 'image' ? 'image' : 'file'\n }}\"\n (click)=\"attachmentUploadRetried(attachmentUpload.file)\"\n (keyup.enter)=\"attachmentUploadRetried(attachmentUpload.file)\"\n >\n <stream-icon-placeholder icon=\"retry\"></stream-icon-placeholder>\n </div>\n</ng-template>\n" }]
5586
6196
  }], ctorParameters: function () { return []; }, propDecorators: { attachmentUploads$: [{
5587
6197
  type: Input
5588
6198
  }], retryAttachmentUpload: [{
@@ -5595,7 +6205,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImpor
5595
6205
  * The `MessageInput` component displays an input where users can type their messages and upload files, and sends the message to the active channel. The component can be used to compose new messages or update existing ones. To send messages, the chat user needs to have the necessary [channel capability](https://getstream.io/chat/docs/javascript/channel_capabilities/?language=javascript).
5596
6206
  */
5597
6207
  class MessageInputComponent {
5598
- constructor(channelService, notificationService, attachmentService, configService, textareaType, componentFactoryResolver, cdRef, emojiInputService, customTemplatesService, messageActionsService) {
6208
+ constructor(channelService, notificationService, attachmentService, configService, textareaType, componentFactoryResolver, cdRef, emojiInputService, customTemplatesService, messageActionsService, voiceRecorderService, audioRecorder) {
5599
6209
  this.channelService = channelService;
5600
6210
  this.notificationService = notificationService;
5601
6211
  this.attachmentService = attachmentService;
@@ -5606,6 +6216,8 @@ class MessageInputComponent {
5606
6216
  this.emojiInputService = emojiInputService;
5607
6217
  this.customTemplatesService = customTemplatesService;
5608
6218
  this.messageActionsService = messageActionsService;
6219
+ this.voiceRecorderService = voiceRecorderService;
6220
+ this.audioRecorder = audioRecorder;
5609
6221
  /**
5610
6222
  * Determines if the message is being dispalyed in a channel or in a [thread](https://getstream.io/chat/docs/javascript/threads/?language=javascript).
5611
6223
  */
@@ -5624,11 +6236,16 @@ class MessageInputComponent {
5624
6236
  * Use this input to control wether a send button is rendered or not. If you don't render a send button, you can still trigger message send using the `sendMessage$` input.
5625
6237
  */
5626
6238
  this.displaySendButton = true;
6239
+ /**
6240
+ * You can enable/disable voice recordings with this input
6241
+ */
6242
+ this.displayVoiceRecordingButton = false;
5627
6243
  /**
5628
6244
  * Emits when a message was successfuly sent or updated
5629
6245
  */
5630
6246
  this.messageUpdate = new EventEmitter();
5631
6247
  this.class = 'str-chat__message-input-angular-host';
6248
+ this.isVoiceRecording = true;
5632
6249
  this.textareaValue = '';
5633
6250
  this.mentionedUsers = [];
5634
6251
  this.typingStart$ = new Subject();
@@ -5649,6 +6266,7 @@ class MessageInputComponent {
5649
6266
  if (channel && this.channel && channel.id !== this.channel.id) {
5650
6267
  this.textareaValue = '';
5651
6268
  this.attachmentService.resetAttachmentUploads();
6269
+ this.voiceRecorderService.isRecorderVisible$.next(false);
5652
6270
  }
5653
6271
  const capabilities = channel?.data?.own_capabilities;
5654
6272
  if (capabilities) {
@@ -5681,6 +6299,9 @@ class MessageInputComponent {
5681
6299
  this.mentionScope = this.configService.mentionScope;
5682
6300
  this.inputMode = this.configService.inputMode;
5683
6301
  this.subscriptions.push(this.typingStart$.subscribe(() => void this.channelService.typingStarted(this.parentMessageId)));
6302
+ this.subscriptions.push(this.voiceRecorderService.isRecorderVisible$.subscribe((isVisible) => {
6303
+ this.isVoiceRecording = isVisible;
6304
+ }));
5684
6305
  this.subscriptions.push(combineLatest([
5685
6306
  this.channelService.latestMessageDateByUserByChannels$,
5686
6307
  this.channelService.activeChannel$,
@@ -5700,6 +6321,11 @@ class MessageInputComponent {
5700
6321
  this.stopCooldown();
5701
6322
  }
5702
6323
  }));
6324
+ this.subscriptions.push(this.voiceRecorderService.recording$.subscribe((recording) => {
6325
+ if (recording) {
6326
+ void this.voiceRecordingReady(recording);
6327
+ }
6328
+ }));
5703
6329
  }
5704
6330
  ngOnInit() {
5705
6331
  this.subscriptions.push(this.customTemplatesService.emojiPickerTemplate$.subscribe((template) => {
@@ -5866,6 +6492,23 @@ class MessageInputComponent {
5866
6492
  attachmentService: this.attachmentService,
5867
6493
  };
5868
6494
  }
6495
+ async startVoiceRecording() {
6496
+ await this.audioRecorder?.start();
6497
+ if (this.audioRecorder?.isRecording) {
6498
+ this.voiceRecorderService.isRecorderVisible$.next(true);
6499
+ }
6500
+ }
6501
+ async voiceRecordingReady(recording) {
6502
+ try {
6503
+ await this.attachmentService.uploadVoiceRecording(recording);
6504
+ if (this.configService.sendVoiceRecordingImmediately) {
6505
+ await this.messageSent();
6506
+ }
6507
+ }
6508
+ finally {
6509
+ this.voiceRecorderService.isRecorderVisible$.next(false);
6510
+ }
6511
+ }
5869
6512
  get isUpdate() {
5870
6513
  return !!this.message;
5871
6514
  }
@@ -5969,15 +6612,17 @@ class MessageInputComponent {
5969
6612
  }
5970
6613
  }
5971
6614
  }
5972
- MessageInputComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: MessageInputComponent, deps: [{ token: ChannelService }, { token: NotificationService }, { token: AttachmentService }, { token: MessageInputConfigService }, { token: textareaInjectionToken }, { token: i0.ComponentFactoryResolver }, { token: i0.ChangeDetectorRef }, { token: EmojiInputService }, { token: CustomTemplatesService }, { token: MessageActionsService }], target: i0.ɵɵFactoryTarget.Component });
5973
- MessageInputComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.0.4", type: MessageInputComponent, selector: "stream-message-input", inputs: { isFileUploadEnabled: "isFileUploadEnabled", areMentionsEnabled: "areMentionsEnabled", mentionScope: "mentionScope", mode: "mode", isMultipleFileUploadEnabled: "isMultipleFileUploadEnabled", message: "message", sendMessage$: "sendMessage$", inputMode: "inputMode", autoFocus: "autoFocus", watchForMessageToEdit: "watchForMessageToEdit", displaySendButton: "displaySendButton" }, outputs: { messageUpdate: "messageUpdate" }, host: { properties: { "class": "this.class" } }, providers: [AttachmentService, EmojiInputService], viewQueries: [{ propertyName: "fileInput", first: true, predicate: ["fileInput"], descendants: true }, { propertyName: "textareaAnchor", first: true, predicate: TextareaDirective, descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"str-chat__message-input str-chat-angular__message-input\">\n <div *ngIf=\"quotedMessage\" class=\"str-chat__quoted-message-preview-header\">\n <div class=\"str-chat__quoted-message-reply-to-message\">\n {{ \"streamChat.Reply to Message\" | translate }}\n </div>\n <button\n class=\"str-chat__quoted-message-remove\"\n data-testid=\"remove-quote\"\n (click)=\"deselectMessageToQuote()\"\n (keyup.enter)=\"deselectMessageToQuote()\"\n >\n <stream-icon-placeholder icon=\"close\"></stream-icon-placeholder>\n </button>\n </div>\n <div *ngIf=\"isUpdate\" class=\"str-chat__quoted-message-preview-header\">\n <div class=\"str-chat__quoted-message-reply-to-message\">\n {{ \"streamChat.Edit Message\" | translate }}\n </div>\n <button\n class=\"str-chat__quoted-message-remove\"\n data-testid=\"remove-quote\"\n (click)=\"deselectMessageToEdit()\"\n (keyup.enter)=\"deselectMessageToEdit()\"\n >\n <stream-icon-placeholder icon=\"close\"></stream-icon-placeholder>\n </button>\n </div>\n <ng-container *ngIf=\"canSendMessages; else notAllowed\">\n <div\n class=\"str-chat__message-input-inner str-chat-angular__message-input-inner\"\n >\n <ng-container\n *ngIf=\"isFileUploadEnabled && isFileUploadAuthorized && canSendMessages\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n customAttachmentUploadTemplate || defaultAttachmentUpload;\n context: getAttachmentUploadContext()\n \"\n ></ng-container>\n <ng-template #defaultAttachmentUpload>\n <div\n class=\"str-chat__file-input-container\"\n data-testid=\"file-upload-button\"\n >\n <input\n #fileInput\n type=\"file\"\n class=\"str-chat__file-input\"\n data-testid=\"file-input\"\n [multiple]=\"isMultipleFileUploadEnabled\"\n id=\"{{ fileInputId }}\"\n (change)=\"filesSelected(fileInput.files)\"\n />\n <label class=\"str-chat__file-input-label\" for=\"{{ fileInputId }}\">\n <stream-icon-placeholder icon=\"attach\"></stream-icon-placeholder>\n </label>\n </div>\n </ng-template>\n </ng-container>\n <div class=\"str-chat__message-textarea-container\">\n <div\n *ngIf=\"quotedMessage\"\n data-testid=\"quoted-message-container\"\n class=\"str-chat__quoted-message-preview\"\n >\n <stream-avatar-placeholder\n data-testid=\"qouted-message-avatar\"\n class=\"str-chat-angular__avatar-host str-chat__message-sender-avatar\"\n type=\"user\"\n location=\"quoted-message-sender\"\n [imageUrl]=\"quotedMessage.user?.image\"\n [name]=\"quotedMessage.user?.name || quotedMessage.user?.id\"\n [user]=\"quotedMessage.user || undefined\"\n ></stream-avatar-placeholder>\n <div\n class=\"quoted-message-preview-content-inner str-chat__quoted-message-bubble\"\n >\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 class=\"str-chat__quoted-message-text\"\n data-testid=\"quoted-message-text\"\n [innerHTML]=\"\n quotedMessage.translation ||\n quotedMessage.html ||\n quotedMessage.text\n \"\n ></div>\n </div>\n </div>\n <ng-template\n #defaultAttachmentsPreview\n let-attachmentUploads$=\"attachmentUploads$\"\n let-retryUploadHandler=\"retryUploadHandler\"\n let-deleteUploadHandler=\"deleteUploadHandler\"\n >\n <stream-attachment-preview-list\n class=\"str-chat__attachment-preview-list-angular-host\"\n [attachmentUploads$]=\"attachmentUploads$\"\n (retryAttachmentUpload)=\"retryUploadHandler($event)\"\n (deleteAttachment)=\"deleteUploadHandler($event)\"\n ></stream-attachment-preview-list>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n attachmentPreviewListTemplate || defaultAttachmentsPreview;\n context: getAttachmentPreviewListContext()\n \"\n ></ng-container>\n <div class=\"str-chat__message-textarea-with-emoji-picker\">\n <ng-container\n streamTextarea\n [componentRef]=\"textareaRef\"\n [areMentionsEnabled]=\"areMentionsEnabled\"\n [mentionScope]=\"mentionScope\"\n [inputMode]=\"inputMode\"\n [autoFocus]=\"autoFocus\"\n [placeholder]=\"textareaPlaceholder\"\n [(value)]=\"textareaValue\"\n (valueChange)=\"typingStart$.next()\"\n (send)=\"messageSent()\"\n (userMentions)=\"mentionedUsers = $event\"\n ></ng-container>\n <ng-container *ngIf=\"emojiPickerTemplate\" data-testid=\"emoji-picker\">\n <ng-container\n *ngTemplateOutlet=\"\n emojiPickerTemplate;\n context: getEmojiPickerContext()\n \"\n ></ng-container>\n </ng-container>\n </div>\n </div>\n <button\n *ngIf=\"canSendMessages && !isCooldownInProgress && displaySendButton\"\n data-testid=\"send-button\"\n class=\"str-chat__send-button\"\n [disabled]=\"\n (attachmentUploadInProgressCounter$ | async)! > 0 ||\n (!textareaValue && (attachmentUploads$ | async)!.length === 0)\n \"\n (click)=\"messageSent()\"\n (keyup.enter)=\"messageSent()\"\n >\n <stream-icon-placeholder icon=\"send\"></stream-icon-placeholder>\n </button>\n <div\n *ngIf=\"isCooldownInProgress\"\n class=\"str-chat__message-input-cooldown\"\n data-testid=\"cooldown-timer\"\n >\n {{ cooldown$ | async }}\n </div>\n </div>\n </ng-container>\n <ng-template #notAllowed>\n <div\n class=\"str-chat__message-input-not-allowed\"\n data-testid=\"disabled-textarea\"\n >\n {{ disabledTextareaText | translate }}\n </div>\n </ng-template>\n</div>\n", dependencies: [{ kind: "directive", type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i4.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: AvatarPlaceholderComponent, selector: "stream-avatar-placeholder", inputs: ["name", "imageUrl", "location", "channel", "user", "type", "initialsType", "showOnlineIndicator"] }, { kind: "component", type: AttachmentListComponent, selector: "stream-attachment-list", inputs: ["messageId", "parentMessageId", "attachments"], outputs: ["imageModalStateChange"] }, { kind: "component", type: AttachmentPreviewListComponent, selector: "stream-attachment-preview-list", inputs: ["attachmentUploads$"], outputs: ["retryAttachmentUpload", "deleteAttachment"] }, { kind: "directive", type: TextareaDirective, selector: "[streamTextarea]", inputs: ["componentRef", "areMentionsEnabled", "mentionScope", "inputMode", "value", "placeholder", "autoFocus"], outputs: ["valueChange", "send", "userMentions"] }, { kind: "component", type: IconPlaceholderComponent, selector: "stream-icon-placeholder", inputs: ["icon"] }, { kind: "pipe", type: i4.AsyncPipe, name: "async" }, { kind: "pipe", type: i10.TranslatePipe, name: "translate" }] });
6615
+ MessageInputComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: MessageInputComponent, deps: [{ token: ChannelService }, { token: NotificationService }, { token: AttachmentService }, { token: MessageInputConfigService }, { token: textareaInjectionToken }, { token: i0.ComponentFactoryResolver }, { token: i0.ChangeDetectorRef }, { token: EmojiInputService }, { token: CustomTemplatesService }, { token: MessageActionsService }, { token: VoiceRecorderService }, { token: AudioRecorderService, optional: true }], target: i0.ɵɵFactoryTarget.Component });
6616
+ MessageInputComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.0.4", type: MessageInputComponent, selector: "stream-message-input", inputs: { isFileUploadEnabled: "isFileUploadEnabled", areMentionsEnabled: "areMentionsEnabled", mentionScope: "mentionScope", mode: "mode", isMultipleFileUploadEnabled: "isMultipleFileUploadEnabled", message: "message", sendMessage$: "sendMessage$", inputMode: "inputMode", autoFocus: "autoFocus", watchForMessageToEdit: "watchForMessageToEdit", displaySendButton: "displaySendButton", displayVoiceRecordingButton: "displayVoiceRecordingButton" }, outputs: { messageUpdate: "messageUpdate" }, host: { properties: { "class": "this.class" } }, providers: [AttachmentService, EmojiInputService, VoiceRecorderService], queries: [{ propertyName: "voiceRecorderRef", first: true, predicate: TemplateRef, descendants: true }], 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__message-input str-chat-angular__message-input\"\n *ngIf=\"!isVoiceRecording\"\n>\n <div *ngIf=\"quotedMessage\" class=\"str-chat__quoted-message-preview-header\">\n <div class=\"str-chat__quoted-message-reply-to-message\">\n {{ \"streamChat.Reply to Message\" | translate }}\n </div>\n <button\n class=\"str-chat__quoted-message-remove\"\n data-testid=\"remove-quote\"\n (click)=\"deselectMessageToQuote()\"\n (keyup.enter)=\"deselectMessageToQuote()\"\n >\n <stream-icon-placeholder icon=\"close\"></stream-icon-placeholder>\n </button>\n </div>\n <div *ngIf=\"isUpdate\" class=\"str-chat__quoted-message-preview-header\">\n <div class=\"str-chat__quoted-message-reply-to-message\">\n {{ \"streamChat.Edit Message\" | translate }}\n </div>\n <button\n class=\"str-chat__quoted-message-remove\"\n data-testid=\"remove-quote\"\n (click)=\"deselectMessageToEdit()\"\n (keyup.enter)=\"deselectMessageToEdit()\"\n >\n <stream-icon-placeholder icon=\"close\"></stream-icon-placeholder>\n </button>\n </div>\n <ng-container *ngIf=\"canSendMessages; else notAllowed\">\n <div\n class=\"str-chat__message-input-inner str-chat-angular__message-input-inner\"\n >\n <ng-content select=\"[message-input-start]\"></ng-content>\n <ng-container\n *ngIf=\"isFileUploadEnabled && isFileUploadAuthorized && canSendMessages\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n customAttachmentUploadTemplate || defaultAttachmentUpload;\n context: getAttachmentUploadContext()\n \"\n ></ng-container>\n <ng-template #defaultAttachmentUpload>\n <div\n class=\"str-chat__file-input-container\"\n data-testid=\"file-upload-button\"\n >\n <input\n #fileInput\n type=\"file\"\n class=\"str-chat__file-input\"\n data-testid=\"file-input\"\n [multiple]=\"isMultipleFileUploadEnabled\"\n id=\"{{ fileInputId }}\"\n (change)=\"filesSelected(fileInput.files)\"\n />\n <label class=\"str-chat__file-input-label\" for=\"{{ fileInputId }}\">\n <stream-icon-placeholder icon=\"attach\"></stream-icon-placeholder>\n </label>\n </div>\n </ng-template>\n </ng-container>\n <div class=\"str-chat__message-textarea-container\">\n <div\n *ngIf=\"quotedMessage\"\n data-testid=\"quoted-message-container\"\n class=\"str-chat__quoted-message-preview\"\n >\n <stream-avatar-placeholder\n data-testid=\"qouted-message-avatar\"\n class=\"str-chat-angular__avatar-host str-chat__message-sender-avatar\"\n type=\"user\"\n location=\"quoted-message-sender\"\n [imageUrl]=\"quotedMessage.user?.image\"\n [name]=\"quotedMessage.user?.name || quotedMessage.user?.id\"\n [user]=\"quotedMessage.user || undefined\"\n ></stream-avatar-placeholder>\n <div\n class=\"quoted-message-preview-content-inner str-chat__quoted-message-bubble\"\n >\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 class=\"str-chat__quoted-message-text\"\n data-testid=\"quoted-message-text\"\n [innerHTML]=\"\n quotedMessage.translation ||\n quotedMessage.html ||\n quotedMessage.text\n \"\n ></div>\n </div>\n </div>\n <ng-template\n #defaultAttachmentsPreview\n let-attachmentUploads$=\"attachmentUploads$\"\n let-retryUploadHandler=\"retryUploadHandler\"\n let-deleteUploadHandler=\"deleteUploadHandler\"\n >\n <stream-attachment-preview-list\n class=\"str-chat__attachment-preview-list-angular-host\"\n [attachmentUploads$]=\"attachmentUploads$\"\n (retryAttachmentUpload)=\"retryUploadHandler($event)\"\n (deleteAttachment)=\"deleteUploadHandler($event)\"\n ></stream-attachment-preview-list>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n attachmentPreviewListTemplate || defaultAttachmentsPreview;\n context: getAttachmentPreviewListContext()\n \"\n ></ng-container>\n <div class=\"str-chat__message-textarea-with-emoji-picker\">\n <ng-container\n streamTextarea\n [componentRef]=\"textareaRef\"\n [areMentionsEnabled]=\"areMentionsEnabled\"\n [mentionScope]=\"mentionScope\"\n [inputMode]=\"inputMode\"\n [autoFocus]=\"autoFocus\"\n [placeholder]=\"textareaPlaceholder\"\n [(value)]=\"textareaValue\"\n (valueChange)=\"typingStart$.next()\"\n (send)=\"messageSent()\"\n (userMentions)=\"mentionedUsers = $event\"\n ></ng-container>\n <ng-container *ngIf=\"emojiPickerTemplate\" data-testid=\"emoji-picker\">\n <ng-container\n *ngTemplateOutlet=\"\n emojiPickerTemplate;\n context: getEmojiPickerContext()\n \"\n ></ng-container>\n </ng-container>\n </div>\n </div>\n <button\n *ngIf=\"canSendMessages && !isCooldownInProgress && displaySendButton\"\n data-testid=\"send-button\"\n class=\"str-chat__send-button\"\n [disabled]=\"\n (attachmentUploadInProgressCounter$ | async)! > 0 ||\n (!textareaValue && (attachmentUploads$ | async)!.length === 0)\n \"\n (click)=\"messageSent()\"\n (keyup.enter)=\"messageSent()\"\n >\n <stream-icon-placeholder icon=\"send\"></stream-icon-placeholder>\n </button>\n <div\n *ngIf=\"isCooldownInProgress\"\n class=\"str-chat__message-input-cooldown\"\n data-testid=\"cooldown-timer\"\n >\n {{ cooldown$ | async }}\n </div>\n <button\n *ngIf=\"displayVoiceRecordingButton\"\n class=\"str-chat__start-recording-audio-button\"\n data-testid=\"start-voice-recording\"\n [disabled]=\"\n voiceRecorderService.isRecorderVisible$.value ||\n audioRecorder?.isRecording\n \"\n (click)=\"startVoiceRecording()\"\n (keyup.enter)=\"startVoiceRecording()\"\n >\n <stream-icon-placeholder icon=\"mic\"></stream-icon-placeholder>\n </button>\n <ng-content select=\"[message-input-end]\"></ng-content>\n </div>\n </ng-container>\n <ng-template #notAllowed>\n <div\n class=\"str-chat__message-input-not-allowed\"\n data-testid=\"disabled-textarea\"\n >\n {{ disabledTextareaText | translate }}\n </div>\n </ng-template>\n</div>\n<ng-template\n *ngIf=\"voiceRecorderRef\"\n [ngTemplateOutlet]=\"voiceRecorderRef\"\n [ngTemplateOutletContext]=\"{ service: voiceRecorderService }\"\n></ng-template>\n", dependencies: [{ kind: "directive", type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i4.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: AvatarPlaceholderComponent, selector: "stream-avatar-placeholder", inputs: ["name", "imageUrl", "location", "channel", "user", "type", "initialsType", "showOnlineIndicator"] }, { kind: "component", type: IconPlaceholderComponent, selector: "stream-icon-placeholder", inputs: ["icon"] }, { kind: "component", type: AttachmentListComponent, selector: "stream-attachment-list", inputs: ["messageId", "parentMessageId", "attachments"], outputs: ["imageModalStateChange"] }, { kind: "component", type: AttachmentPreviewListComponent, selector: "stream-attachment-preview-list", inputs: ["attachmentUploads$"], outputs: ["retryAttachmentUpload", "deleteAttachment"] }, { kind: "directive", type: TextareaDirective, selector: "[streamTextarea]", inputs: ["componentRef", "areMentionsEnabled", "mentionScope", "inputMode", "value", "placeholder", "autoFocus"], outputs: ["valueChange", "send", "userMentions"] }, { kind: "pipe", type: i4.AsyncPipe, name: "async" }, { kind: "pipe", type: i10.TranslatePipe, name: "translate" }] });
5974
6617
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: MessageInputComponent, decorators: [{
5975
6618
  type: Component,
5976
- args: [{ selector: 'stream-message-input', providers: [AttachmentService, EmojiInputService], template: "<div class=\"str-chat__message-input str-chat-angular__message-input\">\n <div *ngIf=\"quotedMessage\" class=\"str-chat__quoted-message-preview-header\">\n <div class=\"str-chat__quoted-message-reply-to-message\">\n {{ \"streamChat.Reply to Message\" | translate }}\n </div>\n <button\n class=\"str-chat__quoted-message-remove\"\n data-testid=\"remove-quote\"\n (click)=\"deselectMessageToQuote()\"\n (keyup.enter)=\"deselectMessageToQuote()\"\n >\n <stream-icon-placeholder icon=\"close\"></stream-icon-placeholder>\n </button>\n </div>\n <div *ngIf=\"isUpdate\" class=\"str-chat__quoted-message-preview-header\">\n <div class=\"str-chat__quoted-message-reply-to-message\">\n {{ \"streamChat.Edit Message\" | translate }}\n </div>\n <button\n class=\"str-chat__quoted-message-remove\"\n data-testid=\"remove-quote\"\n (click)=\"deselectMessageToEdit()\"\n (keyup.enter)=\"deselectMessageToEdit()\"\n >\n <stream-icon-placeholder icon=\"close\"></stream-icon-placeholder>\n </button>\n </div>\n <ng-container *ngIf=\"canSendMessages; else notAllowed\">\n <div\n class=\"str-chat__message-input-inner str-chat-angular__message-input-inner\"\n >\n <ng-container\n *ngIf=\"isFileUploadEnabled && isFileUploadAuthorized && canSendMessages\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n customAttachmentUploadTemplate || defaultAttachmentUpload;\n context: getAttachmentUploadContext()\n \"\n ></ng-container>\n <ng-template #defaultAttachmentUpload>\n <div\n class=\"str-chat__file-input-container\"\n data-testid=\"file-upload-button\"\n >\n <input\n #fileInput\n type=\"file\"\n class=\"str-chat__file-input\"\n data-testid=\"file-input\"\n [multiple]=\"isMultipleFileUploadEnabled\"\n id=\"{{ fileInputId }}\"\n (change)=\"filesSelected(fileInput.files)\"\n />\n <label class=\"str-chat__file-input-label\" for=\"{{ fileInputId }}\">\n <stream-icon-placeholder icon=\"attach\"></stream-icon-placeholder>\n </label>\n </div>\n </ng-template>\n </ng-container>\n <div class=\"str-chat__message-textarea-container\">\n <div\n *ngIf=\"quotedMessage\"\n data-testid=\"quoted-message-container\"\n class=\"str-chat__quoted-message-preview\"\n >\n <stream-avatar-placeholder\n data-testid=\"qouted-message-avatar\"\n class=\"str-chat-angular__avatar-host str-chat__message-sender-avatar\"\n type=\"user\"\n location=\"quoted-message-sender\"\n [imageUrl]=\"quotedMessage.user?.image\"\n [name]=\"quotedMessage.user?.name || quotedMessage.user?.id\"\n [user]=\"quotedMessage.user || undefined\"\n ></stream-avatar-placeholder>\n <div\n class=\"quoted-message-preview-content-inner str-chat__quoted-message-bubble\"\n >\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 class=\"str-chat__quoted-message-text\"\n data-testid=\"quoted-message-text\"\n [innerHTML]=\"\n quotedMessage.translation ||\n quotedMessage.html ||\n quotedMessage.text\n \"\n ></div>\n </div>\n </div>\n <ng-template\n #defaultAttachmentsPreview\n let-attachmentUploads$=\"attachmentUploads$\"\n let-retryUploadHandler=\"retryUploadHandler\"\n let-deleteUploadHandler=\"deleteUploadHandler\"\n >\n <stream-attachment-preview-list\n class=\"str-chat__attachment-preview-list-angular-host\"\n [attachmentUploads$]=\"attachmentUploads$\"\n (retryAttachmentUpload)=\"retryUploadHandler($event)\"\n (deleteAttachment)=\"deleteUploadHandler($event)\"\n ></stream-attachment-preview-list>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n attachmentPreviewListTemplate || defaultAttachmentsPreview;\n context: getAttachmentPreviewListContext()\n \"\n ></ng-container>\n <div class=\"str-chat__message-textarea-with-emoji-picker\">\n <ng-container\n streamTextarea\n [componentRef]=\"textareaRef\"\n [areMentionsEnabled]=\"areMentionsEnabled\"\n [mentionScope]=\"mentionScope\"\n [inputMode]=\"inputMode\"\n [autoFocus]=\"autoFocus\"\n [placeholder]=\"textareaPlaceholder\"\n [(value)]=\"textareaValue\"\n (valueChange)=\"typingStart$.next()\"\n (send)=\"messageSent()\"\n (userMentions)=\"mentionedUsers = $event\"\n ></ng-container>\n <ng-container *ngIf=\"emojiPickerTemplate\" data-testid=\"emoji-picker\">\n <ng-container\n *ngTemplateOutlet=\"\n emojiPickerTemplate;\n context: getEmojiPickerContext()\n \"\n ></ng-container>\n </ng-container>\n </div>\n </div>\n <button\n *ngIf=\"canSendMessages && !isCooldownInProgress && displaySendButton\"\n data-testid=\"send-button\"\n class=\"str-chat__send-button\"\n [disabled]=\"\n (attachmentUploadInProgressCounter$ | async)! > 0 ||\n (!textareaValue && (attachmentUploads$ | async)!.length === 0)\n \"\n (click)=\"messageSent()\"\n (keyup.enter)=\"messageSent()\"\n >\n <stream-icon-placeholder icon=\"send\"></stream-icon-placeholder>\n </button>\n <div\n *ngIf=\"isCooldownInProgress\"\n class=\"str-chat__message-input-cooldown\"\n data-testid=\"cooldown-timer\"\n >\n {{ cooldown$ | async }}\n </div>\n </div>\n </ng-container>\n <ng-template #notAllowed>\n <div\n class=\"str-chat__message-input-not-allowed\"\n data-testid=\"disabled-textarea\"\n >\n {{ disabledTextareaText | translate }}\n </div>\n </ng-template>\n</div>\n" }]
6619
+ args: [{ selector: 'stream-message-input', providers: [AttachmentService, EmojiInputService, VoiceRecorderService], template: "<div\n class=\"str-chat__message-input str-chat-angular__message-input\"\n *ngIf=\"!isVoiceRecording\"\n>\n <div *ngIf=\"quotedMessage\" class=\"str-chat__quoted-message-preview-header\">\n <div class=\"str-chat__quoted-message-reply-to-message\">\n {{ \"streamChat.Reply to Message\" | translate }}\n </div>\n <button\n class=\"str-chat__quoted-message-remove\"\n data-testid=\"remove-quote\"\n (click)=\"deselectMessageToQuote()\"\n (keyup.enter)=\"deselectMessageToQuote()\"\n >\n <stream-icon-placeholder icon=\"close\"></stream-icon-placeholder>\n </button>\n </div>\n <div *ngIf=\"isUpdate\" class=\"str-chat__quoted-message-preview-header\">\n <div class=\"str-chat__quoted-message-reply-to-message\">\n {{ \"streamChat.Edit Message\" | translate }}\n </div>\n <button\n class=\"str-chat__quoted-message-remove\"\n data-testid=\"remove-quote\"\n (click)=\"deselectMessageToEdit()\"\n (keyup.enter)=\"deselectMessageToEdit()\"\n >\n <stream-icon-placeholder icon=\"close\"></stream-icon-placeholder>\n </button>\n </div>\n <ng-container *ngIf=\"canSendMessages; else notAllowed\">\n <div\n class=\"str-chat__message-input-inner str-chat-angular__message-input-inner\"\n >\n <ng-content select=\"[message-input-start]\"></ng-content>\n <ng-container\n *ngIf=\"isFileUploadEnabled && isFileUploadAuthorized && canSendMessages\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n customAttachmentUploadTemplate || defaultAttachmentUpload;\n context: getAttachmentUploadContext()\n \"\n ></ng-container>\n <ng-template #defaultAttachmentUpload>\n <div\n class=\"str-chat__file-input-container\"\n data-testid=\"file-upload-button\"\n >\n <input\n #fileInput\n type=\"file\"\n class=\"str-chat__file-input\"\n data-testid=\"file-input\"\n [multiple]=\"isMultipleFileUploadEnabled\"\n id=\"{{ fileInputId }}\"\n (change)=\"filesSelected(fileInput.files)\"\n />\n <label class=\"str-chat__file-input-label\" for=\"{{ fileInputId }}\">\n <stream-icon-placeholder icon=\"attach\"></stream-icon-placeholder>\n </label>\n </div>\n </ng-template>\n </ng-container>\n <div class=\"str-chat__message-textarea-container\">\n <div\n *ngIf=\"quotedMessage\"\n data-testid=\"quoted-message-container\"\n class=\"str-chat__quoted-message-preview\"\n >\n <stream-avatar-placeholder\n data-testid=\"qouted-message-avatar\"\n class=\"str-chat-angular__avatar-host str-chat__message-sender-avatar\"\n type=\"user\"\n location=\"quoted-message-sender\"\n [imageUrl]=\"quotedMessage.user?.image\"\n [name]=\"quotedMessage.user?.name || quotedMessage.user?.id\"\n [user]=\"quotedMessage.user || undefined\"\n ></stream-avatar-placeholder>\n <div\n class=\"quoted-message-preview-content-inner str-chat__quoted-message-bubble\"\n >\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 class=\"str-chat__quoted-message-text\"\n data-testid=\"quoted-message-text\"\n [innerHTML]=\"\n quotedMessage.translation ||\n quotedMessage.html ||\n quotedMessage.text\n \"\n ></div>\n </div>\n </div>\n <ng-template\n #defaultAttachmentsPreview\n let-attachmentUploads$=\"attachmentUploads$\"\n let-retryUploadHandler=\"retryUploadHandler\"\n let-deleteUploadHandler=\"deleteUploadHandler\"\n >\n <stream-attachment-preview-list\n class=\"str-chat__attachment-preview-list-angular-host\"\n [attachmentUploads$]=\"attachmentUploads$\"\n (retryAttachmentUpload)=\"retryUploadHandler($event)\"\n (deleteAttachment)=\"deleteUploadHandler($event)\"\n ></stream-attachment-preview-list>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n attachmentPreviewListTemplate || defaultAttachmentsPreview;\n context: getAttachmentPreviewListContext()\n \"\n ></ng-container>\n <div class=\"str-chat__message-textarea-with-emoji-picker\">\n <ng-container\n streamTextarea\n [componentRef]=\"textareaRef\"\n [areMentionsEnabled]=\"areMentionsEnabled\"\n [mentionScope]=\"mentionScope\"\n [inputMode]=\"inputMode\"\n [autoFocus]=\"autoFocus\"\n [placeholder]=\"textareaPlaceholder\"\n [(value)]=\"textareaValue\"\n (valueChange)=\"typingStart$.next()\"\n (send)=\"messageSent()\"\n (userMentions)=\"mentionedUsers = $event\"\n ></ng-container>\n <ng-container *ngIf=\"emojiPickerTemplate\" data-testid=\"emoji-picker\">\n <ng-container\n *ngTemplateOutlet=\"\n emojiPickerTemplate;\n context: getEmojiPickerContext()\n \"\n ></ng-container>\n </ng-container>\n </div>\n </div>\n <button\n *ngIf=\"canSendMessages && !isCooldownInProgress && displaySendButton\"\n data-testid=\"send-button\"\n class=\"str-chat__send-button\"\n [disabled]=\"\n (attachmentUploadInProgressCounter$ | async)! > 0 ||\n (!textareaValue && (attachmentUploads$ | async)!.length === 0)\n \"\n (click)=\"messageSent()\"\n (keyup.enter)=\"messageSent()\"\n >\n <stream-icon-placeholder icon=\"send\"></stream-icon-placeholder>\n </button>\n <div\n *ngIf=\"isCooldownInProgress\"\n class=\"str-chat__message-input-cooldown\"\n data-testid=\"cooldown-timer\"\n >\n {{ cooldown$ | async }}\n </div>\n <button\n *ngIf=\"displayVoiceRecordingButton\"\n class=\"str-chat__start-recording-audio-button\"\n data-testid=\"start-voice-recording\"\n [disabled]=\"\n voiceRecorderService.isRecorderVisible$.value ||\n audioRecorder?.isRecording\n \"\n (click)=\"startVoiceRecording()\"\n (keyup.enter)=\"startVoiceRecording()\"\n >\n <stream-icon-placeholder icon=\"mic\"></stream-icon-placeholder>\n </button>\n <ng-content select=\"[message-input-end]\"></ng-content>\n </div>\n </ng-container>\n <ng-template #notAllowed>\n <div\n class=\"str-chat__message-input-not-allowed\"\n data-testid=\"disabled-textarea\"\n >\n {{ disabledTextareaText | translate }}\n </div>\n </ng-template>\n</div>\n<ng-template\n *ngIf=\"voiceRecorderRef\"\n [ngTemplateOutlet]=\"voiceRecorderRef\"\n [ngTemplateOutletContext]=\"{ service: voiceRecorderService }\"\n></ng-template>\n" }]
5977
6620
  }], ctorParameters: function () { return [{ type: ChannelService }, { type: NotificationService }, { type: AttachmentService }, { type: MessageInputConfigService }, { type: i0.Type, decorators: [{
5978
6621
  type: Inject,
5979
6622
  args: [textareaInjectionToken]
5980
- }] }, { type: i0.ComponentFactoryResolver }, { type: i0.ChangeDetectorRef }, { type: EmojiInputService }, { type: CustomTemplatesService }, { type: MessageActionsService }]; }, propDecorators: { isFileUploadEnabled: [{
6623
+ }] }, { type: i0.ComponentFactoryResolver }, { type: i0.ChangeDetectorRef }, { type: EmojiInputService }, { type: CustomTemplatesService }, { type: MessageActionsService }, { type: VoiceRecorderService }, { type: AudioRecorderService, decorators: [{
6624
+ type: Optional
6625
+ }] }]; }, propDecorators: { isFileUploadEnabled: [{
5981
6626
  type: Input
5982
6627
  }], areMentionsEnabled: [{
5983
6628
  type: Input
@@ -5999,8 +6644,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImpor
5999
6644
  type: Input
6000
6645
  }], displaySendButton: [{
6001
6646
  type: Input
6647
+ }], displayVoiceRecordingButton: [{
6648
+ type: Input
6002
6649
  }], messageUpdate: [{
6003
6650
  type: Output
6651
+ }], voiceRecorderRef: [{
6652
+ type: ContentChild,
6653
+ args: [TemplateRef]
6004
6654
  }], class: [{
6005
6655
  type: HostBinding
6006
6656
  }], fileInput: [{
@@ -6857,7 +7507,6 @@ class MessageListComponent {
6857
7507
  this.isLatestMessageInList = true;
6858
7508
  this.parsedDates = new Map();
6859
7509
  this.isViewInited = false;
6860
- this.isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
6861
7510
  this.forceRepaintSubject = new Subject();
6862
7511
  this.scrollPosition$ = new BehaviorSubject('bottom');
6863
7512
  this.messageNotificationJumpClicked = () => {
@@ -7103,7 +7752,7 @@ class MessageListComponent {
7103
7752
  scrollToBottom() {
7104
7753
  this.scrollContainer.nativeElement.scrollTop =
7105
7754
  this.scrollContainer.nativeElement.scrollHeight + 0.1;
7106
- if (this.isSafari) {
7755
+ if (isSafari) {
7107
7756
  this.forceRepaintSubject.next();
7108
7757
  }
7109
7758
  }
@@ -7226,7 +7875,7 @@ class MessageListComponent {
7226
7875
  (messageToAlignTo?.getBoundingClientRect()?.top || 0) -
7227
7876
  (this.anchorMessageTopOffset || 0);
7228
7877
  this.anchorMessageTopOffset = undefined;
7229
- if (this.isSafari) {
7878
+ if (isSafari) {
7230
7879
  this.forceRepaintSubject.next();
7231
7880
  }
7232
7881
  }
@@ -7417,7 +8066,7 @@ class MessageListComponent {
7417
8066
  }
7418
8067
  }
7419
8068
  MessageListComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: MessageListComponent, deps: [{ token: ChannelService }, { token: ChatClientService }, { token: CustomTemplatesService }, { token: DateParserService }, { token: i0.NgZone }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
7420
- MessageListComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.0.4", type: MessageListComponent, selector: "stream-message-list", inputs: { mode: "mode", direction: "direction", hideJumpToLatestButtonDuringScroll: "hideJumpToLatestButtonDuringScroll", displayDateSeparator: "displayDateSeparator", displayUnreadSeparator: "displayUnreadSeparator", dateSeparatorTextPos: "dateSeparatorTextPos", openMessageListAt: "openMessageListAt", hideUnreadCountForNotificationAndIndicator: "hideUnreadCountForNotificationAndIndicator", displayLoadingIndicator: "displayLoadingIndicator" }, host: { properties: { "class": "this.class" } }, viewQueries: [{ propertyName: "scrollContainer", first: true, predicate: ["scrollContainer"], descendants: true }, { propertyName: "parentMessageElement", first: true, predicate: ["parentMessageElement"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<ng-container\n *ngIf=\"\n lastReadMessageId &&\n isUnreadNotificationVisible &&\n openMessageListAt === 'last-message' &&\n displayUnreadSeparator\n \"\n>\n <ng-container\n *ngTemplateOutlet=\"\n customnewMessagesNotificationTemplate ||\n defaultUnreadMessagesNotification;\n context: {\n unreadCount: unreadCount,\n onDismiss: messageNotificationDismissClicked,\n onJump: messageNotificationJumpClicked\n }\n \"\n ></ng-container>\n</ng-container>\n<ng-template\n #defaultUnreadMessagesNotification\n let-unreadCount=\"unreadCount\"\n let-onDismiss=\"onDismiss\"\n let-onJump=\"onJump\"\n>\n <div\n class=\"str-chat__unread-messages-notification\"\n data-testid=\"unread-messages-notification\"\n >\n <button\n data-testid=\"unread-messages-notification-jump-to-message\"\n (click)=\"onJump()\"\n >\n <ng-container\n *ngIf=\"\n unreadCount > 0 && !hideUnreadCountForNotificationAndIndicator;\n else noUnreadCount\n \"\n >\n {{\n (unreadCount === 1\n ? \"streamChat.\\{\\{count\\}\\} unread message\"\n : \"streamChat.\\{\\{count\\}\\} unread messages\"\n ) | translate : { count: unreadCount }\n }}\n </ng-container>\n <ng-template #noUnreadCount>\n {{ \"streamChat.Unread messages\" | translate }}\n </ng-template>\n </button>\n <button\n data-testid=\"unread-messages-notification-dismiss\"\n (click)=\"onDismiss()\"\n >\n <stream-icon-placeholder icon=\"close\"></stream-icon-placeholder>\n </button>\n </div>\n</ng-template>\n<div #scrollContainer data-testid=\"scroll-container\" class=\"str-chat__list\">\n <ng-container *ngIf=\"mode === 'main' && isEmpty && emptyListTemplate\">\n <ng-container *ngTemplateOutlet=\"emptyListTemplate\"></ng-container>\n </ng-container>\n <div class=\"str-chat__reverse-infinite-scroll str-chat__message-list-scroll\">\n <ul class=\"str-chat__ul\">\n <li\n *ngIf=\"mode === 'thread' && parentMessage\"\n #parentMessageElement\n data-testid=\"parent-message\"\n class=\"str-chat__parent-message-li\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n messageTemplateContainer;\n context: { message: parentMessage, index: 'parent' }\n \"\n ></ng-container>\n <div data-testid=\"reply-count\" class=\"str-chat__thread-start\">\n {{parentMessage.reply_count === 1 ? ('streamChat.1 reply' | translate) : ('streamChat.{{ replyCount }}\n replies' | translate:replyCountParam)}}\n </div>\n </li>\n <ng-container *ngIf=\"mode === 'thread' && isEmpty && emptyListTemplate\">\n <ng-container *ngTemplateOutlet=\"emptyListTemplate\"></ng-container>\n </ng-container>\n <stream-loading-indicator-placeholder\n *ngIf=\"\n ((loadingState === 'loading-top' && direction === 'bottom-to-top') ||\n (loadingState === 'loading-bottom' &&\n direction === 'top-to-bottom')) &&\n displayLoadingIndicator;\n else loadingIndicatorPlaceholder\n \"\n data-testid=\"top-loading-indicator\"\n ></stream-loading-indicator-placeholder>\n <ng-container *ngIf=\"messages$ | async as messages\">\n <ng-container\n *ngFor=\"\n let message of messages;\n let i = index;\n let isFirst = first;\n let isLast = last;\n trackBy: trackByMessageId\n \"\n >\n <ng-container *ngIf=\"isFirst\">\n <ng-container\n *ngTemplateOutlet=\"\n dateSeparator;\n context: {\n date: message.created_at,\n parsedDate: parseDate(message.created_at)\n }\n \"\n ></ng-container>\n </ng-container>\n <li\n tabindex=\"0\"\n data-testclass=\"message\"\n class=\"str-chat__li str-chat__li--{{ groupStyles[i] }}\"\n id=\"{{ message.id }}\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n messageTemplateContainer;\n context: { message: message, index: i }\n \"\n ></ng-container>\n </li>\n <ng-container\n *ngIf=\"\n (lastReadMessageId === message?.id &&\n direction === 'bottom-to-top') ||\n (direction === 'top-to-bottom' &&\n lastReadMessageId === messages[i + 1]?.id)\n \"\n >\n <li\n *ngIf=\"displayUnreadSeparator\"\n id=\"stream-chat-new-message-indicator\"\n data-testid=\"new-messages-indicator\"\n class=\"str-chat__li str-chat__unread-messages-separator-wrapper\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n customnewMessagesIndicatorTemplate ||\n defaultNewMessagesIndicator;\n context: { unreadCount: unreadCount }\n \"\n ></ng-container>\n </li>\n </ng-container>\n <ng-container *ngIf=\"isNextMessageOnSeparateDate[i]\">\n <ng-container\n *ngTemplateOutlet=\"\n dateSeparator;\n context: {\n date: messages[i + 1].created_at,\n parsedDate: parseDate(messages[i + 1].created_at)\n }\n \"\n ></ng-container>\n </ng-container>\n </ng-container>\n </ng-container>\n <stream-loading-indicator-placeholder\n *ngIf=\"\n ((loadingState === 'loading-bottom' &&\n direction === 'bottom-to-top') ||\n (loadingState === 'loading-top' &&\n direction === 'top-to-bottom')) &&\n displayLoadingIndicator;\n else loadingIndicatorPlaceholder\n \"\n data-testid=\"bottom-loading-indicator\"\n ></stream-loading-indicator-placeholder>\n <ng-template #loadingIndicatorPlaceholder>\n <div class=\"str-chat__loading-indicator-placeholder\"></div>\n </ng-template>\n </ul>\n <ng-template #defaultTypingIndicator let-usersTyping$=\"usersTyping$\">\n <!-- eslint-disable-next-line @angular-eslint/template/no-any -->\n <ng-container *ngIf=\"$any(usersTyping$ | async) as users\">\n <div\n *ngIf=\"users.length > 0\"\n data-testid=\"typing-indicator\"\n class=\"str-chat__typing-indicator str-chat__typing-indicator--typing\"\n >\n <div class=\"str-chat__typing-indicator__dots\">\n <span class=\"str-chat__typing-indicator__dot\"></span>\n <span class=\"str-chat__typing-indicator__dot\"></span>\n <span class=\"str-chat__typing-indicator__dot\"></span>\n </div>\n <div\n data-testid=\"typing-users\"\n class=\"str-chat__typing-indicator__users\"\n >\n {{\n users.length === 1\n ? (\"streamChat.user is typing\"\n | translate : { user: getTypingIndicatorText(users) })\n : (\"streamChat.users are typing\"\n | translate : { users: getTypingIndicatorText(users) })\n }}\n </div>\n </div>\n </ng-container>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n typingIndicatorTemplate || defaultTypingIndicator;\n context: getTypingIndicatorContext()\n \"\n ></ng-container>\n </div>\n</div>\n<div class=\"str-chat__jump-to-latest-message\">\n <button\n *ngIf=\"isUserScrolled && isJumpToLatestButtonVisible\"\n data-testid=\"scroll-to-latest\"\n class=\"str-chat__message-notification-scroll-to-latest str-chat__message-notification-scroll-to-latest-right str-chat__circle-fab\"\n (keyup.enter)=\"jumpToLatestMessage()\"\n (click)=\"jumpToLatestMessage()\"\n >\n <stream-icon\n class=\"str-chat__jump-to-latest-icon str-chat__circle-fab-icon\"\n [icon]=\"direction === 'bottom-to-top' ? 'arrow-down' : 'arrow-up'\"\n ></stream-icon>\n <div\n *ngIf=\"newMessageCountWhileBeingScrolled > 0\"\n class=\"str-chat__message-notification str-chat__message-notification-scroll-to-latest-unread-count str-chat__jump-to-latest-unread-count\"\n >\n {{ newMessageCountWhileBeingScrolled }}\n </div>\n </button>\n</div>\n\n<ng-template #messageTemplateContainer let-message=\"message\" let-index=\"index\">\n <ng-template\n #defaultMessageTemplate\n let-messageInput=\"message\"\n let-isLastSentMessage=\"isLastSentMessage\"\n let-enabledMessageActions=\"enabledMessageActions\"\n let-mode=\"mode\"\n let-isHighlighted=\"isHighlighted\"\n let-scroll$=\"scroll$\"\n >\n <stream-message\n [message]=\"messageInput\"\n [isLastSentMessage]=\"isLastSentMessage\"\n [enabledMessageActions]=\"enabledMessageActions\"\n [mode]=\"mode\"\n [isHighlighted]=\"isHighlighted\"\n [scroll$]=\"scroll$\"\n ></stream-message>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n messageTemplate || defaultMessageTemplate;\n context: {\n message: message,\n isLastSentMessage: !!(\n lastSentMessageId && message?.id === lastSentMessageId\n ),\n enabledMessageActions: enabledMessageActions,\n mode: mode,\n isHighlighted: message?.id === highlightedMessageId,\n scroll$: scroll$\n }\n \"\n ></ng-container>\n</ng-template>\n\n<ng-template #dateSeparator let-date=\"date\" let-parsedDate=\"parsedDate\">\n <ng-container *ngIf=\"displayDateSeparator\">\n <ng-container\n *ngTemplateOutlet=\"\n customDateSeparatorTemplate || defaultDateSeparator;\n context: {\n date: date,\n parsedDate: parsedDate\n }\n \"\n ></ng-container>\n </ng-container>\n\n <ng-template\n #defaultDateSeparator\n let-date=\"date\"\n let-parsedDate=\"parsedDate\"\n >\n <div data-testid=\"date-separator\" class=\"str-chat__date-separator\">\n <hr\n *ngIf=\"\n dateSeparatorTextPos === 'right' || dateSeparatorTextPos === 'center'\n \"\n class=\"str-chat__date-separator-line\"\n />\n <div class=\"str-chat__date-separator-date\">\n {{ parsedDate }}\n </div>\n <hr\n *ngIf=\"\n dateSeparatorTextPos === 'left' || dateSeparatorTextPos === 'center'\n \"\n class=\"str-chat__date-separator-line\"\n />\n </div>\n </ng-template>\n</ng-template>\n\n<ng-template #defaultNewMessagesIndicator let-unreadCount=\"unreadCount\">\n <div class=\"str-chat__unread-messages-separator\">\n <ng-container\n *ngIf=\"\n unreadCount > 0 && !hideUnreadCountForNotificationAndIndicator;\n else noUnreadCount\n \"\n >\n {{\n (unreadCount === 1\n ? \"streamChat.\\{\\{count\\}\\} unread message\"\n : \"streamChat.\\{\\{count\\}\\} unread messages\"\n ) | translate : { count: unreadCount }\n }}\n </ng-container>\n <ng-template #noUnreadCount>\n {{ \"streamChat.Unread messages\" | translate }}\n </ng-template>\n </div>\n</ng-template>\n", dependencies: [{ kind: "directive", type: i4.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i4.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: MessageComponent, selector: "stream-message", inputs: ["message", "enabledMessageActions", "isLastSentMessage", "mode", "isHighlighted", "scroll$"] }, { kind: "component", type: IconComponent, selector: "stream-icon", inputs: ["icon"] }, { kind: "component", type: IconPlaceholderComponent, selector: "stream-icon-placeholder", inputs: ["icon"] }, { kind: "component", type: LoadingIndicatorPlaceholderComponent, selector: "stream-loading-indicator-placeholder" }, { kind: "pipe", type: i4.AsyncPipe, name: "async" }, { kind: "pipe", type: i10.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
8069
+ MessageListComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.0.4", type: MessageListComponent, selector: "stream-message-list", inputs: { mode: "mode", direction: "direction", hideJumpToLatestButtonDuringScroll: "hideJumpToLatestButtonDuringScroll", displayDateSeparator: "displayDateSeparator", displayUnreadSeparator: "displayUnreadSeparator", dateSeparatorTextPos: "dateSeparatorTextPos", openMessageListAt: "openMessageListAt", hideUnreadCountForNotificationAndIndicator: "hideUnreadCountForNotificationAndIndicator", displayLoadingIndicator: "displayLoadingIndicator" }, host: { properties: { "class": "this.class" } }, viewQueries: [{ propertyName: "scrollContainer", first: true, predicate: ["scrollContainer"], descendants: true }, { propertyName: "parentMessageElement", first: true, predicate: ["parentMessageElement"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<ng-container\n *ngIf=\"\n lastReadMessageId &&\n isUnreadNotificationVisible &&\n openMessageListAt === 'last-message' &&\n displayUnreadSeparator\n \"\n>\n <ng-container\n *ngTemplateOutlet=\"\n customnewMessagesNotificationTemplate ||\n defaultUnreadMessagesNotification;\n context: {\n unreadCount: unreadCount,\n onDismiss: messageNotificationDismissClicked,\n onJump: messageNotificationJumpClicked\n }\n \"\n ></ng-container>\n</ng-container>\n<ng-template\n #defaultUnreadMessagesNotification\n let-unreadCount=\"unreadCount\"\n let-onDismiss=\"onDismiss\"\n let-onJump=\"onJump\"\n>\n <div\n class=\"str-chat__unread-messages-notification\"\n data-testid=\"unread-messages-notification\"\n >\n <button\n data-testid=\"unread-messages-notification-jump-to-message\"\n (click)=\"onJump()\"\n >\n <ng-container\n *ngIf=\"\n unreadCount > 0 && !hideUnreadCountForNotificationAndIndicator;\n else noUnreadCount\n \"\n >\n {{\n (unreadCount === 1\n ? \"streamChat.\\{\\{count\\}\\} unread message\"\n : \"streamChat.\\{\\{count\\}\\} unread messages\"\n ) | translate : { count: unreadCount }\n }}\n </ng-container>\n <ng-template #noUnreadCount>\n {{ \"streamChat.Unread messages\" | translate }}\n </ng-template>\n </button>\n <button\n data-testid=\"unread-messages-notification-dismiss\"\n (click)=\"onDismiss()\"\n >\n <stream-icon-placeholder icon=\"close\"></stream-icon-placeholder>\n </button>\n </div>\n</ng-template>\n<div #scrollContainer data-testid=\"scroll-container\" class=\"str-chat__list\">\n <ng-container *ngIf=\"mode === 'main' && isEmpty && emptyListTemplate\">\n <ng-container *ngTemplateOutlet=\"emptyListTemplate\"></ng-container>\n </ng-container>\n <div class=\"str-chat__reverse-infinite-scroll str-chat__message-list-scroll\">\n <ul class=\"str-chat__ul\">\n <li\n *ngIf=\"mode === 'thread' && parentMessage\"\n #parentMessageElement\n data-testid=\"parent-message\"\n class=\"str-chat__parent-message-li\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n messageTemplateContainer;\n context: { message: parentMessage, index: 'parent' }\n \"\n ></ng-container>\n <div data-testid=\"reply-count\" class=\"str-chat__thread-start\">\n {{parentMessage.reply_count === 1 ? ('streamChat.1 reply' | translate) : ('streamChat.{{ replyCount }}\n replies' | translate:replyCountParam)}}\n </div>\n </li>\n <ng-container *ngIf=\"mode === 'thread' && isEmpty && emptyListTemplate\">\n <ng-container *ngTemplateOutlet=\"emptyListTemplate\"></ng-container>\n </ng-container>\n <stream-loading-indicator-placeholder\n *ngIf=\"\n ((loadingState === 'loading-top' && direction === 'bottom-to-top') ||\n (loadingState === 'loading-bottom' &&\n direction === 'top-to-bottom')) &&\n displayLoadingIndicator;\n else loadingIndicatorPlaceholder\n \"\n data-testid=\"top-loading-indicator\"\n ></stream-loading-indicator-placeholder>\n <ng-container *ngIf=\"messages$ | async as messages\">\n <ng-container\n *ngFor=\"\n let message of messages;\n let i = index;\n let isFirst = first;\n let isLast = last;\n trackBy: trackByMessageId\n \"\n >\n <ng-container *ngIf=\"isFirst\">\n <ng-container\n *ngTemplateOutlet=\"\n dateSeparator;\n context: {\n date: message.created_at,\n parsedDate: parseDate(message.created_at)\n }\n \"\n ></ng-container>\n </ng-container>\n <li\n tabindex=\"0\"\n data-testclass=\"message\"\n class=\"str-chat__li str-chat__li--{{ groupStyles[i] }}\"\n id=\"{{ message.id }}\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n messageTemplateContainer;\n context: { message: message, index: i }\n \"\n ></ng-container>\n </li>\n <ng-container\n *ngIf=\"\n (lastReadMessageId === message?.id &&\n direction === 'bottom-to-top') ||\n (direction === 'top-to-bottom' &&\n lastReadMessageId === messages[i + 1]?.id)\n \"\n >\n <li\n *ngIf=\"displayUnreadSeparator\"\n id=\"stream-chat-new-message-indicator\"\n data-testid=\"new-messages-indicator\"\n class=\"str-chat__li str-chat__unread-messages-separator-wrapper\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n customnewMessagesIndicatorTemplate ||\n defaultNewMessagesIndicator;\n context: { unreadCount: unreadCount }\n \"\n ></ng-container>\n </li>\n </ng-container>\n <ng-container *ngIf=\"isNextMessageOnSeparateDate[i]\">\n <ng-container\n *ngTemplateOutlet=\"\n dateSeparator;\n context: {\n date: messages[i + 1].created_at,\n parsedDate: parseDate(messages[i + 1].created_at)\n }\n \"\n ></ng-container>\n </ng-container>\n </ng-container>\n </ng-container>\n <stream-loading-indicator-placeholder\n *ngIf=\"\n ((loadingState === 'loading-bottom' &&\n direction === 'bottom-to-top') ||\n (loadingState === 'loading-top' &&\n direction === 'top-to-bottom')) &&\n displayLoadingIndicator;\n else loadingIndicatorPlaceholder\n \"\n data-testid=\"bottom-loading-indicator\"\n ></stream-loading-indicator-placeholder>\n <ng-template #loadingIndicatorPlaceholder>\n <div class=\"str-chat__loading-indicator-placeholder\"></div>\n </ng-template>\n </ul>\n <ng-template #defaultTypingIndicator let-usersTyping$=\"usersTyping$\">\n <!-- eslint-disable-next-line @angular-eslint/template/no-any -->\n <ng-container *ngIf=\"$any(usersTyping$ | async) as users\">\n <div\n *ngIf=\"users.length > 0\"\n data-testid=\"typing-indicator\"\n class=\"str-chat__typing-indicator str-chat__typing-indicator--typing\"\n >\n <div class=\"str-chat__typing-indicator__dots\">\n <span class=\"str-chat__typing-indicator__dot\"></span>\n <span class=\"str-chat__typing-indicator__dot\"></span>\n <span class=\"str-chat__typing-indicator__dot\"></span>\n </div>\n <div\n data-testid=\"typing-users\"\n class=\"str-chat__typing-indicator__users\"\n >\n {{\n users.length === 1\n ? (\"streamChat.user is typing\"\n | translate : { user: getTypingIndicatorText(users) })\n : (\"streamChat.users are typing\"\n | translate : { users: getTypingIndicatorText(users) })\n }}\n </div>\n </div>\n </ng-container>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n typingIndicatorTemplate || defaultTypingIndicator;\n context: getTypingIndicatorContext()\n \"\n ></ng-container>\n </div>\n</div>\n<div class=\"str-chat__jump-to-latest-message\">\n <button\n *ngIf=\"isUserScrolled && isJumpToLatestButtonVisible\"\n data-testid=\"scroll-to-latest\"\n class=\"str-chat__message-notification-scroll-to-latest str-chat__message-notification-scroll-to-latest-right str-chat__circle-fab\"\n (keyup.enter)=\"jumpToLatestMessage()\"\n (click)=\"jumpToLatestMessage()\"\n >\n <stream-icon\n class=\"str-chat__jump-to-latest-icon str-chat__circle-fab-icon\"\n [icon]=\"direction === 'bottom-to-top' ? 'arrow-down' : 'arrow-up'\"\n ></stream-icon>\n <div\n *ngIf=\"newMessageCountWhileBeingScrolled > 0\"\n class=\"str-chat__message-notification str-chat__message-notification-scroll-to-latest-unread-count str-chat__jump-to-latest-unread-count\"\n >\n {{ newMessageCountWhileBeingScrolled }}\n </div>\n </button>\n</div>\n\n<ng-template #messageTemplateContainer let-message=\"message\" let-index=\"index\">\n <ng-template\n #defaultMessageTemplate\n let-messageInput=\"message\"\n let-isLastSentMessage=\"isLastSentMessage\"\n let-enabledMessageActions=\"enabledMessageActions\"\n let-mode=\"mode\"\n let-isHighlighted=\"isHighlighted\"\n let-scroll$=\"scroll$\"\n >\n <stream-message\n [message]=\"messageInput\"\n [isLastSentMessage]=\"isLastSentMessage\"\n [enabledMessageActions]=\"enabledMessageActions\"\n [mode]=\"mode\"\n [isHighlighted]=\"isHighlighted\"\n [scroll$]=\"scroll$\"\n ></stream-message>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n messageTemplate || defaultMessageTemplate;\n context: {\n message: message,\n isLastSentMessage: !!(\n lastSentMessageId && message?.id === lastSentMessageId\n ),\n enabledMessageActions: enabledMessageActions,\n mode: mode,\n isHighlighted: message?.id === highlightedMessageId,\n scroll$: scroll$\n }\n \"\n ></ng-container>\n</ng-template>\n\n<ng-template #dateSeparator let-date=\"date\" let-parsedDate=\"parsedDate\">\n <ng-container *ngIf=\"displayDateSeparator\">\n <ng-container\n *ngTemplateOutlet=\"\n customDateSeparatorTemplate || defaultDateSeparator;\n context: {\n date: date,\n parsedDate: parsedDate\n }\n \"\n ></ng-container>\n </ng-container>\n\n <ng-template\n #defaultDateSeparator\n let-date=\"date\"\n let-parsedDate=\"parsedDate\"\n >\n <div data-testid=\"date-separator\" class=\"str-chat__date-separator\">\n <hr\n *ngIf=\"\n dateSeparatorTextPos === 'right' || dateSeparatorTextPos === 'center'\n \"\n class=\"str-chat__date-separator-line\"\n />\n <div class=\"str-chat__date-separator-date\">\n {{ parsedDate }}\n </div>\n <hr\n *ngIf=\"\n dateSeparatorTextPos === 'left' || dateSeparatorTextPos === 'center'\n \"\n class=\"str-chat__date-separator-line\"\n />\n </div>\n </ng-template>\n</ng-template>\n\n<ng-template #defaultNewMessagesIndicator let-unreadCount=\"unreadCount\">\n <div class=\"str-chat__unread-messages-separator\">\n <ng-container\n *ngIf=\"\n unreadCount > 0 && !hideUnreadCountForNotificationAndIndicator;\n else noUnreadCount\n \"\n >\n {{\n (unreadCount === 1\n ? \"streamChat.\\{\\{count\\}\\} unread message\"\n : \"streamChat.\\{\\{count\\}\\} unread messages\"\n ) | translate : { count: unreadCount }\n }}\n </ng-container>\n <ng-template #noUnreadCount>\n {{ \"streamChat.Unread messages\" | translate }}\n </ng-template>\n </div>\n</ng-template>\n", dependencies: [{ kind: "directive", type: i4.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i4.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: IconComponent, selector: "stream-icon", inputs: ["icon"] }, { kind: "component", type: IconPlaceholderComponent, selector: "stream-icon-placeholder", inputs: ["icon"] }, { kind: "component", type: LoadingIndicatorPlaceholderComponent, selector: "stream-loading-indicator-placeholder" }, { kind: "component", type: MessageComponent, selector: "stream-message", inputs: ["message", "enabledMessageActions", "isLastSentMessage", "mode", "isHighlighted", "scroll$"] }, { kind: "pipe", type: i4.AsyncPipe, name: "async" }, { kind: "pipe", type: i10.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
7421
8070
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: MessageListComponent, decorators: [{
7422
8071
  type: Component,
7423
8072
  args: [{ selector: 'stream-message-list', changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container\n *ngIf=\"\n lastReadMessageId &&\n isUnreadNotificationVisible &&\n openMessageListAt === 'last-message' &&\n displayUnreadSeparator\n \"\n>\n <ng-container\n *ngTemplateOutlet=\"\n customnewMessagesNotificationTemplate ||\n defaultUnreadMessagesNotification;\n context: {\n unreadCount: unreadCount,\n onDismiss: messageNotificationDismissClicked,\n onJump: messageNotificationJumpClicked\n }\n \"\n ></ng-container>\n</ng-container>\n<ng-template\n #defaultUnreadMessagesNotification\n let-unreadCount=\"unreadCount\"\n let-onDismiss=\"onDismiss\"\n let-onJump=\"onJump\"\n>\n <div\n class=\"str-chat__unread-messages-notification\"\n data-testid=\"unread-messages-notification\"\n >\n <button\n data-testid=\"unread-messages-notification-jump-to-message\"\n (click)=\"onJump()\"\n >\n <ng-container\n *ngIf=\"\n unreadCount > 0 && !hideUnreadCountForNotificationAndIndicator;\n else noUnreadCount\n \"\n >\n {{\n (unreadCount === 1\n ? \"streamChat.\\{\\{count\\}\\} unread message\"\n : \"streamChat.\\{\\{count\\}\\} unread messages\"\n ) | translate : { count: unreadCount }\n }}\n </ng-container>\n <ng-template #noUnreadCount>\n {{ \"streamChat.Unread messages\" | translate }}\n </ng-template>\n </button>\n <button\n data-testid=\"unread-messages-notification-dismiss\"\n (click)=\"onDismiss()\"\n >\n <stream-icon-placeholder icon=\"close\"></stream-icon-placeholder>\n </button>\n </div>\n</ng-template>\n<div #scrollContainer data-testid=\"scroll-container\" class=\"str-chat__list\">\n <ng-container *ngIf=\"mode === 'main' && isEmpty && emptyListTemplate\">\n <ng-container *ngTemplateOutlet=\"emptyListTemplate\"></ng-container>\n </ng-container>\n <div class=\"str-chat__reverse-infinite-scroll str-chat__message-list-scroll\">\n <ul class=\"str-chat__ul\">\n <li\n *ngIf=\"mode === 'thread' && parentMessage\"\n #parentMessageElement\n data-testid=\"parent-message\"\n class=\"str-chat__parent-message-li\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n messageTemplateContainer;\n context: { message: parentMessage, index: 'parent' }\n \"\n ></ng-container>\n <div data-testid=\"reply-count\" class=\"str-chat__thread-start\">\n {{parentMessage.reply_count === 1 ? ('streamChat.1 reply' | translate) : ('streamChat.{{ replyCount }}\n replies' | translate:replyCountParam)}}\n </div>\n </li>\n <ng-container *ngIf=\"mode === 'thread' && isEmpty && emptyListTemplate\">\n <ng-container *ngTemplateOutlet=\"emptyListTemplate\"></ng-container>\n </ng-container>\n <stream-loading-indicator-placeholder\n *ngIf=\"\n ((loadingState === 'loading-top' && direction === 'bottom-to-top') ||\n (loadingState === 'loading-bottom' &&\n direction === 'top-to-bottom')) &&\n displayLoadingIndicator;\n else loadingIndicatorPlaceholder\n \"\n data-testid=\"top-loading-indicator\"\n ></stream-loading-indicator-placeholder>\n <ng-container *ngIf=\"messages$ | async as messages\">\n <ng-container\n *ngFor=\"\n let message of messages;\n let i = index;\n let isFirst = first;\n let isLast = last;\n trackBy: trackByMessageId\n \"\n >\n <ng-container *ngIf=\"isFirst\">\n <ng-container\n *ngTemplateOutlet=\"\n dateSeparator;\n context: {\n date: message.created_at,\n parsedDate: parseDate(message.created_at)\n }\n \"\n ></ng-container>\n </ng-container>\n <li\n tabindex=\"0\"\n data-testclass=\"message\"\n class=\"str-chat__li str-chat__li--{{ groupStyles[i] }}\"\n id=\"{{ message.id }}\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n messageTemplateContainer;\n context: { message: message, index: i }\n \"\n ></ng-container>\n </li>\n <ng-container\n *ngIf=\"\n (lastReadMessageId === message?.id &&\n direction === 'bottom-to-top') ||\n (direction === 'top-to-bottom' &&\n lastReadMessageId === messages[i + 1]?.id)\n \"\n >\n <li\n *ngIf=\"displayUnreadSeparator\"\n id=\"stream-chat-new-message-indicator\"\n data-testid=\"new-messages-indicator\"\n class=\"str-chat__li str-chat__unread-messages-separator-wrapper\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n customnewMessagesIndicatorTemplate ||\n defaultNewMessagesIndicator;\n context: { unreadCount: unreadCount }\n \"\n ></ng-container>\n </li>\n </ng-container>\n <ng-container *ngIf=\"isNextMessageOnSeparateDate[i]\">\n <ng-container\n *ngTemplateOutlet=\"\n dateSeparator;\n context: {\n date: messages[i + 1].created_at,\n parsedDate: parseDate(messages[i + 1].created_at)\n }\n \"\n ></ng-container>\n </ng-container>\n </ng-container>\n </ng-container>\n <stream-loading-indicator-placeholder\n *ngIf=\"\n ((loadingState === 'loading-bottom' &&\n direction === 'bottom-to-top') ||\n (loadingState === 'loading-top' &&\n direction === 'top-to-bottom')) &&\n displayLoadingIndicator;\n else loadingIndicatorPlaceholder\n \"\n data-testid=\"bottom-loading-indicator\"\n ></stream-loading-indicator-placeholder>\n <ng-template #loadingIndicatorPlaceholder>\n <div class=\"str-chat__loading-indicator-placeholder\"></div>\n </ng-template>\n </ul>\n <ng-template #defaultTypingIndicator let-usersTyping$=\"usersTyping$\">\n <!-- eslint-disable-next-line @angular-eslint/template/no-any -->\n <ng-container *ngIf=\"$any(usersTyping$ | async) as users\">\n <div\n *ngIf=\"users.length > 0\"\n data-testid=\"typing-indicator\"\n class=\"str-chat__typing-indicator str-chat__typing-indicator--typing\"\n >\n <div class=\"str-chat__typing-indicator__dots\">\n <span class=\"str-chat__typing-indicator__dot\"></span>\n <span class=\"str-chat__typing-indicator__dot\"></span>\n <span class=\"str-chat__typing-indicator__dot\"></span>\n </div>\n <div\n data-testid=\"typing-users\"\n class=\"str-chat__typing-indicator__users\"\n >\n {{\n users.length === 1\n ? (\"streamChat.user is typing\"\n | translate : { user: getTypingIndicatorText(users) })\n : (\"streamChat.users are typing\"\n | translate : { users: getTypingIndicatorText(users) })\n }}\n </div>\n </div>\n </ng-container>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n typingIndicatorTemplate || defaultTypingIndicator;\n context: getTypingIndicatorContext()\n \"\n ></ng-container>\n </div>\n</div>\n<div class=\"str-chat__jump-to-latest-message\">\n <button\n *ngIf=\"isUserScrolled && isJumpToLatestButtonVisible\"\n data-testid=\"scroll-to-latest\"\n class=\"str-chat__message-notification-scroll-to-latest str-chat__message-notification-scroll-to-latest-right str-chat__circle-fab\"\n (keyup.enter)=\"jumpToLatestMessage()\"\n (click)=\"jumpToLatestMessage()\"\n >\n <stream-icon\n class=\"str-chat__jump-to-latest-icon str-chat__circle-fab-icon\"\n [icon]=\"direction === 'bottom-to-top' ? 'arrow-down' : 'arrow-up'\"\n ></stream-icon>\n <div\n *ngIf=\"newMessageCountWhileBeingScrolled > 0\"\n class=\"str-chat__message-notification str-chat__message-notification-scroll-to-latest-unread-count str-chat__jump-to-latest-unread-count\"\n >\n {{ newMessageCountWhileBeingScrolled }}\n </div>\n </button>\n</div>\n\n<ng-template #messageTemplateContainer let-message=\"message\" let-index=\"index\">\n <ng-template\n #defaultMessageTemplate\n let-messageInput=\"message\"\n let-isLastSentMessage=\"isLastSentMessage\"\n let-enabledMessageActions=\"enabledMessageActions\"\n let-mode=\"mode\"\n let-isHighlighted=\"isHighlighted\"\n let-scroll$=\"scroll$\"\n >\n <stream-message\n [message]=\"messageInput\"\n [isLastSentMessage]=\"isLastSentMessage\"\n [enabledMessageActions]=\"enabledMessageActions\"\n [mode]=\"mode\"\n [isHighlighted]=\"isHighlighted\"\n [scroll$]=\"scroll$\"\n ></stream-message>\n </ng-template>\n <ng-container\n *ngTemplateOutlet=\"\n messageTemplate || defaultMessageTemplate;\n context: {\n message: message,\n isLastSentMessage: !!(\n lastSentMessageId && message?.id === lastSentMessageId\n ),\n enabledMessageActions: enabledMessageActions,\n mode: mode,\n isHighlighted: message?.id === highlightedMessageId,\n scroll$: scroll$\n }\n \"\n ></ng-container>\n</ng-template>\n\n<ng-template #dateSeparator let-date=\"date\" let-parsedDate=\"parsedDate\">\n <ng-container *ngIf=\"displayDateSeparator\">\n <ng-container\n *ngTemplateOutlet=\"\n customDateSeparatorTemplate || defaultDateSeparator;\n context: {\n date: date,\n parsedDate: parsedDate\n }\n \"\n ></ng-container>\n </ng-container>\n\n <ng-template\n #defaultDateSeparator\n let-date=\"date\"\n let-parsedDate=\"parsedDate\"\n >\n <div data-testid=\"date-separator\" class=\"str-chat__date-separator\">\n <hr\n *ngIf=\"\n dateSeparatorTextPos === 'right' || dateSeparatorTextPos === 'center'\n \"\n class=\"str-chat__date-separator-line\"\n />\n <div class=\"str-chat__date-separator-date\">\n {{ parsedDate }}\n </div>\n <hr\n *ngIf=\"\n dateSeparatorTextPos === 'left' || dateSeparatorTextPos === 'center'\n \"\n class=\"str-chat__date-separator-line\"\n />\n </div>\n </ng-template>\n</ng-template>\n\n<ng-template #defaultNewMessagesIndicator let-unreadCount=\"unreadCount\">\n <div class=\"str-chat__unread-messages-separator\">\n <ng-container\n *ngIf=\"\n unreadCount > 0 && !hideUnreadCountForNotificationAndIndicator;\n else noUnreadCount\n \"\n >\n {{\n (unreadCount === 1\n ? \"streamChat.\\{\\{count\\}\\} unread message\"\n : \"streamChat.\\{\\{count\\}\\} unread messages\"\n ) | translate : { count: unreadCount }\n }}\n </ng-container>\n <ng-template #noUnreadCount>\n {{ \"streamChat.Unread messages\" | translate }}\n </ng-template>\n </div>\n</ng-template>\n" }]
@@ -7506,6 +8155,50 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImpor
7506
8155
  }]
7507
8156
  }] });
7508
8157
 
8158
+ class IconModule {
8159
+ }
8160
+ IconModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: IconModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
8161
+ IconModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "15.0.4", ngImport: i0, type: IconModule, declarations: [IconComponent,
8162
+ IconPlaceholderComponent,
8163
+ LoadingIndicatorComponent,
8164
+ LoadingIndicatorPlaceholderComponent], imports: [CommonModule], exports: [IconComponent,
8165
+ IconPlaceholderComponent,
8166
+ LoadingIndicatorComponent,
8167
+ LoadingIndicatorPlaceholderComponent] });
8168
+ IconModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: IconModule, imports: [CommonModule] });
8169
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: IconModule, decorators: [{
8170
+ type: NgModule,
8171
+ args: [{
8172
+ declarations: [
8173
+ IconComponent,
8174
+ IconPlaceholderComponent,
8175
+ LoadingIndicatorComponent,
8176
+ LoadingIndicatorPlaceholderComponent,
8177
+ ],
8178
+ imports: [CommonModule],
8179
+ exports: [
8180
+ IconComponent,
8181
+ IconPlaceholderComponent,
8182
+ LoadingIndicatorComponent,
8183
+ LoadingIndicatorPlaceholderComponent,
8184
+ ],
8185
+ }]
8186
+ }] });
8187
+
8188
+ class VoiceRecordingModule {
8189
+ }
8190
+ VoiceRecordingModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: VoiceRecordingModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
8191
+ VoiceRecordingModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "15.0.4", ngImport: i0, type: VoiceRecordingModule, declarations: [VoiceRecordingComponent, VoiceRecordingWavebarComponent], imports: [CommonModule, IconModule, TranslateModule], exports: [VoiceRecordingComponent, VoiceRecordingWavebarComponent] });
8192
+ VoiceRecordingModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: VoiceRecordingModule, imports: [CommonModule, IconModule, TranslateModule] });
8193
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: VoiceRecordingModule, decorators: [{
8194
+ type: NgModule,
8195
+ args: [{
8196
+ declarations: [VoiceRecordingComponent, VoiceRecordingWavebarComponent],
8197
+ imports: [CommonModule, IconModule, TranslateModule],
8198
+ exports: [VoiceRecordingComponent, VoiceRecordingWavebarComponent],
8199
+ }]
8200
+ }] });
8201
+
7509
8202
  class StreamChatModule {
7510
8203
  }
7511
8204
  StreamChatModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: StreamChatModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
@@ -7516,8 +8209,6 @@ StreamChatModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", versio
7516
8209
  MessageComponent,
7517
8210
  MessageInputComponent,
7518
8211
  MessageListComponent,
7519
- LoadingIndicatorComponent,
7520
- IconComponent,
7521
8212
  MessageActionsBoxComponent,
7522
8213
  AttachmentListComponent,
7523
8214
  MessageReactionsComponent,
@@ -7527,25 +8218,21 @@ StreamChatModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", versio
7527
8218
  ModalComponent,
7528
8219
  TextareaDirective,
7529
8220
  ThreadComponent,
7530
- IconPlaceholderComponent,
7531
- LoadingIndicatorPlaceholderComponent,
7532
8221
  MessageBouncePromptComponent,
7533
- VoiceRecordingComponent,
7534
- VoiceRecordingWavebarComponent,
7535
8222
  MessageReactionsSelectorComponent,
7536
8223
  UserListComponent,
7537
8224
  PaginatedListComponent], imports: [CommonModule,
7538
8225
  NgxFloatUiModule,
7539
8226
  StreamAvatarModule,
7540
- TranslateModule], exports: [ChannelComponent,
8227
+ TranslateModule,
8228
+ VoiceRecordingModule,
8229
+ IconModule], exports: [ChannelComponent,
7541
8230
  ChannelHeaderComponent,
7542
8231
  ChannelListComponent,
7543
8232
  ChannelPreviewComponent,
7544
8233
  MessageComponent,
7545
8234
  MessageInputComponent,
7546
8235
  MessageListComponent,
7547
- LoadingIndicatorComponent,
7548
- IconComponent,
7549
8236
  MessageActionsBoxComponent,
7550
8237
  AttachmentListComponent,
7551
8238
  MessageReactionsComponent,
@@ -7555,18 +8242,20 @@ StreamChatModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", versio
7555
8242
  ModalComponent,
7556
8243
  StreamAvatarModule,
7557
8244
  ThreadComponent,
7558
- IconPlaceholderComponent,
7559
- LoadingIndicatorPlaceholderComponent,
7560
8245
  MessageBouncePromptComponent,
7561
- VoiceRecordingComponent,
7562
- VoiceRecordingWavebarComponent,
8246
+ VoiceRecordingModule,
7563
8247
  MessageReactionsSelectorComponent,
7564
8248
  UserListComponent,
7565
- PaginatedListComponent] });
7566
- StreamChatModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: StreamChatModule, imports: [CommonModule,
8249
+ PaginatedListComponent,
8250
+ IconModule] });
8251
+ StreamChatModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: StreamChatModule, providers: [VoiceRecorderService], imports: [CommonModule,
7567
8252
  NgxFloatUiModule,
7568
8253
  StreamAvatarModule,
7569
- TranslateModule, StreamAvatarModule] });
8254
+ TranslateModule,
8255
+ VoiceRecordingModule,
8256
+ IconModule, StreamAvatarModule,
8257
+ VoiceRecordingModule,
8258
+ IconModule] });
7570
8259
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: StreamChatModule, decorators: [{
7571
8260
  type: NgModule,
7572
8261
  args: [{
@@ -7578,8 +8267,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImpor
7578
8267
  MessageComponent,
7579
8268
  MessageInputComponent,
7580
8269
  MessageListComponent,
7581
- LoadingIndicatorComponent,
7582
- IconComponent,
7583
8270
  MessageActionsBoxComponent,
7584
8271
  AttachmentListComponent,
7585
8272
  MessageReactionsComponent,
@@ -7589,11 +8276,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImpor
7589
8276
  ModalComponent,
7590
8277
  TextareaDirective,
7591
8278
  ThreadComponent,
7592
- IconPlaceholderComponent,
7593
- LoadingIndicatorPlaceholderComponent,
7594
8279
  MessageBouncePromptComponent,
7595
- VoiceRecordingComponent,
7596
- VoiceRecordingWavebarComponent,
7597
8280
  MessageReactionsSelectorComponent,
7598
8281
  UserListComponent,
7599
8282
  PaginatedListComponent,
@@ -7603,6 +8286,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImpor
7603
8286
  NgxFloatUiModule,
7604
8287
  StreamAvatarModule,
7605
8288
  TranslateModule,
8289
+ VoiceRecordingModule,
8290
+ IconModule,
7606
8291
  ],
7607
8292
  exports: [
7608
8293
  ChannelComponent,
@@ -7612,8 +8297,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImpor
7612
8297
  MessageComponent,
7613
8298
  MessageInputComponent,
7614
8299
  MessageListComponent,
7615
- LoadingIndicatorComponent,
7616
- IconComponent,
7617
8300
  MessageActionsBoxComponent,
7618
8301
  AttachmentListComponent,
7619
8302
  MessageReactionsComponent,
@@ -7623,15 +8306,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImpor
7623
8306
  ModalComponent,
7624
8307
  StreamAvatarModule,
7625
8308
  ThreadComponent,
7626
- IconPlaceholderComponent,
7627
- LoadingIndicatorPlaceholderComponent,
7628
8309
  MessageBouncePromptComponent,
7629
- VoiceRecordingComponent,
7630
- VoiceRecordingWavebarComponent,
8310
+ VoiceRecordingModule,
7631
8311
  MessageReactionsSelectorComponent,
7632
8312
  UserListComponent,
7633
8313
  PaginatedListComponent,
8314
+ IconModule,
7634
8315
  ],
8316
+ providers: [VoiceRecorderService],
7635
8317
  }]
7636
8318
  }] });
7637
8319
 
@@ -7685,6 +8367,190 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImpor
7685
8367
  }]
7686
8368
  }] });
7687
8369
 
8370
+ /**
8371
+ * The `VoiceRecorderWavebarComponent` displays the amplitudes of the recording while the recoding is in progress
8372
+ */
8373
+ class VoiceRecorderWavebarComponent {
8374
+ constructor(amplitudeRecorder, audioRecorder) {
8375
+ this.amplitudeRecorder = amplitudeRecorder;
8376
+ this.audioRecorder = audioRecorder;
8377
+ this.isLongerThanOneHour = false;
8378
+ this.amplitudes$ = this.amplitudeRecorder.amplitudes$;
8379
+ this.formattedDuration = formatDuration(this.audioRecorder.durationMs / 1000);
8380
+ this.durationComputeInterval = setInterval(() => {
8381
+ this.isLongerThanOneHour = this.audioRecorder.durationMs / 1000 > 3600;
8382
+ this.formattedDuration = formatDuration(this.audioRecorder.durationMs / 1000);
8383
+ }, 1000);
8384
+ }
8385
+ trackByIndex(i) {
8386
+ return i;
8387
+ }
8388
+ ngOnDestroy() {
8389
+ clearInterval(this.durationComputeInterval);
8390
+ }
8391
+ }
8392
+ VoiceRecorderWavebarComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: VoiceRecorderWavebarComponent, deps: [{ token: AmplitudeRecorderService }, { token: AudioRecorderService }], target: i0.ɵɵFactoryTarget.Component });
8393
+ VoiceRecorderWavebarComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.0.4", type: VoiceRecorderWavebarComponent, selector: "stream-voice-recorder-wavebar", ngImport: i0, template: "<div\n class=\"str-chat__recording-timer\"\n [class.str-chat__recording-timer--hours]=\"isLongerThanOneHour\"\n>\n {{ formattedDuration }}\n</div>\n<div class=\"str-chat__waveform-box-container\">\n <div class=\"str-chat__audio_recorder__waveform-box\">\n <div\n *ngFor=\"let amplitude of amplitudes$ | async; trackBy: trackByIndex\"\n class=\"str-chat__wave-progress-bar__amplitude-bar\"\n [style.--str-chat__wave-progress-bar__amplitude-bar-height]=\"\n (amplitude ?? 0) * 100 + '%'\n \"\n ></div>\n </div>\n</div>\n", dependencies: [{ kind: "directive", type: i4.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "pipe", type: i4.AsyncPipe, name: "async" }] });
8394
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: VoiceRecorderWavebarComponent, decorators: [{
8395
+ type: Component,
8396
+ args: [{ selector: 'stream-voice-recorder-wavebar', template: "<div\n class=\"str-chat__recording-timer\"\n [class.str-chat__recording-timer--hours]=\"isLongerThanOneHour\"\n>\n {{ formattedDuration }}\n</div>\n<div class=\"str-chat__waveform-box-container\">\n <div class=\"str-chat__audio_recorder__waveform-box\">\n <div\n *ngFor=\"let amplitude of amplitudes$ | async; trackBy: trackByIndex\"\n class=\"str-chat__wave-progress-bar__amplitude-bar\"\n [style.--str-chat__wave-progress-bar__amplitude-bar-height]=\"\n (amplitude ?? 0) * 100 + '%'\n \"\n ></div>\n </div>\n</div>\n" }]
8397
+ }], ctorParameters: function () { return [{ type: AmplitudeRecorderService }, { type: AudioRecorderService }]; } });
8398
+
8399
+ /**
8400
+ * The `VoiceRecorderComponent` makes it possible to record audio, and then upload it as a voice recording attachment
8401
+ */
8402
+ class VoiceRecorderComponent {
8403
+ constructor(recorder) {
8404
+ this.recorder = recorder;
8405
+ this.recordState = MediaRecordingState.STOPPED;
8406
+ this.isLoading = false;
8407
+ this.MediaRecordingState = MediaRecordingState;
8408
+ this.subscriptions = [];
8409
+ }
8410
+ ngOnInit() {
8411
+ this.subscriptions.push(this.recorder.recordingState$.subscribe((s) => {
8412
+ this.recordState = s;
8413
+ if (this.recordState === MediaRecordingState.ERROR) {
8414
+ this.voiceRecorderService?.isRecorderVisible$.next(false);
8415
+ }
8416
+ }));
8417
+ }
8418
+ ngOnChanges(changes) {
8419
+ if (changes.voiceRecorderService && this.voiceRecorderService) {
8420
+ this.isVisibleSubscription =
8421
+ this.voiceRecorderService.isRecorderVisible$.subscribe((isVisible) => {
8422
+ if (!isVisible) {
8423
+ this.recording = undefined;
8424
+ this.isLoading = false;
8425
+ }
8426
+ });
8427
+ }
8428
+ else {
8429
+ this.isVisibleSubscription?.unsubscribe();
8430
+ }
8431
+ }
8432
+ ngOnDestroy() {
8433
+ this.subscriptions.forEach((s) => s.unsubscribe());
8434
+ }
8435
+ cancel() {
8436
+ if (this.recording) {
8437
+ this.recording = undefined;
8438
+ }
8439
+ else {
8440
+ void this.recorder.stop({ cancel: true });
8441
+ }
8442
+ this.voiceRecorderService?.isRecorderVisible$.next(false);
8443
+ }
8444
+ async stop() {
8445
+ this.recording = await this.recorder.stop();
8446
+ }
8447
+ pause() {
8448
+ this.recorder.pause();
8449
+ }
8450
+ resume() {
8451
+ this.recorder.resume();
8452
+ }
8453
+ uploadRecording() {
8454
+ if (!this.recording) {
8455
+ return;
8456
+ }
8457
+ this.isLoading = true;
8458
+ this.voiceRecorderService?.recording$.next(this.recording);
8459
+ }
8460
+ }
8461
+ VoiceRecorderComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: VoiceRecorderComponent, deps: [{ token: AudioRecorderService }], target: i0.ɵɵFactoryTarget.Component });
8462
+ VoiceRecorderComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.0.4", type: VoiceRecorderComponent, selector: "stream-voice-recorder", inputs: { voiceRecorderService: "voiceRecorderService" }, providers: [], usesOnChanges: true, ngImport: i0, template: "<div\n class=\"str-chat__audio_recorder-container\"\n *ngIf=\"voiceRecorderService?.isRecorderVisible$ | async\"\n>\n <div class=\"str-chat__audio_recorder\" data-testid=\"audio-recorder\">\n <button\n class=\"str-chat__audio_recorder__cancel-button\"\n data-testid=\"cancel-recording-audio-button\"\n [disabled]=\"isLoading\"\n (click)=\"cancel()\"\n (keyup.enter)=\"cancel()\"\n >\n <stream-icon-placeholder icon=\"bin\"></stream-icon-placeholder>\n </button>\n <stream-voice-recorder-wavebar\n *ngIf=\"\n (recordState === MediaRecordingState.RECORDING ||\n recordState === MediaRecordingState.PAUSED) &&\n !recording\n \"\n ></stream-voice-recorder-wavebar>\n <!-- eslint-disable @angular-eslint/template/no-any -->\n <stream-voice-recording\n [attachment]=\"$any(recording)\"\n *ngIf=\"!!recording\"\n ></stream-voice-recording>\n <!-- eslint-enable @angular-eslint/template/no-any -->\n <button\n *ngIf=\"recordState === MediaRecordingState.PAUSED && !recording\"\n class=\"str-chat__audio_recorder__resume-recording-button\"\n (click)=\"resume()\"\n (keyup.enter)=\"resume()\"\n >\n <stream-icon-placeholder icon=\"mic\"></stream-icon-placeholder>\n </button>\n <button\n *ngIf=\"recordState === MediaRecordingState.RECORDING && !recording\"\n class=\"str-chat__audio_recorder__pause-recording-button\"\n data-testid=\"pause-recording-audio-button\"\n (click)=\"pause()\"\n (keyup.enter)=\"pause()\"\n >\n <stream-icon-placeholder icon=\"pause\"></stream-icon-placeholder>\n </button>\n <ng-container\n *ngIf=\"recordState === MediaRecordingState.STOPPED; else stopButton\"\n >\n <button\n class=\"str-chat__audio_recorder__complete-button\"\n data-testid=\"audio-recorder-complete-button\"\n [disabled]=\"!recording\"\n (click)=\"uploadRecording()\"\n (keyup.enter)=\"uploadRecording()\"\n >\n <stream-loading-indicator\n *ngIf=\"isLoading; else sendIcon\"\n ></stream-loading-indicator>\n <ng-template #sendIcon>\n <stream-icon-placeholder icon=\"send\"></stream-icon-placeholder>\n </ng-template>\n </button>\n </ng-container>\n <ng-template #stopButton>\n <button\n class=\"str-chat__audio_recorder__stop-button\"\n data-testid=\"audio-recorder-stop-button\"\n [disabled]=\"recordState === MediaRecordingState.STOPPED\"\n (click)=\"stop()\"\n (keyup.enter)=\"stop()\"\n >\n <stream-icon-placeholder icon=\"delivered\"></stream-icon-placeholder>\n </button>\n </ng-template>\n </div>\n</div>\n", dependencies: [{ kind: "directive", type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: VoiceRecordingComponent, selector: "stream-voice-recording", inputs: ["attachment"] }, { kind: "component", type: IconPlaceholderComponent, selector: "stream-icon-placeholder", inputs: ["icon"] }, { kind: "component", type: LoadingIndicatorComponent, selector: "stream-loading-indicator" }, { kind: "component", type: VoiceRecorderWavebarComponent, selector: "stream-voice-recorder-wavebar" }, { kind: "pipe", type: i4.AsyncPipe, name: "async" }] });
8463
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: VoiceRecorderComponent, decorators: [{
8464
+ type: Component,
8465
+ args: [{ selector: 'stream-voice-recorder', providers: [], template: "<div\n class=\"str-chat__audio_recorder-container\"\n *ngIf=\"voiceRecorderService?.isRecorderVisible$ | async\"\n>\n <div class=\"str-chat__audio_recorder\" data-testid=\"audio-recorder\">\n <button\n class=\"str-chat__audio_recorder__cancel-button\"\n data-testid=\"cancel-recording-audio-button\"\n [disabled]=\"isLoading\"\n (click)=\"cancel()\"\n (keyup.enter)=\"cancel()\"\n >\n <stream-icon-placeholder icon=\"bin\"></stream-icon-placeholder>\n </button>\n <stream-voice-recorder-wavebar\n *ngIf=\"\n (recordState === MediaRecordingState.RECORDING ||\n recordState === MediaRecordingState.PAUSED) &&\n !recording\n \"\n ></stream-voice-recorder-wavebar>\n <!-- eslint-disable @angular-eslint/template/no-any -->\n <stream-voice-recording\n [attachment]=\"$any(recording)\"\n *ngIf=\"!!recording\"\n ></stream-voice-recording>\n <!-- eslint-enable @angular-eslint/template/no-any -->\n <button\n *ngIf=\"recordState === MediaRecordingState.PAUSED && !recording\"\n class=\"str-chat__audio_recorder__resume-recording-button\"\n (click)=\"resume()\"\n (keyup.enter)=\"resume()\"\n >\n <stream-icon-placeholder icon=\"mic\"></stream-icon-placeholder>\n </button>\n <button\n *ngIf=\"recordState === MediaRecordingState.RECORDING && !recording\"\n class=\"str-chat__audio_recorder__pause-recording-button\"\n data-testid=\"pause-recording-audio-button\"\n (click)=\"pause()\"\n (keyup.enter)=\"pause()\"\n >\n <stream-icon-placeholder icon=\"pause\"></stream-icon-placeholder>\n </button>\n <ng-container\n *ngIf=\"recordState === MediaRecordingState.STOPPED; else stopButton\"\n >\n <button\n class=\"str-chat__audio_recorder__complete-button\"\n data-testid=\"audio-recorder-complete-button\"\n [disabled]=\"!recording\"\n (click)=\"uploadRecording()\"\n (keyup.enter)=\"uploadRecording()\"\n >\n <stream-loading-indicator\n *ngIf=\"isLoading; else sendIcon\"\n ></stream-loading-indicator>\n <ng-template #sendIcon>\n <stream-icon-placeholder icon=\"send\"></stream-icon-placeholder>\n </ng-template>\n </button>\n </ng-container>\n <ng-template #stopButton>\n <button\n class=\"str-chat__audio_recorder__stop-button\"\n data-testid=\"audio-recorder-stop-button\"\n [disabled]=\"recordState === MediaRecordingState.STOPPED\"\n (click)=\"stop()\"\n (keyup.enter)=\"stop()\"\n >\n <stream-icon-placeholder icon=\"delivered\"></stream-icon-placeholder>\n </button>\n </ng-template>\n </div>\n</div>\n" }]
8466
+ }], ctorParameters: function () { return [{ type: AudioRecorderService }]; }, propDecorators: { voiceRecorderService: [{
8467
+ type: Input
8468
+ }] } });
8469
+
8470
+ class VoiceRecorderModule {
8471
+ }
8472
+ VoiceRecorderModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: VoiceRecorderModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
8473
+ VoiceRecorderModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "15.0.4", ngImport: i0, type: VoiceRecorderModule, declarations: [VoiceRecorderComponent, VoiceRecorderWavebarComponent], imports: [CommonModule, VoiceRecordingModule, IconModule, TranslateModule], exports: [VoiceRecorderComponent, VoiceRecorderWavebarComponent] });
8474
+ VoiceRecorderModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: VoiceRecorderModule, providers: [
8475
+ AudioRecorderService,
8476
+ TranscoderService,
8477
+ AmplitudeRecorderService,
8478
+ ], imports: [CommonModule, VoiceRecordingModule, IconModule, TranslateModule] });
8479
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: VoiceRecorderModule, decorators: [{
8480
+ type: NgModule,
8481
+ args: [{
8482
+ declarations: [VoiceRecorderComponent, VoiceRecorderWavebarComponent],
8483
+ imports: [CommonModule, VoiceRecordingModule, IconModule, TranslateModule],
8484
+ exports: [VoiceRecorderComponent, VoiceRecorderWavebarComponent],
8485
+ providers: [
8486
+ AudioRecorderService,
8487
+ TranscoderService,
8488
+ AmplitudeRecorderService,
8489
+ ],
8490
+ }]
8491
+ }] });
8492
+
8493
+ const ENCODING_BIT_RATE = 128; // kbps;
8494
+ const COUNT_SAMPLES_PER_ENCODED_BLOCK = 1152;
8495
+ const SAMPLE_RATE = 16000;
8496
+ const readFileAsArrayBuffer = (blob) => new Promise((resolve, reject) => {
8497
+ const blobReader = new FileReader();
8498
+ blobReader.onload = () => {
8499
+ resolve(blobReader.result);
8500
+ };
8501
+ blobReader.onerror = () => {
8502
+ reject(blobReader.error);
8503
+ };
8504
+ blobReader.readAsArrayBuffer(blob);
8505
+ });
8506
+ const toAudioBuffer = async (blob) => {
8507
+ const audioCtx = new AudioContext();
8508
+ const arrayBuffer = await readFileAsArrayBuffer(blob);
8509
+ const decodedData = await audioCtx.decodeAudioData(arrayBuffer);
8510
+ if (audioCtx.state !== 'closed')
8511
+ await audioCtx.close();
8512
+ return decodedData;
8513
+ };
8514
+ const renderAudio = async (audioBuffer, sampleRate) => {
8515
+ const offlineAudioCtx = new OfflineAudioContext(audioBuffer.numberOfChannels, audioBuffer.duration * sampleRate, sampleRate);
8516
+ const source = offlineAudioCtx.createBufferSource();
8517
+ source.buffer = audioBuffer;
8518
+ source.connect(offlineAudioCtx.destination);
8519
+ source.start();
8520
+ return await offlineAudioCtx.startRendering();
8521
+ };
8522
+ const float32ArrayToInt16Array = (float32Arr) => {
8523
+ const int16Arr = new Int16Array(float32Arr.length);
8524
+ for (let i = 0; i < float32Arr.length; i++) {
8525
+ const float32Value = float32Arr[i];
8526
+ // Clamp the float value between -1 and 1
8527
+ const clampedValue = Math.max(-1, Math.min(1, float32Value));
8528
+ // Convert the float value to a signed 16-bit integer
8529
+ int16Arr[i] = Math.round(clampedValue * 32767);
8530
+ }
8531
+ return int16Arr;
8532
+ };
8533
+ const splitDataByChannel = (audioBuffer) => Array.from({ length: audioBuffer.numberOfChannels }, (_, i) => audioBuffer.getChannelData(i)).map(float32ArrayToInt16Array);
8534
+ /* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument */
8535
+ async function encodeWebmToMp3(blob, lameJs) {
8536
+ const audioBuffer = await renderAudio(await toAudioBuffer(blob), SAMPLE_RATE);
8537
+ const channelCount = audioBuffer.numberOfChannels;
8538
+ const dataByChannel = splitDataByChannel(audioBuffer);
8539
+ const mp3Encoder = new lameJs.Mp3Encoder(channelCount, SAMPLE_RATE, ENCODING_BIT_RATE);
8540
+ const dataBuffer = [];
8541
+ let remaining = dataByChannel[0].length;
8542
+ for (let i = 0; remaining >= COUNT_SAMPLES_PER_ENCODED_BLOCK; i += COUNT_SAMPLES_PER_ENCODED_BLOCK) {
8543
+ const [leftChannelBlock, rightChannelBlock] = dataByChannel.map((channel) => channel.subarray(i, i + COUNT_SAMPLES_PER_ENCODED_BLOCK));
8544
+ dataBuffer.push(new Int8Array(mp3Encoder.encodeBuffer(leftChannelBlock, rightChannelBlock)));
8545
+ remaining -= COUNT_SAMPLES_PER_ENCODED_BLOCK;
8546
+ }
8547
+ const lastBlock = mp3Encoder.flush();
8548
+ if (lastBlock.length)
8549
+ dataBuffer.push(new Int8Array(lastBlock));
8550
+ return new Blob(dataBuffer, { type: 'audio/mp3;sbu_type=voice' });
8551
+ }
8552
+ /* eslint-enable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument */
8553
+
7688
8554
  /*
7689
8555
  * Public API Surface of stream-chat-angular
7690
8556
  */
@@ -7693,5 +8559,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImpor
7693
8559
  * Generated bundle index. Do not edit.
7694
8560
  */
7695
8561
 
7696
- export { AttachmentConfigurationService, AttachmentListComponent, AttachmentPreviewListComponent, AttachmentService, AutocompleteTextareaComponent, AvatarComponent, AvatarPlaceholderComponent, ChannelComponent, ChannelHeaderComponent, ChannelListComponent, ChannelPreviewComponent, ChannelQuery, ChannelService, ChatClientService, CustomTemplatesService, DateParserService, EmojiInputService, IconComponent, IconPlaceholderComponent, LoadingIndicatorComponent, LoadingIndicatorPlaceholderComponent, MessageActionsBoxComponent, MessageActionsService, MessageBouncePromptComponent, MessageComponent, MessageInputComponent, MessageInputConfigService, MessageListComponent, MessageReactionsComponent, MessageReactionsSelectorComponent, MessageReactionsService, MessageService, ModalComponent, NotificationComponent, NotificationListComponent, NotificationService, PaginatedListComponent, StreamAutocompleteTextareaModule, StreamAvatarModule, StreamChatModule, StreamI18nService, StreamTextareaModule, TextareaComponent, TextareaDirective, ThemeService, ThreadComponent, TransliterationService, UserListComponent, VirtualizedListService, VirtualizedMessageListService, VoiceRecordingComponent, VoiceRecordingWavebarComponent, createMessagePreview, getChannelDisplayText, getGroupStyles, getMessageTranslation, getReadBy, isImageAttachment, isImageFile, isOnSeparateDate, listUsers, parseDate, textareaInjectionToken };
8562
+ export { AmplitudeRecorderService, AttachmentConfigurationService, AttachmentListComponent, AttachmentPreviewListComponent, AttachmentService, AudioRecorderService, AutocompleteTextareaComponent, AvatarComponent, AvatarPlaceholderComponent, ChannelComponent, ChannelHeaderComponent, ChannelListComponent, ChannelPreviewComponent, ChannelQuery, ChannelService, ChatClientService, CustomTemplatesService, DEFAULT_AMPLITUDE_RECORDER_CONFIG, DateParserService, EmojiInputService, IconComponent, IconModule, IconPlaceholderComponent, LoadingIndicatorComponent, LoadingIndicatorPlaceholderComponent, MediaRecordingState, MessageActionsBoxComponent, MessageActionsService, MessageBouncePromptComponent, MessageComponent, MessageInputComponent, MessageInputConfigService, MessageListComponent, MessageReactionsComponent, MessageReactionsSelectorComponent, MessageReactionsService, MessageService, ModalComponent, MultimediaRecorder, NotificationComponent, NotificationListComponent, NotificationService, PaginatedListComponent, StreamAutocompleteTextareaModule, StreamAvatarModule, StreamChatModule, StreamI18nService, StreamTextareaModule, TextareaComponent, TextareaDirective, ThemeService, ThreadComponent, TranscoderService, TransliterationService, UserListComponent, VirtualizedListService, VirtualizedMessageListService, VoiceRecorderComponent, VoiceRecorderModule, VoiceRecorderService, VoiceRecorderWavebarComponent, VoiceRecordingComponent, VoiceRecordingModule, VoiceRecordingWavebarComponent, createFileFromBlobs, createMessagePreview, createUriFromBlob, encodeWebmToMp3, formatDuration, getChannelDisplayText, getExtensionFromMimeType, getGroupStyles, getMessageTranslation, getReadBy, isImageAttachment, isImageFile, isOnSeparateDate, isSafari, listUsers, parseDate, readBlobAsArrayBuffer, textareaInjectionToken };
7697
8563
  //# sourceMappingURL=stream-chat-angular.mjs.map