@banta/sdk 3.3.11 → 4.0.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 (47) hide show
  1. package/banta-sdk.metadata.json +1 -1
  2. package/bundles/banta-sdk.umd.js +1157 -413
  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/banta/banta.component.js +9 -15
  7. package/esm2015/lib/banta-sdk.module.js +10 -4
  8. package/esm2015/lib/chat/banta-chat/banta-chat.component.js +16 -19
  9. package/esm2015/lib/chat/chat-message/chat-message.component.js +2 -2
  10. package/esm2015/lib/chat/chat-view/chat-view.component.js +7 -6
  11. package/esm2015/lib/chat/live-chat-message.component.js +3 -3
  12. package/esm2015/lib/chat-backend-base.js +17 -0
  13. package/esm2015/lib/chat-backend.js +74 -0
  14. package/esm2015/lib/chat-source-base.js +2 -0
  15. package/esm2015/lib/chat-source.js +151 -0
  16. package/esm2015/lib/comments/banta-comments/banta-comments.component.js +344 -174
  17. package/esm2015/lib/comments/comment/comment.component.js +56 -20
  18. package/esm2015/lib/comments/comment-field/comment-field.component.js +19 -17
  19. package/esm2015/lib/comments/comment-view/comment-view.component.js +77 -39
  20. package/esm2015/lib/comments/live-comment.component.js +3 -3
  21. package/esm2015/lib/common/index.js +1 -3
  22. package/esm2015/lib/index.js +6 -1
  23. package/esm2015/lib/sdk-options.js +2 -0
  24. package/fesm2015/banta-sdk.js +761 -310
  25. package/fesm2015/banta-sdk.js.map +1 -1
  26. package/lib/banta/banta.component.d.ts +7 -8
  27. package/lib/banta-sdk.module.d.ts +2 -1
  28. package/lib/chat/banta-chat/banta-chat.component.d.ts +10 -12
  29. package/lib/chat/chat-view/chat-view.component.d.ts +7 -4
  30. package/lib/chat/live-chat-message.component.d.ts +2 -2
  31. package/lib/chat-backend-base.d.ts +22 -0
  32. package/lib/chat-backend.d.ts +21 -0
  33. package/lib/chat-source-base.d.ts +31 -0
  34. package/lib/chat-source.d.ts +38 -0
  35. package/lib/comments/banta-comments/banta-comments.component.d.ts +68 -61
  36. package/lib/comments/comment/comment.component.d.ts +25 -6
  37. package/lib/comments/comment-field/comment-field.component.d.ts +9 -5
  38. package/lib/comments/comment-view/comment-view.component.d.ts +26 -8
  39. package/lib/comments/live-comment.component.d.ts +2 -2
  40. package/lib/common/index.d.ts +0 -2
  41. package/lib/index.d.ts +5 -0
  42. package/lib/sdk-options.d.ts +4 -0
  43. package/package.json +1 -1
  44. package/esm2015/lib/common/banta.service.js +0 -21
  45. package/esm2015/lib/common/chat-backend.service.js +0 -7
  46. package/lib/common/banta.service.d.ts +0 -9
  47. package/lib/common/chat-backend.service.d.ts +0 -14
@@ -1,6 +1,6 @@
1
1
  import { Observable, Subject, BehaviorSubject, Subscription } from 'rxjs';
2
2
  import { publish } from 'rxjs/operators';
3
- import { Injectable, Component, Input, NgModule, Output, ViewChild, ElementRef, HostBinding } from '@angular/core';
3
+ import { Component, Input, NgModule, Output, ViewChild, ElementRef, HostBinding, Injectable, Inject } from '@angular/core';
4
4
  import { CommonModule } from '@angular/common';
5
5
  import { DomSanitizer } from '@angular/platform-browser';
6
6
  import { MatIconModule } from '@angular/material/icon';
@@ -8,9 +8,9 @@ import { MatButtonModule } from '@angular/material/button';
8
8
  import { MatFormFieldModule } from '@angular/material/form-field';
9
9
  import { MatInputModule } from '@angular/material/input';
10
10
  import { FormsModule } from '@angular/forms';
11
- import { __awaiter } from 'tslib';
11
+ import { __awaiter, __decorate, __metadata } from 'tslib';
12
12
  import { MatDialog, MatDialogModule } from '@angular/material/dialog';
13
- import { CommentsOrder } from '@banta/common';
13
+ import { CommentsOrder, SocketRPC, RpcEvent, DurableSocket } from '@banta/common';
14
14
  import { ActivatedRoute } from '@angular/router';
15
15
  import { MatMenuModule } from '@angular/material/menu';
16
16
  import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
@@ -31,25 +31,6 @@ function lazyConnection(options) {
31
31
  return obs.pipe(publish()).refCount();
32
32
  }
33
33
 
