stream-chat-angular 4.1.0 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/assets/version.d.ts +1 -1
  2. package/bundles/stream-chat-angular.umd.js +148 -47
  3. package/bundles/stream-chat-angular.umd.js.map +1 -1
  4. package/esm2015/assets/version.js +2 -2
  5. package/esm2015/lib/attachment-configuration.service.js +92 -17
  6. package/esm2015/lib/attachment-list/attachment-list.component.js +49 -21
  7. package/esm2015/lib/attachment.service.js +4 -1
  8. package/esm2015/lib/channel.service.js +5 -3
  9. package/esm2015/lib/message/message.component.js +7 -8
  10. package/esm2015/lib/message-list/message-list.component.js +2 -2
  11. package/esm2015/lib/types.js +1 -1
  12. package/fesm2015/stream-chat-angular.js +154 -47
  13. package/fesm2015/stream-chat-angular.js.map +1 -1
  14. package/lib/attachment-configuration.service.d.ts +17 -6
  15. package/lib/attachment-list/attachment-list.component.d.ts +9 -7
  16. package/lib/message/message.component.d.ts +1 -20
  17. package/lib/types.d.ts +4 -0
  18. package/package.json +1 -1
  19. package/src/assets/styles/css/index.css +1 -1
  20. package/src/assets/styles/css/index.css.map +1 -1
  21. package/src/assets/styles/scss/Attachment.scss +45 -2
  22. package/src/assets/styles/scss/Gallery.scss +12 -6
  23. package/src/assets/styles/scss/Message.scss +2 -1
  24. package/src/assets/styles/v2/css/index.css +1 -1
  25. package/src/assets/styles/v2/css/index.css.map +1 -1
  26. package/src/assets/styles/v2/css/index.layout.css +1 -1
  27. package/src/assets/styles/v2/css/index.layout.css.map +1 -1
  28. package/src/assets/styles/v2/scss/AttachmentList/AttachmentList-layout.scss +72 -46
  29. package/src/assets/styles/v2/scss/Message/Message-layout.scss +0 -16
  30. package/src/assets/styles/v2/scss/MessageReactions/MessageReactions-layout.scss +1 -10
  31. package/src/assets/styles/v2/scss/Tooltip/Tooltip-layout.scss +2 -23
  32. package/src/assets/version.ts +1 -1
@@ -19,7 +19,7 @@ import transliterate from '@stream-io/transliterate';
19
19
  import * as i8 from 'angular-mentions';
20
20
  import { MentionModule } from 'angular-mentions';
21
21
 
22
- const version = '4.1.0';
22
+ const version = '4.2.0';
23
23
 
24
24
  /**
25
25
  * The `NotificationService` can be used to add or remove notifications. By default the [`NotificationList`](../components/NotificationListComponent.mdx) component displays the currently active notifications.
@@ -641,10 +641,10 @@ class ChannelService {
641
641
  const uploadResults = yield Promise.allSettled(uploads.map((upload) => upload.type === 'image'
642
642
  ? this.customImageUploadRequest
643
643
  ? this.customImageUploadRequest(upload.file, channel)
644
- : channel.sendImage(upload.file)
644
+ : channel.sendImage(upload.file, upload.file.name, upload.file.type)
645
645
  : this.customFileUploadRequest
646
646
  ? this.customFileUploadRequest(upload.file, channel)
647
- : channel.sendFile(upload.file)));
647
+ : channel.sendFile(upload.file, upload.file.name, upload.file.type)));
648
648
  uploadResults.forEach((uploadResult, i) => {
649
649
  const file = uploads[i].file;
650
650
  const type = uploads[i].type;
@@ -654,6 +654,8 @@ class ChannelService {
654
654
  type,
655
655
  state: 'success',
656
656
  url: uploadResult.value.file,
657
+ /* eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access */
658
+ thumb_url: uploadResult.value.thumb_url,
657
659
  });
658
660
  }
