@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,18 +1,18 @@
1
1
  import { Observable, Subject, BehaviorSubject } from 'rxjs';
2
2
  import { publish } from 'rxjs/operators';
3
- import { Injectable, Component, Input, NgModule, Output, ElementRef, ViewChild, HostBinding } from '@angular/core';
3
+ import { Injectable, Component, Input, NgModule, Output, ViewChild, ElementRef, HostBinding } 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';
7
7
  import { MatButtonModule } from '@angular/material/button';
8
+ import { MatFormFieldModule } from '@angular/material/form-field';
9
+ import { MatInputModule } from '@angular/material/input';
10
+ import { FormsModule } from '@angular/forms';
8
11
  import { __awaiter } from 'tslib';
9
12
  import { SubSink } from 'subsink';
10
13
  import { MatDialog, MatDialogModule } from '@angular/material/dialog';
11
- import { FormsModule } from '@angular/forms';
12
14
  import { MatMenuModule } from '@angular/material/menu';
13
15
  import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
14
- import { MatFormFieldModule } from '@angular/material/form-field';
15
- import { MatInputModule } from '@angular/material/input';
16
16
  import { TextFieldModule } from '@angular/cdk/text-field';
17
17
  import { MatTooltipModule } from '@angular/material/tooltip';
18
18
 
