stream-chat-angular 5.3.1 → 5.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/i18n/en.d.ts +4 -0
- package/assets/version.d.ts +1 -1
- package/esm2020/assets/i18n/en.mjs +5 -1
- package/esm2020/assets/version.mjs +2 -2
- package/esm2020/lib/attachment-list/attachment-list.component.mjs +4 -4
- package/esm2020/lib/attachment-preview-list/attachment-preview-list.component.mjs +5 -5
- package/esm2020/lib/attachment.service.mjs +44 -10
- package/esm2020/lib/channel-list/channel-list.component.mjs +5 -5
- package/esm2020/lib/channel-preview/channel-preview.component.mjs +1 -1
- package/esm2020/lib/file-utils.mjs +35 -0
- package/esm2020/lib/format-duration.mjs +16 -0
- package/esm2020/lib/icon/icon-placeholder/icon-placeholder.component.mjs +28 -0
- package/esm2020/lib/icon/icon.component.mjs +1 -1
- package/esm2020/lib/icon/icon.module.mjs +37 -0
- package/esm2020/lib/{loading-indicator → icon/loading-indicator}/loading-indicator.component.mjs +1 -1
- package/esm2020/lib/{loading-indicator-placeholder → icon/loading-indicator-placeholder}/loading-indicator-placeholder.component.mjs +2 -2
- package/esm2020/lib/is-safari.mjs +2 -0
- package/esm2020/lib/message/message.component.mjs +6 -6
- package/esm2020/lib/message-input/message-input-config.service.mjs +6 -1
- package/esm2020/lib/message-input/message-input.component.mjs +57 -14
- package/esm2020/lib/message-input/voice-recorder.service.mjs +27 -0
- package/esm2020/lib/message-list/message-list.component.mjs +9 -9
- package/esm2020/lib/modal/modal.component.mjs +1 -1
- package/esm2020/lib/paginated-list/paginated-list.component.mjs +1 -1
- package/esm2020/lib/stream-chat.module.mjs +21 -35
- package/esm2020/lib/thread/thread.component.mjs +1 -1
- package/esm2020/lib/types.mjs +1 -1
- package/esm2020/lib/voice-recorder/amplitude-recorder.service.mjs +119 -0
- package/esm2020/lib/voice-recorder/audio-recorder.service.mjs +79 -0
- package/esm2020/lib/voice-recorder/media-recorder.mjs +190 -0
- package/esm2020/lib/voice-recorder/mp3-transcoder.mjs +61 -0
- package/esm2020/lib/voice-recorder/transcoder.service.mjs +121 -0
- package/esm2020/lib/voice-recorder/voice-recorder-wavebar/voice-recorder-wavebar.component.mjs +35 -0
- package/esm2020/lib/voice-recorder/voice-recorder.component.mjs +80 -0
- package/esm2020/lib/voice-recorder/voice-recorder.module.mjs +34 -0
- package/esm2020/lib/voice-recording/voice-recording-wavebar/voice-recording-wavebar.component.mjs +4 -75
- package/esm2020/lib/voice-recording/voice-recording.component.mjs +4 -15
- package/esm2020/lib/voice-recording/voice-recording.module.mjs +21 -0
- package/esm2020/lib/wave-form-sampler.mjs +72 -0
- package/esm2020/public-api.mjs +18 -5
- package/fesm2015/stream-chat-angular.mjs +1040 -146
- package/fesm2015/stream-chat-angular.mjs.map +1 -1
- package/fesm2020/stream-chat-angular.mjs +991 -141
- package/fesm2020/stream-chat-angular.mjs.map +1 -1
- package/lib/attachment.service.d.ts +7 -1
- package/lib/file-utils.d.ts +9 -0
- package/lib/format-duration.d.ts +1 -0
- package/lib/{icon-placeholder → icon/icon-placeholder}/icon-placeholder.component.d.ts +3 -3
- package/lib/icon/icon.component.d.ts +1 -1
- package/lib/icon/icon.module.d.ts +11 -0
- package/lib/{loading-indicator-placeholder → icon/loading-indicator-placeholder}/loading-indicator-placeholder.component.d.ts +1 -1
- package/lib/is-safari.d.ts +1 -0
- package/lib/message-input/message-input-config.service.d.ts +5 -0
- package/lib/message-input/message-input.component.d.ts +19 -5
- package/lib/message-input/voice-recorder.service.d.ts +19 -0
- package/lib/message-list/message-list.component.d.ts +0 -1
- package/lib/stream-chat.module.d.ts +20 -24
- package/lib/types.d.ts +11 -1
- package/lib/voice-recorder/amplitude-recorder.service.d.ts +71 -0
- package/lib/voice-recorder/audio-recorder.service.d.ts +46 -0
- package/lib/voice-recorder/media-recorder.d.ts +46 -0
- package/lib/voice-recorder/mp3-transcoder.d.ts +1 -0
- package/lib/voice-recorder/transcoder.service.d.ts +40 -0
- package/lib/voice-recorder/voice-recorder-wavebar/voice-recorder-wavebar.component.d.ts +21 -0
- package/lib/voice-recorder/voice-recorder.component.d.ts +30 -0
- package/lib/voice-recorder/voice-recorder.module.d.ts +12 -0
- package/lib/voice-recording/voice-recording-wavebar/voice-recording-wavebar.component.d.ts +0 -7
- package/lib/voice-recording/voice-recording.component.d.ts +0 -1
- package/lib/voice-recording/voice-recording.module.d.ts +11 -0
- package/lib/wave-form-sampler.d.ts +1 -0
- package/package.json +8 -1
- package/public-api.d.ts +17 -4
- package/src/assets/assets/icons/stream-chat-icons.eot +0 -0
- package/src/assets/assets/icons/stream-chat-icons.svg +4 -0
- package/src/assets/assets/icons/stream-chat-icons.ttf +0 -0
- package/src/assets/assets/icons/stream-chat-icons.woff +0 -0
- package/src/assets/assets/icons/stream-chat-icons.woff2 +0 -0
- package/src/assets/i18n/en.ts +6 -0
- package/src/assets/styles/css/index.css +1 -1
- package/src/assets/styles/css/index.layout.css +1 -1
- package/src/assets/styles/scss/AudioRecorder/AudioRecorder-layout.scss +64 -14
- package/src/assets/styles/scss/AudioRecorder/AudioRecorder-theme.scss +11 -1
- package/src/assets/styles/scss/ChannelList/ChannelList-layout.scss +4 -0
- package/src/assets/styles/scss/Icon/Icon-layout.scss +6 -1
- package/src/assets/styles/scss/MessageInput/MessageInput-layout.scss +1 -0
- package/src/assets/styles/scss/MessageInput/MessageInput-theme.scss +1 -0
- package/src/assets/version.ts +1 -1
- package/esm2020/lib/icon-placeholder/icon-placeholder.component.mjs +0 -28
- package/esm2020/lib/is-image-file.mjs +0 -5
- package/lib/is-image-file.d.ts +0 -1
- /package/lib/{loading-indicator → icon/loading-indicator}/loading-indicator.component.d.ts +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { __awaiter } from 'tslib';
|
|
2
2
|
import * as i0 from '@angular/core';
|
|
3
|
-
import { Injectable, Component, Input, EventEmitter, Output, ViewChild, HostBinding, TemplateRef, ContentChild, ChangeDetectionStrategy, InjectionToken, Directive, Inject,
|
|
3
|
+
import { Injectable, Component, Input, EventEmitter, Output, ViewChild, HostBinding, TemplateRef, ContentChild, ChangeDetectionStrategy, InjectionToken, Directive, NgModule, Inject, Optional } from '@angular/core';
|
|
4
4
|
import { BehaviorSubject, ReplaySubject, combineLatest, take as take$1, Subject, timer, merge, switchMap, distinctUntilChanged, pairwise, filter as filter$1, of, map as map$1 } from 'rxjs';
|
|
5
5
|
import { StreamChat } from 'stream-chat';
|
|
6
6
|
import { take, shareReplay, map, first, filter, tap, debounceTime, throttleTime } from 'rxjs/operators';
|
|
@@ -16,11 +16,13 @@ import emojiRegex from 'emoji-regex';
|
|
|
16
16
|
import * as i8 from 'ngx-float-ui';
|
|
17
17
|
import { NgxFloatUiModule } from 'ngx-float-ui';
|
|
18
18
|
import prettybytes from 'pretty-bytes';
|
|
19
|
+
import fixWebmDuration from 'fix-webm-duration';
|
|
20
|
+
import { NgModel } from '@angular/forms';
|
|
19
21
|
import transliterate from '@stream-io/transliterate';
|
|
20
22
|
import * as i8$1 from 'angular-mentions';
|
|
21
23
|
import { MentionModule } from 'angular-mentions';
|
|
22
24
|
|
|
23
|
-
const version = '5.
|
|
25
|
+
const version = '5.4.0';
|
|
24
26
|
|
|
25
27
|
/**
|
|
26
28
|
* The `NotificationService` can be used to add or remove notifications. By default the [`NotificationList`](../components/NotificationListComponent.mdx) component displays the currently active notifications.
|
|
@@ -2000,6 +2002,37 @@ const isImageFile = (file) => {
|
|
|
2000
2002
|
// photoshop files begin with 'image/'
|
|
2001
2003
|
return file.type.startsWith('image/') && !file.type.endsWith('.photoshop');
|
|
2002
2004
|
};
|
|
2005
|
+
const readBlobAsArrayBuffer = (blob) => new Promise((resolve, reject) => {
|
|
2006
|
+
const fileReader = new FileReader();
|
|
2007
|
+
fileReader.onload = () => {
|
|
2008
|
+
resolve(fileReader.result);
|
|
2009
|
+
};
|
|
2010
|
+
fileReader.onerror = () => {
|
|
2011
|
+
reject(fileReader.error);
|
|
2012
|
+
};
|
|
2013
|
+
fileReader.readAsArrayBuffer(blob);
|
|
2014
|
+
});
|
|
2015
|
+
const createFileFromBlobs = ({ blobsArray, fileName, mimeType, }) => {
|
|
2016
|
+
const concatenatedBlob = new Blob(blobsArray, { type: mimeType });
|
|
2017
|
+
return new File([concatenatedBlob], fileName, {
|
|
2018
|
+
type: concatenatedBlob.type,
|
|
2019
|
+
});
|
|
2020
|
+
};
|
|
2021
|
+
const getExtensionFromMimeType = (mimeType) => {
|
|
2022
|
+
const match = mimeType.match(/\/([^/;]+)/);
|
|
2023
|
+
return match && match[1];
|
|
2024
|
+
};
|
|
2025
|
+
const createUriFromBlob = (blob) => {
|
|
2026
|
+
return new Promise((resolve, reject) => {
|
|
2027
|
+
const reader = new FileReader();
|
|
2028
|
+
reader.onload = (event) => {
|
|
2029
|
+
var _a, _b;
|
|
2030
|
+
resolve((_b = (_a = event.target) === null || _a === void 0 ? void 0 : _a.result) !== null && _b !== void 0 ? _b : undefined);
|
|
2031
|
+
};
|
|
2032
|
+
reader.onerror = (e) => reject(e);
|
|
2033
|
+
reader.readAsDataURL(blob);
|
|
2034
|
+
});
|
|
2035
|
+
};
|
|
2003
2036
|
|
|
2004
2037
|
const isImageAttachment = (attachment) => {
|
|
2005
2038
|
return (attachment.type === 'image' &&
|
|
@@ -2030,6 +2063,37 @@ class AttachmentService {
|
|
|
2030
2063
|
resetAttachmentUploads() {
|
|
2031
2064
|
this.attachmentUploadsSubject.next([]);
|
|
2032
2065
|
}
|
|
2066
|
+
/**
|
|
2067
|
+
* Upload a voice recording
|
|
2068
|
+
* @param audioRecording
|
|
2069
|
+
* @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`.
|
|
2070
|
+
*/
|
|
2071
|
+
uploadVoiceRecording(audioRecording) {
|
|
2072
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
2073
|
+
if (!(yield this.areAttachmentsHaveValidExtension([audioRecording.recording]))) {
|
|
2074
|
+
return false;
|
|
2075
|
+
}
|
|
2076
|
+
if (!(yield this.areAttachmentsHaveValidSize([audioRecording.recording]))) {
|
|
2077
|
+
return false;
|
|
2078
|
+
}
|
|
2079
|
+
const upload = {
|
|
2080
|
+
file: audioRecording.recording,
|
|
2081
|
+
previewUri: audioRecording.asset_url,
|
|
2082
|
+
extraData: {
|
|
2083
|
+
duration: audioRecording.duration,
|
|
2084
|
+
waveform_data: audioRecording.waveform_data,
|
|
2085
|
+
},
|
|
2086
|
+
state: 'uploading',
|
|
2087
|
+
type: 'voiceRecording',
|
|
2088
|
+
};
|
|
2089
|
+
this.attachmentUploadsSubject.next([
|
|
2090
|
+
...this.attachmentUploadsSubject.getValue(),
|
|
2091
|
+
upload,
|
|
2092
|
+
]);
|
|
2093
|
+
yield this.uploadAttachments([upload]);
|
|
2094
|
+
return true;
|
|
2095
|
+
});
|
|
2096
|
+
}
|
|
2033
2097
|
/**
|
|
2034
2098
|
* Uploads the selected files, and creates preview for image files. The result is propagated throught the `attachmentUploads$` stream.
|
|
2035
2099
|
* @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
|
|
@@ -2061,7 +2125,7 @@ class AttachmentService {
|
|
|
2061
2125
|
dataFiles.push(file);
|
|
2062
2126
|
}
|
|
2063
2127
|
});
|
|
2064
|
-
imageFiles.forEach((f) => this.createPreview(f));
|
|
2128
|
+
imageFiles.forEach((f) => void this.createPreview(f));
|
|
2065
2129
|
const newUploads = [
|
|
2066
2130
|
...imageFiles.map((file) => ({
|
|
2067
2131
|
file,
|
|
@@ -2154,7 +2218,7 @@ class AttachmentService {
|
|
|
2154
2218
|
.filter((r) => r.state === 'success')
|
|
2155
2219
|
.map((r) => {
|
|
2156
2220
|
var _a, _b, _c, _d;
|
|
2157
|
-
|
|
2221
|
+
let attachment = {
|
|
2158
2222
|
type: r.type,
|
|
2159
2223
|
};
|
|
2160
2224
|
if (r.fromAttachment) {
|
|
@@ -2172,6 +2236,9 @@ class AttachmentService {
|
|
|
2172
2236
|
attachment.file_size = (_d = r.file) === null || _d === void 0 ? void 0 : _d.size;
|
|
2173
2237
|
attachment.thumb_url = r.thumb_url;
|
|
2174
2238
|
}
|
|
2239
|
+
if (r.extraData) {
|
|
2240
|
+
attachment = Object.assign(Object.assign({}, attachment), r.extraData);
|
|
2241
|
+
}
|
|
2175
2242
|
}
|
|
2176
2243
|
return attachment;
|
|
2177
2244
|
});
|
|
@@ -2220,18 +2287,22 @@ class AttachmentService {
|
|
|
2220
2287
|
}
|
|
2221
2288
|
}
|
|
2222
2289
|
createPreview(file) {
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2290
|
+
var _a, _b;
|
|
2291
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
2292
|
+
try {
|
|
2293
|
+
const uri = yield createUriFromBlob(file);
|
|
2294
|
+
const attachmentUploads = this.attachmentUploadsSubject.getValue();
|
|
2295
|
+
const upload = attachmentUploads.find((upload) => upload.file === file);
|
|
2296
|
+
if (!upload) {
|
|
2297
|
+
return;
|
|
2298
|
+
}
|
|
2299
|
+
upload.previewUri = uri;
|
|
2300
|
+
this.attachmentUploadsSubject.next([...attachmentUploads]);
|
|
2230
2301
|
}
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2302
|
+
catch (e) {
|
|
2303
|
+
(_b = (_a = this.chatClientService) === null || _a === void 0 ? void 0 : _a.chatClient) === null || _b === void 0 ? void 0 : _b.logger('error', e instanceof Error ? e.message : `Can't create image preview`, { error: e, tag: ['AttachmentService'] });
|
|
2304
|
+
}
|
|
2305
|
+
});
|
|
2235
2306
|
}
|
|
2236
2307
|
uploadAttachments(uploads) {
|
|
2237
2308
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -2661,6 +2732,10 @@ const en = {
|
|
|
2661
2732
|
Edited: 'Edited',
|
|
2662
2733
|
'Error playing audio': 'Error playing audio',
|
|
2663
2734
|
'Copy text': 'Copy text',
|
|
2735
|
+
'Please grant permission to use microhpone': 'Please grant permission to use microhpone',
|
|
2736
|
+
'Error starting recording': 'Error starting recording',
|
|
2737
|
+
'An error has occurred during recording': 'An error has occurred during recording',
|
|
2738
|
+
'Media recording not supported': 'Media recording not supported',
|
|
2664
2739
|
},
|
|
2665
2740
|
};
|
|
2666
2741
|
|
|
@@ -4297,12 +4372,100 @@ class ChannelListComponent {
|
|
|
4297
4372
|
}
|
|
4298
4373
|
}
|
|
4299
4374
|
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 });
|
|
4300
|
-
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 >\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:
|
|
4375
|
+
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" }] });
|
|
4301
4376
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: ChannelListComponent, decorators: [{
|
|
4302
4377
|
type: Component,
|
|
4303
|
-
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 >\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" }]
|
|
4378
|
+
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" }]
|
|
4304
4379
|
}], ctorParameters: function () { return [{ type: ChannelService }, { type: CustomTemplatesService }, { type: ThemeService }]; } });
|
|
4305
4380
|
|
|
4381
|
+
const formatDuration = (durationInSeconds) => {
|
|
4382
|
+
if (durationInSeconds === undefined || durationInSeconds <= 0)
|
|
4383
|
+
return '00:00';
|
|
4384
|
+
const [hours, hoursLeftover] = divMod$1(durationInSeconds, 3600);
|
|
4385
|
+
const [minutes, seconds] = divMod$1(hoursLeftover, 60);
|
|
4386
|
+
const roundedSeconds = Math.ceil(seconds);
|
|
4387
|
+
const prependHrsZero = hours.toString().length === 1 ? '0' : '';
|
|
4388
|
+
const prependMinZero = minutes.toString().length === 1 ? '0' : '';
|
|
4389
|
+
const prependSecZero = roundedSeconds.toString().length === 1 ? '0' : '';
|
|
4390
|
+
const minSec = `${prependMinZero}${minutes}:${prependSecZero}${roundedSeconds}`;
|
|
4391
|
+
return hours ? `${prependHrsZero}${hours}:` + minSec : minSec;
|
|
4392
|
+
};
|
|
4393
|
+
const divMod$1 = (num, divisor) => {
|
|
4394
|
+
return [Math.floor(num / divisor), num % divisor];
|
|
4395
|
+
};
|
|
4396
|
+
|
|
4397
|
+
const resampleWaveForm = (waveFormData, sampleSize) => {
|
|
4398
|
+
return waveFormData.length > sampleSize
|
|
4399
|
+
? downsample(waveFormData, sampleSize)
|
|
4400
|
+
: upsample(waveFormData, sampleSize);
|
|
4401
|
+
};
|
|
4402
|
+
const downsample = (waveFormData, sampleSize) => {
|
|
4403
|
+
if (waveFormData.length <= sampleSize) {
|
|
4404
|
+
return waveFormData;
|
|
4405
|
+
}
|
|
4406
|
+
if (sampleSize === 1)
|
|
4407
|
+
return [mean(waveFormData)];
|
|
4408
|
+
const result = [];
|
|
4409
|
+
// bucket size adjusted due to the fact that the first and the last item in the original data array is kept in target output
|
|
4410
|
+
const bucketSize = (waveFormData.length - 2) / (sampleSize - 2);
|
|
4411
|
+
let lastSelectedPointIndex = 0;
|
|
4412
|
+
result.push(waveFormData[lastSelectedPointIndex]); // Always add the first point
|
|
4413
|
+
let maxAreaPoint, maxArea, triangleArea;
|
|
4414
|
+
for (let bucketIndex = 1; bucketIndex < sampleSize - 1; bucketIndex++) {
|
|
4415
|
+
const previousBucketRefPoint = waveFormData[lastSelectedPointIndex];
|
|
4416
|
+
const nextBucketMean = getNextBucketMean(waveFormData, bucketIndex, bucketSize);
|
|
4417
|
+
const currentBucketStartIndex = Math.floor((bucketIndex - 1) * bucketSize) + 1;
|
|
4418
|
+
const nextBucketStartIndex = Math.floor(bucketIndex * bucketSize) + 1;
|
|
4419
|
+
const countUnitsBetweenAtoC = 1 + nextBucketStartIndex - currentBucketStartIndex;
|
|
4420
|
+
maxArea = triangleArea = -1;
|
|
4421
|
+
for (let currentPointIndex = currentBucketStartIndex; currentPointIndex < nextBucketStartIndex; currentPointIndex++) {
|
|
4422
|
+
const countUnitsBetweenAtoB = Math.abs(currentPointIndex - currentBucketStartIndex) + 1;
|
|
4423
|
+
const countUnitsBetweenBtoC = countUnitsBetweenAtoC - countUnitsBetweenAtoB;
|
|
4424
|
+
const currentPointValue = waveFormData[currentPointIndex];
|
|
4425
|
+
triangleArea = triangleAreaHeron(triangleBase(Math.abs(previousBucketRefPoint - currentPointValue), countUnitsBetweenAtoB), triangleBase(Math.abs(currentPointValue - nextBucketMean), countUnitsBetweenBtoC), triangleBase(Math.abs(previousBucketRefPoint - nextBucketMean), countUnitsBetweenAtoC));
|
|
4426
|
+
if (triangleArea > maxArea) {
|
|
4427
|
+
maxArea = triangleArea;
|
|
4428
|
+
maxAreaPoint = waveFormData[currentPointIndex];
|
|
4429
|
+
lastSelectedPointIndex = currentPointIndex;
|
|
4430
|
+
}
|
|
4431
|
+
}
|
|
4432
|
+
if (typeof maxAreaPoint !== 'undefined')
|
|
4433
|
+
result.push(maxAreaPoint);
|
|
4434
|
+
}
|
|
4435
|
+
result.push(waveFormData[waveFormData.length - 1]); // Always add the last point
|
|
4436
|
+
return result;
|
|
4437
|
+
};
|
|
4438
|
+
const upsample = (waveFormData, sampleSize) => {
|
|
4439
|
+
if (sampleSize === waveFormData.length)
|
|
4440
|
+
return waveFormData;
|
|
4441
|
+
// eslint-disable-next-line prefer-const
|
|
4442
|
+
let [bucketSize, remainder] = divMod(sampleSize, waveFormData.length);
|
|
4443
|
+
const result = [];
|
|
4444
|
+
for (let i = 0; i < waveFormData.length; i++) {
|
|
4445
|
+
const extra = remainder && remainder-- ? 1 : 0;
|
|
4446
|
+
result.push(...Array(bucketSize + extra).fill(waveFormData[i]));
|
|
4447
|
+
}
|
|
4448
|
+
return result;
|
|
4449
|
+
};
|
|
4450
|
+
const getNextBucketMean = (data, currentBucketIndex, bucketSize) => {
|
|
4451
|
+
const nextBucketStartIndex = Math.floor(currentBucketIndex * bucketSize) + 1;
|
|
4452
|
+
let nextNextBucketStartIndex = Math.floor((currentBucketIndex + 1) * bucketSize) + 1;
|
|
4453
|
+
nextNextBucketStartIndex =
|
|
4454
|
+
nextNextBucketStartIndex < data.length
|
|
4455
|
+
? nextNextBucketStartIndex
|
|
4456
|
+
: data.length;
|
|
4457
|
+
return mean(data.slice(nextBucketStartIndex, nextNextBucketStartIndex));
|
|
4458
|
+
};
|
|
4459
|
+
const mean = (values) => values.reduce((acc, value) => acc + value, 0) / values.length;
|
|
4460
|
+
const triangleAreaHeron = (a, b, c) => {
|
|
4461
|
+
const s = (a + b + c) / 2;
|
|
4462
|
+
return Math.sqrt(s * (s - a) * (s - b) * (s - c));
|
|
4463
|
+
};
|
|
4464
|
+
const triangleBase = (a, b) => Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2));
|
|
4465
|
+
const divMod = (num, divisor) => {
|
|
4466
|
+
return [Math.floor(num / divisor), num % divisor];
|
|
4467
|
+
};
|
|
4468
|
+
|
|
4306
4469
|
/**
|
|
4307
4470
|
* This component can be used to visualize the wave bar of a voice recording
|
|
4308
4471
|
*/
|
|
@@ -4319,36 +4482,6 @@ class VoiceRecordingWavebarComponent {
|
|
|
4319
4482
|
this.isDragging = false;
|
|
4320
4483
|
this.sampleSize = 40;
|
|
4321
4484
|
this.isViewInited = false;
|
|
4322
|
-
this.upsample = () => {
|
|
4323
|
-
if (this.sampleSize === this.waveFormData.length)
|
|
4324
|
-
return this.waveFormData;
|
|
4325
|
-
// eslint-disable-next-line prefer-const
|
|
4326
|
-
let [bucketSize, remainder] = this.divMod(this.sampleSize, this.waveFormData.length);
|
|
4327
|
-
const result = [];
|
|
4328
|
-
for (let i = 0; i < this.waveFormData.length; i++) {
|
|
4329
|
-
const extra = remainder && remainder-- ? 1 : 0;
|
|
4330
|
-
result.push(...Array(bucketSize + extra).fill(this.waveFormData[i]));
|
|
4331
|
-
}
|
|
4332
|
-
return result;
|
|
4333
|
-
};
|
|
4334
|
-
this.getNextBucketMean = (data, currentBucketIndex, bucketSize) => {
|
|
4335
|
-
const nextBucketStartIndex = Math.floor(currentBucketIndex * bucketSize) + 1;
|
|
4336
|
-
let nextNextBucketStartIndex = Math.floor((currentBucketIndex + 1) * bucketSize) + 1;
|
|
4337
|
-
nextNextBucketStartIndex =
|
|
4338
|
-
nextNextBucketStartIndex < data.length
|
|
4339
|
-
? nextNextBucketStartIndex
|
|
4340
|
-
: data.length;
|
|
4341
|
-
return this.mean(data.slice(nextBucketStartIndex, nextNextBucketStartIndex));
|
|
4342
|
-
};
|
|
4343
|
-
this.mean = (values) => values.reduce((acc, value) => acc + value, 0) / values.length;
|
|
4344
|
-
this.triangleAreaHeron = (a, b, c) => {
|
|
4345
|
-
const s = (a + b + c) / 2;
|
|
4346
|
-
return Math.sqrt(s * (s - a) * (s - b) * (s - c));
|
|
4347
|
-
};
|
|
4348
|
-
this.triangleBase = (a, b) => Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2));
|
|
4349
|
-
this.divMod = (num, divisor) => {
|
|
4350
|
-
return [Math.floor(num / divisor), num % divisor];
|
|
4351
|
-
};
|
|
4352
4485
|
}
|
|
4353
4486
|
ngOnInit() {
|
|
4354
4487
|
var _a;
|
|
@@ -4363,10 +4496,7 @@ class VoiceRecordingWavebarComponent {
|
|
|
4363
4496
|
}
|
|
4364
4497
|
ngOnChanges(changes) {
|
|
4365
4498
|
if (changes.waveFormData) {
|
|
4366
|
-
this.resampledWaveFormData =
|
|
4367
|
-
this.waveFormData.length > this.sampleSize
|
|
4368
|
-
? this.downsample()
|
|
4369
|
-
: this.upsample();
|
|
4499
|
+
this.resampledWaveFormData = resampleWaveForm(this.waveFormData, this.sampleSize);
|
|
4370
4500
|
}
|
|
4371
4501
|
if (changes.audioElement) {
|
|
4372
4502
|
this.ngZone.runOutsideAngular(() => {
|
|
@@ -4424,10 +4554,7 @@ class VoiceRecordingWavebarComponent {
|
|
|
4424
4554
|
sampleSize !== Infinity) {
|
|
4425
4555
|
this.ngZone.run(() => {
|
|
4426
4556
|
this.sampleSize = sampleSize;
|
|
4427
|
-
this.resampledWaveFormData =
|
|
4428
|
-
this.waveFormData.length > this.sampleSize
|
|
4429
|
-
? this.downsample()
|
|
4430
|
-
: this.upsample();
|
|
4557
|
+
this.resampledWaveFormData = resampleWaveForm(this.waveFormData, this.sampleSize);
|
|
4431
4558
|
if (this.isViewInited) {
|
|
4432
4559
|
this.cdRef.detectChanges();
|
|
4433
4560
|
}
|
|
@@ -4435,42 +4562,6 @@ class VoiceRecordingWavebarComponent {
|
|
|
4435
4562
|
}
|
|
4436
4563
|
}
|
|
4437
4564
|
}
|
|
4438
|
-
downsample() {
|
|
4439
|
-
if (this.waveFormData.length <= this.sampleSize) {
|
|
4440
|
-
return this.waveFormData;
|
|
4441
|
-
}
|
|
4442
|
-
if (this.sampleSize === 1)
|
|
4443
|
-
return [this.mean(this.waveFormData)];
|
|
4444
|
-
const result = [];
|
|
4445
|
-
// bucket size adjusted due to the fact that the first and the last item in the original data array is kept in target output
|
|
4446
|
-
const bucketSize = (this.waveFormData.length - 2) / (this.sampleSize - 2);
|
|
4447
|
-
let lastSelectedPointIndex = 0;
|
|
4448
|
-
result.push(this.waveFormData[lastSelectedPointIndex]); // Always add the first point
|
|
4449
|
-
let maxAreaPoint, maxArea, triangleArea;
|
|
4450
|
-
for (let bucketIndex = 1; bucketIndex < this.sampleSize - 1; bucketIndex++) {
|
|
4451
|
-
const previousBucketRefPoint = this.waveFormData[lastSelectedPointIndex];
|
|
4452
|
-
const nextBucketMean = this.getNextBucketMean(this.waveFormData, bucketIndex, bucketSize);
|
|
4453
|
-
const currentBucketStartIndex = Math.floor((bucketIndex - 1) * bucketSize) + 1;
|
|
4454
|
-
const nextBucketStartIndex = Math.floor(bucketIndex * bucketSize) + 1;
|
|
4455
|
-
const countUnitsBetweenAtoC = 1 + nextBucketStartIndex - currentBucketStartIndex;
|
|
4456
|
-
maxArea = triangleArea = -1;
|
|
4457
|
-
for (let currentPointIndex = currentBucketStartIndex; currentPointIndex < nextBucketStartIndex; currentPointIndex++) {
|
|
4458
|
-
const countUnitsBetweenAtoB = Math.abs(currentPointIndex - currentBucketStartIndex) + 1;
|
|
4459
|
-
const countUnitsBetweenBtoC = countUnitsBetweenAtoC - countUnitsBetweenAtoB;
|
|
4460
|
-
const currentPointValue = this.waveFormData[currentPointIndex];
|
|
4461
|
-
triangleArea = this.triangleAreaHeron(this.triangleBase(Math.abs(previousBucketRefPoint - currentPointValue), countUnitsBetweenAtoB), this.triangleBase(Math.abs(currentPointValue - nextBucketMean), countUnitsBetweenBtoC), this.triangleBase(Math.abs(previousBucketRefPoint - nextBucketMean), countUnitsBetweenAtoC));
|
|
4462
|
-
if (triangleArea > maxArea) {
|
|
4463
|
-
maxArea = triangleArea;
|
|
4464
|
-
maxAreaPoint = this.waveFormData[currentPointIndex];
|
|
4465
|
-
lastSelectedPointIndex = currentPointIndex;
|
|
4466
|
-
}
|
|
4467
|
-
}
|
|
4468
|
-
if (typeof maxAreaPoint !== 'undefined')
|
|
4469
|
-
result.push(maxAreaPoint);
|
|
4470
|
-
}
|
|
4471
|
-
result.push(this.waveFormData[this.waveFormData.length - 1]); // Always add the last point
|
|
4472
|
-
return result;
|
|
4473
|
-
}
|
|
4474
4565
|
}
|
|
4475
4566
|
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 });
|
|
4476
4567
|
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"] }] });
|
|
@@ -4556,16 +4647,7 @@ class VoiceRecordingComponent {
|
|
|
4556
4647
|
this.audioElement.nativeElement.playbackRate = playbackRate;
|
|
4557
4648
|
}
|
|
4558
4649
|
getFormattedDuration(duration) {
|
|
4559
|
-
|
|
4560
|
-
return '00:00';
|
|
4561
|
-
const [hours, hoursLeftover] = this.divMod(duration, 3600);
|
|
4562
|
-
const [minutes, seconds] = this.divMod(hoursLeftover, 60);
|
|
4563
|
-
const roundedSeconds = Math.ceil(seconds);
|
|
4564
|
-
const prependHrsZero = hours.toString().length === 1 ? '0' : '';
|
|
4565
|
-
const prependMinZero = minutes.toString().length === 1 ? '0' : '';
|
|
4566
|
-
const prependSecZero = roundedSeconds.toString().length === 1 ? '0' : '';
|
|
4567
|
-
const minSec = `${prependMinZero}${minutes}:${prependSecZero}${roundedSeconds}`;
|
|
4568
|
-
return hours ? `${prependHrsZero}${hours}:` + minSec : minSec;
|
|
4650
|
+
return formatDuration(duration);
|
|
4569
4651
|
}
|
|
4570
4652
|
getFileSize() {
|
|
4571
4653
|
var _a, _b;
|
|
@@ -4575,9 +4657,6 @@ class VoiceRecordingComponent {
|
|
|
4575
4657
|
}
|
|
4576
4658
|
return prettybytes(Number(this.attachment.file_size || 0));
|
|
4577
4659
|
}
|
|
4578
|
-
divMod(num, divisor) {
|
|
4579
|
-
return [Math.floor(num / divisor), num % divisor];
|
|
4580
|
-
}
|
|
4581
4660
|
}
|
|
4582
4661
|
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 });
|
|
4583
4662
|
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" }] });
|
|
@@ -4763,7 +4842,7 @@ class AttachmentListComponent {
|
|
|
4763
4842
|
}
|
|
4764
4843
|
}
|
|
4765
4844
|
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 });
|
|
4766
|
-
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" }] });
|
|
4845
|
+
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" }] });
|
|
4767
4846
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: AttachmentListComponent, decorators: [{
|
|
4768
4847
|
type: Component,
|
|
4769
4848
|
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" }]
|
|
@@ -5477,7 +5556,7 @@ class MessageComponent {
|
|
|
5477
5556
|
}
|
|
5478
5557
|
}
|
|
5479
5558
|
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 });
|
|
5480
|
-
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 });
|
|
5559
|
+
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 });
|
|
5481
5560
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: MessageComponent, decorators: [{
|
|
5482
5561
|
type: Component,
|
|
5483
5562
|
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" }]
|
|
@@ -5616,6 +5695,30 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImpor
|
|
|
5616
5695
|
}]
|
|
5617
5696
|
}], ctorParameters: function () { return []; } });
|
|
5618
5697
|
|
|
5698
|
+
/**
|
|
5699
|
+
* The `VoiceRecorderService` provides a commincation outlet between the message input and voice recorder components.
|
|
5700
|
+
*/
|
|
5701
|
+
class VoiceRecorderService {
|
|
5702
|
+
constructor() {
|
|
5703
|
+
/**
|
|
5704
|
+
* Use this property to get/set if the recording component should be visible
|
|
5705
|
+
*/
|
|
5706
|
+
this.isRecorderVisible$ = new BehaviorSubject(false);
|
|
5707
|
+
/**
|
|
5708
|
+
* The audio recording that was created
|
|
5709
|
+
*/
|
|
5710
|
+
this.recording$ = new BehaviorSubject(undefined);
|
|
5711
|
+
}
|
|
5712
|
+
}
|
|
5713
|
+
VoiceRecorderService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: VoiceRecorderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
5714
|
+
VoiceRecorderService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: VoiceRecorderService, providedIn: NgModule });
|
|
5715
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: VoiceRecorderService, decorators: [{
|
|
5716
|
+
type: Injectable,
|
|
5717
|
+
args: [{
|
|
5718
|
+
providedIn: NgModule,
|
|
5719
|
+
}]
|
|
5720
|
+
}], ctorParameters: function () { return []; } });
|
|
5721
|
+
|
|
5619
5722
|
/**
|
|
5620
5723
|
* 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.
|
|
5621
5724
|
*/
|
|
@@ -5641,6 +5744,11 @@ class MessageInputConfigService {
|
|
|
5641
5744
|
* 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.
|
|
5642
5745
|
*/
|
|
5643
5746
|
this.inputMode = 'desktop';
|
|
5747
|
+
/**
|
|
5748
|
+
* If `true` the recording will be sent as a message immediately after the recording is completed.
|
|
5749
|
+
* If `false`, the recording will added to the attachment preview, and users can continue composing the message.
|
|
5750
|
+
*/
|
|
5751
|
+
this.sendVoiceRecordingImmediately = true;
|
|
5644
5752
|
}
|
|
5645
5753
|
}
|
|
5646
5754
|
MessageInputConfigService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: MessageInputConfigService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
@@ -5652,6 +5760,523 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImpor
|
|
|
5652
5760
|
}]
|
|
5653
5761
|
}], ctorParameters: function () { return []; } });
|
|
5654
5762
|
|
|
5763
|
+
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
|
5764
|
+
|
|
5765
|
+
var MediaRecordingState;
|
|
5766
|
+
(function (MediaRecordingState) {
|
|
5767
|
+
MediaRecordingState["PAUSED"] = "paused";
|
|
5768
|
+
MediaRecordingState["RECORDING"] = "recording";
|
|
5769
|
+
MediaRecordingState["STOPPED"] = "stopped";
|
|
5770
|
+
MediaRecordingState["ERROR"] = "error";
|
|
5771
|
+
})(MediaRecordingState || (MediaRecordingState = {}));
|
|
5772
|
+
class MultimediaRecorder {
|
|
5773
|
+
constructor(notificationService, chatService, transcoder) {
|
|
5774
|
+
this.notificationService = notificationService;
|
|
5775
|
+
this.chatService = chatService;
|
|
5776
|
+
this.transcoder = transcoder;
|
|
5777
|
+
this.recordingSubject = new BehaviorSubject(undefined);
|
|
5778
|
+
this.recordedChunkDurations = [];
|
|
5779
|
+
this.recordingStateSubject = new BehaviorSubject(MediaRecordingState.STOPPED);
|
|
5780
|
+
this.generateRecordingTitle = (mimeType) => {
|
|
5781
|
+
if (this.customGenerateRecordingTitle) {
|
|
5782
|
+
return this.customGenerateRecordingTitle({ mimeType });
|
|
5783
|
+
}
|
|
5784
|
+
else {
|
|
5785
|
+
return `${this.mediaType}_recording_${new Date().toISOString()}.${getExtensionFromMimeType(mimeType)}`; // extension needed so that desktop Safari can play the asset
|
|
5786
|
+
}
|
|
5787
|
+
};
|
|
5788
|
+
this.handleErrorEvent = (e) => {
|
|
5789
|
+
/* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */
|
|
5790
|
+
this.logError(e.error);
|
|
5791
|
+
this.recordingStateSubject.next(MediaRecordingState.ERROR);
|
|
5792
|
+
this.notificationService.addTemporaryNotification('streamChat.An error has occurred during recording');
|
|
5793
|
+
void this.stop({ cancel: true });
|
|
5794
|
+
};
|
|
5795
|
+
this.handleDataavailableEvent = (e) => {
|
|
5796
|
+
if (!e.data.size)
|
|
5797
|
+
return;
|
|
5798
|
+
void this.makeRecording(e.data);
|
|
5799
|
+
};
|
|
5800
|
+
this.recording$ = this.recordingSubject.asObservable();
|
|
5801
|
+
this.recordingState$ = this.recordingStateSubject.asObservable();
|
|
5802
|
+
}
|
|
5803
|
+
get durationMs() {
|
|
5804
|
+
return (this.recordedChunkDurations.reduce((acc, val) => acc + val, 0) +
|
|
5805
|
+
(this.startTime ? Date.now() - this.startTime : 0));
|
|
5806
|
+
}
|
|
5807
|
+
get mediaType() {
|
|
5808
|
+
var _a;
|
|
5809
|
+
return ((_a = this.config.mimeType.split('/')) === null || _a === void 0 ? void 0 : _a[0]) || 'unknown';
|
|
5810
|
+
}
|
|
5811
|
+
get isRecording() {
|
|
5812
|
+
return (this.recordingStateSubject.value === MediaRecordingState.RECORDING ||
|
|
5813
|
+
this.recordingStateSubject.value === MediaRecordingState.PAUSED);
|
|
5814
|
+
}
|
|
5815
|
+
makeRecording(blob) {
|
|
5816
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
5817
|
+
const { mimeType } = this.config;
|
|
5818
|
+
try {
|
|
5819
|
+
if (mimeType.includes('webm')) {
|
|
5820
|
+
// The browser does not include duration metadata with the recorded blob
|
|
5821
|
+
blob = yield fixWebmDuration(blob, this.durationMs, {
|
|
5822
|
+
logger: () => null, // prevents polluting the browser console
|
|
5823
|
+
});
|
|
5824
|
+
}
|
|
5825
|
+
blob = yield this.transcoder.transcode(blob);
|
|
5826
|
+
if (!blob)
|
|
5827
|
+
return;
|
|
5828
|
+
const file = createFileFromBlobs({
|
|
5829
|
+
blobsArray: [blob],
|
|
5830
|
+
fileName: this.generateRecordingTitle(blob.type),
|
|
5831
|
+
mimeType: blob.type,
|
|
5832
|
+
});
|
|
5833
|
+
const previewUrl = yield createUriFromBlob(file);
|
|
5834
|
+
const extraData = this.enrichWithExtraData();
|
|
5835
|
+
this.recordingSubject.next(Object.assign({ recording: file, duration: this.durationMs / 1000, asset_url: previewUrl, mime_type: mimeType }, extraData));
|
|
5836
|
+
return file;
|
|
5837
|
+
}
|
|
5838
|
+
catch (error) {
|
|
5839
|
+
this.logError(error);
|
|
5840
|
+
this.recordingStateSubject.next(MediaRecordingState.ERROR);
|
|
5841
|
+
return undefined;
|
|
5842
|
+
}
|
|
5843
|
+
});
|
|
5844
|
+
}
|
|
5845
|
+
get recordingState() {
|
|
5846
|
+
return this.recordingStateSubject.value;
|
|
5847
|
+
}
|
|
5848
|
+
start() {
|
|
5849
|
+
var _a;
|
|
5850
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
5851
|
+
if ([MediaRecordingState.RECORDING, MediaRecordingState.PAUSED].includes(this.recordingStateSubject.value)) {
|
|
5852
|
+
return;
|
|
5853
|
+
}
|
|
5854
|
+
this.recordingSubject.next(undefined);
|
|
5855
|
+
// account for requirement on iOS as per this bug report: https://bugs.webkit.org/show_bug.cgi?id=252303
|
|
5856
|
+
if (!navigator.mediaDevices) {
|
|
5857
|
+
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)`);
|
|
5858
|
+
const error = new Error('Media recording is not supported');
|
|
5859
|
+
this.logError(error);
|
|
5860
|
+
this.recordingStateSubject.next(MediaRecordingState.ERROR);
|
|
5861
|
+
this.notificationService.addTemporaryNotification(`streamChat.Media recording not supported`);
|
|
5862
|
+
return;
|
|
5863
|
+
}
|
|
5864
|
+
try {
|
|
5865
|
+
const stream = yield navigator.mediaDevices.getUserMedia({ audio: true });
|
|
5866
|
+
this.mediaRecorder = new MediaRecorder(stream, this.config);
|
|
5867
|
+
this.mediaRecorder.addEventListener('dataavailable', this.handleDataavailableEvent);
|
|
5868
|
+
this.mediaRecorder.addEventListener('error', this.handleErrorEvent);
|
|
5869
|
+
this.startTime = new Date().getTime();
|
|
5870
|
+
this.mediaRecorder.start();
|
|
5871
|
+
this.recordingStateSubject.next(MediaRecordingState.RECORDING);
|
|
5872
|
+
}
|
|
5873
|
+
catch (error) {
|
|
5874
|
+
this.logError(error);
|
|
5875
|
+
void this.stop({ cancel: true });
|
|
5876
|
+
this.recordingStateSubject.next(MediaRecordingState.ERROR);
|
|
5877
|
+
const isNotAllowed = (_a = error.name) === null || _a === void 0 ? void 0 : _a.includes('NotAllowedError');
|
|
5878
|
+
this.notificationService.addTemporaryNotification(isNotAllowed
|
|
5879
|
+
? `streamChat.Please grant permission to use microhpone`
|
|
5880
|
+
: `streamChat.Error starting recording`);
|
|
5881
|
+
}
|
|
5882
|
+
});
|
|
5883
|
+
}
|
|
5884
|
+
pause() {
|
|
5885
|
+
var _a;
|
|
5886
|
+
if (this.recordingStateSubject.value !== MediaRecordingState.RECORDING)
|
|
5887
|
+
return;
|
|
5888
|
+
if (this.startTime) {
|
|
5889
|
+
this.recordedChunkDurations.push(new Date().getTime() - this.startTime);
|
|
5890
|
+
this.startTime = undefined;
|
|
5891
|
+
}
|
|
5892
|
+
(_a = this.mediaRecorder) === null || _a === void 0 ? void 0 : _a.pause();
|
|
5893
|
+
this.recordingStateSubject.next(MediaRecordingState.PAUSED);
|
|
5894
|
+
}
|
|
5895
|
+
resume() {
|
|
5896
|
+
var _a;
|
|
5897
|
+
if (this.recordingStateSubject.value !== MediaRecordingState.PAUSED)
|
|
5898
|
+
return;
|
|
5899
|
+
this.startTime = new Date().getTime();
|
|
5900
|
+
(_a = this.mediaRecorder) === null || _a === void 0 ? void 0 : _a.resume();
|
|
5901
|
+
this.recordingStateSubject.next(MediaRecordingState.RECORDING);
|
|
5902
|
+
}
|
|
5903
|
+
stop(options = { cancel: false }) {
|
|
5904
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
5905
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
5906
|
+
if (this.startTime) {
|
|
5907
|
+
this.recordedChunkDurations.push(new Date().getTime() - this.startTime);
|
|
5908
|
+
this.startTime = undefined;
|
|
5909
|
+
}
|
|
5910
|
+
let recording;
|
|
5911
|
+
(_a = this.mediaRecorder) === null || _a === void 0 ? void 0 : _a.stop();
|
|
5912
|
+
try {
|
|
5913
|
+
if (!options.cancel &&
|
|
5914
|
+
this.recordingStateSubject.value !== MediaRecordingState.ERROR) {
|
|
5915
|
+
recording = yield new Promise((resolve, reject) => {
|
|
5916
|
+
this.recording$.subscribe((r) => {
|
|
5917
|
+
if (r) {
|
|
5918
|
+
resolve(r);
|
|
5919
|
+
}
|
|
5920
|
+
});
|
|
5921
|
+
this.recordingState$.subscribe((s) => {
|
|
5922
|
+
if (s === MediaRecordingState.ERROR) {
|
|
5923
|
+
reject(new Error(`Recording couldn't be created`));
|
|
5924
|
+
}
|
|
5925
|
+
});
|
|
5926
|
+
});
|
|
5927
|
+
}
|
|
5928
|
+
}
|
|
5929
|
+
catch (_h) {
|
|
5930
|
+
this.notificationService.addTemporaryNotification('streamChat.An error has occurred during recording');
|
|
5931
|
+
}
|
|
5932
|
+
finally {
|
|
5933
|
+
this.recordedChunkDurations = [];
|
|
5934
|
+
this.startTime = undefined;
|
|
5935
|
+
(_b = this.mediaRecorder) === null || _b === void 0 ? void 0 : _b.removeEventListener('dataavailable', this.handleDataavailableEvent);
|
|
5936
|
+
(_c = this.mediaRecorder) === null || _c === void 0 ? void 0 : _c.removeEventListener('error', this.handleErrorEvent);
|
|
5937
|
+
if ((_e = (_d = this.mediaRecorder) === null || _d === void 0 ? void 0 : _d.stream) === null || _e === void 0 ? void 0 : _e.active) {
|
|
5938
|
+
(_g = (_f = this.mediaRecorder) === null || _f === void 0 ? void 0 : _f.stream) === null || _g === void 0 ? void 0 : _g.getTracks().forEach((track) => {
|
|
5939
|
+
var _a, _b;
|
|
5940
|
+
track.stop();
|
|
5941
|
+
(_b = (_a = this.mediaRecorder) === null || _a === void 0 ? void 0 : _a.stream) === null || _b === void 0 ? void 0 : _b.removeTrack(track);
|
|
5942
|
+
});
|
|
5943
|
+
this.mediaRecorder = undefined;
|
|
5944
|
+
}
|
|
5945
|
+
this.recordingStateSubject.next(MediaRecordingState.STOPPED);
|
|
5946
|
+
}
|
|
5947
|
+
return recording;
|
|
5948
|
+
});
|
|
5949
|
+
}
|
|
5950
|
+
logError(error) {
|
|
5951
|
+
var _a;
|
|
5952
|
+
(_a = this.chatService.chatClient) === null || _a === void 0 ? void 0 : _a.logger('error', error.message, {
|
|
5953
|
+
error: error,
|
|
5954
|
+
tag: ['MediaRecorder'],
|
|
5955
|
+
});
|
|
5956
|
+
}
|
|
5957
|
+
}
|
|
5958
|
+
|
|
5959
|
+
const WAV_HEADER_LENGTH_BYTES = 44;
|
|
5960
|
+
const BYTES_PER_SAMPLE = 2;
|
|
5961
|
+
const RIFF_FILE_MAX_BYTES = 4294967295;
|
|
5962
|
+
const HEADER = {
|
|
5963
|
+
AUDIO_FORMAT: { offset: 20, value: 1 },
|
|
5964
|
+
BITS_PER_SAMPLE: { offset: 34, value: BYTES_PER_SAMPLE * 8 },
|
|
5965
|
+
BLOCK_ALIGN: { offset: 32 },
|
|
5966
|
+
BYTE_RATE: { offset: 28 },
|
|
5967
|
+
CHANNEL_COUNT: { offset: 22 },
|
|
5968
|
+
CHUNK_ID: { offset: 0, value: 0x52494646 },
|
|
5969
|
+
CHUNK_SIZE: { offset: 4 },
|
|
5970
|
+
FILE_FORMAT: { offset: 8, value: 0x57415645 },
|
|
5971
|
+
SAMPLE_RATE: { offset: 24 },
|
|
5972
|
+
SUBCHUNK1_ID: { offset: 12, value: 0x666d7420 },
|
|
5973
|
+
SUBCHUNK1_SIZE: { offset: 16, value: 16 },
|
|
5974
|
+
SUBCHUNK2_ID: { offset: 36, value: 0x64617461 },
|
|
5975
|
+
SUBCHUNK2_SIZE: { offset: 40 }, // actual audio data size
|
|
5976
|
+
};
|
|
5977
|
+
/**
|
|
5978
|
+
* 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.
|
|
5979
|
+
*
|
|
5980
|
+
* If you want to use your own transcoder you can provide a `customTranscoder`.
|
|
5981
|
+
*/
|
|
5982
|
+
class TranscoderService {
|
|
5983
|
+
constructor() {
|
|
5984
|
+
this.config = {
|
|
5985
|
+
sampleRate: 16000,
|
|
5986
|
+
};
|
|
5987
|
+
this.splitDataByChannel = (audioBuffer) => Array.from({ length: audioBuffer.numberOfChannels }, (_, i) => audioBuffer.getChannelData(i));
|
|
5988
|
+
}
|
|
5989
|
+
/**
|
|
5990
|
+
* 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
|
|
5991
|
+
* @param blob
|
|
5992
|
+
* @returns the transcoded file
|
|
5993
|
+
*/
|
|
5994
|
+
transcode(blob) {
|
|
5995
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
5996
|
+
if (this.customTranscoder) {
|
|
5997
|
+
return this.customTranscoder(blob);
|
|
5998
|
+
}
|
|
5999
|
+
if (blob.type.includes('audio/mp4')) {
|
|
6000
|
+
return blob;
|
|
6001
|
+
}
|
|
6002
|
+
const audioBuffer = yield this.renderAudio(yield this.toAudioBuffer(blob), this.config.sampleRate);
|
|
6003
|
+
const numberOfSamples = audioBuffer.duration * this.config.sampleRate;
|
|
6004
|
+
const fileSizeBytes = numberOfSamples * audioBuffer.numberOfChannels * BYTES_PER_SAMPLE +
|
|
6005
|
+
WAV_HEADER_LENGTH_BYTES;
|
|
6006
|
+
const arrayBuffer = new ArrayBuffer(fileSizeBytes);
|
|
6007
|
+
this.writeWavHeader({
|
|
6008
|
+
arrayBuffer,
|
|
6009
|
+
channelCount: audioBuffer.numberOfChannels,
|
|
6010
|
+
sampleRate: this.config.sampleRate,
|
|
6011
|
+
});
|
|
6012
|
+
this.writeWavAudioData({
|
|
6013
|
+
arrayBuffer,
|
|
6014
|
+
dataByChannel: this.splitDataByChannel(audioBuffer),
|
|
6015
|
+
});
|
|
6016
|
+
return new Blob([arrayBuffer], { type: 'audio/wav' });
|
|
6017
|
+
});
|
|
6018
|
+
}
|
|
6019
|
+
renderAudio(audioBuffer, sampleRate) {
|
|
6020
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
6021
|
+
const offlineAudioCtx = new OfflineAudioContext(audioBuffer.numberOfChannels, audioBuffer.duration * sampleRate, sampleRate);
|
|
6022
|
+
const source = offlineAudioCtx.createBufferSource();
|
|
6023
|
+
source.buffer = audioBuffer;
|
|
6024
|
+
source.connect(offlineAudioCtx.destination);
|
|
6025
|
+
source.start();
|
|
6026
|
+
return yield offlineAudioCtx.startRendering();
|
|
6027
|
+
});
|
|
6028
|
+
}
|
|
6029
|
+
toAudioBuffer(blob) {
|
|
6030
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
6031
|
+
const audioCtx = new AudioContext();
|
|
6032
|
+
const arrayBuffer = yield readBlobAsArrayBuffer(blob);
|
|
6033
|
+
const decodedData = yield audioCtx.decodeAudioData(arrayBuffer);
|
|
6034
|
+
if (audioCtx.state !== 'closed')
|
|
6035
|
+
yield audioCtx.close();
|
|
6036
|
+
return decodedData;
|
|
6037
|
+
});
|
|
6038
|
+
}
|
|
6039
|
+
writeWavAudioData({ arrayBuffer, dataByChannel, }) {
|
|
6040
|
+
const dataView = new DataView(arrayBuffer);
|
|
6041
|
+
const channelCount = dataByChannel.length;
|
|
6042
|
+
dataByChannel.forEach((channelData, channelIndex) => {
|
|
6043
|
+
let writeOffset = WAV_HEADER_LENGTH_BYTES + channelCount * channelIndex;
|
|
6044
|
+
channelData.forEach((float32Value) => {
|
|
6045
|
+
dataView.setInt16(writeOffset, float32Value < 0
|
|
6046
|
+
? Math.max(-1, float32Value) * 32768
|
|
6047
|
+
: Math.min(1, float32Value) * 32767, true);
|
|
6048
|
+
writeOffset += channelCount * BYTES_PER_SAMPLE;
|
|
6049
|
+
});
|
|
6050
|
+
});
|
|
6051
|
+
}
|
|
6052
|
+
writeWavHeader({ arrayBuffer, channelCount, sampleRate, }) {
|
|
6053
|
+
const byteRate = sampleRate * channelCount * BYTES_PER_SAMPLE; // bytes/sec
|
|
6054
|
+
const blockAlign = channelCount * BYTES_PER_SAMPLE;
|
|
6055
|
+
const dataView = new DataView(arrayBuffer);
|
|
6056
|
+
/*
|
|
6057
|
+
* 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
|
|
6058
|
+
* data chunk.
|
|
6059
|
+
*/
|
|
6060
|
+
const dataChunkSize = Math.min(dataView.byteLength - WAV_HEADER_LENGTH_BYTES, RIFF_FILE_MAX_BYTES - WAV_HEADER_LENGTH_BYTES);
|
|
6061
|
+
dataView.setUint32(HEADER.CHUNK_ID.offset, HEADER.CHUNK_ID.value); // "RIFF"
|
|
6062
|
+
dataView.setUint32(HEADER.CHUNK_SIZE.offset, arrayBuffer.byteLength - 8, true); // adjustment for the first two headers - chunk id + file size
|
|
6063
|
+
dataView.setUint32(HEADER.FILE_FORMAT.offset, HEADER.FILE_FORMAT.value); // "WAVE"
|
|
6064
|
+
dataView.setUint32(HEADER.SUBCHUNK1_ID.offset, HEADER.SUBCHUNK1_ID.value); // "fmt "
|
|
6065
|
+
dataView.setUint32(HEADER.SUBCHUNK1_SIZE.offset, HEADER.SUBCHUNK1_SIZE.value, true);
|
|
6066
|
+
dataView.setUint16(HEADER.AUDIO_FORMAT.offset, HEADER.AUDIO_FORMAT.value, true);
|
|
6067
|
+
dataView.setUint16(HEADER.CHANNEL_COUNT.offset, channelCount, true);
|
|
6068
|
+
dataView.setUint32(HEADER.SAMPLE_RATE.offset, sampleRate, true);
|
|
6069
|
+
dataView.setUint32(HEADER.BYTE_RATE.offset, byteRate, true);
|
|
6070
|
+
dataView.setUint16(HEADER.BLOCK_ALIGN.offset, blockAlign, true);
|
|
6071
|
+
dataView.setUint16(HEADER.BITS_PER_SAMPLE.offset, HEADER.BITS_PER_SAMPLE.value, true);
|
|
6072
|
+
dataView.setUint32(HEADER.SUBCHUNK2_ID.offset, HEADER.SUBCHUNK2_ID.value); // "data"
|
|
6073
|
+
dataView.setUint32(HEADER.SUBCHUNK2_SIZE.offset, dataChunkSize, true);
|
|
6074
|
+
}
|
|
6075
|
+
}
|
|
6076
|
+
TranscoderService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: TranscoderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
6077
|
+
TranscoderService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: TranscoderService, providedIn: NgModule });
|
|
6078
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: TranscoderService, decorators: [{
|
|
6079
|
+
type: Injectable,
|
|
6080
|
+
args: [{ providedIn: NgModule }]
|
|
6081
|
+
}], ctorParameters: function () { return []; } });
|
|
6082
|
+
|
|
6083
|
+
const MAX_FREQUENCY_AMPLITUDE = 255;
|
|
6084
|
+
const rootMeanSquare = (values) => Math.sqrt(values.reduce((acc, val) => acc + Math.pow(val, 2), 0) / values.length);
|
|
6085
|
+
const DEFAULT_AMPLITUDE_RECORDER_CONFIG = {
|
|
6086
|
+
analyserConfig: {
|
|
6087
|
+
fftSize: 32,
|
|
6088
|
+
maxDecibels: 0,
|
|
6089
|
+
minDecibels: -100,
|
|
6090
|
+
},
|
|
6091
|
+
sampleCount: 100,
|
|
6092
|
+
samplingFrequencyMs: 60,
|
|
6093
|
+
};
|
|
6094
|
+
/**
|
|
6095
|
+
* The `AmplitudeRecorderService` is a utility service used to create amplitude values for voice recordings, making it possible to display a wave bar
|
|
6096
|
+
*/
|
|
6097
|
+
class AmplitudeRecorderService {
|
|
6098
|
+
constructor(chatService) {
|
|
6099
|
+
this.chatService = chatService;
|
|
6100
|
+
this.config = DEFAULT_AMPLITUDE_RECORDER_CONFIG;
|
|
6101
|
+
this.amplitudesSubject = new BehaviorSubject([]);
|
|
6102
|
+
this.errorSubject = new BehaviorSubject(undefined);
|
|
6103
|
+
/**
|
|
6104
|
+
* Start amplitude recording for the given media stream
|
|
6105
|
+
* @param stream
|
|
6106
|
+
*/
|
|
6107
|
+
this.start = (stream) => {
|
|
6108
|
+
this.stop();
|
|
6109
|
+
this.stream = stream;
|
|
6110
|
+
this.init();
|
|
6111
|
+
this.resume();
|
|
6112
|
+
};
|
|
6113
|
+
this.amplitudes$ = this.amplitudesSubject.asObservable();
|
|
6114
|
+
this.error$ = this.errorSubject.asObservable();
|
|
6115
|
+
}
|
|
6116
|
+
/**
|
|
6117
|
+
* The recorded amplitudes
|
|
6118
|
+
*/
|
|
6119
|
+
get amplitudes() {
|
|
6120
|
+
return this.amplitudesSubject.value;
|
|
6121
|
+
}
|
|
6122
|
+
/**
|
|
6123
|
+
* Temporarily pause amplitude recording, recording can be resumed with `resume`
|
|
6124
|
+
*/
|
|
6125
|
+
pause() {
|
|
6126
|
+
clearInterval(this.amplitudeSamplingInterval);
|
|
6127
|
+
this.amplitudeSamplingInterval = undefined;
|
|
6128
|
+
}
|
|
6129
|
+
/**
|
|
6130
|
+
* Resume amplited recording after it was pasued
|
|
6131
|
+
*/
|
|
6132
|
+
resume() {
|
|
6133
|
+
this.amplitudeSamplingInterval = setInterval(() => {
|
|
6134
|
+
if (!this.analyserNode) {
|
|
6135
|
+
return;
|
|
6136
|
+
}
|
|
6137
|
+
const frequencyBins = new Uint8Array(this.analyserNode.frequencyBinCount);
|
|
6138
|
+
try {
|
|
6139
|
+
this.analyserNode.getByteFrequencyData(frequencyBins);
|
|
6140
|
+
}
|
|
6141
|
+
catch (e) {
|
|
6142
|
+
this.logError(e);
|
|
6143
|
+
this.errorSubject.next(e);
|
|
6144
|
+
return;
|
|
6145
|
+
}
|
|
6146
|
+
const normalizedSignalStrength = rootMeanSquare(frequencyBins) / MAX_FREQUENCY_AMPLITUDE;
|
|
6147
|
+
this.amplitudesSubject.next([
|
|
6148
|
+
...this.amplitudesSubject.value,
|
|
6149
|
+
normalizedSignalStrength,
|
|
6150
|
+
]);
|
|
6151
|
+
}, this.config.samplingFrequencyMs);
|
|
6152
|
+
}
|
|
6153
|
+
/**
|
|
6154
|
+
* Stop the amplitude recording and frees up used resources
|
|
6155
|
+
*/
|
|
6156
|
+
stop() {
|
|
6157
|
+
var _a, _b, _c, _d;
|
|
6158
|
+
if (!this.stream) {
|
|
6159
|
+
return;
|
|
6160
|
+
}
|
|
6161
|
+
this.stream = undefined;
|
|
6162
|
+
clearInterval(this.amplitudeSamplingInterval);
|
|
6163
|
+
this.amplitudeSamplingInterval = undefined;
|
|
6164
|
+
this.amplitudesSubject.next([]);
|
|
6165
|
+
this.errorSubject.next(undefined);
|
|
6166
|
+
(_a = this.microphone) === null || _a === void 0 ? void 0 : _a.disconnect();
|
|
6167
|
+
(_b = this.analyserNode) === null || _b === void 0 ? void 0 : _b.disconnect();
|
|
6168
|
+
if (((_c = this.audioContext) === null || _c === void 0 ? void 0 : _c.state) !== 'closed') {
|
|
6169
|
+
void ((_d = this.audioContext) === null || _d === void 0 ? void 0 : _d.close());
|
|
6170
|
+
}
|
|
6171
|
+
}
|
|
6172
|
+
init() {
|
|
6173
|
+
if (!this.stream) {
|
|
6174
|
+
return;
|
|
6175
|
+
}
|
|
6176
|
+
this.audioContext = new AudioContext();
|
|
6177
|
+
this.analyserNode = this.audioContext.createAnalyser();
|
|
6178
|
+
const { analyserConfig } = this.config;
|
|
6179
|
+
this.analyserNode.fftSize = analyserConfig.fftSize;
|
|
6180
|
+
this.analyserNode.maxDecibels = analyserConfig.maxDecibels;
|
|
6181
|
+
this.analyserNode.minDecibels = analyserConfig.minDecibels;
|
|
6182
|
+
this.microphone = this.audioContext.createMediaStreamSource(this.stream);
|
|
6183
|
+
this.microphone.connect(this.analyserNode);
|
|
6184
|
+
}
|
|
6185
|
+
logError(error) {
|
|
6186
|
+
var _a;
|
|
6187
|
+
(_a = this.chatService.chatClient) === null || _a === void 0 ? void 0 : _a.logger('error', error.message, {
|
|
6188
|
+
error: error,
|
|
6189
|
+
tag: ['AmplitudeRecorderService'],
|
|
6190
|
+
});
|
|
6191
|
+
}
|
|
6192
|
+
}
|
|
6193
|
+
AmplitudeRecorderService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: AmplitudeRecorderService, deps: [{ token: ChatClientService }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
6194
|
+
AmplitudeRecorderService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: AmplitudeRecorderService, providedIn: NgModule });
|
|
6195
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: AmplitudeRecorderService, decorators: [{
|
|
6196
|
+
type: Injectable,
|
|
6197
|
+
args: [{ providedIn: NgModule }]
|
|
6198
|
+
}], ctorParameters: function () { return [{ type: ChatClientService }]; } });
|
|
6199
|
+
|
|
6200
|
+
/**
|
|
6201
|
+
* The `AudioRecorderService` can record an audio file, the SDK uses this to record a voice message
|
|
6202
|
+
*/
|
|
6203
|
+
class AudioRecorderService extends MultimediaRecorder {
|
|
6204
|
+
constructor(notificationService, chatService, transcoder, amplitudeRecorder) {
|
|
6205
|
+
super(notificationService, chatService, transcoder);
|
|
6206
|
+
this.amplitudeRecorder = amplitudeRecorder;
|
|
6207
|
+
/**
|
|
6208
|
+
* Due to browser restrictions the following config is used:
|
|
6209
|
+
* - In Safari we record in audio/mp4
|
|
6210
|
+
* - For all other browsers we use audio/webm (which is then transcoded to wav)
|
|
6211
|
+
*/
|
|
6212
|
+
this.config = {
|
|
6213
|
+
mimeType: isSafari ? 'audio/mp4;codecs=mp4a.40.2' : 'audio/webm',
|
|
6214
|
+
};
|
|
6215
|
+
}
|
|
6216
|
+
enrichWithExtraData() {
|
|
6217
|
+
const waveformData = resampleWaveForm(this.amplitudeRecorder.amplitudes, this.amplitudeRecorder.config.sampleCount);
|
|
6218
|
+
return { waveform_data: waveformData };
|
|
6219
|
+
}
|
|
6220
|
+
/**
|
|
6221
|
+
* Start audio recording
|
|
6222
|
+
*/
|
|
6223
|
+
start() {
|
|
6224
|
+
const _super = Object.create(null, {
|
|
6225
|
+
start: { get: () => super.start }
|
|
6226
|
+
});
|
|
6227
|
+
var _a, _b;
|
|
6228
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
6229
|
+
const result = yield _super.start.call(this);
|
|
6230
|
+
if ((_a = this.mediaRecorder) === null || _a === void 0 ? void 0 : _a.stream) {
|
|
6231
|
+
this.amplitudeRecorder.start((_b = this.mediaRecorder) === null || _b === void 0 ? void 0 : _b.stream);
|
|
6232
|
+
}
|
|
6233
|
+
return result;
|
|
6234
|
+
});
|
|
6235
|
+
}
|
|
6236
|
+
/**
|
|
6237
|
+
* Pause audio recording, it can be restarted using `resume`
|
|
6238
|
+
*/
|
|
6239
|
+
pause() {
|
|
6240
|
+
const result = super.pause();
|
|
6241
|
+
this.amplitudeRecorder.pause();
|
|
6242
|
+
return result;
|
|
6243
|
+
}
|
|
6244
|
+
/**
|
|
6245
|
+
* Resume a previously paused recording
|
|
6246
|
+
*/
|
|
6247
|
+
resume() {
|
|
6248
|
+
const result = super.resume();
|
|
6249
|
+
this.amplitudeRecorder.resume();
|
|
6250
|
+
return result;
|
|
6251
|
+
}
|
|
6252
|
+
/**
|
|
6253
|
+
* Stop the recording and free up used resources
|
|
6254
|
+
* @param options
|
|
6255
|
+
* @param options.cancel if this is `true` no recording will be created, but resources will be freed
|
|
6256
|
+
* @returns the recording
|
|
6257
|
+
*/
|
|
6258
|
+
stop(options) {
|
|
6259
|
+
const _super = Object.create(null, {
|
|
6260
|
+
stop: { get: () => super.stop }
|
|
6261
|
+
});
|
|
6262
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
6263
|
+
try {
|
|
6264
|
+
const result = yield _super.stop.call(this, options);
|
|
6265
|
+
return result;
|
|
6266
|
+
}
|
|
6267
|
+
finally {
|
|
6268
|
+
this.amplitudeRecorder.stop();
|
|
6269
|
+
}
|
|
6270
|
+
});
|
|
6271
|
+
}
|
|
6272
|
+
}
|
|
6273
|
+
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 });
|
|
6274
|
+
AudioRecorderService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: AudioRecorderService, providedIn: NgModel });
|
|
6275
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: AudioRecorderService, decorators: [{
|
|
6276
|
+
type: Injectable,
|
|
6277
|
+
args: [{ providedIn: NgModel }]
|
|
6278
|
+
}], ctorParameters: function () { return [{ type: NotificationService }, { type: ChatClientService }, { type: TranscoderService }, { type: AmplitudeRecorderService }]; } });
|
|
6279
|
+
|
|
5655
6280
|
/**
|
|
5656
6281
|
* 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.
|
|
5657
6282
|
*/
|
|
@@ -5677,10 +6302,10 @@ class AttachmentPreviewListComponent {
|
|
|
5677
6302
|
}
|
|
5678
6303
|
}
|
|
5679
6304
|
AttachmentPreviewListComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: AttachmentPreviewListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5680
|
-
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'
|
|
6305
|
+
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" }] });
|
|
5681
6306
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: AttachmentPreviewListComponent, decorators: [{
|
|
5682
6307
|
type: Component,
|
|
5683
|
-
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'
|
|
6308
|
+
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" }]
|
|
5684
6309
|
}], ctorParameters: function () { return []; }, propDecorators: { attachmentUploads$: [{
|
|
5685
6310
|
type: Input
|
|
5686
6311
|
}], retryAttachmentUpload: [{
|
|
@@ -5693,7 +6318,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImpor
|
|
|
5693
6318
|
* 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).
|
|
5694
6319
|
*/
|
|
5695
6320
|
class MessageInputComponent {
|
|
5696
|
-
constructor(channelService, notificationService, attachmentService, configService, textareaType, componentFactoryResolver, cdRef, emojiInputService, customTemplatesService, messageActionsService) {
|
|
6321
|
+
constructor(channelService, notificationService, attachmentService, configService, textareaType, componentFactoryResolver, cdRef, emojiInputService, customTemplatesService, messageActionsService, voiceRecorderService, audioRecorder) {
|
|
5697
6322
|
this.channelService = channelService;
|
|
5698
6323
|
this.notificationService = notificationService;
|
|
5699
6324
|
this.attachmentService = attachmentService;
|
|
@@ -5704,6 +6329,8 @@ class MessageInputComponent {
|
|
|
5704
6329
|
this.emojiInputService = emojiInputService;
|
|
5705
6330
|
this.customTemplatesService = customTemplatesService;
|
|
5706
6331
|
this.messageActionsService = messageActionsService;
|
|
6332
|
+
this.voiceRecorderService = voiceRecorderService;
|
|
6333
|
+
this.audioRecorder = audioRecorder;
|
|
5707
6334
|
/**
|
|
5708
6335
|
* Determines if the message is being dispalyed in a channel or in a [thread](https://getstream.io/chat/docs/javascript/threads/?language=javascript).
|
|
5709
6336
|
*/
|
|
@@ -5722,11 +6349,16 @@ class MessageInputComponent {
|
|
|
5722
6349
|
* 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.
|
|
5723
6350
|
*/
|
|
5724
6351
|
this.displaySendButton = true;
|
|
6352
|
+
/**
|
|
6353
|
+
* You can enable/disable voice recordings with this input
|
|
6354
|
+
*/
|
|
6355
|
+
this.displayVoiceRecordingButton = false;
|
|
5725
6356
|
/**
|
|
5726
6357
|
* Emits when a message was successfuly sent or updated
|
|
5727
6358
|
*/
|
|
5728
6359
|
this.messageUpdate = new EventEmitter();
|
|
5729
6360
|
this.class = 'str-chat__message-input-angular-host';
|
|
6361
|
+
this.isVoiceRecording = true;
|
|
5730
6362
|
this.textareaValue = '';
|
|
5731
6363
|
this.mentionedUsers = [];
|
|
5732
6364
|
this.typingStart$ = new Subject();
|
|
@@ -5748,6 +6380,7 @@ class MessageInputComponent {
|
|
|
5748
6380
|
if (channel && this.channel && channel.id !== this.channel.id) {
|
|
5749
6381
|
this.textareaValue = '';
|
|
5750
6382
|
this.attachmentService.resetAttachmentUploads();
|
|
6383
|
+
this.voiceRecorderService.isRecorderVisible$.next(false);
|
|
5751
6384
|
}
|
|
5752
6385
|
const capabilities = (_a = channel === null || channel === void 0 ? void 0 : channel.data) === null || _a === void 0 ? void 0 : _a.own_capabilities;
|
|
5753
6386
|
if (capabilities) {
|
|
@@ -5780,6 +6413,9 @@ class MessageInputComponent {
|
|
|
5780
6413
|
this.mentionScope = this.configService.mentionScope;
|
|
5781
6414
|
this.inputMode = this.configService.inputMode;
|
|
5782
6415
|
this.subscriptions.push(this.typingStart$.subscribe(() => void this.channelService.typingStarted(this.parentMessageId)));
|
|
6416
|
+
this.subscriptions.push(this.voiceRecorderService.isRecorderVisible$.subscribe((isVisible) => {
|
|
6417
|
+
this.isVoiceRecording = isVisible;
|
|
6418
|
+
}));
|
|
5783
6419
|
this.subscriptions.push(combineLatest([
|
|
5784
6420
|
this.channelService.latestMessageDateByUserByChannels$,
|
|
5785
6421
|
this.channelService.activeChannel$,
|
|
@@ -5800,6 +6436,11 @@ class MessageInputComponent {
|
|
|
5800
6436
|
this.stopCooldown();
|
|
5801
6437
|
}
|
|
5802
6438
|
}));
|
|
6439
|
+
this.subscriptions.push(this.voiceRecorderService.recording$.subscribe((recording) => {
|
|
6440
|
+
if (recording) {
|
|
6441
|
+
void this.voiceRecordingReady(recording);
|
|
6442
|
+
}
|
|
6443
|
+
}));
|
|
5803
6444
|
}
|
|
5804
6445
|
ngOnInit() {
|
|
5805
6446
|
this.subscriptions.push(this.customTemplatesService.emojiPickerTemplate$.subscribe((template) => {
|
|
@@ -5968,6 +6609,28 @@ class MessageInputComponent {
|
|
|
5968
6609
|
attachmentService: this.attachmentService,
|
|
5969
6610
|
};
|
|
5970
6611
|
}
|
|
6612
|
+
startVoiceRecording() {
|
|
6613
|
+
var _a, _b;
|
|
6614
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
6615
|
+
yield ((_a = this.audioRecorder) === null || _a === void 0 ? void 0 : _a.start());
|
|
6616
|
+
if ((_b = this.audioRecorder) === null || _b === void 0 ? void 0 : _b.isRecording) {
|
|
6617
|
+
this.voiceRecorderService.isRecorderVisible$.next(true);
|
|
6618
|
+
}
|
|
6619
|
+
});
|
|
6620
|
+
}
|
|
6621
|
+
voiceRecordingReady(recording) {
|
|
6622
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
6623
|
+
try {
|
|
6624
|
+
yield this.attachmentService.uploadVoiceRecording(recording);
|
|
6625
|
+
if (this.configService.sendVoiceRecordingImmediately) {
|
|
6626
|
+
yield this.messageSent();
|
|
6627
|
+
}
|
|
6628
|
+
}
|
|
6629
|
+
finally {
|
|
6630
|
+
this.voiceRecorderService.isRecorderVisible$.next(false);
|
|
6631
|
+
}
|
|
6632
|
+
});
|
|
6633
|
+
}
|
|
5971
6634
|
get isUpdate() {
|
|
5972
6635
|
return !!this.message;
|
|
5973
6636
|
}
|
|
@@ -6072,16 +6735,18 @@ class MessageInputComponent {
|
|
|
6072
6735
|
}
|
|
6073
6736
|
}
|
|
6074
6737
|
}
|
|
6075
|
-
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 });
|
|
6076
|
-
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
|
|
6738
|
+
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 });
|
|
6739
|
+
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" }] });
|
|
6077
6740
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: MessageInputComponent, decorators: [{
|
|
6078
6741
|
type: Component,
|
|
6079
|
-
args: [{ selector: 'stream-message-input', providers: [AttachmentService, EmojiInputService], template: "<div
|
|
6742
|
+
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" }]
|
|
6080
6743
|
}], ctorParameters: function () {
|
|
6081
6744
|
return [{ type: ChannelService }, { type: NotificationService }, { type: AttachmentService }, { type: MessageInputConfigService }, { type: i0.Type, decorators: [{
|
|
6082
6745
|
type: Inject,
|
|
6083
6746
|
args: [textareaInjectionToken]
|
|
6084
|
-
}] }, { type: i0.ComponentFactoryResolver }, { type: i0.ChangeDetectorRef }, { type: EmojiInputService }, { type: CustomTemplatesService }, { type: MessageActionsService }
|
|
6747
|
+
}] }, { type: i0.ComponentFactoryResolver }, { type: i0.ChangeDetectorRef }, { type: EmojiInputService }, { type: CustomTemplatesService }, { type: MessageActionsService }, { type: VoiceRecorderService }, { type: AudioRecorderService, decorators: [{
|
|
6748
|
+
type: Optional
|
|
6749
|
+
}] }];
|
|
6085
6750
|
}, propDecorators: { isFileUploadEnabled: [{
|
|
6086
6751
|
type: Input
|
|
6087
6752
|
}], areMentionsEnabled: [{
|
|
@@ -6104,8 +6769,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImpor
|
|
|
6104
6769
|
type: Input
|
|
6105
6770
|
}], displaySendButton: [{
|
|
6106
6771
|
type: Input
|
|
6772
|
+
}], displayVoiceRecordingButton: [{
|
|
6773
|
+
type: Input
|
|
6107
6774
|
}], messageUpdate: [{
|
|
6108
6775
|
type: Output
|
|
6776
|
+
}], voiceRecorderRef: [{
|
|
6777
|
+
type: ContentChild,
|
|
6778
|
+
args: [TemplateRef]
|
|
6109
6779
|
}], class: [{
|
|
6110
6780
|
type: HostBinding
|
|
6111
6781
|
}], fileInput: [{
|
|
@@ -6965,7 +7635,6 @@ class MessageListComponent {
|
|
|
6965
7635
|
this.isLatestMessageInList = true;
|
|
6966
7636
|
this.parsedDates = new Map();
|
|
6967
7637
|
this.isViewInited = false;
|
|
6968
|
-
this.isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
|
6969
7638
|
this.forceRepaintSubject = new Subject();
|
|
6970
7639
|
this.scrollPosition$ = new BehaviorSubject('bottom');
|
|
6971
7640
|
this.messageNotificationJumpClicked = () => {
|
|
@@ -7218,7 +7887,7 @@ class MessageListComponent {
|
|
|
7218
7887
|
scrollToBottom() {
|
|
7219
7888
|
this.scrollContainer.nativeElement.scrollTop =
|
|
7220
7889
|
this.scrollContainer.nativeElement.scrollHeight + 0.1;
|
|
7221
|
-
if (
|
|
7890
|
+
if (isSafari) {
|
|
7222
7891
|
this.forceRepaintSubject.next();
|
|
7223
7892
|
}
|
|
7224
7893
|
}
|
|
@@ -7343,7 +8012,7 @@ class MessageListComponent {
|
|
|
7343
8012
|
(((_a = messageToAlignTo === null || messageToAlignTo === void 0 ? void 0 : messageToAlignTo.getBoundingClientRect()) === null || _a === void 0 ? void 0 : _a.top) || 0) -
|
|
7344
8013
|
(this.anchorMessageTopOffset || 0);
|
|
7345
8014
|
this.anchorMessageTopOffset = undefined;
|
|
7346
|
-
if (
|
|
8015
|
+
if (isSafari) {
|
|
7347
8016
|
this.forceRepaintSubject.next();
|
|
7348
8017
|
}
|
|
7349
8018
|
}
|
|
@@ -7547,7 +8216,7 @@ class MessageListComponent {
|
|
|
7547
8216
|
}
|
|
7548
8217
|
}
|
|
7549
8218
|
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 });
|
|
7550
|
-
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 });
|
|
8219
|
+
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 });
|
|
7551
8220
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: MessageListComponent, decorators: [{
|
|
7552
8221
|
type: Component,
|
|
7553
8222
|
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" }]
|
|
@@ -7636,6 +8305,50 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImpor
|
|
|
7636
8305
|
}]
|
|
7637
8306
|
}] });
|
|
7638
8307
|
|
|
8308
|
+
class IconModule {
|
|
8309
|
+
}
|
|
8310
|
+
IconModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: IconModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
8311
|
+
IconModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "15.0.4", ngImport: i0, type: IconModule, declarations: [IconComponent,
|
|
8312
|
+
IconPlaceholderComponent,
|
|
8313
|
+
LoadingIndicatorComponent,
|
|
8314
|
+
LoadingIndicatorPlaceholderComponent], imports: [CommonModule], exports: [IconComponent,
|
|
8315
|
+
IconPlaceholderComponent,
|
|
8316
|
+
LoadingIndicatorComponent,
|
|
8317
|
+
LoadingIndicatorPlaceholderComponent] });
|
|
8318
|
+
IconModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: IconModule, imports: [CommonModule] });
|
|
8319
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: IconModule, decorators: [{
|
|
8320
|
+
type: NgModule,
|
|
8321
|
+
args: [{
|
|
8322
|
+
declarations: [
|
|
8323
|
+
IconComponent,
|
|
8324
|
+
IconPlaceholderComponent,
|
|
8325
|
+
LoadingIndicatorComponent,
|
|
8326
|
+
LoadingIndicatorPlaceholderComponent,
|
|
8327
|
+
],
|
|
8328
|
+
imports: [CommonModule],
|
|
8329
|
+
exports: [
|
|
8330
|
+
IconComponent,
|
|
8331
|
+
IconPlaceholderComponent,
|
|
8332
|
+
LoadingIndicatorComponent,
|
|
8333
|
+
LoadingIndicatorPlaceholderComponent,
|
|
8334
|
+
],
|
|
8335
|
+
}]
|
|
8336
|
+
}] });
|
|
8337
|
+
|
|
8338
|
+
class VoiceRecordingModule {
|
|
8339
|
+
}
|
|
8340
|
+
VoiceRecordingModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: VoiceRecordingModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
8341
|
+
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] });
|
|
8342
|
+
VoiceRecordingModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: VoiceRecordingModule, imports: [CommonModule, IconModule, TranslateModule] });
|
|
8343
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: VoiceRecordingModule, decorators: [{
|
|
8344
|
+
type: NgModule,
|
|
8345
|
+
args: [{
|
|
8346
|
+
declarations: [VoiceRecordingComponent, VoiceRecordingWavebarComponent],
|
|
8347
|
+
imports: [CommonModule, IconModule, TranslateModule],
|
|
8348
|
+
exports: [VoiceRecordingComponent, VoiceRecordingWavebarComponent],
|
|
8349
|
+
}]
|
|
8350
|
+
}] });
|
|
8351
|
+
|
|
7639
8352
|
class StreamChatModule {
|
|
7640
8353
|
}
|
|
7641
8354
|
StreamChatModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: StreamChatModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
@@ -7646,8 +8359,6 @@ StreamChatModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", versio
|
|
|
7646
8359
|
MessageComponent,
|
|
7647
8360
|
MessageInputComponent,
|
|
7648
8361
|
MessageListComponent,
|
|
7649
|
-
LoadingIndicatorComponent,
|
|
7650
|
-
IconComponent,
|
|
7651
8362
|
MessageActionsBoxComponent,
|
|
7652
8363
|
AttachmentListComponent,
|
|
7653
8364
|
MessageReactionsComponent,
|
|
@@ -7657,25 +8368,21 @@ StreamChatModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", versio
|
|
|
7657
8368
|
ModalComponent,
|
|
7658
8369
|
TextareaDirective,
|
|
7659
8370
|
ThreadComponent,
|
|
7660
|
-
IconPlaceholderComponent,
|
|
7661
|
-
LoadingIndicatorPlaceholderComponent,
|
|
7662
8371
|
MessageBouncePromptComponent,
|
|
7663
|
-
VoiceRecordingComponent,
|
|
7664
|
-
VoiceRecordingWavebarComponent,
|
|
7665
8372
|
MessageReactionsSelectorComponent,
|
|
7666
8373
|
UserListComponent,
|
|
7667
8374
|
PaginatedListComponent], imports: [CommonModule,
|
|
7668
8375
|
NgxFloatUiModule,
|
|
7669
8376
|
StreamAvatarModule,
|
|
7670
|
-
TranslateModule
|
|
8377
|
+
TranslateModule,
|
|
8378
|
+
VoiceRecordingModule,
|
|
8379
|
+
IconModule], exports: [ChannelComponent,
|
|
7671
8380
|
ChannelHeaderComponent,
|
|
7672
8381
|
ChannelListComponent,
|
|
7673
8382
|
ChannelPreviewComponent,
|
|
7674
8383
|
MessageComponent,
|
|
7675
8384
|
MessageInputComponent,
|
|
7676
8385
|
MessageListComponent,
|
|
7677
|
-
LoadingIndicatorComponent,
|
|
7678
|
-
IconComponent,
|
|
7679
8386
|
MessageActionsBoxComponent,
|
|
7680
8387
|
AttachmentListComponent,
|
|
7681
8388
|
MessageReactionsComponent,
|
|
@@ -7685,18 +8392,20 @@ StreamChatModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", versio
|
|
|
7685
8392
|
ModalComponent,
|
|
7686
8393
|
StreamAvatarModule,
|
|
7687
8394
|
ThreadComponent,
|
|
7688
|
-
IconPlaceholderComponent,
|
|
7689
|
-
LoadingIndicatorPlaceholderComponent,
|
|
7690
8395
|
MessageBouncePromptComponent,
|
|
7691
|
-
|
|
7692
|
-
VoiceRecordingWavebarComponent,
|
|
8396
|
+
VoiceRecordingModule,
|
|
7693
8397
|
MessageReactionsSelectorComponent,
|
|
7694
8398
|
UserListComponent,
|
|
7695
|
-
PaginatedListComponent
|
|
7696
|
-
|
|
8399
|
+
PaginatedListComponent,
|
|
8400
|
+
IconModule] });
|
|
8401
|
+
StreamChatModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: StreamChatModule, providers: [VoiceRecorderService], imports: [CommonModule,
|
|
7697
8402
|
NgxFloatUiModule,
|
|
7698
8403
|
StreamAvatarModule,
|
|
7699
|
-
TranslateModule,
|
|
8404
|
+
TranslateModule,
|
|
8405
|
+
VoiceRecordingModule,
|
|
8406
|
+
IconModule, StreamAvatarModule,
|
|
8407
|
+
VoiceRecordingModule,
|
|
8408
|
+
IconModule] });
|
|
7700
8409
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: StreamChatModule, decorators: [{
|
|
7701
8410
|
type: NgModule,
|
|
7702
8411
|
args: [{
|
|
@@ -7708,8 +8417,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImpor
|
|
|
7708
8417
|
MessageComponent,
|
|
7709
8418
|
MessageInputComponent,
|
|
7710
8419
|
MessageListComponent,
|
|
7711
|
-
LoadingIndicatorComponent,
|
|
7712
|
-
IconComponent,
|
|
7713
8420
|
MessageActionsBoxComponent,
|
|
7714
8421
|
AttachmentListComponent,
|
|
7715
8422
|
MessageReactionsComponent,
|
|
@@ -7719,11 +8426,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImpor
|
|
|
7719
8426
|
ModalComponent,
|
|
7720
8427
|
TextareaDirective,
|
|
7721
8428
|
ThreadComponent,
|
|
7722
|
-
IconPlaceholderComponent,
|
|
7723
|
-
LoadingIndicatorPlaceholderComponent,
|
|
7724
8429
|
MessageBouncePromptComponent,
|
|
7725
|
-
VoiceRecordingComponent,
|
|
7726
|
-
VoiceRecordingWavebarComponent,
|
|
7727
8430
|
MessageReactionsSelectorComponent,
|
|
7728
8431
|
UserListComponent,
|
|
7729
8432
|
PaginatedListComponent,
|
|
@@ -7733,6 +8436,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImpor
|
|
|
7733
8436
|
NgxFloatUiModule,
|
|
7734
8437
|
StreamAvatarModule,
|
|
7735
8438
|
TranslateModule,
|
|
8439
|
+
VoiceRecordingModule,
|
|
8440
|
+
IconModule,
|
|
7736
8441
|
],
|
|
7737
8442
|
exports: [
|
|
7738
8443
|
ChannelComponent,
|
|
@@ -7742,8 +8447,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImpor
|
|
|
7742
8447
|
MessageComponent,
|
|
7743
8448
|
MessageInputComponent,
|
|
7744
8449
|
MessageListComponent,
|
|
7745
|
-
LoadingIndicatorComponent,
|
|
7746
|
-
IconComponent,
|
|
7747
8450
|
MessageActionsBoxComponent,
|
|
7748
8451
|
AttachmentListComponent,
|
|
7749
8452
|
MessageReactionsComponent,
|
|
@@ -7753,15 +8456,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImpor
|
|
|
7753
8456
|
ModalComponent,
|
|
7754
8457
|
StreamAvatarModule,
|
|
7755
8458
|
ThreadComponent,
|
|
7756
|
-
IconPlaceholderComponent,
|
|
7757
|
-
LoadingIndicatorPlaceholderComponent,
|
|
7758
8459
|
MessageBouncePromptComponent,
|
|
7759
|
-
|
|
7760
|
-
VoiceRecordingWavebarComponent,
|
|
8460
|
+
VoiceRecordingModule,
|
|
7761
8461
|
MessageReactionsSelectorComponent,
|
|
7762
8462
|
UserListComponent,
|
|
7763
8463
|
PaginatedListComponent,
|
|
8464
|
+
IconModule,
|
|
7764
8465
|
],
|
|
8466
|
+
providers: [VoiceRecorderService],
|
|
7765
8467
|
}]
|
|
7766
8468
|
}] });
|
|
7767
8469
|
|
|
@@ -7815,6 +8517,198 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImpor
|
|
|
7815
8517
|
}]
|
|
7816
8518
|
}] });
|
|
7817
8519
|
|
|
8520
|
+
/**
|
|
8521
|
+
* The `VoiceRecorderWavebarComponent` displays the amplitudes of the recording while the recoding is in progress
|
|
8522
|
+
*/
|
|
8523
|
+
class VoiceRecorderWavebarComponent {
|
|
8524
|
+
constructor(amplitudeRecorder, audioRecorder) {
|
|
8525
|
+
this.amplitudeRecorder = amplitudeRecorder;
|
|
8526
|
+
this.audioRecorder = audioRecorder;
|
|
8527
|
+
this.isLongerThanOneHour = false;
|
|
8528
|
+
this.amplitudes$ = this.amplitudeRecorder.amplitudes$;
|
|
8529
|
+
this.formattedDuration = formatDuration(this.audioRecorder.durationMs / 1000);
|
|
8530
|
+
this.durationComputeInterval = setInterval(() => {
|
|
8531
|
+
this.isLongerThanOneHour = this.audioRecorder.durationMs / 1000 > 3600;
|
|
8532
|
+
this.formattedDuration = formatDuration(this.audioRecorder.durationMs / 1000);
|
|
8533
|
+
}, 1000);
|
|
8534
|
+
}
|
|
8535
|
+
trackByIndex(i) {
|
|
8536
|
+
return i;
|
|
8537
|
+
}
|
|
8538
|
+
ngOnDestroy() {
|
|
8539
|
+
clearInterval(this.durationComputeInterval);
|
|
8540
|
+
}
|
|
8541
|
+
}
|
|
8542
|
+
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 });
|
|
8543
|
+
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" }] });
|
|
8544
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: VoiceRecorderWavebarComponent, decorators: [{
|
|
8545
|
+
type: Component,
|
|
8546
|
+
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" }]
|
|
8547
|
+
}], ctorParameters: function () { return [{ type: AmplitudeRecorderService }, { type: AudioRecorderService }]; } });
|
|
8548
|
+
|
|
8549
|
+
/**
|
|
8550
|
+
* The `VoiceRecorderComponent` makes it possible to record audio, and then upload it as a voice recording attachment
|
|
8551
|
+
*/
|
|
8552
|
+
class VoiceRecorderComponent {
|
|
8553
|
+
constructor(recorder) {
|
|
8554
|
+
this.recorder = recorder;
|
|
8555
|
+
this.recordState = MediaRecordingState.STOPPED;
|
|
8556
|
+
this.isLoading = false;
|
|
8557
|
+
this.MediaRecordingState = MediaRecordingState;
|
|
8558
|
+
this.subscriptions = [];
|
|
8559
|
+
}
|
|
8560
|
+
ngOnInit() {
|
|
8561
|
+
this.subscriptions.push(this.recorder.recordingState$.subscribe((s) => {
|
|
8562
|
+
var _a;
|
|
8563
|
+
this.recordState = s;
|
|
8564
|
+
if (this.recordState === MediaRecordingState.ERROR) {
|
|
8565
|
+
(_a = this.voiceRecorderService) === null || _a === void 0 ? void 0 : _a.isRecorderVisible$.next(false);
|
|
8566
|
+
}
|
|
8567
|
+
}));
|
|
8568
|
+
}
|
|
8569
|
+
ngOnChanges(changes) {
|
|
8570
|
+
var _a;
|
|
8571
|
+
if (changes.voiceRecorderService && this.voiceRecorderService) {
|
|
8572
|
+
this.isVisibleSubscription =
|
|
8573
|
+
this.voiceRecorderService.isRecorderVisible$.subscribe((isVisible) => {
|
|
8574
|
+
if (!isVisible) {
|
|
8575
|
+
this.recording = undefined;
|
|
8576
|
+
this.isLoading = false;
|
|
8577
|
+
}
|
|
8578
|
+
});
|
|
8579
|
+
}
|
|
8580
|
+
else {
|
|
8581
|
+
(_a = this.isVisibleSubscription) === null || _a === void 0 ? void 0 : _a.unsubscribe();
|
|
8582
|
+
}
|
|
8583
|
+
}
|
|
8584
|
+
ngOnDestroy() {
|
|
8585
|
+
this.subscriptions.forEach((s) => s.unsubscribe());
|
|
8586
|
+
}
|
|
8587
|
+
cancel() {
|
|
8588
|
+
var _a;
|
|
8589
|
+
if (this.recording) {
|
|
8590
|
+
this.recording = undefined;
|
|
8591
|
+
}
|
|
8592
|
+
else {
|
|
8593
|
+
void this.recorder.stop({ cancel: true });
|
|
8594
|
+
}
|
|
8595
|
+
(_a = this.voiceRecorderService) === null || _a === void 0 ? void 0 : _a.isRecorderVisible$.next(false);
|
|
8596
|
+
}
|
|
8597
|
+
stop() {
|
|
8598
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
8599
|
+
this.recording = yield this.recorder.stop();
|
|
8600
|
+
});
|
|
8601
|
+
}
|
|
8602
|
+
pause() {
|
|
8603
|
+
this.recorder.pause();
|
|
8604
|
+
}
|
|
8605
|
+
resume() {
|
|
8606
|
+
this.recorder.resume();
|
|
8607
|
+
}
|
|
8608
|
+
uploadRecording() {
|
|
8609
|
+
var _a;
|
|
8610
|
+
if (!this.recording) {
|
|
8611
|
+
return;
|
|
8612
|
+
}
|
|
8613
|
+
this.isLoading = true;
|
|
8614
|
+
(_a = this.voiceRecorderService) === null || _a === void 0 ? void 0 : _a.recording$.next(this.recording);
|
|
8615
|
+
}
|
|
8616
|
+
}
|
|
8617
|
+
VoiceRecorderComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: VoiceRecorderComponent, deps: [{ token: AudioRecorderService }], target: i0.ɵɵFactoryTarget.Component });
|
|
8618
|
+
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" }] });
|
|
8619
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: VoiceRecorderComponent, decorators: [{
|
|
8620
|
+
type: Component,
|
|
8621
|
+
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" }]
|
|
8622
|
+
}], ctorParameters: function () { return [{ type: AudioRecorderService }]; }, propDecorators: { voiceRecorderService: [{
|
|
8623
|
+
type: Input
|
|
8624
|
+
}] } });
|
|
8625
|
+
|
|
8626
|
+
class VoiceRecorderModule {
|
|
8627
|
+
}
|
|
8628
|
+
VoiceRecorderModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: VoiceRecorderModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
8629
|
+
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] });
|
|
8630
|
+
VoiceRecorderModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: VoiceRecorderModule, providers: [
|
|
8631
|
+
AudioRecorderService,
|
|
8632
|
+
TranscoderService,
|
|
8633
|
+
AmplitudeRecorderService,
|
|
8634
|
+
], imports: [CommonModule, VoiceRecordingModule, IconModule, TranslateModule] });
|
|
8635
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: VoiceRecorderModule, decorators: [{
|
|
8636
|
+
type: NgModule,
|
|
8637
|
+
args: [{
|
|
8638
|
+
declarations: [VoiceRecorderComponent, VoiceRecorderWavebarComponent],
|
|
8639
|
+
imports: [CommonModule, VoiceRecordingModule, IconModule, TranslateModule],
|
|
8640
|
+
exports: [VoiceRecorderComponent, VoiceRecorderWavebarComponent],
|
|
8641
|
+
providers: [
|
|
8642
|
+
AudioRecorderService,
|
|
8643
|
+
TranscoderService,
|
|
8644
|
+
AmplitudeRecorderService,
|
|
8645
|
+
],
|
|
8646
|
+
}]
|
|
8647
|
+
}] });
|
|
8648
|
+
|
|
8649
|
+
const ENCODING_BIT_RATE = 128; // kbps;
|
|
8650
|
+
const COUNT_SAMPLES_PER_ENCODED_BLOCK = 1152;
|
|
8651
|
+
const SAMPLE_RATE = 16000;
|
|
8652
|
+
const readFileAsArrayBuffer = (blob) => new Promise((resolve, reject) => {
|
|
8653
|
+
const blobReader = new FileReader();
|
|
8654
|
+
blobReader.onload = () => {
|
|
8655
|
+
resolve(blobReader.result);
|
|
8656
|
+
};
|
|
8657
|
+
blobReader.onerror = () => {
|
|
8658
|
+
reject(blobReader.error);
|
|
8659
|
+
};
|
|
8660
|
+
blobReader.readAsArrayBuffer(blob);
|
|
8661
|
+
});
|
|
8662
|
+
const toAudioBuffer = (blob) => __awaiter(void 0, void 0, void 0, function* () {
|
|
8663
|
+
const audioCtx = new AudioContext();
|
|
8664
|
+
const arrayBuffer = yield readFileAsArrayBuffer(blob);
|
|
8665
|
+
const decodedData = yield audioCtx.decodeAudioData(arrayBuffer);
|
|
8666
|
+
if (audioCtx.state !== 'closed')
|
|
8667
|
+
yield audioCtx.close();
|
|
8668
|
+
return decodedData;
|
|
8669
|
+
});
|
|
8670
|
+
const renderAudio = (audioBuffer, sampleRate) => __awaiter(void 0, void 0, void 0, function* () {
|
|
8671
|
+
const offlineAudioCtx = new OfflineAudioContext(audioBuffer.numberOfChannels, audioBuffer.duration * sampleRate, sampleRate);
|
|
8672
|
+
const source = offlineAudioCtx.createBufferSource();
|
|
8673
|
+
source.buffer = audioBuffer;
|
|
8674
|
+
source.connect(offlineAudioCtx.destination);
|
|
8675
|
+
source.start();
|
|
8676
|
+
return yield offlineAudioCtx.startRendering();
|
|
8677
|
+
});
|
|
8678
|
+
const float32ArrayToInt16Array = (float32Arr) => {
|
|
8679
|
+
const int16Arr = new Int16Array(float32Arr.length);
|
|
8680
|
+
for (let i = 0; i < float32Arr.length; i++) {
|
|
8681
|
+
const float32Value = float32Arr[i];
|
|
8682
|
+
// Clamp the float value between -1 and 1
|
|
8683
|
+
const clampedValue = Math.max(-1, Math.min(1, float32Value));
|
|
8684
|
+
// Convert the float value to a signed 16-bit integer
|
|
8685
|
+
int16Arr[i] = Math.round(clampedValue * 32767);
|
|
8686
|
+
}
|
|
8687
|
+
return int16Arr;
|
|
8688
|
+
};
|
|
8689
|
+
const splitDataByChannel = (audioBuffer) => Array.from({ length: audioBuffer.numberOfChannels }, (_, i) => audioBuffer.getChannelData(i)).map(float32ArrayToInt16Array);
|
|
8690
|
+
/* 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 */
|
|
8691
|
+
function encodeWebmToMp3(blob, lameJs) {
|
|
8692
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
8693
|
+
const audioBuffer = yield renderAudio(yield toAudioBuffer(blob), SAMPLE_RATE);
|
|
8694
|
+
const channelCount = audioBuffer.numberOfChannels;
|
|
8695
|
+
const dataByChannel = splitDataByChannel(audioBuffer);
|
|
8696
|
+
const mp3Encoder = new lameJs.Mp3Encoder(channelCount, SAMPLE_RATE, ENCODING_BIT_RATE);
|
|
8697
|
+
const dataBuffer = [];
|
|
8698
|
+
let remaining = dataByChannel[0].length;
|
|
8699
|
+
for (let i = 0; remaining >= COUNT_SAMPLES_PER_ENCODED_BLOCK; i += COUNT_SAMPLES_PER_ENCODED_BLOCK) {
|
|
8700
|
+
const [leftChannelBlock, rightChannelBlock] = dataByChannel.map((channel) => channel.subarray(i, i + COUNT_SAMPLES_PER_ENCODED_BLOCK));
|
|
8701
|
+
dataBuffer.push(new Int8Array(mp3Encoder.encodeBuffer(leftChannelBlock, rightChannelBlock)));
|
|
8702
|
+
remaining -= COUNT_SAMPLES_PER_ENCODED_BLOCK;
|
|
8703
|
+
}
|
|
8704
|
+
const lastBlock = mp3Encoder.flush();
|
|
8705
|
+
if (lastBlock.length)
|
|
8706
|
+
dataBuffer.push(new Int8Array(lastBlock));
|
|
8707
|
+
return new Blob(dataBuffer, { type: 'audio/mp3;sbu_type=voice' });
|
|
8708
|
+
});
|
|
8709
|
+
}
|
|
8710
|
+
/* 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 */
|
|
8711
|
+
|
|
7818
8712
|
/*
|
|
7819
8713
|
* Public API Surface of stream-chat-angular
|
|
7820
8714
|
*/
|
|
@@ -7823,5 +8717,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImpor
|
|
|
7823
8717
|
* Generated bundle index. Do not edit.
|
|
7824
8718
|
*/
|
|
7825
8719
|
|
|
7826
|
-
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 };
|
|
8720
|
+
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 };
|
|
7827
8721
|
//# sourceMappingURL=stream-chat-angular.mjs.map
|