dsh-notify-me 1.9.0 → 1.9.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 +26 -0
- package/README.en.md +5 -5
- package/README.md +5 -5
- package/lib/client.js +213 -21
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,32 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to **dsh-notify-me** are documented here.
|
|
4
4
|
|
|
5
|
+
## [1.9.2] — 2026-10-20
|
|
6
|
+
|
|
7
|
+
### 变更
|
|
8
|
+
|
|
9
|
+
- **设置页的说明文字改成悬浮提示**:每行只留标题,鼠标移上去约 0.3 秒后在指针旁边弹出小方框显示原来的说明;靠近视口右侧/底部自动翻到另一边,滚动即收起。页面因此短了很多,一屏能扫完所有开关。
|
|
10
|
+
- 读屏仍能读到完整说明(每行保留视觉隐藏的 `.dnm-srOnly` 文本);音量百分比、权限提示、外部推送说明这类状态信息仍常显。
|
|
11
|
+
- README(中英)与设置页预览截图同步更新;徽章 bundle 尺寸更新为 ~164 KiB。
|
|
12
|
+
|
|
13
|
+
### 测试
|
|
14
|
+
|
|
15
|
+
- `npm test` 两套件通过;另在无头 Chromium 里用真 bundle + 假设置外壳验证悬浮出现/跟随/右缘翻转的坐标(`box=544,827 270x50 vw=900` 未越界)与中英两版截图。
|
|
16
|
+
|
|
17
|
+
## [1.9.1] — 2026-10-20
|
|
18
|
+
|
|
19
|
+
### 新增
|
|
20
|
+
|
|
21
|
+
- **设置左侧导航行换成品牌铃铛**:DSH 的设置外壳按分区 id 硬编码导航图标,`settings.section` 没有图标字段,所以插件改为面板渲染后打补丁——按本地化标签找到自己那一行的导航按钮,把宿主生成的 `<svg>` 换成品牌线稿(形状与 `docs/brand/icon-outline.svg` 一致),沿用宿主给的 icon class,并用 MutationObserver 跟随面板重渲染与重开。
|
|
22
|
+
- 宿主改结构时补丁静默放弃,显示回默认齿轮,其他功能不受影响。
|
|
23
|
+
|
|
24
|
+
### 变更
|
|
25
|
+
|
|
26
|
+
- README(中英)与 `docs/brand/README.md` 的旧口径「导航行图标换不了」改成实际做法与失效条件;`screenshots.json` 补上 1.8.0 / 1.9.0 的截图并修正已删除的 `.jpg` 路径;徽章 bundle 尺寸更新为 ~161 KiB。
|
|
27
|
+
|
|
28
|
+
### 测试
|
|
29
|
+
|
|
30
|
+
- `npm test` 两套件通过;另用真 bundle + 合成宿主面板(`data-shortcut-modal="settings"`、`button.navCell > svg + span.navLabel`)在无头 Chromium 里验证:三行导航里只有「通知提醒」变成铃铛(`data-dnm-nav="1"`、三条 shape),另外两行保持齿轮,图标 class 未丢。
|
|
5
31
|
## [Unreleased]
|
|
6
32
|
|
|
7
33
|
### 兼容性
|
package/README.en.md
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
[](https://www.npmjs.com/package/dsh-notify-me)
|
|
14
14
|
[](LICENSE)
|
|
15
15
|
[](#installation)
|
|
16
|
-
[](lib/client.js)
|
|
17
17
|
[](#compatibility)
|
|
18
18
|
[](https://awesome-dsh-plugin.com)
|
|
19
19
|
|
|
@@ -47,7 +47,7 @@ A notification plugin for DSH Web / Desktop, so you can keep full access off wit
|
|
|
47
47
|
|
|
48
48
|

|
|
49
49
|
|
|
50
|
-
The real settings component (v1.9.
|
|
50
|
+
The real settings component (v1.9.2 defaults) rendered as the content column in a standalone demo shell. Row explanations now live in a hover tooltip, which is why only the titles show here.
|
|
51
51
|
</details>
|
|
52
52
|
|
|
53
53
|
## Installation
|
|
@@ -116,7 +116,7 @@ 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.
|
|
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 on hover, with a hidden copy kept for screen readers.
|
|
120
120
|
|
|
121
121
|
### How you get told
|
|
122
122
|
|
|
@@ -193,7 +193,7 @@ The last block inside Advanced. The page protocol is detected automatically to t
|
|
|
193
193
|
|
|
194
194
|
</details>
|
|
195
195
|
|
|
196
|
-
> The settings sidebar icon is rendered by the DSH settings shell: it hard-codes icons per section id (account / models / agent-presets / plugins / archived-sessions) and
|
|
196
|
+
> The settings sidebar icon is rendered by the DSH settings shell: it hard-codes icons per section id (account / models / agent-presets / plugins / archived-sessions) and `settings.section` has no icon field. The plugin therefore patches that row after the panel renders: it finds its own entry by the localised label text, swaps the host `<svg>` for the brand bell (keeping the host icon class), and follows re-renders with a MutationObserver; if the host changes its markup the patch quietly gives up and the default gear stays.
|
|
197
197
|
|
|
198
198
|
> Notification permission is required: click once on the page → **Allow** (or address-bar lock → Site settings → Notifications → Allow → reload). On macOS, also allow the browser under System Settings → Notifications.
|
|
199
199
|
|
|
@@ -322,7 +322,7 @@ Verified on the real shell (DSH Desktop 0.2.0-rc.2, three rounds of retesting in
|
|
|
322
322
|
- "Stay quiet for the conversation on screen" is judged by `document.hidden`: a window fully covered by other apps still counts as visible, so waits in the current conversation stay quiet there too.
|
|
323
323
|
- External notification forwarding is subject to browser CORS rules: an endpoint that does not allow cross-origin requests falls back to a "sent but unverifiable" delivery (no-cors) and is reported honestly as such; Discord webhooks do allow cross-origin, so delivery to them is confirmed. HTTP errors are recorded as failures and never blindly retried. Forwarding runs only after the local alert has decided to fire, so anything the rules silence is never sent out.
|
|
324
324
|
|
|
325
|
-
- **The settings sidebar
|
|
325
|
+
- **The settings sidebar glyph relies on a DOM patch**: the shell hard-codes icons per section id and `settings.section` exposes no icon field, so the plugin can only swap the `<svg>` in its own row after the panel renders. It depends on the current panel structure (`data-shortcut-modal="settings"` plus a label `<span>` inside each nav button); if DSH changes that markup the patch silently stops working and the default gear shows instead — nothing else is affected.
|
|
326
326
|
|
|
327
327
|
## Development
|
|
328
328
|
|
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
[](https://www.npmjs.com/package/dsh-notify-me)
|
|
14
14
|
[](LICENSE)
|
|
15
15
|
[](#安装)
|
|
16
|
-
[](lib/client.js)
|
|
17
17
|
[](#兼容性)
|
|
18
18
|
[](https://awesome-dsh-plugin.com)
|
|
19
19
|
|
|
@@ -47,7 +47,7 @@ DSH Web / Desktop 的通知插件,让你敢不开完整访问,也不必一
|
|
|
47
47
|
|
|
48
48
|

|
|
49
49
|
|
|
50
|
-
真实插件设置组件(v1.9.
|
|
50
|
+
真实插件设置组件(v1.9.2 默认配置)在独立演示环境里按内容列整页渲染;每行的说明改成鼠标悬浮才出现的小方框,所以这里只看到标题。
|
|
51
51
|
</details>
|
|
52
52
|
|
|
53
53
|
## 安装
|
|
@@ -112,7 +112,7 @@ dsh-notify-me 把「监督」和「守在屏幕前」解耦:只在真的出现
|
|
|
112
112
|
|
|
113
113
|
## 在 DSH 设置里改配置
|
|
114
114
|
|
|
115
|
-
打开 **设置 → 通知提醒**。页面按「提醒方式 → 什么时候提醒 →
|
|
115
|
+
打开 **设置 → 通知提醒**。页面按「提醒方式 → 什么时候提醒 → 试一试」三段排开,不常改的收进底部「高级设置」,点一下展开。每行只写标题,具体说明在鼠标移上去时弹出的小方框里(触屏或键盘操作时留一份隐藏文本给读屏)。
|
|
116
116
|
|
|
117
117
|
### 提醒方式
|
|
118
118
|
|
|
@@ -189,7 +189,7 @@ Windows 的系统通知横幅固定画在主屏上,系统没有设置项、浏
|
|
|
189
189
|
|
|
190
190
|
</details>
|
|
191
191
|
|
|
192
|
-
> 左侧导航行的图标由 DSH 设置外壳统一渲染:外壳按分区 id 硬编码(只认 account / models / agent-presets / plugins / archived-sessions),其他分区一律给默认齿轮,`settings.section`
|
|
192
|
+
> 左侧导航行的图标由 DSH 设置外壳统一渲染:外壳按分区 id 硬编码(只认 account / models / agent-presets / plugins / archived-sessions),其他分区一律给默认齿轮,`settings.section` 也没有图标字段。插件的做法是在设置面板渲染后替换那一行:按本地化标签文本找到自己的入口,把宿主的 `<svg>` 换成品牌铃铛(保留宿主给的 icon class),并用 MutationObserver 跟随重渲染;宿主改结构时会自动放弃、回落到齿轮。
|
|
193
193
|
|
|
194
194
|
> 需要浏览器通知权限:页面上点一下 → 允许;或地址栏锁 → 站点设置 → 通知 → 允许 → 刷新。macOS 还要在 系统设置 → 通知 里允许该浏览器。
|
|
195
195
|
|
|
@@ -318,7 +318,7 @@ DSH Desktop 是一层 Electron 壳,页面跑在 `dsh-app://app` 下:DOM 的
|
|
|
318
318
|
- 「当前对话不弹通知」按 `document.hidden` 判定:窗口被别的应用完全盖住时仍算「页面可见」,此时当前对话的等待照样静默。
|
|
319
319
|
- 外部通知转发受浏览器跨域(CORS)限制:端点不放行时自动降级为「送出但不可确认」(no-cors),如实标为「已送出不可确认」;Discord Webhook 官方放行跨域,可直接确认送达。HTTP 错误如实记失败、不重试。转发在本地提醒决定要发之后才触发,所以被「当前对话不弹通知」等规则静音的提醒也不会外发。
|
|
320
320
|
|
|
321
|
-
-
|
|
321
|
+
- **设置左侧导航行图标靠 DOM 补丁**:外壳按分区 id 硬编码图标且 `settings.section` 没有图标字段,插件只能在面板渲染后把那一行的 `<svg>` 换成铃铛。命中依赖宿主当前的面板结构(`data-shortcut-modal="settings"` + 导航按钮里的标签 `<span>`);如果 DSH 改了这套结构,补丁会静默失效、显示回默认齿轮,其他功能不受影响。
|
|
322
322
|
|
|
323
323
|
## 开发自检
|
|
324
324
|
|
package/lib/client.js
CHANGED
|
@@ -1937,7 +1937,7 @@
|
|
|
1937
1937
|
});
|
|
1938
1938
|
} catch (e) {}
|
|
1939
1939
|
// keep in sync with package.json "version" on every release
|
|
1940
|
-
api.version = "1.9.
|
|
1940
|
+
api.version = "1.9.2";
|
|
1941
1941
|
api.setConfig = function (patch) { return setConfigLocal(patch); };
|
|
1942
1942
|
api.resetConfig = function () {
|
|
1943
1943
|
try {
|
|
@@ -2311,6 +2311,94 @@
|
|
|
2311
2311
|
offHint: "Reminders are off: enable them first, then test.",
|
|
2312
2312
|
versionLine: "Plugin version: dsh-notify-me v"
|
|
2313
2313
|
};
|
|
2314
|
+
// ─────────────────── sidebar row glyph ───────────────────────────
|
|
2315
|
+
// The settings shell picks the nav glyph from a table keyed by section id
|
|
2316
|
+
// (account / models / agent-presets / plugins / archived-sessions) and hands
|
|
2317
|
+
// every other section the settings gear; settings.section carries no icon
|
|
2318
|
+
// field, so there is no supported way to give our row the bell. Patch the
|
|
2319
|
+
// rendered cell instead: find the nav button whose label is ours and swap
|
|
2320
|
+
// its <svg> for the brand bell. The panel is portaled fresh on every open,
|
|
2321
|
+
// so this re-applies itself; if a host updates its markup the label lookup
|
|
2322
|
+
// simply finds nothing and the gear stays.
|
|
2323
|
+
function bellNavSvg() {
|
|
2324
|
+
var NS = "http://www.w3.org/2000/svg";
|
|
2325
|
+
var svg = document.createElementNS(NS, "svg");
|
|
2326
|
+
var attrs = {
|
|
2327
|
+
viewBox: "0 0 24 24", width: "16", height: "16", fill: "none",
|
|
2328
|
+
stroke: "currentColor", "stroke-width": "1.6", "stroke-linecap": "round",
|
|
2329
|
+
"stroke-linejoin": "round", "aria-hidden": "true", "data-dnm-nav": "1"
|
|
2330
|
+
};
|
|
2331
|
+
for (var k in attrs) if (Object.prototype.hasOwnProperty.call(attrs, k)) svg.setAttribute(k, attrs[k]);
|
|
2332
|
+
var shapes = [
|
|
2333
|
+
["path", { d: "M12 3.4a5.1 5.1 0 0 0-5.1 5.1v3.1c0 .9-.3 1.7-.9 2.4l-1.1 1.3c-.5.6-.1 1.5.7 1.5h12.8c.8 0 1.2-.9.7-1.5l-1.1-1.3a3.8 3.8 0 0 1-.9-2.4V8.5A5.1 5.1 0 0 0 12 3.4Z" }],
|
|
2334
|
+
["path", { d: "M9.8 19.2a2.4 2.4 0 0 0 4.4 0" }],
|
|
2335
|
+
["circle", { cx: "18.3", cy: "5.3", r: "2.35", fill: "currentColor", stroke: "none" }]
|
|
2336
|
+
];
|
|
2337
|
+
for (var i = 0; i < shapes.length; i++) {
|
|
2338
|
+
var el = document.createElementNS(NS, shapes[i][0]);
|
|
2339
|
+
for (var a in shapes[i][1]) if (Object.prototype.hasOwnProperty.call(shapes[i][1], a)) el.setAttribute(a, shapes[i][1][a]);
|
|
2340
|
+
svg.appendChild(el);
|
|
2341
|
+
}
|
|
2342
|
+
return svg;
|
|
2343
|
+
}
|
|
2344
|
+
|
|
2345
|
+
function installNavBell(labelOf) {
|
|
2346
|
+
if (typeof document === "undefined" || !document.body || typeof MutationObserver !== "function") {
|
|
2347
|
+
return function () {};
|
|
2348
|
+
}
|
|
2349
|
+
var PANEL = '[data-shortcut-modal="settings"]';
|
|
2350
|
+
var observed = null;
|
|
2351
|
+
var watching = null;
|
|
2352
|
+
|
|
2353
|
+
function patchPanel(panel) {
|
|
2354
|
+
try {
|
|
2355
|
+
var label = String(labelOf() || "").trim();
|
|
2356
|
+
if (!label || !panel) return;
|
|
2357
|
+
var cells = panel.querySelectorAll("button");
|
|
2358
|
+
for (var i = 0; i < cells.length; i++) {
|
|
2359
|
+
var cell = cells[i];
|
|
2360
|
+
var spans = cell.querySelectorAll("span");
|
|
2361
|
+
var hit = false;
|
|
2362
|
+
for (var s = 0; s < spans.length; s++) {
|
|
2363
|
+
if (String(spans[s].textContent || "").trim() === label) { hit = true; break; }
|
|
2364
|
+
}
|
|
2365
|
+
if (!hit) continue;
|
|
2366
|
+
var svg = cell.querySelector("svg");
|
|
2367
|
+
if (!svg || svg.getAttribute("data-dnm-nav") === "1") continue;
|
|
2368
|
+
var bell = bellNavSvg();
|
|
2369
|
+
// Keep the shell's own icon class so the box size and color match.
|
|
2370
|
+
var cls = svg.getAttribute("class");
|
|
2371
|
+
if (cls) bell.setAttribute("class", cls);
|
|
2372
|
+
if (svg.parentNode) svg.parentNode.replaceChild(bell, svg);
|
|
2373
|
+
}
|
|
2374
|
+
} catch (e) {}
|
|
2375
|
+
}
|
|
2376
|
+
|
|
2377
|
+
function watchPanel(panel) {
|
|
2378
|
+
if (!panel || panel === observed) return;
|
|
2379
|
+
observed = panel;
|
|
2380
|
+
patchPanel(panel);
|
|
2381
|
+
if (watching) { try { watching.disconnect(); } catch (e) {} }
|
|
2382
|
+
watching = new MutationObserver(function () { patchPanel(observed); });
|
|
2383
|
+
watching.observe(panel, { childList: true, subtree: true });
|
|
2384
|
+
}
|
|
2385
|
+
|
|
2386
|
+
// The panel is portaled onto <body>, so a body-level childList observer is
|
|
2387
|
+
// enough to notice it opening without watching the whole page.
|
|
2388
|
+
var opener = new MutationObserver(function () {
|
|
2389
|
+
var panel = document.querySelector(PANEL);
|
|
2390
|
+
if (panel) watchPanel(panel);
|
|
2391
|
+
else if (watching) { try { watching.disconnect(); } catch (e) {} watching = null; observed = null; }
|
|
2392
|
+
});
|
|
2393
|
+
opener.observe(document.body, { childList: true });
|
|
2394
|
+
var existing = document.querySelector(PANEL);
|
|
2395
|
+
if (existing) watchPanel(existing);
|
|
2396
|
+
return function () {
|
|
2397
|
+
try { opener.disconnect(); } catch (e) {}
|
|
2398
|
+
if (watching) { try { watching.disconnect(); } catch (e) {} }
|
|
2399
|
+
};
|
|
2400
|
+
}
|
|
2401
|
+
|
|
2314
2402
|
// Small element helper to keep the hand-written page readable.
|
|
2315
2403
|
function h(tag, props) {
|
|
2316
2404
|
var args = [tag, props || null];
|
|
@@ -2362,6 +2450,13 @@
|
|
|
2362
2450
|
".dnm-advToggle:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#3b82f6);outline-offset:2px}" +
|
|
2363
2451
|
".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))}" +
|
|
2364
2452
|
".dnm-advChev{font-size:10px;translate:0 1px;transition:transform .15s ease}" +
|
|
2453
|
+
".dnm-rowTip{cursor:help}" +
|
|
2454
|
+
".dnm-rowValue{font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary,rgba(120,120,120,.9))}" +
|
|
2455
|
+
// The long explanation lives in a tooltip next to the pointer, so a
|
|
2456
|
+
// row is one line tall. Screen readers still get the text.
|
|
2457
|
+
".dnm-srOnly{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}" +
|
|
2458
|
+
".dnm-tip{position:fixed;left:-9999px;top:-9999px;z-index:2147483001;max-width:270px;box-sizing:border-box;padding:7px 10px;border-radius:8px;border:1px solid var(--dsw-alias-border-l2,rgba(128,128,128,.35));background:var(--dsw-alias-bg-layer-3,#26282c);color:var(--dsw-alias-label-secondary,rgba(90,90,90,.9));font-size:12px;line-height:17px;box-shadow:0 6px 18px rgba(0,0,0,.35);pointer-events:none;opacity:0;transition:opacity .12s ease}" +
|
|
2459
|
+
".dnm-tip.on{opacity:1}" +
|
|
2365
2460
|
".dnm-advChev.open{transform:rotate(180deg)}";
|
|
2366
2461
|
(function injectStyle() {
|
|
2367
2462
|
try {
|
|
@@ -2376,11 +2471,101 @@
|
|
|
2376
2471
|
} catch (e) {}
|
|
2377
2472
|
})();
|
|
2378
2473
|
|
|
2474
|
+
// Hover hints: a row keeps one line of text; its explanation shows up in a
|
|
2475
|
+
// small box beside the pointer after a short delay. One shared node, so
|
|
2476
|
+
// nothing has to be cleaned up per row.
|
|
2477
|
+
var TIP_ID = "dsh-notify-me-tip";
|
|
2478
|
+
var tipEl = null;
|
|
2479
|
+
var tipTimer = null;
|
|
2480
|
+
var tipShown = false;
|
|
2481
|
+
var tipX = 0;
|
|
2482
|
+
var tipY = 0;
|
|
2483
|
+
function tipNode() {
|
|
2484
|
+
try {
|
|
2485
|
+
if (tipEl && tipEl.parentNode) return tipEl;
|
|
2486
|
+
if (typeof document === "undefined" || !document.body) return null;
|
|
2487
|
+
var node = document.createElement("div");
|
|
2488
|
+
node.id = TIP_ID;
|
|
2489
|
+
node.className = "dnm-tip";
|
|
2490
|
+
node.setAttribute("role", "tooltip");
|
|
2491
|
+
document.body.appendChild(node);
|
|
2492
|
+
tipEl = node;
|
|
2493
|
+
return node;
|
|
2494
|
+
} catch (e) { return null; }
|
|
2495
|
+
}
|
|
2496
|
+
function hideTip() {
|
|
2497
|
+
if (tipTimer) { clearTimeout(tipTimer); tipTimer = null; }
|
|
2498
|
+
if (!tipShown) return;
|
|
2499
|
+
tipShown = false;
|
|
2500
|
+
if (tipEl) tipEl.className = "dnm-tip";
|
|
2501
|
+
}
|
|
2502
|
+
// Keep the box on screen: flip left of the pointer near the right edge,
|
|
2503
|
+
// flip above it near the bottom.
|
|
2504
|
+
function placeTip() {
|
|
2505
|
+
var node = tipEl;
|
|
2506
|
+
if (!node || !tipShown) return;
|
|
2507
|
+
var pad = 12;
|
|
2508
|
+
var rect = node.getBoundingClientRect();
|
|
2509
|
+
var vw = window.innerWidth || 0;
|
|
2510
|
+
var vh = window.innerHeight || 0;
|
|
2511
|
+
var x = tipX + 14;
|
|
2512
|
+
if (x + rect.width + pad > vw) x = tipX - rect.width - 14;
|
|
2513
|
+
if (x < pad) x = pad;
|
|
2514
|
+
var y = tipY + 18;
|
|
2515
|
+
if (y + rect.height + pad > vh) y = tipY - rect.height - 12;
|
|
2516
|
+
if (y < pad) y = pad;
|
|
2517
|
+
node.style.left = Math.round(x) + "px";
|
|
2518
|
+
node.style.top = Math.round(y) + "px";
|
|
2519
|
+
}
|
|
2520
|
+
function showTip(text) {
|
|
2521
|
+
var node = tipNode();
|
|
2522
|
+
if (!node) return;
|
|
2523
|
+
node.textContent = text;
|
|
2524
|
+
tipShown = true;
|
|
2525
|
+
node.className = "dnm-tip on";
|
|
2526
|
+
placeTip();
|
|
2527
|
+
}
|
|
2528
|
+
function tipHandlers(text) {
|
|
2529
|
+
return {
|
|
2530
|
+
onMouseEnter: function (e) {
|
|
2531
|
+
tipX = e.clientX;
|
|
2532
|
+
tipY = e.clientY;
|
|
2533
|
+
if (tipTimer) clearTimeout(tipTimer);
|
|
2534
|
+
tipTimer = setTimeout(function () { tipTimer = null; showTip(text); }, 320);
|
|
2535
|
+
},
|
|
2536
|
+
onMouseMove: function (e) {
|
|
2537
|
+
tipX = e.clientX;
|
|
2538
|
+
tipY = e.clientY;
|
|
2539
|
+
if (tipShown) placeTip();
|
|
2540
|
+
},
|
|
2541
|
+
onMouseLeave: hideTip
|
|
2542
|
+
};
|
|
2543
|
+
}
|
|
2544
|
+
// A row that has an explanation: single-line label, text in the tooltip.
|
|
2545
|
+
function tipRow(className, hintText, children) {
|
|
2546
|
+
var props = { className: className + (hintText ? " dnm-rowTip" : "") };
|
|
2547
|
+
if (hintText) {
|
|
2548
|
+
props["data-dnm-tip"] = hintText;
|
|
2549
|
+
var handlers = tipHandlers(hintText);
|
|
2550
|
+
props.onMouseEnter = handlers.onMouseEnter;
|
|
2551
|
+
props.onMouseMove = handlers.onMouseMove;
|
|
2552
|
+
props.onMouseLeave = handlers.onMouseLeave;
|
|
2553
|
+
}
|
|
2554
|
+
return h("div", props, children);
|
|
2555
|
+
}
|
|
2556
|
+
// Read-screen-only copy of the tooltip text.
|
|
2557
|
+
function srOnly(text) {
|
|
2558
|
+
return text ? h("span", { className: "dnm-srOnly" }, text) : null;
|
|
2559
|
+
}
|
|
2560
|
+
(function tipDismissOnScroll() {
|
|
2561
|
+
try { window.addEventListener("scroll", hideTip, true); } catch (e) {}
|
|
2562
|
+
})();
|
|
2563
|
+
|
|
2379
2564
|
function rowSwitch(key, labelText, hintText, checked, onChange) {
|
|
2380
|
-
return
|
|
2565
|
+
return tipRow("dnm-row", hintText, [
|
|
2381
2566
|
h("div", { className: "dnm-rowText" }, [
|
|
2382
2567
|
h("div", { className: "dnm-rowLabel" }, labelText),
|
|
2383
|
-
|
|
2568
|
+
srOnly(hintText)
|
|
2384
2569
|
]),
|
|
2385
2570
|
h("button", {
|
|
2386
2571
|
type: "button",
|
|
@@ -2453,7 +2638,7 @@
|
|
|
2453
2638
|
h("div", { className: "dnm-row" }, [
|
|
2454
2639
|
h("div", { className: "dnm-rowText" }, [
|
|
2455
2640
|
h("div", { className: "dnm-rowLabel" }, labelText),
|
|
2456
|
-
h("div", { className: "dnm-
|
|
2641
|
+
h("div", { className: "dnm-rowValue" }, note)
|
|
2457
2642
|
]),
|
|
2458
2643
|
h("div", { className: "dnm-btnRow" }, [
|
|
2459
2644
|
h("select", {
|
|
@@ -2481,11 +2666,12 @@
|
|
|
2481
2666
|
])
|
|
2482
2667
|
];
|
|
2483
2668
|
if (mode !== "custom") return rows;
|
|
2484
|
-
|
|
2669
|
+
var pickNote = (rec ? Math.round((rec.size || 0) / 1024) + " KB · " : t("soundNone") + " · ")
|
|
2670
|
+
+ "≤" + Math.round(SOUND_MAX_BYTES / 1024) + " KB";
|
|
2671
|
+
rows.push(tipRow("dnm-row", pickNote, [
|
|
2485
2672
|
h("div", { className: "dnm-rowText" }, [
|
|
2486
2673
|
h("div", { className: "dnm-rowLabel" }, t("soundPick")),
|
|
2487
|
-
|
|
2488
|
-
+ "≤" + Math.round(SOUND_MAX_BYTES / 1024) + " KB")
|
|
2674
|
+
srOnly(pickNote)
|
|
2489
2675
|
]),
|
|
2490
2676
|
h("div", { className: "dnm-btnRow" }, [
|
|
2491
2677
|
h("input", {
|
|
@@ -2638,6 +2824,7 @@
|
|
|
2638
2824
|
var permOK = ("Notification" in window) ? Notification.permission === "granted" : false;
|
|
2639
2825
|
var resolvedLang = effectiveLang();
|
|
2640
2826
|
var resolvedLangName = resolvedLang === "en" ? t("langEn") : t("langZh");
|
|
2827
|
+
var langNote = resolvedLangName + (cfg.language === "auto" ? t("langAutoNote") : "");
|
|
2641
2828
|
// The environment row states what detection saw whenever the manual
|
|
2642
2829
|
// choice disagrees with it — the override exists for hosts where the
|
|
2643
2830
|
// protocol read comes out wrong, so the divergence must be visible.
|
|
@@ -2667,10 +2854,10 @@
|
|
|
2667
2854
|
var advState = React.useState(false);
|
|
2668
2855
|
var advancedOpen = advState[0];
|
|
2669
2856
|
var setAdvancedOpen = advState[1];
|
|
2670
|
-
var testRow =
|
|
2857
|
+
var testRow = tipRow("dnm-row dnm-rowStack", t("testHint"), [
|
|
2671
2858
|
h("div", { className: "dnm-rowText" }, [
|
|
2672
2859
|
h("div", { className: "dnm-rowLabel" }, t("testTitle")),
|
|
2673
|
-
|
|
2860
|
+
srOnly(t("testHint"))
|
|
2674
2861
|
]),
|
|
2675
2862
|
h("div", { className: "dnm-btnRow" }, [
|
|
2676
2863
|
h("button", { type: "button", className: "dnm-btn", disabled: !cfg.enabled, onClick: function () { runTest("attention"); } }, t("testAttention")),
|
|
@@ -2696,10 +2883,10 @@
|
|
|
2696
2883
|
}),
|
|
2697
2884
|
rowSwitch("toast", t("toast"), toastHintText(), cfg.toast, function (v) { patch({ toast: v }); }),
|
|
2698
2885
|
rowSwitch("overlay", t("overlay"), t("overlayHint"), cfg.overlay, function (v) { patch({ overlay: v }); }),
|
|
2699
|
-
cfg.overlay ?
|
|
2886
|
+
cfg.overlay ? tipRow("dnm-row dnm-rowSub", t("overlayCornerHint"), [
|
|
2700
2887
|
h("div", { className: "dnm-rowText" }, [
|
|
2701
2888
|
h("div", { className: "dnm-rowLabel" }, t("overlayCorner")),
|
|
2702
|
-
|
|
2889
|
+
srOnly(t("overlayCornerHint"))
|
|
2703
2890
|
]),
|
|
2704
2891
|
h("select", {
|
|
2705
2892
|
className: "dnm-select",
|
|
@@ -2792,10 +2979,10 @@
|
|
|
2792
2979
|
[rowSwitch("externalEnabled", t("externalEnable"), t("externalEnableHint"), cfg.externalEnabled, function (v) { patch({ externalEnabled: v }); })],
|
|
2793
2980
|
[rowSwitch("externalAttention", t("externalAttentionLabel"), t("externalAttentionHint"), cfg.externalAttention, function (v) { patch({ externalAttention: v }); })],
|
|
2794
2981
|
[rowSwitch("externalDone", t("externalDoneLabel"), t("externalDoneHint"), cfg.externalDone, function (v) { patch({ externalDone: v }); })],
|
|
2795
|
-
[
|
|
2982
|
+
[tipRow("dnm-row", t("externalTargetsHint"), [
|
|
2796
2983
|
h("div", { className: "dnm-rowText" }, [
|
|
2797
2984
|
h("div", { className: "dnm-rowLabel" }, t("externalTargetsLabel")),
|
|
2798
|
-
|
|
2985
|
+
srOnly(t("externalTargetsHint"))
|
|
2799
2986
|
])
|
|
2800
2987
|
])],
|
|
2801
2988
|
externalRows(),
|
|
@@ -2814,10 +3001,10 @@
|
|
|
2814
3001
|
}, t("externalAdd"))
|
|
2815
3002
|
])
|
|
2816
3003
|
])],
|
|
2817
|
-
[
|
|
3004
|
+
[tipRow("dnm-row", t("externalTestHint"), [
|
|
2818
3005
|
h("div", { className: "dnm-rowText" }, [
|
|
2819
3006
|
h("div", { className: "dnm-rowLabel" }, t("externalTestLabel")),
|
|
2820
|
-
|
|
3007
|
+
srOnly(t("externalTestHint"))
|
|
2821
3008
|
]),
|
|
2822
3009
|
h("div", { className: "dnm-btnRow" }, [
|
|
2823
3010
|
h("button", {
|
|
@@ -2832,10 +3019,10 @@
|
|
|
2832
3019
|
)),
|
|
2833
3020
|
secTitle(t("secDiag")),
|
|
2834
3021
|
card([
|
|
2835
|
-
|
|
3022
|
+
tipRow("dnm-row", envHint, [
|
|
2836
3023
|
h("div", { className: "dnm-rowText" }, [
|
|
2837
3024
|
h("div", { className: "dnm-rowLabel" }, t("hostEnv")),
|
|
2838
|
-
|
|
3025
|
+
srOnly(envHint)
|
|
2839
3026
|
]),
|
|
2840
3027
|
h("select", {
|
|
2841
3028
|
className: "dnm-select",
|
|
@@ -2848,10 +3035,10 @@
|
|
|
2848
3035
|
h("option", { value: "web" }, t("hostEnvWeb"))
|
|
2849
3036
|
])
|
|
2850
3037
|
]),
|
|
2851
|
-
|
|
3038
|
+
tipRow("dnm-row", langNote, [
|
|
2852
3039
|
h("div", { className: "dnm-rowText" }, [
|
|
2853
3040
|
h("div", { className: "dnm-rowLabel" }, t("language")),
|
|
2854
|
-
|
|
3041
|
+
srOnly(langNote)
|
|
2855
3042
|
]),
|
|
2856
3043
|
h("select", {
|
|
2857
3044
|
className: "dnm-select",
|
|
@@ -2864,10 +3051,10 @@
|
|
|
2864
3051
|
h("option", { value: "en" }, t("langEn"))
|
|
2865
3052
|
])
|
|
2866
3053
|
]),
|
|
2867
|
-
|
|
3054
|
+
tipRow("dnm-row", t("resetHint"), [
|
|
2868
3055
|
h("div", { className: "dnm-rowText" }, [
|
|
2869
3056
|
h("div", { className: "dnm-rowLabel" }, t("reset")),
|
|
2870
|
-
|
|
3057
|
+
srOnly(t("resetHint"))
|
|
2871
3058
|
]),
|
|
2872
3059
|
h("button", { type: "button", className: "dnm-btn", onClick: function () {
|
|
2873
3060
|
// api.resetConfig() also drops the picked cue files, which are
|
|
@@ -2932,6 +3119,11 @@
|
|
|
2932
3119
|
return React.createElement(NotifySettingsView, { ctx: ctx, t: t });
|
|
2933
3120
|
});
|
|
2934
3121
|
});
|
|
3122
|
+
// Same page, different layer: give the sidebar row the bell instead of
|
|
3123
|
+
// the gear the shell falls back to for unknown section ids.
|
|
3124
|
+
ctx.effect(function () {
|
|
3125
|
+
return installNavBell(function () { return t("nav"); });
|
|
3126
|
+
}, "dsh-notify-me: sidebar glyph");
|
|
2935
3127
|
}
|
|
2936
3128
|
} catch (e) {
|
|
2937
3129
|
try { console.error("[dsh-notify-me] settings page unavailable", e); } catch (e2) {}
|
package/package.json
CHANGED