@banta/sdk 4.3.3 → 4.4.2

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 (40) hide show
  1. package/banta-sdk.metadata.json +1 -1
  2. package/bundles/banta-sdk.umd.js +581 -27
  3. package/bundles/banta-sdk.umd.js.map +1 -1
  4. package/bundles/banta-sdk.umd.min.js +1 -1
  5. package/bundles/banta-sdk.umd.min.js.map +1 -1
  6. package/esm2015/lib/attachment-scraper.js +2 -0
  7. package/esm2015/lib/banta-sdk.module.js +23 -1
  8. package/esm2015/lib/chat-backend-base.js +15 -1
  9. package/esm2015/lib/chat-backend.js +19 -1
  10. package/esm2015/lib/comments/banta-comments/banta-comments.component.js +1 -1
  11. package/esm2015/lib/comments/comment/comment.component.js +5 -12
  12. package/esm2015/lib/comments/comment-field/comment-field.component.js +89 -7
  13. package/esm2015/lib/common/attachment/attachment.component.js +81 -0
  14. package/esm2015/lib/common/attachments/attachments.component.js +47 -0
  15. package/esm2015/lib/common/common.module.js +13 -3
  16. package/esm2015/lib/common/index.js +4 -1
  17. package/esm2015/lib/common/trust-resource-url.pipe.js +21 -0
  18. package/esm2015/lib/giphy-attachments.js +20 -0
  19. package/esm2015/lib/index.js +6 -1
  20. package/esm2015/lib/tweet-attachments.js +16 -0
  21. package/esm2015/lib/url-attachments.js +46 -0
  22. package/esm2015/lib/youtube-attachments.js +25 -0
  23. package/fesm2015/banta-sdk.js +395 -22
  24. package/fesm2015/banta-sdk.js.map +1 -1
  25. package/lib/attachment-scraper.d.ts +15 -0
  26. package/lib/banta-sdk.module.d.ts +2 -0
  27. package/lib/chat-backend-base.d.ts +10 -1
  28. package/lib/chat-backend.d.ts +2 -1
  29. package/lib/comments/comment/comment.component.d.ts +1 -4
  30. package/lib/comments/comment-field/comment-field.component.d.ts +11 -2
  31. package/lib/common/attachment/attachment.component.d.ts +22 -0
  32. package/lib/common/attachments/attachments.component.d.ts +15 -0
  33. package/lib/common/index.d.ts +3 -0
  34. package/lib/common/trust-resource-url.pipe.d.ts +7 -0
  35. package/lib/giphy-attachments.d.ts +5 -0
  36. package/lib/index.d.ts +5 -0
  37. package/lib/tweet-attachments.d.ts +5 -0
  38. package/lib/url-attachments.d.ts +11 -0
  39. package/lib/youtube-attachments.d.ts +5 -0
  40. 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/cdk/overlay'), require('@angular/material/button'), 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/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/cdk/overlay', '@angular/material/button', '@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/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.cdk.overlay, global.ng.material.button, 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.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, overlay, button, formField, input, forms, portal, 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,190 @@
207
207
  { type: platformBrowser.DomSanitizer }
208
208
  ]; };
209
209
 
