@banta/sdk 4.3.2 → 4.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/bundles/banta-sdk.umd.js +574 -117
  2. package/bundles/banta-sdk.umd.js.map +1 -1
  3. package/bundles/banta-sdk.umd.min.js +1 -1
  4. package/bundles/banta-sdk.umd.min.js.map +1 -1
  5. package/esm2015/lib/attachment-scraper.js +87 -0
  6. package/esm2015/lib/banta-sdk.module.js +6 -2
  7. package/esm2015/lib/chat-backend-base.js +20 -1
  8. package/esm2015/lib/chat-backend.js +19 -1
  9. package/esm2015/lib/comments/banta-comments/banta-comments.component.js +1 -1
  10. package/esm2015/lib/comments/comment/comment.component.js +9 -12
  11. package/esm2015/lib/comments/comment-field/comment-field.component.js +89 -7
  12. package/esm2015/lib/comments/comments.module.js +6 -2
  13. package/esm2015/lib/common/attachment/attachment.component.js +55 -0
  14. package/esm2015/lib/common/attachments/attachments.component.js +47 -0
  15. package/esm2015/lib/common/common.module.js +13 -3
  16. package/esm2015/lib/common/index.js +4 -1
  17. package/esm2015/lib/common/trust-resource-url.pipe.js +21 -0
  18. package/esm2015/lib/emoji/emoji-selector-button.component.js +56 -105
  19. package/esm2015/lib/emoji/emoji.module.js +6 -2
  20. package/fesm2015/banta-sdk.js +404 -127
  21. package/fesm2015/banta-sdk.js.map +1 -1
  22. package/lib/attachment-scraper.d.ts +30 -0
  23. package/lib/chat-backend-base.d.ts +9 -0
  24. package/lib/chat-backend.d.ts +2 -1
  25. package/lib/comments/comment/comment.component.d.ts +2 -4
  26. package/lib/comments/comment-field/comment-field.component.d.ts +11 -2
  27. package/lib/common/attachment/attachment.component.d.ts +18 -0
  28. package/lib/common/attachments/attachments.component.d.ts +15 -0
  29. package/lib/common/index.d.ts +3 -0
  30. package/lib/common/trust-resource-url.pipe.d.ts +7 -0
  31. package/lib/emoji/emoji-selector-button.component.d.ts +13 -14
  32. package/package.json +1 -1
@@ -1,12 +1,15 @@
1
1
  import { Observable, Subject, BehaviorSubject, Subscription } from 'rxjs';
2
2
  import { publish } from 'rxjs/operators';
3
- import { Component, Input, ViewChild, Pipe, NgModule, Output, ElementRef, HostBinding, Directive, NgZone, ContentChild, TemplateRef, Injectable, Inject } from '@angular/core';
3
+ import { Component, Input, ViewChild, Pipe, Output, HostBinding, NgModule, ElementRef, Injectable, Directive, NgZone, ContentChild, TemplateRef, Inject } from '@angular/core';
4
4
  import { marked, Renderer } from 'marked';
5
5
  import { sanitize } from 'dompurify';
6
6
  import { DomSanitizer } from '@angular/platform-browser';
7
7
  import { CommonModule } from '@angular/common';
8
8
  import { MatIconModule } from '@angular/material/icon';
9
+ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
9
10
  import { MatButtonModule } from '@angular/material/button';
11
+ import { Overlay, OverlayModule } from '@angular/cdk/overlay';
12
+ import { PortalModule } from '@angular/cdk/portal';
10
13
  import { MatFormFieldModule } from '@angular/material/form-field';
11
14
  import { MatInputModule } from '@angular/material/input';
12
15
  import { FormsModule } from '@angular/forms';
@@ -16,7 +19,6 @@ import { CommentsOrder, CDNProvider, SocketRPC, RpcEvent, DurableSocket } from '
16
19
  import { ActivatedRoute } from '@angular/router';
17
20
  import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
18
21
  import { MatMenuModule } from '@angular/material/menu';
19
- import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
20
22
  import { TextFieldModule } from '@angular/cdk/text-field';
21
23
  import { MatTooltipModule } from '@angular/material/tooltip';
22
24
  import { MatSelectModule } from '@angular/material/select';
@@ -220,10 +222,129 @@ BantaMarkdownToHtmlPipe.ctorParameters = () => [
220
222
  { type: DomSanitizer }
221
223
  ];
222
224
 
225
+ class BantaTrustResourceUrlPipe {
226
+ constructor(sanitizer) {
227
+ this.sanitizer = sanitizer;
228
+ }
229
+ transform(value) {
230
+ if (!value)
231
+ return undefined;
232
+ return this.sanitizer.bypassSecurityTrustResourceUrl(value);
233
+ }
234
+ }
235
+ BantaTrustResourceUrlPipe.decorators = [
236
+ { type: Pipe, args: [{
237
+ name: 'trustResourceUrl'
238
+ },] }
239
+ ];
240
+ BantaTrustResourceUrlPipe.ctorParameters = () => [
241
+ { type: DomSanitizer }
242
+ ];
243
+
244
+ class BantaAttachmentComponent {
245
+ constructor() {
246
+ this.loading = false;
247
+ this.editing = false;
248
+ this.loadingMessage = 'Please wait...';
249
+ this.error = false;
250
+ this.errorMessage = 'An error has occurred';
251
+ this.removed = new Subject();
252
+ this.activated = new Subject();
253
+ }
254
+ activate() {
255
+ this.activated.next();
256
+ }
257
+ remove() {
258
+ this.removed.next();
259
+ }
260
+ get isError() {
261
+ var _a, _b;
262
+ return this.error || ((_b = (_a = this.attachment) === null || _a === void 0 ? void 0 : _a.transientState) === null || _b === void 0 ? void 0 : _b.error);
263
+ }
264
+ get theErrorMessage() {
265
+ var _a, _b;
266
+ return this.errorMessage || ((_b = (_a = this.attachment) === null || _a === void 0 ? void 0 : _a.transientState) === null || _b === void 0 ? void 0 : _b.errorMessage);
267
+ }
268
+ get isLoading() {
269
+ var _a;
270
+ return this.loading || !this.attachment || ((_a = this.attachment.transientState) === null || _a === void 0 ? void 0 : _a.loading) || !this.attachment.url;
271
+ }
272
+ get isImageAttachment() {
273
+ if (this.attachment.type.startsWith('image/'))
274
+ return true;
275
+ return false;
276
+ }
277
+ }
278
+ BantaAttachmentComponent.decorators = [
279
+ { type: Component, args: [{
280
+ selector: 'banta-attachment',
281
+ template: "<button type=\"button\" (click)=\"remove()\" mat-mini-fab color=\"primary\" class=\"remove-button\" *ngIf=\"editing\">\r\n <mat-icon>close</mat-icon>\r\n</button>\r\n\r\n<ng-container *ngIf=\"isError\">\r\n <mat-icon class=\"error\">close</mat-icon>\r\n <em class=\"error\">{{theErrorMessage}}</em>\r\n</ng-container>\r\n<ng-container *ngIf=\"!isError\">\r\n <ng-container *ngIf=\"isLoading\">\r\n <mat-spinner></mat-spinner>\r\n <em>{{loadingMessage}}</em>\r\n </ng-container>\r\n <ng-container *ngIf=\"!isLoading\">\r\n <iframe \r\n *ngIf=\"attachment.type === 'iframe'\" \r\n sandbox=\"allow-scripts allow-popups allow-same-origin allow-presentation\" \r\n [src]=\"attachment.url | trustResourceUrl\"></iframe>\r\n <a *ngIf=\"attachment.type === 'card'\" class=\"card\" [href]=\"attachment.url\" target=\"_blank\">\r\n <img \r\n *ngIf=\"attachment.card.image\"\r\n class=\"thumbnail\" \r\n [src]=\"attachment.card.image\"\r\n />\r\n <div class=\"description\">\r\n <h1>{{attachment.card.title}}</h1>\r\n {{attachment.card.description}}\r\n </div>\r\n </a>\r\n <a class=\"image-attachment\" *ngIf=\"isImageAttachment\" href=\"javascript:;\" (click)=\"activate()\">\r\n <img [src]=\"attachment.url\" alt=\"Image Attachment\">\r\n </a>\r\n </ng-container>\r\n</ng-container>",
282
+ styles: [":host{position:relative;display:block}:host.loading{outline:1px solid #333;padding:1em 0;width:300px;text-align:center}:host.loading mat-spinner{display:block;margin:0 auto .5em;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content}a.card{display:flex;align-items:flex-start;gap:1em;width:100%;border:1px solid #666;border-radius:4px;padding:1em;box-sizing:border-box;background-color:#191919}a.card img{width:300px;aspect-ratio:16/9;-o-object-fit:cover;object-fit:cover;border-radius:10px}a.card h1{margin:0;font-size:30px}.remove-button{position:absolute;right:10px;top:10px;margin:0;z-index:1}a.image-attachment{width:300px;position:relative;text-align:center}a.image-attachment.with-border{outline:1px solid #333;padding:1em 0}a.image-attachment mat-spinner{display:block;margin:0 auto .5em;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content}a.image-attachment mat-icon.error{display:block;font-size:48px;width:48px;height:48px;margin:0 auto .5em}a.image-attachment .error{color:#b76363}a.image-attachment img{width:300px;border-radius:10px;max-width:100%;max-height:20em;-o-object-fit:cover;object-fit:cover}iframe{border:none;width:100%;aspect-ratio:16/9}"]
283
+ },] }
284
+ ];
285
+ BantaAttachmentComponent.propDecorators = {
286
+ attachment: [{ type: Input }],
287
+ loading: [{ type: Input }],
288
+ editing: [{ type: Input }],
289
+ loadingMessage: [{ type: Input }],
290
+ error: [{ type: Input }],
291
+ errorMessage: [{ type: Input }],
292
+ removed: [{ type: Output }],
293
+ activated: [{ type: Output }],
294
+ isLoading: [{ type: HostBinding, args: ['class.loading',] }]
295
+ };
296
+
297
+ class BantaAttachmentsComponent {
298
+ constructor() {
299
+ this.editing = false;
300
+ this.remove = new Subject();
301
+ }
302
+ removeAttachment(attachment) {
303
+ this.remove.next(attachment);
304
+ }
305
+ isImageAttachment(attachment) {
306
+ if (attachment.type.startsWith('image/'))
307
+ return true;
308
+ return false;
309
+ }
310
+ isCardAttachment(attachment) {
311
+ if (['card'].includes(attachment.type))
312
+ return true;
313
+ return false;
314
+ }
315
+ showLightbox(image) {
316
+ this.lightbox.open(image.url, this.attachments
317
+ .filter(x => x.type === 'image/png')
318
+ .map(x => x.url));
319
+ }
320
+ get inlineAttachments() {
321
+ return this.attachments.filter(x => x.type !== 'card' && (x.style === 'inline' || !x.style));
322
+ }
323
+ get blockAttachments() {
324
+ return this.attachments.filter(x => x.style === 'block' || x.type === 'card');
325
+ }
326
+ }
327
+ BantaAttachmentsComponent.decorators = [
328
+ { type: Component, args: [{
329
+ selector: 'banta-attachments',
330
+ template: "<ng-container *ngIf=\"attachments?.length > 0\">\r\n <banta-lightbox #lightbox></banta-lightbox>\r\n <div class=\"block-attachments\">\r\n <ng-container *ngFor=\"let attachment of blockAttachments\">\r\n <banta-attachment \r\n [attachment]=\"attachment\"\r\n [editing]=\"editing\"\r\n (removed)=\"removeAttachment(attachment)\"\r\n (activated)=\"showLightbox(attachment)\"\r\n ></banta-attachment>\r\n </ng-container>\r\n </div>\r\n\r\n <div \r\n class=\"inline-attachments\" \r\n [class.single]=\"attachments?.length === 1\" \r\n *ngIf=\"attachments && attachments?.length > 0\"\r\n >\r\n <ng-container *ngFor=\"let attachment of inlineAttachments\">\r\n <banta-attachment \r\n [attachment]=\"attachment\"\r\n [editing]=\"editing\"\r\n (removed)=\"removeAttachment(attachment)\"\r\n (activated)=\"showLightbox(attachment)\"\r\n ></banta-attachment>\r\n </ng-container>\r\n </div>\r\n</ng-container>",
331
+ styles: [".block-attachments{display:flex;flex-direction:column}.block-attachments banta-attachment{width:100%}.inline-attachments{flex-direction:row;margin-top:15px;display:flex;gap:20px;flex-wrap:wrap}"]
332
+ },] }
333
+ ];
334
+ BantaAttachmentsComponent.propDecorators = {
335
+ attachments: [{ type: Input }],
336
+ editing: [{ type: Input }],
337
+ lightbox: [{ type: ViewChild, args: ['lightbox',] }],
338
+ remove: [{ type: Output }]
339
+ };
340
+
223
341
  const COMPONENTS = [
224
342
  TimestampComponent,
225
343
  LightboxComponent,
226
- BantaMarkdownToHtmlPipe
344
+ BantaMarkdownToHtmlPipe,
345
+ BantaTrustResourceUrlPipe,
346
+ BantaAttachmentComponent,
347
+ BantaAttachmentsComponent
227
348
  ];
