dsh-wisp 1.33.2 → 1.34.1
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/README.md +67 -0
- package/lib/client.js +83 -42
- package/lib/client.template.js +83 -42
- package/package.json +1 -1
- package/tools/smoke.mjs +59 -9
package/README.md
CHANGED
|
@@ -448,6 +448,73 @@ node tools/audit-log.mjs --confirm <manifest> # 生成后:并入代理登
|
|
|
448
448
|
|
|
449
449
|
## 变更
|
|
450
450
|
|
|
451
|
+
### 1.34.1
|
|
452
|
+
|
|
453
|
+
**菜单改成紧凑刻度。** 上一版样式好看但**占地太大** —— 对陪伴插件的浮层来说不合适,
|
|
454
|
+
它不该占掉屏幕的观感重心。
|
|
455
|
+
|
|
456
|
+
压缩是分两轮做的,因为**高度**才是占屏幕的主因(宽度只是一部分):
|
|
457
|
+
|
|
458
|
+
| | 1.34.0 | 1.34.1 |
|
|
459
|
+
|---|---|---|
|
|
460
|
+
| 面板宽 | 188px | **174px** |
|
|
461
|
+
| 行高 | 8×10 padding | **4×6** |
|
|
462
|
+
| 字号 | 12.5px | **11px** |
|
|
463
|
+
| 圆角 | 14px | **9px** |
|
|
464
|
+
| 分隔线外边距 | 6px 8px | **3px 4px** |
|
|
465
|
+
| 摘要胶囊 | 9.5px / 15px 行高 | **9px / 13px** |
|
|
466
|
+
| 开关 | 30×16 | **22×12** |
|
|
467
|
+
| 滑块轨 / 旋钮 | 18 / 13px | **12 / 9px** |
|
|
468
|
+
| 角落键帽 | 7×4 padding | **3×3** |
|
|
469
|
+
| 子面板宽 | 164px | **150px** |
|
|
470
|
+
| 字形槽 | 14px | **12px** |
|
|
471
|
+
|
|
472
|
+
实测(真浏览器截图裁剪框):**452×500 → 363×334**,即**宽 −20%、高 −33%**。
|
|
473
|
+
字号到 11px 就停 —— 再小在这个尺寸的浮层上就不叫可读了。
|
|
474
|
+
|
|
475
|
+
**顺带一个副作用**:面板变窄之后,她在屏幕**右下角**时子面板已经能放在右侧
|
|
476
|
+
(以前放不下要翻到左边)。定位算式用的是"菜单宽度 + 面板宽度",所以宽度一变它自己就跟上了 ——
|
|
477
|
+
这也是那条"四角几何"检查的意义:它每次都验一遍真实关系。
|
|
478
|
+
|
|
479
|
+
`MENU_W` / `SUBMENU_W` 两个估算常量跟着更新(前者用于菜单夹取,后者必须等于 CSS 宽度 ——
|
|
480
|
+
定位算式的正确性依赖这一点)。
|
|
481
|
+
|
|
482
|
+
### 1.34.0
|
|
483
|
+
|
|
484
|
+
### 1.34.0
|
|
485
|
+
|
|
486
|
+
**菜单样式重做。** 这一轮先把"菜单拍下来"这一步补上 —— 样式改动光看代码看不出来。
|
|
487
|
+
|
|
488
|
+
- **冒烟支持可选截图**:`WISP_SHOT=<路径> pnpm smoke` 会把"菜单 + 展开的子面板"
|
|
489
|
+
用真浏览器拍下来。默认不拍,不影响正常冒烟。
|
|
490
|
+
|
|
491
|
+
**一拍照就发现了两个用代码看不出的问题**:
|
|
492
|
+
|
|
493
|
+
1. **「放大一点 / 缩小一点」还挂在「外观」子面板里** —— 大小早就做成菜单里的滑块了,
|
|
494
|
+
这两条是重复的(而且是我之前声称删掉、实际没打上的改动)。已删。
|
|
495
|
+
2. **只有主操作有图标**,「专注 45 分钟」等行与它左右不齐。
|
|
496
|
+
|
|
497
|
+
**样式改动**:
|
|
498
|
+
|
|
499
|
+
| 之前 | 现在 |
|
|
500
|
+
|---|---|
|
|
501
|
+
| 摘要用淡字(透明度 .55) | **胶囊 chip**:淡底 + 圆角 —— 收起状态也能一眼扫到 |
|
|
502
|
+
| 主操作只是加粗变色 | **淡色渐变底 + 左侧强调条 + 字形** |
|
|
503
|
+
| 只有主操作有图标 | **每一行都有等宽字形槽**(无图标的留占位)→ 标签左边线对齐 |
|
|
504
|
+
| 焦点环依赖浏览器默认 | 显式 `:focus-visible` 环(菜单项 / 开关 / 滑块 / 角落) |
|
|
505
|
+
| hover 无过渡 | 100ms 过渡 + 按下时的内阴影反馈 |
|
|
506
|
+
| 开关 26×14、旋钮 11px | 开关 **30×16**、旋钮 13px + hover 放大 |
|
|
507
|
+
| 角落是裸文字 | **键帽**:淡底 + 描边 + 方位字形 ↖↗↙↘(当前所在的有强调描边) |
|
|
508
|
+
| 面板与菜单同一阴影 | 面板**更高一层**:阴影加强、模糊加深 |
|
|
509
|
+
| `prefers-reduced-motion` 只管她 | 菜单 / 面板 / 开关 / 滑块一起停 |
|
|
510
|
+
| 容器 6px、行 7×10 | 容器 8px、行 8×10,配色抽成 CSS 变量 |
|
|
511
|
+
|
|
512
|
+
配色仍全部走 `--dsw-alias-*` 主题 token(带兜底),所以明暗主题都跟着走。
|
|
513
|
+
|
|
514
|
+
冒烟 57 项(新增:滑块在真浏览器里改大小且不关菜单)。
|
|
515
|
+
|
|
516
|
+
### 1.33.2
|
|
517
|
+
|
|
451
518
|
### 1.33.2
|
|
452
519
|
|
|
453
520
|
**修「她在屏幕角落时,子菜单跟着偏」。** 现象是:右下 / 左下时子面板整体**偏下**,
|
package/lib/client.js
CHANGED
|
@@ -51,7 +51,7 @@ window.__ModuleLoader__.load({
|
|
|
51
51
|
var exports = module.exports
|
|
52
52
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
|
|
53
53
|
|
|
54
|
-
const VERSION = '1.
|
|
54
|
+
const VERSION = '1.34.1'
|
|
55
55
|
|
|
56
56
|
/* 每一版一句话。**必须写当前版本** —— 预检里有一条断言盯着它,
|
|
57
57
|
忘了更新就会红,不会静默过期。 */
|
|
@@ -76,6 +76,8 @@ const WHATS_NEW = {
|
|
|
76
76
|
'1.33.0': '子菜单改成在旁边弹出(真正的菜单栏形状),→/← 进出自如。',
|
|
77
77
|
'1.33.1': '修子菜单位置算错、会压住主菜单的问题。',
|
|
78
78
|
'1.33.2': '修她在屏幕四角时子菜单跟着偏的问题。',
|
|
79
|
+
'1.34.0': '菜单样式重做:摘要做成胶囊、每行都有图标位、键盘焦点看得见。',
|
|
80
|
+
'1.34.1': '菜单改成紧凑刻度,占屏幕小了三成。',
|
|
79
81
|
}
|
|
80
82
|
|
|
81
83
|
/* 更新这件事**这个插件自己做不到**,如实写在代码里,免得以后有人以为能:
|
|
@@ -694,33 +696,50 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
694
696
|
.wisp-root[data-mood="attn"] .wisp-body{animation-name:wisp-nudge;animation-duration:1.5s;animation-iteration-count:4}
|
|
695
697
|
@keyframes wisp-nudge{0%,100%{transform:translate3d(0,0,0)}25%{transform:translate3d(-7px,0,0)}75%{transform:translate3d(7px,0,0)}}
|
|
696
698
|
@keyframes wisp-hop{0%,100%{transform:translate3d(0,0,0)}45%{transform:translate3d(0,-16px,0)}}
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
.wisp-menu-
|
|
700
|
-
.wisp-menu-
|
|
701
|
-
.wisp-menu-
|
|
702
|
-
.wisp-menu-
|
|
703
|
-
|
|
704
|
-
.wisp-menu-
|
|
705
|
-
.wisp-menu-
|
|
706
|
-
.wisp-menu-
|
|
707
|
-
.wisp-menu-
|
|
708
|
-
.wisp-menu-
|
|
709
|
-
.wisp-
|
|
710
|
-
.wisp-
|
|
711
|
-
|
|
712
|
-
.wisp-
|
|
713
|
-
|
|
714
|
-
.wisp-
|
|
715
|
-
.wisp-
|
|
716
|
-
.wisp-
|
|
717
|
-
.wisp-
|
|
718
|
-
|
|
719
|
-
.wisp-
|
|
720
|
-
.wisp-
|
|
721
|
-
.wisp-
|
|
722
|
-
.wisp-
|
|
723
|
-
.wisp-
|
|
699
|
+
/* 菜单与级联子面板共用一套外观变量 —— 面板是菜单的"上一层",只改阴影与浮起量。
|
|
700
|
+
尺寸走**紧凑刻度**:这是陪伴插件的浮层,不该占掉屏幕的观感重心。 */
|
|
701
|
+
.wisp-menu{--wisp-surface:var(--dsw-alias-bg-overlay,var(--wisp-say-bg));--wisp-line:var(--dsw-alias-border-l1,var(--wisp-say-line));--wisp-hover:var(--dsw-alias-bg-layer-2,rgba(120,170,255,.2));--wisp-accent:var(--dsw-alias-brand-primary,#4a7cf7);position:absolute;min-width:164px;max-width:196px;padding:4px;border-radius:9px;font-size:11px;line-height:1.2;color:var(--dsw-alias-label-primary,var(--wisp-say-fg));background:var(--wisp-surface);border:1px solid var(--wisp-line);box-shadow:0 10px 28px rgba(0,0,0,.3);-webkit-backdrop-filter:blur(16px) saturate(160%);backdrop-filter:blur(16px) saturate(160%);pointer-events:auto;animation:wisp-menu .13s ease}
|
|
702
|
+
.wisp-menu-item{display:block;width:100%;padding:4px 6px;border:0;border-radius:6px;background:none;color:inherit;font:inherit;text-align:left;cursor:pointer;white-space:nowrap;transition:background .1s ease,box-shadow .1s ease}
|
|
703
|
+
.wisp-menu-item:hover{background:var(--wisp-hover,rgba(120,170,255,.2))}
|
|
704
|
+
.wisp-menu-item:active{background:var(--wisp-hover,rgba(120,170,255,.2));box-shadow:inset 0 1px 2px rgba(0,0,0,.12)}
|
|
705
|
+
/* 键盘焦点必须是**看得见**的:这些行都是 border:0 的按钮,浏览器默认轮廓很弱。 */
|
|
706
|
+
.wisp-menu-item:focus-visible,.wisp-corner:focus-visible,.wisp-slider:focus-visible{outline:2px solid var(--wisp-accent,#4a7cf7);outline-offset:-2px}
|
|
707
|
+
.wisp-menu-head{padding:1px 6px 4px}
|
|
708
|
+
.wisp-menu-title{font-size:11.5px;font-weight:600;letter-spacing:.01em}
|
|
709
|
+
.wisp-menu-sub{font-size:9px;opacity:.6;margin-top:0}
|
|
710
|
+
.wisp-menu-sep{height:1px;margin:3px 4px;background:var(--wisp-line,var(--wisp-say-line));opacity:.5}
|
|
711
|
+
.wisp-menu-hint{padding:4px 6px 0;font-size:9px;opacity:.5;border-top:1px solid var(--wisp-line,var(--wisp-say-line));margin-top:3px}
|
|
712
|
+
.wisp-menu-row{display:flex;align-items:center;justify-content:space-between;gap:8px}
|
|
713
|
+
/* 摘要做成胶囊:收起状态也要能一眼扫到"现在是什么",纯淡字太弱。 */
|
|
714
|
+
.wisp-menu-sum{flex:0 1 auto;min-width:0;padding:0 4px;border-radius:999px;background:var(--wisp-hover,rgba(120,170,255,.2));font-size:9px;line-height:13px;opacity:.85;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
715
|
+
/* 主操作(专注):淡色底 + 左侧强调条 + 字形,一眼看出"这是主要动作"。 */
|
|
716
|
+
.wisp-menu-item[data-accent="true"]{position:relative;font-weight:600;color:var(--wisp-accent,inherit);background:linear-gradient(90deg,var(--wisp-hover,rgba(120,170,255,.2)),transparent 88%)}
|
|
717
|
+
.wisp-menu-item[data-accent="true"]::before{content:"";position:absolute;left:2px;top:50%;width:2px;height:11px;margin-top:-5.5px;border-radius:1px;background:var(--wisp-accent,#4a7cf7)}
|
|
718
|
+
.wisp-menu-item[data-accent="true"]:hover{background:linear-gradient(90deg,var(--wisp-hover,rgba(120,170,255,.32)),transparent 92%)}
|
|
719
|
+
.wisp-menu-item[data-muted="true"]{opacity:.8}
|
|
720
|
+
/* 字形槽:每行都留等宽位,标签左边线才对得齐 */
|
|
721
|
+
.wisp-menu-glyph{display:inline-block;width:12px;margin-right:4px;text-align:center;font-size:10.5px;opacity:.72;font-weight:400}
|
|
722
|
+
.wisp-menu-item[data-accent="true"] .wisp-menu-glyph{opacity:1}
|
|
723
|
+
.wisp-menu-caret{opacity:.5;font-size:8px;flex:0 0 auto}
|
|
724
|
+
.wisp-switch{flex:0 0 auto;width:22px;height:12px;border-radius:6px;background:rgba(140,160,190,.4);position:relative;transition:background .16s ease}
|
|
725
|
+
.wisp-switch[data-on="true"]{background:var(--wisp-accent,#4a7cf7);box-shadow:0 0 0 2px rgba(74,124,247,.14)}
|
|
726
|
+
.wisp-switch i{position:absolute;top:2px;left:2px;width:8px;height:8px;border-radius:50%;background:#fff;box-shadow:0 1px 2px rgba(0,0,0,.28);transition:transform .16s ease}
|
|
727
|
+
.wisp-switch[data-on="true"] i{transform:translateX(10px)}
|
|
728
|
+
.wisp-slider-row{padding:3px 6px 5px}
|
|
729
|
+
.wisp-slider-top{display:flex;justify-content:space-between;align-items:baseline;font-size:9.5px;opacity:.85;margin-bottom:1px}
|
|
730
|
+
.wisp-slider-value{font-variant-numeric:tabular-nums;opacity:.75}
|
|
731
|
+
.wisp-slider{position:relative;height:12px;cursor:pointer;outline:none;touch-action:none}
|
|
732
|
+
.wisp-slider-track{position:absolute;left:0;right:0;top:5px;height:2px;border-radius:1px;background:rgba(140,160,190,.4)}
|
|
733
|
+
.wisp-slider-fill{position:absolute;left:0;top:5px;height:2px;border-radius:1px;background:var(--wisp-accent,#4a7cf7)}
|
|
734
|
+
.wisp-slider-knob{position:absolute;top:1px;width:9px;height:9px;margin-left:-5px;border-radius:50%;background:#fff;box-shadow:0 1px 2px rgba(0,0,0,.35);transition:transform .1s ease}
|
|
735
|
+
.wisp-slider:hover .wisp-slider-knob{transform:scale(1.15)}
|
|
736
|
+
.wisp-slider:active .wisp-slider-knob{transform:scale(1.25)}
|
|
737
|
+
/* 四个角落做成"键帽":方位是空间的,控件也该长得像方位键。 */
|
|
738
|
+
.wisp-grid2{display:grid;grid-template-columns:1fr 1fr;gap:3px;padding:1px 4px 3px}
|
|
739
|
+
.wisp-grid2 button,.wisp-corner{display:flex;align-items:center;justify-content:center;gap:3px;padding:3px 3px;border:1px solid var(--wisp-line,var(--wisp-say-line));border-radius:5px;background:var(--wisp-hover,rgba(120,170,255,.12));color:inherit;font:inherit;font-size:9.5px;cursor:pointer;transition:background .1s ease,border-color .1s ease}
|
|
740
|
+
.wisp-grid2 button:hover,.wisp-corner:hover{background:var(--wisp-hover,rgba(120,170,255,.26))}
|
|
741
|
+
.wisp-grid2 button[data-current="true"],.wisp-corner[data-current="true"]{border-color:var(--wisp-accent,#4a7cf7);color:var(--wisp-accent,inherit);font-weight:600;box-shadow:0 0 0 1px var(--wisp-accent,#4a7cf7) inset}
|
|
742
|
+
.wisp-corner-glyph{font-size:10px;opacity:.7}
|
|
724
743
|
.wisp-root[data-hidden="true"]{display:none}
|
|
725
744
|
.wisp-tab{position:absolute;width:56px;height:56px;padding:0;display:flex;align-items:center;justify-content:center;overflow:hidden;border-radius:16px;border:1px solid var(--dsw-alias-border-l1,var(--wisp-say-line));background:var(--dsw-alias-bg-overlay,var(--wisp-say-bg));box-shadow:0 6px 18px rgba(0,0,0,.22);cursor:pointer;pointer-events:auto;animation:wisp-menu .16s ease;transition:transform .16s ease}
|
|
726
745
|
.wisp-tab:hover{transform:translateY(-2px)}
|
|
@@ -731,7 +750,7 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
731
750
|
.wisp-menu-caret{opacity:.55;font-size:10px;flex:0 0 auto}
|
|
732
751
|
/* 级联子菜单:浮在行的**右侧**(放不下时翻到左侧),不把下面的内容推下去。
|
|
733
752
|
与父行顶部对齐、允许轻微重叠 —— 指针从行斜着移到面板上时不会穿出"同一区域"。 */
|
|
734
|
-
.wisp-submenu{position:absolute;box-sizing:border-box;width:
|
|
753
|
+
.wisp-submenu{--wisp-surface:var(--dsw-alias-bg-overlay,var(--wisp-say-bg));--wisp-line:var(--dsw-alias-border-l1,var(--wisp-say-line));--wisp-hover:var(--dsw-alias-bg-layer-2,rgba(120,170,255,.2));--wisp-accent:var(--dsw-alias-brand-primary,#4a7cf7);position:absolute;box-sizing:border-box;width:150px;padding:4px;border-radius:9px;font-size:11px;line-height:1.2;color:var(--dsw-alias-label-primary,var(--wisp-say-fg));background:var(--dsw-alias-bg-overlay,var(--wisp-say-bg));border:1px solid var(--dsw-alias-border-l1,var(--wisp-say-line));box-shadow:0 16px 40px rgba(0,0,0,.34),0 2px 6px rgba(0,0,0,.18);-webkit-backdrop-filter:blur(18px) saturate(170%);backdrop-filter:blur(18px) saturate(170%);pointer-events:auto;animation:wisp-fade .12s ease}
|
|
735
754
|
@keyframes wisp-fade{0%{opacity:0}100%{opacity:1}}
|
|
736
755
|
.wisp-menu-sub{padding-left:10px}
|
|
737
756
|
.wisp-menu-sub .wisp-menu-item{padding-left:12px;opacity:.94}
|
|
@@ -749,7 +768,7 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
749
768
|
.wisp-credits{font-size:11px;opacity:.8;margin:6px 0 0}
|
|
750
769
|
.wisp-dialog-action{display:block;width:100%;margin:4px 0 2px;padding:7px 10px;border:1px solid var(--dsw-alias-border-l1,var(--wisp-say-line));border-radius:8px;background:none;color:inherit;font:inherit;cursor:pointer}
|
|
751
770
|
.wisp-dialog-action:hover{background:var(--dsw-alias-bg-layer-2,rgba(120,170,255,.2))}
|
|
752
|
-
@media (prefers-reduced-motion:reduce){.wisp-body{animation:none!important}}`
|
|
771
|
+
@media (prefers-reduced-motion:reduce){.wisp-body{animation:none!important}.wisp-menu,.wisp-submenu,.wisp-switch,.wisp-switch i,.wisp-slider-knob,.wisp-menu-item,.wisp-corner{animation:none!important;transition:none!important}}`
|
|
753
772
|
|
|
754
773
|
const installStyles = (styles) => {
|
|
755
774
|
if (styles && typeof styles.insert === 'function') {
|
|
@@ -2013,7 +2032,7 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
2013
2032
|
/* ---- 右键 = 叫回原位 ----
|
|
2014
2033
|
她大了之后很容易被拖到看不见的地方,而"恢复位置"原先只能开 Console。
|
|
2015
2034
|
现在右键她本体弹出菜单,归位只是其中一项。 */
|
|
2016
|
-
const MENU_W =
|
|
2035
|
+
const MENU_W = 164
|
|
2017
2036
|
const MENU_ITEM_H = 30
|
|
2018
2037
|
const MENU_PAD = 10
|
|
2019
2038
|
let menuEl = null
|
|
@@ -2024,6 +2043,15 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
2024
2043
|
/* 行构造器:菜单和子面板都用它们 —— 所以放在模块级,不能藏在 openMenu 里面。 */
|
|
2025
2044
|
let menuExtras = 0
|
|
2026
2045
|
/* 动作行:点了就执行、然后关菜单。 */
|
|
2046
|
+
/* 行左侧的小字形(子 span,不写进按钮自己的 textContent) */
|
|
2047
|
+
const glyph = (text) => {
|
|
2048
|
+
const g = document.createElement('span')
|
|
2049
|
+
g.className = 'wisp-menu-glyph'
|
|
2050
|
+
g.setAttribute('aria-hidden', 'true')
|
|
2051
|
+
g.textContent = text
|
|
2052
|
+
return g
|
|
2053
|
+
}
|
|
2054
|
+
|
|
2027
2055
|
const actionRow = (spec, into) => {
|
|
2028
2056
|
const btn = document.createElement('button')
|
|
2029
2057
|
btn.className = 'wisp-menu-item'
|
|
@@ -2033,6 +2061,10 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
2033
2061
|
if (spec.accent === true) btn.dataset.accent = 'true'
|
|
2034
2062
|
if (spec.muted === true) btn.dataset.muted = 'true'
|
|
2035
2063
|
btn.textContent = spec.label
|
|
2064
|
+
/* 字形槽:有图标的画图标,没有的留等宽占位 —— 所有标签的左边线对齐。
|
|
2065
|
+
一律用**子 span**:真浏览器显示图标,简化 DOM 里按钮自己的 textContent
|
|
2066
|
+
仍是纯标签(测试读的就是它)。 */
|
|
2067
|
+
if (btn.insertBefore) btn.insertBefore(glyph(spec.glyph || ''), btn.firstChild)
|
|
2036
2068
|
// 菜单自己吞掉 pointerdown,否则会被下面那个"点外面就关"的监听判成外部点击
|
|
2037
2069
|
btn.addEventListener('pointerdown', (ev) => ev.stopPropagation())
|
|
2038
2070
|
btn.addEventListener('click', (ev) => {
|
|
@@ -2058,6 +2090,7 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
2058
2090
|
btn.dataset.switchKey = spec.key
|
|
2059
2091
|
const label = document.createElement('span')
|
|
2060
2092
|
label.textContent = spec.label
|
|
2093
|
+
if (btn.insertBefore && glyph) btn.insertBefore(glyph(spec.glyph || ''), btn.firstChild)
|
|
2061
2094
|
const toggle = document.createElement('span')
|
|
2062
2095
|
toggle.className = 'wisp-switch'
|
|
2063
2096
|
toggle.dataset.on = on ? 'true' : 'false'
|
|
@@ -2160,6 +2193,16 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
2160
2193
|
b.setAttribute('tabindex', '-1')
|
|
2161
2194
|
b.dataset.corner = corner.label
|
|
2162
2195
|
b.textContent = corner.label
|
|
2196
|
+
/* 方位是空间的,就给它一个方位字形 —— 键帽上写着 ↖ 比写着"左上角"快得多。 */
|
|
2197
|
+
const arrows = { '左上角': '↖', '右上角': '↗', '左下角': '↙', '右下角': '↘' }
|
|
2198
|
+
const arrow = arrows[corner.label]
|
|
2199
|
+
if (arrow && b.insertBefore) {
|
|
2200
|
+
const g = document.createElement('span')
|
|
2201
|
+
g.className = 'wisp-corner-glyph'
|
|
2202
|
+
g.setAttribute('aria-hidden', 'true')
|
|
2203
|
+
g.textContent = arrow
|
|
2204
|
+
b.insertBefore(g, b.firstChild)
|
|
2205
|
+
}
|
|
2163
2206
|
if (corner.label === currentCorner()) b.dataset.current = 'true'
|
|
2164
2207
|
b.addEventListener('pointerdown', (ev) => ev.stopPropagation())
|
|
2165
2208
|
b.addEventListener('click', (ev) => {
|
|
@@ -2190,7 +2233,7 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
2190
2233
|
延迟是 hover intent:鼠标只是路过一行,不该弹出东西。 */
|
|
2191
2234
|
const HOVER_OPEN_MS = 140
|
|
2192
2235
|
const HOVER_CLOSE_MS = 260
|
|
2193
|
-
const SUBMENU_W =
|
|
2236
|
+
const SUBMENU_W = 150
|
|
2194
2237
|
let flyoutName = ''
|
|
2195
2238
|
let flyoutEl = null
|
|
2196
2239
|
let flyoutRow = null
|
|
@@ -2577,10 +2620,10 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
2577
2620
|
{ kind: 'sep' },
|
|
2578
2621
|
/* 专注:最常用的动作,放最上面;进行中时这一行本身就是状态 + 结束 */
|
|
2579
2622
|
...(focusUntil > 0
|
|
2580
|
-
? [{ label: '专注中 · 还剩 ' + Math.max(1, Math.ceil(focusLeftMs() / 60000)) + ' 分钟', run: () => stopFocus(false), accent: true }]
|
|
2623
|
+
? [{ label: '专注中 · 还剩 ' + Math.max(1, Math.ceil(focusLeftMs() / 60000)) + ' 分钟', run: () => stopFocus(false), accent: true, glyph: '⏱' }]
|
|
2581
2624
|
: [
|
|
2582
|
-
{ label: '专注 ' + cfg.focusMinutes + ' 分钟', run: () => startFocus(cfg.focusMinutes), accent: true },
|
|
2583
|
-
{ label: '专注 45 分钟', run: () => startFocus(45) },
|
|
2625
|
+
{ label: '专注 ' + cfg.focusMinutes + ' 分钟', run: () => startFocus(cfg.focusMinutes), accent: true, glyph: '⏱' },
|
|
2626
|
+
{ label: '专注 45 分钟', run: () => startFocus(45), glyph: '⏱' },
|
|
2584
2627
|
]),
|
|
2585
2628
|
{ kind: 'sep' },
|
|
2586
2629
|
{
|
|
@@ -2595,16 +2638,14 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
2595
2638
|
say('换成' + skinLabel(id) + '了。')
|
|
2596
2639
|
},
|
|
2597
2640
|
})),
|
|
2598
|
-
{ label: '放大一点', run: () => applyConfig({ size: cfg.size + 0.5 }) },
|
|
2599
|
-
{ label: '缩小一点', run: () => applyConfig({ size: cfg.size - 0.5 }) },
|
|
2600
2641
|
],
|
|
2601
2642
|
},
|
|
2602
2643
|
{
|
|
2603
2644
|
group: '行为',
|
|
2604
2645
|
summary: onToggleCount() + '/' + TOGGLES.length + ' 开',
|
|
2605
2646
|
children: [
|
|
2606
|
-
{ label: '说句话', run: () => { wake(true); say(pick(LINES.click)) } },
|
|
2607
|
-
{ label: '睡一会儿', run: () => setMood('sleep') },
|
|
2647
|
+
{ label: '说句话', glyph: '✦', run: () => { wake(true); say(pick(LINES.click)) } },
|
|
2648
|
+
{ label: '睡一会儿', glyph: '☾', run: () => setMood('sleep') },
|
|
2608
2649
|
/* 开关是**真正的开关行**:带 aria-checked,点完菜单不关(就在原地更新) */
|
|
2609
2650
|
...TOGGLES.map((t) => ({ kind: 'switch', key: t.key, label: t.label })),
|
|
2610
2651
|
],
|
|
@@ -2616,11 +2657,11 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
2616
2657
|
},
|
|
2617
2658
|
{ kind: 'slider' },
|
|
2618
2659
|
{ kind: 'sep' },
|
|
2619
|
-
{ label: '动作一览…', run: () => actionsDialog() },
|
|
2620
|
-
{ label: '关于她…', run: () => aboutDialog() },
|
|
2660
|
+
{ label: '动作一览…', glyph: '▤', run: () => actionsDialog() },
|
|
2661
|
+
{ label: '关于她…', glyph: 'ⓘ', run: () => aboutDialog() },
|
|
2621
2662
|
{ kind: 'sep' },
|
|
2622
2663
|
/* 一个开关,不是一个单向动作:从迷你按钮右键打开菜单时,它必须能把她叫回来。 */
|
|
2623
|
-
{ label: hidden ? '回来吧' : '先躲起来', run: () => setHidden(!hidden), muted: true },
|
|
2664
|
+
{ label: hidden ? '回来吧' : '先躲起来', glyph: '⤓', run: () => setHidden(!hidden), muted: true },
|
|
2624
2665
|
{ kind: 'hint' },
|
|
2625
2666
|
]
|
|
2626
2667
|
|
package/lib/client.template.js
CHANGED
|
@@ -51,7 +51,7 @@ window.__ModuleLoader__.load({
|
|
|
51
51
|
var exports = module.exports
|
|
52
52
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
|
|
53
53
|
|
|
54
|
-
const VERSION = '1.
|
|
54
|
+
const VERSION = '1.34.1'
|
|
55
55
|
|
|
56
56
|
/* 每一版一句话。**必须写当前版本** —— 预检里有一条断言盯着它,
|
|
57
57
|
忘了更新就会红,不会静默过期。 */
|
|
@@ -76,6 +76,8 @@ const WHATS_NEW = {
|
|
|
76
76
|
'1.33.0': '子菜单改成在旁边弹出(真正的菜单栏形状),→/← 进出自如。',
|
|
77
77
|
'1.33.1': '修子菜单位置算错、会压住主菜单的问题。',
|
|
78
78
|
'1.33.2': '修她在屏幕四角时子菜单跟着偏的问题。',
|
|
79
|
+
'1.34.0': '菜单样式重做:摘要做成胶囊、每行都有图标位、键盘焦点看得见。',
|
|
80
|
+
'1.34.1': '菜单改成紧凑刻度,占屏幕小了三成。',
|
|
79
81
|
}
|
|
80
82
|
|
|
81
83
|
/* 更新这件事**这个插件自己做不到**,如实写在代码里,免得以后有人以为能:
|
|
@@ -643,33 +645,50 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
643
645
|
.wisp-root[data-mood="attn"] .wisp-body{animation-name:wisp-nudge;animation-duration:1.5s;animation-iteration-count:4}
|
|
644
646
|
@keyframes wisp-nudge{0%,100%{transform:translate3d(0,0,0)}25%{transform:translate3d(-7px,0,0)}75%{transform:translate3d(7px,0,0)}}
|
|
645
647
|
@keyframes wisp-hop{0%,100%{transform:translate3d(0,0,0)}45%{transform:translate3d(0,-16px,0)}}
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
.wisp-menu-
|
|
649
|
-
.wisp-menu-
|
|
650
|
-
.wisp-menu-
|
|
651
|
-
.wisp-menu-
|
|
652
|
-
|
|
653
|
-
.wisp-menu-
|
|
654
|
-
.wisp-menu-
|
|
655
|
-
.wisp-menu-
|
|
656
|
-
.wisp-menu-
|
|
657
|
-
.wisp-menu-
|
|
658
|
-
.wisp-
|
|
659
|
-
.wisp-
|
|
660
|
-
|
|
661
|
-
.wisp-
|
|
662
|
-
|
|
663
|
-
.wisp-
|
|
664
|
-
.wisp-
|
|
665
|
-
.wisp-
|
|
666
|
-
.wisp-
|
|
667
|
-
|
|
668
|
-
.wisp-
|
|
669
|
-
.wisp-
|
|
670
|
-
.wisp-
|
|
671
|
-
.wisp-
|
|
672
|
-
.wisp-
|
|
648
|
+
/* 菜单与级联子面板共用一套外观变量 —— 面板是菜单的"上一层",只改阴影与浮起量。
|
|
649
|
+
尺寸走**紧凑刻度**:这是陪伴插件的浮层,不该占掉屏幕的观感重心。 */
|
|
650
|
+
.wisp-menu{--wisp-surface:var(--dsw-alias-bg-overlay,var(--wisp-say-bg));--wisp-line:var(--dsw-alias-border-l1,var(--wisp-say-line));--wisp-hover:var(--dsw-alias-bg-layer-2,rgba(120,170,255,.2));--wisp-accent:var(--dsw-alias-brand-primary,#4a7cf7);position:absolute;min-width:164px;max-width:196px;padding:4px;border-radius:9px;font-size:11px;line-height:1.2;color:var(--dsw-alias-label-primary,var(--wisp-say-fg));background:var(--wisp-surface);border:1px solid var(--wisp-line);box-shadow:0 10px 28px rgba(0,0,0,.3);-webkit-backdrop-filter:blur(16px) saturate(160%);backdrop-filter:blur(16px) saturate(160%);pointer-events:auto;animation:wisp-menu .13s ease}
|
|
651
|
+
.wisp-menu-item{display:block;width:100%;padding:4px 6px;border:0;border-radius:6px;background:none;color:inherit;font:inherit;text-align:left;cursor:pointer;white-space:nowrap;transition:background .1s ease,box-shadow .1s ease}
|
|
652
|
+
.wisp-menu-item:hover{background:var(--wisp-hover,rgba(120,170,255,.2))}
|
|
653
|
+
.wisp-menu-item:active{background:var(--wisp-hover,rgba(120,170,255,.2));box-shadow:inset 0 1px 2px rgba(0,0,0,.12)}
|
|
654
|
+
/* 键盘焦点必须是**看得见**的:这些行都是 border:0 的按钮,浏览器默认轮廓很弱。 */
|
|
655
|
+
.wisp-menu-item:focus-visible,.wisp-corner:focus-visible,.wisp-slider:focus-visible{outline:2px solid var(--wisp-accent,#4a7cf7);outline-offset:-2px}
|
|
656
|
+
.wisp-menu-head{padding:1px 6px 4px}
|
|
657
|
+
.wisp-menu-title{font-size:11.5px;font-weight:600;letter-spacing:.01em}
|
|
658
|
+
.wisp-menu-sub{font-size:9px;opacity:.6;margin-top:0}
|
|
659
|
+
.wisp-menu-sep{height:1px;margin:3px 4px;background:var(--wisp-line,var(--wisp-say-line));opacity:.5}
|
|
660
|
+
.wisp-menu-hint{padding:4px 6px 0;font-size:9px;opacity:.5;border-top:1px solid var(--wisp-line,var(--wisp-say-line));margin-top:3px}
|
|
661
|
+
.wisp-menu-row{display:flex;align-items:center;justify-content:space-between;gap:8px}
|
|
662
|
+
/* 摘要做成胶囊:收起状态也要能一眼扫到"现在是什么",纯淡字太弱。 */
|
|
663
|
+
.wisp-menu-sum{flex:0 1 auto;min-width:0;padding:0 4px;border-radius:999px;background:var(--wisp-hover,rgba(120,170,255,.2));font-size:9px;line-height:13px;opacity:.85;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
664
|
+
/* 主操作(专注):淡色底 + 左侧强调条 + 字形,一眼看出"这是主要动作"。 */
|
|
665
|
+
.wisp-menu-item[data-accent="true"]{position:relative;font-weight:600;color:var(--wisp-accent,inherit);background:linear-gradient(90deg,var(--wisp-hover,rgba(120,170,255,.2)),transparent 88%)}
|
|
666
|
+
.wisp-menu-item[data-accent="true"]::before{content:"";position:absolute;left:2px;top:50%;width:2px;height:11px;margin-top:-5.5px;border-radius:1px;background:var(--wisp-accent,#4a7cf7)}
|
|
667
|
+
.wisp-menu-item[data-accent="true"]:hover{background:linear-gradient(90deg,var(--wisp-hover,rgba(120,170,255,.32)),transparent 92%)}
|
|
668
|
+
.wisp-menu-item[data-muted="true"]{opacity:.8}
|
|
669
|
+
/* 字形槽:每行都留等宽位,标签左边线才对得齐 */
|
|
670
|
+
.wisp-menu-glyph{display:inline-block;width:12px;margin-right:4px;text-align:center;font-size:10.5px;opacity:.72;font-weight:400}
|
|
671
|
+
.wisp-menu-item[data-accent="true"] .wisp-menu-glyph{opacity:1}
|
|
672
|
+
.wisp-menu-caret{opacity:.5;font-size:8px;flex:0 0 auto}
|
|
673
|
+
.wisp-switch{flex:0 0 auto;width:22px;height:12px;border-radius:6px;background:rgba(140,160,190,.4);position:relative;transition:background .16s ease}
|
|
674
|
+
.wisp-switch[data-on="true"]{background:var(--wisp-accent,#4a7cf7);box-shadow:0 0 0 2px rgba(74,124,247,.14)}
|
|
675
|
+
.wisp-switch i{position:absolute;top:2px;left:2px;width:8px;height:8px;border-radius:50%;background:#fff;box-shadow:0 1px 2px rgba(0,0,0,.28);transition:transform .16s ease}
|
|
676
|
+
.wisp-switch[data-on="true"] i{transform:translateX(10px)}
|
|
677
|
+
.wisp-slider-row{padding:3px 6px 5px}
|
|
678
|
+
.wisp-slider-top{display:flex;justify-content:space-between;align-items:baseline;font-size:9.5px;opacity:.85;margin-bottom:1px}
|
|
679
|
+
.wisp-slider-value{font-variant-numeric:tabular-nums;opacity:.75}
|
|
680
|
+
.wisp-slider{position:relative;height:12px;cursor:pointer;outline:none;touch-action:none}
|
|
681
|
+
.wisp-slider-track{position:absolute;left:0;right:0;top:5px;height:2px;border-radius:1px;background:rgba(140,160,190,.4)}
|
|
682
|
+
.wisp-slider-fill{position:absolute;left:0;top:5px;height:2px;border-radius:1px;background:var(--wisp-accent,#4a7cf7)}
|
|
683
|
+
.wisp-slider-knob{position:absolute;top:1px;width:9px;height:9px;margin-left:-5px;border-radius:50%;background:#fff;box-shadow:0 1px 2px rgba(0,0,0,.35);transition:transform .1s ease}
|
|
684
|
+
.wisp-slider:hover .wisp-slider-knob{transform:scale(1.15)}
|
|
685
|
+
.wisp-slider:active .wisp-slider-knob{transform:scale(1.25)}
|
|
686
|
+
/* 四个角落做成"键帽":方位是空间的,控件也该长得像方位键。 */
|
|
687
|
+
.wisp-grid2{display:grid;grid-template-columns:1fr 1fr;gap:3px;padding:1px 4px 3px}
|
|
688
|
+
.wisp-grid2 button,.wisp-corner{display:flex;align-items:center;justify-content:center;gap:3px;padding:3px 3px;border:1px solid var(--wisp-line,var(--wisp-say-line));border-radius:5px;background:var(--wisp-hover,rgba(120,170,255,.12));color:inherit;font:inherit;font-size:9.5px;cursor:pointer;transition:background .1s ease,border-color .1s ease}
|
|
689
|
+
.wisp-grid2 button:hover,.wisp-corner:hover{background:var(--wisp-hover,rgba(120,170,255,.26))}
|
|
690
|
+
.wisp-grid2 button[data-current="true"],.wisp-corner[data-current="true"]{border-color:var(--wisp-accent,#4a7cf7);color:var(--wisp-accent,inherit);font-weight:600;box-shadow:0 0 0 1px var(--wisp-accent,#4a7cf7) inset}
|
|
691
|
+
.wisp-corner-glyph{font-size:10px;opacity:.7}
|
|
673
692
|
.wisp-root[data-hidden="true"]{display:none}
|
|
674
693
|
.wisp-tab{position:absolute;width:56px;height:56px;padding:0;display:flex;align-items:center;justify-content:center;overflow:hidden;border-radius:16px;border:1px solid var(--dsw-alias-border-l1,var(--wisp-say-line));background:var(--dsw-alias-bg-overlay,var(--wisp-say-bg));box-shadow:0 6px 18px rgba(0,0,0,.22);cursor:pointer;pointer-events:auto;animation:wisp-menu .16s ease;transition:transform .16s ease}
|
|
675
694
|
.wisp-tab:hover{transform:translateY(-2px)}
|
|
@@ -680,7 +699,7 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
680
699
|
.wisp-menu-caret{opacity:.55;font-size:10px;flex:0 0 auto}
|
|
681
700
|
/* 级联子菜单:浮在行的**右侧**(放不下时翻到左侧),不把下面的内容推下去。
|
|
682
701
|
与父行顶部对齐、允许轻微重叠 —— 指针从行斜着移到面板上时不会穿出"同一区域"。 */
|
|
683
|
-
.wisp-submenu{position:absolute;box-sizing:border-box;width:
|
|
702
|
+
.wisp-submenu{--wisp-surface:var(--dsw-alias-bg-overlay,var(--wisp-say-bg));--wisp-line:var(--dsw-alias-border-l1,var(--wisp-say-line));--wisp-hover:var(--dsw-alias-bg-layer-2,rgba(120,170,255,.2));--wisp-accent:var(--dsw-alias-brand-primary,#4a7cf7);position:absolute;box-sizing:border-box;width:150px;padding:4px;border-radius:9px;font-size:11px;line-height:1.2;color:var(--dsw-alias-label-primary,var(--wisp-say-fg));background:var(--dsw-alias-bg-overlay,var(--wisp-say-bg));border:1px solid var(--dsw-alias-border-l1,var(--wisp-say-line));box-shadow:0 16px 40px rgba(0,0,0,.34),0 2px 6px rgba(0,0,0,.18);-webkit-backdrop-filter:blur(18px) saturate(170%);backdrop-filter:blur(18px) saturate(170%);pointer-events:auto;animation:wisp-fade .12s ease}
|
|
684
703
|
@keyframes wisp-fade{0%{opacity:0}100%{opacity:1}}
|
|
685
704
|
.wisp-menu-sub{padding-left:10px}
|
|
686
705
|
.wisp-menu-sub .wisp-menu-item{padding-left:12px;opacity:.94}
|
|
@@ -698,7 +717,7 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
698
717
|
.wisp-credits{font-size:11px;opacity:.8;margin:6px 0 0}
|
|
699
718
|
.wisp-dialog-action{display:block;width:100%;margin:4px 0 2px;padding:7px 10px;border:1px solid var(--dsw-alias-border-l1,var(--wisp-say-line));border-radius:8px;background:none;color:inherit;font:inherit;cursor:pointer}
|
|
700
719
|
.wisp-dialog-action:hover{background:var(--dsw-alias-bg-layer-2,rgba(120,170,255,.2))}
|
|
701
|
-
@media (prefers-reduced-motion:reduce){.wisp-body{animation:none!important}}`
|
|
720
|
+
@media (prefers-reduced-motion:reduce){.wisp-body{animation:none!important}.wisp-menu,.wisp-submenu,.wisp-switch,.wisp-switch i,.wisp-slider-knob,.wisp-menu-item,.wisp-corner{animation:none!important;transition:none!important}}`
|
|
702
721
|
|
|
703
722
|
const installStyles = (styles) => {
|
|
704
723
|
if (styles && typeof styles.insert === 'function') {
|
|
@@ -1962,7 +1981,7 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
1962
1981
|
/* ---- 右键 = 叫回原位 ----
|
|
1963
1982
|
她大了之后很容易被拖到看不见的地方,而"恢复位置"原先只能开 Console。
|
|
1964
1983
|
现在右键她本体弹出菜单,归位只是其中一项。 */
|
|
1965
|
-
const MENU_W =
|
|
1984
|
+
const MENU_W = 164
|
|
1966
1985
|
const MENU_ITEM_H = 30
|
|
1967
1986
|
const MENU_PAD = 10
|
|
1968
1987
|
let menuEl = null
|
|
@@ -1973,6 +1992,15 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
1973
1992
|
/* 行构造器:菜单和子面板都用它们 —— 所以放在模块级,不能藏在 openMenu 里面。 */
|
|
1974
1993
|
let menuExtras = 0
|
|
1975
1994
|
/* 动作行:点了就执行、然后关菜单。 */
|
|
1995
|
+
/* 行左侧的小字形(子 span,不写进按钮自己的 textContent) */
|
|
1996
|
+
const glyph = (text) => {
|
|
1997
|
+
const g = document.createElement('span')
|
|
1998
|
+
g.className = 'wisp-menu-glyph'
|
|
1999
|
+
g.setAttribute('aria-hidden', 'true')
|
|
2000
|
+
g.textContent = text
|
|
2001
|
+
return g
|
|
2002
|
+
}
|
|
2003
|
+
|
|
1976
2004
|
const actionRow = (spec, into) => {
|
|
1977
2005
|
const btn = document.createElement('button')
|
|
1978
2006
|
btn.className = 'wisp-menu-item'
|
|
@@ -1982,6 +2010,10 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
1982
2010
|
if (spec.accent === true) btn.dataset.accent = 'true'
|
|
1983
2011
|
if (spec.muted === true) btn.dataset.muted = 'true'
|
|
1984
2012
|
btn.textContent = spec.label
|
|
2013
|
+
/* 字形槽:有图标的画图标,没有的留等宽占位 —— 所有标签的左边线对齐。
|
|
2014
|
+
一律用**子 span**:真浏览器显示图标,简化 DOM 里按钮自己的 textContent
|
|
2015
|
+
仍是纯标签(测试读的就是它)。 */
|
|
2016
|
+
if (btn.insertBefore) btn.insertBefore(glyph(spec.glyph || ''), btn.firstChild)
|
|
1985
2017
|
// 菜单自己吞掉 pointerdown,否则会被下面那个"点外面就关"的监听判成外部点击
|
|
1986
2018
|
btn.addEventListener('pointerdown', (ev) => ev.stopPropagation())
|
|
1987
2019
|
btn.addEventListener('click', (ev) => {
|
|
@@ -2007,6 +2039,7 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
2007
2039
|
btn.dataset.switchKey = spec.key
|
|
2008
2040
|
const label = document.createElement('span')
|
|
2009
2041
|
label.textContent = spec.label
|
|
2042
|
+
if (btn.insertBefore && glyph) btn.insertBefore(glyph(spec.glyph || ''), btn.firstChild)
|
|
2010
2043
|
const toggle = document.createElement('span')
|
|
2011
2044
|
toggle.className = 'wisp-switch'
|
|
2012
2045
|
toggle.dataset.on = on ? 'true' : 'false'
|
|
@@ -2109,6 +2142,16 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
2109
2142
|
b.setAttribute('tabindex', '-1')
|
|
2110
2143
|
b.dataset.corner = corner.label
|
|
2111
2144
|
b.textContent = corner.label
|
|
2145
|
+
/* 方位是空间的,就给它一个方位字形 —— 键帽上写着 ↖ 比写着"左上角"快得多。 */
|
|
2146
|
+
const arrows = { '左上角': '↖', '右上角': '↗', '左下角': '↙', '右下角': '↘' }
|
|
2147
|
+
const arrow = arrows[corner.label]
|
|
2148
|
+
if (arrow && b.insertBefore) {
|
|
2149
|
+
const g = document.createElement('span')
|
|
2150
|
+
g.className = 'wisp-corner-glyph'
|
|
2151
|
+
g.setAttribute('aria-hidden', 'true')
|
|
2152
|
+
g.textContent = arrow
|
|
2153
|
+
b.insertBefore(g, b.firstChild)
|
|
2154
|
+
}
|
|
2112
2155
|
if (corner.label === currentCorner()) b.dataset.current = 'true'
|
|
2113
2156
|
b.addEventListener('pointerdown', (ev) => ev.stopPropagation())
|
|
2114
2157
|
b.addEventListener('click', (ev) => {
|
|
@@ -2139,7 +2182,7 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
2139
2182
|
延迟是 hover intent:鼠标只是路过一行,不该弹出东西。 */
|
|
2140
2183
|
const HOVER_OPEN_MS = 140
|
|
2141
2184
|
const HOVER_CLOSE_MS = 260
|
|
2142
|
-
const SUBMENU_W =
|
|
2185
|
+
const SUBMENU_W = 150
|
|
2143
2186
|
let flyoutName = ''
|
|
2144
2187
|
let flyoutEl = null
|
|
2145
2188
|
let flyoutRow = null
|
|
@@ -2526,10 +2569,10 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
2526
2569
|
{ kind: 'sep' },
|
|
2527
2570
|
/* 专注:最常用的动作,放最上面;进行中时这一行本身就是状态 + 结束 */
|
|
2528
2571
|
...(focusUntil > 0
|
|
2529
|
-
? [{ label: '专注中 · 还剩 ' + Math.max(1, Math.ceil(focusLeftMs() / 60000)) + ' 分钟', run: () => stopFocus(false), accent: true }]
|
|
2572
|
+
? [{ label: '专注中 · 还剩 ' + Math.max(1, Math.ceil(focusLeftMs() / 60000)) + ' 分钟', run: () => stopFocus(false), accent: true, glyph: '⏱' }]
|
|
2530
2573
|
: [
|
|
2531
|
-
{ label: '专注 ' + cfg.focusMinutes + ' 分钟', run: () => startFocus(cfg.focusMinutes), accent: true },
|
|
2532
|
-
{ label: '专注 45 分钟', run: () => startFocus(45) },
|
|
2574
|
+
{ label: '专注 ' + cfg.focusMinutes + ' 分钟', run: () => startFocus(cfg.focusMinutes), accent: true, glyph: '⏱' },
|
|
2575
|
+
{ label: '专注 45 分钟', run: () => startFocus(45), glyph: '⏱' },
|
|
2533
2576
|
]),
|
|
2534
2577
|
{ kind: 'sep' },
|
|
2535
2578
|
{
|
|
@@ -2544,16 +2587,14 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
2544
2587
|
say('换成' + skinLabel(id) + '了。')
|
|
2545
2588
|
},
|
|
2546
2589
|
})),
|
|
2547
|
-
{ label: '放大一点', run: () => applyConfig({ size: cfg.size + 0.5 }) },
|
|
2548
|
-
{ label: '缩小一点', run: () => applyConfig({ size: cfg.size - 0.5 }) },
|
|
2549
2590
|
],
|
|
2550
2591
|
},
|
|
2551
2592
|
{
|
|
2552
2593
|
group: '行为',
|
|
2553
2594
|
summary: onToggleCount() + '/' + TOGGLES.length + ' 开',
|
|
2554
2595
|
children: [
|
|
2555
|
-
{ label: '说句话', run: () => { wake(true); say(pick(LINES.click)) } },
|
|
2556
|
-
{ label: '睡一会儿', run: () => setMood('sleep') },
|
|
2596
|
+
{ label: '说句话', glyph: '✦', run: () => { wake(true); say(pick(LINES.click)) } },
|
|
2597
|
+
{ label: '睡一会儿', glyph: '☾', run: () => setMood('sleep') },
|
|
2557
2598
|
/* 开关是**真正的开关行**:带 aria-checked,点完菜单不关(就在原地更新) */
|
|
2558
2599
|
...TOGGLES.map((t) => ({ kind: 'switch', key: t.key, label: t.label })),
|
|
2559
2600
|
],
|
|
@@ -2565,11 +2606,11 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
2565
2606
|
},
|
|
2566
2607
|
{ kind: 'slider' },
|
|
2567
2608
|
{ kind: 'sep' },
|
|
2568
|
-
{ label: '动作一览…', run: () => actionsDialog() },
|
|
2569
|
-
{ label: '关于她…', run: () => aboutDialog() },
|
|
2609
|
+
{ label: '动作一览…', glyph: '▤', run: () => actionsDialog() },
|
|
2610
|
+
{ label: '关于她…', glyph: 'ⓘ', run: () => aboutDialog() },
|
|
2570
2611
|
{ kind: 'sep' },
|
|
2571
2612
|
/* 一个开关,不是一个单向动作:从迷你按钮右键打开菜单时,它必须能把她叫回来。 */
|
|
2572
|
-
{ label: hidden ? '回来吧' : '先躲起来', run: () => setHidden(!hidden), muted: true },
|
|
2613
|
+
{ label: hidden ? '回来吧' : '先躲起来', glyph: '⤓', run: () => setHidden(!hidden), muted: true },
|
|
2573
2614
|
{ kind: 'hint' },
|
|
2574
2615
|
]
|
|
2575
2616
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-wisp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.34.1",
|
|
4
4
|
"description": "DeepSeek娘 — an unofficial floating desktop companion for the DeepSeek Harness UI. Zero dependencies; sprites inlined as data URIs; schedules on the Client timer service instead of the trapped browser timer globals.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
package/tools/smoke.mjs
CHANGED
|
@@ -286,16 +286,30 @@ check(kbd.focusAfter === 'wisp-body', 'and focus lands back on her, not on <body
|
|
|
286
286
|
const openedAgain = await openMenu()
|
|
287
287
|
check(openedAgain !== null, 'the menu reopens for the activation check')
|
|
288
288
|
const zoomed = await page.evaluate(() => {
|
|
289
|
-
/*
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
289
|
+
/* 大小已经是菜单里的**滑块**了("放大一点/缩小一点"两条旧条目已删 —— 与滑块重复)。
|
|
290
|
+
真浏览器里直接聚焦滑块再按 →,走的就是键盘用户那条路。 */
|
|
291
|
+
const slider = document.querySelector('.wisp-slider')
|
|
292
|
+
if (!slider) return { missing: true }
|
|
293
|
+
const before = window.__wisp.config.size
|
|
294
|
+
/* 用**真实键盘**走到滑块:Home 回到第一项,再一路 ↓ 直到焦点落在滑块上。
|
|
295
|
+
不能直接 .focus() —— 插件用的是 roving tabindex,直接聚焦不会更新它内部的焦点索引,
|
|
296
|
+
于是 → 键到了插件那儿会被当成"焦点不在滑块上",什么都不做(踩过)。 */
|
|
297
|
+
window.dispatchEvent(new KeyboardEvent('keydown', { key: 'Home' }))
|
|
298
|
+
for (let i = 0; i < 16 && document.activeElement !== slider; i++) {
|
|
299
|
+
window.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }))
|
|
300
|
+
}
|
|
301
|
+
window.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight' }))
|
|
302
|
+
window.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight' }))
|
|
303
|
+
return {
|
|
304
|
+
before,
|
|
305
|
+
after: window.__wisp.config.size,
|
|
306
|
+
width: document.querySelector('.wisp-root').style.width,
|
|
307
|
+
menuGone: document.querySelector('.wisp-menu') === null,
|
|
308
|
+
}
|
|
296
309
|
})
|
|
297
|
-
check(zoomed.
|
|
298
|
-
|
|
310
|
+
check(zoomed.after === zoomed.before + 0.5 && zoomed.menuGone === false,
|
|
311
|
+
'the size slider changes her size and keeps the menu open',
|
|
312
|
+
zoomed.missing ? 'the slider was not found' : `${zoomed.before} -> ${zoomed.after}, width=${zoomed.width}, menuGone=${zoomed.menuGone}`)
|
|
299
313
|
|
|
300
314
|
/* 两个弹窗在真浏览器里的样子 —— 可见性、图片真的解码出来,这些假 DOM 测不到。
|
|
301
315
|
注意位置:必须放在键盘那一段**之后**。放前面会扰动它依赖的焦点状态
|
|
@@ -626,6 +640,42 @@ for (const which of ['br', 'bl', 'tr', 'tl']) {
|
|
|
626
640
|
}, which)
|
|
627
641
|
cornerGeo.push(geo)
|
|
628
642
|
}
|
|
643
|
+
/* 可选截图:WISP_SHOT=<路径> 时把"菜单 + 展开的子面板"拍下来。
|
|
644
|
+
样式改动光看代码看不出来,拍一张最直接。默认不拍,不影响正常冒烟。 */
|
|
645
|
+
if (process.env.WISP_SHOT) {
|
|
646
|
+
try {
|
|
647
|
+
await page.evaluate(async () => {
|
|
648
|
+
const wisp = window.__wisp
|
|
649
|
+
const box = document.querySelector(".wisp-root")
|
|
650
|
+
const bw = parseFloat(box.style.width) || 560
|
|
651
|
+
const bh = parseFloat(box.style.height) || 840
|
|
652
|
+
wisp.configure({ right: 26, bottom: 46 })
|
|
653
|
+
wisp.resetPosition()
|
|
654
|
+
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
655
|
+
const body = document.querySelector(".wisp-body")
|
|
656
|
+
body.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter", bubbles: true, cancelable: true }))
|
|
657
|
+
const menu = document.querySelector(".wisp-menu")
|
|
658
|
+
const row = [...menu.querySelectorAll(".wisp-menu-item")].find((el) => el.dataset && el.dataset.group)
|
|
659
|
+
row.dispatchEvent(new PointerEvent("pointerenter", { pointerType: "mouse" }))
|
|
660
|
+
await new Promise((resolve) => setTimeout(resolve, 420))
|
|
661
|
+
void bw; void bh
|
|
662
|
+
})
|
|
663
|
+
const clipBox = await page.evaluate(() => {
|
|
664
|
+
const els = [".wisp-menu", ".wisp-submenu"].map((sel) => document.querySelector(sel)).filter(Boolean)
|
|
665
|
+
const rects = els.map((el) => el.getBoundingClientRect())
|
|
666
|
+
const left = Math.min(...rects.map((r) => r.left)) - 22
|
|
667
|
+
const top = Math.min(...rects.map((r) => r.top)) - 22
|
|
668
|
+
const right = Math.max(...rects.map((r) => r.right)) + 22
|
|
669
|
+
const bottom = Math.max(...rects.map((r) => r.bottom)) + 22
|
|
670
|
+
return { x: Math.max(0, left), y: Math.max(0, top), width: right - left, height: bottom - top }
|
|
671
|
+
})
|
|
672
|
+
await page.screenshot({ path: process.env.WISP_SHOT, clip: clipBox })
|
|
673
|
+
console.log(" screenshot: " + process.env.WISP_SHOT)
|
|
674
|
+
} catch (error) {
|
|
675
|
+
console.log(" screenshot failed: " + String(error && error.message ? error.message : error))
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
629
679
|
const withPanel = cornerGeo.filter((g) => g && g.panel)
|
|
630
680
|
check(withPanel.length === 4, 'the submenu opens from every corner',
|
|
631
681
|
cornerGeo.map((g) => g.which + (g.panel ? '' : '✗')).join(' '))
|