210
+ var BantaTrustResourceUrlPipe = /** @class */ (function () {
211
+ function BantaTrustResourceUrlPipe(sanitizer) {
212
+ this.sanitizer = sanitizer;
213
+ }
214
+ BantaTrustResourceUrlPipe.prototype.transform = function (value) {
215
+ if (!value)
216
+ return undefined;
217
+ return this.sanitizer.bypassSecurityTrustResourceUrl(value);
218
+ };
219
+ return BantaTrustResourceUrlPipe;
220
+ }());
221
+ BantaTrustResourceUrlPipe.decorators = [
222
+ { type: core.Pipe, args: [{
223
+ name: 'trustResourceUrl'
224
+ },] }
225
+ ];
226
+ BantaTrustResourceUrlPipe.ctorParameters = function () { return [
227
+ { type: platformBrowser.DomSanitizer }
228
+ ]; };
229
+
230
+ var BantaAttachmentComponent = /** @class */ (function () {
231
+ function BantaAttachmentComponent() {
232
+ this.loading = false;
233
+ this.editing = false;
234
+ this.loadingMessage = 'Please wait...';
235
+ this.error = false;
236
+ this.errorMessage = 'An error has occurred';
237
+ this.removed = new rxjs.Subject();
238
+ this.activated = new rxjs.Subject();
239
+ }
240
+ BantaAttachmentComponent.prototype.ngOnInit = function () {
241
+ if (typeof window !== 'undefined') {
242
+ setTimeout(function () {
243
+ if (!window['twttr'])
244
+ return;
245
+ window['twttr'].widgets.load();
246
+ }, 100);
247
+ }
248
+ };
249
+ BantaAttachmentComponent.prototype.activate = function () {
250
+ this.activated.next();
251
+ };
252
+ BantaAttachmentComponent.prototype.remove = function () {
253
+ this.removed.next();
254
+ };
255
+ Object.defineProperty(BantaAttachmentComponent.prototype, "isError", {
256
+ get: function () {
257
+ var _a, _b;
258
+ return this.error || ((_b = (_a = this.attachment) === null || _a === void 0 ? void 0 : _a.transientState) === null || _b === void 0 ? void 0 : _b.error);
259
+ },
260
+ enumerable: false,
261
+ configurable: true
262
+ });
263
+ Object.defineProperty(BantaAttachmentComponent.prototype, "theErrorMessage", {
264
+ get: function () {
265
+ var _a, _b;
266
+ return this.errorMessage || ((_b = (_a = this.attachment) === null || _a === void 0 ? void 0 : _a.transientState) === null || _b === void 0 ? void 0 : _b.errorMessage);
267
+ },
268
+ enumerable: false,
269
+ configurable: true
270
+ });
271
+ Object.defineProperty(BantaAttachmentComponent.prototype, "isLoading", {
272
+ get: function () {
273
+ var _a;
274
+ return this.loading || !this.attachment || ((_a = this.attachment.transientState) === null || _a === void 0 ? void 0 : _a.loading) || !this.attachment.url;
275
+ },
276
+ enumerable: false,
277
+ configurable: true
278
+ });
279
+ Object.defineProperty(BantaAttachmentComponent.prototype, "isImageAttachment", {
280
+ get: function () {
281
+ if (this.attachment.type.startsWith('image/'))
282
+ return true;
283
+ return false;
284
+ },
285
+ enumerable: false,
286
+ configurable: true
287
+ });
288
+ Object.defineProperty(BantaAttachmentComponent.prototype, "hasFrame", {
289
+ get: function () {
290
+ if (!this.attachment)
291
+ return false;
292
+ return this.attachment.type === 'iframe' || (this.attachment.type === 'card'
293
+ && this.attachment.card.player);
294
+ },
295
+ enumerable: false,
296
+ configurable: true
297
+ });
298
+ Object.defineProperty(BantaAttachmentComponent.prototype, "frameUrl", {
299
+ get: function () {
300
+ if (!this.attachment)
301
+ return undefined;
302
+ if (this.attachment.type === 'iframe') {
303
+ return this.attachment.url;
304
+ }
305
+ else if (this.attachment.type === 'card') {
306
+ return this.attachment.card.player;
307
+ }
308
+ },
309
+ enumerable: false,
310
+ configurable: true
311
+ });
312
+ return BantaAttachmentComponent;
313
+ }());
314
+ BantaAttachmentComponent.decorators = [
315
+ { type: core.Component, args: [{
316
+ selector: 'banta-attachment',
317
+ 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 *ngIf=\"hasFrame\"\r\n sandbox=\"allow-scripts allow-popups allow-same-origin allow-presentation\" \r\n [src]=\"frameUrl | trustResourceUrl\"></iframe>\r\n <a *ngIf=\"attachment.type === 'card'\" class=\"card\" [href]=\"attachment.url\" target=\"_blank\" [class.has-image]=\"attachment.card.image\">\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 <div>\r\n {{attachment.card.description}}\r\n </div>\r\n <cite>{{attachment.card.url}}</cite>\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 <blockquote *ngIf=\"attachment.type === 'tweet'\" \r\n class=\"twitter-tweet\">\r\n <p lang=\"en\" dir=\"ltr\"></p>\r\n <a href=\"https://twitter.com/seldo/status/1562553608083714050?ref_src=twsrc%5Etfw\"></a>\r\n </blockquote>\r\n </ng-container>\r\n</ng-container>",
318
+ 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;margin:1em 0}a.card img{width:250px;aspect-ratio:16/9;-o-object-fit:cover;object-fit:cover;border-radius:10px}a.card.has-image h1{font-size:22px}a.card h1{min-width:0;margin:0 0 .5em;font-size:26px}a.card cite{min-width:0;opacity:.75;margin-top:1em;display:block;font-size:90%;white-space:nowrap;text-overflow:ellipsis;overflow:hidden}a.card .description{min-width:0}.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}"]
319
+ },] }
320
+ ];
321
+ BantaAttachmentComponent.ctorParameters = function () { return []; };
322
+ BantaAttachmentComponent.propDecorators = {
323
+ attachment: [{ type: core.Input }],
324
+ loading: [{ type: core.Input }],
325
+ editing: [{ type: core.Input }],
326
+ loadingMessage: [{ type: core.Input }],
327
+ error: [{ type: core.Input }],
328
+ errorMessage: [{ type: core.Input }],
329
+ removed: [{ type: core.Output }],
330
+ activated: [{ type: core.Output }],
331
+ isLoading: [{ type: core.HostBinding, args: ['class.loading',] }]
332
+ };
333
+
334
+ var BantaAttachmentsComponent = /** @class */ (function () {
335
+ function BantaAttachmentsComponent() {
336
+ this.editing = false;
337
+ this.remove = new rxjs.Subject();
338
+ }
339
+ BantaAttachmentsComponent.prototype.removeAttachment = function (attachment) {
340
+ this.remove.next(attachment);
341
+ };
342
+ BantaAttachmentsComponent.prototype.isImageAttachment = function (attachment) {
343
+ if (attachment.type.startsWith('image/'))
344
+ return true;
345
+ return false;
346
+ };
347
+ BantaAttachmentsComponent.prototype.isCardAttachment = function (attachment) {
348
+ if (['card'].includes(attachment.type))
349
+ return true;
350
+ return false;
351
+ };
352
+ BantaAttachmentsComponent.prototype.showLightbox = function (image) {
353
+ this.lightbox.open(image.url, this.attachments
354
+ .filter(function (x) { return x.type === 'image/png'; })
355
+ .map(function (x) { return x.url; }));
356
+ };
357
+ Object.defineProperty(BantaAttachmentsComponent.prototype, "inlineAttachments", {
358
+ get: function () {
359
+ return this.attachments.filter(function (x) { return x.type !== 'card' && (x.style === 'inline' || !x.style); });
360
+ },
361
+ enumerable: false,
362
+ configurable: true
363
+ });
364
+ Object.defineProperty(BantaAttachmentsComponent.prototype, "blockAttachments", {
365
+ get: function () {
366
+ return this.attachments.filter(function (x) { return x.style === 'block' || x.type === 'card'; });
367
+ },
368
+ enumerable: false,
369
+ configurable: true
370
+ });
371
+ return BantaAttachmentsComponent;
372
+ }());
373
+ BantaAttachmentsComponent.decorators = [
374
+ { type: core.Component, args: [{
375
+ selector: 'banta-attachments',
376
+ 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>",
377
+ 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}"]
378
+ },] }
379
+ ];
380
+ BantaAttachmentsComponent.propDecorators = {
381
+ attachments: [{ type: core.Input }],
382
+ editing: [{ type: core.Input }],
383
+ lightbox: [{ type: core.ViewChild, args: ['lightbox',] }],
384
+ remove: [{ type: core.Output }]
385
+ };
386
+
210
387
  var COMPONENTS = [
211
388
  TimestampComponent,
212
389
  LightboxComponent,
213
- BantaMarkdownToHtmlPipe
390
+ BantaMarkdownToHtmlPipe,
391
+ BantaTrustResourceUrlPipe,
392
+ BantaAttachmentComponent,
393
+ BantaAttachmentsComponent
214
394
  ];
