dsh-notify-me 1.10.0 → 1.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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.2] — 2026-10-21
6
+
7
+ ### 变更
8
+
9
+ - 设置页「提醒方式」重做成一行分段选择器:左边「系统通知」、右边「浮层提醒」,选中的那半填色。原来的两个单选圆点会出现「一个在左一个在右、像在打勾」的错位感,也读不出互斥关系。
10
+ - 「浮层位置」只在选了浮层提醒时出现,作为可点开的子项(默认显示当前值,点标题展开);「音量」同样改为可点开。
11
+ - 设置行说明的悬浮提示改为只在标题文字上触发(之前的触发区是整行,空白处划过也会弹)。
12
+
13
+ ### 测试
14
+
15
+ - 冒烟测试改断言「两个 role=radio 的分段按钮、选中半区带 on」;`npm test` 两套件通过。
16
+ ## [1.10.1] — 2026-10-21
17
+
18
+ ### 变更
19
+
20
+ - **悬浮提示的触发区收窄到标题文字本身**:行标题是 flex 列里的子项,会被 blockify 撑满整列,之前鼠标停在标题右侧的空白处也会弹说明。改用 width:fit-content,实测标题宽 52px、文本列 437px,只有压在字上才弹。
21
+ - **「浮层位置」「音量」改成点标题展开**:这两项以前跟着上开关立刻出现,页面会突然长出一段缩进区。现在标题后面带 ▸ / ▾,点一下才展开,标题上同时显示当前值(如「右下」「50%」)。
22
+ - **提醒停留时间缩到 5 秒档**:普通提醒 5 秒自动收起(浮层「需要你」「回复完成」同档),带「同意 / 拒绝」的审批卡片 12 秒;原来最长的那张一直占屏。
23
+
24
+ ### 测试
25
+
26
+ - 冒烟:角落下拉折叠后才出现、展开后默认值正确、改值落盘;带按钮卡片 12 秒自收的定时器断言同步更新。
27
+ - npm test 两套件通过(ALL SMOKE TESTS PASSED / cordis-host-test passed)。
28
+
5
29
  ## [1.10.0] — 2026-10-21
6
30
 
7
31
  ### 新增