34
- class BantaService {
35
- constructor() {
36
- this._userChanged = new BehaviorSubject(null);
37
- }
38
- get userChanged() {
39
- return this._userChanged;
40
- }
41
- set user(user) {
42
- this._user = user;
43
- this._userChanged.next(user);
44
- }
45
- get user() {
46
- return this._user;
47
- }
48
- }
49
- BantaService.decorators = [
50
- { type: Injectable }
51
- ];
52
-
53
34
  class TimestampComponent {
54
35
  constructor() {
55
36
  this.relative = '';
@@ -155,12 +136,6 @@ TimestampComponent.propDecorators = {
155
136
  value: [{ type: Input }]
156
137
  };
157
138
 
158
- class ChatBackendService {
159
- }
160
- ChatBackendService.decorators = [
161
- { type: Injectable }
162
- ];
163
-
164
139
  const COMPONENTS = [
165
140
  TimestampComponent
166
141
  ];
@@ -6956,7 +6931,7 @@ class ChatMessageComponent {
6956
6931
  ChatMessageComponent.decorators = [
6957
6932
  { type: Component, args: [{
6958
6933
  selector: 'banta-chat-message',
6959
- template: "<div class=\"message-content\">\r\n <div class=\"user\" (click)=\"selectUser()\">\r\n <div class=\"avatar\" [style.background-image]=\"avatarForUser(message.user)\"></div>\r\n <label>{{message.user.username}}</label>\r\n </div>\r\n <div class=\"content\">\r\n <div (click)=\"select()\">\r\n {{message.message}}\r\n </div>\r\n <div class=\"status\">\r\n <div class=\"count-indicator\" *ngIf=\"message.upvotes > 0\">\r\n {{message.upvotes}} <mat-icon [inline]=\"true\">star</mat-icon>\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n<div class=\"actions\">\r\n <button mat-icon-button matTooltip=\"Upvote\" matTooltipPosition=\"below\" (click)=\"upvote()\">\r\n <mat-icon [inline]=\"true\">thumb_up</mat-icon>\r\n </button>\r\n <button mat-icon-button matTooltip=\"Report\" matTooltipPosition=\"below\" (click)=\"report()\">\r\n <mat-icon [inline]=\"true\">report</mat-icon>\r\n </button>\r\n</div>",
6934
+ template: "<div class=\"message-content\">\r\n <div class=\"user\" (click)=\"selectUser()\">\r\n <div class=\"avatar\" [style.background-image]=\"avatarForUser(message.user)\"></div>\r\n <label>{{message.user.username}}</label>\r\n </div>\r\n <div class=\"content\">\r\n <div (click)=\"select()\">\r\n {{message.message}}\r\n </div>\r\n <div class=\"status\">\r\n <div class=\"count-indicator\" *ngIf=\"message.likes > 0\">\r\n {{message.likes}} <mat-icon [inline]=\"true\">star</mat-icon>\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n<div class=\"actions\">\r\n <button mat-icon-button matTooltip=\"Upvote\" matTooltipPosition=\"below\" (click)=\"upvote()\">\r\n <mat-icon [inline]=\"true\">thumb_up</mat-icon>\r\n </button>\r\n <button mat-icon-button matTooltip=\"Report\" matTooltipPosition=\"below\" (click)=\"report()\">\r\n <mat-icon [inline]=\"true\">report</mat-icon>\r\n </button>\r\n</div>",
6960
6935
  styles: [":host{display:flex;flex-direction:row;align-items:center;padding:0 1em;background-color:#fff;color:#000;transition:background-color .4s ease-out}:host .message-content .content{color:#111}:host:hover{background-color:#ddd}:host.highlight{background:#00121b}:host.highlight:hover{background:#01324d}:host:nth-child(2n){background-color:#eee}:host:nth-child(2n):hover{background:#ddd}:host:nth-child(2n) .message-content .content{color:#222}:host:nth-child(2n).highlight{background:#001a2a}:host:nth-child(2n).highlight:hover{background:#002b44}:host .message-content{display:flex;flex-direction:row;flex-grow:1;align-items:center}:host .message-content .content{display:flex;flex-direction:row;padding:5px 0}:host .message-content .content .status{display:flex;flex-direction:row;align-items:center;margin-left:1em}:host .message-content .content .status mat-icon{margin-left:.5em}:host .user{color:#999;font-weight:400;text-align:right;margin-right:.25em;flex-shrink:0;display:flex;align-items:center}:host .user .avatar{background-position:50%;background-size:cover;background-color:#333;border-radius:100%;flex-shrink:0;flex-grow:0;margin-right:1em;width:2em;height:2em}:host .user:after{content:\":\";margin-right:1em}:host .content{flex-grow:1}:host .actions{flex-shrink:0;white-space:nowrap;opacity:0;transition:opacity .4s ease-out}:host:hover .actions{opacity:1}.count-indicator{white-space:nowrap}:host-context(.mat-dark-theme){background-color:#000;color:#fff}:host-context(.mat-dark-theme) .message-content .content{color:#ddd}:host-context(.mat-dark-theme):hover{background-color:#111}:host-context(.mat-dark-theme):nth-child(2n).highlight{background:#001a2a}:host-context(.mat-dark-theme):nth-child(2n).highlight:hover{background:#002b44}:host-context(.mat-dark-theme):nth-child(2n):hover{background-color:#111}:host-context(.mat-dark-theme):nth-child(2n){background-color:#080808}:host-context(.mat-dark-theme):nth-child(2n) .message-content .content{color:#eee}label{margin:0}"]
6961
6936
  },] }
6962
6937
  ];
@@ -6968,6 +6943,22 @@ ChatMessageComponent.propDecorators = {
6968
6943
  upvoted: [{ type: Output }]
6969
6944
  };
6970
6945
 
6946
+ class ChatBackendBase {
6947
+ constructor() {
6948
+ this._userChanged = new BehaviorSubject(null);
6949
+ }
6950
+ get userChanged() {
6951
+ return this._userChanged;
6952
+ }
6953
+ set user(user) {
6954
+ this._user = user;
6955
+ this._userChanged.next(user);
6956
+ }
6957
+ get user() {
6958
+ return this._user;
6959
+ }
6960
+ }
6961
+
6971
6962
  class LiveChatMessageComponent {
6972
6963
  constructor(backend) {
6973
6964
  this.backend = backend;
@@ -7027,7 +7018,7 @@ LiveChatMessageComponent.decorators = [
7027
7018
  },] }
7028
7019
  ];
7029
7020
  LiveChatMessageComponent.ctorParameters = () => [
7030
- { type: ChatBackendService }
7021
+ { type: ChatBackendBase }
7031
7022
  ];
7032
7023
  LiveChatMessageComponent.propDecorators = {
7033
7024
  upvoted: [{ type: Output }],
@@ -7037,7 +7028,8 @@ LiveChatMessageComponent.propDecorators = {
7037
7028
  };
7038
7029
 
7039
7030
  class ChatViewComponent {
7040
- constructor(elementRef) {
7031
+ constructor(backend, elementRef) {
7032
+ this.backend = backend;
7041
7033
  this.elementRef = elementRef;
7042
7034
  this._sourceSubs = new Subscription();
7043
7035
  this._selected = new Subject();
@@ -7078,10 +7070,8 @@ class ChatViewComponent {
7078
7070
  console.dir(this.messages);
7079
7071
  this._sourceSubs.add(this._source.messageReceived.subscribe(msg => this.messageReceived(msg)));
7080
7072
  this._sourceSubs.add(this._source.messageSent.subscribe(msg => this.messageSent(msg)));
7081
- if (this._source.currentUserChanged) {
7082
- this._sourceSubs.add(this._source.currentUserChanged
7083
- .subscribe(user => this.currentUser = user));
7084
- }
7073
+ this._sourceSubs.add(this.backend.userChanged
7074
+ .subscribe(user => this.currentUser = user));
7085
7075
  }
7086
7076
  }
7087
7077
  addMessage(message) {
@@ -7167,6 +7157,7 @@ ChatViewComponent.decorators = [
7167
7157
  },] }
7168
7158
  ];
7169
7159
  ChatViewComponent.ctorParameters = () => [
7160
+ { type: ChatBackendBase },
7170
7161
  { type: ElementRef }
7171
7162
  ];
7172
7163
  ChatViewComponent.propDecorators = {
@@ -7183,10 +7174,8 @@ ChatViewComponent.propDecorators = {
7183
7174
  * Chat component
7184
7175
  */
7185
7176
  class BantaChatComponent {
7186
- constructor(banta, backend, elementRef) {
7187
- this.banta = banta;
7177
+ constructor(backend) {
7188
7178
  this.backend = backend;
7189
- this.elementRef = elementRef;
7190
7179
  this._subs = new Subscription();
7191
7180
  this.user = null;
7192
7181
  this.signInLabel = 'Sign In';
@@ -7202,7 +7191,7 @@ class BantaChatComponent {
7202
7191
  this.newMessage = {};
7203
7192
  }
7204
7193
  ngOnInit() {
7205
- this._subs.add(this.banta.userChanged.subscribe(user => this.user = user));
7194
+ this._subs.add(this.backend.userChanged.subscribe(user => this.user = user));
7206
7195
  }
7207
7196
  ngOnDestroy() {
7208
7197
  this._subs.unsubscribe();
@@ -7236,8 +7225,8 @@ class BantaChatComponent {
7236
7225
  showSignIn() {
7237
7226
  this._signInSelected.next();
7238
7227
  }
7239
- sendPermissionError() {
7240
- this._permissionDeniedError.next();
7228
+ sendPermissionError(message) {
7229
+ this._permissionDeniedError.next(message);
7241
7230
  }
7242
7231
  insertEmoji(emoji) {
7243
7232
  let message = this.newMessage.message || '';
@@ -7275,14 +7264,15 @@ class BantaChatComponent {
7275
7264
  return this._userSelected;
7276
7265
  }
7277
7266
  get canChat() {
7278
- var _a;
7279
7267
  if (!this.user)
7280
7268
  return false;
7281
- if (!this.user.permissions)
7282
- return true;
7283
- if (!this.user.permissions.canChat)
7284
- return true;
7285
- return (_a = this.user.permissions) === null || _a === void 0 ? void 0 : _a.canChat(this.source);
7269
+ // TODO
7270
+ // if (!this.user.permissions)
7271
+ // return true;
7272
+ // if (!this.user.permissions.canChat)
7273
+ // return true;
7274
+ // return this.user.permissions?.canChat(this.source);
7275
+ return true;
7286
7276
  }
7287
7277
  sendMessage() {
7288
7278
  var _a;
@@ -7296,7 +7286,7 @@ class BantaChatComponent {
7296
7286
  let message = {
7297
7287
  user: null,
7298
7288
  sentAt: Date.now(),
7299
- upvotes: 0,
7289
+ likes: 0,
7300
7290
  url: location.href,
7301
7291
  message: text
7302
7292
  };
@@ -7321,9 +7311,7 @@ BantaChatComponent.decorators = [
7321
7311
  },] }
7322
7312
  ];
7323
7313
  BantaChatComponent.ctorParameters = () => [
7324
- { type: BantaService },
7325
- { type: ChatBackendService },
7326
- { type: ElementRef }
7314
+ { type: ChatBackendBase }
7327
7315
  ];
7328
7316
  BantaChatComponent.propDecorators = {
7329
7317
  shouldInterceptMessageSend: [{ type: Input }],
@@ -7367,8 +7355,7 @@ ChatModule.decorators = [
7367
7355
  * Unified chat and comments component
7368
7356
  */
7369
7357
  class BantaComponent {
7370
- constructor(banta, backend, matDialog) {
7371
- this.banta = banta;
7358
+ constructor(backend, matDialog) {
7372
7359
  this.backend = backend;
7373
7360
  this.matDialog = matDialog;
7374
7361
  this._subs = new Subscription();
@@ -7386,7 +7373,7 @@ class BantaComponent {
7386
7373
  this.genericAvatarUrl = 'https://gravatar.com/avatar/915c804e0be607a4ad766ddadea5c48a?s=512&d=https://codepen.io/assets/avatars/user-avatar-512x512-6e240cf350d2f1cc07c2bed234c3a3bb5f1b237023c204c782622e80d6b212ba.png';
7387
7374
  }
7388
7375
  ngOnInit() {
7389
- this._subs.add(this.banta.userChanged.subscribe(user => this.currentUser = user));
7376
+ this._subs.add(this.backend.userChanged.subscribe(user => this.currentUser = user));
7390
7377
  this._subs.add(this.backend.notificationsChanged.subscribe(notifs => this.notifications = notifs));
7391
7378
  this._subs.add(this.backend.newNotification.subscribe(notif => {
7392
7379
  this.newNotifications = true;
@@ -7401,7 +7388,7 @@ class BantaComponent {
7401
7388
  let message = {
7402
7389
  user: null,
7403
7390
  sentAt: Date.now(),
7404
- upvotes: 0,
7391
+ likes: 0,
7405
7392
  message: text
7406
7393
  };
7407
7394
  try {
@@ -7514,11 +7501,8 @@ class BantaComponent {
7514
7501
  }
7515
7502
  upvoteMessage(message) {
7516
7503
  return __awaiter(this, void 0, void 0, function* () {
7517
- if (message.parentMessageId)
7518
- yield this.backend.upvoteMessage(message.topicId, message.parentMessageId, message.id);
7519
- else
7520
- yield this.backend.upvoteMessage(message.topicId, message.id);
7521
- //message.upvotes += 1;
7504
+ // TODO
7505
+ //await this.backend.likeMessage(message.id);
7522
7506
  });
7523
7507
  }
7524
7508
  showProfile(user) {
@@ -7537,13 +7521,12 @@ class BantaComponent {
7537
7521
  BantaComponent.decorators = [
7538
7522
  { type: Component, args: [{
7539
7523
  selector: `banta`,
7540
- template: "\r\n<mat-menu #userMenu=\"matMenu\">\r\n <ng-container *ngIf=\"currentUser\">\r\n <button [disabled]=\"true\" mat-menu-item>{{currentUser.displayName}} (@{{currentUser.username}})</button>\r\n <button mat-menu-item (click)=\"signOut()\">Sign Out</button>\r\n </ng-container>\r\n <ng-container *ngIf=\"!currentUser\">\r\n <button mat-menu-item>Sign In</button>\r\n </ng-container>\r\n <button mat-menu-item>Help</button>\r\n</mat-menu>\r\n\r\n<div class=\"tabs\">\r\n <div>\r\n <a mat-button (click)=\"mobileFocus = 'chat'\">{{chatLabel}}</a>\r\n <a mat-button (click)=\"mobileFocus = 'comments'\">{{commentsLabel}}</a>\r\n </div>\r\n <div class=\"spacer\"></div>\r\n <div>\r\n <ng-container *ngIf=\"currentUser\">\r\n <button mat-button [matMenuTriggerFor]=\"userMenu\">\r\n @{{currentUser.username}}\r\n </button>\r\n <button mat-icon-button (click)=\"showNotifications()\">\r\n <mat-icon>notification_important</mat-icon>\r\n </button>\r\n </ng-container>\r\n \r\n <button mat-button *ngIf=\"!currentUser\" (click)=\"showSignIn()\">\r\n Sign In\r\n </button>\r\n </div>\r\n</div>\r\n\r\n<div class=\"firehose\" [class.focus]=\"mobileFocus === 'chat'\">\r\n <header>\r\n <div>\r\n <label (click)=\"mobileFocus = 'chat'\">{{chatLabel}}</label>\r\n <div class=\"spacer\"></div>\r\n\r\n <ng-container *ngIf=\"currentUser\">\r\n <button mat-button [matMenuTriggerFor]=\"userMenu\">\r\n @{{currentUser.username}}\r\n </button>\r\n <button mat-icon-button (click)=\"showNotifications()\">\r\n <mat-icon>notification_important</mat-icon>\r\n </button>\r\n </ng-container>\r\n \r\n <button mat-button *ngIf=\"!currentUser\" (click)=\"showSignIn()\">\r\n Sign In\r\n </button>\r\n </div>\r\n </header>\r\n <banta-chat \r\n #firehose\r\n [source]=\"firehoseSource\"\r\n (signInSelected)=\"showSignIn()\"\r\n (upvoted)=\"upvoteMessage($event)\"\r\n (userSelected)=\"showProfile($event.user)\"\r\n (reported)=\"reportMessage($event)\"\r\n ></banta-chat>\r\n</div>\r\n\r\n<div class=\"aux\" [class.focus]=\"mobileFocus === 'aux'\" [class.open]=\"auxOpen\">\r\n <header>\r\n <div>\r\n <label>{{auxTitle}}</label>\r\n <div class=\"spacer\"></div>\r\n <button mat-icon-button (click)=\"auxOpen = false\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n </div>\r\n </header>\r\n <div class=\"aux-contents\">\r\n <ng-container *ngIf=\"auxMode === 'profile'\">\r\n <ng-container *ngIf=\"profileUser\">\r\n\r\n <div>\r\n <strong style=\"font-size: 125%;\">\r\n {{profileUser.displayName}}\r\n </strong>\r\n @{{profileUser.username}}\r\n </div>\r\n\r\n <br/>\r\n <strong>Top Messages</strong>\r\n\r\n <div>\r\n <em>Not yet available</em>\r\n </div>\r\n\r\n <br/>\r\n <strong>Recent Messages</strong>\r\n\r\n <div>\r\n <em>Not yet available</em>\r\n </div>\r\n </ng-container>\r\n </ng-container>\r\n\r\n <ng-container *ngIf=\"auxMode === 'report'\">\r\n <p>Are you sure you want to report this message?</p>\r\n\r\n <banta-live-message [message]=\"reportedMessage\"></banta-live-message>\r\n\r\n <div style=\"text-align: center;\">\r\n <button mat-raised-button color=\"primary\" (click)=\"sendReport(reportedMessage)\">Yes, Report</button>\r\n &nbsp;\r\n <button mat-raised-button color=\"secondary\" (click)=\"auxOpen = false\">No, Cancel</button>\r\n </div>\r\n\r\n </ng-container>\r\n\r\n <ng-container *ngIf=\"auxMode === 'notifications'\">\r\n\r\n <div *ngIf=\"!notifications || notifications.length === 0\">\r\n <em>You do not have any notifications yet</em>\r\n </div>\r\n \r\n <div class=\"notifications\">\r\n <div class=\"notification\" *ngFor=\"let notif of notifications\">\r\n <div>\r\n <ng-container *ngIf=\"notif.type === 'upvote'\">\r\n @{{notif.message?.user?.username}} upvoted your post\r\n \r\n <banta-live-message\r\n [message]=\"notif.message\"\r\n (upvoted)=\"upvoteMessage(notif.message)\"\r\n (reported)=\"reportMessage(notif.message)\"\r\n (selected)=\"goToMessage(notif.message)\">\r\n </banta-live-message>\r\n\r\n </ng-container>\r\n <ng-container *ngIf=\"notif.type === 'notice'\">\r\n <div>\r\n {{notif.message}}\r\n </div>\r\n <a mat-button target=\"_blank\" href=\"{{notif.actionUrl}}\">\r\n {{notif.actionLabel}}\r\n </a>\r\n </ng-container>\r\n <ng-container *ngIf=\"notif.type === 'mention'\">\r\n You were mentioned by @{{notif.message?.user?.username}}\r\n\r\n <banta-live-message\r\n [message]=\"notif.message\"\r\n (upvoted)=\"upvoteMessage(notif.message)\"\r\n (reported)=\"reportMessage(notif.message)\"\r\n (selected)=\"goToMessage(notif.message)\">\r\n </banta-live-message>\r\n\r\n </ng-container>\r\n <ng-container *ngIf=\"notif.type === 'reply'\">\r\n @{{notif.replyMessage?.user?.username}} replied to your post\r\n \r\n <banta-live-message\r\n [message]=\"notif.replyMessage\"\r\n (upvoted)=\"upvoteMessage(notif.replyMessage)\"\r\n (reported)=\"reportMessage(notif.replyMessage)\"\r\n (selected)=\"goToMessage(notif.replyMessage)\">\r\n </banta-live-message>\r\n </ng-container>\r\n </div>\r\n\r\n <banta-timestamp [value]=\"notif.sentAt\"></banta-timestamp>\r\n </div>\r\n </div>\r\n </ng-container>\r\n </div>\r\n</div>\r\n<div class=\"points\" [class.focus]=\"mobileFocus === 'points'\">\r\n <header>\r\n <div>\r\n <label>{{commentsLabel}}</label>\r\n </div>\r\n </header>\r\n <div class=\"point-focus\">\r\n <div class=\"actions\">\r\n <button mat-button (click)=\"pointUnfocus()\">\r\n <mat-icon>arrow_back</mat-icon>\r\n Back\r\n </button>\r\n\r\n <div class=\"spacer\"></div>\r\n \r\n <ng-container *ngIf=\"pointOpen\">\r\n <div class=\"counted-action\">\r\n <div class=\"count-indicator\"> \r\n {{pointOpen.upvotes}}\r\n </div>\r\n <button mat-icon-button>\r\n <mat-icon>thumb_up</mat-icon>\r\n </button>\r\n </div>\r\n\r\n </ng-container>\r\n </div>\r\n\r\n <div *ngIf=\"!pointSubChat\">\r\n Error: No subchat\r\n </div>\r\n \r\n <banta-comment-view\r\n class=\"subcomments\"\r\n *ngIf=\"pointSubChat\"\r\n [newestLast]=\"true\"\r\n [allowReplies]=\"false\"\r\n [source]=\"pointSubChat\"\r\n (upvoted)=\"upvoteMessage($event)\"\r\n (reported)=\"reportMessage($event)\"\r\n (userSelected)=\"showProfile($event.user)\"\r\n >\r\n \r\n <banta-comment\r\n class=\"focused-comment\"\r\n data-before\r\n *ngIf=\"pointOpen\"\r\n (upvoted)=\"upvoteMessage(pointOpen)\"\r\n (userSelected)=\"showProfile(pointOpen.user)\"\r\n (reported)=\"reportMessage(pointOpen)\"\r\n [showReplyAction]=\"false\"\r\n [message]=\"pointOpen\"\r\n ></banta-comment>\r\n \r\n <div class=\"message reply\">\r\n Reply:\r\n <form class=\"new-message\" (submit)=\"sendPointSubMessage()\">\r\n <textarea \r\n name=\"message\" \r\n (keydown)=\"newPointSubMessageKeyDown($event)\"\r\n [(ngModel)]=\"newPointSubMessage.message\"></textarea>\r\n \r\n <div class=\"actions\">\r\n <button [disabled]=\"!newPointSubMessage.message\" \r\n mat-raised-button color=\"primary\">Send</button>\r\n </div>\r\n </form>\r\n </div>\r\n </banta-comment-view>\r\n </div>\r\n <div class=\"points-section\">\r\n <banta-comments\r\n [source]=\"pointSource\"\r\n (signInSelected)=\"showSignIn()\"\r\n (upvoted)=\"upvoteMessage($event)\"\r\n (reported)=\"reportMessage($event)\"\r\n (selected)=\"goToMessage($event)\"\r\n (userSelected)=\"showProfile($event.user)\"\r\n ></banta-comments>\r\n </div>\r\n</div>",
7524
+ template: "\r\n<mat-menu #userMenu=\"matMenu\">\r\n <ng-container *ngIf=\"currentUser\">\r\n <button [disabled]=\"true\" mat-menu-item>{{currentUser.displayName}} (@{{currentUser.username}})</button>\r\n <button mat-menu-item (click)=\"signOut()\">Sign Out</button>\r\n </ng-container>\r\n <ng-container *ngIf=\"!currentUser\">\r\n <button mat-menu-item>Sign In</button>\r\n </ng-container>\r\n <button mat-menu-item>Help</button>\r\n</mat-menu>\r\n\r\n<div class=\"tabs\">\r\n <div>\r\n <a mat-button (click)=\"mobileFocus = 'chat'\">{{chatLabel}}</a>\r\n <a mat-button (click)=\"mobileFocus = 'comments'\">{{commentsLabel}}</a>\r\n </div>\r\n <div class=\"spacer\"></div>\r\n <div>\r\n <ng-container *ngIf=\"currentUser\">\r\n <button mat-button [matMenuTriggerFor]=\"userMenu\">\r\n @{{currentUser.username}}\r\n </button>\r\n <button mat-icon-button (click)=\"showNotifications()\">\r\n <mat-icon>notification_important</mat-icon>\r\n </button>\r\n </ng-container>\r\n \r\n <button mat-button *ngIf=\"!currentUser\" (click)=\"showSignIn()\">\r\n Sign In\r\n </button>\r\n </div>\r\n</div>\r\n\r\n<div class=\"firehose\" [class.focus]=\"mobileFocus === 'chat'\">\r\n <header>\r\n <div>\r\n <label (click)=\"mobileFocus = 'chat'\">{{chatLabel}}</label>\r\n <div class=\"spacer\"></div>\r\n\r\n <ng-container *ngIf=\"currentUser\">\r\n <button mat-button [matMenuTriggerFor]=\"userMenu\">\r\n @{{currentUser.username}}\r\n </button>\r\n <button mat-icon-button (click)=\"showNotifications()\">\r\n <mat-icon>notification_important</mat-icon>\r\n </button>\r\n </ng-container>\r\n \r\n <button mat-button *ngIf=\"!currentUser\" (click)=\"showSignIn()\">\r\n Sign In\r\n </button>\r\n </div>\r\n </header>\r\n <banta-chat \r\n #firehose\r\n [source]=\"firehoseSource\"\r\n (signInSelected)=\"showSignIn()\"\r\n (upvoted)=\"upvoteMessage($event)\"\r\n (userSelected)=\"showProfile($event.user)\"\r\n (reported)=\"reportMessage($event)\"\r\n ></banta-chat>\r\n</div>\r\n\r\n<div class=\"aux\" [class.focus]=\"mobileFocus === 'aux'\" [class.open]=\"auxOpen\">\r\n <header>\r\n <div>\r\n <label>{{auxTitle}}</label>\r\n <div class=\"spacer\"></div>\r\n <button mat-icon-button (click)=\"auxOpen = false\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n </div>\r\n </header>\r\n <div class=\"aux-contents\">\r\n <ng-container *ngIf=\"auxMode === 'profile'\">\r\n <ng-container *ngIf=\"profileUser\">\r\n\r\n <div>\r\n <strong style=\"font-size: 125%;\">\r\n {{profileUser.displayName}}\r\n </strong>\r\n @{{profileUser.username}}\r\n </div>\r\n\r\n <br/>\r\n <strong>Top Messages</strong>\r\n\r\n <div>\r\n <em>Not yet available</em>\r\n </div>\r\n\r\n <br/>\r\n <strong>Recent Messages</strong>\r\n\r\n <div>\r\n <em>Not yet available</em>\r\n </div>\r\n </ng-container>\r\n </ng-container>\r\n\r\n <ng-container *ngIf=\"auxMode === 'report'\">\r\n <p>Are you sure you want to report this message?</p>\r\n\r\n <banta-live-message [message]=\"reportedMessage\"></banta-live-message>\r\n\r\n <div style=\"text-align: center;\">\r\n <button mat-raised-button color=\"primary\" (click)=\"sendReport(reportedMessage)\">Yes, Report</button>\r\n &nbsp;\r\n <button mat-raised-button color=\"secondary\" (click)=\"auxOpen = false\">No, Cancel</button>\r\n </div>\r\n\r\n </ng-container>\r\n\r\n <ng-container *ngIf=\"auxMode === 'notifications'\">\r\n\r\n <div *ngIf=\"!notifications || notifications.length === 0\">\r\n <em>You do not have any notifications yet</em>\r\n </div>\r\n \r\n <div class=\"notifications\">\r\n <div class=\"notification\" *ngFor=\"let notif of notifications\">\r\n <div>\r\n <ng-container *ngIf=\"notif.type === 'upvote'\">\r\n @{{notif.message?.user?.username}} upvoted your post\r\n \r\n <banta-live-message\r\n [message]=\"notif.message\"\r\n (upvoted)=\"upvoteMessage(notif.message)\"\r\n (reported)=\"reportMessage(notif.message)\"\r\n (selected)=\"goToMessage(notif.message)\">\r\n </banta-live-message>\r\n\r\n </ng-container>\r\n <ng-container *ngIf=\"notif.type === 'notice'\">\r\n <div>\r\n {{notif.message}}\r\n </div>\r\n <a mat-button target=\"_blank\" href=\"{{notif.actionUrl}}\">\r\n {{notif.actionLabel}}\r\n </a>\r\n </ng-container>\r\n <ng-container *ngIf=\"notif.type === 'mention'\">\r\n You were mentioned by @{{notif.message?.user?.username}}\r\n\r\n <banta-live-message\r\n [message]=\"notif.message\"\r\n (upvoted)=\"upvoteMessage(notif.message)\"\r\n (reported)=\"reportMessage(notif.message)\"\r\n (selected)=\"goToMessage(notif.message)\">\r\n </banta-live-message>\r\n\r\n </ng-container>\r\n <ng-container *ngIf=\"notif.type === 'reply'\">\r\n @{{notif.replyMessage?.user?.username}} replied to your post\r\n \r\n <banta-live-message\r\n [message]=\"notif.replyMessage\"\r\n (upvoted)=\"upvoteMessage(notif.replyMessage)\"\r\n (reported)=\"reportMessage(notif.replyMessage)\"\r\n (selected)=\"goToMessage(notif.replyMessage)\">\r\n </banta-live-message>\r\n </ng-container>\r\n </div>\r\n\r\n <banta-timestamp [value]=\"notif.sentAt\"></banta-timestamp>\r\n </div>\r\n </div>\r\n </ng-container>\r\n </div>\r\n</div>\r\n<div class=\"points\" [class.focus]=\"mobileFocus === 'points'\">\r\n <header>\r\n <div>\r\n <label>{{commentsLabel}}</label>\r\n </div>\r\n </header>\r\n <div class=\"point-focus\">\r\n <div class=\"actions\">\r\n <button mat-button (click)=\"pointUnfocus()\">\r\n <mat-icon>arrow_back</mat-icon>\r\n Back\r\n </button>\r\n\r\n <div class=\"spacer\"></div>\r\n \r\n <ng-container *ngIf=\"pointOpen\">\r\n <div class=\"counted-action\">\r\n <div class=\"count-indicator\"> \r\n {{pointOpen.likes}}\r\n </div>\r\n <button mat-icon-button>\r\n <mat-icon>thumb_up</mat-icon>\r\n </button>\r\n </div>\r\n\r\n </ng-container>\r\n </div>\r\n\r\n <div *ngIf=\"!pointSubChat\">\r\n Error: No subchat\r\n </div>\r\n \r\n <banta-comment-view\r\n class=\"subcomments\"\r\n *ngIf=\"pointSubChat\"\r\n [newestLast]=\"true\"\r\n [allowReplies]=\"false\"\r\n [source]=\"pointSubChat\"\r\n (upvoted)=\"upvoteMessage($event)\"\r\n (reported)=\"reportMessage($event)\"\r\n (userSelected)=\"showProfile($event.user)\"\r\n >\r\n \r\n <banta-comment\r\n class=\"focused-comment\"\r\n data-before\r\n *ngIf=\"pointOpen\"\r\n (upvoted)=\"upvoteMessage(pointOpen)\"\r\n (userSelected)=\"showProfile(pointOpen.user)\"\r\n (reported)=\"reportMessage(pointOpen)\"\r\n [showReplyAction]=\"false\"\r\n [message]=\"pointOpen\"\r\n ></banta-comment>\r\n \r\n <div class=\"message reply\">\r\n Reply:\r\n <form class=\"new-message\" (submit)=\"sendPointSubMessage()\">\r\n <textarea \r\n name=\"message\" \r\n (keydown)=\"newPointSubMessageKeyDown($event)\"\r\n [(ngModel)]=\"newPointSubMessage.message\"></textarea>\r\n \r\n <div class=\"actions\">\r\n <button [disabled]=\"!newPointSubMessage.message\" \r\n mat-raised-button color=\"primary\">Send</button>\r\n </div>\r\n </form>\r\n </div>\r\n </banta-comment-view>\r\n </div>\r\n <div class=\"points-section\">\r\n <banta-comments\r\n [source]=\"pointSource\"\r\n (signInSelected)=\"showSignIn()\"\r\n (upvoted)=\"upvoteMessage($event)\"\r\n (reported)=\"reportMessage($event)\"\r\n (selected)=\"goToMessage($event)\"\r\n (userSelected)=\"showProfile($event.user)\"\r\n ></banta-comments>\r\n </div>\r\n</div>",
7541
7525
  styles: [":host{display:flex;flex-direction:row;padding:.5em;height:40em;position:relative}.counted-action{display:flex;align-items:center}.count-indicator{font-size:9pt;padding:0 3px;border-radius:3px;border:1px solid #333}header{position:relative;margin-bottom:1em}header div{display:flex;align-items:center;height:30px}header button{color:#666}header label{text-transform:uppercase;z-index:1;font-size:12pt;letter-spacing:2px;font-weight:100;color:#333;margin:0 auto 0 0;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;white-space:nowrap;overflow-x:hidden;text-overflow:ellipsis}header:after,header label{display:block;position:relative}header:after{content:\"\";border:1px solid #ccc;height:0;width:100%;z-index:0}.points{max-width:50em;display:flex;flex-direction:column}:host.point-focus .points{width:66%;max-width:50em}:host.point-focus .points .points-section{opacity:0;pointer-events:none}:host.point-focus .points .point-focus{opacity:1;pointer-events:auto}:host.point-focus .points .point-focus .actions{display:flex}banta-comments{flex-grow:1}.points{width:33%;margin-left:.5em;font-size:12pt;flex-shrink:0;max-width:30em;transition:width .2s ease-in,max-width .2s ease-in;position:relative}.points .points-section{opacity:1;z-index:2}.points .point-focus,.points .points-section{flex-grow:1;display:flex;flex-direction:column;transition:opacity .2s ease-in}.points .point-focus{position:absolute;width:100%;bottom:0;top:1.75em;right:0;left:0;padding:.5em;opacity:0}.firehose{flex-grow:1;font-size:10pt;display:flex;flex-direction:column}form{display:flex;padding:.5em 0;align-items:center}form textarea{font-size:14pt;min-height:6em}form input[type=text],form textarea{background:#000;color:#fff;border:1px solid #333;width:100%}form input[type=text]{height:1em}form .actions{margin-left:1em}form button{display:block;margin:0 0 0 auto}.subcomments ::ng-deep banta-comment{font-size:10pt}.subcomments ::ng-deep banta-comment.focused-comment{background:#001321;color:#fff;font-size:12pt}.aux{width:0;min-width:0;overflow-x:hidden;transition:width .4s ease-out,min-width .4s ease-out;display:flex;flex-direction:column}.aux.open{width:30em;min-width:18em}.aux .aux-contents{width:30em;min-width:10em;max-width:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;flex-grow:1}.notifications .notification{border-bottom:1px solid #333;padding:1em}.notifications .notification banta-timestamp{display:block;text-align:right;font-size:9pt;color:#999}.message.reply{padding:1em}.tabs{display:none}@media (max-width:1015px){:host{flex-direction:column}.tabs{display:flex;position:absolute;top:0;left:0;right:0;width:100%;z-index:10;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);background:rgba(0,0,0,.5)}.points{width:100%;max-width:100%;margin-left:0}header{display:none}.aux,:host.point-focus .points{width:100%;max-width:100%}.aux{min-width:0}.aux,.firehose,.points{position:absolute;top:2em;left:0;right:0;bottom:0;z-index:0;background:#000}.aux.focus,.firehose.focus,.points.focus{z-index:2}}:host-context(.mat-dark-theme) :host{background:#090909;color:#fff}:host-context(.mat-dark-theme) form textarea{background:#ccc;color:#333}:host-context(.mat-dark-theme) header:after{border-color:#222}:host-context(.mat-dark-theme) header label{color:#aaa}"]
7542
7526
  },] }
7543
7527
  ];
7544
7528
  BantaComponent.ctorParameters = () => [
7545
- { type: BantaService },
7546
- { type: ChatBackendService },
7529
+ { type: ChatBackendBase },
7547
7530
  { type: MatDialog }
7548
7531
  ];
7549
7532
  BantaComponent.propDecorators = {
@@ -7650,7 +7633,8 @@ class CommentComponent {
7650
7633
  constructor() {
7651
7634
  this._reported = new Subject();
7652
7635
  this._selected = new Subject();
7653
- this._upvoted = new Subject();
7636
+ this._liked = new Subject();
7637
+ this._unliked = new Subject();
7654
7638
  this._shared = new Subject();
7655
7639
  this._userSelected = new Subject();
7656
7640
  this._avatarSelected = new Subject();
@@ -7658,7 +7642,12 @@ class CommentComponent {
7658
7642
  this.isNew = false;
7659
7643
  this.visible = false;
7660
7644
  this.showReplyAction = true;
7661
- this.upvoting = false;
7645
+ this.mine = false;
7646
+ this.editing = false;
7647
+ this._editStarted = new Subject();
7648
+ this._deleted = new Subject();
7649
+ this._editEnded = new Subject();
7650
+ this._edited = new Subject();
7662
7651
  }
7663
7652
  ngOnInit() {
7664
7653
  let maxTime = 500;
@@ -7670,6 +7659,10 @@ class CommentComponent {
7670
7659
  setTimeout(() => this.isNew = false, 1000);
7671
7660
  }, randomTime);
7672
7661
  }
7662
+ get isHighlighted() {
7663
+ var _a, _b, _c;
7664
+ return (_c = (_b = (_a = this.message) === null || _a === void 0 ? void 0 : _a.transientState) === null || _b === void 0 ? void 0 : _b.highlighted) !== null && _c !== void 0 ? _c : false;
7665
+ }
7673
7666
  get userSelected() {
7674
7667
  return this._userSelected.asObservable();
7675
7668
  }
@@ -7682,24 +7675,42 @@ class CommentComponent {
7682
7675
  get reported() {
7683
7676
  return this._reported.asObservable();
7684
7677
  }
7685
- get upvoted() {
7686
- return this._upvoted.asObservable();
7678
+ saveEdit() {
7679
+ this._edited.next(this.editedMessage);
7687
7680
  }
7688
- get selected() {
7689
- return this._selected.asObservable();
7681
+ endEditing() {
7682
+ this._editEnded.next();
7690
7683
  }
7691
- get commentId() {
7692
- var _a;
7693
- return (_a = this.message) === null || _a === void 0 ? void 0 : _a.id;
7684
+ startEdit() {
7685
+ this._editStarted.next();
7686
+ this.editedMessage = this.message.message;
7687
+ }
7688
+ delete() {
7689
+ this._deleted.next();
7694
7690
  }
7695
- get shared() {
7696
- return this._shared.asObservable();
7691
+ get liked() {
7692
+ return this._liked.asObservable();
7697
7693
  }
7694
+ get unliked() {
7695
+ return this._unliked.asObservable();
7696
+ }
7697
+ get selected() {
7698
+ return this._selected.asObservable();
7699
+ }
7700
+ get edited() { return this._edited.asObservable(); }
7701
+ get deleted() { return this._deleted.asObservable(); }
7702
+ get editStarted() { return this._editStarted.asObservable(); }
7703
+ get editEnded() { return this._editEnded.asObservable(); }
7704
+ get shared() { return this._shared.asObservable(); }
7705
+ get commentId() { var _a; return (_a = this.message) === null || _a === void 0 ? void 0 : _a.id; }
7698
7706
  report() {
7699
7707
  this._reported.next();
7700
7708
  }
7701
- upvote() {
7702
- this._upvoted.next();
7709
+ like() {
7710
+ this._liked.next();
7711
+ }
7712
+ unlike() {
7713
+ this._unliked.next();
7703
7714
  }
7704
7715
  share() {
7705
7716
  this._shared.next(this.message);
@@ -7729,12 +7740,13 @@ class CommentComponent {
7729
7740
  CommentComponent.decorators = [
7730
7741
  { type: Component, args: [{
7731
7742
  selector: 'banta-comment',
7732
- template: "\r\n<mat-menu #pointItemMenu=\"matMenu\">\r\n <button mat-menu-item (click)=\"report()\">Report</button>\r\n <button mat-menu-item>Help</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 </div>\r\n <div class=\"content\">\r\n {{message.message}}\r\n </div>\r\n\r\n <div class=\"actions\">\r\n <banta-timestamp [value]=\"message.sentAt\"></banta-timestamp>\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=\"Comment\" 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\">\r\n <div class=\"count-indicator\">\r\n {{message.upvotes}}\r\n </div>\r\n <button mat-icon-button [matTooltip]=\"upvoting ? 'Please wait...' : 'Like'\" matTooltipPosition=\"below\" (click)=\"upvote()\">\r\n\r\n <mat-spinner *ngIf=\"upvoting\" [diameter]=\"15\" style=\"margin-left: 1em;\"></mat-spinner>\r\n <mat-icon *ngIf=\"!upvoting\" [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",
7733
- 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{-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.new,:host.visible{visibility:visible}:host:hover{background:#eee}:host .message-content .content{margin-left:60px;margin-right:.5em}:host.abbreviated .message-content .content{max-height:8.5em;text-overflow:ellipsis;overflow-y:hidden}:host .actions{display:flex;padding-right:10px;margin-left:60px;align-items:center}:host .actions button{color:#666}:host .actions banta-timestamp{color:#666;font-size:10pt}.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,.user .username.username{color:#666;flex-shrink:0;flex-grow:1}.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}.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}:host-context(.mat-dark-theme) .user .display-name,:host-context(.mat-dark-theme) .user .username{color:#fff}@media (max-width:400px){.avatar{height:32px;width:32px}:host .actions{margin-left:44px}:host .message-content .content{margin-left:44px;margin-right:.5em}}"]
7743
+ 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 {{message.message}}\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 <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=\"Comment\" 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",
7744
+ 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.abbreviated .message-content .content{max-height:8.5em;text-overflow:ellipsis;overflow-y:hidden}:host .actions{display:flex;padding-right:10px;margin-left:60px;align-items:center}:host .actions button{color:#666}:host .actions banta-timestamp{color:#666;font-size:10pt}.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}:host-context(.mat-dark-theme) .user .display-name,:host-context(.mat-dark-theme) .user .username{color:#fff}@media (max-width:400px){.avatar{height:32px;width:32px}:host .actions{margin-left:44px}:host .message-content .content{margin-left:44px;margin-right:.5em}}.user-tag{text-transform:uppercase;font-size:12px;border:1px solid #b27373;background:#7a412b;color:#fff;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}"]
7734
7745
  },] }
7735
7746
  ];
7736
7747
  CommentComponent.propDecorators = {
7737
7748
  isNew: [{ type: HostBinding, args: ['class.new',] }],
7749
+ isHighlighted: [{ type: HostBinding, args: ['class.highlighted',] }],
7738
7750
  visible: [{ type: HostBinding, args: ['class.visible',] }],
7739
7751
  message: [{ type: Input }],
7740
7752
  showReplyAction: [{ type: Input }],
@@ -7742,11 +7754,18 @@ CommentComponent.propDecorators = {
7742
7754
  usernameSelected: [{ type: Output }],
7743
7755
  avatarSelected: [{ type: Output }],
7744
7756
  reported: [{ type: Output }],
7745
- upvoting: [{ type: Input }],
7746
- upvoted: [{ type: Output }],
7757
+ permissions: [{ type: Input }],
7758
+ mine: [{ type: Input }],
7759
+ editing: [{ type: Input }],
7760
+ liked: [{ type: Output }],
7761
+ unliked: [{ type: Output }],
7747
7762
  selected: [{ type: Output }],
7748
- commentId: [{ type: HostBinding, args: ['attr.data-comment-id',] }],
7749
- shared: [{ type: Output }]
7763
+ edited: [{ type: Output }],
7764
+ deleted: [{ type: Output }],
7765
+ editStarted: [{ type: Output }],
7766
+ editEnded: [{ type: Output }],
7767
+ shared: [{ type: Output }],
7768
+ commentId: [{ type: HostBinding, args: ['attr.data-comment-id',] }]
7750
7769
  };
7751
7770
 
7752
7771
  class CommentViewComponent {
@@ -7754,16 +7773,20 @@ class CommentViewComponent {
7754
7773
  this.backend = backend;
7755
7774
  this._sourceSubs = new Subscription();
7756
7775
  this._selected = new Subject();
7757
- this._upvoted = new Subject();
7776
+ this._liked = new Subject();
7777
+ this._unliked = new Subject();
7758
7778
  this._reported = new Subject();
7759
7779
  this._userSelected = new Subject();
7760
7780
  this._usernameSelected = new Subject();
7761
7781
  this._avatarSelected = new Subject();
7762
7782
  this._shared = new Subject();
7783
+ this._deleted = new Subject();
7784
+ this._messageEdited = new Subject();
7763
7785
  this.showEmptyState = true;
7764
7786
  this.allowReplies = true;
7765
7787
  this.menuMessage = null;
7766
7788
  this.messages = [];
7789
+ this.customSortEnabled = false;
7767
7790
  this.maxMessages = 2000;
7768
7791
  this.maxVisibleMessages = 200;
7769
7792
  this.newestLast = false;
@@ -7772,33 +7795,33 @@ class CommentViewComponent {
7772
7795
  this.hasMore = false;
7773
7796
  this.newMessages = [];
7774
7797
  this.olderMessages = [];
7798
+ this.sortOrderChanged = new Subject();
7775
7799
  }
7776
7800
  get selected() {
7777
7801
  return this._selected;
7778
7802
  }
7779
- get userSelected() {
7780
- return this._userSelected;
7803
+ get messageEdited() {
7804
+ return this._messageEdited.asObservable();
7781
7805
  }
7782
- get reported() {
7783
- return this._reported;
7784
- }
7785
- get upvoted() {
7786
- return this._upvoted;
7787
- }
7788
- get usernameSelected() {
7789
- return this._usernameSelected;
7790
- }
7791
- get avatarSelected() {
7792
- return this._avatarSelected;
7793
- }
7794
- get shared() {
7795
- return this._shared;
7806
+ saveEdit(message, newMessage) {
7807
+ this._messageEdited.next({ message, newMessage });
7796
7808
  }
7809
+ get userSelected() { return this._userSelected; }
7810
+ get reported() { return this._reported; }
7811
+ get liked() { return this._liked; }
7812
+ get unliked() { return this._unliked; }
7813
+ get usernameSelected() { return this._usernameSelected; }
7814
+ get avatarSelected() { return this._avatarSelected; }
7815
+ get shared() { return this._shared; }
7816
+ get deleted() { return this._deleted; }
7797
7817
  get source() {
7798
7818
  return this._source;
7799
7819
  }
7800
- upvoteMessage(message) {
7801
- this._upvoted.next(message);
7820
+ likeMessage(message) {
7821
+ this._liked.next(message);
7822
+ }
7823
+ unlikeMessage(message) {
7824
+ this._unliked.next(message);
7802
7825
  }
7803
7826
  reportMessage(message) {
7804
7827
  this._reported.next(message);
@@ -7818,31 +7841,51 @@ class CommentViewComponent {
7818
7841
  sharedMessage(message) {
7819
7842
  this._shared.next(message);
7820
7843
  }
7844
+ startEditing(message) {
7845
+ this.messages.forEach(m => m.transientState.editing = false);
7846
+ message.transientState.editing = true;
7847
+ }
7848
+ deleteMessage(message) {
7849
+ this._deleted.next(message);
7850
+ }
7821
7851
  set source(value) {
7852
+ this.customSortEnabled = (value === null || value === void 0 ? void 0 : value.sortOrder) !== CommentsOrder.NEWEST;
7853
+ this.newMessages = [];
7854
+ this.olderMessages = [];
7855
+ window.bantaSourceDebug = value;
7822
7856
  if (this._sourceSubs) {
7823
7857
  this._sourceSubs.unsubscribe();
7824
7858
  this._sourceSubs = null;
7825
7859
  }
7826
7860
  this._source = value;
7827
7861
  if (value) {
7828
- console.log(`[banta-comment-view] Subscribing to source...`);
7829
7862
  const messages = (value.messages || []).slice();
7830
7863
  this.messages = messages;
7831
7864
  this.olderMessages = messages.splice(this.maxVisibleMessages, messages.length);
7832
- this.hasMore = this.olderMessages.length > 0;
7865
+ this.hasMore = true; //this.olderMessages.length > 0;
7833
7866
  this._sourceSubs = new Subscription();
7834
7867
  this._sourceSubs.add(this._source.messageReceived.subscribe(msg => this.messageReceived(msg)));
7835
7868
  this._sourceSubs.add(this._source.messageSent.subscribe(msg => this.messageSent(msg)));
7836
- if (this._source.currentUserChanged) {
7837
- this._sourceSubs.add(this._source.currentUserChanged.subscribe(user => this.currentUser = user));
7838
- }
7869
+ this._sourceSubs.add(this.backend.userChanged.subscribe(user => this.currentUser = user));
7870
+ this.getInitialMessages();
7839
7871
  }
7840
7872
  }
7873
+ getInitialMessages() {
7874
+ return __awaiter(this, void 0, void 0, function* () {
7875
+ let messages = (yield this._source.getExistingMessages());
7876
+ messages.forEach(m => { var _a; return (_a = m.transientState) !== null && _a !== void 0 ? _a : (m.transientState = {}); });
7877
+ this.messages = messages;
7878
+ });
7879
+ }
7841
7880
  messageIdentity(index, chatMessage) {
7842
7881
  return chatMessage.id;
7843
7882
  }
7844
7883
  showNew() {
7845
7884
  return __awaiter(this, void 0, void 0, function* () {
7885
+ if (this.source && this.source.sortOrder !== CommentsOrder.NEWEST) {
7886
+ this.sortOrderChanged.next(CommentsOrder.NEWEST);
7887
+ return;
7888
+ }
7846
7889
  this.isViewingMore = false;
7847
7890
  this.messages = this.newMessages.splice(0, this.newMessages.length).concat(this.messages);
7848
7891
  let overflow = this.messages.splice(this.maxVisibleMessages, this.messages.length);
@@ -7858,29 +7901,38 @@ class CommentViewComponent {
7858
7901
  this.messages = this.messages.concat(this.olderMessages.splice(0, 50));
7859
7902
  }
7860
7903
  else {
7861
- if (this.source.loadAfter) {
7862
- this.isLoadingMore = true;
7863
- let lastMessage = this.messages[this.messages.length - 1];
7864
- let messages = yield this.source.loadAfter(lastMessage, 100);
7865
- this.messages = this.messages.concat(messages);
7904
+ this.isLoadingMore = true;
7905
+ let nextPageSize = 20;
7906
+ let lastMessage;
7907
+ lastMessage = this.messages[this.messages.length - 1];
7908
+ if (!lastMessage) {
7866
7909
  this.isLoadingMore = false;
7867
- if (messages.length === 0)
7868
- this.hasMore = false;
7910
+ this.hasMore = false;
7911
+ return;
7869
7912
  }
7870
- else {
7913
+ let messages = yield this.source.loadAfter(lastMessage, nextPageSize);
7914
+ messages.forEach(m => { var _a; return (_a = m.transientState) !== null && _a !== void 0 ? _a : (m.transientState = {}); });
7915
+ this.messages = this.messages.concat(messages);
7916
+ this.isLoadingMore = false;
7917
+ if (messages.length === 0) {
7918
+ console.log(`Reached the end of the list.`);
7871
7919
  this.hasMore = false;
7872
7920
  }
7873
7921
  }
7874
7922
  });
7875
7923
  }
7876
7924
  addMessage(message) {
7925
+ var _a;
7926
+ if (!message.transientState)
7927
+ (_a = message.transientState) !== null && _a !== void 0 ? _a : (message.transientState = {});
7877
7928
  let destination = this.messages;
7878
7929
  let bucket = this.olderMessages;
7879
7930
  if (this.isViewingMore) {
7880
7931
  destination = this.newMessages;
7881
7932
  bucket = null;
7882
7933
  }
7883
- if (this.newestLast) {
7934
+ let newestLast = this.newestLast;
7935
+ if (newestLast) {
7884
7936
  destination.push(message);
7885
7937
  let overflow = destination.splice(this.maxVisibleMessages, destination.length);
7886
7938
  bucket === null || bucket === void 0 ? void 0 : bucket.push(...overflow);
@@ -7935,41 +7987,65 @@ class CommentViewComponent {
7935
7987
  CommentViewComponent.decorators = [
7936
7988
  { type: Component, args: [{
7937
7989
  selector: 'banta-comment-view',
7938
- template: "<div class=\"message-container\">\r\n <ng-content select=\"[data-before]\"></ng-content>\r\n\r\n <a mat-button class=\"nav\" [class.visible]=\"isViewingMore\" href=\"javascript:;\" (click)=\"showNew()\">\r\n <mat-icon>file_upload</mat-icon>\r\n New\r\n <ng-container *ngIf=\"newMessages.length >= 1\">\r\n ({{newMessages.length}})\r\n </ng-container>\r\n </a>\r\n\r\n <ng-container *ngIf=\"messages.length === 0\">\r\n <div class=\"empty-state\" *ngIf=\"showEmptyState\">\r\n Be the first to comment!\r\n </div>\r\n </ng-container>\r\n <ng-container *ngFor=\"let message of messages; trackBy: messageIdentity\">\r\n <banta-comment\r\n *ngIf=\"!message.hidden\"\r\n class=\"abbreviated\"\r\n [message]=\"message\"\r\n [upvoting]=\"message['$upvoting']\"\r\n (click)=\"isViewingMore = true\"\r\n [showReplyAction]=\"allowReplies\"\r\n (userSelected)=\"selectMessageUser(message)\"\r\n (avatarSelected)=\"selectAvatar($event)\"\r\n (usernameSelected)=\"selectUsername($event)\"\r\n (upvoted)=\"upvoteMessage(message)\"\r\n (reported)=\"reportMessage(message)\"\r\n (selected)=\"selectMessage(message)\"\r\n (shared)=\"sharedMessage($event)\"\r\n ></banta-comment>\r\n </ng-container>\r\n\r\n <a mat-button class=\"nav\" [class.visible]=\"hasMore && !isLoadingMore\" href=\"javascript:;\" (click)=\"showMore()\">Show more</a>\r\n\r\n <div class=\"loading-more\" *ngIf=\"isLoadingMore\">\r\n <mat-spinner></mat-spinner>\r\n </div>\r\n\r\n <!-- <div style=\"color: #666\">\r\n n={{newMessages.length}}, m={{messages.length}}, o={{olderMessages.length}},\r\n v={{maxVisibleMessages}}, M={{maxMessages}}\r\n </div> -->\r\n\r\n <ng-content select=\":not([data-before])\"></ng-content>\r\n</div>\r\n",
7990
+ template: "<div class=\"message-container\">\r\n <ng-content select=\"[data-before]\"></ng-content>\r\n\r\n <a mat-button class=\"nav\" [class.visible]=\"isViewingMore || customSortEnabled\" href=\"javascript:;\" (click)=\"showNew()\">\r\n <mat-icon>file_upload</mat-icon>\r\n New\r\n <ng-container *ngIf=\"newMessages.length >= 1\">\r\n ({{newMessages.length}})\r\n </ng-container>\r\n </a>\r\n\r\n <ng-container *ngIf=\"messages.length === 0\">\r\n <div class=\"empty-state\" *ngIf=\"showEmptyState\">\r\n Be the first to comment!\r\n </div>\r\n </ng-container>\r\n <ng-container *ngFor=\"let message of messages; trackBy: messageIdentity\">\r\n <banta-comment\r\n *ngIf=\"!message.hidden\"\r\n class=\"abbreviated\"\r\n \r\n [message]=\"message\"\r\n [mine]=\"currentUser?.id === message.user?.id\"\r\n [permissions]=\"source?.permissions\"\r\n (click)=\"isViewingMore = true\"\r\n [showReplyAction]=\"allowReplies\"\r\n [editing]=\"message.transientState.editing\"\r\n (editStarted)=\"startEditing(message)\"\r\n (deleted)=\"deleteMessage(message)\"\r\n (editEnded)=\"message.transientState.editing = false\"\r\n (edited)=\"saveEdit(message, $event)\"\r\n (userSelected)=\"selectMessageUser(message)\"\r\n (avatarSelected)=\"selectAvatar($event)\"\r\n (usernameSelected)=\"selectUsername($event)\"\r\n (liked)=\"likeMessage(message)\"\r\n (unliked)=\"unlikeMessage(message)\"\r\n (reported)=\"reportMessage(message)\"\r\n (selected)=\"selectMessage(message)\"\r\n (shared)=\"sharedMessage($event)\"\r\n ></banta-comment>\r\n </ng-container>\r\n\r\n <a mat-button class=\"nav\" [class.visible]=\"hasMore && !isLoadingMore\" href=\"javascript:;\" (click)=\"showMore()\">Show more</a>\r\n\r\n <div class=\"loading-more\" *ngIf=\"isLoadingMore\">\r\n <mat-spinner></mat-spinner>\r\n </div>\r\n\r\n <!-- <div style=\"color: #666\">\r\n n={{newMessages.length}}, m={{messages.length}}, o={{olderMessages.length}},\r\n v={{maxVisibleMessages}}, M={{maxMessages}}\r\n </div> -->\r\n\r\n <ng-content select=\":not([data-before])\"></ng-content>\r\n</div>\r\n",
7939
7991
  styles: [":host{flex-grow:1;display:flex;flex-direction:column;opacity:1;transition:opacity .2s ease-in}.message-container{flex-grow:1;overflow-x:hidden;color:#111;background:#fff;padding:.5em 1em 3em .5em;opacity:1;transition:opacity .5s ease-in-out;position:relative}.message-container.no-scroll{height:auto;overflow-y:visible}.message-container.faded{opacity:.25}.message-container .overlay{position:absolute;top:0;left:0;right:0;bottom:0;z-index:10}:host.fixed-height .message-container{overflow-y:auto}:host-context(.mat-dark-theme) .message-container{color:#fff;background:#111}.empty-state{text-align:center;margin:3em;color:#666}:host-context(.mat-dark-theme) .empty-state{color:#666}a.nav{position:absolute;right:.5em;z-index:10;text-align:center;opacity:0;transition:opacity .4s ease-in-out;pointer-events:none;border-radius:2em;background:#222}a.nav.visible{opacity:1;pointer-events:auto}.loading-more{padding:2em;text-align:center;margin:0 auto;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content}@media (max-width:400px){.message-container{padding:0 0 3em}}"]
7940
7992
  },] }
7941
7993
  ];
7942
7994
  CommentViewComponent.ctorParameters = () => [
7943
- { type: ChatBackendService }
7995
+ { type: ChatBackendBase }
7944
7996
  ];
7945
7997
  CommentViewComponent.propDecorators = {
7946
7998
  showEmptyState: [{ type: Input }],
7947
7999
  allowReplies: [{ type: Input }],
7948
8000
  fixedHeight: [{ type: Input }, { type: HostBinding, args: ['class.fixed-height',] }],
7949
8001
  selected: [{ type: Output }],
8002
+ messageEdited: [{ type: Output }],
7950
8003
  userSelected: [{ type: Output }],
7951
8004
  reported: [{ type: Output }],
7952
- upvoted: [{ type: Output }],
8005
+ liked: [{ type: Output }],
8006
+ unliked: [{ type: Output }],
7953
8007
  usernameSelected: [{ type: Output }],
7954
8008
  avatarSelected: [{ type: Output }],
7955
8009
  shared: [{ type: Output }],
8010
+ deleted: [{ type: Output }],
7956
8011
  source: [{ type: Input }],
7957
8012
  genericAvatarUrl: [{ type: Input }],
7958
8013
  messageContainer: [{ type: ViewChild, args: ['messageContainer',] }],
7959
8014
  maxMessages: [{ type: Input }],
7960
8015
  maxVisibleMessages: [{ type: Input }],
7961
- newestLast: [{ type: Input }]
8016
+ newestLast: [{ type: Input }],
8017
+ sortOrderChanged: [{ type: Output }]
7962
8018
  };
7963
8019
 
7964
8020
  /**
7965
8021
  * Comments component
7966
8022
  */
7967
8023
  class BantaCommentsComponent {
7968
- constructor(banta, backend, elementRef, activatedRoute) {
7969
- this.banta = banta;
8024
+ constructor(backend, elementRef, activatedRoute) {
7970
8025
  this.backend = backend;
7971
8026
  this.elementRef = elementRef;
7972
8027
  this.activatedRoute = activatedRoute;
8028
+ // Loading Screen
8029
+ this._loadingMessage = '';
8030
+ this.loadingMessageVisible = false;
8031
+ this.loading = true;
8032
+ this.showLoadingScreen = false;
8033
+ this._loadingMessageIndex = 0;
8034
+ this.loadingMessages = [
8035
+ `Just a second...`,
8036
+ `We're definitely working on it.`,
8037
+ `There's no need to refresh.`,
8038
+ `It's definitely worth the wait!`,
8039
+ `This has never happened before.`,
8040
+ `We'll keep trying, but it's not looking great.
8041
+ Commenting & chat services may be down.
8042
+ If you continue to experience issues, please contact support.
8043
+ `
8044
+ ];
8045
+ // Properties
8046
+ this._signInSelected = new Subject();
8047
+ this._permissionDeniedError = new Subject();
8048
+ this._editAvatarSelected = new Subject();
7973
8049
  this._upvoted = new Subject();
7974
8050
  this._reported = new Subject();
7975
8051
  this._selected = new Subject();
@@ -7979,12 +8055,8 @@ class BantaCommentsComponent {
7979
8055
  this._avatarSelected = new Subject();
7980
8056
  this._subs = new Subscription();
7981
8057
  this._sortOrder = CommentsOrder.NEWEST;
7982
- this.hashtags = [
7983
- { hashtag: 'error', description: 'Cause an error' },
7984
- { hashtag: 'timeout', description: 'Cause a slow timeout error' },
7985
- { hashtag: 'slow', description: 'Be slow when this message is posted' },
7986
- ];
7987
- this.participants = [];
8058
+ this.selectedMessageVisible = false;
8059
+ // Inputs
7988
8060
  this.signInLabel = 'Sign In';
7989
8061
  this.sendLabel = 'Send';
7990
8062
  this.replyLabel = 'Reply';
@@ -7992,174 +8064,289 @@ class BantaCommentsComponent {
7992
8064
  this.permissionDeniedLabel = 'Send';
7993
8065
  this.postCommentLabel = 'Post a comment';
7994
8066
  this.postReplyLabel = 'Post a reply';
7995
- this._signInSelected = new Subject();
7996
- this._permissionDeniedError = new Subject();
7997
- this._editAvatarSelected = new Subject();
7998
- this.sending = false;
7999
- this.expandError = false;
8000
- this.selectedMessageVisible = false;
8067
+ this.participants = [];
8068
+ this.hashtags = [
8069
+ { hashtag: 'error', description: 'Cause an error' },
8070
+ { hashtag: 'timeout', description: 'Cause a slow timeout error' },
8071
+ { hashtag: 'slow', description: 'Be slow when this message is posted' },
8072
+ ];
8073
+ this.sendMessage = (message) => __awaiter(this, void 0, void 0, function* () {
8074
+ var _a;
8075
+ try {
8076
+ const intercept = yield ((_a = this.shouldInterceptMessageSend) === null || _a === void 0 ? void 0 : _a.call(this, message, this.source));
8077
+ if (!intercept) {
8078
+ yield this.source.send(message);
8079
+ }
8080
+ if (this.source.sortOrder !== CommentsOrder.NEWEST) {
8081
+ this.sortOrder = CommentsOrder.NEWEST;
8082
+ }
8083
+ return true;
8084
+ }
8085
+ catch (e) {
8086
+ this.handleBackendException(e, 'Could not send: ');
8087
+ }
8088
+ });
8089
+ this.sendReply = (message) => __awaiter(this, void 0, void 0, function* () {
8090
+ var _b;
8091
+ try {
8092
+ const intercept = yield ((_b = this.shouldInterceptMessageSend) === null || _b === void 0 ? void 0 : _b.call(this, message, this.source));
8093
+ if (!intercept) {
8094
+ yield this.selectedMessageThread.send(message);
8095
+ }
8096
+ return true;
8097
+ }
8098
+ catch (e) {
8099
+ this.handleBackendException(e, 'Could not send reply: ');
8100
+ }
8101
+ });
8001
8102
  }
8002
- get sortOrder() {
8003
- return this._sortOrder;
8103
+ handleBackendExceptionAsAlert(e, prefix = '') {
8104
+ try {
8105
+ this.handleBackendException(e, prefix);
8106
+ }
8107
+ catch (e) {
8108
+ alert(e.message);
8109
+ }
8004
8110
  }
8005
- set sortOrder(value) {
8006
- if (this._sortOrder !== value) {
8007
- this._sortOrder = value;
8008
- setTimeout(() => {
8009
- this.setSourceFromTopicID(this.topicID);
8010
- });
8111
+ handleBackendException(e, prefix = '') {
8112
+ let errorMessage = e.message;
8113
+ if (errorMessage.startsWith('permission-denied|')) {
8114
+ errorMessage = errorMessage.replace(/^permission-denied\|/, '');
8115
+ if (errorMessage.startsWith(`app-handle|`)) {
8116
+ // If this is an error during authorizeAction on the backend, pass control to the user-provided
8117
+ // permission-denied handler.
8118
+ this.sendPermissionDenied(errorMessage.replace(/^app-handle\|/, ''));
8119
+ return;
8120
+ }
8011
8121
  }
8122
+ throw new Error(`${prefix}${errorMessage}`);
8012
8123
  }
8124
+ // Lifecycle Events / Initialization
8013
8125
  ngOnInit() {
8014
- this._subs.add(this.banta.userChanged.subscribe(user => this.user = user));
8015
- }
8016
- ngAfterViewInit() {
8017
- if (typeof window !== 'undefined')
8018
- this.checkForSharedComment();
8019
- }
8020
- scrollToComment(commentId) {
8021
- setTimeout(() => {
8022
- const comment = document.querySelectorAll(`[data-comment-id="${commentId}"]`);
8023
- console.log(comment);
8024
- if (comment.length > 0) {
8025
- // comment.item(0).scroll({behavior: 'smooth'});
8026
- comment.item(0).scrollIntoView();
8126
+ this._subs.add(this.backend.userChanged.subscribe(user => this.user = user));
8127
+ this.startLoading();
8128
+ if (typeof window !== 'undefined') {
8129
+ let queryString = window.location.search.substring(1);
8130
+ let query = queryString.split('&')
8131
+ .map(s => s.split('='))
8132
+ .reduce((o, [k, v]) => (o[k] = v, o), {});
8133
+ const commentID = query['comment'];
8134
+ if (commentID) {
8135
+ this.sharedCommentID = commentID;
8027
8136
  }
8028
- }, 1000);
8029
- }
8030
- checkForSharedComment() {
8031
- const commentID = this.activatedRoute.snapshot.queryParamMap.get('comment');
8032
- if (commentID)
8033
- this.scrollToComment(commentID);
8137
+ }
8034
8138
  }
8035
8139
  ngOnDestroy() {
8036
8140
  this._subs.unsubscribe();
8037
8141
  }
8038
- get source() {
8039
- return this._source;
8040
- }
8041
- set source(value) {
8042
- this._source = value;
8043
- }
8044
- get topicID() {
8045
- return this._topicID;
8046
- }
8047
- set topicID(value) {
8048
- if (this._topicID !== value) {
8049
- this._topicID = value;
8050
- setTimeout(() => this.setSourceFromTopicID(value));
8051
- }
8052
- }
8053
8142
  setSourceFromTopicID(topicID) {
8054
- var _a, _b;
8055
8143
  return __awaiter(this, void 0, void 0, function* () {
8056
- (_b = (_a = this._source) === null || _a === void 0 ? void 0 : _a.close) === null || _b === void 0 ? void 0 : _b.call(_a);
8057
- this._source = null;
8144
+ if (this._source) {
8145
+ this._source.close();
8146
+ this._source = null;
8147
+ }
8058
8148
  setTimeout(() => __awaiter(this, void 0, void 0, function* () {
8149
+ console.log(`[banta-comments] Subscribing to topic source '${topicID}'`);
8059
8150
  this._source = yield this.backend.getSourceForTopic(topicID, { sortOrder: this.sortOrder });
8060
- console.log(`[banta-comments] Subscribing to source for topic '${topicID}'`);
8151
+ if (this.sharedCommentID) {
8152
+ this.navigateToSharedComment(this.sharedCommentID);
8153
+ this.sharedCommentID = null;
8154
+ }
8061
8155
  this._source.messageReceived.subscribe(m => this.addParticipant(m));
8062
8156
  this._source.messageSent.subscribe(m => this.addParticipant(m));
8063
8157
  this._source.messages.forEach(m => this.addParticipant(m));
8064
8158
  }));
8065
8159
  });
8066
8160
  }
8067
- addParticipant(message) {
8068
- if (!message || !message.user || !message.user.id)
8069
- return;
8070
- let existing = this.participants.find(x => x.id === message.user.id);
8071
- if (existing)
8072
- return;
8073
- this.participants.push(message.user);
8074
- }
8075
- showSignIn() {
8076
- this._signInSelected.next();
8161
+ get loadingMessage() {
8162
+ return this._loadingMessage;
8077
8163
  }
8078
- showEditAvatar() {
8079
- this._editAvatarSelected.next();
8080
- }
8081
- get newMessageText() {
8082
- return this._newMessageText;
8083
- }
8084
- set newMessageText(value) {
8085
- this._newMessageText = value;
8086
- if (this._newMessageText === '' && this.sendError)
8087
- setTimeout(() => this.sendError = null);
8088
- }
8089
- get signInSelected() {
8090
- return this._signInSelected;
8091
- }
8092
- get editAvatarSelected() {
8093
- return this._editAvatarSelected;
8094
- }
8095
- get permissionDeniedError() {
8096
- return this._permissionDeniedError;
8164
+ set loadingMessage(value) {
8165
+ this.loadingMessageVisible = false;
8166
+ setTimeout(() => {
8167
+ this._loadingMessage = value;
8168
+ this._loadingMessage = value;
8169
+ setTimeout(() => {
8170
+ this.loadingMessageVisible = true;
8171
+ });
8172
+ }, 500);
8097
8173
  }
8098
- showPermissionDenied() {
8099
- this._permissionDeniedError.next();
8174
+ startLoading() {
8175
+ return __awaiter(this, void 0, void 0, function* () {
8176
+ this.loadingStartedAt = this.messageChangedAt = Date.now();
8177
+ if (this.updateLoading())
8178
+ return;
8179
+ yield new Promise(resolve => setTimeout(() => resolve(), 100));
8180
+ if (this.updateLoading())
8181
+ return;
8182
+ yield new Promise(resolve => setTimeout(() => resolve(), 250));
8183
+ if (this.updateLoading())
8184
+ return;
8185
+ yield new Promise(resolve => setTimeout(() => resolve(), 500));
8186
+ if (this.updateLoading())
8187
+ return;
8188
+ console.log(`[Banta] Loading is taking a long time! Showing loading screen.`);
8189
+ this.showLoadingScreen = true;
8190
+ if (typeof window !== 'undefined')
8191
+ this._loadingTimer = setInterval(() => this.updateLoading(), 1000);
8192
+ });
8100
8193
  }
8101
- get canComment() {
8102
- var _a;
8103
- if (!this.user)
8104
- return false;
8105
- if (!this.user.permissions)
8106
- return true;
8107
- if (!this.user.permissions.canComment)
8194
+ updateLoading() {
8195
+ var _a, _b;
8196
+ if (((_a = this.source) === null || _a === void 0 ? void 0 : _a.state) && ((_b = this.source) === null || _b === void 0 ? void 0 : _b.state) !== 'connecting') {
8197
+ clearInterval(this._loadingTimer);
8198
+ this.loadingMessage = `Here we go!`;
8199
+ setTimeout(() => {
8200
+ this.loading = false;
8201
+ }, 750);
8108
8202
  return true;
8109
- return (_a = this.user.permissions) === null || _a === void 0 ? void 0 : _a.canComment(this.source);
8110
- }
8111
- get upvoted() {
8112
- return this._upvoted.asObservable();
8113
- }
8114
- get reported() {
8115
- return this._reported.asObservable();
8116
- }
8117
- get selected() {
8118
- return this._selected.asObservable();
8203
+ }
8204
+ let messageSwitchTime = 5 * 1000;
8205
+ if (this.messageChangedAt + messageSwitchTime < Date.now()) {
8206
+ if (this.loadingMessages[this._loadingMessageIndex]) {
8207
+ this.loadingMessage = this.loadingMessages[this._loadingMessageIndex++];
8208
+ this.messageChangedAt = Date.now();
8209
+ }
8210
+ }
8211
+ return false;
8119
8212
  }
8120
- get userSelected() {
8121
- return this._userSelected.asObservable();
8213
+ get source() { return this._source; }
8214
+ set source(value) {
8215
+ this._source = value;
8216
+ if (value && this.sharedCommentID) {
8217
+ this.navigateToSharedComment(this.sharedCommentID);
8218
+ this.sharedCommentID = null;
8219
+ }
8122
8220
  }
8123
- get usernameSelected() {
8124
- return this._usernameSelected.asObservable();
8221
+ get topicID() { return this._topicID; }
8222
+ set topicID(value) {
8223
+ if (this._topicID !== value) {
8224
+ this._topicID = value;
8225
+ setTimeout(() => this.setSourceFromTopicID(value));
8226
+ }
8125
8227
  }
8126
- get avatarSelected() {
8127
- return this._avatarSelected.asObservable();
8228
+ // Outputs
8229
+ get signInSelected() { return this._signInSelected; }
8230
+ get editAvatarSelected() { return this._editAvatarSelected; }
8231
+ get permissionDeniedError() { return this._permissionDeniedError; }
8232
+ get upvoted() { return this._upvoted.asObservable(); }
8233
+ get reported() { return this._reported.asObservable(); }
8234
+ get selected() { return this._selected.asObservable(); }
8235
+ get userSelected() { return this._userSelected.asObservable(); }
8236
+ get usernameSelected() { return this._usernameSelected.asObservable(); }
8237
+ get avatarSelected() { return this._avatarSelected.asObservable(); }
8238
+ get shared() { return this._shared.asObservable(); }
8239
+ get sortOrder() { return this._sortOrder; }
8240
+ set sortOrder(value) {
8241
+ if (this._sortOrder !== value) {
8242
+ this._sortOrder = value;
8243
+ setTimeout(() => {
8244
+ this.setSourceFromTopicID(this.topicID);
8245
+ });
8246
+ }
8128
8247
  }
8129
- get shared() {
8130
- return this._shared.asObservable();
8248
+ // UI Interactions
8249
+ scrollToComment(commentId) {
8250
+ setTimeout(() => {
8251
+ const comment = document.querySelectorAll(`[data-comment-id="${commentId}"]`);
8252
+ if (comment.length > 0) {
8253
+ // comment.item(0).scroll({behavior: 'smooth'});
8254
+ comment.item(0).scrollIntoView();
8255
+ }
8256
+ }, 1000);
8131
8257
  }
8132
- onKeyDown(event) {
8258
+ navigateToSharedComment(id) {
8259
+ var _a, _b, _c;
8260
+ return __awaiter(this, void 0, void 0, function* () {
8261
+ let source = this.source;
8262
+ yield source.ready;
8263
+ console.log(`Navigating to shared comment with ID '${id}'...`);
8264
+ let message;
8265
+ try {
8266
+ message = yield this.source.get(id);
8267
+ }
8268
+ catch (e) {
8269
+ console.error(`Failed to find comment from URL: ${e.message}`);
8270
+ return;
8271
+ }
8272
+ (_a = message.transientState) !== null && _a !== void 0 ? _a : (message.transientState = {});
8273
+ // If there is a parent message, we should instead focus that and let the
8274
+ // scrollToComment and highlight do the work.
8275
+ if (message.parentMessageId) {
8276
+ let parentMessage = yield this.source.get(message.parentMessageId);
8277
+ (_b = parentMessage.transientState) !== null && _b !== void 0 ? _b : (parentMessage.transientState = {});
8278
+ let thread = yield this.selectMessage(parentMessage);
8279
+ // Need to re-retrieve the message within the new chat source to affect its
8280
+ // transient state.
8281
+ yield thread.ready;
8282
+ message = yield thread.get(message.id);
8283
+ (_c = message.transientState) !== null && _c !== void 0 ? _c : (message.transientState = {});
8284
+ message.transientState.highlighted = true;
8285
+ console.dir(message);
8286
+ yield new Promise(resolve => setTimeout(() => resolve(), 500));
8287
+ }
8288
+ else {
8289
+ this.selectMessage(message);
8290
+ }
8291
+ this.scrollToComment(id);
8292
+ });
8133
8293
  }
8134
- insertEmoji(text) {
8135
- this.newMessageText += text;
8294
+ sendPermissionDenied(message) {
8295
+ this._permissionDeniedError.next(message);
8136
8296
  }
8137
- onReplyKeyDown(event) {
8297
+ scrollToMessage(message) {
8298
+ let el = this.elementRef.nativeElement.querySelector(`[data-comment-id="${message.id}"]`);
8299
+ if (!el)
8300
+ return;
8301
+ el.scrollIntoView({ block: 'center', inline: 'start' });
8138
8302
  }
8139
- insertReplyEmoji(text) {
8140
- this.replyMessage += text;
8303
+ addParticipant(message) {
8304
+ if (!message || !message.user || !message.user.id)
8305
+ return;
8306
+ let existing = this.participants.find(x => x.id === message.user.id);
8307
+ if (existing)
8308
+ return;
8309
+ this.participants.push(message.user);
8141
8310
  }
8142
- indicateError(message) {
8143
- this.sendError = new Error(message);
8144
- setTimeout(() => {
8145
- this.expandError = true;
8146
- setTimeout(() => {
8147
- this.expandError = false;
8148
- }, 5 * 1000);
8311
+ // Actions
8312
+ likeMessage(source, message) {
8313
+ var _a;
8314
+ return __awaiter(this, void 0, void 0, function* () {
8315
+ this._upvoted.next(message);
8316
+ message.transientState.liking = true;
8317
+ if (!((_a = message.userState) === null || _a === void 0 ? void 0 : _a.liked))
8318
+ message.likes = (message.likes || 0) + 1;
8319
+ try {
8320
+ yield source.likeMessage(message.id);
8321
+ }
8322
+ catch (e) {
8323
+ this.handleBackendExceptionAsAlert(e, 'Could not like this message: ');
8324
+ }
8325
+ yield new Promise(resolve => setTimeout(() => resolve(), 250));
8326
+ message.transientState.liking = false;
8149
8327
  });
8150
8328
  }
8151
- upvoteMessage(message) {
8329
+ unlikeMessage(source, message) {
8330
+ var _a;
8152
8331
  return __awaiter(this, void 0, void 0, function* () {
8153
8332
  this._upvoted.next(message);
8154
- message.$upvoting = true;
8155
- message.upvotes = (message.upvotes || 0) + 1;
8156
- yield this.backend.upvoteMessage(message.topicId, message.parentMessageId ? message.parentMessageId : message.id, message.parentMessageId ? message.id : undefined);
8333
+ message.transientState.liking = true;
8334
+ if ((_a = message.userState) === null || _a === void 0 ? void 0 : _a.liked)
8335
+ message.likes = (message.likes || 0) - 1;
8336
+ try {
8337
+ yield source.unlikeMessage(message.id);
8338
+ }
8339
+ catch (e) {
8340
+ this.handleBackendExceptionAsAlert(e, 'Failed to unlike this message: ');
8341
+ }
8157
8342
  yield new Promise(resolve => setTimeout(() => resolve(), 250));
8158
- message.$upvoting = false;
8343
+ message.transientState.liking = false;
8159
8344
  });
8160
8345
  }
8161
8346
  reportMessage(message) {
8162
- this._reported.next(message);
8347
+ return __awaiter(this, void 0, void 0, function* () {
8348
+ this._reported.next(message);
8349
+ });
8163
8350
  }
8164
8351
  unselectMessage() {
8165
8352
  return __awaiter(this, void 0, void 0, function* () {
@@ -8179,70 +8366,101 @@ class BantaCommentsComponent {
8179
8366
  return __awaiter(this, void 0, void 0, function* () {
8180
8367
  this._selected.next(message);
8181
8368
  this.selectedMessage = message;
8369
+ let selectedMessageThread = yield this.backend.getSourceForThread(this.topicID, message.id);
8182
8370
  setTimeout(() => this.selectedMessageVisible = true);
8183
8371
  setTimeout(() => __awaiter(this, void 0, void 0, function* () {
8184
- this.selectedMessageThread = yield this.backend.getSourceForThread(this.topicID, message.id);
8372
+ this.selectedMessageThread = selectedMessageThread;
8185
8373
  }), 250);
8374
+ return selectedMessageThread;
8375
+ });
8376
+ }
8377
+ showSignIn() {
8378
+ return __awaiter(this, void 0, void 0, function* () {
8379
+ this._signInSelected.next();
8380
+ });
8381
+ }
8382
+ showEditAvatar() {
8383
+ return __awaiter(this, void 0, void 0, function* () {
8384
+ this._editAvatarSelected.next();
8186
8385
  });
8187
8386
  }
8188
8387
  selectMessageUser(message) {
8189
- this._userSelected.next(message);
8388
+ return __awaiter(this, void 0, void 0, function* () {
8389
+ this._userSelected.next(message);
8390
+ });
8190
8391
  }
8191
8392
  selectUsername(user) {
8192
- this._usernameSelected.next(user);
8393
+ return __awaiter(this, void 0, void 0, function* () {
8394
+ this._usernameSelected.next(user);
8395
+ });
8193
8396
  }
8194
8397
  selectAvatar(user) {
8195
- this._avatarSelected.next(user);
8398
+ return __awaiter(this, void 0, void 0, function* () {
8399
+ this._avatarSelected.next(user);
8400
+ });
8196
8401
  }
8197
8402
  shareMessage(message) {
8198
- this._shared.next(message);
8403
+ return __awaiter(this, void 0, void 0, function* () {
8404
+ this._shared.next(message);
8405
+ });
8199
8406
  }
8200
- sendReply() {
8407
+ deleteMessage(message) {
8201
8408
  return __awaiter(this, void 0, void 0, function* () {
8202
- yield this.selectedMessageThread.send({
8203
- message: this.replyMessage,
8204
- parentMessageId: this.selectedMessage.id,
8205
- upvotes: 0,
8206
- user: this.user,
8207
- submessages: [],
8208
- submessageCount: 0,
8209
- topicId: this.topicID,
8210
- sentAt: Date.now(),
8211
- updatedAt: Date.now()
8212
- });
8213
- this.replyMessage = '';
8409
+ if (!confirm("Are you sure you want to delete this comment? You cannot undo this action."))
8410
+ return;
8411
+ try {
8412
+ yield this.source.deleteMessage(message.id);
8413
+ }
8414
+ catch (e) {
8415
+ this.handleBackendExceptionAsAlert(e, `Could not delete message: `);
8416
+ }
8214
8417
  });
8215
8418
  }
8216
- scrollToMessage(message) {
8217
- let el = this.elementRef.nativeElement.querySelector(`[data-comment-id="${message.id}"]`);
8218
- if (!el)
8219
- return;
8220
- el.scrollIntoView({ block: 'center', inline: 'start' });
8419
+ editMessage(source, message, newText) {
8420
+ return __awaiter(this, void 0, void 0, function* () {
8421
+ try {
8422
+ yield source.editMessage(message.id, newText);
8423
+ }
8424
+ catch (e) {
8425
+ this.handleBackendExceptionAsAlert(e, 'Could not edit this message: ');
8426
+ return;
8427
+ }
8428
+ message.message = newText;
8429
+ message.transientState.editing = false;
8430
+ });
8431
+ }
8432
+ startEditing(message) {
8433
+ return __awaiter(this, void 0, void 0, function* () {
8434
+ this.selectedMessage.transientState.editing = false;
8435
+ message.transientState.editing = true;
8436
+ });
8437
+ }
8438
+ saveEdit(message, text) {
8439
+ return __awaiter(this, void 0, void 0, function* () {
8440
+ try {
8441
+ yield this.source.editMessage(message.id, text);
8442
+ message.transientState.editing = false;
8443
+ }
8444
+ catch (e) {
8445
+ this.handleBackendExceptionAsAlert(e, `Could not edit message: `);
8446
+ }
8447
+ });
8221
8448
  }
8222
8449
  }
8223
8450
  BantaCommentsComponent.decorators = [
8224
8451
  { type: Component, args: [{
8225
8452
  selector: 'banta-comments',
8226
- template: "\r\n<div class=\"focused\" [class.visible]=\"selectedMessageVisible\" *ngIf=\"selectedMessage\">\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 ></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 ></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]=\"canComment\"\r\n [signInLabel]=\"signInLabel\"\r\n [permissionDeniedLabel]=\"permissionDeniedLabel\"\r\n (permissionDeniedError)=\"showPermissionDenied()\"\r\n [shouldInterceptMessageSend]=\"shouldInterceptMessageSend\"\r\n [user]=\"user\"\r\n [label]=\"postReplyLabel\"\r\n >\r\n <ng-content select=\".reply-send-options\"></ng-content>\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\">\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]=\"canComment\"\r\n [hashtags]=\"hashtags\"\r\n [participants]=\"participants\"\r\n [label]=\"postCommentLabel\"\r\n (editAvatarSelected)=\"showEditAvatar()\"\r\n (signInSelected)=\"showSignIn()\"\r\n [permissionDeniedLabel]=\"permissionDeniedLabel\"\r\n (permissionDeniedError)=\"showPermissionDenied()\"\r\n [shouldInterceptMessageSend]=\"shouldInterceptMessageSend\"\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\"\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 (selected)=\"selectMessage($event)\"\r\n (upvoted)=\"upvoteMessage($event)\"\r\n (reported)=\"reportMessage($event)\"\r\n (usernameSelected)=\"selectUsername($event)\"\r\n (avatarSelected)=\"selectAvatar($event)\"\r\n (shared)=\"shareMessage($event)\"\r\n ></banta-comment-view>\r\n</div>\r\n",
8227
- 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:4em}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}@media (max-width:500px){.focused .replies{margin-left:0}}"]
8453
+ 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\">\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 (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 (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]=\"permissionDeniedLabel\"\r\n (permissionDeniedError)=\"sendPermissionDenied($event)\"\r\n [shouldInterceptMessageSend]=\"shouldInterceptMessageSend\"\r\n [user]=\"user\"\r\n [label]=\"postReplyLabel\"\r\n [submit]=\"sendReply\"\r\n >\r\n <ng-content select=\".reply-send-options\"></ng-content>\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\">\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]=\"permissionDeniedLabel\"\r\n (permissionDeniedError)=\"sendPermissionDenied($event)\"\r\n [shouldInterceptMessageSend]=\"shouldInterceptMessageSend\"\r\n [submit]=\"sendMessage\"\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\"\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 (deleted)=\"deleteMessage($event)\"\r\n ></banta-comment-view>\r\n </div>\r\n</ng-container>",
8454
+ 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:4em}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}@media (max-width:500px){.focused .replies{margin-left:0}}.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}"]
8228
8455
  },] }
8229
8456
  ];
8230
8457
  BantaCommentsComponent.ctorParameters = () => [
8231
- { type: BantaService },
8232
- { type: ChatBackendService },
8458
+ { type: ChatBackendBase },
8233
8459
  { type: ElementRef },
8234
8460
  { type: ActivatedRoute }
8235
8461
  ];
8236
8462
  BantaCommentsComponent.propDecorators = {
8237
- hashtags: [{ type: Input }],
8238
- participants: [{ type: Input }],
8239
- source: [{ type: Input }],
8240
- fixedHeight: [{ type: Input }],
8241
- maxMessages: [{ type: Input }],
8242
- maxVisibleMessages: [{ type: Input }],
8243
- genericAvatarUrl: [{ type: Input }],
8244
- shouldInterceptMessageSend: [{ type: Input }],
8245
- topicID: [{ type: Input }],
8463
+ loadingMessages: [{ type: Input }],
8246
8464
  signInLabel: [{ type: Input }],
8247
8465
  sendLabel: [{ type: Input }],
8248
8466
  replyLabel: [{ type: Input }],
@@ -8250,6 +8468,15 @@ BantaCommentsComponent.propDecorators = {
8250
8468
  permissionDeniedLabel: [{ type: Input }],
8251
8469
  postCommentLabel: [{ type: Input }],
8252
8470
  postReplyLabel: [{ type: Input }],
8471
+ fixedHeight: [{ type: Input }],
8472
+ maxMessages: [{ type: Input }],
8473
+ maxVisibleMessages: [{ type: Input }],
8474
+ genericAvatarUrl: [{ type: Input }],
8475
+ shouldInterceptMessageSend: [{ type: Input }],
8476
+ participants: [{ type: Input }],
8477
+ source: [{ type: Input }],
8478
+ hashtags: [{ type: Input }],
8479
+ topicID: [{ type: Input }],
8253
8480
  signInSelected: [{ type: Output }],
8254
8481
  editAvatarSelected: [{ type: Output }],
8255
8482
  permissionDeniedError: [{ type: Output }],
@@ -8321,7 +8548,7 @@ LiveCommentComponent.decorators = [
8321
8548
  },] }
8322
8549
  ];
8323
8550
  LiveCommentComponent.ctorParameters = () => [
8324
- { type: ChatBackendService }
8551
+ { type: ChatBackendBase }
8325
8552
  ];
8326
8553
  LiveCommentComponent.propDecorators = {
8327
8554
  upvoted: [{ type: Output }],
@@ -8344,6 +8571,7 @@ class CommentFieldComponent {
8344
8571
  this.permissionDeniedLabel = 'Unavailable';
8345
8572
  this.signInLabel = 'Sign In';
8346
8573
  this.placeholder = '';
8574
+ this.textChanged = new Subject();
8347
8575
  this.participants = [];
8348
8576
  this._permissionDeniedError = new Subject();
8349
8577
  this.autocompleteVisible = false;
@@ -8357,8 +8585,8 @@ class CommentFieldComponent {
8357
8585
  let root = document.body.querySelector('[ng-version]') || document.body;
8358
8586
  root.appendChild(this.autocompleteEl.nativeElement);
8359
8587
  }
8360
- showPermissionDenied() {
8361
- this._permissionDeniedError.next();
8588
+ sendPermissionDenied(message) {
8589
+ this._permissionDeniedError.next(message);
8362
8590
  }
8363
8591
  showAutoComplete(options) {
8364
8592
  this.autoCompleteSelected = 0;
@@ -8381,12 +8609,14 @@ class CommentFieldComponent {
8381
8609
  }
8382
8610
  indicateError(message) {
8383
8611
  this.sendError = new Error(message);
8384
- setTimeout(() => {
8612
+ this.expandError = false;
8613
+ clearTimeout(this.errorTimeout);
8614
+ this.errorTimeout = setTimeout(() => {
8385
8615
  this.expandError = true;
8386
- setTimeout(() => {
8616
+ this.errorTimeout = setTimeout(() => {
8387
8617
  this.expandError = false;
8388
8618
  }, 5 * 1000);
8389
- });
8619
+ }, 100);
8390
8620
  }
8391
8621
  autocomplete(replacement) {
8392
8622
  return __awaiter(this, void 0, void 0, function* () {
@@ -8512,7 +8742,6 @@ class CommentFieldComponent {
8512
8742
  this.editAvatarSelected.next();
8513
8743
  }
8514
8744
  sendMessage() {
8515
- var _a;
8516
8745
  return __awaiter(this, void 0, void 0, function* () {
8517
8746
  if (!this.source)
8518
8747
  return;
@@ -8526,20 +8755,16 @@ class CommentFieldComponent {
8526
8755
  user: this.user,
8527
8756
  sentAt: Date.now(),
8528
8757
  url: location.href,
8529
- upvotes: 0,
8758
+ likes: 0,
8530
8759
  message: text
8531
8760
  };
8532
8761
  try {
8533
- const intercept = yield ((_a = this.shouldInterceptMessageSend) === null || _a === void 0 ? void 0 : _a.call(this, message, this.source));
8534
- if (!intercept) {
8535
- yield this.source.send(message);
8536
- }
8762
+ yield this.submit(message);
8537
8763
  this.text = '';
8538
8764
  }
8539
8765
  catch (e) {
8540
- this.indicateError(`Could not send: ${e.message}`);
8541
- console.error(`Failed to send message: `, message);
8542
- console.error(e);
8766
+ yield new Promise(resolve => setTimeout(() => resolve(), 1000));
8767
+ this.indicateError(e.message);
8543
8768
  }
8544
8769
  }
8545
8770
  finally {
@@ -8551,7 +8776,7 @@ class CommentFieldComponent {
8551
8776
  CommentFieldComponent.decorators = [
8552
8777
  { type: Component, args: [{
8553
8778
  selector: 'banta-comment-field',
8554
- 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(' + user?.avatarUrl + ')'\"\r\n ></a>\r\n </div>\r\n <div class=\"text-container\">\r\n <div class=\"field-container\">\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 <ng-content></ng-content>\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\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 </div>\r\n\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\">\r\n <mat-icon *ngIf=\"sendError\">error</mat-icon>\r\n {{sendError.message}}\r\n </div>\r\n <emoji-selector-button\r\n class=\"top-right\"\r\n (selected)=\"insertEmoji($event)\"\r\n ></emoji-selector-button>\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 *ngIf=\"canComment\"\r\n mat-raised-button\r\n class=\"send\"\r\n color=\"primary\"\r\n [disabled]=\"!text || sending\"\r\n >\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 <span class=\"label\">\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 </span>\r\n\r\n </button>\r\n <button\r\n *ngIf=\"!canComment\"\r\n type=\"button\"\r\n (click)=\"showPermissionDenied()\"\r\n mat-raised-button\r\n color=\"primary\"\r\n >{{permissionDeniedLabel}}</button>\r\n </ng-container>\r\n </div>\r\n</form>\r\n",
8779
+ 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(' + user?.avatarUrl + ')'\"\r\n ></a>\r\n </div>\r\n <div class=\"text-container\">\r\n <div class=\"field-container\">\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 <ng-content></ng-content>\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\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 </div>\r\n\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\">\r\n <mat-icon *ngIf=\"sendError\">error</mat-icon>\r\n {{sendError.message}}\r\n </div>\r\n <emoji-selector-button\r\n class=\"top-right\"\r\n (selected)=\"insertEmoji($event)\"\r\n ></emoji-selector-button>\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 *ngIf=\"canComment\"\r\n mat-raised-button\r\n class=\"send\"\r\n color=\"primary\"\r\n [disabled]=\"!text || sending\"\r\n >\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 <span class=\"label\">\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 </span>\r\n\r\n </button>\r\n <button\r\n *ngIf=\"!canComment\"\r\n type=\"button\"\r\n (click)=\"sendPermissionDenied('')\"\r\n mat-raised-button\r\n color=\"primary\"\r\n >{{permissionDeniedLabel}}</button>\r\n </ng-container>\r\n </div>\r\n</form>\r\n",
8555
8780
  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}.avatar-container{width:calc(48px + 1.75em);display:flex;justify-content:flex-end}.avatar-container .avatar{width:48px;height:48px;background:pink;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}form .text-container textarea{font-size:14pt;width:100%}form .text-container textarea[disabled]{opacity:.5}form .text-container mat-form-field{margin-bottom:1em}form .text-container emoji-selector-button{bottom:0;right:0;position:absolute}form .text-container .error-message,form .text-container mat-spinner.loading{position:absolute;left:.5em;bottom:.5em}form .text-container .error-message{color:#683333;overflow-x:hidden;max-width:1.5em;white-space:nowrap;transition:max-width 2s ease-in-out}form .text-container .error-message.expanded,form .text-container .error-message:hover{max-width:100%}form .text-container .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}form button{display:block;margin:0 0 0 auto}form.new-message{display:flex;align-items:flex-start}form.new-message .field-container{flex-grow:1;display:flex;flex-direction:column}form.new-message mat-form-field{width:100%}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}.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}}"]
8556
8781
  },] }
8557
8782
  ];
@@ -8561,19 +8786,23 @@ CommentFieldComponent.propDecorators = {
8561
8786
  canComment: [{ type: Input }],
8562
8787
  signInSelected: [{ type: Output }],
8563
8788
  editAvatarSelected: [{ type: Output }],
8789
+ sendError: [{ type: Input }],
8790
+ expandError: [{ type: Input }],
8564
8791
  sendLabel: [{ type: Input }],
8565
8792
  sendingLabel: [{ type: Input }],
8566
8793
  label: [{ type: Input }],
8567
8794
  permissionDeniedLabel: [{ type: Input }],
8568
8795
  signInLabel: [{ type: Input }],
8569
8796
  placeholder: [{ type: Input }],
8797
+ textChanged: [{ type: Output }],
8570
8798
  shouldInterceptMessageSend: [{ type: Input }],
8571
8799
  autocompleteEl: [{ type: ViewChild, args: ['autocomplete',] }],
8572
8800
  autocompleteContainerEl: [{ type: ViewChild, args: ['autocompleteContainer',] }],
8573
8801
  textareaEl: [{ type: ViewChild, args: ['textarea',] }],
8574
8802
  hashtags: [{ type: Input }],
8575
8803
  participants: [{ type: Input }],
8576
- permissionDeniedError: [{ type: Output }]
8804
+ permissionDeniedError: [{ type: Output }],
8805
+ submit: [{ type: Input }]
8577
8806
  };
8578
8807
 
8579
8808
  class CommentSortComponent {
@@ -8639,12 +8868,234 @@ CommentsModule.decorators = [
8639
8868
  },] }
8640
8869
  ];
8641
8870
 
8871
+ class ChatSource extends SocketRPC {
8872
+ constructor(backend, identifier, parentIdentifier, sortOrder) {
8873
+ super();
8874
+ this.backend = backend;
8875
+ this.identifier = identifier;
8876
+ this.parentIdentifier = parentIdentifier;
8877
+ this.sortOrder = sortOrder;
8878
+ this.subscription = new Subscription();
8879
+ this.state = 'connecting';
8880
+ this.messageMap = new Map();
8881
+ this._messageReceived = new Subject();
8882
+ this._messageSent = new Subject();
8883
+ this.messages = [];
8884
+ this.ready = new Promise(resolve => this.markReady = resolve);
8885
+ }
8886
+ bind(socket) {
8887
+ const _super = Object.create(null, {
8888
+ bind: { get: () => super.bind }
8889
+ });
8890
+ return __awaiter(this, void 0, void 0, function* () {
8891
+ _super.bind.call(this, socket);
8892
+ this.state = 'connected';
8893
+ this.markReady();
8894
+ yield this.subscribeToTopic();
8895
+ this.subscription.add(this.backend.userChanged.subscribe(() => this.authenticate()));
8896
+ socket.addEventListener('open', () => __awaiter(this, void 0, void 0, function* () {
8897
+ this.state = 'connected';
8898
+ }));
8899
+ socket.addEventListener('lost', () => __awaiter(this, void 0, void 0, function* () {
8900
+ this.state = 'lost';
8901
+ }));
8902
+ socket.addEventListener('restore', () => __awaiter(this, void 0, void 0, function* () {
8903
+ this.state = 'restored';
8904
+ yield this.authenticate();
8905
+ yield this.subscribeToTopic();
8906
+ }));
8907
+ return this;
8908
+ });
8909
+ }
8910
+ getExistingMessages() {
8911
+ return __awaiter(this, void 0, void 0, function* () {
8912
+ let messages = yield this.peer.getExistingMessages();
8913
+ messages = messages.map(message => {
8914
+ let existingMessage = this.messageMap.get(message.id);
8915
+ if (existingMessage)
8916
+ message = Object.assign(existingMessage, message);
8917
+ else
8918
+ this.messageMap.set(message.id, message);
8919
+ return message;
8920
+ });
8921
+ return messages;
8922
+ });
8923
+ }
8924
+ editMessage(messageId, text) {
8925
+ return __awaiter(this, void 0, void 0, function* () {
8926
+ this.peer.editMessage(messageId, text);
8927
+ });
8928
+ }
8929
+ subscribeToTopic() {
8930
+ return __awaiter(this, void 0, void 0, function* () {
8931
+ yield this.peer.subscribe(this.identifier, this.parentIdentifier, this.sortOrder);
8932
+ });
8933
+ }
8934
+ authenticate() {
8935
+ var _a;
8936
+ return __awaiter(this, void 0, void 0, function* () {
8937
+ yield this.peer.authenticate((_a = this.backend.user) === null || _a === void 0 ? void 0 : _a.token);
8938
+ });
8939
+ }
8940
+ close() {
8941
+ super.close();
8942
+ this.subscription.unsubscribe();
8943
+ }
8944
+ onPermissions(permissions) {
8945
+ window.bantaPermissionsDebug = permissions;
8946
+ this.permissions = permissions;
8947
+ }
8948
+ onChatMessage(message) {
8949
+ if (this.messageMap.has(message.id)) {
8950
+ Object.assign(this.messageMap.get(message.id), message);
8951
+ }
8952
+ else if (!message.hidden) {
8953
+ // Only process non-hidden messages through here.
8954
+ // Hidden messages may be sent to us when they become hidden (ie moderation is occurring).
8955
+ // But if we never had the message to begin with, we should discard it.
8956
+ this.messageMap.set(message.id, message);
8957
+ this._messageReceived.next(message);
8958
+ }
8959
+ }
8960
+ get messageReceived() { return this._messageReceived.asObservable(); }
8961
+ get messageSent() { return this._messageSent.asObservable(); }
8962
+ send(message) {
8963
+ return __awaiter(this, void 0, void 0, function* () {
8964
+ return yield this.peer.sendMessage(message);
8965
+ });
8966
+ }
8967
+ loadAfter(message, count) {
8968
+ return __awaiter(this, void 0, void 0, function* () {
8969
+ if (!message)
8970
+ return;
8971
+ if (!message.pagingCursor)
8972
+ return [];
8973
+ return this.peer.loadAfter(Number(message.pagingCursor), count);
8974
+ });
8975
+ }
8976
+ get(id) {
8977
+ return __awaiter(this, void 0, void 0, function* () {
8978
+ if (this.messageMap.has(id))
8979
+ return this.messageMap.get(id);
8980
+ let message = yield this.peer.getMessage(id);
8981
+ this.messageMap.set(id, message);
8982
+ return message;
8983
+ });
8984
+ }
8985
+ getCount() {
8986
+ return __awaiter(this, void 0, void 0, function* () {
8987
+ return yield this.peer.getCount();
8988
+ });
8989
+ }
8990
+ likeMessage(messageId) {
8991
+ return __awaiter(this, void 0, void 0, function* () {
8992
+ return yield this.peer.likeMessage(messageId);
8993
+ });
8994
+ }
8995
+ unlikeMessage(messageId) {
8996
+ return __awaiter(this, void 0, void 0, function* () {
8997
+ return yield this.peer.unlikeMessage(messageId);
8998
+ });
8999
+ }
9000
+ deleteMessage(messageId) {
9001
+ return __awaiter(this, void 0, void 0, function* () {
9002
+ return yield this.peer.deleteMessage(messageId);
9003
+ });
9004
+ }
9005
+ }
9006
+ __decorate([
9007
+ RpcEvent(),
9008
+ __metadata("design:type", Function),
9009
+ __metadata("design:paramtypes", [Object]),
9010
+ __metadata("design:returntype", void 0)
9011
+ ], ChatSource.prototype, "onPermissions", null);
9012
+ __decorate([
9013
+ RpcEvent(),
9014
+ __metadata("design:type", Function),
9015
+ __metadata("design:paramtypes", [Object]),
9016
+ __metadata("design:returntype", void 0)
9017
+ ], ChatSource.prototype, "onChatMessage", null);
9018
+
9019
+ const BANTA_SDK_OPTIONS = 'BANTA_SDK_OPTIONS';
9020
+
9021
+ class ChatBackend extends ChatBackendBase {
9022
+ constructor(options) {
9023
+ super();
9024
+ this.options = options;
9025
+ }
9026
+ get serviceUrl() {
9027
+ var _a, _b;
9028
+ return `${(_b = (_a = this.options) === null || _a === void 0 ? void 0 : _a.serviceUrl) !== null && _b !== void 0 ? _b : 'http://localhost:3422'}`;
9029
+ }
9030
+ connectToService() {
9031
+ return __awaiter(this, void 0, void 0, function* () {
9032
+ let socket = new DurableSocket(`${this.serviceUrl.replace(/^http/, 'ws')}/socket`);
9033
+ yield new Promise((resolve, reject) => {
9034
+ socket.onopen = () => {
9035
+ resolve();
9036
+ };
9037
+ socket.onclose = e => {
9038
+ if (e.code === 503) {
9039
+ console.error(`Failed to connect to chat service!`);
9040
+ reject(e);
9041
+ }
9042
+ };
9043
+ });
9044
+ socket.onerror = undefined;
9045
+ return socket;
9046
+ });
9047
+ }
9048
+ getSourceForTopic(topicId, options) {
9049
+ return __awaiter(this, void 0, void 0, function* () {
9050
+ return yield new ChatSource(this, topicId, undefined, (options === null || options === void 0 ? void 0 : options.sortOrder) || CommentsOrder.NEWEST)
9051
+ .bind(yield this.connectToService());
9052
+ });
9053
+ }
9054
+ getSourceForThread(topicId, messageId, options) {
9055
+ return __awaiter(this, void 0, void 0, function* () {
9056
+ return yield new ChatSource(this, topicId, messageId, (options === null || options === void 0 ? void 0 : options.sortOrder) || CommentsOrder.NEWEST)
9057
+ .bind(yield this.connectToService());
9058
+ });
9059
+ }
9060
+ getSourceCountForTopic(topicId) {
9061
+ return __awaiter(this, void 0, void 0, function* () {
9062
+ let response = yield fetch(`${this.serviceUrl}/topics/${topicId}`);
9063
+ if (response.status >= 400)
9064
+ return 0;
9065
+ let topic = yield response.json();
9066
+ return topic.messageCount || 0;
9067
+ });
9068
+ }
9069
+ refreshMessage(message) {
9070
+ throw new Error("Method not implemented.");
9071
+ }
9072
+ getMessage(topicId, messageId) {
9073
+ throw new Error("Method not implemented.");
9074
+ }
9075
+ getSubMessage(topicId, parentMessageId, messageId) {
9076
+ throw new Error("Method not implemented.");
9077
+ }
9078
+ watchMessage(message, handler) {
9079
+ throw new Error("Method not implemented.");
9080
+ }
9081
+ }
9082
+ ChatBackend.decorators = [
9083
+ { type: Injectable }
9084
+ ];
9085
+ ChatBackend.ctorParameters = () => [
9086
+ { type: undefined, decorators: [{ type: Inject, args: [BANTA_SDK_OPTIONS,] }] }
9087
+ ];
9088
+
8642
9089
  class BantaSdkModule {
8643
- static forRoot() {
9090
+ static configure(options) {
8644
9091
  return {
8645
9092
  ngModule: BantaSdkModule,
8646
9093
  providers: [
8647
- BantaService
9094
+ {
9095
+ provide: BANTA_SDK_OPTIONS,
9096
+ useValue: options || {}
9097
+ },
9098
+ { provide: ChatBackendBase, useClass: ChatBackend }
8648
9099
  ]
8649
9100
  };
8650
9101
  }
@@ -8690,5 +9141,5 @@ BantaSdkModule.decorators = [
8690
9141
  * Generated bundle index. Do not edit.
8691
9142
  */
8692
9143
 
8693
- export { BantaChatComponent, BantaCommentsComponent, BantaCommonModule, BantaComponent, BantaLogoComponent, BantaSdkModule, BantaService, ChatBackendService, ChatMessageComponent, ChatModule, ChatViewComponent, CommentComponent, CommentFieldComponent, CommentSortComponent, CommentViewComponent, CommentsModule, EMOJIS, EmojiModule, EmojiSelectorButtonComponent, EmojiSelectorPanelComponent, LiveChatMessageComponent, LiveCommentComponent, LiveMessageComponent, TimestampComponent, lazyConnection };
9144
+ export { BANTA_SDK_OPTIONS, BantaChatComponent, BantaCommentsComponent, BantaCommonModule, BantaComponent, BantaLogoComponent, BantaSdkModule, ChatBackend, ChatBackendBase, ChatMessageComponent, ChatModule, ChatSource, ChatViewComponent, CommentComponent, CommentFieldComponent, CommentSortComponent, CommentViewComponent, CommentsModule, EMOJIS, EmojiModule, EmojiSelectorButtonComponent, EmojiSelectorPanelComponent, LiveChatMessageComponent, LiveCommentComponent, LiveMessageComponent, TimestampComponent, lazyConnection };
8694
9145
  //# sourceMappingURL=banta-sdk.js.map