215
395
  var BantaCommonModule = /** @class */ (function () {
216
396
  function BantaCommonModule() {
@@ -222,7 +402,9 @@
222
402
  declarations: COMPONENTS,
223
403
  imports: [
224
404
  common.CommonModule,
225
- icon.MatIconModule
405
+ icon.MatIconModule,
406
+ progressSpinner.MatProgressSpinnerModule,
407
+ button.MatButtonModule
226
408
  ],
227
409
  exports: COMPONENTS
228
410
  },] }
@@ -7280,6 +7462,8 @@
7280
7462
  var ChatBackendBase = /** @class */ (function () {
7281
7463
  function ChatBackendBase() {
7282
7464
  this._userChanged = new rxjs.BehaviorSubject(null);
7465
+ this._attachmentScrapers = [];
7466
+ this._attachmentResolvers = [];
7283
7467
  }
7284
7468
  Object.defineProperty(ChatBackendBase.prototype, "userChanged", {
7285
7469
  get: function () {
@@ -7299,6 +7483,26 @@
7299
7483
  enumerable: false,
7300
7484
  configurable: true
7301
7485
  });
7486
+ ChatBackendBase.prototype.registerAttachmentScraper = function (scraper) {
7487
+ this._attachmentScrapers.push(scraper);
7488
+ };
7489
+ ChatBackendBase.prototype.registerAttachmentResolver = function (resolver) {
7490
+ this._attachmentResolvers.push(resolver);
7491
+ };
7492
+ Object.defineProperty(ChatBackendBase.prototype, "attachmentScrapers", {
7493
+ get: function () {
7494
+ return this._attachmentScrapers.slice();
7495
+ },
7496
+ enumerable: false,
7497
+ configurable: true
7498
+ });
7499
+ Object.defineProperty(ChatBackendBase.prototype, "attachmentResolvers", {
7500
+ get: function () {
7501
+ return this._attachmentResolvers.slice();
7502
+ },
7503
+ enumerable: false,
7504
+ configurable: true
7505
+ });
7302
7506
  return ChatBackendBase;
7303
7507
  }());
7304
7508
 
@@ -8349,11 +8553,6 @@
8349
8553
  this._avatarSelected.next(user);
8350
8554
  this.selectUser();
8351
8555
  };
8352
- CommentComponent.prototype.showLightbox = function (image) {
8353
- this.lightbox.open(image.url, this.message.attachments
8354
- .filter(function (x) { return x.type === 'image/png'; })
8355
- .map(function (x) { return x.url; }));
8356
- };
8357
8556
  CommentComponent.prototype.avatarForUser = function (user) {
8358
8557
  var url = this.genericAvatarUrl;
8359
8558
  if (user && user.avatarUrl) {
@@ -8374,8 +8573,8 @@
8374
8573
  CommentComponent.decorators = [
8375
8574
  { type: core.Component, args: [{
8376
8575
  selector: 'banta-comment',
8377
- 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 <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 <banta-timestamp [value]=\"message.sentAt\"></banta-timestamp>\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\">\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",
8378
- 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}.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}.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}"]
8576
+ 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",
8577
+ 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}"]
8379
8578
  },] }
8380
8579
  ];
8381
8580
  CommentComponent.propDecorators = {
@@ -8400,8 +8599,7 @@
8400
8599
  editEnded: [{ type: core.Output }],
8401
8600
  shared: [{ type: core.Output }],
8402
8601
  genericAvatarUrl: [{ type: core.Input }],
8403
- commentId: [{ type: core.HostBinding, args: ['attr.data-comment-id',] }],
8404
- lightbox: [{ type: core.ViewChild, args: ['lightbox',] }]
8602
+ commentId: [{ type: core.HostBinding, args: ['attr.data-comment-id',] }]
8405
8603
  };
8406
8604
 
8407
8605
  var CommentViewComponent = /** @class */ (function () {
@@ -9459,7 +9657,7 @@
9459
9657
  { type: core.Component, args: [{
9460
9658
  selector: 'banta-comments',
9461
9659
  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",
9462
- 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%}"]
9660
+ 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%}"]
9463
9661
  },] }
9464
9662
  ];