228
349
  class BantaCommonModule {
229
350
  }
@@ -232,7 +353,9 @@ BantaCommonModule.decorators = [
232
353
  declarations: COMPONENTS,
233
354
  imports: [
234
355
  CommonModule,
235
- MatIconModule
356
+ MatIconModule,
357
+ MatProgressSpinnerModule,
358
+ MatButtonModule
236
359
  ],
237
360
  exports: COMPONENTS
238
361
  },] }
@@ -6830,100 +6953,63 @@ EmojiSelectorPanelComponent.propDecorators = {
6830
6953
 
6831
6954
  /// <reference types="@types/resize-observer-browser" />
6832
6955
  class EmojiSelectorButtonComponent {
6833
- constructor(elementRef) {
6956
+ constructor(elementRef, overlay) {
6834
6957
  this.elementRef = elementRef;
6958
+ this.overlay = overlay;
6835
6959
  this._selected = new Subject();
6836
6960
  this.showEmojiPanel = false;
6837
6961
  }
6838
6962
  get selected() {
6839
6963
  return this._selected;
6840
6964
  }
6841
- ngOnDestroy() {
6842
- this.removeListener();
6843
- this.panelElement.nativeElement.remove();
6965
+ get isOpen() {
6966
+ return this.overlayRef;
6844
6967
  }
6845
- get widthConstrained() { return this.width < 700; }
6846
- ngAfterViewInit() {
6968
+ /**
6969
+ * Insert the given emoji.
6970
+ * @param str
6971
+ */
6972
+ insert(str) {
6973
+ this._selected.next(str);
6847
6974
  }
6848
- putPanelAtRoot() {
6849
- // If we are in full-screen, placing the panel outside of the full-screen element will result in it
6850
- // always being behind said full-screen element, so we need to ensure we never place it further up the
6851
- // stack.
6852
- let root = document.fullscreenElement || document.body.querySelector('[ng-version]') || document.body;
6853
- root.appendChild(this.panelElement.nativeElement);
6854
- }
6855
- removeListener() {
6856
- document.removeEventListener('click', this.clickListener);
6857
- window.removeEventListener('resize', this.resizeListener);
6858
- }
6859
- place() {
6860
- // Not currently used as it can't be easily done handling all
6861
- // scrolling corner cases.
6862
- this.putPanelAtRoot();
6863
- let pos = this.buttonElement.nativeElement.getBoundingClientRect();
6864
- let size = this.panelElement.nativeElement.getBoundingClientRect();
6865
- let left = window.scrollX + pos.left + pos.width - size.width;
6866
- if (left < 0)
6867
- left = (window.scrollX + window.innerWidth) / 2 - size.width / 2;
6868
- let scrollY = window.scrollY;
6869
- if (document.fullscreenElement) {
6975
+ close() {
6976
+ if (this.overlayRef) {
6977
+ this.overlayRef.dispose();
6978
+ this.overlayRef = null;
6979
+ return;
6870
6980
  }
6871
- Object.assign(this.panelElement.nativeElement.style, {
6872
- top: `${window.scrollY + pos.top + pos.height}px`,
6873
- left: `${Math.max(0, left)}px`
6874
- });
6875
6981
  }
6876
6982
  show() {
6877
- if (this.showEmojiPanel) {
6878
- this.showEmojiPanel = false;
6879
- return;
6983
+ if (this.isOpen) {
6984
+ this.close();
6880
6985
  }
6881
- this.showEmojiPanel = true;
6882
- //this.place();
6883
- setTimeout(() => {
6884
- let onResize = () => {
6885
- if (!this.showEmojiPanel)
6886
- return;
6887
- this.width = window.innerWidth;
6888
- this.height = window.innerHeight;
6889
- let edgeOffset = 0;
6890
- let commentField = this.elementRef.nativeElement.closest(`banta-comment-field`);
6891
- if (commentField) {
6892
- let size = commentField.getBoundingClientRect();
6893
- this.width = size.width;
6894
- edgeOffset = window.innerWidth - size.right;
6895
- }
6896
- let buttonRect = this.buttonElement.nativeElement.getBoundingClientRect();
6897
- let buttonRight = window.innerWidth - buttonRect.right - edgeOffset - 10;
6898
- if (this.width < 700) {
6899
- this.panelElement.nativeElement.style.right = `${-buttonRight}px`;
6900
- }
6901
- else {
6902
- this.panelElement.nativeElement.style.right = '';
6903
- }
6904
- this.panelElement.nativeElement.style.maxWidth = `${this.width - 15}px`;
6905
- };
6906
- this.resizeListener = onResize;
6907
- onResize();
6908
- this.clickListener = (ev) => {
6909
- let parent = ev.target;
6910
- let isInDialog = false;
6911
- while (parent) {
6912
- if (parent.matches('emoji-selector-panel'))
6913
- isInDialog = true;
6914
- parent = parent.parentElement;
6986
+ this.overlayRef = this.overlay.create({
6987
+ positionStrategy: this.overlay.position()
6988
+ .flexibleConnectedTo(this.elementRef)
6989
+ .withPositions([
6990
+ {
6991
+ originX: 'end',
6992
+ originY: 'bottom',
6993
+ overlayX: 'end',
6994
+ overlayY: 'top'
6915
6995
  }
6916
- if (isInDialog)
6917
- return;
6918
- this.showEmojiPanel = false;
6919
- this.removeListener();
6920
- };
6921
- document.addEventListener('click', this.clickListener);
6922
- window.addEventListener('resize', this.resizeListener);
6996
+ ])
6997
+ .withFlexibleDimensions(true),
6998
+ hasBackdrop: true,
6999
+ disposeOnNavigation: true,
7000
+ scrollStrategy: this.overlay.scrollStrategies.reposition({
7001
+ autoClose: true
7002
+ })
6923
7003
  });
6924
- }
6925
- insert(str) {
6926
- this._selected.next(str);
7004
+ this.overlayRef.backdropClick().subscribe(() => {
7005
+ this.close();
7006
+ });
7007
+ this.overlayRef.keydownEvents().subscribe(event => {
7008
+ if (event.key === 'Escape') {
7009
+ this.close();
7010
+ }
7011
+ });
7012
+ this.overlayRef.attach(this.selectorPanelTemplate);
6927
7013
  }
6928
7014
  }
6929
7015
  EmojiSelectorButtonComponent.decorators = [
@@ -6933,11 +7019,12 @@ EmojiSelectorButtonComponent.decorators = [
6933
7019
  <button #button type="button" mat-icon-button (click)="show()">
6934
7020
  <mat-icon>emoji_emotions</mat-icon>
6935
7021
  </button>
6936
- <emoji-selector-panel
6937
- #panel
6938
- (selected)="insert($event)"
6939
- [class.visible]="showEmojiPanel"
6940
- ></emoji-selector-panel>
7022
+ <ng-template cdkPortal #selectorPanelTemplate="cdkPortal">
7023
+ <emoji-selector-panel
7024
+ #panel
7025
+ (selected)="insert($event)"
7026
+ ></emoji-selector-panel>
7027
+ </ng-template>
6941
7028
  `,
6942
7029
  styles: [`
6943
7030
  :host {
@@ -6945,20 +7032,6 @@ EmojiSelectorButtonComponent.decorators = [
6945
7032
  position: relative;
6946
7033
  }
6947
7034
 
6948
- emoji-selector-panel {
6949
- position: absolute;
6950
- top: 2.5em;
6951
- right: 0;
6952
- opacity: 0;
6953
- pointer-events: none;
6954
- z-index: 10;
6955
- }
6956
-
6957
- emoji-selector-panel.visible {
6958
- pointer-events: initial;
6959
- opacity: 1;
6960
- }
6961
-
6962
7035
  button {
6963
7036
  color: #666
6964
7037
  }
@@ -6966,13 +7039,12 @@ EmojiSelectorButtonComponent.decorators = [
6966
7039
  },] }
6967
7040
  ];
6968
7041
  EmojiSelectorButtonComponent.ctorParameters = () => [
6969
- { type: ElementRef }
7042
+ { type: ElementRef },
7043
+ { type: Overlay }
6970
7044
  ];
6971
7045
  EmojiSelectorButtonComponent.propDecorators = {
6972
- selected: [{ type: Output }],
6973
- panelElement: [{ type: ViewChild, args: ['panel', { read: ElementRef },] }],
6974
- buttonElement: [{ type: ViewChild, args: ['button', { read: ElementRef },] }],
6975
- widthConstrained: [{ type: HostBinding, args: ['class.width-constrained',] }]
7046
+ selectorPanelTemplate: [{ type: ViewChild, args: ['selectorPanelTemplate',] }],
7047
+ selected: [{ type: Output }]
6976
7048
  };
6977
7049
 
6978
7050
  const COMPONENTS$1 = [
@@ -6990,7 +7062,9 @@ EmojiModule.decorators = [
6990
7062
  MatIconModule,
6991
7063
  MatButtonModule,
6992
7064
  MatFormFieldModule,
6993
- MatInputModule
7065
+ MatInputModule,
7066
+ OverlayModule,
7067
+ PortalModule
6994
7068
  ],
6995
7069
  exports: COMPONENTS$1
6996
7070
  },] }
@@ -7050,9 +7124,99 @@ ChatMessageComponent.propDecorators = {
7050
7124
  upvoted: [{ type: Output }]
7051
7125
  };
7052
7126
 
7127
+ const URL_REGEX = new RegExp('(?!mailto:)(?:(?:http|https|ftp)://)(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[0-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))|localhost)(?::\\d{2,5})?(?:(/|\\?|#)[^\\s]*)?', 'ig');
7128
+ class UrlAttachmentScraper {
7129
+ findFragments(message) {
7130
+ var _a;
7131
+ // If a message already has a URL attachment, don't add another one.
7132
+ if (message.attachments && message.attachments.filter(x => x.type === 'url').length > 0)
7133
+ return null;
7134
+ return (Array.from((_a = message.message.match(URL_REGEX)) !== null && _a !== void 0 ? _a : []))
7135
+ .reduce((a, item) => (a.includes(item) ? undefined : a.push(item), a), [])
7136
+ .map(url => ({
7137
+ text: url,
7138
+ offset: message.message.indexOf(url),
7139
+ type: 'url'
7140
+ }));
7141
+ }
7142
+ }
7143
+ class GiphyAttachmentResolver {
7144
+ resolveFragment(message, fragment) {
7145
+ var _a;
7146
+ return __awaiter(this, void 0, void 0, function* () {
7147
+ if (fragment.type === 'url' && fragment.text.startsWith('https://giphy.com/gifs')) {
7148
+ let gifId = (_a = /[^-\/]+$/.exec(fragment.text)) === null || _a === void 0 ? void 0 : _a.toString();
7149
+ if (!gifId)
7150
+ return null;
7151
+ return {
7152
+ type: 'iframe',
7153
+ url: `https://giphy.com/embed/${gifId}`,
7154
+ style: 'inline'
7155
+ };
7156
+ }
7157
+ return null;
7158
+ });
7159
+ }
7160
+ }
7161
+ class YouTubeAttachmentResolver {
7162
+ resolveFragment(message, fragment) {
7163
+ return __awaiter(this, void 0, void 0, function* () {
7164
+ if (fragment.type !== 'url')
7165
+ return null;
7166
+ let videoId;
7167
+ if (fragment.text.match(/https?:\/\/(www\.)?youtube.com\/watch\?v=/)) {
7168
+ let match = /watch\?v=([^&]+)/.exec(fragment.text);
7169
+ if (match) {
7170
+ videoId = match[1];
7171
+ }
7172
+ }
7173
+ if (videoId) {
7174
+ return {
7175
+ type: 'iframe',
7176
+ url: `https://www.youtube.com/embed/${videoId}`,
7177
+ style: 'block'
7178
+ };
7179
+ }
7180
+ return null;
7181
+ });
7182
+ }
7183
+ }
7184
+ class UrlAttachmentResolver {
7185
+ constructor(backend) {
7186
+ this.backend = backend;
7187
+ }
7188
+ resolveFragment(message, fragment) {
7189
+ return __awaiter(this, void 0, void 0, function* () {
7190
+ if (fragment.type !== 'url')
7191
+ return null;
7192
+ let urlCard = yield this.backend.getCardForUrl(fragment.text);
7193
+ if (urlCard) {
7194
+ return {
7195
+ type: 'card',
7196
+ url: fragment.text,
7197
+ card: urlCard,
7198
+ style: 'block'
7199
+ };
7200
+ }
7201
+ });
7202
+ }
7203
+ }
7204
+ UrlAttachmentResolver.decorators = [
7205
+ { type: Injectable }
7206
+ ];
7207
+ UrlAttachmentResolver.ctorParameters = () => [
7208
+ { type: ChatBackendBase }
7209
+ ];
7210
+
7053
7211
  class ChatBackendBase {
7054
7212
  constructor() {
7055
7213
  this._userChanged = new BehaviorSubject(null);
7214
+ this._attachmentScrapers = [];
7215
+ this._attachmentResolvers = [];
7216
+ this.registerAttachmentScraper(new UrlAttachmentScraper());
7217
+ this.registerAttachmentResolver(new GiphyAttachmentResolver());
7218
+ this.registerAttachmentResolver(new YouTubeAttachmentResolver());
7219
+ this.registerAttachmentResolver(new UrlAttachmentResolver(this));
7056
7220
  }
7057
7221
  get userChanged() {
7058
7222
  return this._userChanged;
@@ -7064,6 +7228,18 @@ class ChatBackendBase {
7064
7228
  get user() {
7065
7229
  return this._user;
7066
7230
  }
7231
+ registerAttachmentScraper(scraper) {
7232
+ this._attachmentScrapers.push(scraper);
7233
+ }
7234
+ registerAttachmentResolver(resolver) {
7235
+ this._attachmentResolvers.push(resolver);
7236
+ }
7237
+ get attachmentScrapers() {
7238
+ return this._attachmentScrapers.slice();
7239
+ }
7240
+ get attachmentResolvers() {
7241
+ return this._attachmentResolvers.slice();
7242
+ }
7067
7243
  }
7068
7244
 
7069
7245
  class LiveChatMessageComponent {
@@ -7836,11 +8012,6 @@ class CommentComponent {
7836
8012
  this._avatarSelected.next(user);
7837
8013
  this.selectUser();
7838
8014
  }
7839
- showLightbox(image) {
7840
- this.lightbox.open(image.url, this.message.attachments
7841
- .filter(x => x.type === 'image/png')
7842
- .map(x => x.url));
7843
- }
7844
8015
  avatarForUser(user) {
7845
8016
  let url = this.genericAvatarUrl;
7846
8017
  if (user && user.avatarUrl) {
@@ -7848,12 +8019,16 @@ class CommentComponent {
7848
8019
  }
7849
8020
  return `url(${url})`;
7850
8021
  }
8022
+ get replyCount() {
8023
+ var _a;
8024
+ return ((_a = this.message.submessages) === null || _a === void 0 ? void 0 : _a.length) || this.message.submessageCount || 0;
8025
+ }
7851
8026
  }
7852
8027
  CommentComponent.decorators = [
7853
8028
  { type: Component, args: [{
7854
8029
  selector: 'banta-comment',
7855
- template: "\r\n<mat-menu #pointItemMenu=\"matMenu\">\r\n <button *ngIf=\"!mine\" mat-menu-item (click)=\"report()\">Report</button>\r\n <button *ngIf=\"mine\" [disabled]=\"!permissions?.canEdit\" mat-menu-item (click)=\"startEdit()\">Edit</button>\r\n <button *ngIf=\"mine\" [disabled]=\"!permissions?.canDelete\" mat-menu-item (click)=\"delete()\">Delete</button>\r\n</mat-menu>\r\n\r\n<div class=\"message-content\">\r\n <div class=\"user\">\r\n <a\r\n href=\"javascript:;\"\r\n class=\"avatar\"\r\n (click)=\"selectAvatar(message.user)\"\r\n [style.background-image]=\"avatarForUser(message.user)\"></a>\r\n <a href=\"javascript:;\" class=\"display-name\" (click)=\"selectUser()\">{{message.user.displayName}}</a>\r\n <a href=\"javascript:;\" class=\"username\" (click)=\"selectUsername(message.user)\">@{{message.user.username}}</a>\r\n <span class=\"user-tag\" *ngIf=\"message.user.tag\">{{message.user.tag}}</span>\r\n <span class=\"spacer\"></span>\r\n </div>\r\n <div class=\"content\" *ngIf=\"!editing\">\r\n <span class=\"banta-message-content\" [innerHTML]=\"message.message | markdownToHtml\"></span>\r\n\r\n <ng-container *ngIf=\"message.attachments?.length > 0\">\r\n <banta-lightbox #lightbox></banta-lightbox>\r\n <div class=\"attachments-row\" [class.single]=\"message.attachments?.length === 1\" *ngIf=\"message.attachments && message.attachments?.length > 0\">\r\n <a \r\n href=\"javascript:;\" \r\n (click)=\"showLightbox(attachment)\"\r\n *ngFor=\"let attachment of message.attachments\" \r\n >\r\n <img [src]=\"attachment.url\" alt=\"\">\r\n </a>\r\n </div>\r\n </ng-container>\r\n\r\n <ul class=\"message-facts small\">\r\n <li *ngIf=\"message.edits?.length > 0\">(Edited)</li>\r\n </ul>\r\n </div>\r\n <div class=\"content\" *ngIf=\"editing\" style=\"padding-bottom: 2em;\">\r\n <div>\r\n <mat-form-field floatLabel=\"always\" appearance=\"outline\" style=\"width: 100%;\">\r\n <mat-label>Edit Message</mat-label>\r\n <textarea matInput [(ngModel)]=\"editedMessage\"></textarea>\r\n </mat-form-field>\r\n </div>\r\n <button mat-raised-button (click)=\"saveEdit()\">Save</button> &nbsp;\r\n <button mat-button (click)=\"endEditing()\">Cancel</button>\r\n </div>\r\n\r\n\r\n <div class=\"actions\">\r\n <banta-timestamp [value]=\"message.sentAt\"></banta-timestamp>\r\n <ul class=\"message-facts\">\r\n <li *ngIf=\"message.edits?.length > 0\">Edited</li>\r\n </ul>\r\n <div class=\"spacer\"></div>\r\n <div class=\"counted-action\" *ngIf=\"showReplyAction\">\r\n <div class=\"count-indicator\">\r\n {{message.submessages?.length || message.submessageCount || 0}}\r\n </div>\r\n <button mat-icon-button matTooltip=\"Replies\" matTooltipPosition=\"below\" (click)=\"select()\">\r\n <mat-icon [inline]=\"true\">comment</mat-icon>\r\n </button>\r\n </div>\r\n <div class=\"counted-action\" [class.active]=\"message.userState?.liked\">\r\n <div class=\"count-indicator\">\r\n {{message.likes}}\r\n </div>\r\n <button \r\n *ngIf=\"message.transientState?.liking\"\r\n mat-icon-button \r\n [disabled]=\"true\" \r\n [matTooltip]=\"upvoting ? 'Please wait...' : message.userState?.liked ? 'Unlike' : 'Like'\" \r\n matTooltipPosition=\"below\" \r\n >\r\n <mat-spinner [diameter]=\"15\" style=\"margin-left: 1em;\"></mat-spinner>\r\n </button>\r\n <button \r\n *ngIf=\"!message.transientState?.liking\"\r\n mat-icon-button \r\n [disabled]=\"!permissions?.canLike\" \r\n [matTooltip]=\"upvoting ? 'Please wait...' : 'Like'\" \r\n matTooltipPosition=\"below\" \r\n (click)=\"message.userState?.liked ? unlike() : like()\" \r\n >\r\n <mat-icon [inline]=\"true\">thumb_up</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <div class=\"counted-action\">\r\n <button mat-icon-button matTooltip=\"Share this comment\" matTooltipPosition=\"below\" (click)=\"share()\">\r\n <mat-icon [inline]=\"true\" >share</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <button mat-icon-button [matMenuTriggerFor]=\"pointItemMenu\">\r\n <mat-icon [inline]=\"true\">more_vert</mat-icon>\r\n </button>\r\n </div>\r\n</div>\r\n",
7856
- styles: ["@-webkit-keyframes comment-appear{0%{transform:translate(100vw)}to{transform:translate(0)}}@keyframes comment-appear{0%{transform:translate(100vw)}to{transform:translate(0)}}:host{display:flex;flex-direction:column;position:relative;padding:.5em;visibility:hidden}:host.new{visibility:visible;-webkit-animation-name:comment-appear;animation-name:comment-appear;-webkit-animation-duration:.4s;animation-duration:.4s;-webkit-animation-fill-mode:both;animation-fill-mode:both}:host.highlighted{background:#00223a;outline:2px solid #003277}:host.visible{visibility:visible}:host:hover{background:#eee}:host .message-content .content{margin-left:60px;margin-right:.5em}:host .message-content .attachments-row{margin-top:15px;display:flex;gap:10px}:host .message-content .attachments-row img{border-radius:10px;width:300px;max-width:100%;max-height:20em;-o-object-fit:cover;object-fit:cover}:host.abbreviated .message-content .content{text-overflow:ellipsis;overflow-y:hidden}:host .actions{display:flex;padding-right:10px;margin-left:60px;align-items:center}:host .actions button{color:#666;flex-shrink:0}:host .actions banta-timestamp{color:#666;font-size:10pt;flex-shrink:0}.user{position:relative;margin:1em 0 0;display:flex;align-items:center}.user .display-name,.user .username{z-index:1;position:relative;padding:0 0 0 1em;font-size:10pt;color:#000;margin:0 auto 0 0;display:block;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;max-width:100%;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;flex-shrink:1;flex-grow:0}.user .display-name.username.username.username,.user .username.username.username.username{color:#666}.avatar{height:48px;width:48px;background-position:50%;background-size:cover;background-color:#333;border-radius:100%;flex-shrink:0;flex-grow:0}.counted-action{display:flex;align-items:center}.counted-action.active .count-indicator,.counted-action.active button{color:#00a5ff}.count-indicator{font-size:9pt;padding:0 0 0 3px;color:#666}:host-context(.mat-dark-theme) .count-indicator{border-color:#333}:host-context(.mat-dark-theme):hover{background:#060606}.user-tag,:host-context(.mat-dark-theme) .user .display-name,:host-context(.mat-dark-theme) .user .username{color:#fff}.user-tag{text-transform:uppercase;font-size:12px;border:1px solid #b27373;background:#7a412b;padding:3px 5px;margin:0 .5em 0 1em;border-radius:3px}.spacer{flex-shrink:1;flex-grow:1}ul.message-facts{margin:0;padding:0;color:#666}ul.message-facts li{list-style-type:none;border-left:1px solid #666;font-size:10pt;padding-left:.5em;margin-left:.5em}ul.message-facts.small{display:none}ul.message-facts.small li{margin-top:.5em}ul.message-facts.small li:first-child{border-left:1px solid transparent;margin-left:0;padding-left:0}@media (max-width:400px){.avatar{height:32px;width:32px}.actions ul.message-facts{display:none}ul.message-facts.small{display:initial}:host .actions{margin-left:0;margin-top:.5em}:host .message-content .content{margin-left:44px;margin-right:.5em}}:host-context(.banta-mobile) .avatar{height:32px;width:32px}:host-context(.banta-mobile) .actions ul.message-facts{display:none}:host-context(.banta-mobile) ul.message-facts.small{display:initial}:host-context(.banta-mobile) :host .actions{margin-left:0;margin-top:.5em}:host-context(.banta-mobile) :host .message-content .content{margin-left:44px;margin-right:.5em}"]
8030
+ template: "\r\n<mat-menu #pointItemMenu=\"matMenu\">\r\n <button mat-menu-item (click)=\"share()\">\r\n <mat-icon>share</mat-icon>\r\n Share\r\n </button>\r\n <button *ngIf=\"!mine\" mat-menu-item (click)=\"report()\">\r\n <mat-icon>warning</mat-icon>\r\n Report\r\n </button>\r\n <button *ngIf=\"mine\" [disabled]=\"!permissions?.canEdit\" mat-menu-item (click)=\"startEdit()\">\r\n <mat-icon>edit</mat-icon>\r\n Edit\r\n </button>\r\n <button *ngIf=\"mine\" [disabled]=\"!permissions?.canDelete\" mat-menu-item (click)=\"delete()\">\r\n <mat-icon>delete</mat-icon>\r\n Delete\r\n </button>\r\n</mat-menu>\r\n\r\n<div class=\"message-content\">\r\n <div class=\"user\">\r\n <div class=\"user-1\">\r\n <a\r\n href=\"javascript:;\"\r\n class=\"avatar\"\r\n (click)=\"selectAvatar(message.user)\"\r\n [style.background-image]=\"avatarForUser(message.user)\"></a>\r\n <div class=\"user-identity\">\r\n <a href=\"javascript:;\" class=\"display-name\" (click)=\"selectUser()\">{{message.user.displayName}}</a>\r\n <a href=\"javascript:;\" class=\"username\" (click)=\"selectUsername(message.user)\">@{{message.user.username}}</a>\r\n </div>\r\n </div>\r\n <div class=\"user-2\">\r\n <span class=\"user-tag\" *ngIf=\"message.user.tag\">{{message.user.tag}}</span>\r\n <banta-timestamp [value]=\"message.sentAt\"></banta-timestamp>\r\n <span class=\"spacer\"></span>\r\n </div>\r\n </div>\r\n <div class=\"content\" *ngIf=\"!editing\">\r\n <span class=\"banta-message-content\" [innerHTML]=\"message.message | markdownToHtml\"></span>\r\n <banta-attachments [attachments]=\"message.attachments\"></banta-attachments>\r\n <ul class=\"message-facts\">\r\n <li *ngIf=\"message.edits?.length > 0\">(Edited)</li>\r\n </ul>\r\n </div>\r\n <div class=\"content\" *ngIf=\"editing\" style=\"padding-bottom: 2em;\">\r\n <div>\r\n <mat-form-field floatLabel=\"always\" appearance=\"outline\" style=\"width: 100%;\">\r\n <mat-label>Edit Message</mat-label>\r\n <textarea matInput [(ngModel)]=\"editedMessage\"></textarea>\r\n </mat-form-field>\r\n </div>\r\n <button mat-raised-button (click)=\"saveEdit()\">Save</button> &nbsp;\r\n <button mat-button (click)=\"endEditing()\">Cancel</button>\r\n </div>\r\n\r\n\r\n <div class=\"actions\">\r\n <div class=\"spacer\"></div>\r\n <div class=\"counted-action\" *ngIf=\"showReplyAction\">\r\n <button mat-button [matTooltip]=\"replyCount > 0 ? 'Replies' : 'Reply'\" matTooltipPosition=\"below\" (click)=\"select()\">\r\n <mat-icon [inline]=\"true\">comment</mat-icon>\r\n <span class=\"count-indicator\">\r\n {{replyCount > 0 ? 'Replies' : 'Reply'}}\r\n {{replyCount > 0 ? '(' + replyCount + ')' : ''}}\r\n </span>\r\n </button>\r\n </div>\r\n <div class=\"counted-action\" [class.active]=\"message.userState?.liked\">\r\n <button \r\n *ngIf=\"message.transientState?.liking\"\r\n mat-icon-button \r\n [disabled]=\"true\" \r\n [matTooltip]=\"upvoting ? 'Please wait...' : message.userState?.liked ? 'Unlike' : 'Like'\" \r\n matTooltipPosition=\"below\" \r\n >\r\n <mat-spinner [diameter]=\"15\" style=\"margin-left: 1em;\"></mat-spinner>\r\n </button>\r\n <button \r\n *ngIf=\"!message.transientState?.liking\"\r\n mat-button \r\n [disabled]=\"!permissions?.canLike\" \r\n [matTooltip]=\"upvoting ? 'Please wait...' : 'Like'\" \r\n matTooltipPosition=\"below\" \r\n (click)=\"message.userState?.liked ? unlike() : like()\" \r\n >\r\n <mat-icon [inline]=\"true\">thumb_up</mat-icon>\r\n <span class=\"count-indicator\" *ngIf=\"message.likes > 0\">\r\n {{message.likes}}\r\n </span>\r\n </button>\r\n </div>\r\n\r\n <button mat-icon-button [matMenuTriggerFor]=\"pointItemMenu\">\r\n <mat-icon [inline]=\"true\">more_vert</mat-icon>\r\n </button>\r\n </div>\r\n</div>\r\n",
8031
+ styles: ["@-webkit-keyframes comment-appear{0%{transform:translate(100vw)}to{transform:translate(0)}}@keyframes comment-appear{0%{transform:translate(100vw)}to{transform:translate(0)}}:host{display:flex;flex-direction:column;position:relative;padding:.5em;visibility:hidden}:host.new{visibility:visible;-webkit-animation-name:comment-appear;animation-name:comment-appear;-webkit-animation-duration:.4s;animation-duration:.4s;-webkit-animation-fill-mode:both;animation-fill-mode:both}:host.highlighted{background:#00223a;outline:2px solid #003277}:host.visible{visibility:visible}:host:hover{background:#eee}:host .message-content .content{margin-left:60px;margin-right:.5em}:host .message-content .attachments-row{margin-top:15px;display:flex;gap:10px}:host .message-content .attachments-row img{border-radius:10px;width:300px;max-width:100%;max-height:20em;-o-object-fit:cover;object-fit:cover}:host.abbreviated .message-content .content{text-overflow:ellipsis;overflow-y:hidden}:host .actions{display:flex;padding-right:10px;margin-left:60px;align-items:center}:host .actions button,banta-timestamp{color:#666;flex-shrink:0}banta-timestamp{font-size:10pt;margin-left:1em;text-align:right}.user{position:relative;margin:1em 0 0;display:flex;align-items:center;flex-wrap:wrap}.user .user-1,.user .user-2{display:flex;flex-wrap:nowrap;align-items:center;min-width:0}.user .user-2{margin:1em 0}.user .user-identity{display:flex;flex-direction:column;min-width:0}.user .display-name,.user .username{z-index:1;position:relative;padding:0 0 0 1em;font-size:10pt;color:#000;margin:0 auto 0 0;display:block;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;max-width:100%;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;flex-shrink:1;flex-grow:0;min-width:0}.user .display-name.username.username.username,.user .username.username.username.username{color:#666}.avatar{height:48px;width:48px;background-position:50%;background-size:cover;background-color:#333;border-radius:100%;flex-shrink:0;flex-grow:0}.counted-action{display:flex;align-items:center}.counted-action.active .count-indicator,.counted-action.active button{color:#00a5ff}.counted-action button .count-indicator{margin-left:.5em}.count-indicator{font-size:9pt;padding:0 0 0 3px;color:#666}:host-context(.mat-dark-theme) .count-indicator{border-color:#333}:host-context(.mat-dark-theme):hover{background:#060606}.user-tag,:host-context(.mat-dark-theme) .user .display-name,:host-context(.mat-dark-theme) .user .username{color:#fff}.user-tag{text-transform:uppercase;font-size:12px;border:1px solid #b27373;background:#7a412b;padding:3px 5px;margin:0 .5em 0 1em;border-radius:3px}.spacer{flex-shrink:1;flex-grow:1}ul.message-facts{margin:0;padding:0;color:#666}ul.message-facts li{list-style-type:none;border-left:1px solid #666;font-size:10pt;padding-left:.5em;margin-left:.5em;margin-top:.5em}ul.message-facts li:first-child{border-left:1px solid transparent;margin-left:0;padding-left:0}@media (max-width:400px){.avatar{height:32px;width:32px}:host .actions{margin-left:0;margin-top:.5em}:host .message-content .content{margin-left:44px;margin-right:.5em}}:host-context(.banta-mobile) .avatar{height:32px;width:32px}:host-context(.banta-mobile) :host .actions{margin-left:0;margin-top:.5em}:host-context(.banta-mobile) :host .message-content .content{margin-left:44px;margin-right:.5em}.card-attachment a{display:flex;align-items:flex-start;gap:1em;width:100%;border:1px solid #666;border-radius:4px;padding:1em;box-sizing:border-box;background-color:#191919}.card-attachment a img{width:300px;aspect-ratio:16/9;-o-object-fit:cover;object-fit:cover;border-radius:10px}.card-attachment a h1{margin:0;font-size:30px}"]
7857
8032
  },] }
7858
8033
  ];
7859
8034
  CommentComponent.propDecorators = {
@@ -7878,8 +8053,7 @@ CommentComponent.propDecorators = {
7878
8053
  editEnded: [{ type: Output }],
7879
8054
  shared: [{ type: Output }],
7880
8055
  genericAvatarUrl: [{ type: Input }],
7881
- commentId: [{ type: HostBinding, args: ['attr.data-comment-id',] }],
7882
- lightbox: [{ type: ViewChild, args: ['lightbox',] }]
8056
+ commentId: [{ type: HostBinding, args: ['attr.data-comment-id',] }]
7883
8057
  };
7884
8058
 
7885
8059
  class CommentViewComponent {
@@ -8623,7 +8797,7 @@ BantaCommentsComponent.decorators = [
8623
8797
  { type: Component, args: [{
8624
8798
  selector: 'banta-comments',
8625
8799
  template: "<ng-container *ngIf=\"loading\">\r\n <div class=\"loading-screen\" [class.visible]=\"showLoadingScreen\">\r\n <h1>Loading Comments</h1>\r\n <div>\r\n <mat-spinner [diameter]=\"300\" [strokeWidth]=\"2\"></mat-spinner>\r\n </div>\r\n\r\n <p class=\"loading-message\" [class.visible]=\"loadingMessageVisible\">{{loadingMessage}}</p>\r\n </div>\r\n</ng-container>\r\n<ng-container *ngIf=\"!loading\">\r\n <div class=\"focused\" [class.visible]=\"selectedMessageVisible\" *ngIf=\"selectedMessage && !useInlineReplies\">\r\n\r\n <div>\r\n <a mat-button href=\"javascript:;\" (click)=\"unselectMessage()\">\r\n <mat-icon>arrow_back</mat-icon>\r\n Latest Comments\r\n </a>\r\n </div>\r\n\r\n <banta-comment\r\n [message]=\"selectedMessage\"\r\n [liking]=\"selectedMessage.transientState.liking\"\r\n [mine]=\"user?.id === selectedMessage.user?.id\"\r\n [permissions]=\"source?.permissions\"\r\n [showReplyAction]=\"false\"\r\n [editing]=\"selectedMessage.transientState.editing\"\r\n [genericAvatarUrl]=\"genericAvatarUrl\"\r\n (editStarted)=\"startEditing(selectedMessage)\"\r\n (editEnded)=\"selectedMessage.transientState.editing = false\"\r\n (edited)=\"saveEdit(selectedMessage, $event)\"\r\n (userSelected)=\"selectMessageUser(selectedMessage)\"\r\n (avatarSelected)=\"selectAvatar($event)\"\r\n (usernameSelected)=\"selectUsername($event)\"\r\n (liked)=\"likeMessage(source, selectedMessage)\"\r\n (unliked)=\"unlikeMessage(source, selectedMessage)\"\r\n (reported)=\"reportMessage(selectedMessage)\"\r\n (selected)=\"selectMessage(selectedMessage)\"\r\n (shared)=\"shareMessage($event)\"\r\n (deleted)=\"deleteMessage(selectedMessage)\"\r\n ></banta-comment>\r\n\r\n <div class=\"replies\">\r\n\r\n <ng-container *ngIf=\"!selectedMessageThread\">\r\n <div class=\"loading\">\r\n <mat-spinner></mat-spinner>\r\n </div>\r\n </ng-container>\r\n\r\n <ng-container *ngIf=\"selectedMessageThread\">\r\n <banta-comment-view\r\n [source]=\"selectedMessageThread\"\r\n [allowReplies]=\"false\"\r\n [fixedHeight]=\"false\"\r\n [showEmptyState]=\"false\"\r\n [newestLast]=\"true\"\r\n [genericAvatarUrl]=\"genericAvatarUrl\"\r\n (liked)=\"likeMessage(selectedMessageThread, $event)\"\r\n (unliked)=\"unlikeMessage(selectedMessageThread, $event)\"\r\n (messageEdited)=\"editMessage(selectedMessageThread, $event.message, $event.newMessage)\"\r\n (reported)=\"reportMessage($event)\"\r\n (usernameSelected)=\"selectUsername($event)\"\r\n (avatarSelected)=\"selectAvatar($event)\"\r\n (shared)=\"shareMessage($event)\"\r\n (deleted)=\"deleteMessage($event)\"\r\n ></banta-comment-view>\r\n\r\n <banta-comment-field\r\n [sendLabel]=\"replyLabel\"\r\n [sendingLabel]=\"sendingLabel\"\r\n [hashtags]=\"hashtags\"\r\n [participants]=\"participants\"\r\n (signInSelected)=\"showSignIn()\"\r\n (editAvatarSelected)=\"showEditAvatar()\"\r\n [source]=\"selectedMessageThread\"\r\n [maxLength]=\"maxCommentLength\"\r\n [canComment]=\"source?.permissions?.canPost\"\r\n [signInLabel]=\"signInLabel\"\r\n [permissionDeniedLabel]=\"source?.permissions?.canPostErrorMessage || permissionDeniedLabel\"\r\n (permissionDeniedError)=\"handlePermissionDenied($event)\"\r\n [shouldInterceptMessageSend]=\"shouldInterceptMessageSend\"\r\n [user]=\"user\"\r\n [label]=\"postReplyLabel\"\r\n [submit]=\"sendReply\"\r\n [allowAttachments]=\"allowAttachments\"\r\n [genericAvatarUrl]=\"genericAvatarUrl\"\r\n >\r\n <ng-container *ngTemplateOutlet=\"sendReplyOptionsTemplate\"></ng-container>\r\n </banta-comment-field>\r\n </ng-container>\r\n </div>\r\n </div>\r\n\r\n <div class=\"main\" [class.hidden]=\"selectedMessage && !useInlineReplies\">\r\n <banta-comment-field\r\n [source]=\"source\"\r\n [user]=\"user\"\r\n [sendLabel]=\"sendLabel\"\r\n [sendingLabel]=\"sendingLabel\"\r\n [signInLabel]=\"signInLabel\"\r\n [canComment]=\"source?.permissions?.canPost\"\r\n [hashtags]=\"hashtags\"\r\n [participants]=\"participants\"\r\n [label]=\"postCommentLabel\"\r\n [maxLength]=\"maxCommentLength\"\r\n (editAvatarSelected)=\"showEditAvatar()\"\r\n (signInSelected)=\"showSignIn()\"\r\n [permissionDeniedLabel]=\"source?.permissions?.canPostErrorMessage || permissionDeniedLabel\"\r\n (permissionDeniedError)=\"handlePermissionDenied($event)\"\r\n [shouldInterceptMessageSend]=\"shouldInterceptMessageSend\"\r\n [submit]=\"sendMessage\"\r\n [allowAttachments]=\"allowAttachments\"\r\n [genericAvatarUrl]=\"genericAvatarUrl\"\r\n >\r\n \r\n </banta-comment-field>\r\n\r\n <banta-comment-sort\r\n [(sort)]=\"sortOrder\"></banta-comment-sort>\r\n\r\n <banta-comment-view\r\n [class.faded]=\"selectedMessage && !useInlineReplies\"\r\n [source]=\"source\"\r\n [fixedHeight]=\"fixedHeight\"\r\n [maxMessages]=\"maxMessages\"\r\n [maxVisibleMessages]=\"maxVisibleMessages\"\r\n [genericAvatarUrl]=\"genericAvatarUrl\"\r\n (userSelected)=\"selectMessageUser($event)\"\r\n (sortOrderChanged)=\"sortOrder = $event\"\r\n (selected)=\"selectMessage($event)\"\r\n (liked)=\"likeMessage(source, $event)\"\r\n (unliked)=\"unlikeMessage(source, $event)\"\r\n (messageEdited)=\"editMessage(source, $event.message, $event.newMessage)\"\r\n (reported)=\"reportMessage($event)\"\r\n (usernameSelected)=\"selectUsername($event)\"\r\n (avatarSelected)=\"selectAvatar($event)\"\r\n (shared)=\"shareMessage($event)\"\r\n [selectedMessage]=\"selectedMessage\"\r\n (deleted)=\"deleteMessage($event)\"\r\n >\r\n <div class=\"inline-replies\">\r\n <div class=\"focused\" [class.visible]=\"selectedMessageVisible\" *ngIf=\"selectedMessage\">\r\n <div class=\"replies\">\r\n \r\n <ng-container *ngIf=\"!selectedMessageThread\">\r\n <div class=\"loading\">\r\n <mat-spinner></mat-spinner>\r\n </div>\r\n </ng-container>\r\n \r\n <ng-container *ngIf=\"selectedMessageThread\">\r\n <banta-comment-view\r\n [source]=\"selectedMessageThread\"\r\n [allowReplies]=\"false\"\r\n [fixedHeight]=\"false\"\r\n [showEmptyState]=\"false\"\r\n [newestLast]=\"true\"\r\n [genericAvatarUrl]=\"genericAvatarUrl\"\r\n (liked)=\"likeMessage(selectedMessageThread, $event)\"\r\n (unliked)=\"unlikeMessage(selectedMessageThread, $event)\"\r\n (messageEdited)=\"editMessage(selectedMessageThread, $event.message, $event.newMessage)\"\r\n (reported)=\"reportMessage($event)\"\r\n (usernameSelected)=\"selectUsername($event)\"\r\n (avatarSelected)=\"selectAvatar($event)\"\r\n (shared)=\"shareMessage($event)\"\r\n (deleted)=\"deleteMessage($event)\"\r\n ></banta-comment-view>\r\n \r\n <banta-comment-field\r\n [sendLabel]=\"replyLabel\"\r\n [sendingLabel]=\"sendingLabel\"\r\n [hashtags]=\"hashtags\"\r\n [participants]=\"participants\"\r\n (signInSelected)=\"showSignIn()\"\r\n [maxLength]=\"maxCommentLength\"\r\n (editAvatarSelected)=\"showEditAvatar()\"\r\n [source]=\"selectedMessageThread\"\r\n [canComment]=\"source?.permissions?.canPost\"\r\n [signInLabel]=\"signInLabel\"\r\n [permissionDeniedLabel]=\"source?.permissions?.canPostErrorMessage || permissionDeniedLabel\"\r\n (permissionDeniedError)=\"handlePermissionDenied($event)\"\r\n [shouldInterceptMessageSend]=\"shouldInterceptMessageSend\"\r\n [user]=\"user\"\r\n [label]=\"postReplyLabel\"\r\n [submit]=\"sendReply\"\r\n [allowAttachments]=\"allowAttachments\"\r\n [genericAvatarUrl]=\"genericAvatarUrl\"\r\n >\r\n <ng-container *ngTemplateOutlet=\"sendReplyOptionsTemplate\"></ng-container>\r\n </banta-comment-field>\r\n </ng-container>\r\n </div>\r\n </div> \r\n </div>\r\n </banta-comment-view>\r\n </div>\r\n</ng-container>\r\n",
8626
- styles: [":host{display:flex;flex-direction:column}@-webkit-keyframes select-comment{0%{transform:scale(1.15)}to{transform:scale(1)}}@keyframes select-comment{0%{transform:scale(1.15)}to{transform:scale(1)}}.focused{-webkit-animation-name:select-comment;animation-name:select-comment;-webkit-animation-duration:.4s;animation-duration:.4s;-webkit-animation-fill-mode:both;animation-fill-mode:both}.focused .replies{margin-top:1em;margin-left:2em;border-left:2px solid #333;padding-left:2em}banta-comment-view{opacity:1;transition:opacity .4s ease-in-out}banta-comment-view.faded{opacity:.25}.loading{display:block;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;margin:0 auto;min-height:16em}.main.hidden{display:none}.loading-screen{text-align:center;opacity:0;transition:opacity .25s ease-in-out}.loading-screen.visible{opacity:1}.loading-screen h1{font-weight:100}.loading-screen mat-spinner{margin:5em auto}.loading-screen .loading-message{opacity:0;transition:opacity .25s ease-in-out;width:500px;max-width:100%;margin:0 auto}.loading-screen .loading-message.visible{opacity:1}banta-comment-sort{margin:0 0 0 auto;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;display:block}.inline-replies{margin-left:4em}@media (max-width:500px){.focused .replies{margin-left:0}banta-comment-sort{margin:0;width:100%}}:host-context(.banta-mobile) .focused .replies{margin-left:0}:host-context(.banta-mobile) banta-comment-sort{margin:0;width:100%}"]
8800
+ styles: [":host{display:flex;flex-direction:column}@-webkit-keyframes select-comment{0%{transform:scale(1.15)}to{transform:scale(1)}}@keyframes select-comment{0%{transform:scale(1.15)}to{transform:scale(1)}}.focused{-webkit-animation-name:select-comment;animation-name:select-comment;-webkit-animation-duration:.4s;animation-duration:.4s;-webkit-animation-fill-mode:both;animation-fill-mode:both}.focused .replies{margin-top:1em;margin-left:2em;border-left:2px solid #333;padding-left:2em}banta-comment-view{opacity:1;transition:opacity .4s ease-in-out}banta-comment-view.faded{opacity:.25}.loading{display:block;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;margin:0 auto;min-height:16em}.main.hidden{display:none}.loading-screen{text-align:center;opacity:0;transition:opacity .25s ease-in-out}.loading-screen.visible{opacity:1}.loading-screen h1{font-weight:100}.loading-screen mat-spinner{margin:5em auto}.loading-screen .loading-message{opacity:0;transition:opacity .25s ease-in-out;width:500px;max-width:100%;margin:0 auto}.loading-screen .loading-message.visible{opacity:1}banta-comment-sort{margin:0 0 0 auto;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;display:block}.inline-replies{margin-left:4em}@media (max-width:500px){.focused .replies{margin-left:0}.inline-replies{margin-left:1em}.focused .replies{padding-left:.5em}banta-comment-sort{margin:0;width:100%}}:host-context(.banta-mobile) .focused .replies{margin-left:0}:host-context(.banta-mobile) .inline-replies{margin-left:1em}:host-context(.banta-mobile) .focused .replies{padding-left:.5em}:host-context(.banta-mobile) banta-comment-sort{margin:0;width:100%}"]
8627
8801
  },] }
8628
8802
  ];
8629
8803
  BantaCommentsComponent.ctorParameters = () => [
@@ -8737,14 +8911,17 @@ LiveCommentComponent.propDecorators = {
8737
8911
  };
8738
8912
 
8739
8913
  class CommentFieldComponent {
8740
- constructor() {
8914
+ constructor(chatBackend) {
8915
+ this.chatBackend = chatBackend;
8741
8916
  this.canComment = true;
8742
8917
  this.allowAttachments = false;
8743
8918
  this.signInSelected = new Subject();
8744
8919
  this.editAvatarSelected = new Subject();
8745
8920
  this.sending = false;
8746
8921
  this.expandError = false;
8747
- this.text = '';
8922
+ this._text = '';
8923
+ this.attachmentScrapeDebounce = 1500;
8924
+ this.attachmentFragments = new Map();
8748
8925
  this.sendLabel = 'Send';
8749
8926
  this.sendingLabel = 'Sending';
8750
8927
  this.label = 'Post a comment';
@@ -8760,6 +8937,79 @@ class CommentFieldComponent {
8760
8937
  this.autoCompleteSelected = 0;
8761
8938
  this.chatMessageAttachments = [];
8762
8939
  }
8940
+ get text() {
8941
+ return this._text;
8942
+ }
8943
+ set text(value) {
8944
+ this._text = value;
8945
+ clearTimeout(this.attachmentScrapeTimeout);
8946
+ this.attachmentScrapeTimeout = setTimeout(() => this.scrapeAttachments(), this.attachmentScrapeDebounce);
8947
+ }
8948
+ scrapeAttachments() {
8949
+ let message = {
8950
+ likes: 0,
8951
+ message: this._text,
8952
+ sentAt: undefined,
8953
+ user: this.user,
8954
+ attachments: this.chatMessageAttachments
8955
+ };
8956
+ let foundFragments = [];
8957
+ for (let scraper of this.chatBackend.attachmentScrapers) {
8958
+ let fragments = scraper.findFragments(message);
8959
+ if (!fragments) {
8960
+ console.error(`Attachment fragment scraper ${scraper.constructor.name} is implemented incorrectly: Returned null instead of array`);
8961
+ continue;
8962
+ }
8963
+ for (let fragment of fragments) {
8964
+ foundFragments.push(fragment.text);
8965
+ if (!this.attachmentFragments.has(fragment.text)) {
8966
+ console.log(`Scraped new fragment:`);
8967
+ console.dir(fragment);
8968
+ this.attachmentFragments.set(fragment.text, {
8969
+ fragment,
8970
+ resolution: undefined
8971
+ });
8972
+ }
8973
+ }
8974
+ }
8975
+ // Remove fragments that are no longer in the message.
8976
+ let removedFragments = [];
8977
+ for (let [key] of this.attachmentFragments) {
8978
+ if (!foundFragments.includes(key))
8979
+ removedFragments.push(key);
8980
+ }
8981
+ for (let removedFragment of removedFragments) {
8982
+ console.log(`Removed fragment: ${removedFragment}`);
8983
+ this.attachmentFragments.delete(removedFragment);
8984
+ }
8985
+ // Process any fragments that are not yet resolved (or being
8986
+ // resolved)
8987
+ for (let [key, state] of this.attachmentFragments) {
8988
+ if (state.resolution)
8989
+ continue;
8990
+ state.resolution = new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
8991
+ console.log(`Resolving fragment ${key}`);
8992
+ for (let resolver of this.chatBackend.attachmentResolvers) {
8993
+ console.log(`- Trying resolver ${resolver.constructor.name}...`);
8994
+ try {
8995
+ let attachment = yield resolver.resolveFragment(message, state.fragment);
8996
+ if (attachment) {
8997
+ console.log(`Resolved fragment ${key} into attachment:`);
8998
+ console.dir(attachment);
8999
+ this.chatMessageAttachments.push(attachment);
9000
+ resolve(attachment);
9001
+ break;
9002
+ }
9003
+ }
9004
+ catch (e) {
9005
+ console.error(`Caught error during attachment resolver ${resolver.constructor.name}:`);
9006
+ console.error(e);
9007
+ continue;
9008
+ }
9009
+ }
9010
+ }));
9011
+ }
9012
+ }
8763
9013
  get userAvatarUrl() {
8764
9014
  var _a;
8765
9015
  return ((_a = this.user) === null || _a === void 0 ? void 0 : _a.avatarUrl) || this.genericAvatarUrl;
@@ -8987,8 +9237,10 @@ class CommentFieldComponent {
8987
9237
  this.chatMessageAttachments = this.chatMessageAttachments.filter(x => x !== attachment);
8988
9238
  }, 3000);
8989
9239
  }
8990
- removeAttachment(index) {
8991
- this.chatMessageAttachments.splice(index, 1);
9240
+ removeAttachment(attachment) {
9241
+ let index = this.chatMessageAttachments.indexOf(attachment);
9242
+ if (index >= 0)
9243
+ this.chatMessageAttachments.splice(index, 1);
8992
9244
  }
8993
9245
  alertError() {
8994
9246
  if (!this.sendError)
@@ -8999,10 +9251,13 @@ class CommentFieldComponent {
8999
9251
  CommentFieldComponent.decorators = [
9000
9252
  { type: Component, args: [{
9001
9253
  selector: 'banta-comment-field',
9002
- template: "<form class=\"new-message\" (submit)=\"sendMessage()\">\r\n <div class=\"avatar-container\">\r\n <a href=\"javascript:;\"\r\n class=\"avatar\"\r\n (click)=\"showEditAvatar()\"\r\n [style.background-image]=\"'url(' + userAvatarUrl + ')'\"\r\n ></a>\r\n </div>\r\n <div class=\"text-container\">\r\n <div class=\"field-container\">\r\n <div class=\"field-row\">\r\n <mat-form-field appearance=\"outline\" floatLabel=\"always\">\r\n <mat-label>{{label}}</mat-label>\r\n <textarea\r\n #textarea\r\n name=\"message\"\r\n [placeholder]=\"placeholder\"\r\n matInput\r\n cdkTextareaAutosize\r\n [maxlength]=\"maxLength\"\r\n (keydown)=\"onKeyDown($event)\"\r\n (blur)=\"onBlur()\"\r\n [disabled]=\"sending\"\r\n [(ngModel)]=\"text\"></textarea>\r\n </mat-form-field>\r\n <div class=\"options-line\">\r\n <mat-spinner *ngIf=\"sending\" class=\"icon loading\" diameter=\"18\" strokeWidth=\"2\"></mat-spinner>\r\n <div *ngIf=\"sendError\" class=\"error-message\" [class.expanded]=\"expandError\" [matTooltip]=\"sendError.message\" (click)=\"alertError()\">\r\n <mat-icon *ngIf=\"sendError\">error</mat-icon>\r\n {{sendError.message}}\r\n </div>\r\n <div class=\"spacer\"></div>\r\n <div class=\"custom\">\r\n <ng-content></ng-content>\r\n </div>\r\n <banta-attachment-button \r\n *ngIf=\"allowAttachments\"\r\n (addedAttachment)=\"addedAttachment($event)\"\r\n (attachmentError)=\"attachmentError($event)\"\r\n ></banta-attachment-button>\r\n <emoji-selector-button (selected)=\"insertEmoji($event)\"></emoji-selector-button>\r\n </div>\r\n \r\n </div>\r\n <div #autocompleteContainer class=\"autocomplete-container\">\r\n <div #autocomplete class=\"autocomplete\" [class.visible]=\"autocompleteVisible\">\r\n\r\n <div>\r\n <strong>{{completionPrefix}}</strong>...\r\n </div>\r\n <a\r\n mat-button\r\n *ngFor=\"let option of autocompleteOptions; index as index\"\r\n (click)=\"activateAutoComplete(option)\"\r\n [class.active]=\"autoCompleteSelected === index\"\r\n >\r\n {{option.label}}\r\n </a>\r\n </div>\r\n </div>\r\n\r\n\r\n <div *ngIf=\"chatMessageAttachments && chatMessageAttachments.length\" class=\"message-attachments-container\">\r\n <div *ngFor=\"let attachment of chatMessageAttachments; index as attachmentIndex\"\r\n class=\"message-attachment\" [class.with-border]=\"!attachment.url\">\r\n <button (click)=\"removeAttachment(attachmentIndex)\" mat-mini-fab color=\"primary\" class=\"remove-img\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n <ng-container *ngIf=\"attachment.transientState?.error\">\r\n <mat-icon class=\"error\">close</mat-icon>\r\n <em class=\"error\">{{attachment.transientState?.errorMessage || 'Error'}}</em>\r\n </ng-container>\r\n <ng-container *ngIf=\"!attachment.transientState?.error\">\r\n <ng-container *ngIf=\"attachment.transientState?.uploading\">\r\n <mat-spinner></mat-spinner>\r\n <em>Uploading...</em>\r\n </ng-container>\r\n <ng-container *ngIf=\"!attachment.transientState?.uploading\">\r\n <img [src]=\"attachment.url\" alt=\"Message Attachment\">\r\n </ng-container>\r\n </ng-container>\r\n </div>\r\n </div>\r\n\r\n </div>\r\n </div>\r\n <div class=\"actions\">\r\n <ng-container *ngIf=\"!user\">\r\n <button\r\n mat-raised-button\r\n color=\"primary\"\r\n type=\"button\"\r\n (click)=\"showSignIn()\"\r\n >{{signInLabel}}</button>\r\n </ng-container>\r\n <ng-container *ngIf=\"user\">\r\n <button\r\n mat-raised-button\r\n class=\"send\"\r\n color=\"primary\"\r\n [disabled]=\"!sendButtonEnabled\"\r\n >\r\n <ng-container *ngIf=\"canComment\">\r\n <mat-icon *ngIf=\"!sending\">chevron_right</mat-icon>\r\n <mat-spinner *ngIf=\"sending\" class=\"icon\" diameter=\"18\" strokeWidth=\"2\"></mat-spinner>\r\n </ng-container>\r\n <span class=\"label\">\r\n <ng-container *ngIf=\"!canComment\">\r\n {{permissionDeniedLabel}}\r\n </ng-container>\r\n <ng-container *ngIf=\"canComment\">\r\n <ng-container *ngIf=\"!sending\">\r\n {{sendLabel}}\r\n </ng-container>\r\n <ng-container *ngIf=\"sending\">\r\n {{sendingLabel}}\r\n </ng-container>\r\n </ng-container>\r\n </span>\r\n </button>\r\n </ng-container>\r\n </div>\r\n</form>",
9003
- styles: ["@-webkit-keyframes comment-field-appear{0%{transform:translateY(128px);opacity:0}to{transform:translate(0);opacity:1}}@keyframes comment-field-appear{0%{transform:translateY(128px);opacity:0}to{transform:translate(0);opacity:1}}:host{margin:0 2em 0 0;display:block;-webkit-animation-name:comment-field-appear;animation-name:comment-field-appear;-webkit-animation-duration:.8s;animation-duration:.8s;-webkit-animation-delay:.4s;animation-delay:.4s;-webkit-animation-fill-mode:both;animation-fill-mode:both;position:relative;z-index:20}.avatar-container{width:calc(48px + 1.75em);display:flex;justify-content:flex-end;flex-shrink:0}.avatar-container .avatar{width:48px;height:48px;background:#000;border-radius:100%;background-size:cover;background-repeat:no-repeat;background-position:50%;margin-top:.75em;margin-right:.75em}form{display:flex;padding:.5em;align-items:center}form .text-container{position:relative;display:flex;flex-grow:1;min-width:0}form .text-container textarea{font-size:14pt;width:100%}form .text-container textarea[disabled]{opacity:.5}form .text-container mat-spinner.loading{position:absolute;left:.5em;bottom:.5em}form .text-container .options-line{display:flex;align-items:center}form .text-container .options-line>*{flex-shrink:0}form .text-container .options-line .error-message{left:.5em;bottom:.5em;color:#683333;overflow-x:hidden;max-width:1.5em;white-space:nowrap;transition:max-width 2s ease-in-out;text-overflow:ellipsis;overflow:hidden;flex-shrink:1}form .text-container .options-line .error-message.expanded,form .text-container .options-line .error-message:hover{max-width:100%}form .text-container .options-line .error-message mat-icon{vertical-align:middle}form input[type=text]{background:#000;color:#fff;border:1px solid #333;width:100%;height:1em}form .actions{margin-left:1em;flex-shrink:0}form button{display:block;margin:0 0 0 auto}form.new-message{display:flex;align-items:flex-start;min-width:0}form.new-message .field-container{flex-grow:1;display:flex;flex-direction:column;min-width:0}form.new-message mat-form-field{width:100%}form.new-message mat-form-field ::ng-deep .mat-form-field-wrapper{padding-bottom:0}form.new-message button{margin:1.25em 0 0}button.send{min-width:9em}textarea{max-height:7em}.autocomplete-container{width:calc(100% - 2em);position:relative;pointer-events:none;top:-2em}.autocomplete{visibility:hidden;pointer-events:none;position:absolute;background:#333;padding:.5em;display:flex;flex-direction:column;z-index:100}.autocomplete.visible{visibility:visible;pointer-events:auto}.autocomplete a{width:100%;text-align:left}.autocomplete a.active{background:#555}@media (max-width:500px){:host{margin:0}.avatar-container{width:auto;flex-shrink:0}.avatar-container .avatar{width:32px;height:32px;margin-top:1.5em}button.send{min-width:auto;margin-top:1.5em}button.send .label{display:none}}:host-context(.banta-mobile) :host{margin:0}:host-context(.banta-mobile) .avatar-container{width:auto;flex-shrink:0}:host-context(.banta-mobile) .avatar-container .avatar{width:32px;height:32px;margin-top:1.5em}:host-context(.banta-mobile) button.send{min-width:auto;margin-top:1.5em}:host-context(.banta-mobile) button.send .label{display:none}.message-attachments-container{display:flex;gap:20px}.message-attachments-container .message-attachment{width:300px;position:relative;text-align:center}.message-attachments-container .message-attachment.with-border{outline:1px solid #333;padding:1em 0}.message-attachments-container .message-attachment mat-spinner{display:block;margin:0 auto .5em;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content}.message-attachments-container .message-attachment mat-icon.error{display:block;font-size:48px;width:48px;height:48px;margin:0 auto .5em}.message-attachments-container .message-attachment .error{color:#b76363}.message-attachments-container .message-attachment img{width:300px;border-radius:10px}.message-attachments-container .message-attachment .remove-img{position:absolute;right:10px;top:10px;margin:0}.field-row{position:relative}"]
9254
+ template: "<form class=\"new-message\" (submit)=\"sendMessage()\">\r\n <div class=\"avatar-container\">\r\n <a href=\"javascript:;\"\r\n class=\"avatar\"\r\n (click)=\"showEditAvatar()\"\r\n [style.background-image]=\"'url(' + userAvatarUrl + ')'\"\r\n ></a>\r\n </div>\r\n <div class=\"text-container\">\r\n <div class=\"field-container\">\r\n <div class=\"field-row\">\r\n <mat-form-field appearance=\"outline\" floatLabel=\"always\">\r\n <mat-label>{{label}}</mat-label>\r\n <textarea\r\n #textarea\r\n name=\"message\"\r\n [placeholder]=\"placeholder\"\r\n matInput\r\n cdkTextareaAutosize\r\n [maxlength]=\"maxLength\"\r\n (keydown)=\"onKeyDown($event)\"\r\n (blur)=\"onBlur()\"\r\n [disabled]=\"sending\"\r\n [(ngModel)]=\"text\"></textarea>\r\n </mat-form-field>\r\n <div class=\"options-line\">\r\n <mat-spinner *ngIf=\"sending\" class=\"icon loading\" diameter=\"18\" strokeWidth=\"2\"></mat-spinner>\r\n <div *ngIf=\"sendError\" class=\"error-message\" [class.expanded]=\"expandError\" [matTooltip]=\"sendError.message\" (click)=\"alertError()\">\r\n <mat-icon *ngIf=\"sendError\">error</mat-icon>\r\n {{sendError.message}}\r\n </div>\r\n <div class=\"spacer\"></div>\r\n <div class=\"custom\">\r\n <ng-content></ng-content>\r\n </div>\r\n <banta-attachment-button \r\n *ngIf=\"allowAttachments\"\r\n (addedAttachment)=\"addedAttachment($event)\"\r\n (attachmentError)=\"attachmentError($event)\"\r\n ></banta-attachment-button>\r\n <emoji-selector-button (selected)=\"insertEmoji($event)\"></emoji-selector-button>\r\n </div>\r\n \r\n </div>\r\n <div #autocompleteContainer class=\"autocomplete-container\">\r\n <div #autocomplete class=\"autocomplete\" [class.visible]=\"autocompleteVisible\">\r\n\r\n <div>\r\n <strong>{{completionPrefix}}</strong>...\r\n </div>\r\n <a\r\n mat-button\r\n *ngFor=\"let option of autocompleteOptions; index as index\"\r\n (click)=\"activateAutoComplete(option)\"\r\n [class.active]=\"autoCompleteSelected === index\"\r\n >\r\n {{option.label}}\r\n </a>\r\n </div>\r\n </div>\r\n\r\n <banta-attachments \r\n [attachments]=\"chatMessageAttachments\"\r\n [editing]=\"true\"\r\n (remove)=\"removeAttachment($event)\"\r\n ></banta-attachments>\r\n </div>\r\n </div>\r\n <div class=\"actions\">\r\n <ng-container *ngIf=\"!user\">\r\n <button\r\n mat-raised-button\r\n color=\"primary\"\r\n type=\"button\"\r\n (click)=\"showSignIn()\"\r\n >{{signInLabel}}</button>\r\n </ng-container>\r\n <ng-container *ngIf=\"user\">\r\n <button\r\n mat-raised-button\r\n class=\"send\"\r\n color=\"primary\"\r\n [disabled]=\"!sendButtonEnabled\"\r\n >\r\n <ng-container *ngIf=\"canComment\">\r\n <mat-icon *ngIf=\"!sending\">chevron_right</mat-icon>\r\n <mat-spinner *ngIf=\"sending\" class=\"icon\" diameter=\"18\" strokeWidth=\"2\"></mat-spinner>\r\n </ng-container>\r\n <span class=\"label\">\r\n <ng-container *ngIf=\"!canComment\">\r\n {{permissionDeniedLabel}}\r\n </ng-container>\r\n <ng-container *ngIf=\"canComment\">\r\n <ng-container *ngIf=\"!sending\">\r\n {{sendLabel}}\r\n </ng-container>\r\n <ng-container *ngIf=\"sending\">\r\n {{sendingLabel}}\r\n </ng-container>\r\n </ng-container>\r\n </span>\r\n </button>\r\n </ng-container>\r\n </div>\r\n</form>",
9255
+ styles: ["@-webkit-keyframes comment-field-appear{0%{transform:translateY(128px);opacity:0}to{transform:translate(0);opacity:1}}@keyframes comment-field-appear{0%{transform:translateY(128px);opacity:0}to{transform:translate(0);opacity:1}}:host{margin:0 2em 0 0;display:block;-webkit-animation-name:comment-field-appear;animation-name:comment-field-appear;-webkit-animation-duration:.8s;animation-duration:.8s;-webkit-animation-delay:.4s;animation-delay:.4s;-webkit-animation-fill-mode:both;animation-fill-mode:both;position:relative;z-index:20}.avatar-container{width:calc(48px + 1.75em);display:flex;justify-content:flex-end;flex-shrink:0}.avatar-container .avatar{width:48px;height:48px;background:#000;border-radius:100%;background-size:cover;background-repeat:no-repeat;background-position:50%;margin-top:.75em;margin-right:.75em}form{display:flex;padding:.5em;align-items:center}form .text-container{position:relative;display:flex;flex-grow:1;min-width:0}form .text-container textarea{font-size:14pt;width:100%}form .text-container textarea[disabled]{opacity:.5}form .text-container mat-spinner.loading{position:absolute;left:.5em;bottom:.5em}form .text-container .options-line{display:flex;align-items:center}form .text-container .options-line>*{flex-shrink:0}form .text-container .options-line .error-message{left:.5em;bottom:.5em;color:#683333;overflow-x:hidden;max-width:1.5em;white-space:nowrap;transition:max-width 2s ease-in-out;text-overflow:ellipsis;overflow:hidden;flex-shrink:1}form .text-container .options-line .error-message.expanded,form .text-container .options-line .error-message:hover{max-width:100%}form .text-container .options-line .error-message mat-icon{vertical-align:middle}form input[type=text]{background:#000;color:#fff;border:1px solid #333;width:100%;height:1em}form .actions{margin-left:1em;flex-shrink:0}form button{display:block;margin:0 0 0 auto}form.new-message{display:flex;align-items:flex-start;min-width:0}form.new-message .field-container{flex-grow:1;display:flex;flex-direction:column;min-width:0}form.new-message mat-form-field{width:100%}form.new-message mat-form-field ::ng-deep .mat-form-field-wrapper{padding-bottom:0}form.new-message button{margin:1.25em 0 0}button.send{min-width:9em}textarea{max-height:7em}.autocomplete-container{width:calc(100% - 2em);position:relative;pointer-events:none;top:-2em}.autocomplete{visibility:hidden;pointer-events:none;position:absolute;background:#333;padding:.5em;display:flex;flex-direction:column;z-index:100}.autocomplete.visible{visibility:visible;pointer-events:auto}.autocomplete a{width:100%;text-align:left}.autocomplete a.active{background:#555}@media (max-width:500px){:host{margin:0}.avatar-container{width:auto;flex-shrink:0}.avatar-container .avatar{width:32px;height:32px;margin-top:1.5em}button.send{min-width:auto;margin-top:1.5em}button.send .label{display:none}}:host-context(.banta-mobile) :host{margin:0}:host-context(.banta-mobile) .avatar-container{width:auto;flex-shrink:0}:host-context(.banta-mobile) .avatar-container .avatar{width:32px;height:32px;margin-top:1.5em}:host-context(.banta-mobile) button.send{min-width:auto;margin-top:1.5em}:host-context(.banta-mobile) button.send .label{display:none}.image-attachments-container{display:flex;gap:20px}.image-attachments-container .image-attachment{width:300px;position:relative;text-align:center}.image-attachments-container .image-attachment.with-border{outline:1px solid #333;padding:1em 0}.image-attachments-container .image-attachment mat-spinner{display:block;margin:0 auto .5em;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content}.image-attachments-container .image-attachment mat-icon.error{display:block;font-size:48px;width:48px;height:48px;margin:0 auto .5em}.image-attachments-container .image-attachment .error{color:#b76363}.image-attachments-container .image-attachment img{width:300px;border-radius:10px}.image-attachments-container .image-attachment .remove-img{position:absolute;right:10px;top:10px;margin:0}.card-attachment,.field-row{position:relative}.card-attachment a{display:flex;align-items:flex-start;gap:1em;width:100%;border:1px solid #666;border-radius:4px;padding:2em;box-sizing:border-box;background-color:#191919}.card-attachment a img{width:300px;aspect-ratio:16/9;-o-object-fit:cover;object-fit:cover;border-radius:10px}.card-attachment a h1{margin:0;font-size:30px}.card-attachment .remove-img{position:absolute;right:10px;top:10px;margin:0}"]
9004
9256
  },] }
9005
9257
  ];
9258
+ CommentFieldComponent.ctorParameters = () => [
9259
+ { type: ChatBackendBase }
9260
+ ];
9006
9261
  CommentFieldComponent.propDecorators = {
9007
9262
  source: [{ type: Input }],
9008
9263
  user: [{ type: Input }],
@@ -9154,7 +9409,9 @@ CommentsModule.decorators = [
9154
9409
  BantaCommonModule,
9155
9410
  EmojiModule,
9156
9411
  MatTooltipModule,
9157
- MatSelectModule
9412
+ MatSelectModule,
9413
+ OverlayModule,
9414
+ PortalModule
9158
9415
  ],
9159
9416
  exports: COMPONENTS$3
9160
9417
  },] }
@@ -9381,6 +9638,24 @@ class ChatBackend extends ChatBackendBase {
9381
9638
  watchMessage(message, handler) {
9382
9639
  throw new Error("Method not implemented.");
9383
9640
  }
9641
+ getCardForUrl(url) {
9642
+ return __awaiter(this, void 0, void 0, function* () {
9643
+ let response = yield fetch(`${this.serviceUrl}/urls`, {
9644
+ method: 'POST',
9645
+ headers: {
9646
+ 'Content-Type': 'application/json'
9647
+ },
9648
+ body: JSON.stringify({
9649
+ url
9650
+ })
9651
+ });
9652
+ if (response.status == 404)
9653
+ return null;
9654
+ if (response.status >= 400)
9655
+ throw new Error(`Failed to retrieve URL card: ${response.status}. Body: '${yield response.text()}'`);
9656
+ return yield response.json();
9657
+ });
9658
+ }
9384
9659
  }
9385
9660
  ChatBackend.decorators = [
9386
9661
  { type: Injectable }
@@ -9420,7 +9695,9 @@ BantaSdkModule.decorators = [
9420
9695
  MatFormFieldModule,
9421
9696
  MatInputModule,
9422
9697
  MatProgressSpinnerModule,
9423
- MatSnackBarModule
9698
+ MatSnackBarModule,
9699
+ OverlayModule,
9700
+ PortalModule
9424
9701
  ],
9425
9702
  declarations: [
9426
9703
  BantaComponent,
@@ -9445,5 +9722,5 @@ BantaSdkModule.decorators = [
9445
9722
  * Generated bundle index. Do not edit.
9446
9723
  */
9447
9724
 
9448
- export { AttachmentButtonComponent, BANTA_SDK_OPTIONS, BantaChatComponent, BantaCommentsComponent, BantaCommonModule, BantaComponent, BantaLogoComponent, BantaMarkdownToHtmlPipe, BantaReplySendOptionsDirective, BantaSdkModule, ChatBackend, ChatBackendBase, ChatMessageComponent, ChatModule, ChatSource, ChatViewComponent, CommentComponent, CommentFieldComponent, CommentSortComponent, CommentViewComponent, CommentsModule, EMOJIS, EmojiModule, EmojiSelectorButtonComponent, EmojiSelectorPanelComponent, LightboxComponent, LiveChatMessageComponent, LiveCommentComponent, LiveMessageComponent, TimestampComponent, lazyConnection };
9725
+ export { AttachmentButtonComponent, BANTA_SDK_OPTIONS, BantaAttachmentComponent, BantaAttachmentsComponent, BantaChatComponent, BantaCommentsComponent, BantaCommonModule, BantaComponent, BantaLogoComponent, BantaMarkdownToHtmlPipe, BantaReplySendOptionsDirective, BantaSdkModule, BantaTrustResourceUrlPipe, ChatBackend, ChatBackendBase, ChatMessageComponent, ChatModule, ChatSource, ChatViewComponent, CommentComponent, CommentFieldComponent, CommentSortComponent, CommentViewComponent, CommentsModule, EMOJIS, EmojiModule, EmojiSelectorButtonComponent, EmojiSelectorPanelComponent, LightboxComponent, LiveChatMessageComponent, LiveCommentComponent, LiveMessageComponent, TimestampComponent, lazyConnection };
9449
9726
  //# sourceMappingURL=banta-sdk.js.map