dsh-notify-me 1.9.1 → 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 CHANGED
@@ -2,6 +2,18 @@
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
+
5
17
  ## [1.9.1] — 2026-10-20
6
18
 
7
19
  ### 新增
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-%7E161%20KiB-green?style=flat-square)](lib/client.js)
16
+ [![Client bundle size](https://img.shields.io/badge/client-%7E164%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
 
@@ -47,7 +47,7 @@ A notification plugin for DSH Web / Desktop, so you can keep full access off wit
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.9.0 defaults) rendered full-page in a standalone demo shell; the sidebar is kept for context, it is not the DSH shell.
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
 
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-%7E161%20KiB-green?style=flat-square)](lib/client.js)
16
+ [![Client bundle size](https://img.shields.io/badge/client-%7E164%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
 
@@ -47,7 +47,7 @@ DSH Web / Desktop 的通知插件,让你敢不开完整访问,也不必一
47
47
 
48
48
  ![通知提醒设置页:页眉、提醒方式、什么时候提醒、试一试与折叠的高级设置](https://raw.githubusercontent.com/chromoany/dsh-notify-me/main/docs/screenshots/settings-zh.png)
49
49
 
50
- 真实插件设置组件(v1.9.0 默认配置)在独立演示环境里整页渲染;只保留左侧导航作参照,不是 DSH 宿主外壳。
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
 
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.1";
1940
+ api.version = "1.9.2";
1941
1941
  api.setConfig = function (patch) { return setConfigLocal(patch); };
1942
1942
  api.resetConfig = function () {
1943
1943
  try {
@@ -2450,6 +2450,13 @@
2450
2450
  ".dnm-advToggle:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#3b82f6);outline-offset:2px}" +
2451
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))}" +
2452
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}" +
2453
2460
  ".dnm-advChev.open{transform:rotate(180deg)}";
2454
2461
  (function injectStyle() {
2455
2462
  try {
@@ -2464,11 +2471,101 @@
2464
2471
  } catch (e) {}
2465
2472
  })();
2466
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
+
2467
2564
  function rowSwitch(key, labelText, hintText, checked, onChange) {
2468
- return h("div", { className: "dnm-row" }, [
2565
+ return tipRow("dnm-row", hintText, [
2469
2566
  h("div", { className: "dnm-rowText" }, [
2470
2567
  h("div", { className: "dnm-rowLabel" }, labelText),
2471
- hintText ? h("div", { className: "dnm-rowHint" }, hintText) : null
2568
+ srOnly(hintText)
2472
2569
  ]),
2473
2570
  h("button", {
2474
2571
  type: "button",
@@ -2541,7 +2638,7 @@
2541
2638
  h("div", { className: "dnm-row" }, [
2542
2639
  h("div", { className: "dnm-rowText" }, [
2543
2640
  h("div", { className: "dnm-rowLabel" }, labelText),
2544
- h("div", { className: "dnm-rowHint" }, note)
2641
+ h("div", { className: "dnm-rowValue" }, note)
2545
2642
  ]),
2546
2643
  h("div", { className: "dnm-btnRow" }, [
2547
2644
  h("select", {
@@ -2569,11 +2666,12 @@
2569
2666
  ])
2570
2667
  ];
2571
2668
  if (mode !== "custom") return rows;
2572
- rows.push(h("div", { className: "dnm-row" }, [
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, [
2573
2672
  h("div", { className: "dnm-rowText" }, [
2574
2673
  h("div", { className: "dnm-rowLabel" }, t("soundPick")),
2575
- h("div", { className: "dnm-rowHint" }, (rec ? Math.round((rec.size || 0) / 1024) + " KB · " : t("soundNone") + " · ")
2576
- + "≤" + Math.round(SOUND_MAX_BYTES / 1024) + " KB")
2674
+ srOnly(pickNote)
2577
2675
  ]),
2578
2676
  h("div", { className: "dnm-btnRow" }, [
2579
2677
  h("input", {
@@ -2726,6 +2824,7 @@
2726
2824
  var permOK = ("Notification" in window) ? Notification.permission === "granted" : false;
2727
2825
  var resolvedLang = effectiveLang();
2728
2826
  var resolvedLangName = resolvedLang === "en" ? t("langEn") : t("langZh");
2827
+ var langNote = resolvedLangName + (cfg.language === "auto" ? t("langAutoNote") : "");
2729
2828
  // The environment row states what detection saw whenever the manual
2730
2829
  // choice disagrees with it — the override exists for hosts where the
2731
2830
  // protocol read comes out wrong, so the divergence must be visible.
@@ -2755,10 +2854,10 @@
2755
2854
  var advState = React.useState(false);
2756
2855
  var advancedOpen = advState[0];
2757
2856
  var setAdvancedOpen = advState[1];
2758
- var testRow = h("div", { className: "dnm-row dnm-rowStack" }, [
2857
+ var testRow = tipRow("dnm-row dnm-rowStack", t("testHint"), [
2759
2858
  h("div", { className: "dnm-rowText" }, [
2760
2859
  h("div", { className: "dnm-rowLabel" }, t("testTitle")),
2761
- h("div", { className: "dnm-rowHint" }, t("testHint"))
2860
+ srOnly(t("testHint"))
2762
2861
  ]),
2763
2862
  h("div", { className: "dnm-btnRow" }, [
2764
2863
  h("button", { type: "button", className: "dnm-btn", disabled: !cfg.enabled, onClick: function () { runTest("attention"); } }, t("testAttention")),
@@ -2784,10 +2883,10 @@
2784
2883
  }),
2785
2884
  rowSwitch("toast", t("toast"), toastHintText(), cfg.toast, function (v) { patch({ toast: v }); }),
2786
2885
  rowSwitch("overlay", t("overlay"), t("overlayHint"), cfg.overlay, function (v) { patch({ overlay: v }); }),
2787
- cfg.overlay ? h("div", { className: "dnm-row dnm-rowSub" }, [
2886
+ cfg.overlay ? tipRow("dnm-row dnm-rowSub", t("overlayCornerHint"), [
2788
2887
  h("div", { className: "dnm-rowText" }, [
2789
2888
  h("div", { className: "dnm-rowLabel" }, t("overlayCorner")),
2790
- h("div", { className: "dnm-rowHint" }, t("overlayCornerHint"))
2889
+ srOnly(t("overlayCornerHint"))
2791
2890
  ]),
2792
2891
  h("select", {
2793
2892
  className: "dnm-select",
@@ -2880,10 +2979,10 @@
2880
2979
  [rowSwitch("externalEnabled", t("externalEnable"), t("externalEnableHint"), cfg.externalEnabled, function (v) { patch({ externalEnabled: v }); })],
2881
2980
  [rowSwitch("externalAttention", t("externalAttentionLabel"), t("externalAttentionHint"), cfg.externalAttention, function (v) { patch({ externalAttention: v }); })],
2882
2981
  [rowSwitch("externalDone", t("externalDoneLabel"), t("externalDoneHint"), cfg.externalDone, function (v) { patch({ externalDone: v }); })],
2883
- [h("div", { className: "dnm-row" }, [
2982
+ [tipRow("dnm-row", t("externalTargetsHint"), [
2884
2983
  h("div", { className: "dnm-rowText" }, [
2885
2984
  h("div", { className: "dnm-rowLabel" }, t("externalTargetsLabel")),
2886
- h("div", { className: "dnm-rowHint" }, t("externalTargetsHint"))
2985
+ srOnly(t("externalTargetsHint"))
2887
2986
  ])
2888
2987
  ])],
2889
2988
  externalRows(),
@@ -2902,10 +3001,10 @@
2902
3001
  }, t("externalAdd"))
2903
3002
  ])
2904
3003
  ])],
2905
- [h("div", { className: "dnm-row" }, [
3004
+ [tipRow("dnm-row", t("externalTestHint"), [
2906
3005
  h("div", { className: "dnm-rowText" }, [
2907
3006
  h("div", { className: "dnm-rowLabel" }, t("externalTestLabel")),
2908
- h("div", { className: "dnm-rowHint" }, t("externalTestHint"))
3007
+ srOnly(t("externalTestHint"))
2909
3008
  ]),
2910
3009
  h("div", { className: "dnm-btnRow" }, [
2911
3010
  h("button", {
@@ -2920,10 +3019,10 @@
2920
3019
  )),
2921
3020
  secTitle(t("secDiag")),
2922
3021
  card([
2923
- h("div", { className: "dnm-row" }, [
3022
+ tipRow("dnm-row", envHint, [
2924
3023
  h("div", { className: "dnm-rowText" }, [
2925
3024
  h("div", { className: "dnm-rowLabel" }, t("hostEnv")),
2926
- h("div", { className: "dnm-rowHint" }, envHint)
3025
+ srOnly(envHint)
2927
3026
  ]),
2928
3027
  h("select", {
2929
3028
  className: "dnm-select",
@@ -2936,10 +3035,10 @@
2936
3035
  h("option", { value: "web" }, t("hostEnvWeb"))
2937
3036
  ])
2938
3037
  ]),
2939
- h("div", { className: "dnm-row" }, [
3038
+ tipRow("dnm-row", langNote, [
2940
3039
  h("div", { className: "dnm-rowText" }, [
2941
3040
  h("div", { className: "dnm-rowLabel" }, t("language")),
2942
- h("div", { className: "dnm-rowHint" }, resolvedLangName + (cfg.language === "auto" ? t("langAutoNote") : ""))
3041
+ srOnly(langNote)
2943
3042
  ]),
2944
3043
  h("select", {
2945
3044
  className: "dnm-select",
@@ -2952,10 +3051,10 @@
2952
3051
  h("option", { value: "en" }, t("langEn"))
2953
3052
  ])
2954
3053
  ]),
2955
- h("div", { className: "dnm-row" }, [
3054
+ tipRow("dnm-row", t("resetHint"), [
2956
3055
  h("div", { className: "dnm-rowText" }, [
2957
3056
  h("div", { className: "dnm-rowLabel" }, t("reset")),
2958
- h("div", { className: "dnm-rowHint" }, t("resetHint"))
3057
+ srOnly(t("resetHint"))
2959
3058
  ]),
2960
3059
  h("button", { type: "button", className: "dnm-btn", onClick: function () {
2961
3060
  // api.resetConfig() also drops the picked cue files, which are
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-notify-me",
3
- "version": "1.9.1",
3
+ "version": "1.9.2",
4
4
  "description": "离开 DSH 页面也能收到提醒:需要你操作 / 后台回复完成时发系统通知、标题标记与可选页面浮层(副屏也能看见),支持审批快捷裁决与外部推送。Brand icon and a reworked settings page in 1.9.0.",
5
5
  "license": "MIT",
6
6
  "type": "module",