9465
9663
  BantaCommentsComponent.ctorParameters = function () { return [
@@ -9583,14 +9781,17 @@
9583
9781
  };
9584
9782
 
9585
9783
  var CommentFieldComponent = /** @class */ (function () {
9586
- function CommentFieldComponent() {
9784
+ function CommentFieldComponent(chatBackend) {
9785
+ this.chatBackend = chatBackend;
9587
9786
  this.canComment = true;
9588
9787
  this.allowAttachments = false;
9589
9788
  this.signInSelected = new rxjs.Subject();
9590
9789
  this.editAvatarSelected = new rxjs.Subject();
9591
9790
  this.sending = false;
9592
9791
  this.expandError = false;
9593
- this.text = '';
9792
+ this._text = '';
9793
+ this.attachmentScrapeDebounce = 1500;
9794
+ this.attachmentFragments = new Map();
9594
9795
  this.sendLabel = 'Send';
9595
9796
  this.sendingLabel = 'Sending';
9596
9797
  this.label = 'Post a comment';
@@ -9606,6 +9807,171 @@
9606
9807
  this.autoCompleteSelected = 0;
9607
9808
  this.chatMessageAttachments = [];
9608
9809
  }
9810
+ Object.defineProperty(CommentFieldComponent.prototype, "text", {
9811
+ get: function () {
9812
+ return this._text;
9813
+ },
9814
+ set: function (value) {
9815
+ var _this = this;
9816
+ this._text = value;
9817
+ clearTimeout(this.attachmentScrapeTimeout);
9818
+ this.attachmentScrapeTimeout = setTimeout(function () { return _this.scrapeAttachments(); }, this.attachmentScrapeDebounce);
9819
+ },
9820
+ enumerable: false,
9821
+ configurable: true
9822
+ });
9823
+ CommentFieldComponent.prototype.scrapeAttachments = function () {
9824
+ var e_1, _b, e_2, _c, e_3, _d, e_4, _e, e_5, _f;
9825
+ var _this = this;
9826
+ var message = {
9827
+ likes: 0,
9828
+ message: this._text,
9829
+ sentAt: undefined,
9830
+ user: this.user,
9831
+ attachments: this.chatMessageAttachments
9832
+ };
9833
+ var foundFragments = [];
9834
+ try {
9835
+ for (var _g = __values(this.chatBackend.attachmentScrapers), _h = _g.next(); !_h.done; _h = _g.next()) {
9836
+ var scraper = _h.value;
9837
+ var fragments = scraper.findFragments(message);
9838
+ if (!fragments) {
9839
+ console.error("Attachment fragment scraper " + scraper.constructor.name + " is implemented incorrectly: Returned null instead of array");
9840
+ continue;
9841
+ }
9842
+ try {
9843
+ 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()) {
9844
+ var fragment = fragments_1_1.value;
9845
+ foundFragments.push(fragment.text);
9846
+ if (!this.attachmentFragments.has(fragment.text)) {
9847
+ console.log("Scraped new fragment:");
9848
+ console.dir(fragment);
9849
+ this.attachmentFragments.set(fragment.text, {
9850
+ fragment: fragment,
9851
+ resolution: undefined
9852
+ });
9853
+ }
9854
+ }
9855
+ }
9856
+ catch (e_2_1) { e_2 = { error: e_2_1 }; }
9857
+ finally {
9858
+ try {
9859
+ if (fragments_1_1 && !fragments_1_1.done && (_c = fragments_1.return)) _c.call(fragments_1);
9860
+ }
9861
+ finally { if (e_2) throw e_2.error; }
9862
+ }
9863
+ }
9864
+ }
9865
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
9866
+ finally {
9867
+ try {
9868
+ if (_h && !_h.done && (_b = _g.return)) _b.call(_g);
9869
+ }
9870
+ finally { if (e_1) throw e_1.error; }
9871
+ }
9872
+ // Remove fragments that are no longer in the message.
9873
+ var removedFragments = [];
9874
+ try {
9875
+ for (var _j = __values(this.attachmentFragments), _k = _j.next(); !_k.done; _k = _j.next()) {
9876
+ var _l = __read(_k.value, 1), key = _l[0];
9877
+ if (!foundFragments.includes(key))
9878
+ removedFragments.push(key);
9879
+ }
9880
+ }
9881
+ catch (e_3_1) { e_3 = { error: e_3_1 }; }
9882
+ finally {
9883
+ try {
9884
+ if (_k && !_k.done && (_d = _j.return)) _d.call(_j);
9885
+ }
9886
+ finally { if (e_3) throw e_3.error; }
9887
+ }
9888
+ try {
9889
+ for (var removedFragments_1 = __values(removedFragments), removedFragments_1_1 = removedFragments_1.next(); !removedFragments_1_1.done; removedFragments_1_1 = removedFragments_1.next()) {
9890
+ var removedFragment = removedFragments_1_1.value;
9891
+ console.log("Removed fragment: " + removedFragment);
9892
+ this.attachmentFragments.delete(removedFragment);
9893
+ }
9894
+ }
9895
+ catch (e_4_1) { e_4 = { error: e_4_1 }; }
9896
+ finally {
9897
+ try {
9898
+ if (removedFragments_1_1 && !removedFragments_1_1.done && (_e = removedFragments_1.return)) _e.call(removedFragments_1);
9899
+ }
9900
+ finally { if (e_4) throw e_4.error; }
9901
+ }
9902
+ var _loop_1 = function (key, state) {
9903
+ if (state.resolution)
9904
+ return "continue";
9905
+ state.resolution = new Promise(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () {
9906
+ var _b, _c, resolver, attachment, e_6, e_7_1;
9907
+ var e_7, _d;
9908
+ return __generator(this, function (_e) {
9909
+ switch (_e.label) {
9910
+ case 0:
9911
+ console.log("Resolving fragment " + key);
9912
+ _e.label = 1;
9913
+ case 1:
9914
+ _e.trys.push([1, 8, 9, 10]);
9915
+ _b = __values(this.chatBackend.attachmentResolvers), _c = _b.next();
9916
+ _e.label = 2;
9917
+ case 2:
9918
+ if (!!_c.done) return [3 /*break*/, 7];
9919
+ resolver = _c.value;
9920
+ console.log("- Trying resolver " + resolver.constructor.name + "...");
9921
+ _e.label = 3;
9922
+ case 3:
9923
+ _e.trys.push([3, 5, , 6]);
9924
+ return [4 /*yield*/, resolver.resolveFragment(message, state.fragment)];
9925
+ case 4:
9926
+ attachment = _e.sent();
9927
+ if (attachment) {
9928
+ console.log("Resolved fragment " + key + " into attachment:");
9929
+ console.dir(attachment);
9930
+ this.chatMessageAttachments.push(attachment);
9931
+ resolve(attachment);
9932
+ return [3 /*break*/, 7];
9933
+ }
9934
+ return [3 /*break*/, 6];
9935
+ case 5:
9936
+ e_6 = _e.sent();
9937
+ console.error("Caught error during attachment resolver " + resolver.constructor.name + ":");
9938
+ console.error(e_6);
9939
+ return [3 /*break*/, 6];
9940
+ case 6:
9941
+ _c = _b.next();
9942
+ return [3 /*break*/, 2];
9943
+ case 7: return [3 /*break*/, 10];
9944
+ case 8:
9945
+ e_7_1 = _e.sent();
9946
+ e_7 = { error: e_7_1 };
9947
+ return [3 /*break*/, 10];
9948
+ case 9:
9949
+ try {
9950
+ if (_c && !_c.done && (_d = _b.return)) _d.call(_b);
9951
+ }
9952
+ finally { if (e_7) throw e_7.error; }
9953
+ return [7 /*endfinally*/];
9954
+ case 10: return [2 /*return*/];
9955
+ }
9956
+ });
9957
+ }); });
9958
+ };
9959
+ try {
9960
+ // Process any fragments that are not yet resolved (or being
9961
+ // resolved)
9962
+ for (var _m = __values(this.attachmentFragments), _o = _m.next(); !_o.done; _o = _m.next()) {
9963
+ var _p = __read(_o.value, 2), key = _p[0], state = _p[1];
9964
+ _loop_1(key, state);
9965
+ }
9966
+ }
9967
+ catch (e_5_1) { e_5 = { error: e_5_1 }; }
9968
+ finally {
9969
+ try {
9970
+ if (_o && !_o.done && (_f = _m.return)) _f.call(_m);
9971
+ }
9972
+ finally { if (e_5) throw e_5.error; }
9973
+ }
9974
+ };
9609
9975
  Object.defineProperty(CommentFieldComponent.prototype, "userAvatarUrl", {
9610
9976
  get: function () {
9611
9977
  var _a;
@@ -9833,7 +10199,7 @@
9833
10199
  };
9834
10200
  CommentFieldComponent.prototype.sendMessage = function () {
9835
10201
  return __awaiter(this, void 0, void 0, function () {
9836
- var text, message, e_1;
10202
+ var text, message, e_8;
9837
10203
  return __generator(this, function (_b) {
9838
10204
  switch (_b.label) {
9839
10205
  case 0:
@@ -9865,11 +10231,11 @@
9865
10231
  this.chatMessageAttachments = [];
9866
10232
  return [3 /*break*/, 6];
9867
10233
  case 4:
9868
- e_1 = _b.sent();
10234
+ e_8 = _b.sent();
9869
10235
  return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(function () { return resolve(); }, 1000); })];
9870
10236
  case 5:
9871
10237
  _b.sent();
9872
- this.indicateError(e_1.message);
10238
+ this.indicateError(e_8.message);
9873
10239
  return [3 /*break*/, 6];
9874
10240
  case 6: return [3 /*break*/, 8];
9875
10241
  case 7:
@@ -9889,8 +10255,10 @@
9889
10255
  _this.chatMessageAttachments = _this.chatMessageAttachments.filter(function (x) { return x !== attachment; });
9890
10256
  }, 3000);
9891
10257
  };
9892
- CommentFieldComponent.prototype.removeAttachment = function (index) {
9893
- this.chatMessageAttachments.splice(index, 1);
10258
+ CommentFieldComponent.prototype.removeAttachment = function (attachment) {
10259
+ var index = this.chatMessageAttachments.indexOf(attachment);
10260
+ if (index >= 0)
10261
+ this.chatMessageAttachments.splice(index, 1);
9894
10262
  };
9895
10263
  CommentFieldComponent.prototype.alertError = function () {
9896
10264
  if (!this.sendError)
@@ -9902,10 +10270,13 @@
9902
10270
  CommentFieldComponent.decorators = [
9903
10271
  { type: core.Component, args: [{
9904
10272
  selector: 'banta-comment-field',
9905
- template: "<form class=\"new-message\" (submit)=\"sendMessage()\">\r\n <div class=\"avatar-container\">\r\n <a href=\"javascript:;\"\r\n class=\"avatar\"\r\n (click)=\"showEditAvatar()\"\r\n [style.background-image]=\"'url(' + userAvatarUrl + ')'\"\r\n ></a>\r\n </div>\r\n <div class=\"text-container\">\r\n <div class=\"field-container\">\r\n <div class=\"field-row\">\r\n <mat-form-field appearance=\"outline\" floatLabel=\"always\">\r\n <mat-label>{{label}}</mat-label>\r\n <textarea\r\n #textarea\r\n name=\"message\"\r\n [placeholder]=\"placeholder\"\r\n matInput\r\n cdkTextareaAutosize\r\n [maxlength]=\"maxLength\"\r\n (keydown)=\"onKeyDown($event)\"\r\n (blur)=\"onBlur()\"\r\n [disabled]=\"sending\"\r\n [(ngModel)]=\"text\"></textarea>\r\n </mat-form-field>\r\n <div class=\"options-line\">\r\n <mat-spinner *ngIf=\"sending\" class=\"icon loading\" diameter=\"18\" strokeWidth=\"2\"></mat-spinner>\r\n <div *ngIf=\"sendError\" class=\"error-message\" [class.expanded]=\"expandError\" [matTooltip]=\"sendError.message\" (click)=\"alertError()\">\r\n <mat-icon *ngIf=\"sendError\">error</mat-icon>\r\n {{sendError.message}}\r\n </div>\r\n <div class=\"spacer\"></div>\r\n <div class=\"custom\">\r\n <ng-content></ng-content>\r\n </div>\r\n <banta-attachment-button \r\n *ngIf=\"allowAttachments\"\r\n (addedAttachment)=\"addedAttachment($event)\"\r\n (attachmentError)=\"attachmentError($event)\"\r\n ></banta-attachment-button>\r\n <emoji-selector-button (selected)=\"insertEmoji($event)\"></emoji-selector-button>\r\n </div>\r\n \r\n </div>\r\n <div #autocompleteContainer class=\"autocomplete-container\">\r\n <div #autocomplete class=\"autocomplete\" [class.visible]=\"autocompleteVisible\">\r\n\r\n <div>\r\n <strong>{{completionPrefix}}</strong>...\r\n </div>\r\n <a\r\n mat-button\r\n *ngFor=\"let option of autocompleteOptions; index as index\"\r\n (click)=\"activateAutoComplete(option)\"\r\n [class.active]=\"autoCompleteSelected === index\"\r\n >\r\n {{option.label}}\r\n </a>\r\n </div>\r\n </div>\r\n\r\n\r\n <div *ngIf=\"chatMessageAttachments && chatMessageAttachments.length\" class=\"message-attachments-container\">\r\n <div *ngFor=\"let attachment of chatMessageAttachments; index as attachmentIndex\"\r\n class=\"message-attachment\" [class.with-border]=\"!attachment.url\">\r\n <button (click)=\"removeAttachment(attachmentIndex)\" mat-mini-fab color=\"primary\" class=\"remove-img\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n <ng-container *ngIf=\"attachment.transientState?.error\">\r\n <mat-icon class=\"error\">close</mat-icon>\r\n <em class=\"error\">{{attachment.transientState?.errorMessage || 'Error'}}</em>\r\n </ng-container>\r\n <ng-container *ngIf=\"!attachment.transientState?.error\">\r\n <ng-container *ngIf=\"attachment.transientState?.uploading\">\r\n <mat-spinner></mat-spinner>\r\n <em>Uploading...</em>\r\n </ng-container>\r\n <ng-container *ngIf=\"!attachment.transientState?.uploading\">\r\n <img [src]=\"attachment.url\" alt=\"Message Attachment\">\r\n </ng-container>\r\n </ng-container>\r\n </div>\r\n </div>\r\n\r\n </div>\r\n </div>\r\n <div class=\"actions\">\r\n <ng-container *ngIf=\"!user\">\r\n <button\r\n mat-raised-button\r\n color=\"primary\"\r\n type=\"button\"\r\n (click)=\"showSignIn()\"\r\n >{{signInLabel}}</button>\r\n </ng-container>\r\n <ng-container *ngIf=\"user\">\r\n <button\r\n mat-raised-button\r\n class=\"send\"\r\n color=\"primary\"\r\n [disabled]=\"!sendButtonEnabled\"\r\n >\r\n <ng-container *ngIf=\"canComment\">\r\n <mat-icon *ngIf=\"!sending\">chevron_right</mat-icon>\r\n <mat-spinner *ngIf=\"sending\" class=\"icon\" diameter=\"18\" strokeWidth=\"2\"></mat-spinner>\r\n </ng-container>\r\n <span class=\"label\">\r\n <ng-container *ngIf=\"!canComment\">\r\n {{permissionDeniedLabel}}\r\n </ng-container>\r\n <ng-container *ngIf=\"canComment\">\r\n <ng-container *ngIf=\"!sending\">\r\n {{sendLabel}}\r\n </ng-container>\r\n <ng-container *ngIf=\"sending\">\r\n {{sendingLabel}}\r\n </ng-container>\r\n </ng-container>\r\n </span>\r\n </button>\r\n </ng-container>\r\n </div>\r\n</form>",
9906
- 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}"]
10273
+ 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>",
10274
+ 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}"]
9907
10275
  },] }
9908
10276
  ];
10277
+ CommentFieldComponent.ctorParameters = function () { return [
10278
+ { type: ChatBackendBase }
10279
+ ]; };
9909
10280
  CommentFieldComponent.propDecorators = {
9910
10281
  source: [{ type: core.Input }],
9911
10282
  user: [{ type: core.Input }],
@@ -10464,6 +10835,35 @@
10464
10835
  ChatBackend.prototype.watchMessage = function (message, handler) {
10465
10836
  throw new Error("Method not implemented.");
10466
10837
  };
10838
+ ChatBackend.prototype.getCardForUrl = function (url) {
10839
+ return __awaiter(this, void 0, void 0, function () {
10840
+ var response, _c, _d;
10841
+ return __generator(this, function (_e) {
10842
+ switch (_e.label) {
10843
+ case 0: return [4 /*yield*/, fetch(this.serviceUrl + "/urls", {
10844
+ method: 'POST',
10845
+ headers: {
10846
+ 'Content-Type': 'application/json'
10847
+ },
10848
+ body: JSON.stringify({
10849
+ url: url
10850
+ })
10851
+ })];
10852
+ case 1:
10853
+ response = _e.sent();
10854
+ if (response.status == 404)
10855
+ return [2 /*return*/, null];
10856
+ if (!(response.status >= 400)) return [3 /*break*/, 3];
10857
+ _c = Error.bind;
10858
+ _d = "Failed to retrieve URL card: " + response.status + ". Body: '";
10859
+ return [4 /*yield*/, response.text()];
10860
+ case 2: throw new (_c.apply(Error, [void 0, _d + (_e.sent()) + "'"]))();
10861
+ case 3: return [4 /*yield*/, response.json()];
10862
+ case 4: return [2 /*return*/, _e.sent()];
10863
+ }
10864
+ });
10865
+ });
10866
+ };
10467
10867
  return ChatBackend;
10468
10868
  }(ChatBackendBase));
10469
10869
  ChatBackend.decorators = [
@@ -10473,8 +10873,151 @@
10473
10873
  { type: undefined, decorators: [{ type: core.Inject, args: [BANTA_SDK_OPTIONS,] }] }
10474
10874
  ]; };
10475
10875
 
10876
+ 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');
10877
+ var UrlAttachmentScraper = /** @class */ (function () {
10878
+ function UrlAttachmentScraper() {
10879
+ }
10880
+ UrlAttachmentScraper.prototype.findFragments = function (message) {
10881
+ var _a;
10882
+ // If a message already has a URL attachment, don't add another one.
10883
+ if (message.attachments && message.attachments.filter(function (x) { return x.type === 'url'; }).length > 0)
10884
+ return null;
10885
+ return (Array.from((_a = message.message.match(URL_REGEX)) !== null && _a !== void 0 ? _a : []))
10886
+ .reduce(function (a, item) { return (a.includes(item) ? undefined : a.push(item), a); }, [])
10887
+ .map(function (url) { return ({
10888
+ text: url,
10889
+ offset: message.message.indexOf(url),
10890
+ type: 'url'
10891
+ }); });
10892
+ };
10893
+ return UrlAttachmentScraper;
10894
+ }());
10895
+ var UrlAttachmentResolver = /** @class */ (function () {
10896
+ function UrlAttachmentResolver(backend) {
10897
+ this.backend = backend;
10898
+ }
10899
+ UrlAttachmentResolver.prototype.resolveFragment = function (message, fragment) {
10900
+ return __awaiter(this, void 0, void 0, function () {
10901
+ var urlCard;
10902
+ return __generator(this, function (_b) {
10903
+ switch (_b.label) {
10904
+ case 0:
10905
+ if (fragment.type !== 'url')
10906
+ return [2 /*return*/, null];
10907
+ return [4 /*yield*/, this.backend.getCardForUrl(fragment.text)];
10908
+ case 1:
10909
+ urlCard = _b.sent();
10910
+ if (urlCard) {
10911
+ return [2 /*return*/, {
10912
+ type: 'card',
10913
+ url: fragment.text,
10914
+ card: urlCard,
10915
+ style: 'block'
10916
+ }];
10917
+ }
10918
+ return [2 /*return*/];
10919
+ }
10920
+ });
10921
+ });
10922
+ };
10923
+ return UrlAttachmentResolver;
10924
+ }());
10925
+ UrlAttachmentResolver.decorators = [
10926
+ { type: core.Injectable }
10927
+ ];
10928
+ UrlAttachmentResolver.ctorParameters = function () { return [
10929
+ { type: ChatBackendBase }
10930
+ ]; };
10931
+
10932
+ var YouTubeAttachmentResolver = /** @class */ (function () {
10933
+ function YouTubeAttachmentResolver() {
10934
+ }
10935
+ YouTubeAttachmentResolver.prototype.resolveFragment = function (message, fragment) {
10936
+ return __awaiter(this, void 0, void 0, function () {
10937
+ var videoId, match;
10938
+ return __generator(this, function (_a) {
10939
+ if (fragment.type !== 'url')
10940
+ return [2 /*return*/, null];
10941
+ if (fragment.text.match(/https?:\/\/(www\.)?youtube.com\/watch\?v=/)) {
10942
+ match = /watch\?v=([^&]+)/.exec(fragment.text);
10943
+ if (match) {
10944
+ videoId = match[1];
10945
+ }
10946
+ }
10947
+ if (videoId) {
10948
+ return [2 /*return*/, {
10949
+ type: 'iframe',
10950
+ url: "https://www.youtube.com/embed/" + videoId,
10951
+ style: 'block'
10952
+ }];
10953
+ }
10954
+ return [2 /*return*/, null];
10955
+ });
10956
+ });
10957
+ };
10958
+ return YouTubeAttachmentResolver;
10959
+ }());
10960
+
10961
+ var GiphyAttachmentResolver = /** @class */ (function () {
10962
+ function GiphyAttachmentResolver() {
10963
+ }
10964
+ GiphyAttachmentResolver.prototype.resolveFragment = function (message, fragment) {
10965
+ var _a;
10966
+ return __awaiter(this, void 0, void 0, function () {
10967
+ var gifId;
10968
+ return __generator(this, function (_b) {
10969
+ if (fragment.type === 'url' && fragment.text.startsWith('https://giphy.com/gifs')) {
10970
+ gifId = (_a = /[^-\/]+$/.exec(fragment.text)) === null || _a === void 0 ? void 0 : _a.toString();
10971
+ if (!gifId)
10972
+ return [2 /*return*/, null];
10973
+ return [2 /*return*/, {
10974
+ type: 'iframe',
10975
+ url: "https://giphy.com/embed/" + gifId,
10976
+ style: 'inline'
10977
+ }];
10978
+ }
10979
+ return [2 /*return*/, null];
10980
+ });
10981
+ });
10982
+ };
10983
+ return GiphyAttachmentResolver;
10984
+ }());
10985
+
10986
+ var TweetAttachmentResolver = /** @class */ (function () {
10987
+ function TweetAttachmentResolver() {
10988
+ }
10989
+ TweetAttachmentResolver.prototype.resolveFragment = function (message, fragment) {
10990
+ return __awaiter(this, void 0, void 0, function () {
10991
+ return __generator(this, function (_a) {
10992
+ if (fragment.type === 'url' && fragment.text.startsWith('https://twitter.com/')) {
10993
+ return [2 /*return*/, {
10994
+ type: 'tweet',
10995
+ url: fragment.text,
10996
+ style: 'block'
10997
+ }];
10998
+ }
10999
+ return [2 /*return*/, null];
11000
+ });
11001
+ });
11002
+ };
11003
+ return TweetAttachmentResolver;
11004
+ }());
11005
+
10476
11006
  var BantaSdkModule = /** @class */ (function () {
10477
- function BantaSdkModule() {
11007
+ function BantaSdkModule(chatBackend) {
11008
+ if (typeof window !== 'undefined') {
11009
+ if (!document.querySelector('script[src="https://platform.twitter.com/widgets.js"]')) {
11010
+ var script = document.createElement('script');
11011
+ script.src = 'https://platform.twitter.com/widgets.js';
11012
+ script.async = true;
11013
+ document.body.appendChild(script);
11014
+ }
11015
+ }
11016
+ chatBackend.registerAttachmentScraper(new UrlAttachmentScraper());
11017
+ chatBackend.registerAttachmentResolver(new GiphyAttachmentResolver());
11018
+ chatBackend.registerAttachmentResolver(new YouTubeAttachmentResolver());
11019
+ chatBackend.registerAttachmentResolver(new TweetAttachmentResolver());
11020
+ chatBackend.registerAttachmentResolver(new UrlAttachmentResolver(chatBackend));
10478
11021
  }
10479
11022
  BantaSdkModule.configure = function (options) {
10480
11023
  return {
@@ -10524,7 +11067,10 @@
10524
11067
  CommentsModule
10525
11068
  ]
10526
11069
  },] }
10527
- ];
11070
+ ];
11071
+ BantaSdkModule.ctorParameters = function () { return [
11072
+ { type: ChatBackendBase }
11073
+ ]; };
10528
11074
 
