@banta/sdk 3.0.1 → 3.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,8 @@
1
1
  (function (global, factory) {
2
- typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('rxjs'), require('rxjs/operators'), require('@angular/core'), require('@angular/common'), require('@angular/platform-browser'), require('@angular/material/icon'), require('@angular/material/button'), require('subsink'), require('@angular/material/dialog'), require('@angular/forms'), require('@angular/material/menu'), require('@angular/material/progress-spinner'), require('@angular/material/form-field'), require('@angular/material/input'), require('@angular/cdk/text-field'), require('@angular/material/tooltip')) :
3
- typeof define === 'function' && define.amd ? define('@banta/sdk', ['exports', 'rxjs', 'rxjs/operators', '@angular/core', '@angular/common', '@angular/platform-browser', '@angular/material/icon', '@angular/material/button', 'subsink', '@angular/material/dialog', '@angular/forms', '@angular/material/menu', '@angular/material/progress-spinner', '@angular/material/form-field', '@angular/material/input', '@angular/cdk/text-field', '@angular/material/tooltip'], factory) :
4
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory((global.banta = global.banta || {}, global.banta.sdk = {}), global.rxjs, global.rxjs.operators, global.ng.core, global.ng.common, global.ng.platformBrowser, global.ng.material.icon, global.ng.material.button, global.subsink, global.ng.material.dialog, global.ng.forms, global.ng.material.menu, global.ng.material.progressSpinner, global.ng.material.formField, global.ng.material.input, global.ng.cdk.textField, global.ng.material.tooltip));
5
- }(this, (function (exports, rxjs, operators, core, common, platformBrowser, icon, button, subsink, dialog, forms, menu, progressSpinner, formField, input, textField, tooltip) { 'use strict';
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('rxjs'), require('rxjs/operators'), require('@angular/core'), require('@angular/common'), require('@angular/platform-browser'), require('@angular/material/icon'), require('@angular/material/button'), require('@angular/material/form-field'), require('@angular/material/input'), require('@angular/forms'), require('subsink'), require('@angular/material/dialog'), require('@angular/material/menu'), require('@angular/material/progress-spinner'), require('@angular/cdk/text-field'), require('@angular/material/tooltip')) :
3
+ typeof define === 'function' && define.amd ? define('@banta/sdk', ['exports', 'rxjs', 'rxjs/operators', '@angular/core', '@angular/common', '@angular/platform-browser', '@angular/material/icon', '@angular/material/button', '@angular/material/form-field', '@angular/material/input', '@angular/forms', 'subsink', '@angular/material/dialog', '@angular/material/menu', '@angular/material/progress-spinner', '@angular/cdk/text-field', '@angular/material/tooltip'], factory) :
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory((global.banta = global.banta || {}, global.banta.sdk = {}), global.rxjs, global.rxjs.operators, global.ng.core, global.ng.common, global.ng.platformBrowser, global.ng.material.icon, global.ng.material.button, global.ng.material.formField, global.ng.material.input, global.ng.forms, global.subsink, global.ng.material.dialog, global.ng.material.menu, global.ng.material.progressSpinner, global.ng.cdk.textField, global.ng.material.tooltip));
5
+ }(this, (function (exports, rxjs, operators, core, common, platformBrowser, icon, button, formField, input, forms, subsink, dialog, menu, progressSpinner, textField, tooltip) { 'use strict';
6
6
 
7
7
  function lazyConnection(options) {
8
8
  var obs = new rxjs.Observable(function (observer) {
@@ -7006,14 +7006,49 @@
7006
7006
  function EmojiSelectorPanelComponent(sanitizer) {
7007
7007
  this.sanitizer = sanitizer;
7008
7008
  this.activeCategory = 'people';
7009
+ this.searchResults = [];
7010
+ this.searchVisible = false;
7009
7011
  this.selected = new rxjs.Subject();
7010
7012
  }
7013
+ Object.defineProperty(EmojiSelectorPanelComponent.prototype, "searchQuery", {
7014
+ get: function () {
7015
+ return this._searchQuery;
7016
+ },
7017
+ set: function (value) {
7018
+ var _this = this;
7019
+ this._searchQuery = value;
7020
+ setTimeout(function () {
7021
+ _this.searchResults = Object.keys(EMOJIS).filter(function (k) { return k.includes(value); }).map(function (k) { return EMOJIS[k]; });
7022
+ _this.searchResults.splice(50, _this.searchResults.length);
7023
+ console.log("looking for '" + value + "' => " + _this.searchResults.length + " results");
7024
+ });
7025
+ },
7026
+ enumerable: false,
7027
+ configurable: true
7028
+ });
7029
+ EmojiSelectorPanelComponent.prototype.humanize = function (str) {
7030
+ return str.replace(/(^| )[a-z]/g, function (k) { return k.toUpperCase(); }).replace(/_/g, ' ');
7031
+ };
7011
7032
  EmojiSelectorPanelComponent.prototype.select = function (char) {
7012
7033
  this.selected.next(char);
7013
7034
  };
7014
7035
  EmojiSelectorPanelComponent.prototype.pairs = function (object) {
7015
7036
  return Object.keys(object).map(function (key) { return [key, object[key]]; });
7016
7037
  };
7038
+ EmojiSelectorPanelComponent.prototype.hideSearch = function () {
7039
+ var _this = this;
7040
+ // because of the "outside click detection"
7041
+ setTimeout(function () {
7042
+ _this.searchVisible = false;
7043
+ });
7044
+ };
7045
+ EmojiSelectorPanelComponent.prototype.showSearch = function () {
7046
+ var _this = this;
7047
+ // because of the "outside click detection"
7048
+ setTimeout(function () {
7049
+ _this.searchVisible = true;
7050
+ });
7051
+ };
7017
7052
  EmojiSelectorPanelComponent.prototype.ngOnInit = function () {
7018
7053
  var e_1, _a;
7019
7054
  var cats = {};
@@ -7057,8 +7092,8 @@
7057
7092
  EmojiSelectorPanelComponent.decorators = [
7058
7093
  { type: core.Component, args: [{
7059
7094
  selector: 'emoji-selector-panel',
7060
- template: "<div class=\"categories\">\r\n\t<a title=\"{{category.name}}\" mat-icon-button *ngFor=\"let category of categories\" (click)=\"activeCategory = category.name\">\r\n\t\t<mat-icon>{{category.icon}}</mat-icon>\r\n\t</a>\r\n</div>\r\n\r\n<div *ngFor=\"let category of categories\">\r\n\t<div class=\"emoji-list\" *ngIf=\"activeCategory && activeCategory == category.name\">\r\n\t\t<a href=\"javascript:;\" (click)=\"select(emoji.char)\" *ngFor=\"let emoji of category.emojis\" [innerHtml]=\"emoji.html || ''\">\r\n\t\t</a>\r\n\t</div>\r\n</div>",
7061
- styles: [":host{background:#111;border:1px solid #333;border-radius:5px;display:flex;flex-direction:column;padding:.5em;width:calc(256px + 8em)}.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}"]
7095
+ template: "<ng-container *ngIf=\"searchVisible\">\r\n\t<div class=\"search-box\" *ngIf=\"searchVisible\">\r\n\t\t<a mat-icon-button href=\"javascript:;\" (click)=\"hideSearch()\">\r\n\t\t\t<mat-icon>arrow_back</mat-icon>\r\n\t\t</a>\r\n\t\t<mat-form-field appearance=\"outline\" floatLabel=\"always\">\r\n\t\t\t<mat-label>Search for emoji</mat-label>\r\n\t\t\t<input name=\"search\" type=\"text\" matInput placeholder=\"Start typing\" [(ngModel)]=\"searchQuery\" />\r\n\t\t</mat-form-field>\r\n\t</div>\r\n\t<div class=\"emoji-list\">\r\n\t\t<a href=\"javascript:;\" (click)=\"select(emoji.char)\" \r\n\t\t\t*ngFor=\"let emoji of searchResults\" [innerHtml]=\"emoji.html || ''\">\r\n\t\t</a>\r\n\t</div>\r\n</ng-container>\r\n<ng-container *ngIf=\"!searchVisible\">\r\n\t<div class=\"categories\">\r\n\t\t<ng-container *ngIf=\"!searchVisible\">\r\n\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<mat-icon>{{category.icon}}</mat-icon>\r\n\t\t\t</a>\r\n\r\n\t\t\t<a title=\"Search\" [class.active] mat-icon-button (click)=\"showSearch()\">\r\n\t\t\t\t<mat-icon>search</mat-icon>\r\n\t\t\t</a>\r\n\t\t</ng-container>\r\n\t</div>\r\n\t<div *ngFor=\"let category of categories\">\r\n\t\t<div class=\"emoji-list\" *ngIf=\"activeCategory && activeCategory == category.name\">\r\n\t\t\t<a href=\"javascript:;\" (click)=\"select(emoji.char)\" \r\n\t\t\t\t*ngFor=\"let emoji of category.emojis\" [innerHtml]=\"emoji.html || ''\">\r\n\t\t\t</a>\r\n\t\t</div>\r\n\t</div>\r\n</ng-container>",
7096
+ styles: [":host{background:#111;border:1px solid #333;border-radius:5px;color:#fff;display:flex;flex-direction:column;padding:.5em;width:calc(288px + 9em)}.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}"]
7062
7097
  },] }
7063
7098
  ];
7064
7099
  EmojiSelectorPanelComponent.ctorParameters = function () { return [
@@ -7082,6 +7117,11 @@
7082
7117
  });
7083
7118
  EmojiSelectorButtonComponent.prototype.ngOnDestroy = function () {
7084
7119
  this.removeListener();
7120
+ this.panelElement.nativeElement.remove();
7121
+ };
7122
+ EmojiSelectorButtonComponent.prototype.ngAfterViewInit = function () {
7123
+ var root = document.body.querySelector('[ng-version]') || document.body;
7124
+ root.appendChild(this.panelElement.nativeElement);
7085
7125
  };
7086
7126
  EmojiSelectorButtonComponent.prototype.removeListener = function () {
7087
7127
  document.removeEventListener('click', this.clickListener);
@@ -7093,6 +7133,12 @@
7093
7133
  return;
7094
7134
  }
7095
7135
  this.showEmojiPanel = true;
7136
+ var pos = this.buttonElement.nativeElement.getBoundingClientRect();
7137
+ var size = this.panelElement.nativeElement.getBoundingClientRect();
7138
+ Object.assign(this.panelElement.nativeElement.style, {
7139
+ top: pos.top + pos.height + "px",
7140
+ right: Math.max(0, window.innerWidth - pos.left - pos.width) + "px"
7141
+ });
7096
7142
  setTimeout(function () {
7097
7143
  _this.clickListener = function (ev) {
7098
7144
  var parent = ev.target;
@@ -7118,12 +7164,14 @@
7118
7164
  EmojiSelectorButtonComponent.decorators = [
7119
7165
  { type: core.Component, args: [{
7120
7166
  selector: 'emoji-selector-button',
7121
- template: "\n <button mat-icon-button (click)=\"show()\">\n <mat-icon>emoji_emotions</mat-icon>\n </button>\n <emoji-selector-panel \n (selected)=\"insert($event)\"\n [class.visible]=\"showEmojiPanel\"\n ></emoji-selector-panel>\n ",
7122
- styles: ["\n :host {\n display: block;\n position: relative;\n }\n\n emoji-selector-panel {\n position: absolute;\n bottom: 2.5em;\n right: 0;\n opacity: 0;\n pointer-events: none;\n z-index: 10;\n }\n\n emoji-selector-panel.visible {\n pointer-events: initial;\n opacity: 1;\n }\n\n button {\n color: #666\n }\n\n :host.bottom-left emoji-selector-panel {\n right: auto;\n left: 0;\n }\n\n :host.top-right emoji-selector-panel {\n top: 2.4em;\n bottom: auto;\n }\n\n :host.top-left emoji-selector-panel {\n top: 2.4em;\n bottom: auto;\n left: 0;\n right: auto;\n }\n "]
7167
+ template: "\n <button #button type=\"button\" mat-icon-button (click)=\"show()\">\n <mat-icon>emoji_emotions</mat-icon>\n </button>\n <emoji-selector-panel \n #panel\n (selected)=\"insert($event)\"\n [class.visible]=\"showEmojiPanel\"\n ></emoji-selector-panel>\n ",
7168
+ styles: ["\n :host {\n display: block;\n position: relative;\n }\n\n emoji-selector-panel {\n position: absolute;\n /* bottom: 2.5em;\n right: 0; */\n opacity: 0;\n pointer-events: none;\n z-index: 10;\n }\n\n emoji-selector-panel.visible {\n pointer-events: initial;\n opacity: 1;\n }\n\n button {\n color: #666\n }\n\n /* :host.bottom-left emoji-selector-panel {\n right: auto;\n left: 0;\n }\n\n :host.top-right emoji-selector-panel {\n top: 2.4em;\n bottom: auto;\n }\n\n :host.top-left emoji-selector-panel {\n top: 2.4em;\n bottom: auto;\n left: 0;\n right: auto;\n } */\n "]
7123
7169
  },] }
7124
7170
  ];
7125
7171
  EmojiSelectorButtonComponent.propDecorators = {
7126
- selected: [{ type: core.Output }]
7172
+ selected: [{ type: core.Output }],
7173
+ panelElement: [{ type: core.ViewChild, args: ['panel', { read: core.ElementRef },] }],
7174
+ buttonElement: [{ type: core.ViewChild, args: ['button', { read: core.ElementRef },] }]
7127
7175
  };
7128
7176
 
7129
7177
  var COMPONENTS$1 = [
@@ -7140,8 +7188,11 @@
7140
7188
  declarations: COMPONENTS$1,
7141
7189
  imports: [
7142
7190
  common.CommonModule,
7191
+ forms.FormsModule,
7143
7192
  icon.MatIconModule,
7144
- button.MatButtonModule
7193
+ button.MatButtonModule,
7194
+ formField.MatFormFieldModule,
7195
+ input.MatInputModule
7145
7196
  ],
7146
7197
  exports: COMPONENTS$1
7147
7198
  },] }
@@ -7168,8 +7219,6 @@
7168
7219
  this.pointSubChat = null;
7169
7220
  this.newPointSubMessage = {};
7170
7221
  this.genericAvatarUrl = 'https://gravatar.com/avatar/915c804e0be607a4ad766ddadea5c48a?s=512&d=https://codepen.io/assets/avatars/user-avatar-512x512-6e240cf350d2f1cc07c2bed234c3a3bb5f1b237023c204c782622e80d6b212ba.png';
7171
- // this.firehoseSource = new MockFirehoseSource();
7172
- // this.pointSource = new MockPointSource();
7173
7222
  }
7174
7223
  BantaComponent.prototype.ngOnInit = function () {
7175
7224
  var _this = this;
@@ -7387,7 +7436,7 @@
7387
7436
  BantaComponent.decorators = [
7388
7437
  { type: core.Component, args: [{
7389
7438
  selector: "banta",
7390
- 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\r\n <pre>{{profileUser | json}}</pre>\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>",
7439
+ 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>",
7391
7440
  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}"]
7392
7441
  },] }
7393
7442
  ];
@@ -7876,8 +7925,12 @@
7876
7925
  Object.defineProperty(BantaChatComponent.prototype, "canChat", {
7877
7926
  get: function () {
7878
7927
  var _a;
7928
+ if (!this.user)
7929
+ return false;
7879
7930
  if (!this.user.permissions)
7880
7931
  return true;
7932
+ if (!this.user.permissions.canChat)
7933
+ return true;
7881
7934
  return (_a = this.user.permissions) === null || _a === void 0 ? void 0 : _a.canChat(this.source);
7882
7935
  },
7883
7936
  enumerable: false,
@@ -8049,8 +8102,21 @@
8049
8102
  this._selected = new rxjs.Subject();
8050
8103
  this._upvoted = new rxjs.Subject();
8051
8104
  this._userSelected = new rxjs.Subject();
8105
+ this.isNew = false;
8106
+ this.visible = false;
8052
8107
  this.showReplyAction = true;
8053
8108
  }
8109
+ CommentComponent.prototype.ngOnInit = function () {
8110
+ var _this = this;
8111
+ var maxTime = 500;
8112
+ var minTime = 0;
8113
+ var randomTime = minTime + Math.random() * (maxTime - minTime);
8114
+ setTimeout(function () {
8115
+ _this.isNew = true;
8116
+ _this.visible = true;
8117
+ setTimeout(function () { return _this.isNew = false; }, 1000);
8118
+ }, randomTime);
8119
+ };
8054
8120
  Object.defineProperty(CommentComponent.prototype, "userSelected", {
8055
8121
  get: function () {
8056
8122
  return this._userSelected;
@@ -8079,6 +8145,14 @@
8079
8145
  enumerable: false,
8080
8146
  configurable: true
8081
8147
  });
8148
+ Object.defineProperty(CommentComponent.prototype, "commentId", {
8149
+ get: function () {
8150
+ var _a;
8151
+ return (_a = this.message) === null || _a === void 0 ? void 0 : _a.id;
8152
+ },
8153
+ enumerable: false,
8154
+ configurable: true
8155
+ });
8082
8156
  CommentComponent.prototype.report = function () {
8083
8157
  this._reported.next();
8084
8158
  };
@@ -8103,17 +8177,20 @@
8103
8177
  CommentComponent.decorators = [
8104
8178
  { type: core.Component, args: [{
8105
8179
  selector: 'banta-comment',
8106
- 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 <div class=\"avatar\" \r\n (click)=\"selectUser()\"\r\n [style.background-image]=\"avatarForUser(message.user)\"></div>\r\n <label class=\"display-name\" (click)=\"selectUser()\">{{message.user.displayName}}</label>\r\n <label class=\"username\" (click)=\"selectUser()\">@{{message.user.username}}</label>\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 class=\"content\" (click)=\"select()\">\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 </div>\r\n</div>\r\n",
8107
- styles: [":host{display:flex;flex-direction:column;padding:.5em;position:relative}:host:hover{background:#eee}:host .message-content .content{margin-left:4em;margin-right:3em}:host.abbreviated .message-content .content{max-height:8.5em;overflow-y:hidden;text-overflow:ellipsis}:host .actions{align-items:center;display:flex;margin-left:4em;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 label{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 label.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:3em;width:3em}.counted-action{align-items:center;display:flex}.count-indicator{border:1px solid #ccc;border-radius:3px;color:#666;font-size:9pt;padding: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 label{color:#fff}"]
8180
+ 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)=\"selectUser()\"\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)=\"selectUser()\">@{{message.user.username}}</a>\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 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 </div>\r\n</div>\r\n",
8181
+ 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:4em;margin-right:3em}:host.abbreviated .message-content .content{max-height:8.5em;overflow-y:hidden;text-overflow:ellipsis}:host .actions{align-items:center;display:flex;margin-left:4em;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:3em;width:3em}.counted-action{align-items:center;display:flex}.count-indicator{border:1px solid #ccc;border-radius:3px;color:#666;font-size:9pt;padding: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}"]
8108
8182
  },] }
8109
8183
  ];
8110
8184
  CommentComponent.propDecorators = {
8185
+ isNew: [{ type: core.HostBinding, args: ['class.new',] }],
8186
+ visible: [{ type: core.HostBinding, args: ['class.visible',] }],
8111
8187
  message: [{ type: core.Input }],
8112
8188
  showReplyAction: [{ type: core.Input }],
8113
8189
  userSelected: [{ type: core.Output }],
8114
8190
  reported: [{ type: core.Output }],
8115
8191
  upvoted: [{ type: core.Output }],
8116
- selected: [{ type: core.Output }]
8192
+ selected: [{ type: core.Output }],
8193
+ commentId: [{ type: core.HostBinding, args: ['attr.data-comment-id',] }]
8117
8194
  };
8118
8195
 
8119
8196
  var CommentViewComponent = /** @class */ (function () {
@@ -8210,7 +8287,7 @@
8210
8287
  CommentViewComponent.prototype.selectMessageUser = function (message) {
8211
8288
  this._userSelected.next(message);
8212
8289
  };
8213
- CommentViewComponent.prototype.messageIdentity = function (chatMessage) {
8290
+ CommentViewComponent.prototype.messageIdentity = function (index, chatMessage) {
8214
8291
  return chatMessage.id;
8215
8292
  };
8216
8293
  CommentViewComponent.prototype.showNew = function () {
@@ -8321,8 +8398,8 @@
8321
8398
  CommentViewComponent.decorators = [
8322
8399
  { type: core.Component, args: [{
8323
8400
  selector: 'banta-comment-view',
8324
- template: "<div class=\"message-container\">\r\n <ng-content select=\"[data-before]\"></ng-content>\r\n \r\n <a mat-button class=\"nav\" *ngIf=\"isViewingMore\" href=\"javascript:;\" (click)=\"showNew()\">\r\n Show \r\n <ng-container *ngIf=\"newMessages.length === 1\">\r\n {{newMessages.length}} new message\r\n </ng-container>\r\n <ng-container *ngIf=\"newMessages.length > 1\">\r\n {{newMessages.length}} new messages\r\n </ng-container>\r\n <ng-container *ngIf=\"newMessages.length === 0\">\r\n new messages\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 <banta-comment \r\n *ngFor=\"let message of messages; trackBy: messageIdentity\"\r\n class=\"abbreviated\"\r\n [message]=\"message\"\r\n [showReplyAction]=\"allowReplies\"\r\n (userSelected)=\"selectMessageUser(message)\"\r\n (upvoted)=\"upvoteMessage(message)\"\r\n (reported)=\"reportMessage(message)\"\r\n (selected)=\"selectMessage(message)\"\r\n ></banta-comment>\r\n\r\n <a mat-button class=\"nav\" *ngIf=\"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>",
8325
- styles: [":host{display:flex;flex-direction:column;flex-grow:1;opacity:1;transition:opacity .2s ease-in}.message-container{background:#fff;color:#000;flex-grow:1;opacity:1;overflow-x:hidden;padding:.5em 1em .5em .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:#000;color:#fff}.empty-state{color:#666;margin:3em;text-align:center}:host-context(.mat-dark-theme) .empty-state{color:#666}a.nav{text-transform:uppercase;width:100%}.loading-more,a.nav{padding:2em;text-align:center}.loading-more{margin:0 auto;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content}"]
8401
+ 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 Show \r\n <ng-container *ngIf=\"newMessages.length === 1\">\r\n {{newMessages.length}} new message\r\n </ng-container>\r\n <ng-container *ngIf=\"newMessages.length > 1\">\r\n {{newMessages.length}} new messages\r\n </ng-container>\r\n <ng-container *ngIf=\"newMessages.length === 0\">\r\n new messages\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 <banta-comment \r\n *ngFor=\"let message of messages; trackBy: messageIdentity\"\r\n class=\"abbreviated\"\r\n [message]=\"message\"\r\n (click)=\"isViewingMore = true\"\r\n [showReplyAction]=\"allowReplies\"\r\n (userSelected)=\"selectMessageUser(message)\"\r\n (upvoted)=\"upvoteMessage(message)\"\r\n (reported)=\"reportMessage(message)\"\r\n (selected)=\"selectMessage(message)\"\r\n ></banta-comment>\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>",
8402
+ 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 .5em .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{opacity:0;pointer-events:none;text-align:center;text-transform:uppercase;transition:opacity .4s ease-in-out;width:100%}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}"]
8326
8403
  },] }
8327
8404
  ];
8328
8405
  CommentViewComponent.ctorParameters = function () { return [
@@ -8348,19 +8425,34 @@
8348
8425
  * Comments component
8349
8426
  */
8350
8427
  var BantaCommentsComponent = /** @class */ (function () {
8351
- function BantaCommentsComponent(banta, backend) {
8428
+ function BantaCommentsComponent(banta, backend, elementRef) {
8352
8429
  this.banta = banta;
8353
8430
  this.backend = backend;
8431
+ this.elementRef = elementRef;
8354
8432
  this._upvoted = new rxjs.Subject();
8355
8433
  this._reported = new rxjs.Subject();
8356
8434
  this._selected = new rxjs.Subject();
8357
8435
  this._userSelected = new rxjs.Subject();
8358
8436
  this._subs = new subsink.SubSink();
8437
+ this.hashtags = [
8438
+ { hashtag: 'error', description: 'Cause an error' },
8439
+ { hashtag: 'timeout', description: 'Cause a slow timeout error' },
8440
+ { hashtag: 'slow', description: 'Be slow when this message is posted' },
8441
+ ];
8442
+ this.participants = [];
8359
8443
  this.signInLabel = 'Sign In';
8360
8444
  this.sendLabel = 'Send';
8445
+ this.replyLabel = 'Reply';
8446
+ this.sendingLabel = 'Sending';
8361
8447
  this.permissionDeniedLabel = 'Send';
8448
+ this.postCommentLabel = 'Post a comment';
8449
+ this.postReplyLabel = 'Post a reply';
8362
8450
  this._signInSelected = new rxjs.Subject();
8363
8451
  this._permissionDeniedError = new rxjs.Subject();
8452
+ this._editAvatarSelected = new rxjs.Subject();
8453
+ this.sending = false;
8454
+ this.expandError = false;
8455
+ this.selectedMessageVisible = false;
8364
8456
  }
8365
8457
  BantaCommentsComponent.prototype.ngOnInit = function () {
8366
8458
  var _this = this;
@@ -8392,6 +8484,7 @@
8392
8484
  BantaCommentsComponent.prototype.setSourceFromTopicID = function (topicID) {
8393
8485
  return __awaiter(this, void 0, void 0, function () {
8394
8486
  var _b;
8487
+ var _this = this;
8395
8488
  return __generator(this, function (_c) {
8396
8489
  switch (_c.label) {
8397
8490
  case 0:
@@ -8402,14 +8495,41 @@
8402
8495
  return [4 /*yield*/, this.backend.getSourceForTopic(topicID)];
8403
8496
  case 1:
8404
8497
  _b._source = _c.sent();
8498
+ this._source.messageReceived.subscribe(function (m) { return _this.addParticipant(m); });
8499
+ this._source.messageSent.subscribe(function (m) { return _this.addParticipant(m); });
8500
+ this._source.messages.forEach(function (m) { return _this.addParticipant(m); });
8405
8501
  return [2 /*return*/];
8406
8502
  }
8407
8503
  });
8408
8504
  });
8409
8505
  };
8506
+ BantaCommentsComponent.prototype.addParticipant = function (message) {
8507
+ if (!message || !message.user || !message.user.id)
8508
+ return;
8509
+ var existing = this.participants.find(function (x) { return x.id === message.user.id; });
8510
+ if (existing)
8511
+ return;
8512
+ this.participants.push(message.user);
8513
+ };
8410
8514
  BantaCommentsComponent.prototype.showSignIn = function () {
8411
8515
  this._signInSelected.next();
8412
8516
  };
8517
+ BantaCommentsComponent.prototype.showEditAvatar = function () {
8518
+ this._editAvatarSelected.next();
8519
+ };
8520
+ Object.defineProperty(BantaCommentsComponent.prototype, "newMessageText", {
8521
+ get: function () {
8522
+ return this._newMessageText;
8523
+ },
8524
+ set: function (value) {
8525
+ var _this = this;
8526
+ this._newMessageText = value;
8527
+ if (this._newMessageText === '' && this.sendError)
8528
+ setTimeout(function () { return _this.sendError = null; });
8529
+ },
8530
+ enumerable: false,
8531
+ configurable: true
8532
+ });
8413
8533
  Object.defineProperty(BantaCommentsComponent.prototype, "signInSelected", {
8414
8534
  get: function () {
8415
8535
  return this._signInSelected;
@@ -8417,6 +8537,13 @@
8417
8537
  enumerable: false,
8418
8538
  configurable: true
8419
8539
  });
8540
+ Object.defineProperty(BantaCommentsComponent.prototype, "editAvatarSelected", {
8541
+ get: function () {
8542
+ return this._editAvatarSelected;
8543
+ },
8544
+ enumerable: false,
8545
+ configurable: true
8546
+ });
8420
8547
  Object.defineProperty(BantaCommentsComponent.prototype, "permissionDeniedError", {
8421
8548
  get: function () {
8422
8549
  return this._permissionDeniedError;
@@ -8430,8 +8557,12 @@
8430
8557
  Object.defineProperty(BantaCommentsComponent.prototype, "canComment", {
8431
8558
  get: function () {
8432
8559
  var _a;
8560
+ if (!this.user)
8561
+ return false;
8433
8562
  if (!this.user.permissions)
8434
8563
  return true;
8564
+ if (!this.user.permissions.canComment)
8565
+ return true;
8435
8566
  return (_a = this.user.permissions) === null || _a === void 0 ? void 0 : _a.canComment(this.source);
8436
8567
  },
8437
8568
  enumerable: false,
@@ -8475,6 +8606,16 @@
8475
8606
  BantaCommentsComponent.prototype.insertReplyEmoji = function (text) {
8476
8607
  this.replyMessage += text;
8477
8608
  };
8609
+ BantaCommentsComponent.prototype.indicateError = function (message) {
8610
+ var _this = this;
8611
+ this.sendError = new Error(message);
8612
+ setTimeout(function () {
8613
+ _this.expandError = true;
8614
+ setTimeout(function () {
8615
+ _this.expandError = false;
8616
+ }, 5 * 1000);
8617
+ });
8618
+ };
8478
8619
  BantaCommentsComponent.prototype.sendMessage = function () {
8479
8620
  return __awaiter(this, void 0, void 0, function () {
8480
8621
  var text, message, e_1;
@@ -8483,8 +8624,12 @@
8483
8624
  case 0:
8484
8625
  if (!this.source)
8485
8626
  return [2 /*return*/];
8627
+ this.sending = true;
8628
+ this.sendError = null;
8629
+ _b.label = 1;
8630
+ case 1:
8631
+ _b.trys.push([1, , 6, 7]);
8486
8632
  text = (this.newMessageText || '').trim();
8487
- this.newMessageText = '';
8488
8633
  if (text === '')
8489
8634
  return [2 /*return*/];
8490
8635
  message = {
@@ -8493,32 +8638,52 @@
8493
8638
  upvotes: 0,
8494
8639
  message: text
8495
8640
  };
8496
- _b.label = 1;
8497
- case 1:
8498
- _b.trys.push([1, 3, , 4]);
8499
- return [4 /*yield*/, this.source.send(message)];
8641
+ _b.label = 2;
8500
8642
  case 2:
8501
- _b.sent();
8502
- return [3 /*break*/, 4];
8643
+ _b.trys.push([2, 4, , 5]);
8644
+ return [4 /*yield*/, this.source.send(message)];
8503
8645
  case 3:
8646
+ _b.sent();
8647
+ this.newMessageText = '';
8648
+ return [3 /*break*/, 5];
8649
+ case 4:
8504
8650
  e_1 = _b.sent();
8651
+ this.indicateError("Could not send: " + e_1.message);
8505
8652
  console.error("Failed to send message: ", message);
8506
8653
  console.error(e_1);
8507
- return [3 /*break*/, 4];
8508
- case 4: return [2 /*return*/];
8654
+ return [3 /*break*/, 5];
8655
+ case 5: return [3 /*break*/, 7];
8656
+ case 6:
8657
+ this.sending = false;
8658
+ return [7 /*endfinally*/];
8659
+ case 7: return [2 /*return*/];
8509
8660
  }
8510
8661
  });
8511
8662
  });
8512
8663
  };
8513
8664
  BantaCommentsComponent.prototype.upvoteMessage = function (message) {
8514
- this._upvoted.next(message);
8665
+ return __awaiter(this, void 0, void 0, function () {
8666
+ return __generator(this, function (_b) {
8667
+ switch (_b.label) {
8668
+ case 0:
8669
+ this._upvoted.next(message);
8670
+ return [4 /*yield*/, this.backend.upvoteMessage(message.topicId, message.parentMessageId ? message.parentMessageId : message.id, message.parentMessageId ? message.id : undefined)];
8671
+ case 1:
8672
+ _b.sent();
8673
+ return [2 /*return*/];
8674
+ }
8675
+ });
8676
+ });
8515
8677
  };
8516
8678
  BantaCommentsComponent.prototype.reportMessage = function (message) {
8517
8679
  this._reported.next(message);
8518
8680
  };
8519
8681
  BantaCommentsComponent.prototype.unselectMessage = function () {
8520
8682
  return __awaiter(this, void 0, void 0, function () {
8683
+ var message;
8684
+ var _this = this;
8521
8685
  return __generator(this, function (_b) {
8686
+ message = this.selectedMessage;
8522
8687
  this._selected.next(null);
8523
8688
  this.selectedMessage = null;
8524
8689
  if (this.selectedMessageThread) {
@@ -8526,24 +8691,33 @@
8526
8691
  this.selectedMessageThread.close();
8527
8692
  this.selectedMessageThread = null;
8528
8693
  }
8694
+ if (message)
8695
+ setTimeout(function () { return _this.scrollToMessage(message); });
8529
8696
  return [2 /*return*/];
8530
8697
  });
8531
8698
  });
8532
8699
  };
8533
8700
  BantaCommentsComponent.prototype.selectMessage = function (message) {
8534
8701
  return __awaiter(this, void 0, void 0, function () {
8535
- var _b;
8536
- return __generator(this, function (_c) {
8537
- switch (_c.label) {
8538
- case 0:
8539
- this._selected.next(message);
8540
- this.selectedMessage = message;
8541
- _b = this;
8542
- return [4 /*yield*/, this.backend.getSourceForThread(this.topicID, message.id)];
8543
- case 1:
8544
- _b.selectedMessageThread = _c.sent();
8545
- return [2 /*return*/];
8546
- }
8702
+ var _this = this;
8703
+ return __generator(this, function (_b) {
8704
+ this._selected.next(message);
8705
+ this.selectedMessage = message;
8706
+ setTimeout(function () { return _this.selectedMessageVisible = true; });
8707
+ setTimeout(function () { return __awaiter(_this, void 0, void 0, function () {
8708
+ var _b;
8709
+ return __generator(this, function (_c) {
8710
+ switch (_c.label) {
8711
+ case 0:
8712
+ _b = this;
8713
+ return [4 /*yield*/, this.backend.getSourceForThread(this.topicID, message.id)];
8714
+ case 1:
8715
+ _b.selectedMessageThread = _c.sent();
8716
+ return [2 /*return*/];
8717
+ }
8718
+ });
8719
+ }); }, 250);
8720
+ return [2 /*return*/];
8547
8721
  });
8548
8722
  });
8549
8723
  };
@@ -8572,20 +8746,29 @@
8572
8746
  });
8573
8747
  });
8574
8748
  };
8749
+ BantaCommentsComponent.prototype.scrollToMessage = function (message) {
8750
+ var el = this.elementRef.nativeElement.querySelector("[data-comment-id=\"" + message.id + "\"]");
8751
+ if (!el)
8752
+ return;
8753
+ el.scrollIntoView({ block: 'center', inline: 'start' });
8754
+ };
8575
8755
  return BantaCommentsComponent;
8576
8756
  }());
8577
8757
  BantaCommentsComponent.decorators = [
8578
8758
  { type: core.Component, args: [{
8579
8759
  selector: 'banta-comments',
8580
- template: "\r\n<div class=\"focused\" *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 Reply\r\n <form class=\"new-message\" (submit)=\"sendReply()\">\r\n <div class=\"text-container\">\r\n <textarea \r\n name=\"message\" \r\n (keydown)=\"onReplyKeyDown($event)\"\r\n [(ngModel)]=\"replyMessage\"></textarea>\r\n <emoji-selector-button \r\n class=\"top-right\"\r\n (selected)=\"insertReplyEmoji($event)\"\r\n ></emoji-selector-button>\r\n </div>\r\n <button [disabled]=\"!replyMessage\" \r\n mat-raised-button color=\"primary\">Send</button>\r\n </form>\r\n <banta-comment-view \r\n [source]=\"selectedMessageThread\"\r\n [allowReplies]=\"false\"\r\n [fixedHeight]=\"false\"\r\n [showEmptyState]=\"false\"\r\n ></banta-comment-view>\r\n </div>\r\n</div>\r\n\r\n<form class=\"new-message\" (submit)=\"sendMessage()\" *ngIf=\"!selectedMessage\">\r\n <div class=\"text-container\">\r\n <textarea \r\n name=\"message\" \r\n placeholder=\"Type your comment\"\r\n (keydown)=\"onKeyDown($event)\"\r\n [(ngModel)]=\"newMessageText\"></textarea>\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 color=\"primary\"\r\n [disabled]=\"!newMessageText\" \r\n >{{sendLabel}}</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\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 ></banta-comment-view>",
8581
- styles: [":host{flex-direction:column}:host,form{display:flex}form{align-items:center;padding:.5em 0}form .text-container{display:flex;flex-grow:1;position:relative}form .text-container textarea{background-color:#fff;border:1px solid #ccc;color:#333;font-size:14pt;min-height:6em;width:100%}form .text-container emoji-selector-button{bottom:0;position:absolute;right:0}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}:host-context(.mat-dark-theme) form .text-container textarea{background:#000;border-color:#333;color:#fff}.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}form.new-message{align-items:flex-start;display:flex}form.new-message mat-form-field{flex-grow:1}form.new-message button{margin-left:1em;margin-top:.5em}"]
8760
+ 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 [user]=\"user\"\r\n [label]=\"postReplyLabel\"\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 ></banta-comment-field>\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 ></banta-comment-view>\r\n</div>",
8761
+ 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}"]
8582
8762
  },] }
8583
8763
  ];
8584
8764
  BantaCommentsComponent.ctorParameters = function () { return [
8585
8765
  { type: BantaService },
8586
- { type: ChatBackendService }
8766
+ { type: ChatBackendService },
8767
+ { type: core.ElementRef }
8587
8768
  ]; };
8588
8769
  BantaCommentsComponent.propDecorators = {
8770
+ hashtags: [{ type: core.Input }],
8771
+ participants: [{ type: core.Input }],
8589
8772
  source: [{ type: core.Input }],
8590
8773
  fixedHeight: [{ type: core.Input }],
8591
8774
  maxMessages: [{ type: core.Input }],
@@ -8594,8 +8777,13 @@
8594
8777
  topicID: [{ type: core.Input }],
8595
8778
  signInLabel: [{ type: core.Input }],
8596
8779
  sendLabel: [{ type: core.Input }],
8780
+ replyLabel: [{ type: core.Input }],
8781
+ sendingLabel: [{ type: core.Input }],
8597
8782
  permissionDeniedLabel: [{ type: core.Input }],
8783
+ postCommentLabel: [{ type: core.Input }],
8784
+ postReplyLabel: [{ type: core.Input }],
8598
8785
  signInSelected: [{ type: core.Output }],
8786
+ editAvatarSelected: [{ type: core.Output }],
8599
8787
  permissionDeniedError: [{ type: core.Output }],
8600
8788
  upvoted: [{ type: core.Output }],
8601
8789
  reported: [{ type: core.Output }],
@@ -8681,11 +8869,277 @@
8681
8869
  message: [{ type: core.Input }]
8682
8870
  };
8683
8871
 
8872
+ var CommentFieldComponent = /** @class */ (function () {
8873
+ function CommentFieldComponent() {
8874
+ this.canComment = true;
8875
+ this.signInSelected = new rxjs.Subject();
8876
+ this.editAvatarSelected = new rxjs.Subject();
8877
+ this.sending = false;
8878
+ this.expandError = false;
8879
+ this.text = '';
8880
+ this.sendLabel = 'Send';
8881
+ this.sendingLabel = 'Sending';
8882
+ this.label = 'Post a comment';
8883
+ this.permissionDeniedLabel = 'Unavailable';
8884
+ this.signInLabel = 'Sign In';
8885
+ this.participants = [];
8886
+ this.autocompleteVisible = false;
8887
+ this.autocompleteOptions = [];
8888
+ this.autoCompleteSelected = 0;
8889
+ }
8890
+ CommentFieldComponent.prototype.ngAfterViewInit = function () {
8891
+ var root = document.body.querySelector('[ng-version]') || document.body;
8892
+ root.appendChild(this.autocompleteEl.nativeElement);
8893
+ };
8894
+ CommentFieldComponent.prototype.showAutoComplete = function (options) {
8895
+ this.autoCompleteSelected = 0;
8896
+ this.autocompleteOptions = options;
8897
+ var pos = this.autocompleteContainerEl.nativeElement.getBoundingClientRect();
8898
+ var size = this.autocompleteEl.nativeElement.getBoundingClientRect();
8899
+ this.autocompleteEl.nativeElement.style.left = pos.left + "px";
8900
+ this.autocompleteEl.nativeElement.style.top = pos.top + "px";
8901
+ this.autocompleteEl.nativeElement.style.width = pos.width + "px";
8902
+ this.autocompleteVisible = true;
8903
+ };
8904
+ CommentFieldComponent.prototype.activateAutoComplete = function (option) {
8905
+ option.action();
8906
+ this.dismissAutoComplete();
8907
+ };
8908
+ CommentFieldComponent.prototype.dismissAutoComplete = function () {
8909
+ this.autocompleteVisible = false;
8910
+ this.completionFunc = null;
8911
+ this.completionPrefix = '';
8912
+ };
8913
+ CommentFieldComponent.prototype.indicateError = function (message) {
8914
+ var _this = this;
8915
+ this.sendError = new Error(message);
8916
+ setTimeout(function () {
8917
+ _this.expandError = true;
8918
+ setTimeout(function () {
8919
+ _this.expandError = false;
8920
+ }, 5 * 1000);
8921
+ });
8922
+ };
8923
+ CommentFieldComponent.prototype.autocomplete = function (replacement) {
8924
+ return __awaiter(this, void 0, void 0, function () {
8925
+ var el;
8926
+ return __generator(this, function (_a) {
8927
+ el = this.textareaEl.nativeElement;
8928
+ this.text = this.text.slice(0, el.selectionStart - this.completionPrefix.length) + replacement + this.text.slice(el.selectionStart);
8929
+ return [2 /*return*/];
8930
+ });
8931
+ });
8932
+ };
8933
+ CommentFieldComponent.prototype.insert = function (str) {
8934
+ return __awaiter(this, void 0, void 0, function () {
8935
+ var el;
8936
+ return __generator(this, function (_a) {
8937
+ el = this.textareaEl.nativeElement;
8938
+ this.text = this.text.slice(0, el.selectionStart) + str + this.text.slice(el.selectionStart);
8939
+ return [2 /*return*/];
8940
+ });
8941
+ });
8942
+ };
8943
+ CommentFieldComponent.prototype.onKeyDown = function (event) {
8944
+ return __awaiter(this, void 0, void 0, function () {
8945
+ var _this = this;
8946
+ return __generator(this, function (_a) {
8947
+ switch (_a.label) {
8948
+ case 0:
8949
+ console.log(event.key);
8950
+ if (this.autocompleteVisible) {
8951
+ if (event.key === 'Escape') {
8952
+ this.dismissAutoComplete();
8953
+ return [2 /*return*/];
8954
+ }
8955
+ if (event.key === 'Shift') {
8956
+ return [2 /*return*/];
8957
+ }
8958
+ if (event.key === 'Enter') {
8959
+ this.activateAutoComplete(this.autocompleteOptions[this.autoCompleteSelected]);
8960
+ event.stopPropagation();
8961
+ event.preventDefault();
8962
+ return [2 /*return*/];
8963
+ }
8964
+ if (event.key === 'ArrowUp') {
8965
+ if (this.autoCompleteSelected === 0)
8966
+ this.autoCompleteSelected = this.autocompleteOptions.length - 1;
8967
+ else
8968
+ this.autoCompleteSelected = this.autoCompleteSelected - 1;
8969
+ event.stopPropagation();
8970
+ event.preventDefault();
8971
+ return [2 /*return*/];
8972
+ }
8973
+ else if (event.key === 'ArrowDown') {
8974
+ this.autoCompleteSelected = (this.autoCompleteSelected + 1) % this.autocompleteOptions.length;
8975
+ event.stopPropagation();
8976
+ event.preventDefault();
8977
+ return [2 /*return*/];
8978
+ }
8979
+ }
8980
+ if (!(event.key === 'Enter' && event.ctrlKey)) return [3 /*break*/, 2];
8981
+ return [4 /*yield*/, this.sendMessage()];
8982
+ case 1:
8983
+ _a.sent();
8984
+ return [2 /*return*/];
8985
+ case 2:
8986
+ if (this.completionFunc) {
8987
+ if (event.key === 'Backspace') {
8988
+ this.completionPrefix = this.completionPrefix.slice(0, this.completionPrefix.length - 1);
8989
+ if (this.completionPrefix === '') {
8990
+ this.dismissAutoComplete();
8991
+ return [2 /*return*/];
8992
+ }
8993
+ }
8994
+ else if (event.key === ' ' || event.key.length > 1) {
8995
+ this.dismissAutoComplete();
8996
+ return [2 /*return*/];
8997
+ }
8998
+ else {
8999
+ this.completionPrefix += event.key;
9000
+ }
9001
+ this.showAutoComplete(this.completionFunc(this.completionPrefix));
9002
+ }
9003
+ else {
9004
+ if (event.key === ':') {
9005
+ this.startAutoComplete(event, function (prefix) {
9006
+ prefix = prefix.slice(1);
9007
+ // makes :-), :-( etc work (as they are ":)" etc in the db)
9008
+ if (prefix.startsWith('-'))
9009
+ prefix = prefix.slice(1);
9010
+ return Object.keys(EMOJIS)
9011
+ .filter(function (k) { return k.includes(prefix) || EMOJIS[k].keywords.some(function (kw) { return kw.includes(prefix); }); })
9012
+ .map(function (k) { return ({
9013
+ label: EMOJIS[k].char + " " + k,
9014
+ action: function () { return _this.autocomplete(EMOJIS[k].char); }
9015
+ }); })
9016
+ .slice(0, 5);
9017
+ });
9018
+ }
9019
+ else if (event.key === '@') {
9020
+ this.startAutoComplete(event, function (prefix) {
9021
+ prefix = prefix.slice(1);
9022
+ return _this.participants.filter(function (x) { return x.username.includes(prefix); })
9023
+ .map(function (p) { return ({
9024
+ label: "@" + p.username + " -- " + p.displayName,
9025
+ action: function () { return _this.autocomplete("@" + p.username); }
9026
+ }); });
9027
+ });
9028
+ }
9029
+ else if (event.key === '#') {
9030
+ this.startAutoComplete(event, function (prefix) {
9031
+ prefix = prefix.slice(1);
9032
+ return _this.hashtags
9033
+ .filter(function (ht) { return ht.hashtag.includes(prefix); })
9034
+ .map(function (ht) { return ({
9035
+ label: "#" + ht.hashtag + (ht.description ? " -- " + ht.description : ""),
9036
+ action: function () { return _this.autocomplete("#" + ht.hashtag); }
9037
+ }); })
9038
+ .slice(0, 5);
9039
+ });
9040
+ }
9041
+ }
9042
+ return [2 /*return*/];
9043
+ }
9044
+ });
9045
+ });
9046
+ };
9047
+ CommentFieldComponent.prototype.startAutoComplete = function (event, completionFunc) {
9048
+ this.completionPrefix = event.key;
9049
+ this.completionFunc = completionFunc;
9050
+ this.showAutoComplete(this.completionFunc(this.completionPrefix));
9051
+ };
9052
+ CommentFieldComponent.prototype.onBlur = function () {
9053
+ var _this = this;
9054
+ setTimeout(function () { return _this.dismissAutoComplete(); }, 250);
9055
+ };
9056
+ CommentFieldComponent.prototype.insertEmoji = function (text) {
9057
+ this.text += text;
9058
+ };
9059
+ CommentFieldComponent.prototype.showSignIn = function () {
9060
+ this.signInSelected.next();
9061
+ };
9062
+ CommentFieldComponent.prototype.showEditAvatar = function () {
9063
+ this.editAvatarSelected.next();
9064
+ };
9065
+ CommentFieldComponent.prototype.sendMessage = function () {
9066
+ return __awaiter(this, void 0, void 0, function () {
9067
+ var text, message, e_1;
9068
+ return __generator(this, function (_a) {
9069
+ switch (_a.label) {
9070
+ case 0:
9071
+ if (!this.source)
9072
+ return [2 /*return*/];
9073
+ this.sending = true;
9074
+ this.sendError = null;
9075
+ _a.label = 1;
9076
+ case 1:
9077
+ _a.trys.push([1, , 6, 7]);
9078
+ text = (this.text || '').trim();
9079
+ if (text === '')
9080
+ return [2 /*return*/];
9081
+ message = {
9082
+ user: this.user,
9083
+ sentAt: Date.now(),
9084
+ upvotes: 0,
9085
+ message: text
9086
+ };
9087
+ _a.label = 2;
9088
+ case 2:
9089
+ _a.trys.push([2, 4, , 5]);
9090
+ return [4 /*yield*/, this.source.send(message)];
9091
+ case 3:
9092
+ _a.sent();
9093
+ this.text = '';
9094
+ return [3 /*break*/, 5];
9095
+ case 4:
9096
+ e_1 = _a.sent();
9097
+ this.indicateError("Could not send: " + e_1.message);
9098
+ console.error("Failed to send message: ", message);
9099
+ console.error(e_1);
9100
+ return [3 /*break*/, 5];
9101
+ case 5: return [3 /*break*/, 7];
9102
+ case 6:
9103
+ this.sending = false;
9104
+ return [7 /*endfinally*/];
9105
+ case 7: return [2 /*return*/];
9106
+ }
9107
+ });
9108
+ });
9109
+ };
9110
+ return CommentFieldComponent;
9111
+ }());
9112
+ CommentFieldComponent.decorators = [
9113
+ { type: core.Component, args: [{
9114
+ selector: 'banta-comment-field',
9115
+ 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=\"Type your comment\"\r\n matInput\r\n cdkTextareaAutosize \r\n (keydown)=\"onKeyDown($event)\"\r\n (blur)=\"onBlur()\"\r\n [disabled]=\"sending\"\r\n [(ngModel)]=\"text\"></textarea>\r\n </mat-form-field>\r\n <div #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 <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\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",
9116
+ 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 0}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 1em}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}"]
9117
+ },] }
9118
+ ];
9119
+ CommentFieldComponent.propDecorators = {
9120
+ source: [{ type: core.Input }],
9121
+ user: [{ type: core.Input }],
9122
+ canComment: [{ type: core.Input }],
9123
+ signInSelected: [{ type: core.Output }],
9124
+ editAvatarSelected: [{ type: core.Output }],
9125
+ sendLabel: [{ type: core.Input }],
9126
+ sendingLabel: [{ type: core.Input }],
9127
+ label: [{ type: core.Input }],
9128
+ permissionDeniedLabel: [{ type: core.Input }],
9129
+ signInLabel: [{ type: core.Input }],
9130
+ autocompleteEl: [{ type: core.ViewChild, args: ['autocomplete',] }],
9131
+ autocompleteContainerEl: [{ type: core.ViewChild, args: ['autocompleteContainer',] }],
9132
+ textareaEl: [{ type: core.ViewChild, args: ['textarea',] }],
9133
+ hashtags: [{ type: core.Input }],
9134
+ participants: [{ type: core.Input }]
9135
+ };
9136
+
8684
9137
  var COMPONENTS$3 = [
8685
9138
  CommentComponent,
8686
9139
  CommentViewComponent,
8687
9140
  BantaCommentsComponent,
8688
- LiveCommentComponent
9141
+ LiveCommentComponent,
9142
+ CommentFieldComponent
8689
9143
  ];
8690
9144
  var CommentsModule = /** @class */ (function () {
8691
9145
  function CommentsModule() {
@@ -8778,6 +9232,7 @@
8778
9232
  exports.ChatModule = ChatModule;
8779
9233
  exports.ChatViewComponent = ChatViewComponent;
8780
9234
  exports.CommentComponent = CommentComponent;
9235
+ exports.CommentFieldComponent = CommentFieldComponent;
8781
9236
  exports.CommentViewComponent = CommentViewComponent;
8782
9237
  exports.CommentsModule = CommentsModule;
8783
9238
  exports.EMOJIS = EMOJIS;