@@ -6686,14 +6686,42 @@ class EmojiSelectorPanelComponent {
6686
6686
  constructor(sanitizer) {
6687
6687
  this.sanitizer = sanitizer;
6688
6688
  this.activeCategory = 'people';
6689
+ this.searchResults = [];
6690
+ this.searchVisible = false;
6689
6691
  this.selected = new Subject();
6690
6692
  }
6693
+ get searchQuery() {
6694
+ return this._searchQuery;
6695
+ }
6696
+ set searchQuery(value) {
6697
+ this._searchQuery = value;
6698
+ setTimeout(() => {
6699
+ this.searchResults = Object.keys(EMOJIS).filter(k => k.includes(value)).map(k => EMOJIS[k]);
6700
+ this.searchResults.splice(50, this.searchResults.length);
6701
+ console.log(`looking for '${value}' => ${this.searchResults.length} results`);
6702
+ });
6703
+ }
6704
+ humanize(str) {
6705
+ return str.replace(/(^| )[a-z]/g, k => k.toUpperCase()).replace(/_/g, ' ');
6706
+ }
6691
6707
  select(char) {
6692
6708
  this.selected.next(char);
6693
6709
  }
6694
6710
  pairs(object) {
6695
6711
  return Object.keys(object).map(key => [key, object[key]]);
6696
6712
  }
6713
+ hideSearch() {
6714
+ // because of the "outside click detection"
6715
+ setTimeout(() => {
6716
+ this.searchVisible = false;
6717
+ });
6718
+ }
6719
+ showSearch() {
6720
+ // because of the "outside click detection"
6721
+ setTimeout(() => {
6722
+ this.searchVisible = true;
6723
+ });
6724
+ }
6697
6725
  ngOnInit() {
6698
6726
  let cats = {};
6699
6727
  let categoryIcons = {
@@ -6725,8 +6753,8 @@ class EmojiSelectorPanelComponent {
6725
6753
  EmojiSelectorPanelComponent.decorators = [
6726
6754
  { type: Component, args: [{
6727
6755
  selector: 'emoji-selector-panel',
6728
- 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>",
6729
- 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}"]
6756
+ 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>",
6757
+ 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}"]
6730
6758
  },] }
6731
6759
  ];
6732
6760
  EmojiSelectorPanelComponent.ctorParameters = () => [
@@ -6746,6 +6774,11 @@ class EmojiSelectorButtonComponent {
6746
6774
  }
6747
6775
  ngOnDestroy() {
6748
6776
  this.removeListener();
6777
+ this.panelElement.nativeElement.remove();
6778
+ }
6779
+ ngAfterViewInit() {
6780
+ let root = document.body.querySelector('[ng-version]') || document.body;
6781
+ root.appendChild(this.panelElement.nativeElement);
6749
6782
  }
6750
6783
  removeListener() {
6751
6784
  document.removeEventListener('click', this.clickListener);
@@ -6756,6 +6789,12 @@ class EmojiSelectorButtonComponent {
6756
6789
  return;
6757
6790
  }
6758
6791
  this.showEmojiPanel = true;
6792
+ let pos = this.buttonElement.nativeElement.getBoundingClientRect();
6793
+ let size = this.panelElement.nativeElement.getBoundingClientRect();
6794
+ Object.assign(this.panelElement.nativeElement.style, {
6795
+ top: `${pos.top + pos.height}px`,
6796
+ right: `${Math.max(0, window.innerWidth - pos.left - pos.width)}px`
6797
+ });
6759
6798
  setTimeout(() => {
6760
6799
  this.clickListener = (ev) => {
6761
6800
  let parent = ev.target;
@@ -6781,10 +6820,11 @@ EmojiSelectorButtonComponent.decorators = [
6781
6820
  { type: Component, args: [{
6782
6821
  selector: 'emoji-selector-button',
6783
6822
  template: `
6784
- <button mat-icon-button (click)="show()">
6823
+ <button #button type="button" mat-icon-button (click)="show()">
6785
6824
  <mat-icon>emoji_emotions</mat-icon>
6786
6825
  </button>
6787
6826
  <emoji-selector-panel
6827
+ #panel
6788
6828
  (selected)="insert($event)"
6789
6829
  [class.visible]="showEmojiPanel"
6790
6830
  ></emoji-selector-panel>
@@ -6797,8 +6837,8 @@ EmojiSelectorButtonComponent.decorators = [
6797
6837
 
6798
6838
  emoji-selector-panel {
6799
6839
  position: absolute;
6800
- bottom: 2.5em;
6801
- right: 0;
6840
+ /* bottom: 2.5em;
6841
+ right: 0; */
6802
6842
  opacity: 0;
6803
6843
  pointer-events: none;
6804
6844
  z-index: 10;
@@ -6813,7 +6853,7 @@ EmojiSelectorButtonComponent.decorators = [
6813
6853
  color: #666
6814
6854
  }
6815
6855
 
6816
- :host.bottom-left emoji-selector-panel {
6856
+ /* :host.bottom-left emoji-selector-panel {
6817
6857
  right: auto;
6818
6858
  left: 0;
6819
6859
  }
@@ -6828,12 +6868,14 @@ EmojiSelectorButtonComponent.decorators = [
6828
6868
  bottom: auto;
6829
6869
  left: 0;
6830
6870
  right: auto;
6831
- }
6871
+ } */
6832
6872
  `]
6833
6873
  },] }
6834
6874
  ];
6835
6875
  EmojiSelectorButtonComponent.propDecorators = {
6836
- selected: [{ type: Output }]
6876
+ selected: [{ type: Output }],
6877
+ panelElement: [{ type: ViewChild, args: ['panel', { read: ElementRef },] }],
6878
+ buttonElement: [{ type: ViewChild, args: ['button', { read: ElementRef },] }]
6837
6879
  };
6838
6880
 
6839
6881
  const COMPONENTS$1 = [
@@ -6847,8 +6889,11 @@ EmojiModule.decorators = [
6847
6889
  declarations: COMPONENTS$1,
6848
6890
  imports: [
6849
6891
  CommonModule,
6892
+ FormsModule,
6850
6893
  MatIconModule,
6851
- MatButtonModule
6894
+ MatButtonModule,
6895
+ MatFormFieldModule,
6896
+ MatInputModule
6852
6897
  ],
6853
6898
  exports: COMPONENTS$1
6854
6899
  },] }
@@ -7217,8 +7262,12 @@ class BantaChatComponent {
7217
7262
  }
7218
7263
  get canChat() {
7219
7264
  var _a;
7265
+ if (!this.user)
7266
+ return false;
7220
7267
  if (!this.user.permissions)
7221
7268
  return true;
7269
+ if (!this.user.permissions.canChat)
7270
+ return true;
7222
7271
  return (_a = this.user.permissions) === null || _a === void 0 ? void 0 : _a.canChat(this.source);
7223
7272
  }
7224
7273
  sendMessage() {
@@ -7315,8 +7364,6 @@ class BantaComponent {
7315
7364
  this.pointSubChat = null;
7316
7365
  this.newPointSubMessage = {};
7317
7366
  this.genericAvatarUrl = 'https://gravatar.com/avatar/915c804e0be607a4ad766ddadea5c48a?s=512&d=https://codepen.io/assets/avatars/user-avatar-512x512-6e240cf350d2f1cc07c2bed234c3a3bb5f1b237023c204c782622e80d6b212ba.png';
7318
- // this.firehoseSource = new MockFirehoseSource();
7319
- // this.pointSource = new MockPointSource();
7320
7367
  }
7321
7368
  ngOnInit() {
7322
7369
  this._subs.add(this.banta.userChanged.subscribe(user => this.currentUser = user), this.backend.notificationsChanged.subscribe(notifs => this.notifications = notifs), this.backend.newNotification.subscribe(notif => {
@@ -7468,7 +7515,7 @@ class BantaComponent {
7468
7515
  BantaComponent.decorators = [
7469
7516
  { type: Component, args: [{
7470
7517
  selector: `banta`,
7471
- 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>",
7518
+ 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>",
7472
7519
  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}"]
7473
7520
  },] }
7474
7521
  ];
@@ -7583,8 +7630,20 @@ class CommentComponent {
7583
7630
  this._selected = new Subject();
7584
7631
  this._upvoted = new Subject();
7585
7632
  this._userSelected = new Subject();
7633
+ this.isNew = false;
7634
+ this.visible = false;
7586
7635
  this.showReplyAction = true;
7587
7636
  }
7637
+ ngOnInit() {
7638
+ let maxTime = 500;
7639
+ let minTime = 0;
7640
+ let randomTime = minTime + Math.random() * (maxTime - minTime);
7641
+ setTimeout(() => {
7642
+ this.isNew = true;
7643
+ this.visible = true;
7644
+ setTimeout(() => this.isNew = false, 1000);
7645
+ }, randomTime);
7646
+ }
7588
7647
  get userSelected() {
7589
7648
  return this._userSelected;
7590
7649
  }
@@ -7597,6 +7656,10 @@ class CommentComponent {
7597
7656
  get selected() {
7598
7657
  return this._selected;
7599
7658
  }
7659
+ get commentId() {
7660
+ var _a;
7661
+ return (_a = this.message) === null || _a === void 0 ? void 0 : _a.id;
7662
+ }
7600
7663
  report() {
7601
7664
  this._reported.next();
7602
7665
  }
@@ -7620,17 +7683,20 @@ class CommentComponent {
7620
7683
  CommentComponent.decorators = [
7621
7684
  { type: Component, args: [{
7622
7685
  selector: 'banta-comment',
7623
- 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",
7624
- 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}"]
7686
+ 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",
7687
+ 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}"]
7625
7688
  },] }
7626
7689
  ];
7627
7690
  CommentComponent.propDecorators = {
7691
+ isNew: [{ type: HostBinding, args: ['class.new',] }],
7692
+ visible: [{ type: HostBinding, args: ['class.visible',] }],
7628
7693
  message: [{ type: Input }],
7629
7694
  showReplyAction: [{ type: Input }],
7630
7695
  userSelected: [{ type: Output }],
7631
7696
  reported: [{ type: Output }],
7632
7697
  upvoted: [{ type: Output }],
7633
- selected: [{ type: Output }]
7698
+ selected: [{ type: Output }],
7699
+ commentId: [{ type: HostBinding, args: ['attr.data-comment-id',] }]
7634
7700
  };
7635
7701
 
7636
7702
  class CommentViewComponent {
@@ -7703,7 +7769,7 @@ class CommentViewComponent {
7703
7769
  }
7704
7770
  }
7705
7771
  }
7706
- messageIdentity(chatMessage) {
7772
+ messageIdentity(index, chatMessage) {
7707
7773
  return chatMessage.id;
7708
7774
  }
7709
7775
  showNew() {
@@ -7800,8 +7866,8 @@ class CommentViewComponent {
7800
7866
  CommentViewComponent.decorators = [
7801
7867
  { type: Component, args: [{
7802
7868
  selector: 'banta-comment-view',
7803
- 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>",
7804
- 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}"]
7869
+ 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>",
7870
+ 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}"]
7805
7871
  },] }
7806
7872
  ];
7807
7873
  CommentViewComponent.ctorParameters = () => [
@@ -7827,19 +7893,34 @@ CommentViewComponent.propDecorators = {
7827
7893
  * Comments component
7828
7894
  */
7829
7895
  class BantaCommentsComponent {
7830
- constructor(banta, backend) {
7896
+ constructor(banta, backend, elementRef) {
7831
7897
  this.banta = banta;
7832
7898
  this.backend = backend;
7899
+ this.elementRef = elementRef;
7833
7900
  this._upvoted = new Subject();
7834
7901
  this._reported = new Subject();
7835
7902
  this._selected = new Subject();
7836
7903
  this._userSelected = new Subject();
7837
7904
  this._subs = new SubSink();
7905
+ this.hashtags = [
7906
+ { hashtag: 'error', description: 'Cause an error' },
7907
+ { hashtag: 'timeout', description: 'Cause a slow timeout error' },
7908
+ { hashtag: 'slow', description: 'Be slow when this message is posted' },
7909
+ ];
7910
+ this.participants = [];
7838
7911
  this.signInLabel = 'Sign In';
7839
7912
  this.sendLabel = 'Send';
7913
+ this.replyLabel = 'Reply';
7914
+ this.sendingLabel = 'Sending';
7840
7915
  this.permissionDeniedLabel = 'Send';
7916
+ this.postCommentLabel = 'Post a comment';
7917
+ this.postReplyLabel = 'Post a reply';
7841
7918
  this._signInSelected = new Subject();
7842
7919
  this._permissionDeniedError = new Subject();
7920
+ this._editAvatarSelected = new Subject();
7921
+ this.sending = false;
7922
+ this.expandError = false;
7923
+ this.selectedMessageVisible = false;
7843
7924
  }
7844
7925
  ngOnInit() {
7845
7926
  this._subs.add(this.banta.userChanged.subscribe(user => this.user = user));
@@ -7865,14 +7946,39 @@ class BantaCommentsComponent {
7865
7946
  this._source.close();
7866
7947
  this._source = null;
7867
7948
  this._source = yield this.backend.getSourceForTopic(topicID);
7949
+ this._source.messageReceived.subscribe(m => this.addParticipant(m));
7950
+ this._source.messageSent.subscribe(m => this.addParticipant(m));
7951
+ this._source.messages.forEach(m => this.addParticipant(m));
7868
7952
  });
7869
7953
  }
7954
+ addParticipant(message) {
7955
+ if (!message || !message.user || !message.user.id)
7956
+ return;
7957
+ let existing = this.participants.find(x => x.id === message.user.id);
7958
+ if (existing)
7959
+ return;
7960
+ this.participants.push(message.user);
7961
+ }
7870
7962
  showSignIn() {
7871
7963
  this._signInSelected.next();
7872
7964
  }
7965
+ showEditAvatar() {
7966
+ this._editAvatarSelected.next();
7967
+ }
7968
+ get newMessageText() {
7969
+ return this._newMessageText;
7970
+ }
7971
+ set newMessageText(value) {
7972
+ this._newMessageText = value;
7973
+ if (this._newMessageText === '' && this.sendError)
7974
+ setTimeout(() => this.sendError = null);
7975
+ }
7873
7976
  get signInSelected() {
7874
7977
  return this._signInSelected;
7875
7978
  }
7979
+ get editAvatarSelected() {
7980
+ return this._editAvatarSelected;
7981
+ }
7876
7982
  get permissionDeniedError() {
7877
7983
  return this._permissionDeniedError;
7878
7984
  }
@@ -7881,8 +7987,12 @@ class BantaCommentsComponent {
7881
7987
  }
7882
7988
  get canComment() {
7883
7989
  var _a;
7990
+ if (!this.user)
7991
+ return false;
7884
7992
  if (!this.user.permissions)
7885
7993
  return true;
7994
+ if (!this.user.permissions.canComment)
7995
+ return true;
7886
7996
  return (_a = this.user.permissions) === null || _a === void 0 ? void 0 : _a.canComment(this.source);
7887
7997
  }
7888
7998
  get upvoted() {
@@ -7907,37 +8017,58 @@ class BantaCommentsComponent {
7907
8017
  insertReplyEmoji(text) {
7908
8018
  this.replyMessage += text;
7909
8019
  }
8020
+ indicateError(message) {
8021
+ this.sendError = new Error(message);
8022
+ setTimeout(() => {
8023
+ this.expandError = true;
8024
+ setTimeout(() => {
8025
+ this.expandError = false;
8026
+ }, 5 * 1000);
8027
+ });
8028
+ }
7910
8029
  sendMessage() {
7911
8030
  return __awaiter(this, void 0, void 0, function* () {
7912
8031
  if (!this.source)
7913
8032
  return;
7914
- let text = (this.newMessageText || '').trim();
7915
- this.newMessageText = '';
7916
- if (text === '')
7917
- return;
7918
- let message = {
7919
- user: this.user,
7920
- sentAt: Date.now(),
7921
- upvotes: 0,
7922
- message: text
7923
- };
8033
+ this.sending = true;
8034
+ this.sendError = null;
7924
8035
  try {
7925
- yield this.source.send(message);
8036
+ let text = (this.newMessageText || '').trim();
8037
+ if (text === '')
8038
+ return;
8039
+ let message = {
8040
+ user: this.user,
8041
+ sentAt: Date.now(),
8042
+ upvotes: 0,
8043
+ message: text
8044
+ };
8045
+ try {
8046
+ yield this.source.send(message);
8047
+ this.newMessageText = '';
8048
+ }
8049
+ catch (e) {
8050
+ this.indicateError(`Could not send: ${e.message}`);
8051
+ console.error(`Failed to send message: `, message);
8052
+ console.error(e);
8053
+ }
7926
8054
  }
7927
- catch (e) {
7928
- console.error(`Failed to send message: `, message);
7929
- console.error(e);
8055
+ finally {
8056
+ this.sending = false;
7930
8057
  }
7931
8058
  });
7932
8059
  }
7933
8060
  upvoteMessage(message) {
7934
- this._upvoted.next(message);
8061
+ return __awaiter(this, void 0, void 0, function* () {
8062
+ this._upvoted.next(message);
8063
+ yield this.backend.upvoteMessage(message.topicId, message.parentMessageId ? message.parentMessageId : message.id, message.parentMessageId ? message.id : undefined);
8064
+ });
7935
8065
  }
7936
8066
  reportMessage(message) {
7937
8067
  this._reported.next(message);
7938
8068
  }
7939
8069
  unselectMessage() {
7940
8070
  return __awaiter(this, void 0, void 0, function* () {
8071
+ let message = this.selectedMessage;
7941
8072
  this._selected.next(null);
7942
8073
  this.selectedMessage = null;
7943
8074
  if (this.selectedMessageThread) {
@@ -7945,13 +8076,18 @@ class BantaCommentsComponent {
7945
8076
  this.selectedMessageThread.close();
7946
8077
  this.selectedMessageThread = null;
7947
8078
  }
8079
+ if (message)
8080
+ setTimeout(() => this.scrollToMessage(message));
7948
8081
  });
7949
8082
  }
7950
8083
  selectMessage(message) {
7951
8084
  return __awaiter(this, void 0, void 0, function* () {
7952
8085
  this._selected.next(message);
7953
8086
  this.selectedMessage = message;
7954
- this.selectedMessageThread = yield this.backend.getSourceForThread(this.topicID, message.id);
8087
+ setTimeout(() => this.selectedMessageVisible = true);
8088
+ setTimeout(() => __awaiter(this, void 0, void 0, function* () {
8089
+ this.selectedMessageThread = yield this.backend.getSourceForThread(this.topicID, message.id);
8090
+ }), 250);
7955
8091
  });
7956
8092
  }
7957
8093
  selectMessageUser(message) {
@@ -7972,19 +8108,28 @@ class BantaCommentsComponent {
7972
8108
  this.replyMessage = '';
7973
8109
  });
7974
8110
  }
8111
+ scrollToMessage(message) {
8112
+ let el = this.elementRef.nativeElement.querySelector(`[data-comment-id="${message.id}"]`);
8113
+ if (!el)
8114
+ return;
8115
+ el.scrollIntoView({ block: 'center', inline: 'start' });
8116
+ }
7975
8117
  }
7976
8118
  BantaCommentsComponent.decorators = [
7977
8119
  { type: Component, args: [{
7978
8120
  selector: 'banta-comments',
7979
- 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>",
7980
- 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}"]
8121
+ 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>",
8122
+ 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}"]
7981
8123
  },] }
7982
8124
  ];
7983
8125
  BantaCommentsComponent.ctorParameters = () => [
7984
8126
  { type: BantaService },
7985
- { type: ChatBackendService }
8127
+ { type: ChatBackendService },
8128
+ { type: ElementRef }
7986
8129
  ];
7987
8130
  BantaCommentsComponent.propDecorators = {
8131
+ hashtags: [{ type: Input }],
8132
+ participants: [{ type: Input }],
7988
8133
  source: [{ type: Input }],
7989
8134
  fixedHeight: [{ type: Input }],
7990
8135
  maxMessages: [{ type: Input }],
@@ -7993,8 +8138,13 @@ BantaCommentsComponent.propDecorators = {
7993
8138
  topicID: [{ type: Input }],
7994
8139
  signInLabel: [{ type: Input }],
7995
8140
  sendLabel: [{ type: Input }],
8141
+ replyLabel: [{ type: Input }],
8142
+ sendingLabel: [{ type: Input }],
7996
8143
  permissionDeniedLabel: [{ type: Input }],
8144
+ postCommentLabel: [{ type: Input }],
8145
+ postReplyLabel: [{ type: Input }],
7997
8146
  signInSelected: [{ type: Output }],
8147
+ editAvatarSelected: [{ type: Output }],
7998
8148
  permissionDeniedError: [{ type: Output }],
7999
8149
  upvoted: [{ type: Output }],
8000
8150
  reported: [{ type: Output }],
@@ -8070,11 +8220,243 @@ LiveCommentComponent.propDecorators = {
8070
8220
  message: [{ type: Input }]
8071
8221
  };
8072
8222
 
8223
+ class CommentFieldComponent {
8224
+ constructor() {
8225
+ this.canComment = true;
8226
+ this.signInSelected = new Subject();
8227
+ this.editAvatarSelected = new Subject();
8228
+ this.sending = false;
8229
+ this.expandError = false;
8230
+ this.text = '';
8231
+ this.sendLabel = 'Send';
8232
+ this.sendingLabel = 'Sending';
8233
+ this.label = 'Post a comment';
8234
+ this.permissionDeniedLabel = 'Unavailable';
8235
+ this.signInLabel = 'Sign In';
8236
+ this.participants = [];
8237
+ this.autocompleteVisible = false;
8238
+ this.autocompleteOptions = [];
8239
+ this.autoCompleteSelected = 0;
8240
+ }
8241
+ ngAfterViewInit() {
8242
+ let root = document.body.querySelector('[ng-version]') || document.body;
8243
+ root.appendChild(this.autocompleteEl.nativeElement);
8244
+ }
8245
+ showAutoComplete(options) {
8246
+ this.autoCompleteSelected = 0;
8247
+ this.autocompleteOptions = options;
8248
+ let pos = this.autocompleteContainerEl.nativeElement.getBoundingClientRect();
8249
+ let size = this.autocompleteEl.nativeElement.getBoundingClientRect();
8250
+ this.autocompleteEl.nativeElement.style.left = `${pos.left}px`;
8251
+ this.autocompleteEl.nativeElement.style.top = `${pos.top}px`;
8252
+ this.autocompleteEl.nativeElement.style.width = `${pos.width}px`;
8253
+ this.autocompleteVisible = true;
8254
+ }
8255
+ activateAutoComplete(option) {
8256
+ option.action();
8257
+ this.dismissAutoComplete();
8258
+ }
8259
+ dismissAutoComplete() {
8260
+ this.autocompleteVisible = false;
8261
+ this.completionFunc = null;
8262
+ this.completionPrefix = '';
8263
+ }
8264
+ indicateError(message) {
8265
+ this.sendError = new Error(message);
8266
+ setTimeout(() => {
8267
+ this.expandError = true;
8268
+ setTimeout(() => {
8269
+ this.expandError = false;
8270
+ }, 5 * 1000);
8271
+ });
8272
+ }
8273
+ autocomplete(replacement) {
8274
+ return __awaiter(this, void 0, void 0, function* () {
8275
+ let el = this.textareaEl.nativeElement;
8276
+ this.text = this.text.slice(0, el.selectionStart - this.completionPrefix.length) + replacement + this.text.slice(el.selectionStart);
8277
+ });
8278
+ }
8279
+ insert(str) {
8280
+ return __awaiter(this, void 0, void 0, function* () {
8281
+ let el = this.textareaEl.nativeElement;
8282
+ this.text = this.text.slice(0, el.selectionStart) + str + this.text.slice(el.selectionStart);
8283
+ });
8284
+ }
8285
+ onKeyDown(event) {
8286
+ return __awaiter(this, void 0, void 0, function* () {
8287
+ console.log(event.key);
8288
+ if (this.autocompleteVisible) {
8289
+ if (event.key === 'Escape') {
8290
+ this.dismissAutoComplete();
8291
+ return;
8292
+ }
8293
+ if (event.key === 'Shift') {
8294
+ return;
8295
+ }
8296
+ if (event.key === 'Enter') {
8297
+ this.activateAutoComplete(this.autocompleteOptions[this.autoCompleteSelected]);
8298
+ event.stopPropagation();
8299
+ event.preventDefault();
8300
+ return;
8301
+ }
8302
+ if (event.key === 'ArrowUp') {
8303
+ if (this.autoCompleteSelected === 0)
8304
+ this.autoCompleteSelected = this.autocompleteOptions.length - 1;
8305
+ else
8306
+ this.autoCompleteSelected = this.autoCompleteSelected - 1;
8307
+ event.stopPropagation();
8308
+ event.preventDefault();
8309
+ return;
8310
+ }
8311
+ else if (event.key === 'ArrowDown') {
8312
+ this.autoCompleteSelected = (this.autoCompleteSelected + 1) % this.autocompleteOptions.length;
8313
+ event.stopPropagation();
8314
+ event.preventDefault();
8315
+ return;
8316
+ }
8317
+ }
8318
+ if (event.key === 'Enter' && event.ctrlKey) {
8319
+ yield this.sendMessage();
8320
+ return;
8321
+ }
8322
+ if (this.completionFunc) {
8323
+ if (event.key === 'Backspace') {
8324
+ this.completionPrefix = this.completionPrefix.slice(0, this.completionPrefix.length - 1);
8325
+ if (this.completionPrefix === '') {
8326
+ this.dismissAutoComplete();
8327
+ return;
8328
+ }
8329
+ }
8330
+ else if (event.key === ' ' || event.key.length > 1) {
8331
+ this.dismissAutoComplete();
8332
+ return;
8333
+ }
8334
+ else {
8335
+ this.completionPrefix += event.key;
8336
+ }
8337
+ this.showAutoComplete(this.completionFunc(this.completionPrefix));
8338
+ }
8339
+ else {
8340
+ if (event.key === ':') {
8341
+ this.startAutoComplete(event, prefix => {
8342
+ prefix = prefix.slice(1);
8343
+ // makes :-), :-( etc work (as they are ":)" etc in the db)
8344
+ if (prefix.startsWith('-'))
8345
+ prefix = prefix.slice(1);
8346
+ return Object.keys(EMOJIS)
8347
+ .filter(k => k.includes(prefix) || EMOJIS[k].keywords.some(kw => kw.includes(prefix)))
8348
+ .map(k => ({
8349
+ label: `${EMOJIS[k].char} ${k}`,
8350
+ action: () => this.autocomplete(EMOJIS[k].char)
8351
+ }))
8352
+ .slice(0, 5);
8353
+ });
8354
+ }
8355
+ else if (event.key === '@') {
8356
+ this.startAutoComplete(event, prefix => {
8357
+ prefix = prefix.slice(1);
8358
+ return this.participants.filter(x => x.username.includes(prefix))
8359
+ .map(p => ({
8360
+ label: `@${p.username} -- ${p.displayName}`,
8361
+ action: () => this.autocomplete(`@${p.username}`)
8362
+ }));
8363
+ });
8364
+ }
8365
+ else if (event.key === '#') {
8366
+ this.startAutoComplete(event, prefix => {
8367
+ prefix = prefix.slice(1);
8368
+ return this.hashtags
8369
+ .filter(ht => ht.hashtag.includes(prefix))
8370
+ .map(ht => ({
8371
+ label: `#${ht.hashtag}${ht.description ? ` -- ${ht.description}` : ``}`,
8372
+ action: () => this.autocomplete(`#${ht.hashtag}`)
8373
+ }))
8374
+ .slice(0, 5);
8375
+ });
8376
+ }
8377
+ }
8378
+ });
8379
+ }
8380
+ startAutoComplete(event, completionFunc) {
8381
+ this.completionPrefix = event.key;
8382
+ this.completionFunc = completionFunc;
8383
+ this.showAutoComplete(this.completionFunc(this.completionPrefix));
8384
+ }
8385
+ onBlur() {
8386
+ setTimeout(() => this.dismissAutoComplete(), 250);
8387
+ }
8388
+ insertEmoji(text) {
8389
+ this.text += text;
8390
+ }
8391
+ showSignIn() {
8392
+ this.signInSelected.next();
8393
+ }
8394
+ showEditAvatar() {
8395
+ this.editAvatarSelected.next();
8396
+ }
8397
+ sendMessage() {
8398
+ return __awaiter(this, void 0, void 0, function* () {
8399
+ if (!this.source)
8400
+ return;
8401
+ this.sending = true;
8402
+ this.sendError = null;
8403
+ try {
8404
+ let text = (this.text || '').trim();
8405
+ if (text === '')
8406
+ return;
8407
+ let message = {
8408
+ user: this.user,
8409
+ sentAt: Date.now(),
8410
+ upvotes: 0,
8411
+ message: text
8412
+ };
8413
+ try {
8414
+ yield this.source.send(message);
8415
+ this.text = '';
8416
+ }
8417
+ catch (e) {
8418
+ this.indicateError(`Could not send: ${e.message}`);
8419
+ console.error(`Failed to send message: `, message);
8420
+ console.error(e);
8421
+ }
8422
+ }
8423
+ finally {
8424
+ this.sending = false;
8425
+ }
8426
+ });
8427
+ }
8428
+ }
8429
+ CommentFieldComponent.decorators = [
8430
+ { type: Component, args: [{
8431
+ selector: 'banta-comment-field',
8432
+ 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",
8433
+ 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}"]
8434
+ },] }
8435
+ ];
8436
+ CommentFieldComponent.propDecorators = {
8437
+ source: [{ type: Input }],
8438
+ user: [{ type: Input }],
8439
+ canComment: [{ type: Input }],
8440
+ signInSelected: [{ type: Output }],
8441
+ editAvatarSelected: [{ type: Output }],
8442
+ sendLabel: [{ type: Input }],
8443
+ sendingLabel: [{ type: Input }],
8444
+ label: [{ type: Input }],
8445
+ permissionDeniedLabel: [{ type: Input }],
8446
+ signInLabel: [{ type: Input }],
8447
+ autocompleteEl: [{ type: ViewChild, args: ['autocomplete',] }],
8448
+ autocompleteContainerEl: [{ type: ViewChild, args: ['autocompleteContainer',] }],
8449
+ textareaEl: [{ type: ViewChild, args: ['textarea',] }],
8450
+ hashtags: [{ type: Input }],
8451
+ participants: [{ type: Input }]
8452
+ };
8453
+
8073
8454
  const COMPONENTS$3 = [
8074
8455
  CommentComponent,
8075
8456
  CommentViewComponent,
8076
8457
  BantaCommentsComponent,
8077
- LiveCommentComponent
8458
+ LiveCommentComponent,
8459
+ CommentFieldComponent
8078
8460
  ];
8079
8461
  class CommentsModule {
8080
8462
  }
@@ -8149,5 +8531,5 @@ BantaSdkModule.decorators = [
8149
8531
  * Generated bundle index. Do not edit.
8150
8532
  */
8151
8533
 
8152
- export { BantaChatComponent, BantaCommentsComponent, BantaCommonModule, BantaComponent, BantaLogoComponent, BantaSdkModule, BantaService, ChatBackendService, ChatMessageComponent, ChatModule, ChatViewComponent, CommentComponent, CommentViewComponent, CommentsModule, EMOJIS, EmojiModule, EmojiSelectorButtonComponent, EmojiSelectorPanelComponent, LiveChatMessageComponent, LiveCommentComponent, LiveMessageComponent, TimestampComponent, lazyConnection };
8534
+ export { BantaChatComponent, BantaCommentsComponent, BantaCommonModule, BantaComponent, BantaLogoComponent, BantaSdkModule, BantaService, ChatBackendService, ChatMessageComponent, ChatModule, ChatViewComponent, CommentComponent, CommentFieldComponent, CommentViewComponent, CommentsModule, EMOJIS, EmojiModule, EmojiSelectorButtonComponent, EmojiSelectorPanelComponent, LiveChatMessageComponent, LiveCommentComponent, LiveMessageComponent, TimestampComponent, lazyConnection };
8153
8535
  //# sourceMappingURL=banta-sdk.js.map