659
661
  else {
@@ -1564,6 +1566,7 @@ class AttachmentService {
1564
1566
  attachment.asset_url = r.url;
1565
1567
  attachment.title = (_b = r.file) === null || _b === void 0 ? void 0 : _b.name;
1566
1568
  attachment.file_size = (_c = r.file) === null || _c === void 0 ? void 0 : _c.size;
1569
+ attachment.thumb_url = r.thumb_url;
1567
1570
  }
1568
1571
  return attachment;
1569
1572
  });
@@ -1596,6 +1599,7 @@ class AttachmentService {
1596
1599
  size: attachment.file_size,
1597
1600
  },
1598
1601
  type: attachment.type,
1602
+ thumb_url: attachment.thumb_url,
1599
1603
  });
1600
1604
  }
1601
1605
  });
@@ -1635,6 +1639,7 @@ class AttachmentService {
1635
1639
  }
1636
1640
  upload.state = r.state;
1637
1641
  upload.url = r.url;
1642
+ upload.thumb_url = r.thumb_url;
1638
1643
  if (upload.state === 'error') {
1639
1644
  this.notificationService.addTemporaryNotification(upload.type === 'image'
1640
1645
  ? 'streamChat.Error uploading image'
@@ -1656,28 +1661,38 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.5", ngImpor
1656
1661
  }], ctorParameters: function () { return [{ type: ChannelService }, { type: NotificationService }]; } });
1657
1662
 
1658
1663
  /**
1659
- * The `AttachmentConfigurationService` provides customization for certain attributes of attachments displayed inside the message component.
1664
+ * The `AttachmentConfigurationService` provides customization for certain attributes of attachments displayed inside the message component. If you're using your own CDN, you can integrate resizing features of it by providing your own handlers.
1660
1665
  */
