@banta/sdk 3.3.9 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/banta-sdk.metadata.json +1 -1
  2. package/bundles/banta-sdk.umd.js +1142 -410
  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/banta-sdk.js +1 -1
  7. package/esm2015/lib/banta/banta.component.js +10 -16
  8. package/esm2015/lib/banta-logo.component.js +1 -1
  9. package/esm2015/lib/banta-sdk.module.js +10 -4
  10. package/esm2015/lib/chat/banta-chat/banta-chat.component.js +15 -18
  11. package/esm2015/lib/chat/chat-message/chat-message.component.js +3 -3
  12. package/esm2015/lib/chat/chat-view/chat-view.component.js +8 -7
  13. package/esm2015/lib/chat/chat.module.js +1 -1
  14. package/esm2015/lib/chat/index.js +1 -1
  15. package/esm2015/lib/chat/live-chat-message.component.js +3 -3
  16. package/esm2015/lib/chat-backend-base.js +17 -0
  17. package/esm2015/lib/chat-backend.js +74 -0
  18. package/esm2015/lib/chat-source-base.js +2 -0
  19. package/esm2015/lib/chat-source.js +151 -0
  20. package/esm2015/lib/comments/banta-comments/banta-comments.component.js +331 -172
  21. package/esm2015/lib/comments/comment/comment.component.js +56 -18
  22. package/esm2015/lib/comments/comment-field/comment-field.component.js +17 -15
  23. package/esm2015/lib/comments/comment-sort/comment-sort.component.js +1 -1
  24. package/esm2015/lib/comments/comment-view/comment-view.component.js +78 -40
  25. package/esm2015/lib/comments/comments.module.js +1 -1
  26. package/esm2015/lib/comments/index.js +1 -1
  27. package/esm2015/lib/comments/live-comment.component.js +3 -3
  28. package/esm2015/lib/common/common.module.js +1 -1
  29. package/esm2015/lib/common/index.js +1 -3
  30. package/esm2015/lib/common/lazy-connection.js +1 -1
  31. package/esm2015/lib/common/timestamp.component.js +1 -1
  32. package/esm2015/lib/emoji/emoji-selector-button.component.js +1 -1
  33. package/esm2015/lib/emoji/emoji-selector-panel/emoji-selector-panel.component.js +2 -2
  34. package/esm2015/lib/emoji/emoji.module.js +1 -1
  35. package/esm2015/lib/emoji/emojis.js +1 -1
  36. package/esm2015/lib/emoji/index.js +1 -1
  37. package/esm2015/lib/index.js +6 -1
  38. package/esm2015/lib/live-message.component.js +1 -1
  39. package/esm2015/lib/sdk-options.js +2 -0
  40. package/esm2015/public-api.js +1 -1
  41. package/fesm2015/banta-sdk.js +750 -308
  42. package/fesm2015/banta-sdk.js.map +1 -1
  43. package/lib/banta/banta.component.d.ts +8 -9
  44. package/lib/banta-sdk.module.d.ts +2 -1
  45. package/lib/chat/banta-chat/banta-chat.component.d.ts +8 -10
  46. package/lib/chat/chat-view/chat-view.component.d.ts +7 -4
  47. package/lib/chat/live-chat-message.component.d.ts +2 -2
  48. package/lib/chat-backend-base.d.ts +22 -0
  49. package/lib/chat-backend.d.ts +21 -0
  50. package/lib/chat-source-base.d.ts +31 -0
  51. package/lib/chat-source.d.ts +38 -0
  52. package/lib/comments/banta-comments/banta-comments.component.d.ts +65 -60
  53. package/lib/comments/comment/comment.component.d.ts +25 -5
  54. package/lib/comments/comment-field/comment-field.component.d.ts +7 -3
  55. package/lib/comments/comment-view/comment-view.component.d.ts +26 -8
  56. package/lib/comments/live-comment.component.d.ts +2 -2
  57. package/lib/common/index.d.ts +0 -2
  58. package/lib/index.d.ts +5 -0
  59. package/lib/sdk-options.d.ts +4 -0
  60. package/package.json +2 -2
  61. package/esm2015/lib/common/banta.service.js +0 -21
  62. package/esm2015/lib/common/chat-backend.service.js +0 -7
  63. package/lib/common/banta.service.d.ts +0 -9
  64. 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
  ];
@@ -6756,7 +6731,7 @@ EmojiSelectorPanelComponent.decorators = [
6756
6731
  { type: Component, args: [{
6757
6732
  selector: 'emoji-selector-panel',
6758
6733
  template: "<div class=\"search-box\" *ngIf=\"searchVisible\">\r\n\t<a mat-icon-button href=\"javascript:;\" (click)=\"hideSearch()\">\r\n\t\t<mat-icon>arrow_back</mat-icon>\r\n\t</a>\r\n\t<mat-form-field appearance=\"outline\" floatLabel=\"always\">\r\n\t\t<mat-label>Search for emoji</mat-label>\r\n\t\t<input name=\"search\" type=\"text\" matInput placeholder=\"Start typing\" [(ngModel)]=\"searchQuery\" />\r\n\t</mat-form-field>\r\n</div>\r\n\r\n<div class=\"selector\">\r\n\t<ng-container *ngIf=\"searchVisible\">\r\n\t\t<div class=\"emoji-list\">\r\n\t\t\t<a href=\"javascript:;\" (click)=\"select(emoji.char)\" \r\n\t\t\t\t*ngFor=\"let emoji of searchResults\" [innerHtml]=\"emoji.html || ''\">\r\n\t\t\t</a>\r\n\t\t</div>\r\n\t</ng-container>\r\n\t<ng-container *ngIf=\"!searchVisible\">\r\n\t\t<div class=\"categories\">\r\n\t\t\t<ng-container *ngIf=\"!searchVisible\">\r\n\t\t\t\t<a [title]=\"humanize(category.name)\" [class.active]=\"activeCategory === category.name\" mat-icon-button *ngFor=\"let category of categories\" (click)=\"activeCategory = category.name\">\r\n\t\t\t\t\t<mat-icon>{{category.icon}}</mat-icon>\r\n\t\t\t\t</a>\r\n\r\n\t\t\t\t<a title=\"Search\" [class.active] mat-icon-button (click)=\"showSearch()\">\r\n\t\t\t\t\t<mat-icon>search</mat-icon>\r\n\t\t\t\t</a>\r\n\t\t\t</ng-container>\r\n\t\t</div>\r\n\t\t<ng-container *ngFor=\"let category of categories\">\r\n\t\t\t<div class=\"emoji-list\" *ngIf=\"activeCategory && activeCategory == category.name\">\r\n\t\t\t\t<a href=\"javascript:;\" (click)=\"select(emoji.char)\" \r\n\t\t\t\t\t*ngFor=\"let emoji of category.emojis\" [innerHtml]=\"emoji.html || ''\">\r\n\t\t\t\t</a>\r\n\t\t\t</div>\r\n\t\t</ng-container>\r\n\t</ng-container>\r\n</div>",
6759
- styles: [":host{background:#111;border:1px solid #333;border-radius:5px;color:#fff;max-width:calc(100vw - 1.5em);padding:.5em;width:calc(288px + 9em)}.selector{display:flex;flex-direction:column}.categories a{opacity:.25;transition:opacity .4s ease-in-out}.categories a:hover{opacity:.5}.categories a.active{opacity:1}.emoji-list{flex-grow:1;height:20em;overflow-y:auto}.emoji-list a{background-color:#111;display:inline-block;margin:4px;padding:2px}.emoji-list a ::ng-deep .emoji{height:32px;width:32px}.emoji-list a:hover{background-color:#333}.search-box{align-items:baseline;display:flex}.search-box mat-form-field{flex-grow:1}@media (max-width:500px){.selector{flex-direction:row;height:27em}.emoji-list{height:auto}}"]
6734
+ styles: [":host{background:#111;color:#fff;border:1px solid #333;border-radius:5px;padding:.5em;width:calc(9*(32px + 1em));max-width:calc(100vw - 1.5em)}.selector{display:flex;flex-direction:column}.categories a{opacity:.25;transition:opacity .4s ease-in-out}.categories a:hover{opacity:.5}.categories a.active{opacity:1}.emoji-list{flex-grow:1;overflow-y:auto;height:20em}.emoji-list a{display:inline-block;padding:2px;margin:4px;background-color:#111}.emoji-list a ::ng-deep .emoji{width:32px;height:32px}.emoji-list a:hover{background-color:#333}.search-box{display:flex;align-items:baseline}.search-box mat-form-field{flex-grow:1}@media (max-width:500px){.selector{flex-direction:row;height:27em}.emoji-list{height:auto}}"]
6760
6735
  },] }
6761
6736
  ];
6762
6737
  EmojiSelectorPanelComponent.ctorParameters = () => [
@@ -6956,8 +6931,8 @@ 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>",
6960
- styles: [":host{align-items:center;background-color:#fff;color:#000;display:flex;flex-direction:row;padding:0 1em;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{align-items:center;display:flex;flex-direction:row;flex-grow:1}:host .message-content .content{display:flex;flex-direction:row;padding:5px 0}:host .message-content .content .status{align-items:center;display:flex;flex-direction:row;margin-left:1em}:host .message-content .content .status mat-icon{margin-left:.5em}:host .user{align-items:center;color:#999;display:flex;flex-shrink:0;font-weight:400;margin-right:.25em;text-align:right}:host .user .avatar{background-color:#333;background-position:50%;background-size:cover;border-radius:100%;flex-grow:0;flex-shrink:0;height:2em;margin-right:1em;width:2em}:host .user:after{content:\":\";margin-right:1em}:host .content{flex-grow:1}:host .actions{flex-shrink:0;opacity:0;transition:opacity .4s ease-out;white-space:nowrap}: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}"]
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>",
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
  ];
