@banta/sdk 4.7.10 → 4.7.12

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,7 +1,9 @@
1
1
  import { Observable, Subject, BehaviorSubject, Subscription } from 'rxjs';
2
2
  import { publish, take } from 'rxjs/operators';
3
3
  import * as i0 from '@angular/core';
4
- import { Component, Input, ViewChild, Pipe, Inject, Optional, Output, HostBinding, NgModule, ViewChildren, Directive, TemplateRef, ContentChild, Injectable } from '@angular/core';
4
+ import { Component, Input, ViewChild, Pipe, Inject, Optional, Output, HostBinding, NgModule, ViewChildren, Directive, TemplateRef, ContentChild, Injectable as Injectable$1 } from '@angular/core';
5
+ import * as i1 from 'projects/sdk/src/lib/common/timer-pool.service';
6
+ import { TimerPool as TimerPool$1 } from 'projects/sdk/src/lib/common/timer-pool.service';
5
7
  import * as i2 from '@angular/common';
6
8
  import { CommonModule } from '@angular/common';
7
9
  import * as i2$1 from '@angular/material/icon';
@@ -9,19 +11,22 @@ import { MatIconModule } from '@angular/material/icon';
9
11
  import * as marked from 'marked';
10
12
  import createDOMPurify from 'dompurify';
11
13
  import twemoji$1 from 'twemoji';
12
- import * as i1 from '@angular/platform-browser';
14
+ import * as i1$1 from '@angular/platform-browser';
13
15
  import * as i3 from '@angular/cdk/bidi';
14
16
  import * as i4 from '@angular/material/progress-spinner';
15
17
  import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
16
18
  import * as i6 from '@angular/material/button';
17
19
  import { MatButtonModule } from '@angular/material/button';
20
+ import { __decorate } from 'tslib';
21
+ import * as i1$3 from '@banta/common';
22
+ import { Injectable, CommentsOrder, SocketRPC, RpcEvent, DurableSocket } from '@banta/common';
18
23
  import * as i4$1 from '@angular/forms';
19
24
  import { FormsModule } from '@angular/forms';
20
25
  import * as i6$1 from '@angular/material/form-field';
21
26
  import { MatFormFieldModule } from '@angular/material/form-field';
22
27
  import * as i7 from '@angular/material/input';
23
28
  import { MatInputModule } from '@angular/material/input';
24
- import * as i1$1 from '@angular/cdk/overlay';
29
+ import * as i1$2 from '@angular/cdk/overlay';
25
30
  import { OverlayModule } from '@angular/cdk/overlay';
26
31
  import * as i4$2 from '@angular/cdk/portal';
27
32
  import { PortalModule } from '@angular/cdk/portal';
@@ -31,8 +36,6 @@ import * as i7$1 from '@angular/material/menu';
31
36
  import { MatMenuModule } from '@angular/material/menu';
32
37
  import * as i11 from '@angular/material/tooltip';
33
38
  import { MatTooltipModule } from '@angular/material/tooltip';
34
- import * as i1$2 from '@banta/common';
35
- import { CommentsOrder, SocketRPC, RpcEvent, DurableSocket } from '@banta/common';
36
39
  import * as i2$3 from '@angular/router';
37
40
  import * as i3$3 from '@angular/material/snack-bar';
38
41
  import { MatSnackBarModule } from '@angular/material/snack-bar';
@@ -41,7 +44,6 @@ import { TextFieldModule } from '@angular/cdk/text-field';
41
44
  import * as i2$2 from '@angular/material/select';
42
45
  import { MatSelectModule } from '@angular/material/select';
43
46
  import * as i3$2 from '@angular/material/core';
44
- import { __decorate } from 'tslib';
45
47
 
