@banta/sdk 4.3.1 → 4.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/bundles/banta-sdk.umd.js +576 -118
  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 +5 -3
  10. package/esm2015/lib/comments/comment/comment.component.js +9 -12
  11. package/esm2015/lib/comments/comment-field/comment-field.component.js +91 -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/trust-resource-url.pipe.js +21 -0
  17. package/esm2015/lib/emoji/emoji-selector-button.component.js +56 -105
  18. package/esm2015/lib/emoji/emoji.module.js +6 -2
  19. package/fesm2015/banta-sdk.js +408 -127
  20. package/fesm2015/banta-sdk.js.map +1 -1
  21. package/lib/attachment-scraper.d.ts +30 -0
  22. package/lib/chat-backend-base.d.ts +9 -0
  23. package/lib/chat-backend.d.ts +2 -1
  24. package/lib/comments/banta-comments/banta-comments.component.d.ts +1 -0
  25. package/lib/comments/comment/comment.component.d.ts +2 -4
  26. package/lib/comments/comment-field/comment-field.component.d.ts +12 -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/trust-resource-url.pipe.d.ts +7 -0
  30. package/lib/emoji/emoji-selector-button.component.d.ts +13 -14
  31. package/package.json +1 -1
@@ -1,8 +1,8 @@
1
1
  (function (global, factory) {
2
- typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('rxjs'), require('rxjs/operators'), require('@angular/core'), require('marked'), require('dompurify'), require('@angular/platform-browser'), require('@angular/common'), require('@angular/material/icon'), require('@angular/material/button'), require('@angular/material/form-field'), require('@angular/material/input'), require('@angular/forms'), require('@angular/material/dialog'), require('@banta/common'), require('@angular/router'), require('@angular/material/snack-bar'), require('@angular/material/menu'), require('@angular/material/progress-spinner'), require('@angular/cdk/text-field'), require('@angular/material/tooltip'), require('@angular/material/select')) :
3
- typeof define === 'function' && define.amd ? define('@banta/sdk', ['exports', 'rxjs', 'rxjs/operators', '@angular/core', 'marked', 'dompurify', '@angular/platform-browser', '@angular/common', '@angular/material/icon', '@angular/material/button', '@angular/material/form-field', '@angular/material/input', '@angular/forms', '@angular/material/dialog', '@banta/common', '@angular/router', '@angular/material/snack-bar', '@angular/material/menu', '@angular/material/progress-spinner', '@angular/cdk/text-field', '@angular/material/tooltip', '@angular/material/select'], factory) :
4
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory((global.banta = global.banta || {}, global.banta.sdk = {}), global.rxjs, global.rxjs.operators, global.ng.core, global.marked, global.DOMPurify, global.ng.platformBrowser, global.ng.common, global.ng.material.icon, global.ng.material.button, global.ng.material.formField, global.ng.material.input, global.ng.forms, global.ng.material.dialog, global.common$1, global.ng.router, global.ng.material.snackBar, global.ng.material.menu, global.ng.material.progressSpinner, global.ng.cdk.textField, global.ng.material.tooltip, global.ng.material.select));
5
- }(this, (function (exports, rxjs, operators, core, marked, DOMPurify, platformBrowser, common, icon, button, formField, input, forms, dialog, common$1, router, snackBar, menu, progressSpinner, textField, tooltip, select) { 'use strict';
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('rxjs'), require('rxjs/operators'), require('@angular/core'), require('marked'), require('dompurify'), require('@angular/platform-browser'), require('@angular/common'), require('@angular/material/icon'), require('@angular/material/progress-spinner'), require('@angular/material/button'), require('@angular/cdk/overlay'), require('@angular/material/form-field'), require('@angular/material/input'), require('@angular/forms'), require('@angular/cdk/portal'), require('@angular/material/dialog'), require('@banta/common'), require('@angular/router'), require('@angular/material/snack-bar'), require('@angular/material/menu'), require('@angular/cdk/text-field'), require('@angular/material/tooltip'), require('@angular/material/select')) :
3
+ typeof define === 'function' && define.amd ? define('@banta/sdk', ['exports', 'rxjs', 'rxjs/operators', '@angular/core', 'marked', 'dompurify', '@angular/platform-browser', '@angular/common', '@angular/material/icon', '@angular/material/progress-spinner', '@angular/material/button', '@angular/cdk/overlay', '@angular/material/form-field', '@angular/material/input', '@angular/forms', '@angular/cdk/portal', '@angular/material/dialog', '@banta/common', '@angular/router', '@angular/material/snack-bar', '@angular/material/menu', '@angular/cdk/text-field', '@angular/material/tooltip', '@angular/material/select'], factory) :
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory((global.banta = global.banta || {}, global.banta.sdk = {}), global.rxjs, global.rxjs.operators, global.ng.core, global.marked, global.DOMPurify, global.ng.platformBrowser, global.ng.common, global.ng.material.icon, global.ng.material.progressSpinner, global.ng.material.button, global.ng.cdk.overlay, global.ng.material.formField, global.ng.material.input, global.ng.forms, global.ng.cdk.portal, global.ng.material.dialog, global.common$1, global.ng.router, global.ng.material.snackBar, global.ng.material.menu, global.ng.cdk.textField, global.ng.material.tooltip, global.ng.material.select));
5
+ }(this, (function (exports, rxjs, operators, core, marked, DOMPurify, platformBrowser, common, icon, progressSpinner, button, overlay, formField, input, forms, portal, dialog, common$1, router, snackBar, menu, textField, tooltip, select) { 'use strict';
6
6
 
7
7
  function lazyConnection(options) {
8
8
  var obs = new rxjs.Observable(function (observer) {
@@ -207,10 +207,156 @@
207
207
  { type: platformBrowser.DomSanitizer }
208
208
  ]; };
209
209
 
210
+ var BantaAttachmentsComponent = /** @class */ (function () {
211
+ function BantaAttachmentsComponent() {
212
+ this.editing = false;
213
+ this.remove = new rxjs.Subject();
214
+ }
215
+ BantaAttachmentsComponent.prototype.removeAttachment = function (attachment) {
216
+ this.remove.next(attachment);
217
+ };
218
+ BantaAttachmentsComponent.prototype.isImageAttachment = function (attachment) {
219
+ if (attachment.type.startsWith('image/'))
220
+ return true;
221
+ return false;
222
+ };
223
+ BantaAttachmentsComponent.prototype.isCardAttachment = function (attachment) {
224
+ if (['card'].includes(attachment.type))
225
+ return true;
226
+ return false;
227
+ };
228
+ BantaAttachmentsComponent.prototype.showLightbox = function (image) {
229
+ this.lightbox.open(image.url, this.attachments
230
+ .filter(function (x) { return x.type === 'image/png'; })
231
+ .map(function (x) { return x.url; }));
232
+ };
233
+ Object.defineProperty(BantaAttachmentsComponent.prototype, "inlineAttachments", {
234
+ get: function () {
235
+ return this.attachments.filter(function (x) { return x.type !== 'card' && (x.style === 'inline' || !x.style); });
236
+ },
237
+ enumerable: false,
238
+ configurable: true
239
+ });
240
+ Object.defineProperty(BantaAttachmentsComponent.prototype, "blockAttachments", {
241
+ get: function () {
242
+ return this.attachments.filter(function (x) { return x.style === 'block' || x.type === 'card'; });
243
+ },
244
+ enumerable: false,
245
+ configurable: true
246
+ });
247
+ return BantaAttachmentsComponent;
248
+ }());
249
+ BantaAttachmentsComponent.decorators = [
250
+ { type: core.Component, args: [{
251
+ selector: 'banta-attachments',
252
+ 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>",
253
+ 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}"]
254
+ },] }
255
+ ];
256
+ BantaAttachmentsComponent.propDecorators = {
257
+ attachments: [{ type: core.Input }],
258
+ editing: [{ type: core.Input }],
259
+ lightbox: [{ type: core.ViewChild, args: ['lightbox',] }],
260
+ remove: [{ type: core.Output }]
261
+ };
262
+
263
+ var BantaTrustResourceUrlPipe = /** @class */ (function () {
264
+ function BantaTrustResourceUrlPipe(sanitizer) {
265
+ this.sanitizer = sanitizer;
266
+ }
267
+ BantaTrustResourceUrlPipe.prototype.transform = function (value) {
268
+ if (!value)
269
+ return undefined;
270
+ return this.sanitizer.bypassSecurityTrustResourceUrl(value);
271
+ };
272
+ return BantaTrustResourceUrlPipe;
273
+ }());
274
+ BantaTrustResourceUrlPipe.decorators = [
275
+ { type: core.Pipe, args: [{
276
+ name: 'trustResourceUrl'
277
+ },] }
278
+ ];
279
+ BantaTrustResourceUrlPipe.ctorParameters = function () { return [
280
+ { type: platformBrowser.DomSanitizer }
281
+ ]; };
282
+
283
+ var BantaAttachmentComponent = /** @class */ (function () {
284
+ function BantaAttachmentComponent() {
285
+ this.loading = false;
286
+ this.editing = false;
287
+ this.loadingMessage = 'Please wait...';
288
+ this.error = false;
289
+ this.errorMessage = 'An error has occurred';
290
+ this.removed = new rxjs.Subject();
291
+ this.activated = new rxjs.Subject();
292
+ }
293
+ BantaAttachmentComponent.prototype.activate = function () {
294
+ this.activated.next();
295
+ };
296
+ BantaAttachmentComponent.prototype.remove = function () {
297
+ this.removed.next();
298
+ };
299
+ Object.defineProperty(BantaAttachmentComponent.prototype, "isError", {
300
+ get: function () {
301
+ var _a, _b;
302
+ return this.error || ((_b = (_a = this.attachment) === null || _a === void 0 ? void 0 : _a.transientState) === null || _b === void 0 ? void 0 : _b.error);
303
+ },
304
+ enumerable: false,
305
+ configurable: true
306
+ });
307
+ Object.defineProperty(BantaAttachmentComponent.prototype, "theErrorMessage", {
308
+ get: function () {
309
+ var _a, _b;
310
+ return this.errorMessage || ((_b = (_a = this.attachment) === null || _a === void 0 ? void 0 : _a.transientState) === null || _b === void 0 ? void 0 : _b.errorMessage);
311
+ },
312
+ enumerable: false,
313
+ configurable: true
314
+ });
315
+ Object.defineProperty(BantaAttachmentComponent.prototype, "isLoading", {
316
+ get: function () {
317
+ var _a;
318
+ return this.loading || !this.attachment || ((_a = this.attachment.transientState) === null || _a === void 0 ? void 0 : _a.loading) || !this.attachment.url;
319
+ },
320
+ enumerable: false,
321
+ configurable: true
322
+ });
323
+ Object.defineProperty(BantaAttachmentComponent.prototype, "isImageAttachment", {
324
+ get: function () {
325
+ if (this.attachment.type.startsWith('image/'))
326
+ return true;
327
+ return false;
328
+ },
329
+ enumerable: false,
330
+ configurable: true
331
+ });
332
+ return BantaAttachmentComponent;
333
+ }());
334
+ BantaAttachmentComponent.decorators = [
335
+ { type: core.Component, args: [{
336
+ selector: 'banta-attachment',
337
+ 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>",
338
+ 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}"]
339
+ },] }
340
+ ];
341
+ BantaAttachmentComponent.propDecorators = {
342
+ attachment: [{ type: core.Input }],
343
+ loading: [{ type: core.Input }],
344
+ editing: [{ type: core.Input }],
345
+ loadingMessage: [{ type: core.Input }],
346
+ error: [{ type: core.Input }],
347
+ errorMessage: [{ type: core.Input }],
348
+ removed: [{ type: core.Output }],
349
+ activated: [{ type: core.Output }],
350
+ isLoading: [{ type: core.HostBinding, args: ['class.loading',] }]
351
+ };
352
+
210
353
  var COMPONENTS = [
211
354
  TimestampComponent,
212
355
  LightboxComponent,
213
- BantaMarkdownToHtmlPipe
356
+ BantaMarkdownToHtmlPipe,
357
+ BantaTrustResourceUrlPipe,
358
+ BantaAttachmentComponent,
359
+ BantaAttachmentsComponent
214
360
  ];
215
361
  var BantaCommonModule = /** @class */ (function () {
216
362
  function BantaCommonModule() {
@@ -222,7 +368,9 @@
222
368
  declarations: COMPONENTS,
223
369
  imports: [
224
370
  common.CommonModule,
225
- icon.MatIconModule
371
+ icon.MatIconModule,
372
+ progressSpinner.MatProgressSpinnerModule,
373
+ button.MatButtonModule
226
374
  ],
227
375
  exports: COMPONENTS
228
376
  },] }
@@ -7166,8 +7314,9 @@
7166
7314
 
7167
7315
  /// <reference types="@types/resize-observer-browser" />
7168
7316
  var EmojiSelectorButtonComponent = /** @class */ (function () {
7169
- function EmojiSelectorButtonComponent(elementRef) {
7317
+ function EmojiSelectorButtonComponent(elementRef, overlay) {
7170
7318
  this.elementRef = elementRef;
7319
+ this.overlay = overlay;
7171
7320
  this._selected = new rxjs.Subject();
7172
7321
  this.showEmojiPanel = false;
7173
7322
  }
@@ -7178,115 +7327,76 @@
7178
7327
  enumerable: false,
7179
7328
  configurable: true
7180
7329
  });
7181
- EmojiSelectorButtonComponent.prototype.ngOnDestroy = function () {
7182
- this.removeListener();
7183
- this.panelElement.nativeElement.remove();
7184
- };
7185
- Object.defineProperty(EmojiSelectorButtonComponent.prototype, "widthConstrained", {
7186
- get: function () { return this.width < 700; },
7330
+ Object.defineProperty(EmojiSelectorButtonComponent.prototype, "isOpen", {
7331
+ get: function () {
7332
+ return this.overlayRef;
7333
+ },
7187
7334
  enumerable: false,
7188
7335
  configurable: true
7189
7336
  });
7190
- EmojiSelectorButtonComponent.prototype.ngAfterViewInit = function () {
7191
- };
7192
- EmojiSelectorButtonComponent.prototype.putPanelAtRoot = function () {
7193
- // If we are in full-screen, placing the panel outside of the full-screen element will result in it
7194
- // always being behind said full-screen element, so we need to ensure we never place it further up the
7195
- // stack.
7196
- var root = document.fullscreenElement || document.body.querySelector('[ng-version]') || document.body;
7197
- root.appendChild(this.panelElement.nativeElement);
7198
- };
7199
- EmojiSelectorButtonComponent.prototype.removeListener = function () {
7200
- document.removeEventListener('click', this.clickListener);
7201
- window.removeEventListener('resize', this.resizeListener);
7202
- };
7203
- EmojiSelectorButtonComponent.prototype.place = function () {
7204
- // Not currently used as it can't be easily done handling all
7205
- // scrolling corner cases.
7206
- this.putPanelAtRoot();
7207
- var pos = this.buttonElement.nativeElement.getBoundingClientRect();
7208
- var size = this.panelElement.nativeElement.getBoundingClientRect();
7209
- var left = window.scrollX + pos.left + pos.width - size.width;
7210
- if (left < 0)
7211
- left = (window.scrollX + window.innerWidth) / 2 - size.width / 2;
7212
- var scrollY = window.scrollY;
7213
- if (document.fullscreenElement) {
7337
+ /**
7338
+ * Insert the given emoji.
7339
+ * @param str
7340
+ */
7341
+ EmojiSelectorButtonComponent.prototype.insert = function (str) {
7342
+ this._selected.next(str);
7343
+ };
7344
+ EmojiSelectorButtonComponent.prototype.close = function () {
7345
+ if (this.overlayRef) {
7346
+ this.overlayRef.dispose();
7347
+ this.overlayRef = null;
7348
+ return;
7214
7349
  }
7215
- Object.assign(this.panelElement.nativeElement.style, {
7216
- top: window.scrollY + pos.top + pos.height + "px",
7217
- left: Math.max(0, left) + "px"
7218
- });
7219
7350
  };
7220
7351
  EmojiSelectorButtonComponent.prototype.show = function () {
7221
7352
  var _this = this;
7222
- if (this.showEmojiPanel) {
7223
- this.showEmojiPanel = false;
7224
- return;
7353
+ if (this.isOpen) {
7354
+ this.close();
7225
7355
  }
7226
- this.showEmojiPanel = true;
7227
- //this.place();
7228
- setTimeout(function () {
7229
- var onResize = function () {
7230
- if (!_this.showEmojiPanel)
7231
- return;
7232
- _this.width = window.innerWidth;
7233
- _this.height = window.innerHeight;
7234
- var edgeOffset = 0;
7235
- var commentField = _this.elementRef.nativeElement.closest("banta-comment-field");
7236
- if (commentField) {
7237
- var size = commentField.getBoundingClientRect();
7238
- _this.width = size.width;
7239
- edgeOffset = window.innerWidth - size.right;
7240
- }
7241
- var buttonRect = _this.buttonElement.nativeElement.getBoundingClientRect();
7242
- var buttonRight = window.innerWidth - buttonRect.right - edgeOffset - 10;
7243
- if (_this.width < 700) {
7244
- _this.panelElement.nativeElement.style.right = -buttonRight + "px";
7245
- }
7246
- else {
7247
- _this.panelElement.nativeElement.style.right = '';
7248
- }
7249
- _this.panelElement.nativeElement.style.maxWidth = _this.width - 15 + "px";
7250
- };
7251
- _this.resizeListener = onResize;
7252
- onResize();
7253
- _this.clickListener = function (ev) {
7254
- var parent = ev.target;
7255
- var isInDialog = false;
7256
- while (parent) {
7257
- if (parent.matches('emoji-selector-panel'))
7258
- isInDialog = true;
7259
- parent = parent.parentElement;
7356
+ this.overlayRef = this.overlay.create({
7357
+ positionStrategy: this.overlay.position()
7358
+ .flexibleConnectedTo(this.elementRef)
7359
+ .withPositions([
7360
+ {
7361
+ originX: 'end',
7362
+ originY: 'bottom',
7363
+ overlayX: 'end',
7364
+ overlayY: 'top'
7260
7365
  }
7261
- if (isInDialog)
7262
- return;
7263
- _this.showEmojiPanel = false;
7264
- _this.removeListener();
7265
- };
7266
- document.addEventListener('click', _this.clickListener);
7267
- window.addEventListener('resize', _this.resizeListener);
7366
+ ])
7367
+ .withFlexibleDimensions(true),
7368
+ hasBackdrop: true,
7369
+ disposeOnNavigation: true,
7370
+ scrollStrategy: this.overlay.scrollStrategies.reposition({
7371
+ autoClose: true
7372
+ })
7268
7373
  });
7269
- };
7270
- EmojiSelectorButtonComponent.prototype.insert = function (str) {
7271
- this._selected.next(str);
7374
+ this.overlayRef.backdropClick().subscribe(function () {
7375
+ _this.close();
7376
+ });
7377
+ this.overlayRef.keydownEvents().subscribe(function (event) {
7378
+ if (event.key === 'Escape') {
7379
+ _this.close();
7380
+ }
7381
+ });
7382
+ this.overlayRef.attach(this.selectorPanelTemplate);
7272
7383
  };
7273
7384
  return EmojiSelectorButtonComponent;
7274
7385
  }());
7275
7386
  EmojiSelectorButtonComponent.decorators = [
7276
7387
  { type: core.Component, args: [{
7277
7388
  selector: 'emoji-selector-button',
7278
- template: "\n <button #button type=\"button\" mat-icon-button (click)=\"show()\">\n <mat-icon>emoji_emotions</mat-icon>\n </button>\n <emoji-selector-panel \n #panel\n (selected)=\"insert($event)\"\n [class.visible]=\"showEmojiPanel\"\n ></emoji-selector-panel>\n ",
7279
- styles: ["\n :host {\n display: block;\n position: relative;\n }\n\n emoji-selector-panel {\n position: absolute;\n top: 2.5em;\n right: 0;\n opacity: 0;\n pointer-events: none;\n z-index: 10;\n }\n\n emoji-selector-panel.visible {\n pointer-events: initial;\n opacity: 1;\n }\n\n button {\n color: #666\n }\n "]
7389
+ template: "\n <button #button type=\"button\" mat-icon-button (click)=\"show()\">\n <mat-icon>emoji_emotions</mat-icon>\n </button>\n <ng-template cdkPortal #selectorPanelTemplate=\"cdkPortal\">\n <emoji-selector-panel \n #panel\n (selected)=\"insert($event)\"\n ></emoji-selector-panel>\n </ng-template>\n ",
7390
+ styles: ["\n :host {\n display: block;\n position: relative;\n }\n\n button {\n color: #666\n }\n "]
7280
7391
  },] }
7281
7392
  ];
7282
7393
  EmojiSelectorButtonComponent.ctorParameters = function () { return [
7283
- { type: core.ElementRef }
7394
+ { type: core.ElementRef },
7395
+ { type: overlay.Overlay }
7284
7396
  ]; };
7285
7397
  EmojiSelectorButtonComponent.propDecorators = {
7286
- selected: [{ type: core.Output }],
7287
- panelElement: [{ type: core.ViewChild, args: ['panel', { read: core.ElementRef },] }],
7288
- buttonElement: [{ type: core.ViewChild, args: ['button', { read: core.ElementRef },] }],
7289
- widthConstrained: [{ type: core.HostBinding, args: ['class.width-constrained',] }]
7398
+ selectorPanelTemplate: [{ type: core.ViewChild, args: ['selectorPanelTemplate',] }],
7399
+ selected: [{ type: core.Output }]
7290
7400
  };
7291
7401
 
7292
7402
  var COMPONENTS$1 = [
@@ -7307,15 +7417,131 @@
7307
7417
  icon.MatIconModule,
7308
7418
  button.MatButtonModule,
7309
7419
  formField.MatFormFieldModule,
7310
- input.MatInputModule
7420
+ input.MatInputModule,
7421
+ overlay.OverlayModule,
7422
+ portal.PortalModule
7311
7423
  ],
7312
7424
  exports: COMPONENTS$1
7313
7425
  },] }
7314
7426
  ];
7315
7427
 
7428
+ var 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');
7429
+ var UrlAttachmentScraper = /** @class */ (function () {
7430
+ function UrlAttachmentScraper() {
7431
+ }
7432
+ UrlAttachmentScraper.prototype.findFragments = function (message) {
7433
+ var _a;
7434
+ // If a message already has a URL attachment, don't add another one.
7435
+ if (message.attachments && message.attachments.filter(function (x) { return x.type === 'url'; }).length > 0)
7436
+ return null;
7437
+ return (Array.from((_a = message.message.match(URL_REGEX)) !== null && _a !== void 0 ? _a : []))
7438
+ .reduce(function (a, item) { return (a.includes(item) ? undefined : a.push(item), a); }, [])
7439
+ .map(function (url) { return ({
7440
+ text: url,
7441
+ offset: message.message.indexOf(url),
7442
+ type: 'url'
7443
+ }); });
7444
+ };
7445
+ return UrlAttachmentScraper;
7446
+ }());
7447
+ var GiphyAttachmentResolver = /** @class */ (function () {
7448
+ function GiphyAttachmentResolver() {
7449
+ }
7450
+ GiphyAttachmentResolver.prototype.resolveFragment = function (message, fragment) {
7451
+ var _a;
7452
+ return __awaiter(this, void 0, void 0, function () {
7453
+ var gifId;
7454
+ return __generator(this, function (_b) {
7455
+ if (fragment.type === 'url' && fragment.text.startsWith('https://giphy.com/gifs')) {
7456
+ gifId = (_a = /[^-\/]+$/.exec(fragment.text)) === null || _a === void 0 ? void 0 : _a.toString();
7457
+ if (!gifId)
7458
+ return [2 /*return*/, null];
7459
+ return [2 /*return*/, {
7460
+ type: 'iframe',
7461
+ url: "https://giphy.com/embed/" + gifId,
7462
+ style: 'inline'
7463
+ }];
7464
+ }
7465
+ return [2 /*return*/, null];
7466
+ });
7467
+ });
7468
+ };
7469
+ return GiphyAttachmentResolver;
7470
+ }());
7471
+ var YouTubeAttachmentResolver = /** @class */ (function () {
7472
+ function YouTubeAttachmentResolver() {
7473
+ }
7474
+ YouTubeAttachmentResolver.prototype.resolveFragment = function (message, fragment) {
7475
+ return __awaiter(this, void 0, void 0, function () {
7476
+ var videoId, match;
7477
+ return __generator(this, function (_b) {
7478
+ if (fragment.type !== 'url')
7479
+ return [2 /*return*/, null];
7480
+ if (fragment.text.match(/https?:\/\/(www\.)?youtube.com\/watch\?v=/)) {
7481
+ match = /watch\?v=([^&]+)/.exec(fragment.text);
7482
+ if (match) {
7483
+ videoId = match[1];
7484
+ }
7485
+ }
7486
+ if (videoId) {
7487
+ return [2 /*return*/, {
7488
+ type: 'iframe',
7489
+ url: "https://www.youtube.com/embed/" + videoId,
7490
+ style: 'block'
7491
+ }];
7492
+ }
7493
+ return [2 /*return*/, null];
7494
+ });
7495
+ });
7496
+ };
7497
+ return YouTubeAttachmentResolver;
7498
+ }());
7499
+ var UrlAttachmentResolver = /** @class */ (function () {
7500
+ function UrlAttachmentResolver(backend) {
7501
+ this.backend = backend;
7502
+ }
7503
+ UrlAttachmentResolver.prototype.resolveFragment = function (message, fragment) {
7504
+ return __awaiter(this, void 0, void 0, function () {
7505
+ var urlCard;
7506
+ return __generator(this, function (_b) {
7507
+ switch (_b.label) {
7508
+ case 0:
7509
+ if (fragment.type !== 'url')
7510
+ return [2 /*return*/, null];
7511
+ return [4 /*yield*/, this.backend.getCardForUrl(fragment.text)];
7512
+ case 1:
7513
+ urlCard = _b.sent();
7514
+ if (urlCard) {
7515
+ return [2 /*return*/, {
7516
+ type: 'card',
7517
+ url: fragment.text,
7518
+ card: urlCard,
7519
+ style: 'block'
7520
+ }];
7521
+ }
7522
+ return [2 /*return*/];
7523
+ }
7524
+ });
7525
+ });
7526
+ };
7527
+ return UrlAttachmentResolver;
7528
+ }());
7529
+ UrlAttachmentResolver.decorators = [
7530
+ { type: core.Injectable }
7531
+ ];
7532
+ UrlAttachmentResolver.ctorParameters = function () { return [
7533
+ { type: ChatBackendBase }
7534
+ ]; };
7535
+
7316
7536
  var ChatBackendBase = /** @class */ (function () {
7317
7537
  function ChatBackendBase() {
7318
7538
  this._userChanged = new rxjs.BehaviorSubject(null);
7539
+ this._attachmentScrapers = [];
7540
+ this._attachmentResolvers = [];
7541
+ this.registerAttachmentScraper(new UrlAttachmentScraper());
7542
+ this.registerAttachmentResolver(new GiphyAttachmentResolver());
7543
+ this.registerAttachmentResolver(new YouTubeAttachmentResolver());
7544
+ this.registerAttachmentResolver(new UrlAttachmentResolver(this));
7319
7545
  }
7320
7546
  Object.defineProperty(ChatBackendBase.prototype, "userChanged", {
7321
7547
  get: function () {
@@ -7335,6 +7561,26 @@
7335
7561
  enumerable: false,
7336
7562
  configurable: true
7337
7563
  });
7564
+ ChatBackendBase.prototype.registerAttachmentScraper = function (scraper) {
7565
+ this._attachmentScrapers.push(scraper);
7566
+ };
7567
+ ChatBackendBase.prototype.registerAttachmentResolver = function (resolver) {
7568
+ this._attachmentResolvers.push(resolver);
7569
+ };
7570
+ Object.defineProperty(ChatBackendBase.prototype, "attachmentScrapers", {
7571
+ get: function () {
7572
+ return this._attachmentScrapers.slice();
7573
+ },
7574
+ enumerable: false,
7575
+ configurable: true
7576
+ });
7577
+ Object.defineProperty(ChatBackendBase.prototype, "attachmentResolvers", {
7578
+ get: function () {
7579
+ return this._attachmentResolvers.slice();
7580
+ },
7581
+ enumerable: false,
7582
+ configurable: true
7583
+ });
7338
7584
  return ChatBackendBase;
7339
7585
  }());
7340
7586
 
@@ -8385,11 +8631,6 @@
8385
8631
  this._avatarSelected.next(user);
8386
8632
  this.selectUser();
8387
8633
  };
8388
- CommentComponent.prototype.showLightbox = function (image) {
8389
- this.lightbox.open(image.url, this.message.attachments
8390
- .filter(function (x) { return x.type === 'image/png'; })
8391
- .map(function (x) { return x.url; }));
8392
- };
8393
8634
  CommentComponent.prototype.avatarForUser = function (user) {
8394
8635
  var url = this.genericAvatarUrl;
8395
8636
  if (user && user.avatarUrl) {
@@ -8397,13 +8638,21 @@
8397
8638
  }
8398
8639
  return "url(" + url + ")";
8399
8640
  };
8641
+ Object.defineProperty(CommentComponent.prototype, "replyCount", {
8642
+ get: function () {
8643
+ var _a;
8644
+ return ((_a = this.message.submessages) === null || _a === void 0 ? void 0 : _a.length) || this.message.submessageCount || 0;
8645
+ },
8646
+ enumerable: false,
8647
+ configurable: true
8648
+ });
8400
8649
  return CommentComponent;
8401
8650
  }());
8402
8651
  CommentComponent.decorators = [
8403
8652
  { type: core.Component, args: [{
8404
8653
  selector: 'banta-comment',
8405
- 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",
8406
- 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}"]
8654
+ 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",
8655
+ 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}"]
8407
8656
  },] }
8408
8657
  ];
8409
8658
  CommentComponent.propDecorators = {
@@ -8428,8 +8677,7 @@
8428
8677
  editEnded: [{ type: core.Output }],
8429
8678
  shared: [{ type: core.Output }],
8430
8679
  genericAvatarUrl: [{ type: core.Input }],
8431
- commentId: [{ type: core.HostBinding, args: ['attr.data-comment-id',] }],
8432
- lightbox: [{ type: core.ViewChild, args: ['lightbox',] }]
8680
+ commentId: [{ type: core.HostBinding, args: ['attr.data-comment-id',] }]
8433
8681
  };
8434
8682
 
8435
8683
  var CommentViewComponent = /** @class */ (function () {
@@ -8772,6 +9020,7 @@
8772
9020
  this.loading = true;
8773
9021
  this.showLoadingScreen = false;
8774
9022
  this._loadingMessageIndex = 0;
9023
+ this.maxCommentLength = 1500;
8775
9024
  this.loadingMessages = [
8776
9025
  "Just a second...",
8777
9026
  "We're definitely working on it.",
@@ -9485,8 +9734,8 @@
9485
9734
  BantaCommentsComponent.decorators = [
9486
9735
  { type: core.Component, args: [{
9487
9736
  selector: 'banta-comments',
9488
- 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 [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 (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 (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",
9489
- 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%}"]
9737
+ 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",
9738
+ 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%}"]
9490
9739
  },] }
9491
9740
  ];
9492
9741
  BantaCommentsComponent.ctorParameters = function () { return [
@@ -9499,6 +9748,7 @@
9499
9748
  BantaCommentsComponent.propDecorators = {
9500
9749
  isMobileSized: [{ type: core.HostBinding, args: ['class.banta-mobile',] }],
9501
9750
  sendReplyOptionsTemplate: [{ type: core.ContentChild, args: [BantaReplySendOptionsDirective, { read: core.TemplateRef },] }],
9751
+ maxCommentLength: [{ type: core.Input }],
9502
9752
  loadingMessages: [{ type: core.Input }],
9503
9753
  useInlineReplies: [{ type: core.Input }],
9504
9754
  signInLabel: [{ type: core.Input }],
@@ -9609,19 +9859,23 @@
9609
9859
  };
9610
9860
 
9611
9861
  var CommentFieldComponent = /** @class */ (function () {
9612
- function CommentFieldComponent() {
9862
+ function CommentFieldComponent(chatBackend) {
9863
+ this.chatBackend = chatBackend;
9613
9864
  this.canComment = true;
9614
9865
  this.allowAttachments = false;
9615
9866
  this.signInSelected = new rxjs.Subject();
9616
9867
  this.editAvatarSelected = new rxjs.Subject();
9617
9868
  this.sending = false;
9618
9869
  this.expandError = false;
9619
- this.text = '';
9870
+ this._text = '';
9871
+ this.attachmentScrapeDebounce = 1500;
9872
+ this.attachmentFragments = new Map();
9620
9873
  this.sendLabel = 'Send';
9621
9874
  this.sendingLabel = 'Sending';
9622
9875
  this.label = 'Post a comment';
9623
9876
  this.permissionDeniedLabel = 'Unavailable';
9624
9877
  this.signInLabel = 'Sign In';
9878
+ this.maxLength = 1500;
9625
9879
  this.placeholder = '';
9626
9880
  this.textChanged = new rxjs.Subject();
9627
9881
  this.participants = [];
@@ -9631,6 +9885,171 @@
9631
9885
  this.autoCompleteSelected = 0;
9632
9886
  this.chatMessageAttachments = [];
9633
9887
  }
9888
+ Object.defineProperty(CommentFieldComponent.prototype, "text", {
9889
+ get: function () {
9890
+ return this._text;
9891
+ },
9892
+ set: function (value) {
9893
+ var _this = this;
9894
+ this._text = value;
9895
+ clearTimeout(this.attachmentScrapeTimeout);
9896
+ this.attachmentScrapeTimeout = setTimeout(function () { return _this.scrapeAttachments(); }, this.attachmentScrapeDebounce);
9897
+ },
9898
+ enumerable: false,
9899
+ configurable: true
9900
+ });
9901
+ CommentFieldComponent.prototype.scrapeAttachments = function () {
9902
+ var e_1, _b, e_2, _c, e_3, _d, e_4, _e, e_5, _f;
9903
+ var _this = this;
9904
+ var message = {
9905
+ likes: 0,
9906
+ message: this._text,
9907
+ sentAt: undefined,
9908
+ user: this.user,
9909
+ attachments: this.chatMessageAttachments
9910
+ };
9911
+ var foundFragments = [];
9912
+ try {
9913
+ for (var _g = __values(this.chatBackend.attachmentScrapers), _h = _g.next(); !_h.done; _h = _g.next()) {
9914
+ var scraper = _h.value;
9915
+ var fragments = scraper.findFragments(message);
9916
+ if (!fragments) {
9917
+ console.error("Attachment fragment scraper " + scraper.constructor.name + " is implemented incorrectly: Returned null instead of array");
9918
+ continue;
9919
+ }
9920
+ try {
9921
+ for (var fragments_1 = (e_2 = void 0, __values(fragments)), fragments_1_1 = fragments_1.next(); !fragments_1_1.done; fragments_1_1 = fragments_1.next()) {
9922
+ var fragment = fragments_1_1.value;
9923
+ foundFragments.push(fragment.text);
9924
+ if (!this.attachmentFragments.has(fragment.text)) {
9925
+ console.log("Scraped new fragment:");
9926
+ console.dir(fragment);
9927
+ this.attachmentFragments.set(fragment.text, {
9928
+ fragment: fragment,
9929
+ resolution: undefined
9930
+ });
9931
+ }
9932
+ }
9933
+ }
9934
+ catch (e_2_1) { e_2 = { error: e_2_1 }; }
9935
+ finally {
9936
+ try {
9937
+ if (fragments_1_1 && !fragments_1_1.done && (_c = fragments_1.return)) _c.call(fragments_1);
9938
+ }
9939
+ finally { if (e_2) throw e_2.error; }
9940
+ }
9941
+ }
9942
+ }
9943
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
9944
+ finally {
9945
+ try {
9946
+ if (_h && !_h.done && (_b = _g.return)) _b.call(_g);
9947
+ }
9948
+ finally { if (e_1) throw e_1.error; }
9949
+ }
9950
+ // Remove fragments that are no longer in the message.
9951
+ var removedFragments = [];
9952
+ try {
9953
+ for (var _j = __values(this.attachmentFragments), _k = _j.next(); !_k.done; _k = _j.next()) {
9954
+ var _l = __read(_k.value, 1), key = _l[0];
9955
+ if (!foundFragments.includes(key))
9956
+ removedFragments.push(key);
9957
+ }
9958
+ }
9959
+ catch (e_3_1) { e_3 = { error: e_3_1 }; }
9960
+ finally {
9961
+ try {
9962
+ if (_k && !_k.done && (_d = _j.return)) _d.call(_j);
9963
+ }
9964
+ finally { if (e_3) throw e_3.error; }
9965
+ }
9966
+ try {
9967
+ for (var removedFragments_1 = __values(removedFragments), removedFragments_1_1 = removedFragments_1.next(); !removedFragments_1_1.done; removedFragments_1_1 = removedFragments_1.next()) {
9968
+ var removedFragment = removedFragments_1_1.value;
9969
+ console.log("Removed fragment: " + removedFragment);
9970
+ this.attachmentFragments.delete(removedFragment);
9971
+ }
9972
+ }
9973
+ catch (e_4_1) { e_4 = { error: e_4_1 }; }
9974
+ finally {
9975
+ try {
9976
+ if (removedFragments_1_1 && !removedFragments_1_1.done && (_e = removedFragments_1.return)) _e.call(removedFragments_1);
9977
+ }
9978
+ finally { if (e_4) throw e_4.error; }
9979
+ }
9980
+ var _loop_1 = function (key, state) {
9981
+ if (state.resolution)
9982
+ return "continue";
9983
+ state.resolution = new Promise(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () {
9984
+ var _b, _c, resolver, attachment, e_6, e_7_1;
9985
+ var e_7, _d;
9986
+ return __generator(this, function (_e) {
9987
+ switch (_e.label) {
9988
+ case 0:
9989
+ console.log("Resolving fragment " + key);
9990
+ _e.label = 1;
9991
+ case 1:
9992
+ _e.trys.push([1, 8, 9, 10]);
9993
+ _b = __values(this.chatBackend.attachmentResolvers), _c = _b.next();
9994
+ _e.label = 2;
9995
+ case 2:
9996
+ if (!!_c.done) return [3 /*break*/, 7];
9997
+ resolver = _c.value;
9998
+ console.log("- Trying resolver " + resolver.constructor.name + "...");
9999
+ _e.label = 3;
10000
+ case 3:
10001
+ _e.trys.push([3, 5, , 6]);
10002
+ return [4 /*yield*/, resolver.resolveFragment(message, state.fragment)];
10003
+ case 4:
10004
+ attachment = _e.sent();
10005
+ if (attachment) {
10006
+ console.log("Resolved fragment " + key + " into attachment:");
10007
+ console.dir(attachment);
10008
+ this.chatMessageAttachments.push(attachment);
10009
+ resolve(attachment);
10010
+ return [3 /*break*/, 7];
10011
+ }
10012
+ return [3 /*break*/, 6];
10013
+ case 5:
10014
+ e_6 = _e.sent();
10015
+ console.error("Caught error during attachment resolver " + resolver.constructor.name + ":");
10016
+ console.error(e_6);
10017
+ return [3 /*break*/, 6];
10018
+ case 6:
10019
+ _c = _b.next();
10020
+ return [3 /*break*/, 2];
10021
+ case 7: return [3 /*break*/, 10];
10022
+ case 8:
10023
+ e_7_1 = _e.sent();
10024
+ e_7 = { error: e_7_1 };
10025
+ return [3 /*break*/, 10];
10026
+ case 9:
10027
+ try {
10028
+ if (_c && !_c.done && (_d = _b.return)) _d.call(_b);
10029
+ }
10030
+ finally { if (e_7) throw e_7.error; }
10031
+ return [7 /*endfinally*/];
10032
+ case 10: return [2 /*return*/];
10033
+ }
10034
+ });
10035
+ }); });
10036
+ };
10037
+ try {
10038
+ // Process any fragments that are not yet resolved (or being
10039
+ // resolved)
10040
+ for (var _m = __values(this.attachmentFragments), _o = _m.next(); !_o.done; _o = _m.next()) {
10041
+ var _p = __read(_o.value, 2), key = _p[0], state = _p[1];
10042
+ _loop_1(key, state);
10043
+ }
10044
+ }
10045
+ catch (e_5_1) { e_5 = { error: e_5_1 }; }
10046
+ finally {
10047
+ try {
10048
+ if (_o && !_o.done && (_f = _m.return)) _f.call(_m);
10049
+ }
10050
+ finally { if (e_5) throw e_5.error; }
10051
+ }
10052
+ };
9634
10053
  Object.defineProperty(CommentFieldComponent.prototype, "userAvatarUrl", {
9635
10054
  get: function () {
9636
10055
  var _a;
@@ -9858,7 +10277,7 @@
9858
10277
  };
9859
10278
  CommentFieldComponent.prototype.sendMessage = function () {
9860
10279
  return __awaiter(this, void 0, void 0, function () {
9861
- var text, message, e_1;
10280
+ var text, message, e_8;
9862
10281
  return __generator(this, function (_b) {
9863
10282
  switch (_b.label) {
9864
10283
  case 0:
@@ -9890,11 +10309,11 @@
9890
10309
  this.chatMessageAttachments = [];
9891
10310
  return [3 /*break*/, 6];
9892
10311
  case 4:
9893
- e_1 = _b.sent();
10312
+ e_8 = _b.sent();
9894
10313
  return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(function () { return resolve(); }, 1000); })];
9895
10314
  case 5:
9896
10315
  _b.sent();
9897
- this.indicateError(e_1.message);
10316
+ this.indicateError(e_8.message);
9898
10317
  return [3 /*break*/, 6];
9899
10318
  case 6: return [3 /*break*/, 8];
9900
10319
  case 7:
@@ -9914,8 +10333,10 @@
9914
10333
  _this.chatMessageAttachments = _this.chatMessageAttachments.filter(function (x) { return x !== attachment; });
9915
10334
  }, 3000);
9916
10335
  };
9917
- CommentFieldComponent.prototype.removeAttachment = function (index) {
9918
- this.chatMessageAttachments.splice(index, 1);
10336
+ CommentFieldComponent.prototype.removeAttachment = function (attachment) {
10337
+ var index = this.chatMessageAttachments.indexOf(attachment);
10338
+ if (index >= 0)
10339
+ this.chatMessageAttachments.splice(index, 1);
9919
10340
  };
9920
10341
  CommentFieldComponent.prototype.alertError = function () {
9921
10342
  if (!this.sendError)
@@ -9927,10 +10348,13 @@
9927
10348
  CommentFieldComponent.decorators = [
9928
10349
  { type: core.Component, args: [{
9929
10350
  selector: 'banta-comment-field',
9930
- 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 (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>",
9931
- 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}"]
10351
+ 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>",
10352
+ 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}"]
9932
10353
  },] }
9933
10354
  ];
10355
+ CommentFieldComponent.ctorParameters = function () { return [
10356
+ { type: ChatBackendBase }
10357
+ ]; };
9934
10358
  CommentFieldComponent.propDecorators = {
9935
10359
  source: [{ type: core.Input }],
9936
10360
  user: [{ type: core.Input }],
@@ -9943,6 +10367,7 @@
9943
10367
  label: [{ type: core.Input }],
9944
10368
  permissionDeniedLabel: [{ type: core.Input }],
9945
10369
  signInLabel: [{ type: core.Input }],
10370
+ maxLength: [{ type: core.Input }],
9946
10371
  placeholder: [{ type: core.Input }],
9947
10372
  textChanged: [{ type: core.Output }],
9948
10373
  shouldInterceptMessageSend: [{ type: core.Input }],
@@ -10115,7 +10540,9 @@
10115
10540
  BantaCommonModule,
10116
10541
  EmojiModule,
10117
10542
  tooltip.MatTooltipModule,
10118
- select.MatSelectModule
10543
+ select.MatSelectModule,
10544
+ overlay.OverlayModule,
10545
+ portal.PortalModule
10119
10546
  ],
10120
10547
  exports: COMPONENTS$3
10121
10548
  },] }
@@ -10486,6 +10913,35 @@
10486
10913
  ChatBackend.prototype.watchMessage = function (message, handler) {
10487
10914
  throw new Error("Method not implemented.");
10488
10915
  };
10916
+ ChatBackend.prototype.getCardForUrl = function (url) {
10917
+ return __awaiter(this, void 0, void 0, function () {
10918
+ var response, _c, _d;
10919
+ return __generator(this, function (_e) {
10920
+ switch (_e.label) {
10921
+ case 0: return [4 /*yield*/, fetch(this.serviceUrl + "/urls", {
10922
+ method: 'POST',
10923
+ headers: {
10924
+ 'Content-Type': 'application/json'
10925
+ },
10926
+ body: JSON.stringify({
10927
+ url: url
10928
+ })
10929
+ })];
10930
+ case 1:
10931
+ response = _e.sent();
10932
+ if (response.status == 404)
10933
+ return [2 /*return*/, null];
10934
+ if (!(response.status >= 400)) return [3 /*break*/, 3];
10935
+ _c = Error.bind;
10936
+ _d = "Failed to retrieve URL card: " + response.status + ". Body: '";
10937
+ return [4 /*yield*/, response.text()];
10938
+ case 2: throw new (_c.apply(Error, [void 0, _d + (_e.sent()) + "'"]))();
10939
+ case 3: return [4 /*yield*/, response.json()];
10940
+ case 4: return [2 /*return*/, _e.sent()];
10941
+ }
10942
+ });
10943
+ });
10944
+ };
10489
10945
  return ChatBackend;
10490
10946
  }(ChatBackendBase));
10491
10947
  ChatBackend.decorators = [
@@ -10529,7 +10985,9 @@
10529
10985
  formField.MatFormFieldModule,
10530
10986
  input.MatInputModule,
10531
10987
  progressSpinner.MatProgressSpinnerModule,
10532
- snackBar.MatSnackBarModule
10988
+ snackBar.MatSnackBarModule,
10989
+ overlay.OverlayModule,
10990
+ portal.PortalModule
10533
10991
  ],
10534
10992
  declarations: [
10535
10993
  BantaComponent,