6963
6938
  ChatMessageComponent.propDecorators = {
@@ -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) {
@@ -7163,10 +7153,11 @@ ChatViewComponent.decorators = [
7163
7153
  { type: Component, args: [{
7164
7154
  selector: 'banta-chat-view',
7165
7155
  template: "<div class=\"message-container\" #messageContainer>\r\n <ng-content select=\"[data-before]\"></ng-content>\r\n <ng-container *ngFor=\"let message of messages\">\r\n <banta-chat-message\r\n *ngIf=\"!message.hidden\"\r\n [message]=\"message\"\r\n [class.highlight]=\"mentionsMe(message)\" \r\n [class.flash]=\"message.id === flashedMessageId\"\r\n (selected)=\"selectMessage(message)\"\r\n (upvoted)=\"upvoteMessage(message)\"\r\n (reported)=\"reportMessage(message)\"\r\n (userSelected)=\"selectMessageUser(message)\"\r\n [attr.data-id]=\"message.id\"\r\n >\r\n </banta-chat-message>\r\n </ng-container>\r\n <ng-content select=\":not([data-before])\"></ng-content>\r\n</div>",
7166
- styles: [":host{display:flex;flex-direction:column;flex-grow:1}.message-container{background:#fff;color:#000;flex-grow:1;height:10em;overflow-x:hidden;overflow-y:auto;padding:.5em 1em .5em .5em}.message-container.no-scroll{height:auto;overflow-y:visible}:host-context(.mat-dark-theme) .message-container{background:#000;color:#fff}banta-chat-message.flash{-webkit-animation:flash;-webkit-animation-duration:2.5s;-webkit-animation-iteration-count:1;animation:flash;animation-duration:2.5s;animation-iteration-count:1}@-webkit-keyframes flash{0%{background:#425700;transform:scale(1)}25%{transform:scale(1.05)}50%{transform:scale(1)}75%{transform:scale(1.05)}to{transform:scale(1)}}@keyframes flash{0%{background:#425700;transform:scale(1)}25%{transform:scale(1.05)}50%{transform:scale(1)}75%{transform:scale(1.05)}to{transform:scale(1)}}"]
7156
+ styles: [":host{display:flex;flex-direction:column;flex-grow:1}.message-container{flex-grow:1;overflow-y:auto;overflow-x:hidden;height:10em;color:#000;background:#fff;padding:.5em 1em .5em .5em}.message-container.no-scroll{height:auto;overflow-y:visible}:host-context(.mat-dark-theme) .message-container{color:#fff;background:#000}banta-chat-message.flash{-webkit-animation:flash;animation:flash;-webkit-animation-duration:2.5s;animation-duration:2.5s;-webkit-animation-iteration-count:1;animation-iteration-count:1}@-webkit-keyframes flash{0%{transform:scale(1);background:#425700}25%{transform:scale(1.05)}50%{transform:scale(1)}75%{transform:scale(1.05)}to{transform:scale(1)}}@keyframes flash{0%{transform:scale(1);background:#425700}25%{transform:scale(1.05)}50%{transform:scale(1)}75%{transform:scale(1.05)}to{transform:scale(1)}}"]
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();
@@ -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
  };
@@ -7317,13 +7307,11 @@ BantaChatComponent.decorators = [
7317
7307
  { type: Component, args: [{
7318
7308
  selector: 'banta-chat',
7319
7309
  template: "<banta-chat-view \r\n #chatView\r\n [source]=\"source\"\r\n (upvoted)=\"upvote($event)\"\r\n (reported)=\"report($event)\"\r\n (selected)=\"select($event)\"\r\n (userSelected)=\"selectUser($event)\"\r\n ></banta-chat-view>\r\n\r\n<form class=\"new-message\" (submit)=\"sendMessage()\">\r\n \r\n <div class=\"entry-container\">\r\n <input\r\n type=\"text\"\r\n name=\"message\" \r\n (keydown)=\"onKeyDown($event)\"\r\n [(ngModel)]=\"newMessage.message\" />\r\n \r\n <emoji-selector-button\r\n (selected)=\"insertEmoji($event)\"\r\n ></emoji-selector-button>\r\n </div>\r\n\r\n <div class=\"actions\">\r\n\r\n <ng-container *ngIf=\"!user\">\r\n <button type=\"button\" (click)=\"showSignIn()\" mat-raised-button color=\"primary\">{{signInLabel}}</button>\r\n </ng-container>\r\n <ng-container *ngIf=\"user\">\r\n <button *ngIf=\"canChat\" [disabled]=\"!newMessage.message\" mat-raised-button color=\"primary\">{{sendLabel}}</button>\r\n <button *ngIf=\"!canChat\" type=\"button\" (click)=\"sendPermissionError()\" mat-raised-button color=\"primary\">{{permissionDeniedLabel}}</button>\r\n </ng-container>\r\n </div>\r\n</form>",
7320
- styles: [":host{border-radius:5px;flex-direction:column;font-size:10pt;padding:10px}.entry-container,:host{display:flex;flex-grow:1}.entry-container{flex-direction:row;position:relative}.entry-container emoji-selector-button{position:absolute;right:0;top:.15em}.entry-container input{font-size:12pt;height:2.6em;padding-left:1em}.entry-container input:-webkit-autofill,.entry-container input:-webkit-autofill:focus,.entry-container input:-webkit-autofill:hover{-webkit-box-shadow:0 0 0 1000px #211e07 inset;-webkit-text-fill-color:#9da302;-webkit-transition:background-color 5000s ease-in-out 0s;caret-color:#9da302;outline:1px solid #9da302;transition:background-color 5000s ease-in-out 0s}.entry-container emoji-selector-panel{bottom:3.5em;opacity:0;pointer-events:none;position:absolute;right:0}.entry-container emoji-selector-panel.visible{opacity:1;pointer-events:auto}form{align-items:center;display:flex;padding:.5em 0}form textarea{background:#000;border:1px solid #333;color:#fff;font-size:14pt;min-height:6em;width:100%}form input[type=text]{background:#fff;border:1px solid #ccc;color:#000;height:2.5em;width:100%}form .actions{margin-left:1em}form button{display:block;margin:0 0 0 auto}:host-context(.mat-dark-theme) form input[type=text]{background:#000;border:1px solid #333;color:#fff}"]
7310
+ styles: [":host{flex-direction:column;border-radius:5px;padding:10px;font-size:10pt}.entry-container,:host{display:flex;flex-grow:1}.entry-container{flex-direction:row;position:relative}.entry-container emoji-selector-button{position:absolute;right:0;top:.15em}.entry-container input{height:2.6em;font-size:12pt;padding-left:1em}.entry-container input:-webkit-autofill,.entry-container input:-webkit-autofill:focus,.entry-container input:-webkit-autofill:hover{outline:1px solid #9da302;-webkit-text-fill-color:#9da302;-webkit-box-shadow:0 0 0 1000px #211e07 inset;-webkit-transition:background-color 5000s ease-in-out 0s;transition:background-color 5000s ease-in-out 0s;caret-color:#9da302}.entry-container emoji-selector-panel{pointer-events:none;opacity:0;position:absolute;bottom:3.5em;right:0}.entry-container emoji-selector-panel.visible{opacity:1;pointer-events:auto}form{display:flex;padding:.5em 0;align-items:center}form textarea{font-size:14pt;background:#000;color:#fff;border:1px solid #333;min-height:6em;width:100%}form input[type=text]{background:#fff;color:#000;border:1px solid #ccc;width:100%;height:2.5em}form .actions{margin-left:1em}form button{display:block;margin:0 0 0 auto}:host-context(.mat-dark-theme) form input[type=text]{background:#000;color:#fff;border:1px solid #333}"]
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>",
7541
- styles: [":host{display:flex;flex-direction:row;height:40em;padding:.5em;position:relative}.counted-action{align-items:center;display:flex}.count-indicator{border:1px solid #333;border-radius:3px;font-size:9pt;padding:0 3px}header{margin-bottom:1em;position:relative}header div{align-items:center;display:flex;height:30px}header button{color:#666}header label{color:#333;font-size:12pt;font-weight:100;letter-spacing:2px;margin:0 auto 0 0;overflow-x:hidden;text-overflow:ellipsis;text-transform:uppercase;white-space:nowrap;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;z-index:1}header:after,header label{display:block;position:relative}header:after{border:1px solid #ccc;content:\"\";height:0;width:100%;z-index:0}.points{display:flex;flex-direction:column;max-width:50em}:host.point-focus .points{max-width:50em;width:66%}: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{flex-shrink:0;font-size:12pt;margin-left:.5em;max-width:30em;position:relative;transition:width .2s ease-in,max-width .2s ease-in;width:33%}.points .points-section{opacity:1;z-index:2}.points .point-focus,.points .points-section{display:flex;flex-direction:column;flex-grow:1;transition:opacity .2s ease-in}.points .point-focus{bottom:0;left:0;opacity:0;padding:.5em;position:absolute;right:0;top:1.75em;width:100%}.firehose{display:flex;flex-direction:column;flex-grow:1;font-size:10pt}form{align-items:center;display:flex;padding:.5em 0}form textarea{font-size:14pt;min-height:6em}form input[type=text],form textarea{background:#000;border:1px solid #333;color:#fff;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{display:flex;flex-direction:column;min-width:0;overflow-x:hidden;transition:width .4s ease-out,min-width .4s ease-out;width:0}.aux.open{min-width:18em;width:30em}.aux .aux-contents{align-items:center;display:flex;flex-direction:column;flex-grow:1;justify-content:center;max-width:100%;min-width:10em;width:30em}.notifications .notification{border-bottom:1px solid #333;padding:1em}.notifications .notification banta-timestamp{color:#999;display:block;font-size:9pt;text-align:right}.message.reply{padding:1em}.tabs{display:none}@media (max-width:1015px){:host{flex-direction:column}.tabs{-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);background:rgba(0,0,0,.5);display:flex;left:0;position:absolute;right:0;top:0;width:100%;z-index:10}.points{margin-left:0;max-width:100%;width:100%}header{display:none}.aux,:host.point-focus .points{max-width:100%;width:100%}.aux{min-width:0}.aux,.firehose,.points{background:#000;bottom:0;left:0;position:absolute;right:0;top:2em;z-index:0}.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}"]
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>",
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,6 +7642,12 @@ class CommentComponent {
7658
7642
  this.isNew = false;
7659
7643
  this.visible = false;
7660
7644
  this.showReplyAction = true;
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();
7661
7651
  }
7662
7652
  ngOnInit() {
7663
7653
  let maxTime = 500;
@@ -7669,6 +7659,10 @@ class CommentComponent {
7669
7659
  setTimeout(() => this.isNew = false, 1000);
7670
7660
  }, randomTime);
7671
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
+ }
7672
7666
  get userSelected() {
7673
7667
  return this._userSelected.asObservable();
7674
7668
  }
@@ -7681,24 +7675,42 @@ class CommentComponent {
7681
7675
  get reported() {
7682
7676
  return this._reported.asObservable();
7683
7677
  }
7684
- get upvoted() {
7685
- return this._upvoted.asObservable();
7678
+ saveEdit() {
7679
+ this._edited.next(this.editedMessage);
7686
7680
  }
7687
- get selected() {
7688
- return this._selected.asObservable();
7681
+ endEditing() {
7682
+ this._editEnded.next();
7689
7683
  }
7690
- get commentId() {
7691
- var _a;
7692
- 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();
7693
7690
  }
7694
- get shared() {
7695
- return this._shared.asObservable();
7691
+ get liked() {
7692
+ return this._liked.asObservable();
7696
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; }
7697
7706
  report() {
7698
7707
  this._reported.next();
7699
7708
  }
7700
- upvote() {
7701
- this._upvoted.next();
7709
+ like() {
7710
+ this._liked.next();
7711
+ }
7712
+ unlike() {
7713
+ this._unliked.next();
7702
7714
  }
7703
7715
  share() {
7704
7716
  this._shared.next(this.message);
@@ -7728,12 +7740,13 @@ class CommentComponent {
7728
7740
  CommentComponent.decorators = [
7729
7741
  { type: Component, args: [{
7730
7742
  selector: 'banta-comment',
7731
- 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 || 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=\"Upvote\" matTooltipPosition=\"below\" (click)=\"upvote()\">\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",
7732
- 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;padding:.5em;position:relative;visibility:hidden}:host.new{-webkit-animation-duration:.4s;-webkit-animation-fill-mode:both;-webkit-animation-name:comment-appear;animation-duration:.4s;animation-fill-mode:both;animation-name:comment-appear}: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;overflow-y:hidden;text-overflow:ellipsis}:host .actions{align-items:center;display:flex;margin-left:60px;padding-right:10px}:host .actions button{color:#666}:host .actions banta-timestamp{color:#666;font-size:10pt}.user{align-items:center;display:flex;margin:1em 0 0;position:relative}.user .display-name,.user .username{color:#000;display:block;flex-grow:0;flex-shrink:1;font-size:10pt;margin:0 auto 0 0;max-width:100%;overflow:hidden;padding:0 0 0 1em;position:relative;text-overflow:ellipsis;white-space:nowrap;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;z-index:1}.user .display-name.username,.user .username.username{color:#666;flex-grow:1;flex-shrink:0}.avatar{background-color:#333;background-position:50%;background-size:cover;border-radius:100%;flex-grow:0;flex-shrink:0;height:48px;width:48px}.counted-action{align-items:center;display:flex}.count-indicator{color:#666;font-size:9pt;padding:0 0 0 3px}: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}"]
7733
7745
  },] }
7734
7746
  ];
7735
7747
  CommentComponent.propDecorators = {
7736
7748
  isNew: [{ type: HostBinding, args: ['class.new',] }],
7749
+ isHighlighted: [{ type: HostBinding, args: ['class.highlighted',] }],
7737
7750
  visible: [{ type: HostBinding, args: ['class.visible',] }],
7738
7751
  message: [{ type: Input }],
7739
7752
  showReplyAction: [{ type: Input }],
@@ -7741,10 +7754,18 @@ CommentComponent.propDecorators = {
7741
7754
  usernameSelected: [{ type: Output }],
7742
7755
  avatarSelected: [{ type: Output }],
7743
7756
  reported: [{ type: Output }],
7744
- upvoted: [{ type: Output }],
7757
+ permissions: [{ type: Input }],
7758
+ mine: [{ type: Input }],
7759
+ editing: [{ type: Input }],
7760
+ liked: [{ type: Output }],
7761
+ unliked: [{ type: Output }],
7745
7762
  selected: [{ type: Output }],
7746
- commentId: [{ type: HostBinding, args: ['attr.data-comment-id',] }],
7747
- 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',] }]
7748
7769
  };
7749
7770
 
7750
7771
  class CommentViewComponent {
@@ -7752,16 +7773,20 @@ class CommentViewComponent {
7752
7773
  this.backend = backend;
7753
7774
  this._sourceSubs = new Subscription();
7754
7775
  this._selected = new Subject();
7755
- this._upvoted = new Subject();
7776
+ this._liked = new Subject();
7777
+ this._unliked = new Subject();
7756
7778
  this._reported = new Subject();
7757
7779
  this._userSelected = new Subject();
7758
7780
  this._usernameSelected = new Subject();
7759
7781
  this._avatarSelected = new Subject();
7760
7782
  this._shared = new Subject();
7783
+ this._deleted = new Subject();
7784
+ this._messageEdited = new Subject();
7761
7785
  this.showEmptyState = true;
7762
7786
  this.allowReplies = true;
7763
7787
  this.menuMessage = null;
7764
7788
  this.messages = [];
7789
+ this.customSortEnabled = false;
7765
7790
  this.maxMessages = 2000;
7766
7791
  this.maxVisibleMessages = 200;
7767
7792
  this.newestLast = false;
@@ -7770,33 +7795,33 @@ class CommentViewComponent {
7770
7795
  this.hasMore = false;
7771
7796
  this.newMessages = [];
7772
7797
  this.olderMessages = [];
7798
+ this.sortOrderChanged = new Subject();
7773
7799
  }
7774
7800
  get selected() {
7775
7801
  return this._selected;
7776
7802
  }
7777
- get userSelected() {
7778
- return this._userSelected;
7803
+ get messageEdited() {
7804
+ return this._messageEdited.asObservable();
7779
7805
  }
7780
- get reported() {
7781
- return this._reported;
7782
- }
7783
- get upvoted() {
7784
- return this._upvoted;
7785
- }
7786
- get usernameSelected() {
7787
- return this._usernameSelected;
7788
- }
7789
- get avatarSelected() {
7790
- return this._avatarSelected;
7791
- }
7792
- get shared() {
7793
- return this._shared;
7806
+ saveEdit(message, newMessage) {
7807
+ this._messageEdited.next({ message, newMessage });
7794
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; }
7795
7817
  get source() {
7796
7818
  return this._source;
7797
7819
  }
7798
- upvoteMessage(message) {
7799
- this._upvoted.next(message);
7820
+ likeMessage(message) {
7821
+ this._liked.next(message);
7822
+ }
7823
+ unlikeMessage(message) {
7824
+ this._unliked.next(message);
7800
7825
  }
7801
7826
  reportMessage(message) {
7802
7827
  this._reported.next(message);
@@ -7816,31 +7841,51 @@ class CommentViewComponent {
7816
7841
  sharedMessage(message) {
7817
7842
  this._shared.next(message);
7818
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
+ }
7819
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;
7820
7856
  if (this._sourceSubs) {
7821
7857
  this._sourceSubs.unsubscribe();
7822
7858
  this._sourceSubs = null;
7823
7859
  }
7824
7860
  this._source = value;
7825
7861
  if (value) {
7826
- console.log(`[banta-comment-view] Subscribing to source...`);
7827
7862
  const messages = (value.messages || []).slice();
7828
7863
  this.messages = messages;
7829
7864
  this.olderMessages = messages.splice(this.maxVisibleMessages, messages.length);
7830
- this.hasMore = this.olderMessages.length > 0;
7865
+ this.hasMore = true; //this.olderMessages.length > 0;
7831
7866
  this._sourceSubs = new Subscription();
7832
7867
  this._sourceSubs.add(this._source.messageReceived.subscribe(msg => this.messageReceived(msg)));
7833
7868
  this._sourceSubs.add(this._source.messageSent.subscribe(msg => this.messageSent(msg)));
7834
- if (this._source.currentUserChanged) {
7835
- this._sourceSubs.add(this._source.currentUserChanged.subscribe(user => this.currentUser = user));
7836
- }
7869
+ this._sourceSubs.add(this.backend.userChanged.subscribe(user => this.currentUser = user));
7870
+ this.getInitialMessages();
7837
7871
  }
7838
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
+ }
7839
7880
  messageIdentity(index, chatMessage) {
7840
7881
  return chatMessage.id;
7841
7882
  }
7842
7883
  showNew() {
7843
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
+ }
7844
7889
  this.isViewingMore = false;
7845
7890
  this.messages = this.newMessages.splice(0, this.newMessages.length).concat(this.messages);
7846
7891
  let overflow = this.messages.splice(this.maxVisibleMessages, this.messages.length);
@@ -7856,29 +7901,38 @@ class CommentViewComponent {
7856
7901
  this.messages = this.messages.concat(this.olderMessages.splice(0, 50));
7857
7902
  }
7858
7903
  else {
7859
- if (this.source.loadAfter) {
7860
- this.isLoadingMore = true;
7861
- let lastMessage = this.messages[this.messages.length - 1];
7862
- let messages = yield this.source.loadAfter(lastMessage, 100);
7863
- 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) {
7864
7909
  this.isLoadingMore = false;
7865
- if (messages.length === 0)
7866
- this.hasMore = false;
7910
+ this.hasMore = false;
7911
+ return;
7867
7912
  }
7868
- 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.`);
7869
7919
  this.hasMore = false;
7870
7920
  }
7871
7921
  }
7872
7922
  });
7873
7923
  }
7874
7924
  addMessage(message) {
7925
+ var _a;
7926
+ if (!message.transientState)
7927
+ (_a = message.transientState) !== null && _a !== void 0 ? _a : (message.transientState = {});
7875
7928
  let destination = this.messages;
7876
7929
  let bucket = this.olderMessages;
7877
7930
  if (this.isViewingMore) {
7878
7931
  destination = this.newMessages;
7879
7932
  bucket = null;
7880
7933
  }
7881
- if (this.newestLast) {
7934
+ let newestLast = this.newestLast;
7935
+ if (newestLast) {
7882
7936
  destination.push(message);
7883
7937
  let overflow = destination.splice(this.maxVisibleMessages, destination.length);
7884
7938
  bucket === null || bucket === void 0 ? void 0 : bucket.push(...overflow);
@@ -7933,41 +7987,65 @@ class CommentViewComponent {
7933
7987
  CommentViewComponent.decorators = [
7934
7988
  { type: Component, args: [{
7935
7989
  selector: 'banta-comment-view',
7936
- 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 (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",
7937
- styles: [":host{display:flex;flex-direction:column;flex-grow:1;opacity:1;transition:opacity .2s ease-in}.message-container{background:#fff;color:#111;flex-grow:1;opacity:1;overflow-x:hidden;padding:.5em 1em 3em .5em;position:relative;transition:opacity .5s ease-in-out}.message-container.no-scroll{height:auto;overflow-y:visible}.message-container.faded{opacity:.25}.message-container .overlay{bottom:0;left:0;position:absolute;right:0;top:0;z-index:10}:host.fixed-height .message-container{overflow-y:auto}:host-context(.mat-dark-theme) .message-container{background:#111;color:#fff}.empty-state{color:#666;margin:3em;text-align:center}:host-context(.mat-dark-theme) .empty-state{color:#666}a.nav{background:#222;border-radius:2em;opacity:0;pointer-events:none;position:absolute;right:.5em;text-align:center;transition:opacity .4s ease-in-out;z-index:10}a.nav.visible{opacity:1;pointer-events:auto}.loading-more{margin:0 auto;padding:2em;text-align:center;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content}@media (max-width:400px){.message-container{padding:0 0 3em}}"]
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",
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}}"]
7938
7992
  },] }
7939
7993
  ];
7940
7994
  CommentViewComponent.ctorParameters = () => [
7941
- { type: ChatBackendService }
7995
+ { type: ChatBackendBase }
7942
7996
  ];
7943
7997
  CommentViewComponent.propDecorators = {
7944
7998
  showEmptyState: [{ type: Input }],
7945
7999
  allowReplies: [{ type: Input }],
7946
8000
  fixedHeight: [{ type: Input }, { type: HostBinding, args: ['class.fixed-height',] }],
7947
8001
  selected: [{ type: Output }],
8002
+ messageEdited: [{ type: Output }],
7948
8003
  userSelected: [{ type: Output }],
7949
8004
  reported: [{ type: Output }],
7950
- upvoted: [{ type: Output }],
8005
+ liked: [{ type: Output }],
8006
+ unliked: [{ type: Output }],
7951
8007
  usernameSelected: [{ type: Output }],
7952
8008
  avatarSelected: [{ type: Output }],
7953
8009
  shared: [{ type: Output }],
8010
+ deleted: [{ type: Output }],
7954
8011
  source: [{ type: Input }],
7955
8012
  genericAvatarUrl: [{ type: Input }],
7956
8013
  messageContainer: [{ type: ViewChild, args: ['messageContainer',] }],
7957
8014
  maxMessages: [{ type: Input }],
7958
8015
  maxVisibleMessages: [{ type: Input }],
7959
- newestLast: [{ type: Input }]
8016
+ newestLast: [{ type: Input }],
8017
+ sortOrderChanged: [{ type: Output }]
7960
8018
  };
7961
8019
 
7962
8020
  /**
7963
8021
  * Comments component
7964
8022
  */
7965
8023
  class BantaCommentsComponent {
7966
- constructor(banta, backend, elementRef, activatedRoute) {
7967
- this.banta = banta;
8024
+ constructor(backend, elementRef, activatedRoute) {
7968
8025
  this.backend = backend;
7969
8026
  this.elementRef = elementRef;
7970
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();
7971
8049
  this._upvoted = new Subject();
7972
8050
  this._reported = new Subject();
7973
8051
  this._selected = new Subject();
@@ -7977,12 +8055,8 @@ class BantaCommentsComponent {
7977
8055
  this._avatarSelected = new Subject();
7978
8056
  this._subs = new Subscription();
7979
8057
  this._sortOrder = CommentsOrder.NEWEST;
7980
- this.hashtags = [
7981
- { hashtag: 'error', description: 'Cause an error' },
7982
- { hashtag: 'timeout', description: 'Cause a slow timeout error' },
7983
- { hashtag: 'slow', description: 'Be slow when this message is posted' },
7984
- ];
7985
- this.participants = [];
8058
+ this.selectedMessageVisible = false;
8059
+ // Inputs
7986
8060
  this.signInLabel = 'Sign In';
7987
8061
  this.sendLabel = 'Send';
7988
8062
  this.replyLabel = 'Reply';
@@ -7990,16 +8064,166 @@ class BantaCommentsComponent {
7990
8064
  this.permissionDeniedLabel = 'Send';
7991
8065
  this.postCommentLabel = 'Post a comment';
7992
8066
  this.postReplyLabel = 'Post a reply';
7993
- this._signInSelected = new Subject();
7994
- this._permissionDeniedError = new Subject();
7995
- this._editAvatarSelected = new Subject();
7996
- this.sending = false;
7997
- this.expandError = false;
7998
- 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
+ console.error(`Failed to send message: `, message);
8087
+ console.error(e);
8088
+ throw new Error(`Could not send: ${e.message}`);
8089
+ }
8090
+ });
8091
+ this.sendReply = (message) => __awaiter(this, void 0, void 0, function* () {
8092
+ var _b;
8093
+ try {
8094
+ const intercept = yield ((_b = this.shouldInterceptMessageSend) === null || _b === void 0 ? void 0 : _b.call(this, message, this.source));
8095
+ if (!intercept) {
8096
+ yield this.selectedMessageThread.send(message);
8097
+ }
8098
+ return true;
8099
+ }
8100
+ catch (e) {
8101
+ console.error(`Failed to send message: `, message);
8102
+ console.error(e);
8103
+ throw new Error(`Could not send reply: ${e.message}`);
8104
+ }
8105
+ });
8106
+ }
8107
+ // Lifecycle Events / Initialization
8108
+ ngOnInit() {
8109
+ this._subs.add(this.backend.userChanged.subscribe(user => this.user = user));
8110
+ this.startLoading();
8111
+ console.log(`Checking...`);
8112
+ if (typeof window !== 'undefined') {
8113
+ let queryString = window.location.search.substring(1);
8114
+ let query = queryString.split('&')
8115
+ .map(s => s.split('='))
8116
+ .reduce((o, [k, v]) => (o[k] = v, o), {});
8117
+ console.log('here:');
8118
+ console.dir(query);
8119
+ const commentID = query['comment'];
8120
+ if (commentID) {
8121
+ this.sharedCommentID = commentID;
8122
+ }
8123
+ }
7999
8124
  }
8000
- get sortOrder() {
8001
- return this._sortOrder;
8125
+ ngOnDestroy() {
8126
+ this._subs.unsubscribe();
8127
+ }
8128
+ setSourceFromTopicID(topicID) {
8129
+ return __awaiter(this, void 0, void 0, function* () {
8130
+ if (this._source) {
8131
+ this._source.close();
8132
+ this._source = null;
8133
+ }
8134
+ setTimeout(() => __awaiter(this, void 0, void 0, function* () {
8135
+ console.log(`[banta-comments] Subscribing to topic source '${topicID}'`);
8136
+ this._source = yield this.backend.getSourceForTopic(topicID, { sortOrder: this.sortOrder });
8137
+ if (this.sharedCommentID) {
8138
+ this.navigateToSharedComment(this.sharedCommentID);
8139
+ this.sharedCommentID = null;
8140
+ }
8141
+ this._source.messageReceived.subscribe(m => this.addParticipant(m));
8142
+ this._source.messageSent.subscribe(m => this.addParticipant(m));
8143
+ this._source.messages.forEach(m => this.addParticipant(m));
8144
+ }));
8145
+ });
8002
8146
  }
8147
+ get loadingMessage() {
8148
+ return this._loadingMessage;
8149
+ }
8150
+ set loadingMessage(value) {
8151
+ this.loadingMessageVisible = false;
8152
+ setTimeout(() => {
8153
+ this._loadingMessage = value;
8154
+ this._loadingMessage = value;
8155
+ setTimeout(() => {
8156
+ this.loadingMessageVisible = true;
8157
+ });
8158
+ }, 500);
8159
+ }
8160
+ startLoading() {
8161
+ return __awaiter(this, void 0, void 0, function* () {
8162
+ this.loadingStartedAt = this.messageChangedAt = Date.now();
8163
+ if (this.updateLoading())
8164
+ return;
8165
+ yield new Promise(resolve => setTimeout(() => resolve(), 100));
8166
+ if (this.updateLoading())
8167
+ return;
8168
+ yield new Promise(resolve => setTimeout(() => resolve(), 250));
8169
+ if (this.updateLoading())
8170
+ return;
8171
+ yield new Promise(resolve => setTimeout(() => resolve(), 500));
8172
+ if (this.updateLoading())
8173
+ return;
8174
+ console.log(`[Banta] Loading is taking a long time! Showing loading screen.`);
8175
+ this.showLoadingScreen = true;
8176
+ if (typeof window !== 'undefined')
8177
+ this._loadingTimer = setInterval(() => this.updateLoading(), 1000);
8178
+ });
8179
+ }
8180
+ updateLoading() {
8181
+ var _a, _b, _c;
8182
+ if (((_a = this.source) === null || _a === void 0 ? void 0 : _a.state) && ((_b = this.source) === null || _b === void 0 ? void 0 : _b.state) !== 'connecting') {
8183
+ clearInterval(this._loadingTimer);
8184
+ this.loadingMessage = `Here we go!`;
8185
+ setTimeout(() => {
8186
+ this.loading = false;
8187
+ }, 750);
8188
+ return true;
8189
+ }
8190
+ console.log(`[Banta] Status check: ${((_c = this.source) === null || _c === void 0 ? void 0 : _c.state) || 'connecting'}`);
8191
+ let messageSwitchTime = 5 * 1000;
8192
+ if (this.messageChangedAt + messageSwitchTime < Date.now()) {
8193
+ if (this.loadingMessages[this._loadingMessageIndex]) {
8194
+ this.loadingMessage = this.loadingMessages[this._loadingMessageIndex++];
8195
+ this.messageChangedAt = Date.now();
8196
+ }
8197
+ }
8198
+ return false;
8199
+ }
8200
+ get source() { return this._source; }
8201
+ set source(value) {
8202
+ this._source = value;
8203
+ if (value && this.sharedCommentID) {
8204
+ this.navigateToSharedComment(this.sharedCommentID);
8205
+ this.sharedCommentID = null;
8206
+ }
8207
+ }
8208
+ get topicID() { return this._topicID; }
8209
+ set topicID(value) {
8210
+ if (this._topicID !== value) {
8211
+ this._topicID = value;
8212
+ setTimeout(() => this.setSourceFromTopicID(value));
8213
+ }
8214
+ }
8215
+ // Outputs
8216
+ get signInSelected() { return this._signInSelected; }
8217
+ get editAvatarSelected() { return this._editAvatarSelected; }
8218
+ get permissionDeniedError() { return this._permissionDeniedError; }
8219
+ get upvoted() { return this._upvoted.asObservable(); }
8220
+ get reported() { return this._reported.asObservable(); }
8221
+ get selected() { return this._selected.asObservable(); }
8222
+ get userSelected() { return this._userSelected.asObservable(); }
8223
+ get usernameSelected() { return this._usernameSelected.asObservable(); }
8224
+ get avatarSelected() { return this._avatarSelected.asObservable(); }
8225
+ get shared() { return this._shared.asObservable(); }
8226
+ get sortOrder() { return this._sortOrder; }
8003
8227
  set sortOrder(value) {
8004
8228
  if (this._sortOrder !== value) {
8005
8229
  this._sortOrder = value;
@@ -8008,60 +8232,62 @@ class BantaCommentsComponent {
8008
8232
  });
8009
8233
  }
8010
8234
  }
8011
- ngOnInit() {
8012
- this._subs.add(this.banta.userChanged.subscribe(user => this.user = user));
8013
- }
8014
- ngAfterViewInit() {
8015
- if (typeof window !== 'undefined')
8016
- this.checkForSharedComment();
8017
- }
8235
+ // UI Interactions
8018
8236
  scrollToComment(commentId) {
8019
8237
  setTimeout(() => {
8020
8238
  const comment = document.querySelectorAll(`[data-comment-id="${commentId}"]`);
8021
- console.log(comment);
8239
+ console.dir(comment);
8022
8240
  if (comment.length > 0) {
8023
8241
  // comment.item(0).scroll({behavior: 'smooth'});
8024
8242
  comment.item(0).scrollIntoView();
8025
8243
  }
8026
8244
  }, 1000);
8027
8245
  }
8028
- checkForSharedComment() {
8029
- const commentID = this.activatedRoute.snapshot.queryParamMap.get('comment');
8030
- if (commentID)
8031
- this.scrollToComment(commentID);
8032
- }
8033
- ngOnDestroy() {
8034
- this._subs.unsubscribe();
8035
- }
8036
- get source() {
8037
- return this._source;
8038
- }
8039
- set source(value) {
8040
- this._source = value;
8041
- }
8042
- get topicID() {
8043
- return this._topicID;
8044
- }
8045
- set topicID(value) {
8046
- if (this._topicID !== value) {
8047
- this._topicID = value;
8048
- setTimeout(() => this.setSourceFromTopicID(value));
8049
- }
8050
- }
8051
- setSourceFromTopicID(topicID) {
8052
- var _a, _b;
8246
+ navigateToSharedComment(id) {
8247
+ var _a, _b, _c;
8053
8248
  return __awaiter(this, void 0, void 0, function* () {
8054
- (_b = (_a = this._source) === null || _a === void 0 ? void 0 : _a.close) === null || _b === void 0 ? void 0 : _b.call(_a);
8055
- this._source = null;
8056
- setTimeout(() => __awaiter(this, void 0, void 0, function* () {
8057
- this._source = yield this.backend.getSourceForTopic(topicID, { sortOrder: this.sortOrder });
8058
- console.log(`[banta-comments] Subscribing to source for topic '${topicID}'`);
8059
- this._source.messageReceived.subscribe(m => this.addParticipant(m));
8060
- this._source.messageSent.subscribe(m => this.addParticipant(m));
8061
- this._source.messages.forEach(m => this.addParticipant(m));
8062
- }));
8249
+ let source = this.source;
8250
+ yield source.ready;
8251
+ console.log(`Navigating to shared comment with ID '${id}'...`);
8252
+ let message;
8253
+ try {
8254
+ message = yield this.source.get(id);
8255
+ }
8256
+ catch (e) {
8257
+ console.error(`Failed to find comment from URL: ${e.message}`);
8258
+ return;
8259
+ }
8260
+ (_a = message.transientState) !== null && _a !== void 0 ? _a : (message.transientState = {});
8261
+ // If there is a parent message, we should instead focus that and let the
8262
+ // scrollToComment and highlight do the work.
8263
+ if (message.parentMessageId) {
8264
+ let parentMessage = yield this.source.get(message.parentMessageId);
8265
+ (_b = parentMessage.transientState) !== null && _b !== void 0 ? _b : (parentMessage.transientState = {});
8266
+ let thread = yield this.selectMessage(parentMessage);
8267
+ // Need to re-retrieve the message within the new chat source to affect its
8268
+ // transient state.
8269
+ yield thread.ready;
8270
+ message = yield thread.get(message.id);
8271
+ (_c = message.transientState) !== null && _c !== void 0 ? _c : (message.transientState = {});
8272
+ message.transientState.highlighted = true;
8273
+ console.dir(message);
8274
+ yield new Promise(resolve => setTimeout(() => resolve(), 500));
8275
+ }
8276
+ else {
8277
+ this.selectMessage(message);
8278
+ }
8279
+ this.scrollToComment(id);
8063
8280
  });
8064
8281
  }
8282
+ showPermissionDenied() {
8283
+ this._permissionDeniedError.next();
8284
+ }
8285
+ scrollToMessage(message) {
8286
+ let el = this.elementRef.nativeElement.querySelector(`[data-comment-id="${message.id}"]`);
8287
+ if (!el)
8288
+ return;
8289
+ el.scrollIntoView({ block: 'center', inline: 'start' });
8290
+ }
8065
8291
  addParticipant(message) {
8066
8292
  if (!message || !message.user || !message.user.id)
8067
8293
  return;
@@ -8070,90 +8296,46 @@ class BantaCommentsComponent {
8070
8296
  return;
8071
8297
  this.participants.push(message.user);
8072
8298
  }
8073
- showSignIn() {
8074
- this._signInSelected.next();
8075
- }
8076
- showEditAvatar() {
8077
- this._editAvatarSelected.next();
8078
- }
8079
- get newMessageText() {
8080
- return this._newMessageText;
8081
- }
8082
- set newMessageText(value) {
8083
- this._newMessageText = value;
8084
- if (this._newMessageText === '' && this.sendError)
8085
- setTimeout(() => this.sendError = null);
8086
- }
8087
- get signInSelected() {
8088
- return this._signInSelected;
8089
- }
8090
- get editAvatarSelected() {
8091
- return this._editAvatarSelected;
8092
- }
8093
- get permissionDeniedError() {
8094
- return this._permissionDeniedError;
8095
- }
8096
- showPermissionDenied() {
8097
- this._permissionDeniedError.next();
8098
- }
8099
- get canComment() {
8299
+ // Actions
8300
+ likeMessage(source, message) {
8100
8301
  var _a;
8101
- if (!this.user)
8102
- return false;
8103
- if (!this.user.permissions)
8104
- return true;
8105
- if (!this.user.permissions.canComment)
8106
- return true;
8107
- return (_a = this.user.permissions) === null || _a === void 0 ? void 0 : _a.canComment(this.source);
8108
- }
8109
- get upvoted() {
8110
- return this._upvoted.asObservable();
8111
- }
8112
- get reported() {
8113
- return this._reported.asObservable();
8114
- }
8115
- get selected() {
8116
- return this._selected.asObservable();
8117
- }
8118
- get userSelected() {
8119
- return this._userSelected.asObservable();
8120
- }
8121
- get usernameSelected() {
8122
- return this._usernameSelected.asObservable();
8123
- }
8124
- get avatarSelected() {
8125
- return this._avatarSelected.asObservable();
8126
- }
8127
- get shared() {
8128
- return this._shared.asObservable();
8129
- }
8130
- onKeyDown(event) {
8131
- }
8132
- insertEmoji(text) {
8133
- this.newMessageText += text;
8134
- }
8135
- onReplyKeyDown(event) {
8136
- }
8137
- insertReplyEmoji(text) {
8138
- this.replyMessage += text;
8139
- }
8140
- indicateError(message) {
8141
- this.sendError = new Error(message);
8142
- setTimeout(() => {
8143
- this.expandError = true;
8144
- setTimeout(() => {
8145
- this.expandError = false;
8146
- }, 5 * 1000);
8302
+ return __awaiter(this, void 0, void 0, function* () {
8303
+ this._upvoted.next(message);
8304
+ message.transientState.liking = true;
8305
+ if (!((_a = message.userState) === null || _a === void 0 ? void 0 : _a.liked))
8306
+ message.likes = (message.likes || 0) + 1;
8307
+ try {
8308
+ yield source.likeMessage(message.id);
8309
+ }
8310
+ catch (e) {
8311
+ alert(`Could not like this message: ${e.message}`);
8312
+ return;
8313
+ }
8314
+ yield new Promise(resolve => setTimeout(() => resolve(), 250));
8315
+ message.transientState.liking = false;
8147
8316
  });
8148
8317
  }
8149
- upvoteMessage(message) {
8318
+ unlikeMessage(source, message) {
8319
+ var _a;
8150
8320
  return __awaiter(this, void 0, void 0, function* () {
8151
8321
  this._upvoted.next(message);
8152
- yield this.backend.upvoteMessage(message.topicId, message.parentMessageId ? message.parentMessageId : message.id, message.parentMessageId ? message.id : undefined);
8322
+ message.transientState.liking = true;
8323
+ if ((_a = message.userState) === null || _a === void 0 ? void 0 : _a.liked)
8324
+ message.likes = (message.likes || 0) - 1;
8325
+ try {
8326
+ yield source.unlikeMessage(message.id);
8327
+ }
8328
+ catch (e) {
8329
+ alert(`Failed to unlike message: ${e.message}`);
8330
+ }
8331
+ yield new Promise(resolve => setTimeout(() => resolve(), 250));
8332
+ message.transientState.liking = false;
8153
8333
  });
8154
8334
  }
8155
8335
  reportMessage(message) {
8156
- this._reported.next(message);
8336
+ return __awaiter(this, void 0, void 0, function* () {
8337
+ this._reported.next(message);
8338
+ });
8157
8339
  }
8158
8340
  unselectMessage() {
8159
8341
  return __awaiter(this, void 0, void 0, function* () {
@@ -8173,69 +8355,96 @@ class BantaCommentsComponent {
8173
8355
  return __awaiter(this, void 0, void 0, function* () {
8174
8356
  this._selected.next(message);
8175
8357
  this.selectedMessage = message;
8358
+ let selectedMessageThread = yield this.backend.getSourceForThread(this.topicID, message.id);
8176
8359
  setTimeout(() => this.selectedMessageVisible = true);
8177
8360
  setTimeout(() => __awaiter(this, void 0, void 0, function* () {
8178
- this.selectedMessageThread = yield this.backend.getSourceForThread(this.topicID, message.id);
8361
+ this.selectedMessageThread = selectedMessageThread;
8179
8362
  }), 250);
8363
+ return selectedMessageThread;
8364
+ });
8365
+ }
8366
+ showSignIn() {
8367
+ return __awaiter(this, void 0, void 0, function* () {
8368
+ this._signInSelected.next();
8369
+ });
8370
+ }
8371
+ showEditAvatar() {
8372
+ return __awaiter(this, void 0, void 0, function* () {
8373
+ this._editAvatarSelected.next();
8180
8374
  });
8181
8375
  }
8182
8376
  selectMessageUser(message) {
8183
- this._userSelected.next(message);
8377
+ return __awaiter(this, void 0, void 0, function* () {
8378
+ this._userSelected.next(message);
8379
+ });
8184
8380
  }
8185
8381
  selectUsername(user) {
8186
- this._usernameSelected.next(user);
8382
+ return __awaiter(this, void 0, void 0, function* () {
8383
+ this._usernameSelected.next(user);
8384
+ });
8187
8385
  }
8188
8386
  selectAvatar(user) {
8189
- this._avatarSelected.next(user);
8387
+ return __awaiter(this, void 0, void 0, function* () {
8388
+ this._avatarSelected.next(user);
8389
+ });
8190
8390
  }
8191
8391
  shareMessage(message) {
8192
- this._shared.next(message);
8392
+ return __awaiter(this, void 0, void 0, function* () {
8393
+ this._shared.next(message);
8394
+ });
8193
8395
  }
8194
- sendReply() {
8396
+ deleteMessage(message) {
8195
8397
  return __awaiter(this, void 0, void 0, function* () {
8196
- yield this.selectedMessageThread.send({
8197
- message: this.replyMessage,
8198
- parentMessageId: this.selectedMessage.id,
8199
- upvotes: 0,
8200
- user: this.user,
8201
- submessages: [],
8202
- topicId: this.topicID,
8203
- sentAt: Date.now(),
8204
- updatedAt: Date.now()
8205
- });
8206
- this.replyMessage = '';
8398
+ if (!confirm("Are you sure you want to delete this comment? You cannot undo this action."))
8399
+ return;
8400
+ this.source.deleteMessage(message.id);
8207
8401
  });
8208
8402
  }
8209
- scrollToMessage(message) {
8210
- let el = this.elementRef.nativeElement.querySelector(`[data-comment-id="${message.id}"]`);
8211
- if (!el)
8212
- return;
8213
- el.scrollIntoView({ block: 'center', inline: 'start' });
8403
+ editMessage(source, message, newText) {
8404
+ return __awaiter(this, void 0, void 0, function* () {
8405
+ try {
8406
+ yield source.editMessage(message.id, newText);
8407
+ }
8408
+ catch (e) {
8409
+ alert(e.message);
8410
+ return;
8411
+ }
8412
+ message.message = newText;
8413
+ message.transientState.editing = false;
8414
+ });
8415
+ }
8416
+ startEditing(message) {
8417
+ return __awaiter(this, void 0, void 0, function* () {
8418
+ this.selectedMessage.transientState.editing = false;
8419
+ message.transientState.editing = true;
8420
+ });
8421
+ }
8422
+ saveEdit(message, text) {
8423
+ return __awaiter(this, void 0, void 0, function* () {
8424
+ try {
8425
+ yield this.source.editMessage(message.id, text);
8426
+ message.transientState.editing = false;
8427
+ }
8428
+ catch (e) {
8429
+ alert(`Could not edit message: ${e.message}`);
8430
+ }
8431
+ });
8214
8432
  }
8215
8433
  }
8216
8434
  BantaCommentsComponent.decorators = [
8217
8435
  { type: Component, args: [{
8218
8436
  selector: 'banta-comments',
8219
- 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",
8220
- 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-duration:.4s;-webkit-animation-fill-mode:both;-webkit-animation-name:select-comment;animation-duration:.4s;animation-fill-mode:both;animation-name:select-comment}.focused .replies{margin-left:4em;margin-top:1em}banta-comment-view{opacity:1;transition:opacity .4s ease-in-out}banta-comment-view.faded{opacity:.25}.loading{display:block;margin:0 auto;min-height:16em;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content}.main.hidden{display:none}@media (max-width:500px){.focused .replies{margin-left:0}}"]
8437
+ 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(selectedMessage)\"\r\n (unliked)=\"unlikeMessage(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)=\"showPermissionDenied()\"\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)=\"showPermissionDenied()\"\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>",
8438
+ 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}"]
8221
8439
  },] }
8222
8440
  ];
8223
8441
  BantaCommentsComponent.ctorParameters = () => [
8224
- { type: BantaService },
8225
- { type: ChatBackendService },
8442
+ { type: ChatBackendBase },
8226
8443
  { type: ElementRef },
8227
8444
  { type: ActivatedRoute }
8228
8445
  ];
8229
8446
  BantaCommentsComponent.propDecorators = {
8230
- hashtags: [{ type: Input }],
8231
- participants: [{ type: Input }],
8232
- source: [{ type: Input }],
8233
- fixedHeight: [{ type: Input }],
8234
- maxMessages: [{ type: Input }],
8235
- maxVisibleMessages: [{ type: Input }],
8236
- genericAvatarUrl: [{ type: Input }],
8237
- shouldInterceptMessageSend: [{ type: Input }],
8238
- topicID: [{ type: Input }],
8447
+ loadingMessages: [{ type: Input }],
8239
8448
  signInLabel: [{ type: Input }],
8240
8449
  sendLabel: [{ type: Input }],
8241
8450
  replyLabel: [{ type: Input }],
@@ -8243,6 +8452,15 @@ BantaCommentsComponent.propDecorators = {
8243
8452
  permissionDeniedLabel: [{ type: Input }],
8244
8453
  postCommentLabel: [{ type: Input }],
8245
8454
  postReplyLabel: [{ type: Input }],
8455
+ fixedHeight: [{ type: Input }],
8456
+ maxMessages: [{ type: Input }],
8457
+ maxVisibleMessages: [{ type: Input }],
8458
+ genericAvatarUrl: [{ type: Input }],
8459
+ shouldInterceptMessageSend: [{ type: Input }],
8460
+ participants: [{ type: Input }],
8461
+ source: [{ type: Input }],
8462
+ hashtags: [{ type: Input }],
8463
+ topicID: [{ type: Input }],
8246
8464
  signInSelected: [{ type: Output }],
8247
8465
  editAvatarSelected: [{ type: Output }],
8248
8466
  permissionDeniedError: [{ type: Output }],
@@ -8314,7 +8532,7 @@ LiveCommentComponent.decorators = [
8314
8532
  },] }
8315
8533
  ];
8316
8534
  LiveCommentComponent.ctorParameters = () => [
8317
- { type: ChatBackendService }
8535
+ { type: ChatBackendBase }
8318
8536
  ];
8319
8537
  LiveCommentComponent.propDecorators = {
8320
8538
  upvoted: [{ type: Output }],
@@ -8337,6 +8555,7 @@ class CommentFieldComponent {
8337
8555
  this.permissionDeniedLabel = 'Unavailable';
8338
8556
  this.signInLabel = 'Sign In';
8339
8557
  this.placeholder = '';
8558
+ this.textChanged = new Subject();
8340
8559
  this.participants = [];
8341
8560
  this._permissionDeniedError = new Subject();
8342
8561
  this.autocompleteVisible = false;
@@ -8374,12 +8593,14 @@ class CommentFieldComponent {
8374
8593
  }
8375
8594
  indicateError(message) {
8376
8595
  this.sendError = new Error(message);
8377
- setTimeout(() => {
8596
+ this.expandError = false;
8597
+ clearTimeout(this.errorTimeout);
8598
+ this.errorTimeout = setTimeout(() => {
8378
8599
  this.expandError = true;
8379
- setTimeout(() => {
8600
+ this.errorTimeout = setTimeout(() => {
8380
8601
  this.expandError = false;
8381
8602
  }, 5 * 1000);
8382
- });
8603
+ }, 100);
8383
8604
  }
8384
8605
  autocomplete(replacement) {
8385
8606
  return __awaiter(this, void 0, void 0, function* () {
@@ -8505,7 +8726,6 @@ class CommentFieldComponent {
8505
8726
  this.editAvatarSelected.next();
8506
8727
  }
8507
8728
  sendMessage() {
8508
- var _a;
8509
8729
  return __awaiter(this, void 0, void 0, function* () {
8510
8730
  if (!this.source)
8511
8731
  return;
@@ -8519,20 +8739,16 @@ class CommentFieldComponent {
8519
8739
  user: this.user,
8520
8740
  sentAt: Date.now(),
8521
8741
  url: location.href,
8522
- upvotes: 0,
8742
+ likes: 0,
8523
8743
  message: text
8524
8744
  };
8525
8745
  try {
8526
- const intercept = yield ((_a = this.shouldInterceptMessageSend) === null || _a === void 0 ? void 0 : _a.call(this, message, this.source));
8527
- if (!intercept) {
8528
- yield this.source.send(message);
8529
- }
8746
+ yield this.submit(message);
8530
8747
  this.text = '';
8531
8748
  }
8532
8749
  catch (e) {
8533
- this.indicateError(`Could not send: ${e.message}`);
8534
- console.error(`Failed to send message: `, message);
8535
- console.error(e);
8750
+ yield new Promise(resolve => setTimeout(() => resolve(), 1000));
8751
+ this.indicateError(e.message);
8536
8752
  }
8537
8753
  }
8538
8754
  finally {
@@ -8545,7 +8761,7 @@ CommentFieldComponent.decorators = [
8545
8761
  { type: Component, args: [{
8546
8762
  selector: 'banta-comment-field',
8547
8763
  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",
8548
- styles: ["@-webkit-keyframes comment-field-appear{0%{opacity:0;transform:translateY(128px)}to{opacity:1;transform:translate(0)}}@keyframes comment-field-appear{0%{opacity:0;transform:translateY(128px)}to{opacity:1;transform:translate(0)}}:host{-webkit-animation-delay:.4s;-webkit-animation-duration:.8s;-webkit-animation-fill-mode:both;-webkit-animation-name:comment-field-appear;animation-delay:.4s;animation-duration:.8s;animation-fill-mode:both;animation-name:comment-field-appear;display:block;margin:0 2em 0 0}.avatar-container{display:flex;justify-content:flex-end;width:calc(48px + 1.75em)}.avatar-container .avatar{background:pink;background-position:50%;background-repeat:no-repeat;background-size:cover;border-radius:100%;height:48px;margin-right:.75em;margin-top:.75em;width:48px}form{align-items:center;display:flex;padding:.5em}form .text-container{display:flex;flex-grow:1;position:relative}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;position:absolute;right:0}form .text-container .error-message,form .text-container mat-spinner.loading{bottom:.5em;left:.5em;position:absolute}form .text-container .error-message{color:#683333;max-width:1.5em;overflow-x:hidden;transition:max-width 2s ease-in-out;white-space:nowrap}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;border:1px solid #333;color:#fff;height:1em;width:100%}form .actions{margin-left:1em}form button{display:block;margin:0 0 0 auto}form.new-message{align-items:flex-start;display:flex}form.new-message .field-container{display:flex;flex-direction:column;flex-grow:1}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{pointer-events:none;position:relative;top:-2em;width:calc(100% - 2em)}.autocomplete{background:#333;display:flex;flex-direction:column;padding:.5em;pointer-events:none;position:absolute;visibility:hidden;z-index:100}.autocomplete.visible{pointer-events:auto;visibility:visible}.autocomplete a{text-align:left;width:100%}.autocomplete a.active{background:#555}@media (max-width:500px){:host{margin:0}.avatar-container{width:auto}.avatar-container .avatar{height:32px;margin-top:1.5em;width:32px}button.send{margin-top:1.5em;min-width:auto}button.send .label{display:none}}"]
8764
+ 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}}"]
8549
8765
  },] }
8550
8766
  ];
8551
8767
  CommentFieldComponent.propDecorators = {
@@ -8554,19 +8770,23 @@ CommentFieldComponent.propDecorators = {
8554
8770
  canComment: [{ type: Input }],
8555
8771
  signInSelected: [{ type: Output }],
8556
8772
  editAvatarSelected: [{ type: Output }],
8773
+ sendError: [{ type: Input }],
8774
+ expandError: [{ type: Input }],
8557
8775
  sendLabel: [{ type: Input }],
8558
8776
  sendingLabel: [{ type: Input }],
8559
8777
  label: [{ type: Input }],
8560
8778
  permissionDeniedLabel: [{ type: Input }],
8561
8779
  signInLabel: [{ type: Input }],
8562
8780
  placeholder: [{ type: Input }],
8781
+ textChanged: [{ type: Output }],
8563
8782
  shouldInterceptMessageSend: [{ type: Input }],
8564
8783
  autocompleteEl: [{ type: ViewChild, args: ['autocomplete',] }],
8565
8784
  autocompleteContainerEl: [{ type: ViewChild, args: ['autocompleteContainer',] }],
8566
8785
  textareaEl: [{ type: ViewChild, args: ['textarea',] }],
8567
8786
  hashtags: [{ type: Input }],
8568
8787
  participants: [{ type: Input }],
8569
- permissionDeniedError: [{ type: Output }]
8788
+ permissionDeniedError: [{ type: Output }],
8789
+ submit: [{ type: Input }]
8570
8790
  };
8571
8791
 
8572
8792
  class CommentSortComponent {
@@ -8632,12 +8852,234 @@ CommentsModule.decorators = [
8632
8852
  },] }
8633
8853
  ];
8634
8854
 
8855
+ class ChatSource extends SocketRPC {
8856
+ constructor(backend, identifier, parentIdentifier, sortOrder) {
8857
+ super();
8858
+ this.backend = backend;
8859
+ this.identifier = identifier;
8860
+ this.parentIdentifier = parentIdentifier;
8861
+ this.sortOrder = sortOrder;
8862
+ this.subscription = new Subscription();
8863
+ this.state = 'connecting';
8864
+ this.messageMap = new Map();
8865
+ this._messageReceived = new Subject();
8866
+ this._messageSent = new Subject();
8867
+ this.messages = [];
8868
+ this.ready = new Promise(resolve => this.markReady = resolve);
8869
+ }
8870
+ bind(socket) {
8871
+ const _super = Object.create(null, {
8872
+ bind: { get: () => super.bind }
8873
+ });
8874
+ return __awaiter(this, void 0, void 0, function* () {
8875
+ _super.bind.call(this, socket);
8876
+ this.state = 'connected';
8877
+ this.markReady();
8878
+ yield this.subscribeToTopic();
8879
+ this.subscription.add(this.backend.userChanged.subscribe(() => this.authenticate()));
8880
+ socket.addEventListener('open', () => __awaiter(this, void 0, void 0, function* () {
8881
+ this.state = 'connected';
8882
+ }));
8883
+ socket.addEventListener('lost', () => __awaiter(this, void 0, void 0, function* () {
8884
+ this.state = 'lost';
8885
+ }));
8886
+ socket.addEventListener('restore', () => __awaiter(this, void 0, void 0, function* () {
8887
+ this.state = 'restored';
8888
+ yield this.authenticate();
8889
+ yield this.subscribeToTopic();
8890
+ }));
8891
+ return this;
8892
+ });
8893
+ }
8894
+ getExistingMessages() {
8895
+ return __awaiter(this, void 0, void 0, function* () {
8896
+ let messages = yield this.peer.getExistingMessages();
8897
+ messages = messages.map(message => {
8898
+ let existingMessage = this.messageMap.get(message.id);
8899
+ if (existingMessage)
8900
+ message = Object.assign(existingMessage, message);
8901
+ else
8902
+ this.messageMap.set(message.id, message);
8903
+ return message;
8904
+ });
8905
+ return messages;
8906
+ });
8907
+ }
8908
+ editMessage(messageId, text) {
8909
+ return __awaiter(this, void 0, void 0, function* () {
8910
+ this.peer.editMessage(messageId, text);
8911
+ });
8912
+ }
8913
+ subscribeToTopic() {
8914
+ return __awaiter(this, void 0, void 0, function* () {
8915
+ yield this.peer.subscribe(this.identifier, this.parentIdentifier, this.sortOrder);
8916
+ });
8917
+ }
8918
+ authenticate() {
8919
+ var _a;
8920
+ return __awaiter(this, void 0, void 0, function* () {
8921
+ yield this.peer.authenticate((_a = this.backend.user) === null || _a === void 0 ? void 0 : _a.token);
8922
+ });
8923
+ }
8924
+ close() {
8925
+ super.close();
8926
+ this.subscription.unsubscribe();
8927
+ }
8928
+ onPermissions(permissions) {
8929
+ window.bantaPermissionsDebug = permissions;
8930
+ this.permissions = permissions;
8931
+ }
8932
+ onChatMessage(message) {
8933
+ if (this.messageMap.has(message.id)) {
8934
+ Object.assign(this.messageMap.get(message.id), message);
8935
+ }
8936
+ else if (!message.hidden) {
8937
+ // Only process non-hidden messages through here.
8938
+ // Hidden messages may be sent to us when they become hidden (ie moderation is occurring).
8939
+ // But if we never had the message to begin with, we should discard it.
8940
+ this.messageMap.set(message.id, message);
8941
+ this._messageReceived.next(message);
8942
+ }
8943
+ }
8944
+ get messageReceived() { return this._messageReceived.asObservable(); }
8945
+ get messageSent() { return this._messageSent.asObservable(); }
8946
+ send(message) {
8947
+ return __awaiter(this, void 0, void 0, function* () {
8948
+ return yield this.peer.sendMessage(message);
8949
+ });
8950
+ }
8951
+ loadAfter(message, count) {
8952
+ return __awaiter(this, void 0, void 0, function* () {
8953
+ if (!message)
8954
+ return;
8955
+ if (!message.pagingCursor)
8956
+ return [];
8957
+ return this.peer.loadAfter(Number(message.pagingCursor), count);
8958
+ });
8959
+ }
8960
+ get(id) {
8961
+ return __awaiter(this, void 0, void 0, function* () {
8962
+ if (this.messageMap.has(id))
8963
+ return this.messageMap.get(id);
8964
+ let message = yield this.peer.getMessage(id);
8965
+ this.messageMap.set(id, message);
8966
+ return message;
8967
+ });
8968
+ }
8969
+ getCount() {
8970
+ return __awaiter(this, void 0, void 0, function* () {
8971
+ return yield this.peer.getCount();
8972
+ });
8973
+ }
8974
+ likeMessage(messageId) {
8975
+ return __awaiter(this, void 0, void 0, function* () {
8976
+ return yield this.peer.likeMessage(messageId);
8977
+ });
8978
+ }
8979
+ unlikeMessage(messageId) {
8980
+ return __awaiter(this, void 0, void 0, function* () {
8981
+ return yield this.peer.unlikeMessage(messageId);
8982
+ });
8983
+ }
8984
+ deleteMessage(messageId) {
8985
+ return __awaiter(this, void 0, void 0, function* () {
8986
+ return yield this.peer.deleteMessage(messageId);
8987
+ });
8988
+ }
8989
+ }
8990
+ __decorate([
8991
+ RpcEvent(),
8992
+ __metadata("design:type", Function),
8993
+ __metadata("design:paramtypes", [Object]),
8994
+ __metadata("design:returntype", void 0)
8995
+ ], ChatSource.prototype, "onPermissions", null);
8996
+ __decorate([
8997
+ RpcEvent(),
8998
+ __metadata("design:type", Function),
8999
+ __metadata("design:paramtypes", [Object]),
9000
+ __metadata("design:returntype", void 0)
9001
+ ], ChatSource.prototype, "onChatMessage", null);
9002
+
9003
+ const BANTA_SDK_OPTIONS = 'BANTA_SDK_OPTIONS';
9004
+
9005
+ class ChatBackend extends ChatBackendBase {
9006
+ constructor(options) {
9007
+ super();
9008
+ this.options = options;
9009
+ }
9010
+ get serviceUrl() {
9011
+ var _a, _b;
9012
+ return `${(_b = (_a = this.options) === null || _a === void 0 ? void 0 : _a.serviceUrl) !== null && _b !== void 0 ? _b : 'http://localhost:3422'}`;
9013
+ }
9014
+ connectToService() {
9015
+ return __awaiter(this, void 0, void 0, function* () {
9016
+ let socket = new DurableSocket(`${this.serviceUrl.replace(/^http/, 'ws')}/socket`);
9017
+ yield new Promise((resolve, reject) => {
9018
+ socket.onopen = () => {
9019
+ resolve();
9020
+ };
9021
+ socket.onclose = e => {
9022
+ if (e.code === 503) {
9023
+ console.error(`Failed to connect to chat service!`);
9024
+ reject(e);
9025
+ }
9026
+ };
9027
+ });
9028
+ socket.onerror = undefined;
9029
+ return socket;
9030
+ });
9031
+ }
9032
+ getSourceForTopic(topicId, options) {
9033
+ return __awaiter(this, void 0, void 0, function* () {
9034
+ return yield new ChatSource(this, topicId, undefined, (options === null || options === void 0 ? void 0 : options.sortOrder) || CommentsOrder.NEWEST)
9035
+ .bind(yield this.connectToService());
9036
+ });
9037
+ }
9038
+ getSourceForThread(topicId, messageId, options) {
9039
+ return __awaiter(this, void 0, void 0, function* () {
9040
+ return yield new ChatSource(this, topicId, messageId, (options === null || options === void 0 ? void 0 : options.sortOrder) || CommentsOrder.NEWEST)
9041
+ .bind(yield this.connectToService());
9042
+ });
9043
+ }
9044
+ getSourceCountForTopic(topicId) {
9045
+ return __awaiter(this, void 0, void 0, function* () {
9046
+ let response = yield fetch(`${this.serviceUrl}/topics/${topicId}`);
9047
+ if (response.status >= 400)
9048
+ return 0;
9049
+ let topic = yield response.json();
9050
+ return topic.messageCount || 0;
9051
+ });
9052
+ }
9053
+ refreshMessage(message) {
9054
+ throw new Error("Method not implemented.");
9055
+ }
9056
+ getMessage(topicId, messageId) {
9057
+ throw new Error("Method not implemented.");
9058
+ }
9059
+ getSubMessage(topicId, parentMessageId, messageId) {
9060
+ throw new Error("Method not implemented.");
9061
+ }
9062
+ watchMessage(message, handler) {
9063
+ throw new Error("Method not implemented.");
9064
+ }
9065
+ }
9066
+ ChatBackend.decorators = [
9067
+ { type: Injectable }
9068
+ ];
9069
+ ChatBackend.ctorParameters = () => [
9070
+ { type: undefined, decorators: [{ type: Inject, args: [BANTA_SDK_OPTIONS,] }] }
9071
+ ];
9072
+
8635
9073
  class BantaSdkModule {
8636
- static forRoot() {
9074
+ static configure(options) {
8637
9075
  return {
8638
9076
  ngModule: BantaSdkModule,
8639
9077
  providers: [
8640
- BantaService
9078
+ {
9079
+ provide: BANTA_SDK_OPTIONS,
9080
+ useValue: options || {}
9081
+ },
9082
+ { provide: ChatBackendBase, useClass: ChatBackend }
8641
9083
  ]
8642
9084
  };
8643
9085
  }
@@ -8683,5 +9125,5 @@ BantaSdkModule.decorators = [
8683
9125
  * Generated bundle index. Do not edit.
8684
9126
  */
8685
9127
 
8686
- 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 };
9128
+ 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 };
8687
9129
  //# sourceMappingURL=banta-sdk.js.map