46
48
  function lazyConnection(options) {
47
49
  let obs = new Observable(observer => {
@@ -57,20 +59,24 @@ function lazyConnection(options) {
57
59
  }
58
60
 
59
61
  class TimestampComponent {
60
- constructor() {
62
+ constructor(timerPool) {
63
+ this.timerPool = timerPool;
61
64
  this.relative = '';
62
65
  this.tooltip = '';
63
- this.updateInterval = null;
66
+ this.timerInterval = 0;
67
+ this._destroyed = false;
64
68
  this.showAbsolute = false;
65
69
  }
66
70
  ngOnDestroy() {
67
- if (this.updateInterval)
68
- clearInterval(this.updateInterval);
71
+ this._destroyed = true;
72
+ this.timerUnsubscribe?.();
69
73
  }
70
74
  get value() {
71
75
  return this._value;
72
76
  }
73
77
  update() {
78
+ if (this._destroyed)
79
+ return;
74
80
  let now = Date.now();
75
81
  let diff = now - this.value;
76
82
  let minute = 1000 * 60;
@@ -132,18 +138,23 @@ class TimestampComponent {
132
138
  updateTime = 1000 * 30;
133
139
  }
134
140
  if (typeof window !== 'undefined') {
135
- clearInterval(this.updateInterval);
136
- if (updateTime > 0) {
137
- this.updateInterval = setInterval(() => this.update());
141
+ if (this.timerInterval !== updateTime) {
142
+ this.timerInterval = updateTime;
143
+ this.timerUnsubscribe?.();
144
+ if (updateTime > 0) {
145
+ this.timerUnsubscribe = this.timerPool.addTimer(updateTime, () => this.update());
146
+ }
138
147
  }
139
148
  }
140
149
  }
141
150
  set value(v) {
142
- this._value = v;
143
- this.update();
151
+ if (this._value !== v) {
152
+ this._value = v;
153
+ this.update();
154
+ }
144
155
  }
145
156
  }
146
- TimestampComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: TimestampComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
157
+ TimestampComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: TimestampComponent, deps: [{ token: i1.TimerPool }], target: i0.ɵɵFactoryTarget.Component });
147
158
  TimestampComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.2.12", type: TimestampComponent, selector: "banta-timestamp", inputs: { value: "value" }, ngImport: i0, template: `
148
159
  <span *ngIf="showAbsolute" [title]="value | date : 'short'">
149
160
  {{value | date : 'shortDate'}}
@@ -162,7 +173,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImpo
162
173
  {{relative}}
163
174
  </span>
164
175
  ` }]
165
- }], propDecorators: { value: [{
176
+ }], ctorParameters: function () { return [{ type: i1.TimerPool }]; }, propDecorators: { value: [{
166
177
  type: Input
167
178
  }] } });
168
179
 
@@ -264,14 +275,14 @@ class BantaMarkdownToHtmlPipe {
264
275
  }));
265
276
  }
266
277
  }
267
- BantaMarkdownToHtmlPipe.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: BantaMarkdownToHtmlPipe, deps: [{ token: i1.DomSanitizer }, { token: BANTA_SDK_OPTIONS, optional: true }], target: i0.ɵɵFactoryTarget.Pipe });
278
+ BantaMarkdownToHtmlPipe.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: BantaMarkdownToHtmlPipe, deps: [{ token: i1$1.DomSanitizer }, { token: BANTA_SDK_OPTIONS, optional: true }], target: i0.ɵɵFactoryTarget.Pipe });
268
279
  BantaMarkdownToHtmlPipe.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "14.2.12", ngImport: i0, type: BantaMarkdownToHtmlPipe, name: "markdownToHtml" });
269
280
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: BantaMarkdownToHtmlPipe, decorators: [{
270
281
  type: Pipe,
271
282
  args: [{
272
283
  name: 'markdownToHtml'
273
284
  }]
274
- }], ctorParameters: function () { return [{ type: i1.DomSanitizer }, { type: undefined, decorators: [{
285
+ }], ctorParameters: function () { return [{ type: i1$1.DomSanitizer }, { type: undefined, decorators: [{
275
286
  type: Inject,
276
287
  args: [BANTA_SDK_OPTIONS]
277
288
  }, {
@@ -288,14 +299,14 @@ class BantaTrustResourceUrlPipe {
288
299
  return this.sanitizer.bypassSecurityTrustResourceUrl(value);
289
300
  }
290
301
  }
291
- BantaTrustResourceUrlPipe.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: BantaTrustResourceUrlPipe, deps: [{ token: i1.DomSanitizer }], target: i0.ɵɵFactoryTarget.Pipe });
302
+ BantaTrustResourceUrlPipe.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: BantaTrustResourceUrlPipe, deps: [{ token: i1$1.DomSanitizer }], target: i0.ɵɵFactoryTarget.Pipe });
292
303
  BantaTrustResourceUrlPipe.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "14.2.12", ngImport: i0, type: BantaTrustResourceUrlPipe, name: "trustResourceUrl" });
293
304
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: BantaTrustResourceUrlPipe, decorators: [{
294
305
  type: Pipe,
295
306
  args: [{
296
307
  name: 'trustResourceUrl'
297
308
  }]
298
- }], ctorParameters: function () { return [{ type: i1.DomSanitizer }]; } });
309
+ }], ctorParameters: function () { return [{ type: i1$1.DomSanitizer }]; } });
299
310
 
300
311
  class BantaMentionLinkerPipe {
301
312
  transform(value, links) {
@@ -502,6 +513,86 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImpo
502
513
  type: Output
503
514
  }] } });
504
515
 
516
+ /**
517
+ * Provides a way to hook in to a shared set of timers, instead of creating a timer per instance.
518
+ * This is very useful for cases where the update is not extremely time-sensitive, but happens at scale.
519
+ * The principal use case is the TimestampComponent. When several hundred (or several thousand) comments are
520
+ * being displayed, we do not want to trigger thousands of independent relative timestamp updates, because over
521
+ * time the updates will saturate the CPU since they don't perfectly align.
522
+ */
523
+ let TimerPool = class TimerPool {
524
+ constructor() {
525
+ this.subscriptions = new Map();
526
+ this.newSubscriptions = new Map();
527
+ this.removedSubscriptions = new Map();
528
+ }
529
+ addTimer(interval, callback) {
530
+ if (interval <= 0) {
531
+ console.warn(`Refusing to set timer with interval of ${interval}!`);
532
+ return () => { };
533
+ }
534
+ let state;
535
+ let sizeWas = this.subscriptions.size;
536
+ if (!this.subscriptions.has(interval)) {
537
+ state = { subscribers: [] };
538
+ state.handle = setInterval(() => {
539
+ console.debug(`[Banta/TimerPool] Notifying ${state.subscribers.length} subs [${interval}ms]`);
540
+ state.subscribers.forEach(sub => sub());
541
+ }, interval);
542
+ this.subscriptions.set(interval, state);
543
+ }
544
+ else {
545
+ state = this.subscriptions.get(interval);
546
+ }
547
+ state.subscribers.push(callback);
548
+ // Debug information //////////////////////////
549
+ //
550
+ if (!this.newSubscriptions.has(interval))
551
+ this.newSubscriptions.set(interval, 0);
552
+ this.newSubscriptions.set(interval, (this.newSubscriptions.get(interval) ?? 0) + 1);
553
+ clearTimeout(this.newSubscriptionsNotice);
554
+ this.newSubscriptionsNotice = setTimeout(() => {
555
+ for (let [interval, count] of this.newSubscriptions) {
556
+ console.debug(`[Banta/TimerPool] ${count} new subscriptions to ${interval}ms [${state.subscribers.length} total]`);
557
+ }
558
+ this.newSubscriptions.clear();
559
+ });
560
+ //
561
+ ///////////////////////////////////////////////
562
+ if (sizeWas === 0) {
563
+ console.debug(`[Banta/TimerPool] No longer idle.`);
564
+ }
565
+ // Unsubscribe function
566
+ return () => {
567
+ let state = this.subscriptions.get(interval);
568
+ let index = state.subscribers.indexOf(callback);
569
+ if (index >= 0)
570
+ state.subscribers.splice(index, 1);
571
+ if (state.subscribers.length === 0) {
572
+ clearInterval(state.handle);
573
+ this.subscriptions.delete(interval);
574
+ }
575
+ if (!this.removedSubscriptions.has(interval))
576
+ this.removedSubscriptions.set(interval, 0);
577
+ this.removedSubscriptions.set(interval, (this.removedSubscriptions.get(interval) ?? 0) + 1);
578
+ // Debug information ////////////////////////////////////////////////////////////////////
579
+ clearTimeout(this.removedSubscriptionsNotice);
580
+ this.removedSubscriptionsNotice = setTimeout(() => {
581
+ for (let [interval, count] of this.removedSubscriptions) {
582
+ let state = this.subscriptions.get(interval);
583
+ console.debug(`[Banta/TimerPool] ${count} unsubscribed from ${interval}ms [${state?.subscribers?.length ?? 0} remain]`);
584
+ }
585
+ if (this.subscriptions.size === 0)
586
+ console.debug(`[Banta/TimerPool] All subscriptions have been removed. Now idle.`);
587
+ this.removedSubscriptions.clear();
588
+ });
589
+ };
590
+ }
591
+ };
592
+ TimerPool = __decorate([
593
+ Injectable()
594
+ ], TimerPool);
595
+
505
596
  const COMPONENTS$3 = [
506
597
  TimestampComponent,
507
598
  LightboxComponent,
@@ -512,6 +603,14 @@ const COMPONENTS$3 = [
512
603
  BantaAttachmentsComponent
513
604
  ];
514
605
  class BantaCommonModule {
606
+ static forRoot() {
607
+ return {
608
+ ngModule: BantaCommonModule,
609
+ providers: [
610
+ TimerPool$1
611
+ ]
612
+ };
613
+ }
515
614
  }
516
615
  BantaCommonModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: BantaCommonModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
517
616
  BantaCommonModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "14.2.12", ngImport: i0, type: BantaCommonModule, declarations: [TimestampComponent,
@@ -7128,12 +7227,12 @@ class EmojiSelectorPanelComponent {
7128
7227
  this.categories = this.pairs(cats).map(pair => pair[1]);
7129
7228
  }
7130
7229
  }
7131
- EmojiSelectorPanelComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: EmojiSelectorPanelComponent, deps: [{ token: i1.DomSanitizer }, { token: BANTA_SDK_OPTIONS, optional: true }], target: i0.ɵɵFactoryTarget.Component });
7230
+ EmojiSelectorPanelComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: EmojiSelectorPanelComponent, deps: [{ token: i1$1.DomSanitizer }, { token: BANTA_SDK_OPTIONS, optional: true }], target: i0.ɵɵFactoryTarget.Component });
7132
7231
  EmojiSelectorPanelComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.2.12", type: EmojiSelectorPanelComponent, selector: "emoji-selector-panel", outputs: { selected: "selected" }, ngImport: i0, template: "<div class=\"search-box\" *ngIf=\"searchVisible\">\r\n\t<a mat-icon-button href=\"javascript:;\" (click)=\"hideSearch()\">\r\n\t\t<mat-icon>arrow_back</mat-icon>\r\n\t</a>\r\n\t<mat-form-field appearance=\"outline\" floatLabel=\"always\">\r\n\t\t<mat-label>Search for emoji</mat-label>\r\n\t\t<input name=\"search\" type=\"text\" matInput placeholder=\"Start typing\" [(ngModel)]=\"searchQuery\" />\r\n\t</mat-form-field>\r\n</div>\r\n\r\n<div class=\"selector\">\r\n\t<ng-container *ngIf=\"searchVisible\">\r\n\t\t<div class=\"emoji-list\">\r\n\t\t\t<a href=\"javascript:;\" (click)=\"select(emoji.char)\" \r\n\t\t\t\t*ngFor=\"let emoji of searchResults\" [innerHtml]=\"emoji.html || ''\">\r\n\t\t\t</a>\r\n\t\t</div>\r\n\t</ng-container>\r\n\t<ng-container *ngIf=\"!searchVisible\">\r\n\t\t<div class=\"categories\">\r\n\t\t\t<ng-container *ngIf=\"!searchVisible\">\r\n\t\t\t\t<a [title]=\"humanize(category.name)\" [class.active]=\"activeCategory === category.name\" mat-icon-button *ngFor=\"let category of categories\" (click)=\"activeCategory = category.name\">\r\n\t\t\t\t\t<mat-icon>{{category.icon}}</mat-icon>\r\n\t\t\t\t</a>\r\n\r\n\t\t\t\t<a title=\"Search\" [class.active] mat-icon-button (click)=\"showSearch()\">\r\n\t\t\t\t\t<mat-icon>search</mat-icon>\r\n\t\t\t\t</a>\r\n\t\t\t</ng-container>\r\n\t\t</div>\r\n\t\t<ng-container *ngFor=\"let category of categories\">\r\n\t\t\t<div class=\"emoji-list\" *ngIf=\"activeCategory && activeCategory == category.name\">\r\n\t\t\t\t<a href=\"javascript:;\" (click)=\"select(emoji.char)\" \r\n\t\t\t\t\t*ngFor=\"let emoji of category.emojis\" [innerHtml]=\"emoji.html || ''\">\r\n\t\t\t\t</a>\r\n\t\t\t</div>\r\n\t\t</ng-container>\r\n\t</ng-container>\r\n</div>", styles: [":host{background:#111;color:#fff;border:1px solid #333;border-radius:5px;padding:.5em;width:calc(9*(32px + 1em));max-width:calc(100vw - 1.5em - 5px)}.selector{display:flex;flex-direction:column}.categories a{opacity:.25;transition:.4s opacity ease-in-out}.categories a:hover{opacity:.5}.categories a.active{opacity:1}.emoji-list{flex-grow:1;overflow-y:auto;height:20em}.emoji-list a{display:inline-block;padding:2px;margin:4px;background-color:#111}.emoji-list a ::ng-deep .emoji{width:32px;height:32px}.emoji-list a:hover{background-color:#333}.search-box{display:flex;align-items:baseline}.search-box mat-form-field{flex-grow:1}@media (max-width: 500px){.selector{flex-direction:row;height:27em}.emoji-list{height:auto}}:host-context(.banta-mobile) .selector{flex-direction:row;height:27em}:host-context(.banta-mobile) .emoji-list{height:auto}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i4$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i4$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i4$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i2$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i6.MatAnchor, selector: "a[mat-button], a[mat-raised-button], a[mat-icon-button], a[mat-fab], a[mat-mini-fab], a[mat-stroked-button], a[mat-flat-button]", inputs: ["disabled", "disableRipple", "color", "tabIndex"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i6$1.MatFormField, selector: "mat-form-field", inputs: ["color", "appearance", "hideRequiredMarker", "hintLabel", "floatLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i6$1.MatLabel, selector: "mat-label" }, { kind: "directive", type: i7.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }] });
7133
7232
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: EmojiSelectorPanelComponent, decorators: [{
7134
7233
  type: Component,
7135
7234
  args: [{ selector: 'emoji-selector-panel', template: "<div class=\"search-box\" *ngIf=\"searchVisible\">\r\n\t<a mat-icon-button href=\"javascript:;\" (click)=\"hideSearch()\">\r\n\t\t<mat-icon>arrow_back</mat-icon>\r\n\t</a>\r\n\t<mat-form-field appearance=\"outline\" floatLabel=\"always\">\r\n\t\t<mat-label>Search for emoji</mat-label>\r\n\t\t<input name=\"search\" type=\"text\" matInput placeholder=\"Start typing\" [(ngModel)]=\"searchQuery\" />\r\n\t</mat-form-field>\r\n</div>\r\n\r\n<div class=\"selector\">\r\n\t<ng-container *ngIf=\"searchVisible\">\r\n\t\t<div class=\"emoji-list\">\r\n\t\t\t<a href=\"javascript:;\" (click)=\"select(emoji.char)\" \r\n\t\t\t\t*ngFor=\"let emoji of searchResults\" [innerHtml]=\"emoji.html || ''\">\r\n\t\t\t</a>\r\n\t\t</div>\r\n\t</ng-container>\r\n\t<ng-container *ngIf=\"!searchVisible\">\r\n\t\t<div class=\"categories\">\r\n\t\t\t<ng-container *ngIf=\"!searchVisible\">\r\n\t\t\t\t<a [title]=\"humanize(category.name)\" [class.active]=\"activeCategory === category.name\" mat-icon-button *ngFor=\"let category of categories\" (click)=\"activeCategory = category.name\">\r\n\t\t\t\t\t<mat-icon>{{category.icon}}</mat-icon>\r\n\t\t\t\t</a>\r\n\r\n\t\t\t\t<a title=\"Search\" [class.active] mat-icon-button (click)=\"showSearch()\">\r\n\t\t\t\t\t<mat-icon>search</mat-icon>\r\n\t\t\t\t</a>\r\n\t\t\t</ng-container>\r\n\t\t</div>\r\n\t\t<ng-container *ngFor=\"let category of categories\">\r\n\t\t\t<div class=\"emoji-list\" *ngIf=\"activeCategory && activeCategory == category.name\">\r\n\t\t\t\t<a href=\"javascript:;\" (click)=\"select(emoji.char)\" \r\n\t\t\t\t\t*ngFor=\"let emoji of category.emojis\" [innerHtml]=\"emoji.html || ''\">\r\n\t\t\t\t</a>\r\n\t\t\t</div>\r\n\t\t</ng-container>\r\n\t</ng-container>\r\n</div>", styles: [":host{background:#111;color:#fff;border:1px solid #333;border-radius:5px;padding:.5em;width:calc(9*(32px + 1em));max-width:calc(100vw - 1.5em - 5px)}.selector{display:flex;flex-direction:column}.categories a{opacity:.25;transition:.4s opacity ease-in-out}.categories a:hover{opacity:.5}.categories a.active{opacity:1}.emoji-list{flex-grow:1;overflow-y:auto;height:20em}.emoji-list a{display:inline-block;padding:2px;margin:4px;background-color:#111}.emoji-list a ::ng-deep .emoji{width:32px;height:32px}.emoji-list a:hover{background-color:#333}.search-box{display:flex;align-items:baseline}.search-box mat-form-field{flex-grow:1}@media (max-width: 500px){.selector{flex-direction:row;height:27em}.emoji-list{height:auto}}:host-context(.banta-mobile) .selector{flex-direction:row;height:27em}:host-context(.banta-mobile) .emoji-list{height:auto}\n"] }]
7136
- }], ctorParameters: function () { return [{ type: i1.DomSanitizer }, { type: undefined, decorators: [{
7235
+ }], ctorParameters: function () { return [{ type: i1$1.DomSanitizer }, { type: undefined, decorators: [{
7137
7236
  type: Inject,
7138
7237
  args: [BANTA_SDK_OPTIONS]
7139
7238
  }, {
@@ -7207,7 +7306,7 @@ class EmojiSelectorButtonComponent {
7207
7306
  this.overlayRef.attach(this.selectorPanelTemplate);
7208
7307
  }
7209
7308
  }
7210
- EmojiSelectorButtonComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: EmojiSelectorButtonComponent, deps: [{ token: i0.ElementRef }, { token: i1$1.Overlay }], target: i0.ɵɵFactoryTarget.Component });
7309
+ EmojiSelectorButtonComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: EmojiSelectorButtonComponent, deps: [{ token: i0.ElementRef }, { token: i1$2.Overlay }], target: i0.ɵɵFactoryTarget.Component });
7211
7310
  EmojiSelectorButtonComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.2.12", type: EmojiSelectorButtonComponent, selector: "emoji-selector-button", inputs: { overlayX: "overlayX", overlayY: "overlayY", originX: "originX", originY: "originY" }, outputs: { selected: "selected" }, viewQueries: [{ propertyName: "selectorPanelTemplate", first: true, predicate: ["selectorPanelTemplate"], descendants: true }], ngImport: i0, template: `
7212
7311
  <button #button type="button" mat-icon-button (click)="show()">
7213
7312
  <mat-icon>emoji_emotions</mat-icon>
@@ -7232,7 +7331,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImpo
7232
7331
  ></emoji-selector-panel>
7233
7332
  </ng-template>
7234
7333
  `, styles: [":host{display:block;position:relative}button{color:#666}\n"] }]
7235
- }], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: i1$1.Overlay }]; }, propDecorators: { selectorPanelTemplate: [{
7334
+ }], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: i1$2.Overlay }]; }, propDecorators: { selectorPanelTemplate: [{
7236
7335
  type: ViewChild,
7237
7336
  args: ['selectorPanelTemplate']
7238
7337
  }], selected: [{
@@ -7947,12 +8046,12 @@ class AttachmentButtonComponent {
7947
8046
  }
7948
8047
  }
7949
8048
  }
7950
- AttachmentButtonComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: AttachmentButtonComponent, deps: [{ token: i1$2.CDNProvider }], target: i0.ɵɵFactoryTarget.Component });
8049
+ AttachmentButtonComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: AttachmentButtonComponent, deps: [{ token: i1$3.CDNProvider }], target: i0.ɵɵFactoryTarget.Component });
7951
8050
  AttachmentButtonComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.2.12", type: AttachmentButtonComponent, selector: "banta-attachment-button", outputs: { addedAttachment: "addedAttachment", attachmentError: "attachmentError" }, viewQueries: [{ propertyName: "fileInput", first: true, predicate: ["fileUpload"], descendants: true }], ngImport: i0, template: "<button matTooltip=\"Add an image or gif\" #button type=\"button\" mat-icon-button (click)=\"show()\">\r\n\t<mat-icon>image</mat-icon>\r\n</button>\r\n<input style=\"display: none;\" #fileUpload [multiple]=\"false\" (change)=\"fileChange($event)\" type=\"file\" >", styles: ["button{color:#666}\n"], dependencies: [{ kind: "component", type: i2$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i6.MatButton, selector: "button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]", inputs: ["disabled", "disableRipple", "color"], exportAs: ["matButton"] }, { kind: "directive", type: i11.MatTooltip, selector: "[matTooltip]", exportAs: ["matTooltip"] }] });
7952
8051
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: AttachmentButtonComponent, decorators: [{
7953
8052
  type: Component,
7954
8053
  args: [{ selector: 'banta-attachment-button', template: "<button matTooltip=\"Add an image or gif\" #button type=\"button\" mat-icon-button (click)=\"show()\">\r\n\t<mat-icon>image</mat-icon>\r\n</button>\r\n<input style=\"display: none;\" #fileUpload [multiple]=\"false\" (change)=\"fileChange($event)\" type=\"file\" >", styles: ["button{color:#666}\n"] }]
7955
- }], ctorParameters: function () { return [{ type: i1$2.CDNProvider }]; }, propDecorators: { fileInput: [{
8054
+ }], ctorParameters: function () { return [{ type: i1$3.CDNProvider }]; }, propDecorators: { fileInput: [{
7956
8055
  type: ViewChild,
7957
8056
  args: ['fileUpload', { static: false }]
7958
8057
  }], addedAttachment: [{
@@ -8105,7 +8204,10 @@ class CommentFieldComponent {
8105
8204
  if (this._source.connectionStateChanged) {
8106
8205
  this._subs.add(this._source.connectionStateChanged.subscribe(state => {
8107
8206
  if (state === 'lost') {
8108
- this.transientMessage = `Reconnecting...`;
8207
+ if (this._source.errorState === 'server-issue')
8208
+ this.transientMessage = `Error occurred, trying again...`;
8209
+ else
8210
+ this.transientMessage = `Reconnecting...`;
8109
8211
  }
8110
8212
  else if (state === 'restored') {
8111
8213
  this.transientMessage = undefined;
@@ -8696,7 +8798,7 @@ class BantaCommentsComponent {
8696
8798
  return this.threadView;
8697
8799
  }
8698
8800
  updateLoading() {
8699
- if (this.source?.state && this.source?.state !== 'connecting') {
8801
+ if (this.source?.state && !['connecting', 'lost'].includes(this.source?.state)) {
8700
8802
  clearInterval(this._loadingTimer);
8701
8803
  this.loadingMessage = `Here we go!`;
8702
8804
  setTimeout(() => {
@@ -10064,6 +10166,8 @@ class ChatSource extends SocketRPC {
10064
10166
  this.subscription = new Subscription();
10065
10167
  this._state = 'connecting';
10066
10168
  this._connectionStateChanged = new Subject();
10169
+ this.wasRestored = false;
10170
+ this.subscribeAttempt = 0;
10067
10171
  this.messageMap = new Map();
10068
10172
  this._messageReceived = new Subject();
10069
10173
  this._messageUpdated = new Subject();
@@ -10084,21 +10188,19 @@ class ChatSource extends SocketRPC {
10084
10188
  }
10085
10189
  async bind(socket) {
10086
10190
  super.bind(socket);
10087
- this.state = 'connected';
10088
- this.markReady();
10089
- await this.subscribeToTopic();
10090
10191
  this.subscription.add(this.backend.userChanged.subscribe(() => this.authenticate()));
10091
10192
  socket.addEventListener('open', async () => {
10092
- this.state = 'connected';
10193
+ console.log(`[Banta/${this.identifier}] Socket is open`);
10093
10194
  });
10094
10195
  socket.addEventListener('lost', async () => {
10095
10196
  this.state = 'lost';
10096
10197
  });
10097
10198
  socket.addEventListener('restore', async () => {
10098
- this.state = 'restored';
10199
+ this.wasRestored = true;
10099
10200
  await this.authenticate();
10100
10201
  await this.subscribeToTopic();
10101
10202
  });
10203
+ await this.subscribeToTopic();
10102
10204
  return this;
10103
10205
  }
10104
10206
  mapOrUpdateMessages(messages) {
@@ -10116,9 +10218,16 @@ class ChatSource extends SocketRPC {
10116
10218
  return message;
10117
10219
  }
10118
10220
  async getExistingMessages() {
10119
- let messages = await this.peer.getExistingMessages();
10120
- messages = this.mapOrUpdateMessages(messages);
10121
- return messages;
10221
+ try {
10222
+ let messages = await this.peer.getExistingMessages();
10223
+ messages = this.mapOrUpdateMessages(messages);
10224
+ return messages;
10225
+ }
10226
+ catch (e) {
10227
+ console.error(`[Banta/${this.identifier}] Error occurred while trying to get existing messages:`);
10228
+ console.error(e);
10229
+ return [];
10230
+ }
10122
10231
  }
10123
10232
  async ensureConnection(errorMessage) {
10124
10233
  let reason = `Connection to chat services is not currently available.`;
@@ -10133,8 +10242,30 @@ class ChatSource extends SocketRPC {
10133
10242
  await this.ensureConnection();
10134
10243
  await this.peer.editMessage(messageId, text);
10135
10244
  }
10245
+ get errorState() {
10246
+ return this._errorState;
10247
+ }
10136
10248
  async subscribeToTopic() {
10137
- await this.peer.subscribe(this.identifier, this.parentIdentifier, this.sortOrder);
10249
+ try {
10250
+ await this.peer.subscribe(this.identifier, this.parentIdentifier, this.sortOrder);
10251
+ this.subscribeAttempt = 0;
10252
+ this._errorState = undefined;
10253
+ this.state = this.wasRestored ? 'restored' : 'connected';
10254
+ this.markReady();
10255
+ }
10256
+ catch (e) {
10257
+ console.error(`[Banta/${this.identifier}] Error while subscribing to topic`);
10258
+ console.error(e);
10259
+ this.state = 'lost';
10260
+ this._errorState = 'server-issue';
10261
+ this.subscribeAttempt += 1;
10262
+ let delay = Math.min(30 * 1000, (3 * 1000 * this.subscribeAttempt) * (1 + Math.random()));
10263
+ console.error(`[Banta/${this.identifier}] Waiting ${delay}ms before attempting to reconnect...`);
10264
+ setTimeout(() => {
10265
+ console.info(`Attempting reconnection after error in subscribeToTopic...`);
10266
+ this.reconnect();
10267
+ }, delay);
10268
+ }
10138
10269
  }
10139
10270
  async authenticate() {
10140
10271
  if (this.backend.user) {
@@ -10252,12 +10383,24 @@ class ChatBackend extends ChatBackendBase {
10252
10383
  return await new ChatSource(this, topicId, messageId, options?.sortOrder || CommentsOrder.NEWEST)
10253
10384
  .bind(await this.connectToService());
10254
10385
  }
10386
+ /**
10387
+ * Get the count of the given topic
10388
+ * @param topicId
10389
+ * @returns
10390
+ */
10255
10391
  async getSourceCountForTopic(topicId) {
10256
- let response = await fetch(`${this.serviceUrl}/topics/${topicId}`);
10257
- if (response.status >= 400)
10258
- return 0;
10259
- let topic = await response.json();
10260
- return topic.messageCount || 0;
10392
+ try {
10393
+ let response = await fetch(`${this.serviceUrl}/topics/${topicId}`);
10394
+ if (response.status >= 400)
10395
+ return 0;
10396
+ let topic = await response.json();
10397
+ return topic.messageCount || 0;
10398
+ }
10399
+ catch (e) {
10400
+ console.error(`[Banta/${topicId}] Failed to get message count for topic:`);
10401
+ console.error(e);
10402
+ return undefined;
10403
+ }
10261
10404
  }
10262
10405
  refreshMessage(message) {
10263
10406
  throw new Error("Method not implemented.");
@@ -10291,7 +10434,7 @@ class ChatBackend extends ChatBackendBase {
10291
10434
  ChatBackend.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: ChatBackend, deps: [{ token: BANTA_SDK_OPTIONS }], target: i0.ɵɵFactoryTarget.Injectable });
10292
10435
  ChatBackend.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: ChatBackend });
10293
10436
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: ChatBackend, decorators: [{
10294
- type: Injectable
10437
+ type: Injectable$1
10295
10438
  }], ctorParameters: function () { return [{ type: undefined, decorators: [{
10296
10439
  type: Inject,
10297
10440
  args: [BANTA_SDK_OPTIONS]
@@ -10333,7 +10476,7 @@ class UrlAttachmentResolver {
10333
10476
  UrlAttachmentResolver.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: UrlAttachmentResolver, deps: [{ token: ChatBackendBase }], target: i0.ɵɵFactoryTarget.Injectable });
10334
10477
  UrlAttachmentResolver.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: UrlAttachmentResolver });
10335
10478
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: UrlAttachmentResolver, decorators: [{
10336
- type: Injectable
10479
+ type: Injectable$1
10337
10480
  }], ctorParameters: function () { return [{ type: ChatBackendBase }]; } });
10338
10481
 
10339
10482
  class YouTubeAttachmentResolver {
@@ -10427,9 +10570,7 @@ BantaSdkModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version:
10427
10570
  BantaSdkModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "14.2.12", ngImport: i0, type: BantaSdkModule, declarations: [BantaComponent,
10428
10571
  BantaLogoComponent,
10429
10572
  LiveMessageComponent], imports: [CommonModule,
10430
- FormsModule,
10431
- BantaCommonModule,
10432
- CommentsModule,
10573
+ FormsModule, BantaCommonModule, CommentsModule,
10433
10574
  ChatModule,
10434
10575
  EmojiModule,
10435
10576
  MatIconModule,
@@ -10451,7 +10592,7 @@ BantaSdkModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version:
10451
10592
  EmojiModule] });
10452
10593
  BantaSdkModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: BantaSdkModule, imports: [CommonModule,
10453
10594
  FormsModule,
10454
- BantaCommonModule,
10595
+ BantaCommonModule.forRoot(),
10455
10596
  CommentsModule,
10456
10597
  ChatModule,
10457
10598
  EmojiModule,
@@ -10475,7 +10616,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImpo
10475
10616
  imports: [
10476
10617
  CommonModule,
10477
10618
  FormsModule,
10478
- BantaCommonModule,
10619
+ BantaCommonModule.forRoot(),
10479
10620
  CommentsModule,
10480
10621
  ChatModule,
10481
10622
  EmojiModule,
@@ -10516,5 +10657,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImpo
10516
10657
  * Generated bundle index. Do not edit.
10517
10658
  */
10518
10659
 
10519
- export { AttachmentButtonComponent, AttachmentScraperDirective, BANTA_SDK_OPTIONS, BantaAttachmentComponent, BantaAttachmentsComponent, BantaChatComponent, BantaCommentsComponent, BantaCommonModule, BantaComponent, BantaLogoComponent, BantaMarkdownToHtmlPipe, BantaMentionLinkerPipe, BantaReplySendOptionsDirective, BantaSdkModule, BantaTrustResourceUrlPipe, ChatBackend, ChatBackendBase, ChatMessageComponent, ChatModule, ChatSource, ChatViewComponent, CommentComponent, CommentFieldComponent, CommentSortComponent, CommentViewComponent, CommentsModule, EMOJIS, EmojiModule, EmojiSelectorButtonComponent, EmojiSelectorPanelComponent, GiphyAttachmentResolver, LightboxComponent, LiveChatMessageComponent, LiveCommentComponent, LiveMessageComponent, TimestampComponent, TweetAttachmentResolver, UrlAttachmentResolver, UrlAttachmentScraper, YouTubeAttachmentResolver, lazyConnection };
10660
+ export { AttachmentButtonComponent, AttachmentScraperDirective, BANTA_SDK_OPTIONS, BantaAttachmentComponent, BantaAttachmentsComponent, BantaChatComponent, BantaCommentsComponent, BantaCommonModule, BantaComponent, BantaLogoComponent, BantaMarkdownToHtmlPipe, BantaMentionLinkerPipe, BantaReplySendOptionsDirective, BantaSdkModule, BantaTrustResourceUrlPipe, ChatBackend, ChatBackendBase, ChatMessageComponent, ChatModule, ChatSource, ChatViewComponent, CommentComponent, CommentFieldComponent, CommentSortComponent, CommentViewComponent, CommentsModule, EMOJIS, EmojiModule, EmojiSelectorButtonComponent, EmojiSelectorPanelComponent, GiphyAttachmentResolver, LightboxComponent, LiveChatMessageComponent, LiveCommentComponent, LiveMessageComponent, TimerPool, TimestampComponent, TweetAttachmentResolver, UrlAttachmentResolver, UrlAttachmentScraper, YouTubeAttachmentResolver, lazyConnection };
10520
10661
  //# sourceMappingURL=banta-sdk.mjs.map