stream-chat-angular 3.0.0-beta.8 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -12
- package/assets/version.d.ts +1 -1
- package/bundles/stream-chat-angular.umd.js +89 -35
- package/bundles/stream-chat-angular.umd.js.map +1 -1
- package/esm2015/assets/version.js +2 -2
- package/esm2015/lib/attachment-list/attachment-list.component.js +9 -2
- package/esm2015/lib/attachment-preview-list/attachment-preview-list.component.js +2 -2
- package/esm2015/lib/attachment.service.js +20 -5
- package/esm2015/lib/channel.service.js +42 -17
- package/esm2015/lib/message/message.component.js +7 -2
- package/esm2015/lib/message-input/message-input.component.js +8 -3
- package/esm2015/lib/types.js +1 -1
- package/fesm2015/stream-chat-angular.js +83 -26
- package/fesm2015/stream-chat-angular.js.map +1 -1
- package/lib/attachment-list/attachment-list.component.d.ts +1 -0
- package/lib/channel.service.d.ts +28 -2
- package/lib/types.d.ts +1 -1
- package/package.json +2 -2
- package/src/assets/styles/css/index.css +1 -1
- package/src/assets/styles/css/index.css.map +1 -1
- package/src/assets/styles/scss/ActionsBox.scss +2 -2
- package/src/assets/styles/scss/Attachment.scss +1 -1
- package/src/assets/styles/scss/ChannelList.scss +6 -0
- package/src/assets/styles/scss/ChannelSearch.scss +12 -1
- package/src/assets/styles/scss/Message.scss +104 -93
- package/src/assets/styles/scss/MessageInput.scss +8 -2
- package/src/assets/styles/scss/MessageInputFlat.scss +6 -0
- package/src/assets/styles/scss/Thread.scss +15 -2
- package/src/assets/styles/scss/VirtualMessage.scss +1 -0
- package/src/assets/styles/scss/_base.scss +4 -0
- package/src/assets/version.ts +1 -1
|
@@ -17,7 +17,7 @@ import transliterate from '@stream-io/transliterate';
|
|
|
17
17
|
import * as i7 from 'angular-mentions';
|
|
18
18
|
import { MentionModule } from 'angular-mentions';
|
|
19
19
|
|
|
20
|
-
const version = '3.0.
|
|
20
|
+
const version = '3.0.1';
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* The `NotificationService` can be used to add or remove notifications. By default the [`NotificationList`](../components/NotificationListComponent.mdx) component displays the currently active notifications.
|
|
@@ -253,7 +253,7 @@ const getReadBy = (message, channel) => {
|
|
|
253
253
|
};
|
|
254
254
|
|
|
255
255
|
/**
|
|
256
|
-
* The `ChannelService` provides data and interaction for the channel list and message list.
|
|
256
|
+
* The `ChannelService` provides data and interaction for the channel list and message list.
|
|
257
257
|
*/
|
|
258
258
|
class ChannelService {
|
|
259
259
|
constructor(chatClientService, ngZone) {
|
|
@@ -346,6 +346,21 @@ class ChannelService {
|
|
|
346
346
|
this.activeThreadMessagesSubject.next([]);
|
|
347
347
|
this.messageToQuoteSubject.next(undefined);
|
|
348
348
|
}
|
|
349
|
+
/**
|
|
350
|
+
* Deselects the currently active (if any) channel
|
|
351
|
+
*/
|
|
352
|
+
deselectActiveChannel() {
|
|
353
|
+
const activeChannel = this.activeChannelSubject.getValue();
|
|
354
|
+
if (!activeChannel) {
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
this.activeChannelMessagesSubject.next([]);
|
|
358
|
+
this.activeChannelSubject.next(undefined);
|
|
359
|
+
this.activeParentMessageIdSubject.next(undefined);
|
|
360
|
+
this.activeThreadMessagesSubject.next([]);
|
|
361
|
+
this.latestMessageDateByUserByChannelsSubject.next({});
|
|
362
|
+
this.selectMessageToQuote(undefined);
|
|
363
|
+
}
|
|
349
364
|
/**
|
|
350
365
|
* Sets the given `message` as an active parent message. If `undefined` is provided, it will deleselect the current parent message.
|
|
351
366
|
* @param message
|
|
@@ -416,8 +431,10 @@ class ChannelService {
|
|
|
416
431
|
* @param filters
|
|
417
432
|
* @param sort
|
|
418
433
|
* @param options
|
|
434
|
+
* @param shouldSetActiveChannel Decides if the first channel in the result should be made as an active channel, or no channel should be marked as active
|
|
435
|
+
* @returns the list of channels found by the query
|
|
419
436
|
*/
|
|
420
|
-
init(filters, sort, options) {
|
|
437
|
+
init(filters, sort, options, shouldSetActiveChannel = true) {
|
|
421
438
|
return __awaiter(this, void 0, void 0, function* () {
|
|
422
439
|
this.filters = filters;
|
|
423
440
|
this.options = options || {
|
|
@@ -429,21 +446,17 @@ class ChannelService {
|
|
|
429
446
|
message_limit: this.messagePageSize,
|
|
430
447
|
};
|
|
431
448
|
this.sort = sort || { last_message_at: -1, updated_at: -1 };
|
|
432
|
-
yield this.queryChannels();
|
|
449
|
+
const result = yield this.queryChannels(shouldSetActiveChannel);
|
|
433
450
|
this.chatClientService.events$.subscribe((notification) => void this.handleNotification(notification));
|
|
451
|
+
return result;
|
|
434
452
|
});
|
|
435
453
|
}
|
|
436
454
|
/**
|
|
437
455
|
* Resets the `activeChannel$`, `channels$` and `activeChannelMessages$` Observables. Useful when disconnecting a chat user, use in combination with [`disconnectUser`](./ChatClientService.mdx/#disconnectuser).
|
|
438
456
|
*/
|
|
439
457
|
reset() {
|
|
440
|
-
this.
|
|
441
|
-
this.activeChannelSubject.next(undefined);
|
|
442
|
-
this.activeParentMessageIdSubject.next(undefined);
|
|
443
|
-
this.activeThreadMessagesSubject.next([]);
|
|
458
|
+
this.deselectActiveChannel();
|
|
444
459
|
this.channelsSubject.next(undefined);
|
|
445
|
-
this.latestMessageDateByUserByChannelsSubject.next({});
|
|
446
|
-
this.selectMessageToQuote(undefined);
|
|
447
460
|
}
|
|
448
461
|
/**
|
|
449
462
|
* Loads the next page of channels. The page size can be set in the [query option](https://getstream.io/chat/docs/javascript/query_channels/?language=javascript#query-options) object.
|
|
@@ -451,7 +464,7 @@ class ChannelService {
|
|
|
451
464
|
loadMoreChannels() {
|
|
452
465
|
return __awaiter(this, void 0, void 0, function* () {
|
|
453
466
|
this.options.offset = this.channels.length;
|
|
454
|
-
yield this.queryChannels();
|
|
467
|
+
yield this.queryChannels(false);
|
|
455
468
|
});
|
|
456
469
|
}
|
|
457
470
|
/**
|
|
@@ -535,8 +548,12 @@ class ChannelService {
|
|
|
535
548
|
const result = [];
|
|
536
549
|
const channel = this.activeChannelSubject.getValue();
|
|
537
550
|
const uploadResults = yield Promise.allSettled(uploads.map((upload) => upload.type === 'image'
|
|
538
|
-
?
|
|
539
|
-
|
|
551
|
+
? this.customImageUploadRequest
|
|
552
|
+
? this.customImageUploadRequest(upload.file, channel)
|
|
553
|
+
: channel.sendImage(upload.file)
|
|
554
|
+
: this.customFileUploadRequest
|
|
555
|
+
? this.customFileUploadRequest(upload.file, channel)
|
|
556
|
+
: channel.sendFile(upload.file)));
|
|
540
557
|
uploadResults.forEach((uploadResult, i) => {
|
|
541
558
|
const file = uploads[i].file;
|
|
542
559
|
const type = uploads[i].type;
|
|
@@ -563,8 +580,12 @@ class ChannelService {
|
|
|
563
580
|
return __awaiter(this, void 0, void 0, function* () {
|
|
564
581
|
const channel = this.activeChannelSubject.getValue();
|
|
565
582
|
yield (attachmentUpload.type === 'image'
|
|
566
|
-
?
|
|
567
|
-
|
|
583
|
+
? this.customImageDeleteRequest
|
|
584
|
+
? this.customImageDeleteRequest(attachmentUpload.url, channel)
|
|
585
|
+
: channel.deleteImage(attachmentUpload.url)
|
|
586
|
+
: this.customFileDeleteRequest
|
|
587
|
+
? this.customFileDeleteRequest(attachmentUpload.url, channel)
|
|
588
|
+
: channel.deleteFile(attachmentUpload.url));
|
|
568
589
|
});
|
|
569
590
|
}
|
|
570
591
|
/**
|
|
@@ -865,20 +886,24 @@ class ChannelService {
|
|
|
865
886
|
this.activeChannelSubscriptions.forEach((s) => s.unsubscribe());
|
|
866
887
|
this.activeChannelSubscriptions = [];
|
|
867
888
|
}
|
|
868
|
-
queryChannels() {
|
|
889
|
+
queryChannels(shouldSetActiveChannel) {
|
|
869
890
|
return __awaiter(this, void 0, void 0, function* () {
|
|
870
891
|
try {
|
|
871
892
|
const channels = yield this.chatClientService.chatClient.queryChannels(this.filters, this.sort, this.options);
|
|
872
893
|
channels.forEach((c) => this.watchForChannelEvents(c));
|
|
873
894
|
const prevChannels = this.channelsSubject.getValue() || [];
|
|
874
895
|
this.channelsSubject.next([...prevChannels, ...channels]);
|
|
875
|
-
if (channels.length > 0 &&
|
|
896
|
+
if (channels.length > 0 &&
|
|
897
|
+
!this.activeChannelSubject.getValue() &&
|
|
898
|
+
shouldSetActiveChannel) {
|
|
876
899
|
this.setAsActiveChannel(channels[0]);
|
|
877
900
|
}
|
|
878
901
|
this.hasMoreChannelsSubject.next(channels.length >= this.options.limit);
|
|
902
|
+
return channels;
|
|
879
903
|
}
|
|
880
904
|
catch (error) {
|
|
881
905
|
this.channelsSubject.error(error);
|
|
906
|
+
throw error;
|
|
882
907
|
}
|
|
883
908
|
});
|
|
884
909
|
}
|
|
@@ -1244,10 +1269,14 @@ class AttachmentService {
|
|
|
1244
1269
|
}
|
|
1245
1270
|
const imageFiles = [];
|
|
1246
1271
|
const dataFiles = [];
|
|
1272
|
+
const videoFiles = [];
|
|
1247
1273
|
Array.from(fileList).forEach((file) => {
|
|
1248
1274
|
if (isImageFile(file)) {
|
|
1249
1275
|
imageFiles.push(file);
|
|
1250
1276
|
}
|
|
1277
|
+
else if (file.type.startsWith('video/')) {
|
|
1278
|
+
videoFiles.push(file);
|
|
1279
|
+
}
|
|
1251
1280
|
else {
|
|
1252
1281
|
dataFiles.push(file);
|
|
1253
1282
|
}
|
|
@@ -1259,6 +1288,11 @@ class AttachmentService {
|
|
|
1259
1288
|
state: 'uploading',
|
|
1260
1289
|
type: 'image',
|
|
1261
1290
|
})),
|
|
1291
|
+
...videoFiles.map((file) => ({
|
|
1292
|
+
file,
|
|
1293
|
+
state: 'uploading',
|
|
1294
|
+
type: 'video',
|
|
1295
|
+
})),
|
|
1262
1296
|
...dataFiles.map((file) => ({
|
|
1263
1297
|
file,
|
|
1264
1298
|
state: 'uploading',
|
|
@@ -1296,19 +1330,25 @@ class AttachmentService {
|
|
|
1296
1330
|
deleteAttachment(upload) {
|
|
1297
1331
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1298
1332
|
const attachmentUploads = this.attachmentUploadsSubject.getValue();
|
|
1333
|
+
let result;
|
|
1299
1334
|
if (upload.state === 'success') {
|
|
1300
1335
|
try {
|
|
1301
1336
|
yield this.channelService.deleteAttachment(upload);
|
|
1302
|
-
attachmentUploads
|
|
1337
|
+
result = [...attachmentUploads];
|
|
1338
|
+
const index = attachmentUploads.indexOf(upload);
|
|
1339
|
+
result.splice(index, 1);
|
|
1303
1340
|
}
|
|
1304
1341
|
catch (error) {
|
|
1342
|
+
result = attachmentUploads;
|
|
1305
1343
|
this.notificationService.addTemporaryNotification('streamChat.Error deleting attachment');
|
|
1306
1344
|
}
|
|
1307
1345
|
}
|
|
1308
1346
|
else {
|
|
1309
|
-
attachmentUploads
|
|
1347
|
+
result = [...attachmentUploads];
|
|
1348
|
+
const index = attachmentUploads.indexOf(upload);
|
|
1349
|
+
result.splice(index, 1);
|
|
1310
1350
|
}
|
|
1311
|
-
this.attachmentUploadsSubject.next([...
|
|
1351
|
+
this.attachmentUploadsSubject.next([...result]);
|
|
1312
1352
|
});
|
|
1313
1353
|
}
|
|
1314
1354
|
/**
|
|
@@ -1390,9 +1430,9 @@ class AttachmentService {
|
|
|
1390
1430
|
}
|
|
1391
1431
|
uploadAttachments(uploads) {
|
|
1392
1432
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1393
|
-
const attachmentUploads = this.attachmentUploadsSubject.getValue();
|
|
1394
1433
|
this.attachmentUploadInProgressCounterSubject.next(this.attachmentUploadInProgressCounterSubject.getValue() + 1);
|
|
1395
1434
|
const result = yield this.channelService.uploadAttachments(uploads);
|
|
1435
|
+
const attachmentUploads = this.attachmentUploadsSubject.getValue();
|
|
1396
1436
|
result.forEach((r) => {
|
|
1397
1437
|
const upload = attachmentUploads.find((upload) => upload.file === r.file);
|
|
1398
1438
|
if (!upload) {
|
|
@@ -2094,6 +2134,7 @@ class AttachmentListComponent {
|
|
|
2094
2134
|
const containsGallery = images.length >= 2;
|
|
2095
2135
|
this.orderedAttachments = [
|
|
2096
2136
|
...(containsGallery ? this.createGallery(images) : images),
|
|
2137
|
+
...this.attachments.filter((a) => this.isVideo(a)),
|
|
2097
2138
|
...this.attachments.filter((a) => this.isFile(a)),
|
|
2098
2139
|
...this.attachments.filter((a) => this.isCard(a)),
|
|
2099
2140
|
];
|
|
@@ -2110,6 +2151,12 @@ class AttachmentListComponent {
|
|
|
2110
2151
|
isGallery(attachment) {
|
|
2111
2152
|
return attachment.type === 'gallery';
|
|
2112
2153
|
}
|
|
2154
|
+
isVideo(attachment) {
|
|
2155
|
+
return (attachment.type === 'video' &&
|
|
2156
|
+
attachment.asset_url &&
|
|
2157
|
+
!attachment.og_scrape_url // links from video share services (such as YouTube or Facebook) are can't be played
|
|
2158
|
+
);
|
|
2159
|
+
}
|
|
2113
2160
|
isCard(attachment) {
|
|
2114
2161
|
return (!attachment.type ||
|
|
2115
2162
|
(attachment.type === 'image' && !this.isImage(attachment)) ||
|
|
@@ -2177,7 +2224,7 @@ class AttachmentListComponent {
|
|
|
2177
2224
|
}
|
|
2178
2225
|
}
|
|
2179
2226
|
AttachmentListComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: AttachmentListComponent, deps: [{ token: CustomTemplatesService }, { token: ImageLoadService }, { token: ChannelService }], target: i0.ɵɵFactoryTarget.Component });
|
|
2180
|
-
AttachmentListComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.5", type: AttachmentListComponent, selector: "stream-attachment-list", inputs: { messageId: "messageId", attachments: "attachments" }, viewQueries: [{ propertyName: "modalContent", first: true, predicate: ["modalContent"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: "<ng-container *ngFor=\"let attachment of orderedAttachments; trackBy: trackById\">\n <div\n data-testclass=\"attachment-container\"\n class=\"str-chat__message-attachment str-chat__message-attachment--{{\n attachment.type\n }}\"\n [class.str-chat__message-attachment--card]=\"isCard(attachment)\"\n [class.str-chat-angular__message-attachment-file-single]=\"\n isFile(attachment)\n \"\n >\n <img\n *ngIf=\"isImage(attachment)\"\n class=\"str-chat__message-attachment--img\"\n data-testclass=\"image\"\n [src]=\"attachment.img_url || attachment.thumb_url || attachment.image_url\"\n [alt]=\"attachment?.fallback\"\n (load)=\"imageLoaded()\"\n (click)=\"openImageModal([attachment])\"\n (keyup.enter)=\"openImageModal([attachment])\"\n />\n <div\n class=\"str-chat__gallery\"\n data-testid=\"image-gallery\"\n *ngIf=\"isGallery(attachment)\"\n [class.str-chat__gallery--square]=\"(attachment?.images)!.length > 3\"\n >\n <ng-container\n *ngFor=\"\n let galleryImage of attachment.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 (click)=\"openImageModal(attachment.images!, index)\"\n (keyup.enter)=\"openImageModal(attachment.images!, index)\"\n >\n <img\n [src]=\"\n galleryImage.img_url ||\n galleryImage.thumb_url ||\n galleryImage.image_url\n \"\n [alt]=\"galleryImage.fallback\"\n (load)=\"imageLoaded()\"\n />\n </button>\n <button\n *ngIf=\"index === 3 && !isLast\"\n class=\"str-chat__gallery-placeholder\"\n data-testclass=\"gallery-image\"\n (click)=\"openImageModal(attachment.images!, index)\"\n (keyup.enter)=\"openImageModal(attachment.images!, index)\"\n [ngStyle]=\"{\n 'background-image':\n 'url(' +\n (galleryImage.img_url ||\n galleryImage.thumb_url ||\n galleryImage.image_url) +\n ')'\n }\"\n >\n <p\n [innerHTML]=\"\n 'streamChat.{{ imageCount }} more'\n | translate: { imageCount: attachment!.images!.length - 4 }\n \"\n ></p>\n </button>\n </ng-container>\n </div>\n <div\n *ngIf=\"isFile(attachment)\"\n class=\"\n str-chat__message-attachment-file--item\n str-chat-angular__message-attachment-file-single\n \"\n >\n <stream-icon-placeholder\n icon=\"file\"\n [size]=\"30\"\n ></stream-icon-placeholder>\n <div class=\"str-chat__message-attachment-file--item-text\">\n <a\n data-testclass=\"file-link\"\n download\n href=\"{{ attachment.asset_url }}\"\n target=\"_blank\"\n >\n {{ attachment.title }}\n </a>\n <span data-testclass=\"size\" *ngIf=\"hasFileSize(attachment)\">{{\n getFileSize(attachment)\n }}</span>\n </div>\n </div>\n <div\n *ngIf=\"isCard(attachment)\"\n class=\"str-chat__message-attachment-card str-chat__message-attachment-card--{{\n attachment.type\n }}\"\n >\n <div\n *ngIf=\"attachment.image_url || attachment.thumb_url\"\n class=\"str-chat__message-attachment-card--header\"\n >\n <img\n data-testclass=\"card-img\"\n alt=\"{{ attachment.image_url || attachment.thumb_url }}\"\n src=\"{{ attachment.image_url || attachment.thumb_url }}\"\n />\n </div>\n <div class=\"str-chat__message-attachment-card--content\">\n <div class=\"str-chat__message-attachment-card--flex\">\n <div\n *ngIf=\"attachment.title\"\n data-testclass=\"card-title\"\n class=\"str-chat__message-attachment-card--title\"\n >\n {{ attachment.title }}\n </div>\n <div\n *ngIf=\"attachment.text\"\n class=\"str-chat__message-attachment-card--text\"\n data-testclass=\"card-text\"\n >\n {{ attachment.text }}\n </div>\n <a\n class=\"str-chat__message-attachment-card--url\"\n *ngIf=\"attachment.title_link || attachment.og_scrape_url\"\n data-testclass=\"url-link\"\n noopener\n noreferrer\n href=\"{{ attachment.title_link || attachment.og_scrape_url }}\"\n target=\"_blank\"\n >\n {{ trimUrl(attachment.title_link || attachment.og_scrape_url) }}\n </a>\n </div>\n </div>\n </div>\n <div\n class=\"str-chat__message-attachment-actions\"\n *ngIf=\"attachment.actions && attachment.actions.length > 0\"\n >\n <div class=\"str-chat__message-attachment-actions-form\">\n <button\n *ngFor=\"let action of attachment.actions; trackBy: trackByActionValue\"\n class=\"str-chat__message-attachment-actions-button str-chat__message-attachment-actions-button--{{\n action.style\n }}\"\n data-testclass=\"attachment-action\"\n (click)=\"sendAction(action)\"\n (keyup.enter)=\"sendAction(action)\"\n >\n {{ action.text }}\n </button>\n </div>\n </div>\n </div>\n</ng-container>\n\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\n<ng-template\n #defaultModal\n let-isOpen=\"isOpen\"\n let-isOpenChangeHandler=\"isOpenChangeHandler\"\n let-content=\"content\"\n>\n <stream-modal\n [isOpen]=\"isOpen\"\n (isOpenChange)=\"isOpenChangeHandler($event)\"\n [content]=\"content\"\n >\n </stream-modal>\n</ng-template>\n\n<ng-template #modalContent>\n <div class=\"stream-chat-angular__image-modal\">\n <button\n class=\"stream-chat-angular__image-modal-stepper\"\n [ngStyle]=\"{\n visibility: isImageModalPrevButtonVisible ? 'visible' : 'hidden'\n }\"\n data-testid=\"image-modal-prev\"\n type=\"button\"\n (click)=\"stepImages(-1)\"\n (keyup.enter)=\"stepImages(-1)\"\n >\n <stream-icon-placeholder icon=\"arrow-left\"></stream-icon-placeholder>\n </button>\n <img\n class=\"stream-chat-angular__image-modal-image\"\n data-testid=\"modal-image\"\n [src]=\"\n imagesToView[imagesToViewCurrentIndex].img_url ||\n imagesToView[imagesToViewCurrentIndex].thumb_url ||\n imagesToView[imagesToViewCurrentIndex].image_url\n \"\n [alt]=\"imagesToView[imagesToViewCurrentIndex].fallback\"\n />\n <button\n class=\"stream-chat-angular__image-modal-stepper\"\n type=\"button\"\n [ngStyle]=\"{\n visibility: isImageModalNextButtonVisible ? 'visible' : 'hidden'\n }\"\n data-testid=\"image-modal-next\"\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</ng-template>\n", components: [{ type: IconPlaceholderComponent, selector: "stream-icon-placeholder", inputs: ["icon", "size"] }, { type: ModalComponent, selector: "stream-modal", inputs: ["isOpen", "content"], outputs: ["isOpenChange"] }], directives: [{ type: i3.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i3.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { type: i3.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet"] }], pipes: { "translate": i2.TranslatePipe, "async": i3.AsyncPipe } });
|
|
2227
|
+
AttachmentListComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.5", type: AttachmentListComponent, selector: "stream-attachment-list", inputs: { messageId: "messageId", attachments: "attachments" }, viewQueries: [{ propertyName: "modalContent", first: true, predicate: ["modalContent"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: "<ng-container *ngFor=\"let attachment of orderedAttachments; trackBy: trackById\">\n <div\n data-testclass=\"attachment-container\"\n class=\"str-chat__message-attachment str-chat__message-attachment--{{\n attachment.type\n }}\"\n [class.str-chat__message-attachment--card]=\"isCard(attachment)\"\n [class.str-chat-angular__message-attachment-file-single]=\"\n isFile(attachment)\n \"\n >\n <img\n *ngIf=\"isImage(attachment)\"\n class=\"str-chat__message-attachment--img\"\n data-testclass=\"image\"\n [src]=\"attachment.img_url || attachment.thumb_url || attachment.image_url\"\n [alt]=\"attachment?.fallback\"\n (load)=\"imageLoaded()\"\n (click)=\"openImageModal([attachment])\"\n (keyup.enter)=\"openImageModal([attachment])\"\n />\n <div\n class=\"str-chat__gallery\"\n data-testid=\"image-gallery\"\n *ngIf=\"isGallery(attachment)\"\n [class.str-chat__gallery--square]=\"(attachment?.images)!.length > 3\"\n >\n <ng-container\n *ngFor=\"\n let galleryImage of attachment.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 (click)=\"openImageModal(attachment.images!, index)\"\n (keyup.enter)=\"openImageModal(attachment.images!, index)\"\n >\n <img\n [src]=\"\n galleryImage.img_url ||\n galleryImage.thumb_url ||\n galleryImage.image_url\n \"\n [alt]=\"galleryImage.fallback\"\n (load)=\"imageLoaded()\"\n />\n </button>\n <button\n *ngIf=\"index === 3 && !isLast\"\n class=\"str-chat__gallery-placeholder\"\n data-testclass=\"gallery-image\"\n (click)=\"openImageModal(attachment.images!, index)\"\n (keyup.enter)=\"openImageModal(attachment.images!, index)\"\n [ngStyle]=\"{\n 'background-image':\n 'url(' +\n (galleryImage.img_url ||\n galleryImage.thumb_url ||\n galleryImage.image_url) +\n ')'\n }\"\n >\n <p\n [innerHTML]=\"\n 'streamChat.{{ imageCount }} more'\n | translate: { imageCount: attachment!.images!.length - 4 }\n \"\n ></p>\n </button>\n </ng-container>\n </div>\n <video\n *ngIf=\"isVideo(attachment)\"\n controls\n data-testclass=\"video-attachment\"\n [src]=\"attachment.asset_url\"\n style=\"\n width: 100%;\n max-width: 400px;\n height: 300px;\n border-radius: inherit;\n \"\n ></video>\n <div\n *ngIf=\"isFile(attachment)\"\n class=\"\n str-chat__message-attachment-file--item\n str-chat-angular__message-attachment-file-single\n \"\n >\n <stream-icon-placeholder\n icon=\"file\"\n [size]=\"30\"\n ></stream-icon-placeholder>\n <div class=\"str-chat__message-attachment-file--item-text\">\n <a\n data-testclass=\"file-link\"\n download\n href=\"{{ attachment.asset_url }}\"\n target=\"_blank\"\n >\n {{ attachment.title }}\n </a>\n <span data-testclass=\"size\" *ngIf=\"hasFileSize(attachment)\">{{\n getFileSize(attachment)\n }}</span>\n </div>\n </div>\n <div\n *ngIf=\"isCard(attachment)\"\n class=\"str-chat__message-attachment-card str-chat__message-attachment-card--{{\n attachment.type\n }}\"\n >\n <div\n *ngIf=\"attachment.image_url || attachment.thumb_url\"\n class=\"str-chat__message-attachment-card--header\"\n >\n <img\n data-testclass=\"card-img\"\n alt=\"{{ attachment.image_url || attachment.thumb_url }}\"\n src=\"{{ attachment.image_url || attachment.thumb_url }}\"\n />\n </div>\n <div class=\"str-chat__message-attachment-card--content\">\n <div class=\"str-chat__message-attachment-card--flex\">\n <div\n *ngIf=\"attachment.title\"\n data-testclass=\"card-title\"\n class=\"str-chat__message-attachment-card--title\"\n >\n {{ attachment.title }}\n </div>\n <div\n *ngIf=\"attachment.text\"\n class=\"str-chat__message-attachment-card--text\"\n data-testclass=\"card-text\"\n >\n {{ attachment.text }}\n </div>\n <a\n class=\"str-chat__message-attachment-card--url\"\n *ngIf=\"attachment.title_link || attachment.og_scrape_url\"\n data-testclass=\"url-link\"\n noopener\n noreferrer\n href=\"{{ attachment.title_link || attachment.og_scrape_url }}\"\n target=\"_blank\"\n >\n {{ trimUrl(attachment.title_link || attachment.og_scrape_url) }}\n </a>\n </div>\n </div>\n </div>\n <div\n class=\"str-chat__message-attachment-actions\"\n *ngIf=\"attachment.actions && attachment.actions.length > 0\"\n >\n <div class=\"str-chat__message-attachment-actions-form\">\n <button\n *ngFor=\"let action of attachment.actions; trackBy: trackByActionValue\"\n class=\"str-chat__message-attachment-actions-button str-chat__message-attachment-actions-button--{{\n action.style\n }}\"\n data-testclass=\"attachment-action\"\n (click)=\"sendAction(action)\"\n (keyup.enter)=\"sendAction(action)\"\n >\n {{ action.text }}\n </button>\n </div>\n </div>\n </div>\n</ng-container>\n\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\n<ng-template\n #defaultModal\n let-isOpen=\"isOpen\"\n let-isOpenChangeHandler=\"isOpenChangeHandler\"\n let-content=\"content\"\n>\n <stream-modal\n [isOpen]=\"isOpen\"\n (isOpenChange)=\"isOpenChangeHandler($event)\"\n [content]=\"content\"\n >\n </stream-modal>\n</ng-template>\n\n<ng-template #modalContent>\n <div class=\"stream-chat-angular__image-modal\">\n <button\n class=\"stream-chat-angular__image-modal-stepper\"\n [ngStyle]=\"{\n visibility: isImageModalPrevButtonVisible ? 'visible' : 'hidden'\n }\"\n data-testid=\"image-modal-prev\"\n type=\"button\"\n (click)=\"stepImages(-1)\"\n (keyup.enter)=\"stepImages(-1)\"\n >\n <stream-icon-placeholder icon=\"arrow-left\"></stream-icon-placeholder>\n </button>\n <img\n class=\"stream-chat-angular__image-modal-image\"\n data-testid=\"modal-image\"\n [src]=\"\n imagesToView[imagesToViewCurrentIndex].img_url ||\n imagesToView[imagesToViewCurrentIndex].thumb_url ||\n imagesToView[imagesToViewCurrentIndex].image_url\n \"\n [alt]=\"imagesToView[imagesToViewCurrentIndex].fallback\"\n />\n <button\n class=\"stream-chat-angular__image-modal-stepper\"\n type=\"button\"\n [ngStyle]=\"{\n visibility: isImageModalNextButtonVisible ? 'visible' : 'hidden'\n }\"\n data-testid=\"image-modal-next\"\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</ng-template>\n", components: [{ type: IconPlaceholderComponent, selector: "stream-icon-placeholder", inputs: ["icon", "size"] }, { type: ModalComponent, selector: "stream-modal", inputs: ["isOpen", "content"], outputs: ["isOpenChange"] }], directives: [{ type: i3.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i3.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { type: i3.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet"] }], pipes: { "translate": i2.TranslatePipe, "async": i3.AsyncPipe } });
|
|
2181
2228
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: AttachmentListComponent, decorators: [{
|
|
2182
2229
|
type: Component,
|
|
2183
2230
|
args: [{
|
|
@@ -2219,7 +2266,7 @@ class AttachmentPreviewListComponent {
|
|
|
2219
2266
|
}
|
|
2220
2267
|
}
|
|
2221
2268
|
AttachmentPreviewListComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: AttachmentPreviewListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2222
|
-
AttachmentPreviewListComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.5", type: AttachmentPreviewListComponent, selector: "stream-attachment-preview-list", inputs: { attachmentUploads$: "attachmentUploads$" }, outputs: { retryAttachmentUpload: "retryAttachmentUpload", deleteAttachment: "deleteAttachment" }, ngImport: i0, template: "<div class=\"rfu-image-previewer\" *ngIf=\"(attachmentUploads$ | async)?.length\">\n <ng-container\n *ngFor=\"\n let attachmentUpload of attachmentUploads$ | async;\n trackBy: trackByFile\n \"\n >\n <div\n *ngIf=\"attachmentUpload.type === 'image'\"\n class=\"rfu-image-previewer__image\"\n [class.rfu-image-previewer__image--loaded]=\"\n attachmentUpload.state === 'success'\n \"\n data-testclass=\"attachment-image-preview\"\n >\n <div\n *ngIf=\"attachmentUpload.state === 'error'\"\n class=\"rfu-image-previewer__retry\"\n (click)=\"attachmentUploadRetried(attachmentUpload.file)\"\n (keyup.enter)=\"attachmentUploadRetried(attachmentUpload.file)\"\n data-testclass=\"upload-error\"\n >\n <stream-icon-placeholder icon=\"retry\"></stream-icon-placeholder>\n </div>\n <div class=\"rfu-thumbnail__wrapper\" style=\"width: 100; height: 100\">\n <div class=\"rfu-thumbnail__overlay\">\n <div\n class=\"rfu-icon-button\"\n data-testclass=\"delete-attachment\"\n role=\"button\"\n (click)=\"attachmentDeleted(attachmentUpload)\"\n (keyup.enter)=\"attachmentDeleted(attachmentUpload)\"\n >\n <stream-icon-placeholder icon=\"close\"></stream-icon-placeholder>\n </div>\n </div>\n <img\n *ngIf=\"attachmentUpload.url || attachmentUpload.previewUri\"\n src=\"{{\n attachmentUpload.url\n ? attachmentUpload.url\n : attachmentUpload.previewUri\n }}\"\n alt=\"attachmentUpload.file.name\"\n class=\"rfu-thumbnail__image\"\n data-testclass=\"attachment-image\"\n />\n </div>\n <stream-loading-indicator-placeholder\n data-testclass=\"loading-indicator\"\n color=\"rgba(255,255,255,0.7)\"\n *ngIf=\"attachmentUpload.state === 'uploading'\"\n ></stream-loading-indicator-placeholder>\n </div>\n <div\n class=\"rfu-file-previewer\"\n *ngIf=\"attachmentUpload.type === 'file'\"\n data-testclass=\"attachment-file-preview\"\n >\n <ol>\n <li\n class=\"rfu-file-previewer__file\"\n [class.rfu-file-previewer__file--uploading]=\"\n attachmentUpload.state === 'uploading'\n \"\n [class.rfu-file-previewer__file--failed]=\"\n attachmentUpload.state === 'error'\n \"\n >\n <stream-icon-placeholder icon=\"file\"></stream-icon-placeholder>\n\n <a\n data-testclass=\"file-download-link\"\n href=\"{{ attachmentUpload.url }}\"\n (click)=\"attachmentUpload.url ? null : $event.preventDefault()\"\n (keyup.enter)=\"\n attachmentUpload.url ? null : $event.preventDefault()\n \"\n download\n >\n {{ attachmentUpload.file.name }}\n <ng-container *ngIf=\"attachmentUpload.state === 'error'\">\n <div\n data-testclass=\"file-upload-retry\"\n class=\"rfu-file-previewer__failed\"\n (click)=\"attachmentUploadRetried(attachmentUpload.file)\"\n (keyup.enter)=\"attachmentUploadRetried(attachmentUpload.file)\"\n translate\n >\n streamChat.failed\n </div>\n <div\n class=\"rfu-file-previewer__retry\"\n (click)=\"attachmentUploadRetried(attachmentUpload.file)\"\n (keyup.enter)=\"attachmentUploadRetried(attachmentUpload.file)\"\n translate\n >\n streamChat.retry\n </div>\n </ng-container>\n </a>\n\n <span\n data-testclass=\"file-delete\"\n class=\"rfu-file-previewer__close-button\"\n (click)=\"attachmentDeleted(attachmentUpload)\"\n (keyup.enter)=\"attachmentDeleted(attachmentUpload)\"\n >\n \u2718\n </span>\n <div\n *ngIf=\"attachmentUpload.state === 'uploading'\"\n class=\"rfu-file-previewer__loading-indicator\"\n >\n <stream-loading-indicator-placeholder></stream-loading-indicator-placeholder>\n </div>\n </li>\n </ol>\n </div>\n </ng-container>\n</div>\n", components: [{ type: IconPlaceholderComponent, selector: "stream-icon-placeholder", inputs: ["icon", "size"] }, { type: LoadingIndicatorPlaceholderComponent, selector: "stream-loading-indicator-placeholder", inputs: ["size", "color"] }], directives: [{ type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i3.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { type: i2.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }], pipes: { "async": i3.AsyncPipe } });
|
|
2269
|
+
AttachmentPreviewListComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.5", type: AttachmentPreviewListComponent, selector: "stream-attachment-preview-list", inputs: { attachmentUploads$: "attachmentUploads$" }, outputs: { retryAttachmentUpload: "retryAttachmentUpload", deleteAttachment: "deleteAttachment" }, ngImport: i0, template: "<div class=\"rfu-image-previewer\" *ngIf=\"(attachmentUploads$ | async)?.length\">\n <ng-container\n *ngFor=\"\n let attachmentUpload of attachmentUploads$ | async;\n trackBy: trackByFile\n \"\n >\n <div\n *ngIf=\"attachmentUpload.type === 'image'\"\n class=\"rfu-image-previewer__image\"\n [class.rfu-image-previewer__image--loaded]=\"\n attachmentUpload.state === 'success'\n \"\n data-testclass=\"attachment-image-preview\"\n >\n <div\n *ngIf=\"attachmentUpload.state === 'error'\"\n class=\"rfu-image-previewer__retry\"\n (click)=\"attachmentUploadRetried(attachmentUpload.file)\"\n (keyup.enter)=\"attachmentUploadRetried(attachmentUpload.file)\"\n data-testclass=\"upload-error\"\n >\n <stream-icon-placeholder icon=\"retry\"></stream-icon-placeholder>\n </div>\n <div class=\"rfu-thumbnail__wrapper\" style=\"width: 100; height: 100\">\n <div class=\"rfu-thumbnail__overlay\">\n <div\n class=\"rfu-icon-button\"\n data-testclass=\"delete-attachment\"\n role=\"button\"\n (click)=\"attachmentDeleted(attachmentUpload)\"\n (keyup.enter)=\"attachmentDeleted(attachmentUpload)\"\n >\n <stream-icon-placeholder icon=\"close\"></stream-icon-placeholder>\n </div>\n </div>\n <img\n *ngIf=\"attachmentUpload.url || attachmentUpload.previewUri\"\n src=\"{{\n attachmentUpload.url\n ? attachmentUpload.url\n : attachmentUpload.previewUri\n }}\"\n alt=\"attachmentUpload.file.name\"\n class=\"rfu-thumbnail__image\"\n data-testclass=\"attachment-image\"\n />\n </div>\n <stream-loading-indicator-placeholder\n data-testclass=\"loading-indicator\"\n color=\"rgba(255,255,255,0.7)\"\n *ngIf=\"attachmentUpload.state === 'uploading'\"\n ></stream-loading-indicator-placeholder>\n </div>\n <div\n class=\"rfu-file-previewer\"\n *ngIf=\"\n attachmentUpload.type === 'file' || attachmentUpload.type === 'video'\n \"\n data-testclass=\"attachment-file-preview\"\n >\n <ol>\n <li\n class=\"rfu-file-previewer__file\"\n [class.rfu-file-previewer__file--uploading]=\"\n attachmentUpload.state === 'uploading'\n \"\n [class.rfu-file-previewer__file--failed]=\"\n attachmentUpload.state === 'error'\n \"\n >\n <stream-icon-placeholder icon=\"file\"></stream-icon-placeholder>\n\n <a\n data-testclass=\"file-download-link\"\n href=\"{{ attachmentUpload.url }}\"\n (click)=\"attachmentUpload.url ? null : $event.preventDefault()\"\n (keyup.enter)=\"\n attachmentUpload.url ? null : $event.preventDefault()\n \"\n download\n >\n {{ attachmentUpload.file.name }}\n <ng-container *ngIf=\"attachmentUpload.state === 'error'\">\n <div\n data-testclass=\"file-upload-retry\"\n class=\"rfu-file-previewer__failed\"\n (click)=\"attachmentUploadRetried(attachmentUpload.file)\"\n (keyup.enter)=\"attachmentUploadRetried(attachmentUpload.file)\"\n translate\n >\n streamChat.failed\n </div>\n <div\n class=\"rfu-file-previewer__retry\"\n (click)=\"attachmentUploadRetried(attachmentUpload.file)\"\n (keyup.enter)=\"attachmentUploadRetried(attachmentUpload.file)\"\n translate\n >\n streamChat.retry\n </div>\n </ng-container>\n </a>\n\n <span\n data-testclass=\"file-delete\"\n class=\"rfu-file-previewer__close-button\"\n (click)=\"attachmentDeleted(attachmentUpload)\"\n (keyup.enter)=\"attachmentDeleted(attachmentUpload)\"\n >\n \u2718\n </span>\n <div\n *ngIf=\"attachmentUpload.state === 'uploading'\"\n class=\"rfu-file-previewer__loading-indicator\"\n >\n <stream-loading-indicator-placeholder></stream-loading-indicator-placeholder>\n </div>\n </li>\n </ol>\n </div>\n </ng-container>\n</div>\n", components: [{ type: IconPlaceholderComponent, selector: "stream-icon-placeholder", inputs: ["icon", "size"] }, { type: LoadingIndicatorPlaceholderComponent, selector: "stream-loading-indicator-placeholder", inputs: ["size", "color"] }], directives: [{ type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i3.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { type: i2.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }], pipes: { "async": i3.AsyncPipe } });
|
|
2223
2270
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: AttachmentPreviewListComponent, decorators: [{
|
|
2224
2271
|
type: Component,
|
|
2225
2272
|
args: [{
|
|
@@ -2388,10 +2435,15 @@ class MessageInputComponent {
|
|
|
2388
2435
|
return;
|
|
2389
2436
|
}
|
|
2390
2437
|
const attachments = this.attachmentService.mapToAttachments();
|
|
2391
|
-
|
|
2392
|
-
|
|
2438
|
+
let text = this.textareaValue;
|
|
2439
|
+
const textContainsOnlySpaceChars = !text.replace(/ /g, ''); //spcae
|
|
2440
|
+
if ((!text || textContainsOnlySpaceChars) &&
|
|
2441
|
+
(!attachments || attachments.length === 0)) {
|
|
2393
2442
|
return;
|
|
2394
2443
|
}
|
|
2444
|
+
if (textContainsOnlySpaceChars) {
|
|
2445
|
+
text = '';
|
|
2446
|
+
}
|
|
2395
2447
|
if (this.containsLinks && !this.canSendLinks) {
|
|
2396
2448
|
this.notificationService.addTemporaryNotification('streamChat.Sending links is not allowed in this conversation');
|
|
2397
2449
|
return;
|
|
@@ -3513,7 +3565,12 @@ class MessageComponent {
|
|
|
3513
3565
|
this.message.mentioned_users.length === 0) {
|
|
3514
3566
|
// Wrap emojis in span to display emojis correctly in Chrome https://bugs.chromium.org/p/chromium/issues/detail?id=596223
|
|
3515
3567
|
const regex = new RegExp(emojiRegex(), 'g');
|
|
3516
|
-
|
|
3568
|
+
// Based on this: https://stackoverflow.com/questions/4565112/javascript-how-to-find-out-if-the-user-browser-is-chrome
|
|
3569
|
+
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
3570
|
+
const isChrome = !!window.chrome &&
|
|
3571
|
+
typeof window.opr === 'undefined';
|
|
3572
|
+
/* eslint-enable @typescript-eslint/no-unsafe-member-access */
|
|
3573
|
+
content = content.replace(regex, (match) => `<span ${isChrome ? 'class="str-chat__emoji-display-fix"' : ''}>${match}</span>`);
|
|
3517
3574
|
this.messageTextParts = [{ content, type: 'text' }];
|
|
3518
3575
|
}
|
|
3519
3576
|
else {
|