flexdesk 0.17.2 → 0.17.3
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.
- package/CHANGELOG.md +16 -0
- package/README.md +28 -1
- package/dist/widgets.js +45 -24
- package/dist/widgets.js.map +2 -2
- package/package.json +1 -1
- package/src/ui/components/notification_history.js +17 -24
- package/src/ui/notification_center.js +41 -3
package/package.json
CHANGED
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
* NotificationHistory - Bottom bar notification history panel
|
|
3
3
|
*
|
|
4
4
|
* Bell icon in the global-bottom-bar that opens a popover listing past notifications.
|
|
5
|
-
* Reads from NotificationCenter.history
|
|
6
|
-
*
|
|
5
|
+
* Reads from NotificationCenter.history and follows it through
|
|
6
|
+
* `notificationCenter.subscribe()` (0.17.3): every entry the centre records — a
|
|
7
|
+
* toast from `show()`/`warn()`/... or from the bus — is one unread, until the
|
|
8
|
+
* popover is opened. The bus is not watched for toasts.
|
|
7
9
|
*
|
|
8
10
|
* THREE OPTIONS, all additive (FlexDesk STORY-0020); without them it is the bell
|
|
9
11
|
* it always was:
|
|
@@ -34,7 +36,8 @@ export class NotificationHistory {
|
|
|
34
36
|
|
|
35
37
|
this.triggerEl = null;
|
|
36
38
|
this.badgeEl = null;
|
|
37
|
-
|
|
39
|
+
/** Entries recorded since the popover was last opened. */
|
|
40
|
+
this.unreadCount = 0;
|
|
38
41
|
this._popoverCleanup = null;
|
|
39
42
|
this._subscriptions = [];
|
|
40
43
|
}
|
|
@@ -80,10 +83,14 @@ export class NotificationHistory {
|
|
|
80
83
|
return;
|
|
81
84
|
}
|
|
82
85
|
|
|
83
|
-
//
|
|
84
|
-
this.
|
|
85
|
-
|
|
86
|
-
|
|
86
|
+
// What is in the history at mount is not unread.
|
|
87
|
+
this.unreadCount = 0;
|
|
88
|
+
const unsubscribe = this.notificationCenter?.subscribe?.(() => {
|
|
89
|
+
this.unreadCount += 1;
|
|
90
|
+
this.#updateBadge();
|
|
91
|
+
});
|
|
92
|
+
if (typeof unsubscribe === 'function') this._subscriptions.push(unsubscribe);
|
|
93
|
+
this.#updateBadge();
|
|
87
94
|
}
|
|
88
95
|
|
|
89
96
|
unmount() {
|
|
@@ -99,20 +106,6 @@ export class NotificationHistory {
|
|
|
99
106
|
this.badgeEl = null;
|
|
100
107
|
}
|
|
101
108
|
|
|
102
|
-
#subscribeEvents() {
|
|
103
|
-
if (!this.eventBus) return;
|
|
104
|
-
|
|
105
|
-
// Update badge whenever a new notification comes in
|
|
106
|
-
const badgeUpdate = () => queueMicrotask(() => this.#updateBadge());
|
|
107
|
-
const events = ['toast:show', 'notification:show', 'log:entry',
|
|
108
|
-
'workspace:save:failed', 'workspace:import:failed'];
|
|
109
|
-
|
|
110
|
-
events.forEach(name => {
|
|
111
|
-
const handle = this.eventBus.on(name, badgeUpdate);
|
|
112
|
-
this._subscriptions.push(() => handle?.dispose?.());
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
|
|
116
109
|
/** The Alerts tab, asked for on the bus — or the embedder's own verb. */
|
|
117
110
|
#openAlerts() {
|
|
118
111
|
if (this.onOpen) {
|
|
@@ -126,7 +119,7 @@ export class NotificationHistory {
|
|
|
126
119
|
const historyLen = this.notificationCenter?.history?.length ?? 0;
|
|
127
120
|
const unread = this.alertLog
|
|
128
121
|
? this.alertLog.unreadCount()
|
|
129
|
-
: Math.
|
|
122
|
+
: Math.min(this.unreadCount, historyLen);
|
|
130
123
|
|
|
131
124
|
if (!this.badgeEl) return;
|
|
132
125
|
|
|
@@ -164,7 +157,7 @@ export class NotificationHistory {
|
|
|
164
157
|
this.#closePanel();
|
|
165
158
|
|
|
166
159
|
// Mark all as seen
|
|
167
|
-
this.
|
|
160
|
+
this.unreadCount = 0;
|
|
168
161
|
this.#updateBadge();
|
|
169
162
|
|
|
170
163
|
const panel = document.createElement('div');
|
|
@@ -197,7 +190,7 @@ export class NotificationHistory {
|
|
|
197
190
|
header.querySelector('.twm-notification-history__clear-btn')
|
|
198
191
|
.addEventListener('click', () => {
|
|
199
192
|
this.notificationCenter.history.splice(0);
|
|
200
|
-
this.
|
|
193
|
+
this.unreadCount = 0;
|
|
201
194
|
this.#updateBadge();
|
|
202
195
|
this.#renderList(list);
|
|
203
196
|
});
|
|
@@ -27,6 +27,14 @@
|
|
|
27
27
|
* collapsed state and a hovered button survive. A running progress toast is
|
|
28
28
|
* never held behind "+N more" (and never dropped by 'drop'); it counts toward
|
|
29
29
|
* `maxVisible`, so it takes the place of the newest other toast.
|
|
30
|
+
*
|
|
31
|
+
* History subscribers (0.17.3)
|
|
32
|
+
* ----------------------------
|
|
33
|
+
* `history` holds one entry per toast, whichever way it came (`show()`, the
|
|
34
|
+
* `warn()`/`error()`/... helpers, `toast:show`, `notification:show`,
|
|
35
|
+
* `log:entry`). A toast that leaves marks its own entry `dismissed` with a
|
|
36
|
+
* `reason`; it adds none. `subscribe(fn)` calls `fn(entry, center)` for each
|
|
37
|
+
* entry added, so a history view follows the centre rather than the bus.
|
|
30
38
|
*/
|
|
31
39
|
import { ComponentBase } from './base/component_base.js';
|
|
32
40
|
import { getSetting } from '../core/settings.js';
|
|
@@ -55,6 +63,7 @@ const SUPPRESSED_NAMESPACE_TITLES = new Set(['bootstrap', 'app']);
|
|
|
55
63
|
export class NotificationCenter extends ComponentBase {
|
|
56
64
|
#items;
|
|
57
65
|
#summaryEl;
|
|
66
|
+
#historyListeners;
|
|
58
67
|
|
|
59
68
|
constructor({
|
|
60
69
|
eventBus,
|
|
@@ -90,6 +99,7 @@ export class NotificationCenter extends ComponentBase {
|
|
|
90
99
|
/** id -> the drawn toast's nodes, for the toasts on screen. */
|
|
91
100
|
this.#items = new Map();
|
|
92
101
|
this.#summaryEl = null;
|
|
102
|
+
this.#historyListeners = new Set();
|
|
93
103
|
|
|
94
104
|
this.container = null;
|
|
95
105
|
this.rootEl = null;
|
|
@@ -151,6 +161,7 @@ export class NotificationCenter extends ComponentBase {
|
|
|
151
161
|
dispose() {
|
|
152
162
|
this.#clearTimers();
|
|
153
163
|
this.#unsubscribeEvents();
|
|
164
|
+
this.#historyListeners.clear();
|
|
154
165
|
// Give the toast helpers back to the console — unless someone has
|
|
155
166
|
// pointed them at another bus since this centre mounted.
|
|
156
167
|
if (this.eventBus && getToastBus() === this.eventBus) setToastBus(null);
|
|
@@ -190,7 +201,7 @@ export class NotificationCenter extends ComponentBase {
|
|
|
190
201
|
if (idx !== -1) {
|
|
191
202
|
const [removed] = this.notifications.splice(idx, 1);
|
|
192
203
|
this.#stopTimer(removed.id);
|
|
193
|
-
this.#
|
|
204
|
+
this.#recordDismissal(removed, 'overflow');
|
|
194
205
|
}
|
|
195
206
|
}
|
|
196
207
|
|
|
@@ -207,13 +218,13 @@ export class NotificationCenter extends ComponentBase {
|
|
|
207
218
|
const h = this.held.findIndex((item) => item.id === notificationId);
|
|
208
219
|
if (h === -1) return false;
|
|
209
220
|
const [gone] = this.held.splice(h, 1);
|
|
210
|
-
this.#
|
|
221
|
+
this.#recordDismissal(gone, reason);
|
|
211
222
|
this.#render();
|
|
212
223
|
return true;
|
|
213
224
|
}
|
|
214
225
|
const [removed] = this.notifications.splice(idx, 1);
|
|
215
226
|
this.#stopTimer(notificationId);
|
|
216
|
-
this.#
|
|
227
|
+
this.#recordDismissal(removed, reason);
|
|
217
228
|
// A place on screen came free: the oldest held-back toast takes it.
|
|
218
229
|
this.#rebalance();
|
|
219
230
|
this.#render();
|
|
@@ -475,12 +486,39 @@ export class NotificationCenter extends ComponentBase {
|
|
|
475
486
|
this.activeTimers.clear();
|
|
476
487
|
}
|
|
477
488
|
|
|
489
|
+
/**
|
|
490
|
+
* Hear of every entry added to `history`, by `show()`, `warn()`/`error()`/
|
|
491
|
+
* `info()`/`success()` and the bus alike: `listener(entry, center)`, once
|
|
492
|
+
* per toast, after the entry is in `history`. A dismissal changes the
|
|
493
|
+
* toast's own entry and is not an entry of its own. Answers the unsubscribe.
|
|
494
|
+
*/
|
|
495
|
+
subscribe(listener) {
|
|
496
|
+
if (typeof listener !== 'function') return () => {};
|
|
497
|
+
this.#historyListeners.add(listener);
|
|
498
|
+
return () => { this.#historyListeners.delete(listener); };
|
|
499
|
+
}
|
|
500
|
+
|
|
478
501
|
#recordHistory(entry) {
|
|
479
502
|
if (entry.skipHistory) return;
|
|
480
503
|
this.history.push(entry);
|
|
481
504
|
if (this.history.length > this.historyLimit) {
|
|
482
505
|
this.history.shift();
|
|
483
506
|
}
|
|
507
|
+
[...this.#historyListeners].forEach((listener) => {
|
|
508
|
+
try {
|
|
509
|
+
listener(entry, this);
|
|
510
|
+
} catch (err) {
|
|
511
|
+
this.logger?.warn?.('twm-notification-center', 'History listener failed', { err });
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/** A toast that left marks its own entry: one toast, one entry. */
|
|
517
|
+
#recordDismissal(notification, reason) {
|
|
518
|
+
if (notification.skipHistory) return;
|
|
519
|
+
const at = this.history.findLastIndex((entry) => entry.id === notification.id);
|
|
520
|
+
if (at === -1) return;
|
|
521
|
+
this.history[at] = { ...notification, dismissed: true, reason };
|
|
484
522
|
}
|
|
485
523
|
|
|
486
524
|
/** A progress toast that has not been given an outcome yet. */
|