dsh-notify-me 1.10.3 → 1.10.5
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 +24 -0
- package/README.en.md +9 -9
- package/README.md +9 -9
- package/lib/client.js +133 -38
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to **dsh-notify-me** are documented here.
|
|
4
4
|
|
|
5
|
+
## [1.10.5] — 2026-10-21
|
|
6
|
+
|
|
7
|
+
### 修复
|
|
8
|
+
|
|
9
|
+
- **桌面端窗口被遮住时「回复完成」不提醒**:本插件判断「你离开了」只看 `document.hidden` / `visibilityState`,而桌面壳里把 DSH 窗口切到别的应用后面、或被别的窗口盖住时,页面依然是 `visible` —— 只有最小化或整窗被遮挡才会变成 `hidden`。于是「只在后台提醒」的回复完成提醒在窗口只是被盖住时被静默丢弃(1.10.5 之前只在真正最小化时才响)。现在把窗口焦点也算进「离开」:页面失焦即视为后台,失焦瞬间同样补发此前因为「就在你眼前」而被压下的审批提醒;窗口回到前台后照常安静。设置页三个相关开关的文案同步改为「窗口在前台时也提醒」,说明里点明「切走 / 被遮挡 / 最小化」都算后台。
|
|
10
|
+
|
|
11
|
+
### 测试
|
|
12
|
+
|
|
13
|
+
- 冒烟新增 `awayWithoutHiding` 用例:焦点在时回复完成静默、失焦后同一页发出提醒、回焦再次静默、`visibilityState` 全程保持 `visible`、失焦时补发被压下的审批提醒。
|
|
14
|
+
|
|
15
|
+
## [1.10.4] — 2026-10-21
|
|
16
|
+
|
|
17
|
+
### 新增
|
|
18
|
+
|
|
19
|
+
- **桌面端审批按钮落到页内卡片**:桌面壳在 `dsh-app:` 协议下拿不到 Service Worker(注册被拒),带按钮的审批通知因此只剩标题、点不出「同意 / 拒绝」。现在这种拿不到 SW 的情形会在窗口角落画一张卡片:审批标题与正文、`同意` / `拒绝` 两个按钮、正文过长时可就地展开「详情」,12 秒自动收起,同一条审批不叠第二张卡。按钮走的是和系统通知完全同一条裁决路径(`allowed-once` / `rejected`),裁完即撤卡。Web 端(SW 可用)行为不变,仍是带按钮的系统通知。
|
|
20
|
+
|
|
21
|
+
### 修复
|
|
22
|
+
|
|
23
|
+
- 页面里写的 `api.version` 与 `package.json` 对齐(1.10.3 的发版提交只改了 `package.json`,页内版本号停在 1.10.2,冒烟断言因此一开始就是红的)。
|
|
24
|
+
|
|
25
|
+
### 测试
|
|
26
|
+
|
|
27
|
+
- 冒烟新增 12 组断言覆盖桌面降级卡片:出现时机、两个按钮、详情展开/收起、点卡片本体不撤卡、同意、拒绝、审批从状态里消失时撤卡、同一条审批不叠卡、开关关闭时不画卡、12 秒回收、Web 路径不回归。
|
|
28
|
+
|
|
5
29
|
## [1.10.3] — 2026-10-21
|
|
6
30
|
|
|
7
31
|
### 修复
|
package/README.en.md
CHANGED
|
@@ -75,16 +75,16 @@ The Electron application manages the `desktop` profile. Do not change the Web co
|
|
|
75
75
|
|
|
76
76
|
1. Click once in the DSH page and allow notifications when prompted. If you previously declined, allow them in the browser's site settings. The operating system must also allow notifications from the browser / DSH Desktop.
|
|
77
77
|
2. Open **Settings → Notify me**. Confirm that the section and plugin version at its foot appear, and leave the master switch, the alert type on system notifications, and sound enabled.
|
|
78
|
-
3. Click **Test "reply finished"** on the settings page. A test notification should appear and sound according to your cue settings. This local test does not forward to external endpoints and ignores the "while the
|
|
78
|
+
3. Click **Test "reply finished"** on the settings page. A test notification should appear and sound according to your cue settings. This local test does not forward to external endpoints and ignores the "while the window is in front" toggle.
|
|
79
79
|
|
|
80
|
-
Web users can also test approval buttons. On Desktop
|
|
80
|
+
Web users can also test approval buttons. On Desktop the shell cannot register a Service Worker, so this test draws an in-page card with Approve / Reject in the window corner (since 1.10.4; earlier versions sent a plain notification).
|
|
81
81
|
|
|
82
82
|
**Optional console check** — in environments with DevTools, you can also run:
|
|
83
83
|
|
|
84
84
|
```js
|
|
85
85
|
window.__dshNotifyMe.test("done") // "reply finished" sample
|
|
86
86
|
window.__dshNotifyMe.test("attention") // "needs your input" sample
|
|
87
|
-
window.__dshNotifyMe.test("approval") // Approve / Reject with an active Web bridge;
|
|
87
|
+
window.__dshNotifyMe.test("approval") // Approve / Reject with an active Web bridge; an in-page card on Desktop
|
|
88
88
|
window.__dshNotifyMe.debug() // the bridge field reports the button channel: "active" = buttons work
|
|
89
89
|
```
|
|
90
90
|
|
|
@@ -108,9 +108,9 @@ dsh-notify-me decouples **supervision** from **sitting in front of the screen**:
|
|
|
108
108
|
| 🔔 Input needed | Alerts even in the foreground; the conversation on screen keeps only a title marker |
|
|
109
109
|
| ✅ Reply finished | Alerts while the page is hidden / backgrounded |
|
|
110
110
|
|
|
111
|
-
**Input needed** covers approval requests, plan review, and questions (`ask_user_question`).
|
|
111
|
+
**Input needed** covers approval requests, plan review, and questions (`ask_user_question`). Approvals carry Approve / Reject: on Web as a system notification with buttons, on Desktop (which cannot register a Service Worker) as a card drawn in the window corner.
|
|
112
112
|
|
|
113
|
-
**Reply finished** also covers background sessions. A finished but unread session keeps the `✅ Reply finished ·` title marker until opened. Title markers are independent of the "while the
|
|
113
|
+
**Reply finished** also covers background sessions. A finished but unread session keeps the `✅ Reply finished ·` title marker until opened. Title markers are independent of the "while the window is in front" toggles.
|
|
114
114
|
|
|
115
115
|
> **Background** subagent child sessions are **not** alerted about by default: DSH lists each of them as its own session, but they are steps inside the parent conversation's turn, so an alert for them is only noise. The subagent session you have open is not part of this and alerts as usual. Settings → "Ignore subagent sessions" turns the mute off.
|
|
116
116
|
|
|
@@ -125,7 +125,7 @@ Open **Settings → Notify me**. The page runs as how you get told, when to aler
|
|
|
125
125
|
- **System notifications** (default): native toasts landing in the Windows / macOS notification center; needs notification permission;
|
|
126
126
|
- **In-page overlay**: cards drawn in the page corner, on whichever display the window sits on — system notifications only land on the primary display. No permission needed, and the Desktop shell can Approve / Reject right on the card. With the overlay chosen, a Corner row appears; open it to pick one of four positions. See [Secondary displays and the overlay](#secondary-displays-and-the-overlay);
|
|
127
127
|
- **Sound / Volume**: the switch is direct; the slider folds into that row — click the title (▸ turns into ▾) to reveal it. What it plays comes from Advanced → Alert sound;
|
|
128
|
-
- **Decide approvals from the toast** (on by default):
|
|
128
|
+
- **Decide approvals from the toast** (on by default): approvals carry Approve / Reject that settle the request in place. On Web that is a system notification with buttons; on Desktop `dsh-app://` cannot register a Service Worker, so the buttons are drawn on a card in the window corner (auto-dismissed after 12 s, one card per approval). You can also switch the channel to the in-page overlay and decide there.
|
|
129
129
|
|
|
130
130
|
### Secondary displays and the overlay
|
|
131
131
|
|
|
@@ -143,9 +143,9 @@ The overlay only knows which screen the window is on, not which screen you are l
|
|
|
143
143
|
|
|
144
144
|
### When to alert
|
|
145
145
|
|
|
146
|
-
- **Also alert "needs you" while the
|
|
147
|
-
- **Stay quiet for the conversation on screen** (on): a wait belonging to the conversation you are looking at keeps only the tab marker so the approval card stays readable;
|
|
148
|
-
- **Also alert "reply finished" while the
|
|
146
|
+
- **Also alert "needs you" while the window is in front** (on): turn it off to hear only once the window goes to the background — another app focused, the window covered, or minimized all count;
|
|
147
|
+
- **Stay quiet for the conversation on screen** (on): a wait belonging to the conversation you are looking at keeps only the tab marker so the approval card stays readable; moving the window to the background restores alerts;
|
|
148
|
+
- **Also alert "reply finished" while the window is in front** (off): by default only background finishes alert (a covered Desktop window counts as background since 1.10.5, not only a minimized one);
|
|
149
149
|
- **Ignore subagent sessions** (on): skip background sessions spawned by a subagent — the parent conversation covers them; the one you have open still alerts;
|
|
150
150
|
- **Click a toast to open that conversation** (on): the click switches to the conversation that raised it; in a browser it also brings the window forward, while on the Desktop shell it is flaky — sometimes the window comes forward, sometimes only the conversation switches (see [How the Desktop window gets raised](#how-the-desktop-window-gets-raised)); with several tabs open only one is raised, and off means a click does nothing.
|
|
151
151
|
|
package/README.md
CHANGED
|
@@ -75,16 +75,16 @@ dsh plugin --profile web add dsh-notify-me
|
|
|
75
75
|
|
|
76
76
|
1. 在 DSH 页面上点击一次,按提示允许通知;若此前拒绝过,在浏览器站点设置中允许通知。操作系统也需要允许对应浏览器 / DSH Desktop 显示通知。
|
|
77
77
|
2. 打开 **设置 → 通知提醒**,确认出现该分区及底部插件版本号,保持「启用提醒」「提示音」开启,通知类型先留「系统通知」。
|
|
78
|
-
3.
|
|
78
|
+
3. 点击设置页的「测试『回复完成』」按钮:应出现一条测试通知,并按当前音效设置播放声音。这个本地测试不会转发到外部通知端点,也不受「窗口在前台时也提醒」开关限制。
|
|
79
79
|
|
|
80
|
-
Web
|
|
80
|
+
Web 用户可再测试审批按钮。桌面端拿不到 Service Worker,这条测试会在窗口角落画一张带「同意 / 拒绝」的卡片(1.10.4 起;之前的版本这里只发普通通知)。
|
|
81
81
|
|
|
82
82
|
**控制台验证(可选)** — 支持 DevTools 的环境也可执行:
|
|
83
83
|
|
|
84
84
|
```js
|
|
85
85
|
window.__dshNotifyMe.test("done") // 「完成」示例
|
|
86
86
|
window.__dshNotifyMe.test("attention") // 「需要你」示例
|
|
87
|
-
window.__dshNotifyMe.test("approval") //
|
|
87
|
+
window.__dshNotifyMe.test("approval") // 桥可用时带「同意 / 拒绝」按钮;桌面端画在窗口角落的卡片上
|
|
88
88
|
window.__dshNotifyMe.debug() // 桥接状态看 bridge 字段,active = 按钮可用
|
|
89
89
|
```
|
|
90
90
|
|
|
@@ -104,9 +104,9 @@ dsh-notify-me 把「监督」和「守在屏幕前」解耦:只在真的出现
|
|
|
104
104
|
| 🔔 需要你操作 | 页面可见也提醒;当前对话只留标题标记 |
|
|
105
105
|
| ✅ 回复完成 | 页面隐藏 / 后台时提醒 |
|
|
106
106
|
|
|
107
|
-
**需要你操作**包括审批请求、方案待确认(plan review)和提问(`ask_user_question
|
|
107
|
+
**需要你操作**包括审批请求、方案待确认(plan review)和提问(`ask_user_question`)。审批带「同意 / 拒绝」:Web 走带按钮的系统通知,桌面端(注册不了 Service Worker)改画在窗口角落的卡片上。
|
|
108
108
|
|
|
109
|
-
**回复完成**也覆盖后台会话;完成但还没看时,标题带 `✅ 回复完成 ·`
|
|
109
|
+
**回复完成**也覆盖后台会话;完成但还没看时,标题带 `✅ 回复完成 ·` 标记,打开对应会话即撤。标题标记不受「窗口在前台时也提醒」开关限制。
|
|
110
110
|
|
|
111
111
|
> **后台**子代理(subagent)子会话的等待与完成默认**不提醒**:DSH 把每个子代理子会话列成独立会话,但它们是所属主对话那一轮里的步骤,提醒只会是噪音。你当前打开的那个子代理会话不算在内,照常提醒。设置页「子代理会话不提醒」可关掉这个静音。
|
|
112
112
|
|
|
@@ -121,7 +121,7 @@ dsh-notify-me 把「监督」和「守在屏幕前」解耦:只在真的出现
|
|
|
121
121
|
- **系统通知**(默认):进 Windows / macOS 通知中心,需要通知权限;
|
|
122
122
|
- **页面浮层**:在 DSH 页面角落画提醒卡片,窗口在哪块屏它就在哪块屏——系统通知只会画在主屏,主屏不看时用它;不需要通知权限,桌面端也能在卡片上直接「同意 / 拒绝」。选中后卡片里会多出「浮层位置」子行,点开可选四个角落,见[副屏与浮层提醒](#副屏与浮层提醒);
|
|
123
123
|
- **提示音 / 音量**:开关直接切;音量滑块收在该行里,点一下标题(▸ 变成 ▾)展开。具体播什么由「高级设置 → 音效来源」决定;
|
|
124
|
-
-
|
|
124
|
+
- **审批通知上直接裁决**(默认开):审批带「同意 / 拒绝」,点一下即完成裁决,不用切回页面。Web 走带按钮的系统通知;桌面端 `dsh-app://` 注册不了 Service Worker,于是把按钮画在窗口角落的卡片上(12 秒自动收起,同一条审批不叠第二张卡)。也可以把「通知类型」换成「页面浮层」,改在浮层卡片上裁决。
|
|
125
125
|
|
|
126
126
|
### 副屏与浮层提醒
|
|
127
127
|
|
|
@@ -139,9 +139,9 @@ Windows 的系统通知横幅固定画在主屏上,系统没有设置项、浏
|
|
|
139
139
|
|
|
140
140
|
### 什么时候提醒
|
|
141
141
|
|
|
142
|
-
-
|
|
143
|
-
-
|
|
144
|
-
-
|
|
142
|
+
- **窗口在前台时也提醒「需要你」**(默认开):窗口在前台也提醒;关掉后只有窗口转到后台才提醒——切到别的应用、被别的窗口遮住、最小化都算后台;
|
|
143
|
+
- **当前对话不弹通知**(默认开):等待就发生在你正看着的对话里时只留标签页标记,免得挡住审批卡片;窗口转到后台后照常提醒,若你已处理掉就作废;
|
|
144
|
+
- **窗口在前台时也提醒「回复完成」**(默认关):默认只在窗口转到后台时提醒,避免打扰(桌面端窗口被遮住也算后台,1.10.5 起不再要求最小化);
|
|
145
145
|
- **子代理会话不提醒**(默认开):跳过后台子代理(subagent)会话——它们是主对话那一轮的步骤,完成由主对话自己的提醒覆盖;你当前打开的那个子代理会话照常提醒;
|
|
146
146
|
- **点击通知回到对应对话**(默认开):点通知切到提醒所属的对话;浏览器里同时把窗口带到前台;桌面端不稳定——有时窗口会上前,有时只切对话(见[桌面端怎么抬窗的](#桌面端怎么抬窗的));多个标签页同时开着时只调起一个(优先当前可见的那个);关掉后点通知不做任何事。
|
|
147
147
|
|
package/lib/client.js
CHANGED
|
@@ -321,13 +321,18 @@
|
|
|
321
321
|
// Persist a patch, swap the live module config and re-apply any active
|
|
322
322
|
// title marker (its prefix follows the notification language).
|
|
323
323
|
function setConfigLocal(patch) {
|
|
324
|
+
var wasOverlay = config.channel === "overlay";
|
|
324
325
|
config = writeConfig(patch);
|
|
325
326
|
applyTitle();
|
|
326
|
-
// Overlay re-apply:
|
|
327
|
-
// new corner moves the layer without waiting for the next
|
|
327
|
+
// Overlay re-apply: leaving the overlay channel takes its cards down at
|
|
328
|
+
// once, and a new corner moves the layer without waiting for the next
|
|
329
|
+
// alert. Cards can also exist on the toast channel (the desktop fallback),
|
|
330
|
+
// so an unrelated patch must not sweep them away.
|
|
328
331
|
try {
|
|
329
|
-
if (config.channel !== "overlay")
|
|
330
|
-
|
|
332
|
+
if (config.channel !== "overlay") {
|
|
333
|
+
if (wasOverlay) clearOverlays();
|
|
334
|
+
else if (overlayLayer) overlayLayer.className = "dnm-ov-layer dnm-ov-" + overlayCorner();
|
|
335
|
+
} else if (overlayLayer) overlayLayer.className = "dnm-ov-layer dnm-ov-" + overlayCorner();
|
|
331
336
|
} catch (e) {}
|
|
332
337
|
return config;
|
|
333
338
|
}
|
|
@@ -384,6 +389,8 @@
|
|
|
384
389
|
approve: "同意",
|
|
385
390
|
reject: "拒绝",
|
|
386
391
|
dismiss: "关闭这条提醒",
|
|
392
|
+
details: "详情",
|
|
393
|
+
collapse: "收起",
|
|
387
394
|
testAttention: "这是一条测试提醒:DSH 需要你的操作",
|
|
388
395
|
testDone: "测试提醒 · 回复完成示例",
|
|
389
396
|
testApproval: "这是一条测试审批:点「同意 / 拒绝」试试快捷裁决",
|
|
@@ -411,6 +418,8 @@
|
|
|
411
418
|
approve: "Approve",
|
|
412
419
|
reject: "Reject",
|
|
413
420
|
dismiss: "Dismiss this alert",
|
|
421
|
+
details: "Details",
|
|
422
|
+
collapse: "Collapse",
|
|
414
423
|
testAttention: "This is a test alert: DSH is waiting for your input",
|
|
415
424
|
testDone: "Test alert · reply-finished sample",
|
|
416
425
|
testApproval: "This is a test approval: try the Approve / Reject buttons to settle it",
|
|
@@ -833,9 +842,11 @@
|
|
|
833
842
|
".dnm-ov-attention .dnm-ov-dot{color:var(--dsw-alias-state-warning-primary,#f59e0b);opacity:1}" +
|
|
834
843
|
".dnm-ov-done .dnm-ov-dot{color:var(--dsw-alias-state-success-primary,#22c55e);opacity:1}" +
|
|
835
844
|
".dnm-ov-title{flex:1;min-width:0;font-weight:600;font-size:13px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}" +
|
|
836
|
-
".dnm-ov-x{flex:none;border:0;background:transparent;color:inherit;opacity:.6;font-size:15px;line-height:15px;padding:0 2px;cursor:pointer;font-family:inherit}" +
|
|
837
|
-
".dnm-ov-x:hover{opacity:1}" +
|
|
845
|
+
".dnm-ov-x,.dnm-ov-more{flex:none;border:0;background:transparent;color:inherit;opacity:.6;font-size:15px;line-height:15px;padding:0 2px;cursor:pointer;font-family:inherit}" +
|
|
846
|
+
".dnm-ov-x:hover,.dnm-ov-more:hover{opacity:1}" +
|
|
847
|
+
".dnm-ov-more{font-size:11px;opacity:.8;white-space:nowrap}" +
|
|
838
848
|
".dnm-ov-body{color:var(--dsw-alias-label-secondary,rgba(220,222,226,.86));font-size:12px;line-height:18px;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;word-break:break-word}" +
|
|
849
|
+
".dnm-ov.dnm-ov-open .dnm-ov-body{display:block;-webkit-line-clamp:unset;max-height:40vh;overflow:auto}" +
|
|
839
850
|
".dnm-ov-actions{display:flex;gap:8px;margin-top:2px}" +
|
|
840
851
|
".dnm-ov-btn{flex:1;border:1px solid var(--dsw-alias-border-l2,rgba(128,128,128,.4));background:var(--dsw-alias-bg-layer-1,rgba(255,255,255,.06));color:inherit;border-radius:8px;padding:5px 10px;font-size:12px;font-family:inherit;cursor:pointer}" +
|
|
841
852
|
".dnm-ov-btn:hover{background:var(--dsw-alias-bg-layer-2,rgba(255,255,255,.12))}" +
|
|
@@ -911,17 +922,30 @@
|
|
|
911
922
|
}
|
|
912
923
|
}
|
|
913
924
|
function overlayCount() { return Object.keys(overlayCards).length; }
|
|
914
|
-
//
|
|
915
|
-
//
|
|
916
|
-
|
|
917
|
-
|
|
925
|
+
// One card builder for both channels: the overlay channel draws with it,
|
|
926
|
+
// and so does the desktop fallback below. `opts` keeps the two behaviours
|
|
927
|
+
// apart: the overlay card is a shortcut (a click opens the conversation and
|
|
928
|
+
// takes the card down), while the fallback card carries a decision the OS
|
|
929
|
+
// notification cannot — so a click must not dismiss it unanswered, and a
|
|
930
|
+
// "详情 / Details" toggle exposes the body that the CSS clamps to 3 lines.
|
|
931
|
+
function drawOverlayCard(kind, title, body, sessionId, quick, opts) {
|
|
932
|
+
opts = opts || {};
|
|
918
933
|
var layer = ensureOverlayLayer();
|
|
919
934
|
if (!layer) return false;
|
|
935
|
+
// One card per approval key: a re-delivery (the quieted copy that fires
|
|
936
|
+
// when the page is backgrounded) must not stack a second card.
|
|
937
|
+
if (quick && typeof quick.key === "string") {
|
|
938
|
+
var known = Object.keys(overlayCards);
|
|
939
|
+
for (var k = 0; k < known.length; k++) {
|
|
940
|
+
if (overlayCards[known[k]].key === quick.key) return false;
|
|
941
|
+
}
|
|
942
|
+
}
|
|
920
943
|
var d = texts();
|
|
921
944
|
try {
|
|
922
945
|
var id = "ov" + (++overlaySeq);
|
|
946
|
+
var baseClass = "dnm-ov dnm-ov-" + (kind === "attention" ? "attention" : "done");
|
|
923
947
|
var card = document.createElement("div");
|
|
924
|
-
card.className =
|
|
948
|
+
card.className = baseClass;
|
|
925
949
|
card.setAttribute("role", kind === "attention" ? "alert" : "status");
|
|
926
950
|
var head = document.createElement("div");
|
|
927
951
|
head.className = "dnm-ov-head";
|
|
@@ -930,6 +954,22 @@
|
|
|
930
954
|
var label = document.createElement("span");
|
|
931
955
|
label.className = "dnm-ov-title";
|
|
932
956
|
label.textContent = String(title == null ? "" : title);
|
|
957
|
+
var more = null;
|
|
958
|
+
if (opts.expand && body) {
|
|
959
|
+
more = document.createElement("button");
|
|
960
|
+
more.type = "button";
|
|
961
|
+
more.className = "dnm-ov-more";
|
|
962
|
+
more.setAttribute("aria-label", d.details);
|
|
963
|
+
more.setAttribute("aria-expanded", "false");
|
|
964
|
+
more.textContent = d.details;
|
|
965
|
+
more.onclick = function (ev) {
|
|
966
|
+
if (ev && ev.stopPropagation) ev.stopPropagation();
|
|
967
|
+
var open = card.className.indexOf("dnm-ov-open") !== -1;
|
|
968
|
+
card.className = baseClass + (open ? "" : " dnm-ov-open");
|
|
969
|
+
more.textContent = open ? d.details : d.collapse;
|
|
970
|
+
more.setAttribute("aria-expanded", open ? "false" : "true");
|
|
971
|
+
};
|
|
972
|
+
}
|
|
933
973
|
var close = document.createElement("button");
|
|
934
974
|
close.type = "button";
|
|
935
975
|
close.className = "dnm-ov-x";
|
|
@@ -941,6 +981,7 @@
|
|
|
941
981
|
};
|
|
942
982
|
head.appendChild(dot);
|
|
943
983
|
head.appendChild(label);
|
|
984
|
+
if (more) head.appendChild(more);
|
|
944
985
|
head.appendChild(close);
|
|
945
986
|
card.appendChild(head);
|
|
946
987
|
if (body) {
|
|
@@ -970,10 +1011,12 @@
|
|
|
970
1011
|
}
|
|
971
1012
|
// A click on the card body opens the conversation, exactly like a toast
|
|
972
1013
|
// body click — and here it is an ordinary page gesture, so it reaches the
|
|
973
|
-
// desktop's dsh:// raise path even though a worker click would not.
|
|
1014
|
+
// desktop's dsh:// raise path even though a worker click would not. The
|
|
1015
|
+
// overlay channel also takes the card down; the fallback card stays, so
|
|
1016
|
+
// the decision is still one click away after a look at the conversation.
|
|
974
1017
|
card.onclick = function () {
|
|
975
1018
|
if (config.autoFocus) focusSession(sessionId || null);
|
|
976
|
-
dismissOverlay(id);
|
|
1019
|
+
if (opts.dismissOnClick !== false) dismissOverlay(id);
|
|
977
1020
|
};
|
|
978
1021
|
layer.appendChild(card);
|
|
979
1022
|
var rec = { card: card, key: (quick && quick.key) ? quick.key : null, kind: kind, timer: null };
|
|
@@ -987,6 +1030,29 @@
|
|
|
987
1030
|
return true;
|
|
988
1031
|
} catch (e) { return false; }
|
|
989
1032
|
}
|
|
1033
|
+
// Same inputs as showToast: `quick` is the approval record built by
|
|
1034
|
+
// quickFor(), and it is what lets an overlay settle the request in place.
|
|
1035
|
+
function showOverlay(kind, title, body, sessionId, quick) {
|
|
1036
|
+
if (config.channel !== "overlay") return false;
|
|
1037
|
+
return drawOverlayCard(kind, title, body, sessionId, quick, { dismissOnClick: true });
|
|
1038
|
+
}
|
|
1039
|
+
// The desktop shell cannot register the Service Worker that OS action
|
|
1040
|
+
// buttons require, so an approval toast on a dsh-app:// page has no button
|
|
1041
|
+
// to click. While the alert channel is system notifications, an approval
|
|
1042
|
+
// therefore also drops a card into the same in-page layer, in the same
|
|
1043
|
+
// corner and with the same styling: its buttons call decidePending(), the
|
|
1044
|
+
// exact path a Web toast button takes. Only states that prove the bridge is
|
|
1045
|
+
// unusable qualify — 'idle'/'registering' are transitional, 'disabled' means
|
|
1046
|
+
// the quick-decision switch is off (quickFor() then returns null, so no card
|
|
1047
|
+
// is drawn at all), and the Web path stays untouched once the worker is up.
|
|
1048
|
+
function showFallbackApprovalCard(kind, title, body, sessionId, quick) {
|
|
1049
|
+
if (kind !== "attention") return false;
|
|
1050
|
+
if (!quick || typeof quick.key !== "string") return false;
|
|
1051
|
+
if (config.channel !== "toast") return false;
|
|
1052
|
+
if (bridgeActive()) return false;
|
|
1053
|
+
if (swState.indexOf("unsupported:") !== 0 && swState.indexOf("failed:") !== 0) return false;
|
|
1054
|
+
return drawOverlayCard(kind, title, body, sessionId, quick, { dismissOnClick: false, expand: true });
|
|
1055
|
+
}
|
|
990
1056
|
// Sound: WebAudio beeps (no asset needed). The context only runs after a
|
|
991
1057
|
// user gesture; we unlock it on the first pointer/key event.
|
|
992
1058
|
var audioCtx = null;
|
|
@@ -1326,6 +1392,25 @@
|
|
|
1326
1392
|
|
|
1327
1393
|
function hidden() { return document.hidden || document.visibilityState === "hidden"; }
|
|
1328
1394
|
|
|
1395
|
+
// "Background" is not the same as "hidden": a desktop window sitting behind
|
|
1396
|
+
// another app, or a browser tab on a second monitor, still reports itself as
|
|
1397
|
+
// visible. Only a minimised or fully occluded window flips document.hidden,
|
|
1398
|
+
// so the reminders that are meant to reach a user who walked away stayed
|
|
1399
|
+
// silent whenever the window was merely covered. Window focus is the missing
|
|
1400
|
+
// half of the signal: an unfocused page counts as away.
|
|
1401
|
+
var pageFocused = true;
|
|
1402
|
+
try {
|
|
1403
|
+
if (typeof document.hasFocus === "function") pageFocused = document.hasFocus() !== false;
|
|
1404
|
+
} catch (e) {}
|
|
1405
|
+
function onPageFocus() { pageFocused = true; }
|
|
1406
|
+
function onPageBlur() {
|
|
1407
|
+
pageFocused = false;
|
|
1408
|
+
// Walking away is what re-arms the waits kept quiet because they belonged
|
|
1409
|
+
// to the conversation on screen; a blur alone never fires visibilitychange.
|
|
1410
|
+
try { flushQuieted(); } catch (e) {}
|
|
1411
|
+
}
|
|
1412
|
+
function away() { return hidden() || !pageFocused; }
|
|
1413
|
+
|
|
1329
1414
|
// Deliver one alert regardless of visibility rules (used by the test
|
|
1330
1415
|
// buttons); respects the master switch and permission state. The title
|
|
1331
1416
|
// marker is NOT touched here — its ownership belongs to the watchers
|
|
@@ -1341,6 +1426,7 @@
|
|
|
1341
1426
|
if (!config.enabled) return;
|
|
1342
1427
|
showToast(kind, title, body, sessionId, quick);
|
|
1343
1428
|
showOverlay(kind, title, body, sessionId, quick);
|
|
1429
|
+
showFallbackApprovalCard(kind, title, body, sessionId, quick);
|
|
1344
1430
|
if (kind === "attention") {
|
|
1345
1431
|
playAttentionSound();
|
|
1346
1432
|
} else {
|
|
@@ -1350,13 +1436,13 @@
|
|
|
1350
1436
|
}
|
|
1351
1437
|
function fire(kind, title, body, isCurrent, sessionId, quick) {
|
|
1352
1438
|
if (!config.enabled) return false;
|
|
1353
|
-
if (kind === "attention" && config.attentionHiddenOnly && !
|
|
1439
|
+
if (kind === "attention" && config.attentionHiddenOnly && !away()) return false;
|
|
1354
1440
|
// The wait belongs to the conversation the user is looking at right now:
|
|
1355
1441
|
// the approval card is already on screen, so a toast would only cover the
|
|
1356
1442
|
// thing it is asking about. The caller keeps the tab marker, and
|
|
1357
1443
|
// flushQuieted() re-delivers the alert the moment the page is backgrounded.
|
|
1358
1444
|
if (kind === "attention" && quietedByVisibleSession(isCurrent)) return false;
|
|
1359
|
-
if (kind === "done" && config.doneHiddenOnly && !
|
|
1445
|
+
if (kind === "done" && config.doneHiddenOnly && !away()) return false;
|
|
1360
1446
|
deliver(kind, title, body, sessionId, quick);
|
|
1361
1447
|
// The alert reached the user locally (or would have, had the tab a
|
|
1362
1448
|
// voice): mirror the same copy to the outside world. Fire-and-forget —
|
|
@@ -1368,7 +1454,7 @@
|
|
|
1368
1454
|
// session already on screen. Kept separate from fire() so the caller can
|
|
1369
1455
|
// queue exactly this case and nothing else.
|
|
1370
1456
|
function quietedByVisibleSession(isCurrent) {
|
|
1371
|
-
return !!(isCurrent && config.currentHiddenOnly && !
|
|
1457
|
+
return !!(isCurrent && config.currentHiddenOnly && !away());
|
|
1372
1458
|
}
|
|
1373
1459
|
// Queued copies of the alerts that rule suppressed. Delivered by key so a
|
|
1374
1460
|
// wait that got answered while the user was still on the page never fires.
|
|
@@ -1376,7 +1462,7 @@
|
|
|
1376
1462
|
if (key && copy) quietedAttention[key] = copy;
|
|
1377
1463
|
}
|
|
1378
1464
|
function flushQuieted() {
|
|
1379
|
-
if (!config.enabled || !
|
|
1465
|
+
if (!config.enabled || !away()) return;
|
|
1380
1466
|
var keys = Object.keys(quietedAttention);
|
|
1381
1467
|
for (var i = 0; i < keys.length; i++) {
|
|
1382
1468
|
var copy = quietedAttention[keys[i]];
|
|
@@ -1980,7 +2066,7 @@
|
|
|
1980
2066
|
});
|
|
1981
2067
|
} catch (e) {}
|
|
1982
2068
|
// keep in sync with package.json "version" on every release
|
|
1983
|
-
api.version = "1.10.
|
|
2069
|
+
api.version = "1.10.5";
|
|
1984
2070
|
api.setConfig = function (patch) { return setConfigLocal(patch); };
|
|
1985
2071
|
api.resetConfig = function () {
|
|
1986
2072
|
try {
|
|
@@ -2082,6 +2168,8 @@
|
|
|
2082
2168
|
actionKeys: Object.keys(pendingActions),
|
|
2083
2169
|
permission: ("Notification" in window) ? Notification.permission : "n/a",
|
|
2084
2170
|
visibility: hidden() ? "hidden" : "visible",
|
|
2171
|
+
focused: pageFocused,
|
|
2172
|
+
away: away(),
|
|
2085
2173
|
uiSession: uiPending ? "bound" : "unbound",
|
|
2086
2174
|
uiSessionNote: uiBindNote,
|
|
2087
2175
|
uiSessionSource: uiSource,
|
|
@@ -2182,27 +2270,27 @@
|
|
|
2182
2270
|
externalCountFailed: "失败 ",
|
|
2183
2271
|
externalNoTargets: "先添加至少一个推送目标。",
|
|
2184
2272
|
externalOffHint: "先打开「转发外部通知」。",
|
|
2185
|
-
attentionVisible: "
|
|
2186
|
-
attentionVisibleHint: "
|
|
2273
|
+
attentionVisible: "窗口在前台时也提醒「需要你」",
|
|
2274
|
+
attentionVisibleHint: "窗口在前台也提醒。关掉后只在窗口转到后台(切走、被遮挡或最小化)时提醒。",
|
|
2187
2275
|
currentQuiet: "当前对话不弹通知",
|
|
2188
|
-
currentQuietHint: "
|
|
2189
|
-
doneVisible: "
|
|
2190
|
-
doneVisibleHint: "
|
|
2276
|
+
currentQuietHint: "等待就发生在你正看着的对话里时只留标签页标记,免得挡住审批卡片;窗口转到后台后照常提醒。",
|
|
2277
|
+
doneVisible: "窗口在前台时也提醒「回复完成」",
|
|
2278
|
+
doneVisibleHint: "窗口在前台也提醒。默认只在窗口转到后台时提醒,避免打扰。",
|
|
2191
2279
|
subagentQuiet: "子代理会话不提醒",
|
|
2192
2280
|
subagentQuietHint: "跳过后台子代理会话(结果由所属主对话的提醒覆盖);你正打开的那个照常提醒。",
|
|
2193
2281
|
autoFocus: "点击通知回到对应对话",
|
|
2194
2282
|
autoFocusHint: "点通知会切到这条提醒所属的对话;关掉后点击不做任何事。",
|
|
2195
2283
|
quickActions: "审批通知上直接裁决",
|
|
2196
|
-
quickActionsHint: "审批通知上带「同意 / 拒绝」,点一下即完成裁决,不用切回页面。需要 Service Worker(http://127.0.0.1 或 https
|
|
2284
|
+
quickActionsHint: "审批通知上带「同意 / 拒绝」,点一下即完成裁决,不用切回页面。需要 Service Worker(http://127.0.0.1 或 https);用不了时按钮改画在窗口角落的卡片上(桌面端就是这条路)。",
|
|
2197
2285
|
quickApprovalNote: "「同意」等价于审批卡片上的「允许一次」,只放行这一次。",
|
|
2198
2286
|
bridgeOff: "快捷裁决按钮暂不可用(",
|
|
2199
|
-
bridgeOffTail: "
|
|
2200
|
-
bridgeOffDesktop: "
|
|
2287
|
+
bridgeOffTail: "):系统通知没有按钮,按钮改画在窗口角落的卡片上。",
|
|
2288
|
+
bridgeOffDesktop: "系统通知本身没有「同意 / 拒绝」按钮:桌面端页面(dsh-app://)注册不了 Service Worker(宿主少给 allowServiceWorkers 特权)。审批类提醒会同时在窗口角落弹一张卡片,按钮就在卡片上;想只留卡片、不弹系统通知,把「提醒方式」切到「页面浮层」。",
|
|
2201
2289
|
overlay: "页面浮层提醒",
|
|
2202
2290
|
overlayHint: "在窗口角落画提醒卡片:窗口在哪块屏,它就在哪块屏;系统通知只会出现在主屏,主屏不看时用它。不需要通知权限,卡片上能直接「同意 / 拒绝」。与系统通知二选一。",
|
|
2203
2291
|
channelLabel: "通知类型",
|
|
2204
2292
|
overlaySeg: "浮层提醒",
|
|
2205
|
-
channelHint: "
|
|
2293
|
+
channelHint: "系统通知只会出现在主屏,进通知中心要权限,桌面端的通知本身没有「同意 / 拒绝」按钮(审批会额外在窗口角落弹一张可点击的卡片);页面浮层画在 DSH 窗口所在的那块屏,不需要权限,卡片上能直接裁决。两个只能选一个。",
|
|
2206
2294
|
overlayCorner: "浮层位置",
|
|
2207
2295
|
overlayCornerHint: "贴在 DSH 窗口的哪个角;窗口在哪块屏,浮层就出现在哪块屏。",
|
|
2208
2296
|
overlayPosTR: "右上",
|
|
@@ -2210,7 +2298,7 @@
|
|
|
2210
2298
|
overlayPosTL: "左上",
|
|
2211
2299
|
overlayPosBL: "左下",
|
|
2212
2300
|
overlayQuickNote: "浮层上的「同意」同样等价于审批卡片上的「允许一次」,与通知按钮一致。",
|
|
2213
|
-
testApprovalNoBridge: "
|
|
2301
|
+
testApprovalNoBridge: "已发测试提醒:系统通知没有按钮,快捷裁决按钮画在窗口角落的卡片上。",
|
|
2214
2302
|
testNeedsQuick: "先打开上面的「审批通知上直接裁决」才能测按钮。",
|
|
2215
2303
|
volume: "音量",
|
|
2216
2304
|
language: "通知语言",
|
|
@@ -2312,27 +2400,27 @@
|
|
|
2312
2400
|
externalCountFailed: "failed ",
|
|
2313
2401
|
externalNoTargets: "Add at least one push target first.",
|
|
2314
2402
|
externalOffHint: "Turn on \"Forward to external targets\" first.",
|
|
2315
|
-
attentionVisible: "Also alert \"needs you\" while the
|
|
2316
|
-
attentionVisibleHint: "Alert even while the
|
|
2403
|
+
attentionVisible: "Also alert \"needs you\" while the window is in front",
|
|
2404
|
+
attentionVisibleHint: "Alert even while the window has focus. Turn off to hear only once the window goes to the background (another app, covered, or minimised).",
|
|
2317
2405
|
currentQuiet: "Stay quiet for the conversation on screen",
|
|
2318
|
-
currentQuietHint: "A wait belonging to the conversation on screen keeps only the tab marker, so the approval card stays readable;
|
|
2319
|
-
doneVisible: "Also alert \"reply finished\" while the
|
|
2320
|
-
doneVisibleHint: "Alert even while the
|
|
2406
|
+
currentQuietHint: "A wait belonging to the conversation on screen keeps only the tab marker, so the approval card stays readable; moving the window to the background restores alerts.",
|
|
2407
|
+
doneVisible: "Also alert \"reply finished\" while the window is in front",
|
|
2408
|
+
doneVisibleHint: "Alert even while the window has focus. By default only finishes in the background alert.",
|
|
2321
2409
|
subagentQuiet: "Ignore subagent sessions",
|
|
2322
2410
|
subagentQuietHint: "Skip sessions spawned by a subagent (the parent conversation covers them); the one you have open still alerts.",
|
|
2323
2411
|
autoFocus: "Click a toast to open that conversation",
|
|
2324
2412
|
autoFocusHint: "A click switches to the conversation the alert came from; off means a click does nothing.",
|
|
2325
2413
|
quickActions: "Decide approvals from the toast",
|
|
2326
|
-
quickActionsHint: "Approval notifications carry Approve / Reject; one click settles the request without switching back. Needs a Service Worker browser (http://127.0.0.1 or https)
|
|
2414
|
+
quickActionsHint: "Approval notifications carry Approve / Reject; one click settles the request without switching back. Needs a Service Worker browser (http://127.0.0.1 or https); when it is unavailable the buttons land on a card in the window corner (the Desktop shell's route).",
|
|
2327
2415
|
quickApprovalNote: "\"Approve\" equals \"Allow once\" on the approval card — it allows that one call only.",
|
|
2328
2416
|
bridgeOff: "Quick-decision buttons are unavailable right now (",
|
|
2329
|
-
bridgeOffTail: ");
|
|
2330
|
-
bridgeOffDesktop: "System notifications carry no Approve / Reject buttons: dsh-app:// pages cannot register a Service Worker (the shell does not grant the allowServiceWorkers privilege)
|
|
2417
|
+
bridgeOffTail: "); system notifications carry no buttons, so they are drawn on a card in the window corner instead.",
|
|
2418
|
+
bridgeOffDesktop: "System notifications carry no Approve / Reject buttons: dsh-app:// pages cannot register a Service Worker (the shell does not grant the allowServiceWorkers privilege). An approval therefore also drops a clickable card in the window corner, buttons and all; switch the alert channel to the in-page overlay to keep only the card and skip the system notification.",
|
|
2331
2419
|
overlay: "In-page overlay",
|
|
2332
2420
|
overlayHint: "Draw a card in the window corner, on whatever display the window sits on. System notifications only ever show on the primary display. No permission needed, and the card carries Approve / Reject directly. It replaces system notifications rather than adding to them.",
|
|
2333
2421
|
channelLabel: "Alert type",
|
|
2334
2422
|
overlaySeg: "Overlay",
|
|
2335
|
-
channelHint: "System notifications land in the Windows notification center: permission needed, primary display only, and no Approve / Reject buttons
|
|
2423
|
+
channelHint: "System notifications land in the Windows notification center: permission needed, primary display only, and the Desktop shell's notifications carry no Approve / Reject buttons (an approval also drops a clickable card in the window corner). The in-page overlay draws on whichever display the DSH window sits on, needs no permission, and lets the Desktop shell decide right on the card. Pick one.",
|
|
2336
2424
|
overlayCorner: "Overlay position",
|
|
2337
2425
|
overlayCornerHint: "Which corner of the DSH window the cards stack in; they follow the window to whatever display it is on.",
|
|
2338
2426
|
overlayPosTR: "Top right",
|
|
@@ -2340,7 +2428,7 @@
|
|
|
2340
2428
|
overlayPosTL: "Top left",
|
|
2341
2429
|
overlayPosBL: "Bottom left",
|
|
2342
2430
|
overlayQuickNote: "\"Approve\" here equals \"Allow once\" on the approval card — the same decision the toast buttons send.",
|
|
2343
|
-
testApprovalNoBridge: "
|
|
2431
|
+
testApprovalNoBridge: "Test alert sent: system notifications carry no buttons, so the quick-decision buttons are drawn on a card in the window corner.",
|
|
2344
2432
|
testNeedsQuick: "Turn on \"Decide approvals from the toast\" above to test the buttons.",
|
|
2345
2433
|
volume: "Volume",
|
|
2346
2434
|
language: "Notification language",
|
|
@@ -3314,6 +3402,13 @@
|
|
|
3314
3402
|
var onVisibility = function () { try { flushQuieted(); } catch (e) {} };
|
|
3315
3403
|
try { document.addEventListener("visibilitychange", onVisibility); } catch (e) {}
|
|
3316
3404
|
unsubs.push(function () { try { document.removeEventListener("visibilitychange", onVisibility); } catch (e) {} });
|
|
3405
|
+
// Same re-arm for the other half of "went away": losing window focus.
|
|
3406
|
+
try { window.addEventListener("focus", onPageFocus); } catch (e) {}
|
|
3407
|
+
try { window.addEventListener("blur", onPageBlur); } catch (e) {}
|
|
3408
|
+
unsubs.push(function () {
|
|
3409
|
+
try { window.removeEventListener("focus", onPageFocus); } catch (e) {}
|
|
3410
|
+
try { window.removeEventListener("blur", onPageBlur); } catch (e) {}
|
|
3411
|
+
});
|
|
3317
3412
|
// Quick-decision bridge: register the worker half of this bundle (a
|
|
3318
3413
|
// no-op where quickActions is off or a worker cannot run) and start
|
|
3319
3414
|
// listening for toast-button relays.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-notify-me",
|
|
3
|
-
"version": "1.10.
|
|
4
|
-
"description": "离开 DSH 页面也能收到提醒:需要你操作 /
|
|
3
|
+
"version": "1.10.5",
|
|
4
|
+
"description": "离开 DSH 页面也能收到提醒:需要你操作 / 后台回复完成时发系统通知、标题标记与可选页面浮层(副屏也能看见),支持审批快捷裁决与外部推送,Web 版与桌面版均可用。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "lib/index.js",
|