stream-chat-angular 3.0.0-beta.9 → 3.0.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/README.md CHANGED
@@ -30,15 +30,38 @@ The best way to get started is to follow the [Angular Chat Tutorial](https://get
30
30
  Stream is free for most side and hobby projects. To qualify, your project/company must have no more than 5 team members and earn less than $10k in monthly revenue.
31
31
  For complete pricing and details visit our [Chat Pricing Page](https://getstream.io/chat/pricing/).
32
32
 
33
+ ## Docs
34
+
35
+ The [docs](https://getstream.io/chat/docs/sdk/angular/) provide a brief description about the components and services in the library.
36
+
37
+ The Angular library is created using the [stream-chat-js](https://github.com/getstream/stream-chat-js) library. For the most common use cases our services should give a nice abstraction over this library, however you might need it for more advanced customization, the [documentation](https://getstream.io/chat/docs/js/) is on our website.
38
+
39
+ ## Contributing
40
+
41
+ We welcome code changes that improve this library or fix a problem. Please make sure to follow all best practices and add tests, if applicable, before submitting a pull request on GitHub. We are pleased to merge your code into the official repository if it meets a need. Make sure to sign our [Contributor License Agreement (CLA)](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform) first. See our license file for more details.
42
+
43
+ ## We are hiring!
44
+
45
+ We recently closed a [$38 million Series B funding round](https://techcrunch.com/2021/03/04/stream-raises-38m-as-its-chat-and-activity-feed-apis-power-communications-for-1b-users/) and are actively growing.
46
+ Our APIs are used by more than a billion end-users, and by working at Stream, you have the chance to make a huge impact on a team of very strong engineers.
47
+
48
+ Check out our current openings and apply via [Stream's website](https://getstream.io/team/#jobs).
49
+
33
50
  ## Installation
34
51
 
35
52
  ### Install with NPM
36
53
 
37
- ```
38
- npm install stream-chat-angular @stream-io/stream-chat-css stream-chat @ngx-translate/core
54
+ Run the following command if you are using **Angular 13**:
55
+
56
+ ```shell
57
+ npm install stream-chat-angular stream-chat @ngx-translate/core
39
58
  ```
40
59
 
41
- **Important note** If you are using **Angular 12** you will need to add `--legacy-peer-deps` flag as `@ngx-translate/core`'s newest version only supports Angular 13.
60
+ Run this command if you are using **Angular 12**:
61
+
62
+ ```shell
63
+ npm install stream-chat-angular stream-chat@5 @ngx-translate/core --legacy-peer-deps
64
+ ```
42
65
 
43
66
  ## Sample App
44
67
 
@@ -56,19 +79,28 @@ STREAM_USER_TOKEN=<Your user token>
56
79
 
57
80
  Run `npm start` and navigate to `http://localhost:4200/`.
58
81
 
59
- ## Docs
82
+ ## Customization examples
60
83
 
61
- The [docs](https://getstream.io/chat/docs/sdk/angular/) provide a brief description about the components and services in the library.
84
+ This repository includes a sample app that showcases how you can provide your own template for different components within the SDK:
62
85
 
63
- The Angular library is created using the [stream-chat-js](https://github.com/getstream/stream-chat-js) library. For the most common use cases our services should give a nice abstraction over this library, however you might need it for more advanced customization, the [documentation](https://getstream.io/chat/docs/js/) is on our website.
86
+ To run the app:
64
87
 
65
- ## Contributing
88
+ Create a file named `.env` in the root directory with the following content:
66
89
 
67
- We welcome code changes that improve this library or fix a problem. Please make sure to follow all best practices and add tests, if applicable, before submitting a pull request on GitHub. We are pleased to merge your code into the official repository if it meets a need. Make sure to sign our [Contributor License Agreement (CLA)](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform) first. See our license file for more details.
90
+ ```
91
+ STREAM_API_KEY=<Your API key>
92
+ STREAM_USER_ID=<Your user ID>
93
+ STREAM_USER_TOKEN=<Your user token>
94
+ ```
68
95
 
69
- ## We are hiring!
96
+ Run `npm start:customizations-example` and navigate to `http://localhost:4200/`.
70
97
 
71
- We recently closed a [$38 million Series B funding round](https://techcrunch.com/2021/03/04/stream-raises-38m-as-its-chat-and-activity-feed-apis-power-communications-for-1b-users/) and are actively growing.
72
- Our APIs are used by more than a billion end-users, and by working at Stream, you have the chance to make a huge impact on a team of very strong engineers.
98
+ ## Local development
73
99
 
74
- Check out our current openings and apply via [Stream's website](https://getstream.io/team/#jobs).
100
+ Run `npm install` in the root of the project. You can use the `npm run start:dev` command to start the SampleApp with automatic reloading.
101
+
102
+ A note about the documentation:
103
+
104
+ - Documentations for Angular services are generated from doc comments in the source files (not under source control)
105
+ - Documentations for inputs and outputs of Angular components are generated from doc comments in the source files (not under source control)
106
+ - Everything else in the documentation is written in `mdx` files located in the `docusaurus` folder
@@ -1 +1 @@
1
- export declare const version = "3.0.0-beta.9";
1
+ export declare const version = "3.0.0";
@@ -354,7 +354,7 @@
354
354
  return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
355
355
  }
356
356
 
357
- var version = '3.0.0-beta.9';
357
+ var version = '3.0.0';
358
358
 
359
359
  /**
360
360
  * The `NotificationService` can be used to add or remove notifications. By default the [`NotificationList`](../components/NotificationListComponent.mdx) component displays the currently active notifications.
@@ -652,7 +652,7 @@
652
652
  };
653
653
 
654
654
  /**
655
- * The `ChannelService` provides data and interaction for the channel list and message list. TEST
655
+ * The `ChannelService` provides data and interaction for the channel list and message list.
656
656
  */
657
657
  var ChannelService = /** @class */ (function () {
658
658
  function ChannelService(chatClientService, ngZone) {
@@ -747,6 +747,21 @@
747
747
  this.activeThreadMessagesSubject.next([]);
748
748
  this.messageToQuoteSubject.next(undefined);
749
749
  };
750
+ /**
751
+ * Deselects the currently active (if any) channel
752
+ */
753
+ ChannelService.prototype.deselectActiveChannel = function () {
754
+ var activeChannel = this.activeChannelSubject.getValue();
755
+ if (!activeChannel) {
756
+ return;
757
+ }
758
+ this.activeChannelMessagesSubject.next([]);
759
+ this.activeChannelSubject.next(undefined);
760
+ this.activeParentMessageIdSubject.next(undefined);
761
+ this.activeThreadMessagesSubject.next([]);
762
+ this.latestMessageDateByUserByChannelsSubject.next({});
763
+ this.selectMessageToQuote(undefined);
764
+ };
750
765
  /**
751
766
  * Sets the given `message` as an active parent message. If `undefined` is provided, it will deleselect the current parent message.
752
767
  * @param message
@@ -842,9 +857,13 @@
842
857
  * @param filters
843
858
  * @param sort
844
859
  * @param options
860
+ * @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
861
+ * @returns the list of channels found by the query
845
862
  */
846
- ChannelService.prototype.init = function (filters, sort, options) {
863
+ ChannelService.prototype.init = function (filters, sort, options, shouldSetActiveChannel) {
864
+ if (shouldSetActiveChannel === void 0) { shouldSetActiveChannel = true; }
847
865
  return __awaiter(this, void 0, void 0, function () {
866
+ var result;
848
867
  var _this = this;
849
868
  return __generator(this, function (_h) {
850
869
  switch (_h.label) {
@@ -859,11 +878,11 @@
859
878
  message_limit: this.messagePageSize,
860
879
  };
861
880
  this.sort = sort || { last_message_at: -1, updated_at: -1 };
862
- return [4 /*yield*/, this.queryChannels()];
881
+ return [4 /*yield*/, this.queryChannels(shouldSetActiveChannel)];
863
882
  case 1:
864
- _h.sent();
883
+ result = _h.sent();
865
884
  this.chatClientService.events$.subscribe(function (notification) { return void _this.handleNotification(notification); });
866
- return [2 /*return*/];
885
+ return [2 /*return*/, result];
867
886
  }
868
887
  });
869
888
  });
@@ -872,13 +891,8 @@
872
891
  * Resets the `activeChannel$`, `channels$` and `activeChannelMessages$` Observables. Useful when disconnecting a chat user, use in combination with [`disconnectUser`](./ChatClientService.mdx/#disconnectuser).
873
892
  */
874
893
  ChannelService.prototype.reset = function () {
875
- this.activeChannelMessagesSubject.next([]);
876
- this.activeChannelSubject.next(undefined);
877
- this.activeParentMessageIdSubject.next(undefined);
878
- this.activeThreadMessagesSubject.next([]);
894
+ this.deselectActiveChannel();
879
895
  this.channelsSubject.next(undefined);
880
- this.latestMessageDateByUserByChannelsSubject.next({});
881
- this.selectMessageToQuote(undefined);
882
896
  };
883
897
  /**
884
898
  * 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.
@@ -889,7 +903,7 @@
889
903
  switch (_h.label) {
890
904
  case 0:
891
905
  this.options.offset = this.channels.length;
892
- return [4 /*yield*/, this.queryChannels()];
906
+ return [4 /*yield*/, this.queryChannels(false)];
893
907
  case 1:
894
908
  _h.sent();
895
909
  return [2 /*return*/];
@@ -1027,14 +1041,19 @@
1027
1041
  ChannelService.prototype.uploadAttachments = function (uploads) {
1028
1042
  return __awaiter(this, void 0, void 0, function () {
1029
1043
  var result, channel, uploadResults;
1044
+ var _this = this;
1030
1045
  return __generator(this, function (_h) {
1031
1046
  switch (_h.label) {
1032
1047
  case 0:
1033
1048
  result = [];
1034
1049
  channel = this.activeChannelSubject.getValue();
1035
1050
  return [4 /*yield*/, Promise.allSettled(uploads.map(function (upload) { return upload.type === 'image'
1036
- ? channel.sendImage(upload.file)
1037
- : channel.sendFile(upload.file); }))];
1051
+ ? _this.customImageUploadRequest
1052
+ ? _this.customImageUploadRequest(upload.file, channel)
1053
+ : channel.sendImage(upload.file)
1054
+ : _this.customFileUploadRequest
1055
+ ? _this.customFileUploadRequest(upload.file, channel)
1056
+ : channel.sendFile(upload.file); }))];
1038
1057
  case 1:
1039
1058
  uploadResults = _h.sent();
1040
1059
  uploadResults.forEach(function (uploadResult, i) {
@@ -1069,8 +1088,12 @@
1069
1088
  case 0:
1070
1089
  channel = this.activeChannelSubject.getValue();
1071
1090
  return [4 /*yield*/, (attachmentUpload.type === 'image'
1072
- ? channel.deleteImage(attachmentUpload.url)
1073
- : channel.deleteFile(attachmentUpload.url))];
1091
+ ? this.customImageDeleteRequest
1092
+ ? this.customImageDeleteRequest(attachmentUpload.url, channel)
1093
+ : channel.deleteImage(attachmentUpload.url)
1094
+ : this.customFileDeleteRequest
1095
+ ? this.customFileDeleteRequest(attachmentUpload.url, channel)
1096
+ : channel.deleteFile(attachmentUpload.url))];
1074
1097
  case 1:
1075
1098
  _h.sent();
1076
1099
  return [2 /*return*/];
@@ -1413,7 +1436,7 @@
1413
1436
  this.activeChannelSubscriptions.forEach(function (s) { return s.unsubscribe(); });
1414
1437
  this.activeChannelSubscriptions = [];
1415
1438
  };
1416
- ChannelService.prototype.queryChannels = function () {
1439
+ ChannelService.prototype.queryChannels = function (shouldSetActiveChannel) {
1417
1440
  return __awaiter(this, void 0, void 0, function () {
1418
1441
  var channels, prevChannels, error_2;
1419
1442
  var _this = this;
@@ -1427,15 +1450,17 @@
1427
1450
  channels.forEach(function (c) { return _this.watchForChannelEvents(c); });
1428
1451
  prevChannels = this.channelsSubject.getValue() || [];
1429
1452
  this.channelsSubject.next(__spreadArray(__spreadArray([], __read(prevChannels)), __read(channels)));
1430
- if (channels.length > 0 && !this.activeChannelSubject.getValue()) {
1453
+ if (channels.length > 0 &&
1454
+ !this.activeChannelSubject.getValue() &&
1455
+ shouldSetActiveChannel) {
1431
1456
  this.setAsActiveChannel(channels[0]);
1432
1457
  }
1433
1458
  this.hasMoreChannelsSubject.next(channels.length >= this.options.limit);
1434
- return [3 /*break*/, 3];
1459
+ return [2 /*return*/, channels];
1435
1460
  case 2:
1436
1461
  error_2 = _h.sent();
1437
1462
  this.channelsSubject.error(error_2);
1438
- return [3 /*break*/, 3];
1463
+ throw error_2;
1439
1464
  case 3: return [2 /*return*/];
1440
1465
  }
1441
1466
  });
@@ -1819,7 +1844,7 @@
1819
1844
  */
1820
1845
  AttachmentService.prototype.filesSelected = function (fileList) {
1821
1846
  return __awaiter(this, void 0, void 0, function () {
1822
- var imageFiles, dataFiles, newUploads;
1847
+ var imageFiles, dataFiles, videoFiles, newUploads;
1823
1848
  var _this = this;
1824
1849
  return __generator(this, function (_d) {
1825
1850
  switch (_d.label) {
@@ -1829,19 +1854,27 @@
1829
1854
  }
1830
1855
  imageFiles = [];
1831
1856
  dataFiles = [];
1857
+ videoFiles = [];
1832
1858
  Array.from(fileList).forEach(function (file) {
1833
1859
  if (isImageFile(file)) {
1834
1860
  imageFiles.push(file);
1835
1861
  }
1862
+ else if (file.type.startsWith('video/')) {
1863
+ videoFiles.push(file);
1864
+ }
1836
1865
  else {
1837
1866
  dataFiles.push(file);
1838
1867
  }
1839
1868
  });
1840
1869
  imageFiles.forEach(function (f) { return _this.createPreview(f); });
1841
- newUploads = __spreadArray(__spreadArray([], __read(imageFiles.map(function (file) { return ({
1870
+ newUploads = __spreadArray(__spreadArray(__spreadArray([], __read(imageFiles.map(function (file) { return ({
1842
1871
  file: file,
1843
1872
  state: 'uploading',
1844
1873
  type: 'image',
1874
+ }); }))), __read(videoFiles.map(function (file) { return ({
1875
+ file: file,
1876
+ state: 'uploading',
1877
+ type: 'video',
1845
1878
  }); }))), __read(dataFiles.map(function (file) { return ({
1846
1879
  file: file,
1847
1880
  state: 'uploading',
@@ -2735,7 +2768,7 @@
2735
2768
  var _this = this;
2736
2769
  var images = this.attachments.filter(this.isImage);
2737
2770
  var containsGallery = images.length >= 2;
2738
- this.orderedAttachments = __spreadArray(__spreadArray(__spreadArray([], __read((containsGallery ? this.createGallery(images) : images))), __read(this.attachments.filter(function (a) { return _this.isFile(a); }))), __read(this.attachments.filter(function (a) { return _this.isCard(a); })));
2771
+ this.orderedAttachments = __spreadArray(__spreadArray(__spreadArray(__spreadArray([], __read((containsGallery ? this.createGallery(images) : images))), __read(this.attachments.filter(function (a) { return _this.isVideo(a); }))), __read(this.attachments.filter(function (a) { return _this.isFile(a); }))), __read(this.attachments.filter(function (a) { return _this.isCard(a); })));
2739
2772
  };
2740
2773
  AttachmentListComponent.prototype.trackById = function (index) {
2741
2774
  return index;
@@ -2749,6 +2782,12 @@
2749
2782
  AttachmentListComponent.prototype.isGallery = function (attachment) {
2750
2783
  return attachment.type === 'gallery';
2751
2784
  };
2785
+ AttachmentListComponent.prototype.isVideo = function (attachment) {
2786
+ return (attachment.type === 'video' &&
2787
+ attachment.asset_url &&
2788
+ !attachment.og_scrape_url // links from video share services (such as YouTube or Facebook) are can't be played
2789
+ );
2790
+ };
2752
2791
  AttachmentListComponent.prototype.isCard = function (attachment) {
2753
2792
  return (!attachment.type ||
2754
2793
  (attachment.type === 'image' && !this.isImage(attachment)) ||
@@ -2828,7 +2867,7 @@
2828
2867
  return AttachmentListComponent;
2829
2868
  }());
2830
2869
  AttachmentListComponent.ɵfac = i0__namespace.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0__namespace, type: AttachmentListComponent, deps: [{ token: CustomTemplatesService }, { token: ImageLoadService }, { token: ChannelService }], target: i0__namespace.ɵɵFactoryTarget.Component });
2831
- AttachmentListComponent.ɵcmp = i0__namespace.ɵɵ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__namespace, 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__namespace.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { type: i3__namespace.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i3__namespace.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { type: i3__namespace.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet"] }], pipes: { "translate": i2__namespace.TranslatePipe, "async": i3__namespace.AsyncPipe } });
2870
+ AttachmentListComponent.ɵcmp = i0__namespace.ɵɵ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__namespace, 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__namespace.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { type: i3__namespace.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i3__namespace.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { type: i3__namespace.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet"] }], pipes: { "translate": i2__namespace.TranslatePipe, "async": i3__namespace.AsyncPipe } });
2832
2871
  i0__namespace.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0__namespace, type: AttachmentListComponent, decorators: [{
2833
2872
  type: i0.Component,
2834
2873
  args: [{
@@ -2871,7 +2910,7 @@
2871
2910
  return AttachmentPreviewListComponent;
2872
2911
  }());
2873
2912
  AttachmentPreviewListComponent.ɵfac = i0__namespace.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0__namespace, type: AttachmentPreviewListComponent, deps: [], target: i0__namespace.ɵɵFactoryTarget.Component });
2874
- AttachmentPreviewListComponent.ɵcmp = i0__namespace.ɵɵ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__namespace, 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__namespace.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i3__namespace.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { type: i2__namespace.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }], pipes: { "async": i3__namespace.AsyncPipe } });
2913
+ AttachmentPreviewListComponent.ɵcmp = i0__namespace.ɵɵ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__namespace, 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__namespace.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i3__namespace.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { type: i2__namespace.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }], pipes: { "async": i3__namespace.AsyncPipe } });
2875
2914
  i0__namespace.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0__namespace, type: AttachmentPreviewListComponent, decorators: [{
2876
2915
  type: i0.Component,
2877
2916
  args: [{
@@ -4369,7 +4408,12 @@
4369
4408
  this.message.mentioned_users.length === 0) {
4370
4409
  // Wrap emojis in span to display emojis correctly in Chrome https://bugs.chromium.org/p/chromium/issues/detail?id=596223
4371
4410
  var regex = new RegExp(emojiRegex__default['default'](), 'g');
4372
- content = content.replace(regex, function (match) { return "<span class=\"str-chat__emoji-display-fix\">" + match + "</span>"; });
4411
+ // Based on this: https://stackoverflow.com/questions/4565112/javascript-how-to-find-out-if-the-user-browser-is-chrome
4412
+ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
4413
+ var isChrome_1 = !!window.chrome &&
4414
+ typeof window.opr === 'undefined';
4415
+ /* eslint-enable @typescript-eslint/no-unsafe-member-access */
4416
+ content = content.replace(regex, function (match) { return "<span " + (isChrome_1 ? 'class="str-chat__emoji-display-fix"' : '') + ">" + match + "</span>"; });
4373
4417
  this.messageTextParts = [{ content: content, type: 'text' }];
4374
4418
  }
4375
4419
  else {