10529
11075
  /*
10530
11076
  * Public API Surface of sdk
@@ -10536,6 +11082,8 @@
10536
11082
 
10537
11083
  exports.AttachmentButtonComponent = AttachmentButtonComponent;
10538
11084
  exports.BANTA_SDK_OPTIONS = BANTA_SDK_OPTIONS;
11085
+ exports.BantaAttachmentComponent = BantaAttachmentComponent;
11086
+ exports.BantaAttachmentsComponent = BantaAttachmentsComponent;
10539
11087
  exports.BantaChatComponent = BantaChatComponent;
10540
11088
  exports.BantaCommentsComponent = BantaCommentsComponent;
10541
11089
  exports.BantaCommonModule = BantaCommonModule;
@@ -10544,6 +11092,7 @@
10544
11092
  exports.BantaMarkdownToHtmlPipe = BantaMarkdownToHtmlPipe;
10545
11093
  exports.BantaReplySendOptionsDirective = BantaReplySendOptionsDirective;
10546
11094
  exports.BantaSdkModule = BantaSdkModule;
11095
+ exports.BantaTrustResourceUrlPipe = BantaTrustResourceUrlPipe;
10547
11096
  exports.ChatBackend = ChatBackend;
10548
11097
  exports.ChatBackendBase = ChatBackendBase;
10549
11098
  exports.ChatMessageComponent = ChatMessageComponent;
@@ -10559,11 +11108,16 @@
10559
11108
  exports.EmojiModule = EmojiModule;
10560
11109
  exports.EmojiSelectorButtonComponent = EmojiSelectorButtonComponent;
10561
11110
  exports.EmojiSelectorPanelComponent = EmojiSelectorPanelComponent;
11111
+ exports.GiphyAttachmentResolver = GiphyAttachmentResolver;
10562
11112
  exports.LightboxComponent = LightboxComponent;
10563
11113
  exports.LiveChatMessageComponent = LiveChatMessageComponent;
10564
11114
  exports.LiveCommentComponent = LiveCommentComponent;
10565
11115
  exports.LiveMessageComponent = LiveMessageComponent;
10566
11116
  exports.TimestampComponent = TimestampComponent;
11117
+ exports.TweetAttachmentResolver = TweetAttachmentResolver;
11118
+ exports.UrlAttachmentResolver = UrlAttachmentResolver;
11119
+ exports.UrlAttachmentScraper = UrlAttachmentScraper;
11120
+ exports.YouTubeAttachmentResolver = YouTubeAttachmentResolver;
10567
11121
  exports.lazyConnection = lazyConnection;
10568
11122
 
10569
11123
  Object.defineProperty(exports, '__esModule', { value: true });