package/README.en.md CHANGED
@@ -13,7 +13,7 @@
13
13
  [![npm total downloads](https://img.shields.io/npm/dt/dsh-notify-me?style=flat-square&label=total%20downloads&color=1F883D)](https://www.npmjs.com/package/dsh-notify-me)
14
14
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue?style=flat-square)](LICENSE)
15
15
  [![DSH Web and Desktop](https://img.shields.io/badge/DSH-Web%20%7C%20Desktop-blue?style=flat-square)](#installation)
16
- [![Client bundle size](https://img.shields.io/badge/client-%7E168%20KiB-green?style=flat-square)](lib/client.js)
16
+ [![Client bundle size](https://img.shields.io/badge/client-%7E173%20KiB-green?style=flat-square)](lib/client.js)
17
17
  [![DSH Compatibility](https://img.shields.io/badge/DSH-tested%200.2.1--rc.2-6366f1.svg?style=flat-square)](#compatibility)
18
18
  [![Awesome DSH Plugin](https://awesome-dsh-plugin.com/badge.svg)](https://awesome-dsh-plugin.com)
19
19
 
@@ -40,14 +40,14 @@ A notification plugin for DSH Web / Desktop, so you can keep full access off wit
40
40
 
41
41
  ![Three overlay cards in the bottom-right corner of the DSH page: question, approval request with Approve / Reject, and reply finished](https://raw.githubusercontent.com/chromoany/dsh-notify-me/main/docs/screenshots/overlay-en.png)
42
42
 
43
- *In-page overlay (1.8.0) · the real plugin rendered on a demo page · for when system notifications cannot reach the secondary display; cards appear on whichever display the DSH window is on.* [View full size](https://raw.githubusercontent.com/chromoany/dsh-notify-me/main/docs/screenshots/overlay-en.png)
43
+ *In-page overlay · the real plugin rendered on a demo page · for when system notifications cannot reach the secondary display; cards appear on whichever display the DSH window is on.* [View full size](https://raw.githubusercontent.com/chromoany/dsh-notify-me/main/docs/screenshots/overlay-en.png)
44
44
 
45
45
  <details>
46
46
  <summary>See the settings interface</summary>
47
47
 
48
48
  ![The Notify me settings page: header, how you get told, when to alert, try it, and the collapsed Advanced block](https://raw.githubusercontent.com/chromoany/dsh-notify-me/main/docs/screenshots/settings-en.png)
49
49
 
50
- The real settings component (v1.10.0 defaults) rendered in a standalone demo shell. The alert channel is a single choice, and row explanations live in a hover tooltip that appears when the pointer rests on a title, which is why only the titles show here.
50
+ The real settings component (v1.10.2 defaults) rendered in a standalone demo shell. Alert type is a segmented control, and row explanations live in a hover tooltip that appears when the pointer rests on a title, which is why only the titles show here.
51
51
  </details>
52
52
 
53
53
  ## Installation
@@ -116,15 +116,15 @@ dsh-notify-me decouples **supervision** from **sitting in front of the screen**:
116
116
 
117
117
  ## Configure it from DSH Settings
118
118
 
119
- Open **Settings → Notify me**. The page runs as how you get told, when to alert, and a try-it block; the knobs you rarely touch fold into **Advanced** at the bottom. Rows show titles only — the explanation pops up next to the pointer once it rests on the title text (hovering a switch does nothing), with a hidden copy kept for screen readers.
119
+ Open **Settings → Notify me**. The page runs as how you get told, when to alert, and a try-it block; the knobs you rarely touch fold into **Advanced** at the bottom. Rows show titles only — the explanation pops up next to the pointer once it rests on the title text (hovering a switch does nothing), with a hidden copy kept for screen readers. Rows marked with ▸ open a sub-item when clicked.
120
120
 
121
121
  ### How you get told
122
122
 
123
123
  - **Enable reminders**: master switch — off means no toasts, no sounds, and the tab title is left alone;
124
- - **How you get told (pick one)**: system notifications or the in-page overlay — a single choice, so turning one on turns the other off;
124
+ - **Alert type (pick one)**: one segmented control — system notifications on the left, in-page overlay on the right; whichever you pick is the only channel used;
125
125
  - **System notifications** (default): native toasts landing in the Windows / macOS notification center; needs notification permission;
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. Pick one of four corners when it is the chosen channel. See [Secondary displays and the overlay](#secondary-displays-and-the-overlay);
127
- - **Sound / Volume**: master cue switch and a volume slider; what it plays comes from Advanced → Alert sound;
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. Click the row open to pick one of four corners when it is the chosen channel. See [Secondary displays and the overlay](#secondary-displays-and-the-overlay);
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
128
  - **Decide approvals from the toast** (on by default): approval toasts carry Approve / Reject that settle the request in place. **Desktop notifications have no buttons** (`dsh-app://` cannot register a Service Worker) — pick the in-page overlay as the channel instead; the page states plainly which channel can carry buttons right now.
129
129
 
130
130
  ### Secondary displays and the overlay
@@ -133,11 +133,11 @@ Windows draws system notification banners on the primary display only. There is
133
133
 
134
134
  The **in-page overlay** skips the notification center and draws the card inside the DSH page instead: wherever the window is, the card is there too.
135
135
 
136
- - **Position** — top right / bottom right / top left / bottom left, default bottom right; changes apply immediately;
136
+ - **Position** — click the In-page overlay row open (▸ turns into ▾), then pick top right / bottom right / top left / bottom left; the row shows the current value. Default bottom right, changes apply immediately;
137
137
  - **No notification permission needed** — it works when permission was never granted, and on the Desktop shell where buttonless toasts are all the OS channel can do;
138
138
  - **Approval cards carry Approve / Reject** — the same decision as Allow once / Reject on the approval card, and they work on the Desktop too (no Service Worker involved). Clicking the card body opens its conversation;
139
139
  - **Lifetime** — "needs you" cards retire after 12s, "reply finished" after 6s, and an approval card with buttons after 18s; a card or its close button retires one earlier. At most 4 cards stack at once;
140
- - **Relation to system notifications** — it is one or the other. Both used to be switchable at once, which meant every alert arrived twice and the pair covered each other; 1.10.0 makes it a single choice, and an old config with the overlay on migrates to it.
140
+ - **Relation to system notifications** — it is one or the other. Both used to be switchable at once, which meant every alert arrived twice and the pair covered each other; 1.10.0 made it a single choice, and 1.10.2 turns that into one segmented row. An old config with the overlay on migrates to it.
141
141
 
142
142
  The overlay only knows which screen the window is on, not which screen you are looking at. If the window stays on the primary display while you watch another one, it stays invisible — same as a toast.
143
143
 
package/README.md CHANGED
@@ -13,7 +13,7 @@
13
13
  [![npm total downloads](https://img.shields.io/npm/dt/dsh-notify-me?style=flat-square&label=total%20downloads&color=1F883D)](https://www.npmjs.com/package/dsh-notify-me)
14
14
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue?style=flat-square)](LICENSE)
15
15
  [![DSH Web and Desktop](https://img.shields.io/badge/DSH-Web%20%7C%20Desktop-blue?style=flat-square)](#安装)
16
- [![Client bundle size](https://img.shields.io/badge/client-%7E168%20KiB-green?style=flat-square)](lib/client.js)
16
+ [![Client bundle size](https://img.shields.io/badge/client-%7E173%20KiB-green?style=flat-square)](lib/client.js)
17
17
  [![DSH Compatibility](https://img.shields.io/badge/DSH-%E5%B7%B2%E9%80%82%E9%85%8D%200.2.1--rc.2-6366f1.svg?style=flat-square)](#兼容性)
18
18
  [![Awesome DSH Plugin](https://awesome-dsh-plugin.com/badge.svg)](https://awesome-dsh-plugin.com)
19
19
 
@@ -40,14 +40,14 @@ DSH Web / Desktop 的通知插件,让你敢不开完整访问,也不必一
40
40
 
41
41
  ![DSH 页面右下角的三张浮层提醒:提问、审批请求(带「同意 / 拒绝」按钮)、回复完成](https://raw.githubusercontent.com/chromoany/dsh-notify-me/main/docs/screenshots/overlay-zh.png)
42
42
 
43
- *页面浮层提醒(1.8.0)· 真实插件在独立演示页里渲染 · 系统通知画不到副屏时用它,卡片落在 DSH 窗口所在的那块屏。* [查看原图](https://raw.githubusercontent.com/chromoany/dsh-notify-me/main/docs/screenshots/overlay-zh.png)
43
+ *页面浮层提醒 · 真实插件在独立演示页里渲染 · 系统通知画不到副屏时用它,卡片落在 DSH 窗口所在的那块屏。* [查看原图](https://raw.githubusercontent.com/chromoany/dsh-notify-me/main/docs/screenshots/overlay-zh.png)
44
44
 
45
45
  <details>
46
46
  <summary>展开设置界面预览</summary>
47
47
 
48
48
  ![通知提醒设置页:页眉、提醒方式、什么时候提醒、试一试与折叠的高级设置](https://raw.githubusercontent.com/chromoany/dsh-notify-me/main/docs/screenshots/settings-zh.png)
49
49
 
50
- 真实插件设置组件(v1.10.0 默认配置)在独立演示环境里渲染;提醒方式是一组单选,每行的说明在鼠标停在标题文字上时才出现,所以这里只看到标题。
50
+ 真实插件设置组件(v1.10.2 默认配置)在独立演示环境里渲染;「通知类型」是分段选择器,每行的说明在鼠标停在标题文字上时才出现,所以这里只看到标题。
51
51
  </details>
52
52
 
53
53
  ## 安装
@@ -112,15 +112,15 @@ dsh-notify-me 把「监督」和「守在屏幕前」解耦:只在真的出现
112
112
 
113
113
  ## 在 DSH 设置里改配置
114
114
 
115
- 打开 **设置 → 通知提醒**。页面按「提醒方式 → 什么时候提醒 → 试一试」三段排开,不常改的收进底部「高级设置」,点一下展开。每行只写标题,具体说明在鼠标移上去时弹出的小方框里(触屏或键盘操作时留一份隐藏文本给读屏)。
115
+ 打开 **设置 → 通知提醒**。页面按「提醒方式 → 什么时候提醒 → 试一试」三段排开,不常改的收进底部「高级设置」,点一下展开。每行只写标题,具体说明在鼠标停在标题文字上时弹出的小方框里(只有标题本身触发布局,整行空白处不会弹;触屏或键盘操作时留一份隐藏文本给读屏)。带 ▸ 的行点一下展开子项。
116
116
 
117
117
  ### 提醒方式
118
118
 
119
119
  - **启用提醒** 总开关:关闭后不弹通知、不响提示音,也不改标签页标题;
120
- - **提醒方式(二选一)**:系统通知 / 页面浮层,单选,开了这个就不再弹那个;
120
+ - **通知类型(二选一)**:一行分段选择器,左边「系统通知」、右边「浮层提醒」,点哪个就只走哪个;
121
121
  - **系统通知**(默认):进 Windows / macOS 通知中心,需要通知权限;
122
- - **页面浮层**:在 DSH 页面角落画提醒卡片,窗口在哪块屏它就在哪块屏——系统通知只会画在主屏,主屏不看时用它;不需要通知权限,桌面端也能在卡片上直接「同意 / 拒绝」。选中后可选四个角落的位置,见[副屏与浮层提醒](#副屏与浮层提醒);
123
- - **提示音 / 音量**:提示音开关与音量滑块;具体播什么由「高级设置 → 音效来源」决定;
122
+ - **页面浮层**:在 DSH 页面角落画提醒卡片,窗口在哪块屏它就在哪块屏——系统通知只会画在主屏,主屏不看时用它;不需要通知权限,桌面端也能在卡片上直接「同意 / 拒绝」。选中后可在该行点开选四个角落的位置,见[副屏与浮层提醒](#副屏与浮层提醒);
123
+ - **提示音 / 音量**:开关直接切;音量滑块收在该行里,点一下标题(▸ 变成 ▾)展开。具体播什么由「高级设置 → 音效来源」决定;
124
124
  - **审批通知上直接裁决**(默认开):审批通知带「同意 / 拒绝」,点一下即完成裁决,不用切回页面。**桌面端的通知没有按钮**(`dsh-app://` 页面无法注册 Service Worker),需要按钮就打开「页面浮层提醒」,改在浮层上裁决;设置页会如实说明当前哪种渠道能给按钮。
125
125
 
126
126
  ### 副屏与浮层提醒
@@ -129,11 +129,11 @@ Windows 的系统通知横幅固定画在主屏上,系统没有设置项、浏
129
129
 
130
130
  「页面浮层提醒」绕开系统通知中心,直接在 DSH 页面里画一张卡片:窗口在哪块屏,卡片就出现在哪块屏。
131
131
 
132
- - **位置**:设置里选右上 / 右下 / 左上 / 左下,默认右下;改完立刻生效;
132
+ - **位置**:点开「页面浮层提醒」那一行(▸ 变 ▾)再选右上 / 右下 / 左上 / 左下,默认右下,行上直接显示当前值;改完立刻生效;
133
133
  - **不需要通知权限**:通知权限没给、或桌面端弹不出带按钮的通知时,浮层照常工作;
134
134
  - **审批卡片带「同意 / 拒绝」**:与审批卡片的「允许一次 / 拒绝」等价,桌面端也能用(不依赖 Service Worker);点卡片正文则切回对应对话;
135
- - **停留时间**:「需要你」12 秒,「回复完成」6 秒,带按钮的审批卡片 18 秒,都会自动收起;也可以点卡片或右上角叉提前关掉。最多同时叠 4 张;
136
- - **与系统通知的关系**:二选一。以前两个开关可以同开,结果同一条提醒弹两遍、还互相挡住,1.10.0 起改成单选。老配置里开着浮层的,升级后自动落到浮层。
135
+ - **停留时间**:普通提醒 5 秒自动收起,带「同意 / 拒绝」的审批卡片 12 秒,系统通知同档(普通 5 秒、带按钮 12 秒);也可以点卡片或右上角叉提前关掉。最多同时叠 4 张;
136
+ - **与系统通知的关系**:二选一。以前两个开关可以同开,结果同一条提醒弹两遍、还互相挡住;1.10.0 起改成单选,1.10.2 起做成一行分段选择器。老配置里开着浮层的,升级后自动落到浮层。
137
137
 
138
138
  浮层只知道窗口在哪块屏,不知道你人在看哪块屏。窗口留在主屏、人却盯着副屏时,它同样看不见。
139
139
 
package/lib/client.js CHANGED
@@ -782,7 +782,7 @@
782
782
  }
783
783
  // Longer for an actionable toast so there is time to click it, shorter
784
784
  // for a plain one: a notification that just sits there is noise.
785
- setTimeout(function () { n.close(); }, quick ? 20000 : 10000);
785
+ setTimeout(function () { n.close(); }, quick ? 12000 : 5000);
786
786
  } catch (e) {}
787
787
  }
788
788
 
@@ -801,7 +801,7 @@
801
801
  // How long a card stays. "needs you" deserves a longer look than a finished
802
802
  // reply; actionable cards get the longest window but still fade, because a
803
803
  // card that never leaves is just clutter once the user has moved on.
804
- var OVERLAY_TTL = { attention: 12000, done: 6000, action: 18000 };
804
+ var OVERLAY_TTL = { attention: 5000, done: 5000, action: 12000 };
805
805
  var OVERLAY_CORNERS = ["bottom-right", "bottom-left", "top-right", "top-left"];
806
806
  var OVERLAY_CSS = "" +
807
807
  ".dnm-ov-layer{position:fixed;z-index:2147483000;display:flex;flex-direction:column;gap:10px;pointer-events:none;max-width:calc(100vw - 24px)}" +
@@ -1964,7 +1964,7 @@
1964
1964
  });
1965
1965
  } catch (e) {}
1966
1966
  // keep in sync with package.json "version" on every release
1967
- api.version = "1.10.0";
1967
+ api.version = "1.10.2";
1968
1968
  api.setConfig = function (patch) { return setConfigLocal(patch); };
1969
1969
  api.resetConfig = function () {
1970
1970
  try {
@@ -2183,7 +2183,10 @@
2183
2183
  bridgeOffTail: "):审批通知会退回不带按钮的普通样式。",
2184
2184
  bridgeOffDesktop: "系统通知没有「同意 / 拒绝」按钮:桌面端页面(dsh-app://)注册不了 Service Worker(宿主少给 allowServiceWorkers 特权),按钮发不出来。想一键裁决就把「提醒方式」切到「页面浮层」。",
2185
2185
  overlay: "页面浮层提醒",
2186
- overlayHint: "在窗口角落画提醒卡片:窗口在哪块屏,它就在哪块屏;系统通知只会出现在主屏,主屏不看时用它。不需要通知权限,卡片上能直接「同意 / 拒绝」。与上面二选一,选它就不再弹系统通知。",
2186
+ overlayHint: "在窗口角落画提醒卡片:窗口在哪块屏,它就在哪块屏;系统通知只会出现在主屏,主屏不看时用它。不需要通知权限,卡片上能直接「同意 / 拒绝」。与系统通知二选一。",
2187
+ channelLabel: "通知类型",
2188
+ overlaySeg: "浮层提醒",
2189
+ channelHint: "系统通知只会出现在主屏,进通知中心要权限,桌面端也没有「同意 / 拒绝」按钮;页面浮层画在 DSH 窗口所在的那块屏,不需要权限,卡片上能直接裁决。两个只能选一个。",
2187
2190
  overlayCorner: "浮层位置",
2188
2191
  overlayCornerHint: "贴在 DSH 窗口的哪个角;窗口在哪块屏,浮层就出现在哪块屏。",
2189
2192
  overlayPosTR: "右上",
@@ -2310,7 +2313,10 @@
2310
2313
  bridgeOffTail: "); approval toasts fall back to their plain form.",
2311
2314
  bridgeOffDesktop: "System notifications carry no Approve / Reject buttons: dsh-app:// pages cannot register a Service Worker (the shell does not grant the allowServiceWorkers privilege), so the buttons cannot be attached. Switch the alert channel to the in-page overlay to settle approvals in one click.",
2312
2315
  overlay: "In-page overlay",
2313
- overlayHint: "Draw a card in the window corner, on whatever display the window sits on. System notifications only land on the primary display. No permission needed, and the card carries Approve / Reject directly. Pick this or the system notification above: choosing it stops system notifications.",
2316
+ 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.",
2317
+ channelLabel: "Alert type",
2318
+ overlaySeg: "Overlay",
2319
+ channelHint: "System notifications land in the Windows notification center: permission needed, primary display only, and no Approve / Reject buttons on the Desktop shell. 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.",
2314
2320
  overlayCorner: "Overlay position",
2315
2321
  overlayCornerHint: "Which corner of the DSH window the cards stack in; they follow the window to whatever display it is on.",
2316
2322
  overlayPosTR: "Top right",
@@ -2448,8 +2454,11 @@
2448
2454
  ".dnm-knob{position:absolute;top:2px;left:2px;width:14px;height:14px;border-radius:50%;background:#fff;transition:left .15s ease}" +
2449
2455
  ".dnm-switch.on .dnm-knob{left:16px}" +
2450
2456
  ".dnm-radio{flex:none;width:16px;height:16px;margin:0;accent-color:var(--dsw-alias-state-business-primary,#3b82f6);cursor:pointer}" +
2451
- ".dnm-rowRadio{justify-content:flex-start;gap:10px}" +
2452
- ".dnm-rowRadio .dnm-rowText{flex:1}" +
2457
+ ".dnm-seg{display:inline-flex;flex:none;border:1px solid var(--dsw-alias-border-l2,rgba(128,128,128,.35));border-radius:8px;overflow:hidden}" +
2458
+ ".dnm-segBtn{height:28px;padding:0 12px;border:0;background:transparent;color:var(--dsw-alias-label-secondary,rgba(90,90,90,.95));font:inherit;font-size:12px;line-height:28px;cursor:pointer;white-space:nowrap}" +
2459
+ ".dnm-segBtn+.dnm-segBtn{border-left:1px solid var(--dsw-alias-border-l2,rgba(128,128,128,.35))}" +
2460
+ ".dnm-segBtn:hover{color:var(--dsw-alias-label-primary,rgba(20,20,20,.9))}" +
2461
+ ".dnm-segBtn.on{background:var(--dsw-alias-state-business-primary,#3b82f6);color:#fff}" +
2453
2462
  ".dnm-range{width:150px;accent-color:var(--dsw-alias-state-business-primary,#3b82f6)}" +
2454
2463
  ".dnm-select{border:1px solid var(--dsw-alias-border-l2,rgba(128,128,128,.4));background:var(--dsw-alias-bg-layer-1,rgba(255,255,255,.03));color:var(--dsw-alias-label-primary,rgba(20,20,20,.9));border-radius:8px;padding:5px 8px;font-size:13px;font-family:inherit}" +
2455
2464
  ".dnm-btn{border:1px solid var(--dsw-alias-border-l2,rgba(128,128,128,.4));background:transparent;color:var(--dsw-alias-label-primary,rgba(20,20,20,.9));border-radius:8px;padding:5px 12px;font-size:13px;cursor:pointer;font-family:inherit}" +
@@ -2481,7 +2490,12 @@
2481
2490
  ".dnm-advToggle:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#3b82f6);outline-offset:2px}" +
2482
2491
  ".dnm-advRight{display:flex;align-items:center;gap:8px;flex:none;font-weight:400;color:var(--dsw-alias-label-tertiary,rgba(120,120,120,.9))}" +
2483
2492
  ".dnm-advChev{font-size:10px;translate:0 1px;transition:transform .15s ease}" +
2484
- ".dnm-rowLabel.dnm-rowTip{cursor:help}" +
2493
+ // A flex item is blockified, so display:inline-block would still stretch to the
2494
+ // column width; fit-content is what actually hugs the glyphs.
2495
+ ".dnm-rowLabel.dnm-rowTip{width:fit-content;max-width:100%;cursor:help}" +
2496
+ ".dnm-rowLabelBtn{cursor:pointer}" +
2497
+ ".dnm-rowLabelBtn.dnm-rowTip{display:inline-flex;align-items:center;gap:6px;width:fit-content;cursor:pointer}" +
2498
+ ".dnm-chev{font-size:10px;line-height:1;color:var(--dsw-alias-label-tertiary,rgba(120,120,120,.9))}" +
2485
2499
  ".dnm-rowValue{font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary,rgba(120,120,120,.9))}" +
2486
2500
  // The long explanation lives in a tooltip next to the pointer, so a
2487
2501
  // row is one line tall. Screen readers still get the text.
@@ -2907,24 +2921,27 @@
2907
2921
  function secTitle(labelText) {
2908
2922
  return h("div", { className: "dnm-secTitle" }, labelText);
2909
2923
  }
2910
- // One channel at a time, so the pair renders as radios, not switches. The
2911
- // radio sits ahead of the label: two rows sharing one choice read as a
2912
- // list of options, which a lone dot on the far right does not.
2913
- function rowRadio(labelText, hintText, checked, onChange) {
2914
- var pick = function () { if (!checked) onChange(); };
2915
- return tipRow("dnm-row dnm-rowRadio", hintText, [
2916
- h("input", {
2917
- type: "radio",
2918
- className: "dnm-radio",
2919
- name: "dnm-alert-channel",
2920
- checked: !!checked,
2921
- "aria-label": labelText,
2922
- onChange: pick,
2923
- onClick: pick
2924
- }),
2924
+ // One channel at a time. A pair of radios read as two unrelated dots, so
2925
+ // the choice is a segmented control: the filled half is the active option.
2926
+ function segButton(labelText, checked, onPick) {
2927
+ return h("button", {
2928
+ type: "button",
2929
+ role: "radio",
2930
+ "aria-checked": checked ? "true" : "false",
2931
+ "aria-label": labelText,
2932
+ className: "dnm-segBtn" + (checked ? " on" : ""),
2933
+ onClick: function () { if (!checked) onPick(); }
2934
+ }, labelText);
2935
+ }
2936
+ function rowChannel(labelText, hintText, current, onPick) {
2937
+ return tipRow("dnm-row", hintText, [
2925
2938
  h("div", { className: "dnm-rowText" }, [
2926
2939
  h("div", { className: "dnm-rowLabel" }, labelText),
2927
2940
  srOnly(hintText)
2941
+ ]),
2942
+ h("div", { className: "dnm-seg", role: "radiogroup", "aria-label": labelText }, [
2943
+ segButton(t("toast"), current !== "overlay", function () { onPick("toast"); }),
2944
+ segButton(t("overlaySeg"), current === "overlay", function () { onPick("overlay"); })
2928
2945
  ])
2929
2946
  ]);
2930
2947
  }
@@ -2936,6 +2953,41 @@
2936
2953
  var advState = React.useState(false);
2937
2954
  var advancedOpen = advState[0];
2938
2955
  var setAdvancedOpen = advState[1];
2956
+ // Dependent controls (overlay corner, volume) stay folded away until the
2957
+ // row they belong to is clicked open.
2958
+ var subStateHook = React.useState({});
2959
+ var subState = subStateHook[0];
2960
+ var setSubState = subStateHook[1];
2961
+ function discloseRow(opts) {
2962
+ var open = !!(subState && subState[opts.key]);
2963
+ var toggle = function () {
2964
+ setSubState(function (prev) {
2965
+ var next = {};
2966
+ for (var k in prev) next[k] = prev[k];
2967
+ next[opts.key] = !prev[opts.key];
2968
+ return next;
2969
+ });
2970
+ };
2971
+ var label = h("div", {
2972
+ className: "dnm-rowLabel dnm-rowLabelBtn",
2973
+ role: "button",
2974
+ tabIndex: 0,
2975
+ "aria-expanded": open ? "true" : "false",
2976
+ "data-dnm-expand": opts.key,
2977
+ onClick: toggle,
2978
+ onKeyDown: function (e) {
2979
+ if (e.key === "Enter" || e.key === " ") { e.preventDefault(); toggle(); }
2980
+ }
2981
+ }, [
2982
+ opts.labelText,
2983
+ opts.valueText ? h("span", { className: "dnm-rowValue" }, opts.valueText) : null,
2984
+ h("span", { className: "dnm-chev" }, open ? "▾" : "▸")
2985
+ ]);
2986
+ var kids = [h("div", { className: "dnm-rowText" }, [label, srOnly(opts.hintText)])];
2987
+ if (opts.control) kids.push(opts.control);
2988
+ var row = tipRow(opts.rowClass || "dnm-row", opts.hintText, kids);
2989
+ return open ? [row, opts.sub] : [row];
2990
+ }
2939
2991
  var testRow = tipRow("dnm-row dnm-rowStack", t("testHint"), [
2940
2992
  h("div", { className: "dnm-rowText" }, [
2941
2993
  h("div", { className: "dnm-rowLabel" }, t("testTitle")),
@@ -2963,40 +3015,59 @@
2963
3015
  patch({ enabled: v });
2964
3016
  if (!v && attentionActive > 0) { attentionActive = 0; applyTitle(); }
2965
3017
  }),
2966
- rowRadio(t("toast"), toastHintText(), cfg.channel !== "overlay", function () { patch({ channel: "toast" }); }),
2967
- rowRadio(t("overlay"), t("overlayHint"), cfg.channel === "overlay", function () { patch({ channel: "overlay" }); }),
2968
- cfg.channel === "overlay" ? tipRow("dnm-row dnm-rowSub", t("overlayCornerHint"), [
2969
- h("div", { className: "dnm-rowText" }, [
2970
- h("div", { className: "dnm-rowLabel" }, t("overlayCorner")),
2971
- srOnly(t("overlayCornerHint"))
2972
- ]),
2973
- h("select", {
2974
- className: "dnm-select",
2975
- value: overlayCorner(),
2976
- "aria-label": t("overlayCorner"),
2977
- onChange: function (e) { patch({ overlayCorner: e.currentTarget.value }); }
2978
- }, [
2979
- h("option", { value: "top-right" }, t("overlayPosTR")),
2980
- h("option", { value: "bottom-right" }, t("overlayPosBR")),
2981
- h("option", { value: "top-left" }, t("overlayPosTL")),
2982
- h("option", { value: "bottom-left" }, t("overlayPosBL"))
3018
+ rowChannel(t("channelLabel"), t("channelHint"), cfg.channel, function (next) { patch({ channel: next }); }),
3019
+ cfg.channel === "overlay" ? discloseRow({
3020
+ key: "overlayCorner",
3021
+ rowClass: "dnm-row dnm-rowSub",
3022
+ labelText: t("overlayCorner"),
3023
+ hintText: t("overlayCornerHint"),
3024
+ valueText: t("overlayPos" + ({ "top-right": "TR", "bottom-right": "BR", "top-left": "TL", "bottom-left": "BL" })[overlayCorner()] || "BR"),
3025
+ sub: tipRow("dnm-row dnm-rowSub", t("overlayCornerHint"), [
3026
+ h("div", { className: "dnm-rowText" }, [
3027
+ h("div", { className: "dnm-rowLabel" }, t("overlayCorner")),
3028
+ srOnly(t("overlayCornerHint"))
3029
+ ]),
3030
+ h("select", {
3031
+ className: "dnm-select",
3032
+ value: overlayCorner(),
3033
+ "aria-label": t("overlayCorner"),
3034
+ onChange: function (e) { patch({ overlayCorner: e.currentTarget.value }); }
3035
+ }, [
3036
+ h("option", { value: "top-right" }, t("overlayPosTR")),
3037
+ h("option", { value: "bottom-right" }, t("overlayPosBR")),
3038
+ h("option", { value: "top-left" }, t("overlayPosTL")),
3039
+ h("option", { value: "bottom-left" }, t("overlayPosBL"))
3040
+ ])
2983
3041
  ])
2984
- ]) : null,
2985
- rowSwitch("sound", t("sound"), t("soundHint"), cfg.sound, function (v) { patch({ sound: v }); }),
2986
- cfg.sound ? h("div", { className: "dnm-row dnm-rowSub" }, [
2987
- h("div", { className: "dnm-rowText" }, [
2988
- h("div", { className: "dnm-rowLabel" }, t("volume")),
2989
- h("div", { className: "dnm-rowHint" }, String(Math.round(cfg.volume * 100)) + "%")
2990
- ]),
2991
- h("input", {
2992
- type: "range",
2993
- className: "dnm-range",
2994
- min: 0, max: 100, step: 5,
2995
- value: Math.round(cfg.volume * 100),
2996
- "aria-label": t("volume"),
2997
- onChange: function (e) { patch({ volume: Number(e.currentTarget.value) / 100 }); }
2998
- })
2999
- ]) : null,
3042
+ }) : null,
3043
+ cfg.sound ? discloseRow({
3044
+ key: "volume",
3045
+ labelText: t("sound"),
3046
+ hintText: t("soundHint"),
3047
+ valueText: String(Math.round(cfg.volume * 100)) + "%",
3048
+ control: h("button", {
3049
+ type: "button",
3050
+ role: "switch",
3051
+ "aria-checked": "true",
3052
+ "aria-label": t("sound"),
3053
+ className: "dnm-switch on",
3054
+ onClick: function () { patch({ sound: false }); }
3055
+ }, h("span", { className: "dnm-knob" })),
3056
+ sub: h("div", { className: "dnm-row dnm-rowSub" }, [
3057
+ h("div", { className: "dnm-rowText" }, [
3058
+ h("div", { className: "dnm-rowLabel" }, t("volume")),
3059
+ h("div", { className: "dnm-rowHint" }, String(Math.round(cfg.volume * 100)) + "%")
3060
+ ]),
3061
+ h("input", {
3062
+ type: "range",
3063
+ className: "dnm-range",
3064
+ min: 0, max: 100, step: 5,
3065
+ value: Math.round(cfg.volume * 100),
3066
+ "aria-label": t("volume"),
3067
+ onChange: function (e) { patch({ volume: Number(e.currentTarget.value) / 100 }); }
3068
+ })
3069
+ ])
3070
+ }) : rowSwitch("sound", t("sound"), t("soundHint"), false, function (v) { patch({ sound: v }); }),
3000
3071
  rowSwitch("quickActions", t("quickActions"), t("quickActionsHint"), cfg.quickActions, function (v) {
3001
3072
  patch({ quickActions: v });
3002
3073
  if (v) ensureActionBridge();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-notify-me",
3
- "version": "1.10.0",
3
+ "version": "1.10.2",
4
4
  "description": "离开 DSH 页面也能收到提醒:需要你操作 / 后台回复完成时发系统通知、标题标记与可选页面浮层(副屏也能看见),支持审批快捷裁决与外部推送。Brand icon and a reworked settings page in 1.9.0.",
5
5
  "license": "MIT",
6
6
  "type": "module",