1661
1666
  class AttachmentConfigurationService {
1667
+ constructor() {
1668
+ /**
1669
+ * You can turn on/off thumbnail generation for video attachments
1670
+ */
1671
+ this.shouldGenerateVideoThumbnail = true;
1672
+ }
1662
1673
  /**
1663
1674
  * Handles the configuration for image attachments, it's possible to provide your own function to override the default logic
1664
1675
  * @param attachment The attachment to configure
1665
1676
  * @param location Specifies where the image is being displayed
1677
+ * @param element The default resizing logics reads the height/max-height and max-width propperties of this element and reduces file size based on the given values. File size reduction is done by Stream's CDN.
1666
1678
  */
1667
- getImageAttachmentConfiguration(attachment, location) {
1679
+ getImageAttachmentConfiguration(attachment, location, element) {
1668
1680
  if (this.customImageAttachmentConfigurationHandler) {
1669
- return this.customImageAttachmentConfigurationHandler(attachment, location);
1681
+ return this.customImageAttachmentConfigurationHandler(attachment, location, element);
1682
+ }
1683
+ const url = new URL((attachment.img_url ||
1684
+ attachment.thumb_url ||
1685
+ attachment.image_url ||
1686
+ ''));
1687
+ const { sizeRestriction, height } = this.getSizingRestrictions(url, element);
1688
+ if (sizeRestriction) {
1689
+ // Apply 2x for retina displays
1690
+ sizeRestriction.height *= 2;
1691
+ sizeRestriction.width *= 2;
1692
+ this.addResizingParamsToUrl(sizeRestriction, url);
1670
1693
  }
1671
- const height = {
1672
- gallery: '',
1673
- single: '300px',
1674
- carousel: '', // Set from CSS
1675
- }[location];
1676
1694
  return {
1677
- url: (attachment.img_url ||
1678
- attachment.thumb_url ||
1679
- attachment.image_url ||
1680
- ''),
1695
+ url: url.href,
1681
1696
  width: '',
1682
1697
  height,
1683
1698
  };
@@ -1685,15 +1700,34 @@ class AttachmentConfigurationService {
1685
1700
  /**
1686
1701
  * Handles the configuration for video attachments, it's possible to provide your own function to override the default logic
1687
1702
  * @param attachment The attachment to configure
1703
+ * @param element The default resizing logics reads the height/max-height and max-width propperties of this element and reduces file size based on the given values. File size reduction is done by Stream's CDN.
1688
1704
  */
1689
- getVideoAttachmentConfiguration(attachment) {
1705
+ getVideoAttachmentConfiguration(attachment, element) {
1690
1706
  if (this.customVideoAttachmentConfigurationHandler) {
1691
- return this.customVideoAttachmentConfigurationHandler(attachment);
1707
+ return this.customVideoAttachmentConfigurationHandler(attachment, element);
1708
+ }
1709
+ let attachmentHeight = ``;
1710
+ let thumbUrl = undefined;
1711
+ if (attachment.thumb_url && this.shouldGenerateVideoThumbnail) {
1712
+ const url = new URL(attachment.thumb_url);
1713
+ const { sizeRestriction, height } = this.getSizingRestrictions(url, element);
1714
+ if (sizeRestriction) {
1715
+ sizeRestriction.height *= 2;
1716
+ sizeRestriction.width *= 2;
1717
+ this.addResizingParamsToUrl(sizeRestriction, url);
1718
+ }
1719
+ thumbUrl = url.href;
1720
+ attachmentHeight = height;
1721
+ }
1722
+ else {
1723
+ const cssSizeRestriction = this.getCSSSizeRestriction(element);
1724
+ attachmentHeight = `${cssSizeRestriction.maxHeight || cssSizeRestriction.height || ''}px`;
1692
1725
  }
1693
1726
  return {
1694
1727
  url: attachment.asset_url || '',
1695
- width: '100%',
1696
- height: '100%',
1728
+ width: '',
1729
+ height: attachmentHeight,
1730
+ thumbUrl: thumbUrl,
1697
1731
  };
1698
1732
  }
1699
1733
  /**
@@ -1726,6 +1760,52 @@ class AttachmentConfigurationService {
1726
1760
  height: '', // Set from CSS
1727
1761
  };
1728
1762
  }
1763
+ addResizingParamsToUrl(sizeRestriction, url) {
1764
+ url.searchParams.set('h', sizeRestriction.height.toString());
1765
+ url.searchParams.set('w', sizeRestriction.width.toString());
1766
+ }
1767
+ getSizingRestrictions(url, htmlElement) {
1768
+ const urlParams = url.searchParams;
1769
+ const originalHeight = Number(urlParams.get('oh')) || 1;
1770
+ const originalWidth = Number(urlParams.get('ow')) || 1;
1771
+ const cssSizeRestriction = this.getCSSSizeRestriction(htmlElement);
1772
+ let sizeRestriction;
1773
+ let height = '';
1774
+ if ((cssSizeRestriction.maxHeight || cssSizeRestriction.height) &&
1775
+ cssSizeRestriction.maxWidth) {
1776
+ sizeRestriction = this.getSizeRestrictions(originalHeight, originalWidth, (cssSizeRestriction.maxHeight || cssSizeRestriction.height), cssSizeRestriction.maxWidth);
1777
+ if (cssSizeRestriction.maxHeight) {
1778
+ const heightNum = originalHeight > 1 && originalWidth > 1
1779
+ ? originalHeight <= cssSizeRestriction.maxHeight &&
1780
+ originalWidth <= cssSizeRestriction.maxWidth
1781
+ ? originalHeight
1782
+ : Math.round(Math.min(cssSizeRestriction.maxHeight, (cssSizeRestriction.maxWidth / originalWidth) *
1783
+ originalHeight))
1784
+ : cssSizeRestriction.maxHeight;
1785
+ height = `${heightNum}px`;
1786
+ }
1787
+ }
1788
+ else {
1789
+ sizeRestriction = undefined;
1790
+ }
1791
+ return { sizeRestriction, height };
1792
+ }
1793
+ getSizeRestrictions(originalHeight, originalWidth, maxHeight, maxWidth) {
1794
+ return {
1795
+ height: Math.round(Math.max(maxHeight, (maxWidth / originalWidth) * originalHeight)),
1796
+ width: Math.round(Math.max(maxHeight, (maxWidth / originalHeight) * originalWidth)),
1797
+ };
1798
+ }
1799
+ getCSSSizeRestriction(htmlElement) {
1800
+ const computedStylesheet = getComputedStyle(htmlElement);
1801
+ const height = this.getValueRepresentationOfCSSProperty(computedStylesheet.getPropertyValue('height'));
1802
+ const maxHeight = this.getValueRepresentationOfCSSProperty(computedStylesheet.getPropertyValue('max-height'));
1803
+ const maxWidth = this.getValueRepresentationOfCSSProperty(computedStylesheet.getPropertyValue('max-width'));
1804
+ return { height, maxHeight, maxWidth };
1805
+ }
1806
+ getValueRepresentationOfCSSProperty(property) {
1807
+ return Number(property.replace('px', '')) || undefined;
1808
+ }
1729
1809
  }
1730
1810
  AttachmentConfigurationService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: AttachmentConfigurationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1731
1811
  AttachmentConfigurationService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: AttachmentConfigurationService, providedIn: 'root' });
@@ -2434,24 +2514,31 @@ class AttachmentListComponent {
2434
2514
  this.orderedAttachments = [];
2435
2515
  this.imagesToView = [];
2436
2516
  this.imagesToViewCurrentIndex = 0;
2517
+ this.attachmentConfigurations = new Map();
2437
2518
  this.themeVersion = themeService.themeVersion;
2438
2519
  }
2439
- ngOnChanges() {
2440
- const images = this.attachments.filter(this.isImage);
2441
- const containsGallery = images.length >= 2;
2442
- this.orderedAttachments = [
2443
- ...(containsGallery ? this.createGallery(images) : images),
2444
- ...this.attachments.filter((a) => this.isVideo(a)),
2445
- ...this.attachments.filter((a) => this.isFile(a)),
2446
- ];
2447
- // Display link attachments only if there are no other attachments
2448
- // Giphy-s always sent without other attachments
2449
- if (this.orderedAttachments.length === 0) {
2450
- this.orderedAttachments.push(...this.attachments.filter((a) => this.isCard(a)));
2520
+ ngOnChanges(changes) {
2521
+ if (changes.attachments) {
2522
+ const images = this.attachments.filter(this.isImage);
2523
+ const containsGallery = images.length >= 2;
2524
+ this.orderedAttachments = [
2525
+ ...(containsGallery ? this.createGallery(images) : images),
2526
+ ...this.attachments.filter((a) => this.isVideo(a)),
2527
+ ...this.attachments.filter((a) => this.isFile(a)),
2528
+ ];
2529
+ this.attachmentConfigurations = new Map();
2530
+ // Display link attachments only if there are no other attachments
2531
+ // Giphy-s always sent without other attachments
2532
+ if (this.orderedAttachments.length === 0) {
2533
+ this.orderedAttachments.push(...this.attachments.filter((a) => this.isCard(a)));
2534
+ }
2451
2535
  }
2452
2536
  }
2453
- trackById(index) {
2454
- return index;
2537
+ trackByUrl(_, attachment) {
2538
+ return (attachment.image_url ||
2539
+ attachment.img_url ||
2540
+ attachment.asset_url ||
2541
+ attachment.thumb_url);
2455
2542
  }
2456
2543
  isImage(attachment) {
2457
2544
  return isImageAttachment(attachment);
@@ -2517,18 +2604,39 @@ class AttachmentListComponent {
2517
2604
  trackByImageUrl(_, item) {
2518
2605
  return item.image_url || item.img_url || item.thumb_url;
2519
2606
  }
2520
- getImageAttachmentConfiguration(attachment, type) {
2521
- return this.attachmentConfigurationService.getImageAttachmentConfiguration(attachment, type);
2607
+ getImageAttachmentConfiguration(attachment, type, element) {
2608
+ const existingConfiguration = this.attachmentConfigurations.get(attachment);
2609
+ if (existingConfiguration) {
2610
+ return existingConfiguration;
2611
+ }
2612
+ const configuration = this.attachmentConfigurationService.getImageAttachmentConfiguration(attachment, type, element);
2613
+ this.attachmentConfigurations.set(attachment, configuration);
2614
+ return configuration;
2615
+ }
2616
+ getCarouselImageAttachmentConfiguration(attachment, element) {
2617
+ return this.attachmentConfigurationService.getImageAttachmentConfiguration(attachment, 'carousel', element);
2522
2618
  }
2523
- getVideoAttachmentConfiguration(attachment) {
2524
- return this.attachmentConfigurationService.getVideoAttachmentConfiguration(attachment);
2619
+ getVideoAttachmentConfiguration(attachment, element) {
2620
+ const existingConfiguration = this.attachmentConfigurations.get(attachment);
2621
+ if (existingConfiguration) {
2622
+ return existingConfiguration;
2623
+ }
2624
+ const configuration = this.attachmentConfigurationService.getVideoAttachmentConfiguration(attachment, element);
2625
+ this.attachmentConfigurations.set(attachment, configuration);
2626
+ return configuration;
2525
2627
  }
2526
2628
  getCardAttachmentConfiguration(attachment) {
2629
+ const existingConfiguration = this.attachmentConfigurations.get(attachment);
2630
+ if (existingConfiguration) {
2631
+ return existingConfiguration;
2632
+ }
2527
2633
  if (attachment.type === 'giphy') {
2528
2634
  return this.attachmentConfigurationService.getGiphyAttachmentConfiguration(attachment);
2529
2635
  }
2530
2636
  else {
2531
- return this.attachmentConfigurationService.getScrapedImageAttachmentConfiguration(attachment);
2637
+ const configuration = this.attachmentConfigurationService.getScrapedImageAttachmentConfiguration(attachment);
2638
+ this.attachmentConfigurations.set(attachment, configuration);
2639
+ return configuration;
2532
2640
  }
2533
2641
  }
2534
2642
  get isImageModalPrevButtonVisible() {
@@ -2550,7 +2658,7 @@ class AttachmentListComponent {
2550
2658
  }
2551
2659
  }
2552
2660
  AttachmentListComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: AttachmentListComponent, deps: [{ token: CustomTemplatesService }, { token: ChannelService }, { token: AttachmentConfigurationService }, { token: ThemeService }], target: i0.ɵɵFactoryTarget.Component });
2553
- AttachmentListComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.5", type: AttachmentListComponent, selector: "stream-attachment-list", inputs: { messageId: "messageId", parentMessageId: "parentMessageId", attachments: "attachments" }, 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: trackById\"\n >\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 [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 <img\n *ngIf=\"\n isImage(attachment) &&\n getImageAttachmentConfiguration(\n attachment,\n 'single'\n ) as attachmentConfiguration\n \"\n class=\"str-chat__message-attachment--img\"\n data-testclass=\"image\"\n [src]=\"attachmentConfiguration.url\"\n [alt]=\"attachment?.fallback\"\n (click)=\"openImageModal([attachment])\"\n (keyup.enter)=\"openImageModal([attachment])\"\n [ngStyle]=\"{\n height: attachmentConfiguration.height,\n width: attachmentConfiguration.width\n }\"\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 [class.str-chat__gallery-two-rows]=\"(attachment?.images)!.length > 2\"\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 [class.str-chat__message-attachment--svg-image]=\"\n isSvg(galleryImage)\n \"\n >\n <img\n *ngIf=\"\n getImageAttachmentConfiguration(\n galleryImage,\n 'gallery'\n ) as attachmentConfiguration\n \"\n [src]=\"attachmentConfiguration.url\"\n [alt]=\"galleryImage.fallback\"\n [ngStyle]=\"{\n height: attachmentConfiguration.height,\n width: attachmentConfiguration.width\n }\"\n />\n </button>\n <button\n *ngIf=\"\n index === 3 &&\n !isLast &&\n getImageAttachmentConfiguration(\n galleryImage,\n 'gallery'\n ) as attachmentConfiguration\n \"\n class=\"str-chat__gallery-placeholder\"\n data-testclass=\"gallery-image\"\n data-testid=\"more-image-button\"\n (click)=\"openImageModal(attachment.images!, index)\"\n (keyup.enter)=\"openImageModal(attachment.images!, index)\"\n [class.str-chat__message-attachment--svg-image]=\"\n isSvg(galleryImage)\n \"\n [ngStyle]=\"{\n 'background-image': 'url(' + attachmentConfiguration.url + ')',\n height: attachmentConfiguration.height,\n width: attachmentConfiguration.width\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 class=\"str-chat__player-wrapper\"\n *ngIf=\"\n isVideo(attachment) &&\n getVideoAttachmentConfiguration(attachment) as attachmentConfiguration\n \"\n >\n <video\n class=\"str-chat__video-angular\"\n controls\n data-testclass=\"video-attachment\"\n [src]=\"attachmentConfiguration.url\"\n [ngStyle]=\"{\n height: attachmentConfiguration.height,\n width: attachmentConfiguration.width\n }\"\n ></video>\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 *ngIf=\"themeVersion === '1'\"\n icon=\"file\"\n [size]=\"30\"\n ></stream-icon-placeholder>\n <stream-icon-placeholder\n *ngIf=\"themeVersion === '2'\"\n icon=\"unspecified-filetype\"\n [size]=\"30\"\n ></stream-icon-placeholder>\n <div class=\"str-chat__message-attachment-file--item-text\">\n <div class=\"str-chat__message-attachment-file--item-first-row\">\n <div\n data-testclass=\"file-title\"\n class=\"str-chat__message-attachment-file--item-name\"\n >\n {{ attachment.title }}\n </div>\n <a\n class=\"str-chat__message-attachment-file--item-download\"\n data-testclass=\"file-link\"\n download\n href=\"{{ attachment.asset_url }}\"\n target=\"_blank\"\n >\n <stream-icon-placeholder\n class=\"str-chat__message-attachment-download-icon\"\n icon=\"download\"\n ></stream-icon-placeholder>\n </a>\n </div>\n <span\n class=\"str-chat__message-attachment-file--item-size\"\n data-testclass=\"size\"\n *ngIf=\"hasFileSize(attachment)\"\n >{{ getFileSize(attachment) }}</span\n >\n </div>\n </div>\n <div\n *ngIf=\"\n isCard(attachment) &&\n getCardAttachmentConfiguration(attachment) as attachmentConfiguration\n \"\n class=\"str-chat__message-attachment-card str-chat__message-attachment-card--{{\n attachment.type\n }}\"\n >\n <div\n *ngIf=\"attachmentConfiguration.url\"\n class=\"str-chat__message-attachment-card--header\"\n >\n <img\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=\"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=\"\n let action of attachment.actions;\n trackBy: trackByActionValue\n \"\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</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 (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 str-chat__image-carousel\">\n <button\n class=\"\n stream-chat-angular__image-modal-stepper\n str-chat__image-carousel-stepper\n \"\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 *ngIf=\"\n getImageAttachmentConfiguration(\n imagesToView[imagesToViewCurrentIndex],\n 'carousel'\n ) as attachmentConfiguration\n \"\n class=\"\n stream-chat-angular__image-modal-image\n str-chat__image-carousel-image\n \"\n data-testid=\"modal-image\"\n [src]=\"attachmentConfiguration.url\"\n [alt]=\"imagesToView[imagesToViewCurrentIndex].fallback\"\n [ngStyle]=\"{\n width: attachmentConfiguration.width,\n height: attachmentConfiguration.height\n }\"\n />\n <button\n class=\"\n stream-chat-angular__image-modal-stepper\n str-chat__image-carousel-stepper\n \"\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: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i4.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { type: i4.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { type: i4.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet"] }], pipes: { "translate": i6.TranslatePipe, "async": i4.AsyncPipe } });
2661
+ AttachmentListComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.5", type: AttachmentListComponent, selector: "stream-attachment-list", inputs: { messageId: "messageId", parentMessageId: "parentMessageId", attachments: "attachments" }, 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-with-actions]=\"\n attachment.actions && attachment.actions.length > 0\n \"\n [class.str-chat__message-attachment--svg-image]=\"isSvg(attachment)\"\n >\n <img\n #imgElement\n *ngIf=\"isImage(attachment)\"\n class=\"str-chat__message-attachment--img\"\n data-testclass=\"image\"\n [src]=\"\n getImageAttachmentConfiguration(attachment, 'single', imgElement).url\n \"\n [alt]=\"attachment?.fallback\"\n (click)=\"openImageModal([attachment])\"\n (keyup.enter)=\"openImageModal([attachment])\"\n [ngStyle]=\"{\n height: getImageAttachmentConfiguration(\n attachment,\n 'single',\n imgElement\n ).height,\n width: getImageAttachmentConfiguration(\n attachment,\n 'single',\n imgElement\n ).width\n }\"\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 [class.str-chat__gallery-two-rows]=\"(attachment?.images)!.length > 2\"\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 [class.str-chat__message-attachment--svg-image]=\"\n isSvg(galleryImage)\n \"\n >\n <img\n #imgElement\n [src]=\"\n getImageAttachmentConfiguration(\n galleryImage,\n 'gallery',\n imgElement\n ).url\n \"\n [alt]=\"galleryImage.fallback\"\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 #element\n *ngIf=\"index === 3 && !isLast\"\n class=\"str-chat__gallery-placeholder\"\n data-testclass=\"gallery-image\"\n data-testid=\"more-image-button\"\n (click)=\"openImageModal(attachment.images!, index)\"\n (keyup.enter)=\"openImageModal(attachment.images!, index)\"\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 }\"\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 class=\"str-chat__player-wrapper\" *ngIf=\"isVideo(attachment)\">\n <video\n #videoElement\n class=\"str-chat__video-angular\"\n controls\n data-testclass=\"video-attachment\"\n [src]=\"getVideoAttachmentConfiguration(attachment, videoElement).url\"\n [ngStyle]=\"{\n height: getVideoAttachmentConfiguration(attachment, videoElement)\n .height,\n width: getVideoAttachmentConfiguration(attachment, videoElement)\n .width\n }\"\n [poster]=\"\n getVideoAttachmentConfiguration(attachment, videoElement).thumbUrl\n \"\n ></video>\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 *ngIf=\"themeVersion === '1'\"\n icon=\"file\"\n [size]=\"30\"\n ></stream-icon-placeholder>\n <stream-icon-placeholder\n *ngIf=\"themeVersion === '2'\"\n icon=\"unspecified-filetype\"\n [size]=\"30\"\n ></stream-icon-placeholder>\n <div class=\"str-chat__message-attachment-file--item-text\">\n <div class=\"str-chat__message-attachment-file--item-first-row\">\n <div\n data-testclass=\"file-title\"\n class=\"str-chat__message-attachment-file--item-name\"\n >\n {{ attachment.title }}\n </div>\n <a\n class=\"str-chat__message-attachment-file--item-download\"\n data-testclass=\"file-link\"\n download\n href=\"{{ attachment.asset_url }}\"\n target=\"_blank\"\n >\n <stream-icon-placeholder\n class=\"str-chat__message-attachment-download-icon\"\n icon=\"download\"\n ></stream-icon-placeholder>\n </a>\n </div>\n <span\n class=\"str-chat__message-attachment-file--item-size\"\n data-testclass=\"size\"\n *ngIf=\"hasFileSize(attachment)\"\n >{{ getFileSize(attachment) }}</span\n >\n </div>\n </div>\n <div\n *ngIf=\"\n isCard(attachment) &&\n getCardAttachmentConfiguration(attachment) as attachmentConfiguration\n \"\n class=\"str-chat__message-attachment-card str-chat__message-attachment-card--{{\n attachment.type\n }}\"\n >\n <div\n *ngIf=\"attachmentConfiguration.url\"\n class=\"str-chat__message-attachment-card--header\"\n >\n <img\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=\"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=\"\n let action of attachment.actions;\n trackBy: trackByActionValue\n \"\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</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 (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 str-chat__image-carousel\">\n <button\n class=\"\n stream-chat-angular__image-modal-stepper\n str-chat__image-carousel-stepper\n \"\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 #imgElement\n class=\"\n stream-chat-angular__image-modal-image\n str-chat__image-carousel-image\n \"\n data-testid=\"modal-image\"\n [src]=\"\n getCarouselImageAttachmentConfiguration(\n imagesToView[imagesToViewCurrentIndex],\n imgElement\n ).url\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 <button\n class=\"\n stream-chat-angular__image-modal-stepper\n str-chat__image-carousel-stepper\n \"\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: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i4.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { type: i4.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { type: i4.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet"] }], pipes: { "translate": i6.TranslatePipe, "async": i4.AsyncPipe } });
2554
2662
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.5", ngImport: i0, type: AttachmentListComponent, decorators: [{
2555
2663
  type: Component,
2556
2664
  args: [{
@@ -3777,8 +3885,14 @@ class MessageComponent {
3777
3885
  this.subscriptions.push(this.customTemplatesService.messageReactionsTemplate$.subscribe((template) => (this.messageReactionsTemplate = template)));
3778
3886
  }
3779
3887
  ngOnChanges(changes) {
3888
+ var _a, _b;
3780
3889
  if (changes.message) {
3781
3890
  this.createMessageParts();
3891
+ const originalAttachments = (_b = (_a = this.message) === null || _a === void 0 ? void 0 : _a.quoted_message) === null || _b === void 0 ? void 0 : _b.attachments;
3892
+ this.quotedMessageAttachments =
3893
+ originalAttachments && originalAttachments.length
3894
+ ? [originalAttachments[0]]
3895
+ : [];
3782
3896
  }
3783
3897
  if (changes.enabledMessageActions) {
3784
3898
  this.canReactToMessage =
@@ -3855,13 +3969,6 @@ class MessageComponent {
3855
3969
  return (this.canReceiveReadEvents !== false &&
3856
3970
  this.enabledMessageActions.indexOf('read-events') !== -1);
3857
3971
  }
3858
- get quotedMessageAttachments() {
3859
- var _a, _b;
3860
- const originalAttachments = (_b = (_a = this.message) === null || _a === void 0 ? void 0 : _a.quoted_message) === null || _b === void 0 ? void 0 : _b.attachments;
3861
- return originalAttachments && originalAttachments.length
3862
- ? [originalAttachments[0]]
3863
- : [];
3864
- }
3865
3972
  getAttachmentListContext() {
3866
3973
  var _a, _b, _c;
3867
3974
  return {
@@ -4605,7 +4712,7 @@ class MessageListComponent {
4605
4712
  (((_b = this.parentMessageElement) === null || _b === void 0 ? void 0 : _b.nativeElement.clientHeight) || 0))) {
4606
4713
  position = 'top';
4607
4714
  }
4608
- else if (Math.round(this.scrollContainer.nativeElement.scrollTop) +
4715
+ else if (Math.ceil(this.scrollContainer.nativeElement.scrollTop) +
4609
4716
  this.scrollContainer.nativeElement.clientHeight >=
4610
4717
  this.scrollContainer.nativeElement.scrollHeight) {
4611
4718
  position = 'bottom';