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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.17.3 (2026-10-22)
|
|
4
|
+
|
|
5
|
+
A patch release: everything below is additive. Every entry is documented in a
|
|
6
|
+
README section headed "(0.17.3)"; each entry links to it.
|
|
7
|
+
|
|
8
|
+
### Added and changed
|
|
9
|
+
|
|
10
|
+
- [One toast, one history entry, one unread](README.md#one-toast-one-history-entry-one-unread-0173): `NotificationCenter.history` holds one entry per toast however it was raised (`show()`, `warn()` / `error()` / `info()` / `success()`, `toast:show`, `notification:show`, `log:entry`), and a toast that leaves marks its own entry `dismissed: true` with the `reason` instead of adding a second one. `nc.subscribe(fn)` calls `fn(entry, center)` once per entry added and answers the unsubscribe. `NotificationHistory` follows the centre through `subscribe()`, so every toast counts once on the bell, also one raised through the API (BUG-0034). It no longer watches the bus for toasts: its `notificationCenter` is a `NotificationCenter` (or anything with `history` and `subscribe`).
|
|
11
|
+
|
|
12
|
+
### Removed or renamed exports
|
|
13
|
+
|
|
14
|
+
None. Compared with 0.17.2, across every entry point (`core`, `widgets`, `wm`,
|
|
15
|
+
`host`, `canvas`, `flow`, `charts`, `tiles`, `editor`) and the package
|
|
16
|
+
`exports` map, no export was added, removed or renamed. Not an export but worth
|
|
17
|
+
knowing: `NotificationCenter` gained the `subscribe(fn)` method (see above).
|
|
18
|
+
|
|
3
19
|
## 0.17.2 (2026-10-22)
|
|
4
20
|
|
|
5
21
|
A patch release: everything below is additive. Every entry is documented in a
|
package/README.md
CHANGED
|
@@ -3527,7 +3527,8 @@ again: its pane hears `onShow` and `onSelect` fires, and nothing is written back
|
|
|
3527
3527
|
Toasts, from `toast:show` on the bus (or `toastInfo` / `toastWarn` /
|
|
3528
3528
|
`toastError` / `toastSuccess`) or `show()`. A payload with `persistent: true`
|
|
3529
3529
|
stays until its × is clicked. `history` keeps every notification shown, and
|
|
3530
|
-
`NotificationHistory` puts it behind a bell in the bottom bar
|
|
3530
|
+
`NotificationHistory` puts it behind a bell in the bottom bar (one entry per toast;
|
|
3531
|
+
`subscribe(fn)` hears each one, 0.17.3).
|
|
3531
3532
|
|
|
3532
3533
|
*New in 0.11.0:* **`overflow: 'collapse'`.** By default, a toast past
|
|
3533
3534
|
`maxVisible` (3) pushes the oldest off the screen. With `'collapse'`, the toasts
|
|
@@ -3607,6 +3608,32 @@ clicked. A consumer stylesheet that targeted `.notification` should target
|
|
|
3607
3608
|
`tests/notification_toast_styles.test.mjs` holds the classes, the cascade and
|
|
3608
3609
|
the sheets.
|
|
3609
3610
|
|
|
3611
|
+
### One toast, one history entry, one unread (0.17.3)
|
|
3612
|
+
|
|
3613
|
+
`history` holds **one entry per toast**, however it was raised: `show()`, the
|
|
3614
|
+
`warn()` / `error()` / `info()` / `success()` helpers, `toast:show`,
|
|
3615
|
+
`notification:show` or `log:entry`. A toast that leaves (timeout, ×, overflow,
|
|
3616
|
+
`clearAll`) marks its own entry `dismissed: true` with the `reason`, and adds
|
|
3617
|
+
no entry. **`nc.subscribe(fn)`** calls `fn(entry, center)` once for each entry
|
|
3618
|
+
added, after it is in `history` (not for a `skipHistory` toast, and not for a
|
|
3619
|
+
dismissal), and answers the unsubscribe; `dispose()` drops every listener.
|
|
3620
|
+
|
|
3621
|
+
`NotificationHistory` follows the centre through `subscribe()`: every entry
|
|
3622
|
+
recorded is one unread on the bell until the popover is opened, also past
|
|
3623
|
+
`historyLimit`. It no longer watches the bus for toasts, so its
|
|
3624
|
+
`notificationCenter` is a `NotificationCenter` (or anything with `history` and
|
|
3625
|
+
`subscribe`).
|
|
3626
|
+
|
|
3627
|
+
```js
|
|
3628
|
+
const off = nc.subscribe((entry) => console.log('recorded', entry.severity, entry.message));
|
|
3629
|
+
nc.warn('Disk almost full'); // the bell says 1
|
|
3630
|
+
```
|
|
3631
|
+
|
|
3632
|
+
Until 0.17.3 the bell counted only on bus events, so a toast raised through
|
|
3633
|
+
`show()` / `warn()` / `error()` / `info()` never raised its badge, and a dismissal
|
|
3634
|
+
was pushed as a second entry: a toast that timed out was in `history` twice
|
|
3635
|
+
and counted twice. `tests/notification_history_follows_center.test.mjs` holds it.
|
|
3636
|
+
|
|
3610
3637
|
### Alerts with history: a log, a pane, a tab host and a bell (0.14.0)
|
|
3611
3638
|
|
|
3612
3639
|
A toast is the moment. The **alert log** keeps what came in until it is
|
package/dist/widgets.js
CHANGED
|
@@ -3033,7 +3033,7 @@ var NotificationHistory = class {
|
|
|
3033
3033
|
this.onOpen = typeof onOpen === "function" ? onOpen : null;
|
|
3034
3034
|
this.triggerEl = null;
|
|
3035
3035
|
this.badgeEl = null;
|
|
3036
|
-
this.
|
|
3036
|
+
this.unreadCount = 0;
|
|
3037
3037
|
this._popoverCleanup = null;
|
|
3038
3038
|
this._subscriptions = [];
|
|
3039
3039
|
}
|
|
@@ -3064,8 +3064,13 @@ var NotificationHistory = class {
|
|
|
3064
3064
|
this.#updateBadge();
|
|
3065
3065
|
return;
|
|
3066
3066
|
}
|
|
3067
|
-
this.
|
|
3068
|
-
this
|
|
3067
|
+
this.unreadCount = 0;
|
|
3068
|
+
const unsubscribe = this.notificationCenter?.subscribe?.(() => {
|
|
3069
|
+
this.unreadCount += 1;
|
|
3070
|
+
this.#updateBadge();
|
|
3071
|
+
});
|
|
3072
|
+
if (typeof unsubscribe === "function") this._subscriptions.push(unsubscribe);
|
|
3073
|
+
this.#updateBadge();
|
|
3069
3074
|
}
|
|
3070
3075
|
unmount() {
|
|
3071
3076
|
this.#closePanel();
|
|
@@ -3079,21 +3084,6 @@ var NotificationHistory = class {
|
|
|
3079
3084
|
this.triggerEl = null;
|
|
3080
3085
|
this.badgeEl = null;
|
|
3081
3086
|
}
|
|
3082
|
-
#subscribeEvents() {
|
|
3083
|
-
if (!this.eventBus) return;
|
|
3084
|
-
const badgeUpdate = () => queueMicrotask(() => this.#updateBadge());
|
|
3085
|
-
const events = [
|
|
3086
|
-
"toast:show",
|
|
3087
|
-
"notification:show",
|
|
3088
|
-
"log:entry",
|
|
3089
|
-
"workspace:save:failed",
|
|
3090
|
-
"workspace:import:failed"
|
|
3091
|
-
];
|
|
3092
|
-
events.forEach((name) => {
|
|
3093
|
-
const handle = this.eventBus.on(name, badgeUpdate);
|
|
3094
|
-
this._subscriptions.push(() => handle?.dispose?.());
|
|
3095
|
-
});
|
|
3096
|
-
}
|
|
3097
3087
|
/** The Alerts tab, asked for on the bus — or the embedder's own verb. */
|
|
3098
3088
|
#openAlerts() {
|
|
3099
3089
|
if (this.onOpen) {
|
|
@@ -3108,7 +3098,7 @@ var NotificationHistory = class {
|
|
|
3108
3098
|
}
|
|
3109
3099
|
#updateBadge() {
|
|
3110
3100
|
const historyLen = this.notificationCenter?.history?.length ?? 0;
|
|
3111
|
-
const unread = this.alertLog ? this.alertLog.unreadCount() : Math.
|
|
3101
|
+
const unread = this.alertLog ? this.alertLog.unreadCount() : Math.min(this.unreadCount, historyLen);
|
|
3112
3102
|
if (!this.badgeEl) return;
|
|
3113
3103
|
if (this.alertLog) {
|
|
3114
3104
|
const level = unread > 0 ? this.alertLog.worstUnread() : null;
|
|
@@ -3140,7 +3130,7 @@ var NotificationHistory = class {
|
|
|
3140
3130
|
}
|
|
3141
3131
|
#openPanel() {
|
|
3142
3132
|
this.#closePanel();
|
|
3143
|
-
this.
|
|
3133
|
+
this.unreadCount = 0;
|
|
3144
3134
|
this.#updateBadge();
|
|
3145
3135
|
const panel = document.createElement("div");
|
|
3146
3136
|
panel.className = "twm-notification-history__panel";
|
|
@@ -3155,7 +3145,7 @@ var NotificationHistory = class {
|
|
|
3155
3145
|
header.querySelector(".twm-notification-history__close-btn").addEventListener("click", () => this.#closePanel());
|
|
3156
3146
|
header.querySelector(".twm-notification-history__clear-btn").addEventListener("click", () => {
|
|
3157
3147
|
this.notificationCenter.history.splice(0);
|
|
3158
|
-
this.
|
|
3148
|
+
this.unreadCount = 0;
|
|
3159
3149
|
this.#updateBadge();
|
|
3160
3150
|
this.#renderList(list);
|
|
3161
3151
|
});
|
|
@@ -6699,6 +6689,7 @@ var SUPPRESSED_NAMESPACE_TITLES = /* @__PURE__ */ new Set(["bootstrap", "app"]);
|
|
|
6699
6689
|
var NotificationCenter = class _NotificationCenter extends ComponentBase {
|
|
6700
6690
|
#items;
|
|
6701
6691
|
#summaryEl;
|
|
6692
|
+
#historyListeners;
|
|
6702
6693
|
constructor({
|
|
6703
6694
|
eventBus,
|
|
6704
6695
|
logger,
|
|
@@ -6729,6 +6720,7 @@ var NotificationCenter = class _NotificationCenter extends ComponentBase {
|
|
|
6729
6720
|
this.eventDisposers = [];
|
|
6730
6721
|
this.#items = /* @__PURE__ */ new Map();
|
|
6731
6722
|
this.#summaryEl = null;
|
|
6723
|
+
this.#historyListeners = /* @__PURE__ */ new Set();
|
|
6732
6724
|
this.container = null;
|
|
6733
6725
|
this.rootEl = null;
|
|
6734
6726
|
this.listEl = null;
|
|
@@ -6777,6 +6769,7 @@ var NotificationCenter = class _NotificationCenter extends ComponentBase {
|
|
|
6777
6769
|
dispose() {
|
|
6778
6770
|
this.#clearTimers();
|
|
6779
6771
|
this.#unsubscribeEvents();
|
|
6772
|
+
this.#historyListeners.clear();
|
|
6780
6773
|
if (this.eventBus && getToastBus() === this.eventBus) setToastBus(null);
|
|
6781
6774
|
this.#items.forEach((entry) => entry.bar?.dispose());
|
|
6782
6775
|
this.#items.clear();
|
|
@@ -6809,7 +6802,7 @@ var NotificationCenter = class _NotificationCenter extends ComponentBase {
|
|
|
6809
6802
|
if (idx !== -1) {
|
|
6810
6803
|
const [removed] = this.notifications.splice(idx, 1);
|
|
6811
6804
|
this.#stopTimer(removed.id);
|
|
6812
|
-
this.#
|
|
6805
|
+
this.#recordDismissal(removed, "overflow");
|
|
6813
6806
|
}
|
|
6814
6807
|
}
|
|
6815
6808
|
this.#recordHistory(normalized);
|
|
@@ -6824,13 +6817,13 @@ var NotificationCenter = class _NotificationCenter extends ComponentBase {
|
|
|
6824
6817
|
const h = this.held.findIndex((item) => item.id === notificationId);
|
|
6825
6818
|
if (h === -1) return false;
|
|
6826
6819
|
const [gone] = this.held.splice(h, 1);
|
|
6827
|
-
this.#
|
|
6820
|
+
this.#recordDismissal(gone, reason);
|
|
6828
6821
|
this.#render();
|
|
6829
6822
|
return true;
|
|
6830
6823
|
}
|
|
6831
6824
|
const [removed] = this.notifications.splice(idx, 1);
|
|
6832
6825
|
this.#stopTimer(notificationId);
|
|
6833
|
-
this.#
|
|
6826
|
+
this.#recordDismissal(removed, reason);
|
|
6834
6827
|
this.#rebalance();
|
|
6835
6828
|
this.#render();
|
|
6836
6829
|
return true;
|
|
@@ -7057,12 +7050,40 @@ var NotificationCenter = class _NotificationCenter extends ComponentBase {
|
|
|
7057
7050
|
this.activeTimers.forEach((timer) => clearTimeout(timer));
|
|
7058
7051
|
this.activeTimers.clear();
|
|
7059
7052
|
}
|
|
7053
|
+
/**
|
|
7054
|
+
* Hear of every entry added to `history`, by `show()`, `warn()`/`error()`/
|
|
7055
|
+
* `info()`/`success()` and the bus alike: `listener(entry, center)`, once
|
|
7056
|
+
* per toast, after the entry is in `history`. A dismissal changes the
|
|
7057
|
+
* toast's own entry and is not an entry of its own. Answers the unsubscribe.
|
|
7058
|
+
*/
|
|
7059
|
+
subscribe(listener) {
|
|
7060
|
+
if (typeof listener !== "function") return () => {
|
|
7061
|
+
};
|
|
7062
|
+
this.#historyListeners.add(listener);
|
|
7063
|
+
return () => {
|
|
7064
|
+
this.#historyListeners.delete(listener);
|
|
7065
|
+
};
|
|
7066
|
+
}
|
|
7060
7067
|
#recordHistory(entry) {
|
|
7061
7068
|
if (entry.skipHistory) return;
|
|
7062
7069
|
this.history.push(entry);
|
|
7063
7070
|
if (this.history.length > this.historyLimit) {
|
|
7064
7071
|
this.history.shift();
|
|
7065
7072
|
}
|
|
7073
|
+
[...this.#historyListeners].forEach((listener) => {
|
|
7074
|
+
try {
|
|
7075
|
+
listener(entry, this);
|
|
7076
|
+
} catch (err) {
|
|
7077
|
+
this.logger?.warn?.("twm-notification-center", "History listener failed", { err });
|
|
7078
|
+
}
|
|
7079
|
+
});
|
|
7080
|
+
}
|
|
7081
|
+
/** A toast that left marks its own entry: one toast, one entry. */
|
|
7082
|
+
#recordDismissal(notification, reason) {
|
|
7083
|
+
if (notification.skipHistory) return;
|
|
7084
|
+
const at = this.history.findLastIndex((entry) => entry.id === notification.id);
|
|
7085
|
+
if (at === -1) return;
|
|
7086
|
+
this.history[at] = { ...notification, dismissed: true, reason };
|
|
7066
7087
|
}
|
|
7067
7088
|
/** A progress toast that has not been given an outcome yet. */
|
|
7068
7089
|
static #isRunning(notification) {
|