dsh-notify-yimit 0.1.3 → 0.1.4
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/LICENSE +21 -0
- package/README.en.md +97 -97
- package/README.md +96 -96
- package/lib/client.js +269 -28
- package/lib/index.js +137 -12
- package/lib/toast-host.ps1 +213 -5
- package/lib/typert.host.js +34 -1
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -40,7 +40,10 @@ window.__ModuleLoader__.load({
|
|
|
40
40
|
'.dn-seg{display:flex;gap:2px;padding:2px;border-radius:10px;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);max-width:520px}',
|
|
41
41
|
'.dn-seg-btn{flex:1;font:inherit;font-size:13px;color:var(--dsw-alias-label-secondary);border:0;border-radius:8px;padding:6px 10px;cursor:pointer;background:transparent;transition:background .15s ease,color .15s ease;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}',
|
|
42
42
|
'.dn-seg-btn:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}',
|
|
43
|
-
|
|
43
|
+
// 选中态用主题主色 + 反色文字:浅色模式下与背景对比也足够清晰(此前浅色下
|
|
44
|
+
// 白底+深灰与容器太接近,深色正常)。
|
|
45
|
+
'.dn-seg-btn[data-on="true"]{background:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-label-primary-inverted);box-shadow:var(--dsw-shadow-lv1)}',
|
|
46
|
+
'.dn-seg-btn[data-on="true"]:hover{background:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-label-primary-inverted)}',
|
|
44
47
|
'.dn-seg-btn:disabled{opacity:.45;cursor:not-allowed}',
|
|
45
48
|
'.dn-seg-btn:disabled:hover{background:transparent;color:var(--dsw-alias-label-secondary)}',
|
|
46
49
|
'.dn-collapse{display:grid;grid-template-rows:1fr;opacity:1;transition:grid-template-rows .24s ease,opacity .2s ease}',
|
|
@@ -49,14 +52,28 @@ window.__ModuleLoader__.load({
|
|
|
49
52
|
'.dn-collapse>.dn-collapse-body>.dn-block{min-width:0}',
|
|
50
53
|
'.dn-kind-list{display:flex;flex-direction:column;gap:8px}',
|
|
51
54
|
'.dn-kind-row{display:flex;align-items:center;gap:12px;padding:8px 12px;border:1px solid var(--dsw-alias-border-l1);border-radius:10px;background:var(--dsw-alias-bg-layer-1)}',
|
|
52
|
-
|
|
53
|
-
'.dn-kind-
|
|
55
|
+
// 每类型的背景/文字/开关项右对齐(预览块留在左侧)
|
|
56
|
+
'.dn-kind-row>.dn-kind-field:first-of-type{margin-left:auto}',
|
|
57
|
+
// 样式预览:纯色块内嵌类型文字,始终用配置的背景/文字色(内联样式,不受主题影响)
|
|
58
|
+
'.dn-kind-preview{flex:none;min-width:72px;text-align:center;font-size:12px;font-weight:600;line-height:22px;padding:2px 10px;border-radius:6px;white-space:nowrap;box-shadow:inset 0 0 0 1px rgba(0,0,0,.08)}',
|
|
54
59
|
'.dn-kind-field{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--dsw-alias-label-tertiary);flex:none}',
|
|
55
60
|
'.dn-input{font:inherit;font-size:13px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l1);border-radius:8px;padding:5px 10px;outline:none;max-width:120px}',
|
|
56
61
|
'.dn-input:focus{border-color:var(--dsw-alias-state-business-primary)}',
|
|
57
62
|
'.dn-input[type=color]{padding:2px 4px;width:48px;height:28px;cursor:pointer;max-width:none}',
|
|
58
63
|
'.dn-input:disabled{opacity:.45;cursor:not-allowed}',
|
|
59
|
-
|
|
64
|
+
// 浏览器选择器:原生 select 的展开列表为系统绘制、无法贴合主题,改为自绘下拉,
|
|
65
|
+
// 触发器与弹层都走 --dsw-* 变量,深浅色模式下与设置页风格一致。
|
|
66
|
+
'.dn-bselect{position:relative;width:280px;max-width:100%;flex:none}',
|
|
67
|
+
'.dn-bselect-trigger{display:flex;align-items:center;justify-content:space-between;gap:8px;width:100%;font:inherit;font-size:13px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l1);border-radius:8px;padding:5px 10px;cursor:pointer;outline:none}',
|
|
68
|
+
'.dn-bselect-trigger:hover:not(:disabled){border-color:var(--dsw-alias-border-l2)}',
|
|
69
|
+
'.dn-bselect-trigger.open{border-color:var(--dsw-alias-state-business-primary)}',
|
|
70
|
+
'.dn-bselect-trigger:disabled{opacity:.45;cursor:not-allowed}',
|
|
71
|
+
'.dn-bselect-value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
|
|
72
|
+
'.dn-bselect-arrow{flex:none;font-size:10px;color:var(--dsw-alias-label-tertiary)}',
|
|
73
|
+
'.dn-bselect-pop{position:absolute;top:calc(100% + 4px);left:0;right:0;z-index:100;background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l1);border-radius:10px;box-shadow:var(--dsw-shadow-lv3);padding:4px;max-height:240px;overflow:auto}',
|
|
74
|
+
'.dn-bselect-opt{display:block;width:100%;text-align:left;font:inherit;font-size:13px;color:var(--dsw-alias-label-primary);background:transparent;border:0;border-radius:7px;padding:6px 10px;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}',
|
|
75
|
+
'.dn-bselect-opt:hover{background:var(--dsw-alias-interactive-bg-hover)}',
|
|
76
|
+
'.dn-bselect-opt.sel{color:var(--dsw-alias-state-business-primary);font-weight:600}',
|
|
60
77
|
'.dn-color-preview{font-size:12px;color:var(--dsw-alias-label-tertiary);font-family:var(--dsw-font-mono)}',
|
|
61
78
|
'.dn-btn{font:inherit;font-size:13px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-button-elevated-fill);border:1px solid var(--dsw-alias-border-l1);border-radius:8px;padding:6px 14px;cursor:pointer}',
|
|
62
79
|
'.dn-btn:hover{background:var(--dsw-alias-interactive-bg-hover)}',
|
|
@@ -120,6 +137,12 @@ window.__ModuleLoader__.load({
|
|
|
120
137
|
permissionDefault: '系统通知需要浏览器通知权限',
|
|
121
138
|
requestPermission: '申请通知权限',
|
|
122
139
|
triggerNote: '触发场景:任务完成、任务出错、运行中(实时内容)、等待审批、等待回答。通知标题为对话标题;点击通知或跳转按钮可打开对应会话。',
|
|
140
|
+
toastStyleTitle: '通知窗口样式',
|
|
141
|
+
toastGapLabel: '通知间距(px)',
|
|
142
|
+
enterEdit: '进入编辑模式',
|
|
143
|
+
endEdit: '完成编辑',
|
|
144
|
+
editHint: '拖动样板窗调整位置;拖拽窗口边缘调整宽度;完成后点击「完成编辑」保存。',
|
|
145
|
+
resetPosition: '恢复默认位置',
|
|
123
146
|
saveHint: '所有设置修改后自动保存。',
|
|
124
147
|
saved: '已保存',
|
|
125
148
|
saveFailed: '保存失败:{message}',
|
|
@@ -160,6 +183,12 @@ window.__ModuleLoader__.load({
|
|
|
160
183
|
permissionDefault: 'System notifications need browser notification permission',
|
|
161
184
|
requestPermission: 'Request permission',
|
|
162
185
|
triggerNote: 'Triggers: task completed, task failed, running (live activity), awaiting approval, awaiting answer. The title is the conversation title; clicking the notification or the jump button opens the session.',
|
|
186
|
+
toastStyleTitle: 'Toast window style',
|
|
187
|
+
toastGapLabel: 'Toast gap (px)',
|
|
188
|
+
enterEdit: 'Enter edit mode',
|
|
189
|
+
endEdit: 'Finish editing',
|
|
190
|
+
editHint: 'Drag the sample window to reposition; drag its edges to resize the width; click "Finish editing" to save.',
|
|
191
|
+
resetPosition: 'Reset to default position',
|
|
163
192
|
saveHint: 'All settings changes are auto-saved.',
|
|
164
193
|
saved: 'Saved',
|
|
165
194
|
saveFailed: 'Save failed: {message}',
|
|
@@ -283,7 +312,8 @@ window.__ModuleLoader__.load({
|
|
|
283
312
|
.colors) ? v.colors[key] : undefined
|
|
284
313
|
return {
|
|
285
314
|
bg: typeof entry?.bg === 'string' ? entry.bg : fallbackBg,
|
|
286
|
-
fg: typeof entry?.fg === 'string' ? entry.fg : fallbackFg
|
|
315
|
+
fg: typeof entry?.fg === 'string' ? entry.fg : fallbackFg,
|
|
316
|
+
enabled: entry?.enabled !== false
|
|
287
317
|
}
|
|
288
318
|
}
|
|
289
319
|
const topBg = typeof v.bgColor === 'string' ? v.bgColor : '#20272f'
|
|
@@ -297,6 +327,15 @@ window.__ModuleLoader__.load({
|
|
|
297
327
|
bgColor: topBg,
|
|
298
328
|
textColor: topFg,
|
|
299
329
|
locale: v.locale === 'zh' || v.locale === 'en' ? v.locale : 'en',
|
|
330
|
+
toast: {
|
|
331
|
+
width: typeof v.toast?.width === 'number' ? v.toast.width : 340,
|
|
332
|
+
gap: typeof v.toast?.gap === 'number' ? v.toast.gap : 12,
|
|
333
|
+
position: v.toast !== null && typeof v.toast === 'object' && v.toast.position !== null &&
|
|
334
|
+
typeof v.toast.position === 'object' && !Array.isArray(v.toast.position) &&
|
|
335
|
+
typeof v.toast.position.left === 'number' && typeof v.toast.position.top === 'number'
|
|
336
|
+
? { left: v.toast.position.left, top: v.toast.position.top }
|
|
337
|
+
: null,
|
|
338
|
+
},
|
|
300
339
|
colors: {
|
|
301
340
|
completed: kindColors('completed', topBg, topFg),
|
|
302
341
|
error: kindColors('error', topBg, topFg),
|
|
@@ -433,6 +472,34 @@ window.__ModuleLoader__.load({
|
|
|
433
472
|
typeSymbol: 'dsh-notify-yimit#BrowserList',
|
|
434
473
|
schema: browsersCodec
|
|
435
474
|
}
|
|
475
|
+
}, {
|
|
476
|
+
id: 'dsh-notify-yimit#notify/startEditMode',
|
|
477
|
+
service: 'notify',
|
|
478
|
+
namespace: 'notify',
|
|
479
|
+
method: 'startEditMode',
|
|
480
|
+
invocation: {
|
|
481
|
+
kind: 'direct'
|
|
482
|
+
},
|
|
483
|
+
parameters: [],
|
|
484
|
+
result: {
|
|
485
|
+
mode: 'strict',
|
|
486
|
+
typeSymbol: 'dsh-notify-yimit#AckResult',
|
|
487
|
+
schema: ackCodec
|
|
488
|
+
}
|
|
489
|
+
}, {
|
|
490
|
+
id: 'dsh-notify-yimit#notify/endEditMode',
|
|
491
|
+
service: 'notify',
|
|
492
|
+
namespace: 'notify',
|
|
493
|
+
method: 'endEditMode',
|
|
494
|
+
invocation: {
|
|
495
|
+
kind: 'direct'
|
|
496
|
+
},
|
|
497
|
+
parameters: [],
|
|
498
|
+
result: {
|
|
499
|
+
mode: 'strict',
|
|
500
|
+
typeSymbol: 'dsh-notify-yimit#AckResult',
|
|
501
|
+
schema: ackCodec
|
|
502
|
+
}
|
|
436
503
|
}]
|
|
437
504
|
}
|
|
438
505
|
|
|
@@ -585,6 +652,81 @@ window.__ModuleLoader__.load({
|
|
|
585
652
|
}, entry[1])))
|
|
586
653
|
}
|
|
587
654
|
|
|
655
|
+
/** 跳转浏览器选择器:原生 select 的展开列表无法贴合主题,自绘下拉(触发器 + 弹层)。 */
|
|
656
|
+
function BrowserSelect(props) {
|
|
657
|
+
const {
|
|
658
|
+
value,
|
|
659
|
+
options,
|
|
660
|
+
disabled,
|
|
661
|
+
onChange,
|
|
662
|
+
t
|
|
663
|
+
} = props
|
|
664
|
+
const [open, setOpen] = useState(false)
|
|
665
|
+
const wrapRef = useRef(null)
|
|
666
|
+
// 点击外部 / Esc 关闭弹层。
|
|
667
|
+
useEffect(() => {
|
|
668
|
+
if (!open) return
|
|
669
|
+
const onDown = (e) => {
|
|
670
|
+
if (wrapRef.current !== null && !wrapRef.current.contains(e.target)) setOpen(false)
|
|
671
|
+
}
|
|
672
|
+
const onKey = (e) => {
|
|
673
|
+
if (e.key === 'Escape') setOpen(false)
|
|
674
|
+
}
|
|
675
|
+
document.addEventListener('mousedown', onDown)
|
|
676
|
+
document.addEventListener('keydown', onKey)
|
|
677
|
+
return () => {
|
|
678
|
+
document.removeEventListener('mousedown', onDown)
|
|
679
|
+
document.removeEventListener('keydown', onKey)
|
|
680
|
+
}
|
|
681
|
+
}, [open])
|
|
682
|
+
|
|
683
|
+
const current = options.find((b) => b.path === value)
|
|
684
|
+
const pick = (path) => {
|
|
685
|
+
onChange(path)
|
|
686
|
+
setOpen(false)
|
|
687
|
+
}
|
|
688
|
+
return el('div', {
|
|
689
|
+
className: 'dn-bselect',
|
|
690
|
+
ref: wrapRef
|
|
691
|
+
},
|
|
692
|
+
el('button', {
|
|
693
|
+
type: 'button',
|
|
694
|
+
className: 'dn-bselect-trigger' + (open ? ' open' : ''),
|
|
695
|
+
disabled: disabled === true,
|
|
696
|
+
'aria-haspopup': 'listbox',
|
|
697
|
+
'aria-expanded': String(open),
|
|
698
|
+
onClick: () => {
|
|
699
|
+
if (disabled !== true) setOpen(!open)
|
|
700
|
+
},
|
|
701
|
+
},
|
|
702
|
+
el('span', {
|
|
703
|
+
className: 'dn-bselect-value'
|
|
704
|
+
}, current !== undefined ? current.name : t('browserDefault')),
|
|
705
|
+
el('span', {
|
|
706
|
+
className: 'dn-bselect-arrow'
|
|
707
|
+
}, open ? '▴' : '▾')),
|
|
708
|
+
open ? el('div', {
|
|
709
|
+
className: 'dn-bselect-pop',
|
|
710
|
+
role: 'listbox'
|
|
711
|
+
},
|
|
712
|
+
el('button', {
|
|
713
|
+
type: 'button',
|
|
714
|
+
role: 'option',
|
|
715
|
+
'aria-selected': String(value === ''),
|
|
716
|
+
className: 'dn-bselect-opt' + (value === '' ? ' sel' : ''),
|
|
717
|
+
onClick: () => pick(''),
|
|
718
|
+
}, t('browserDefault')),
|
|
719
|
+
options.map((b) => el('button', {
|
|
720
|
+
type: 'button',
|
|
721
|
+
role: 'option',
|
|
722
|
+
key: b.id,
|
|
723
|
+
'aria-selected': String(value === b.path),
|
|
724
|
+
className: 'dn-bselect-opt' + (value === b.path ? ' sel' : ''),
|
|
725
|
+
onClick: () => pick(b.path),
|
|
726
|
+
}, b.name)))
|
|
727
|
+
: null)
|
|
728
|
+
}
|
|
729
|
+
|
|
588
730
|
const KIND_ORDER = ['completed', 'error', 'running', 'approval', 'question']
|
|
589
731
|
|
|
590
732
|
function NotifySection(props) {
|
|
@@ -599,6 +741,15 @@ window.__ModuleLoader__.load({
|
|
|
599
741
|
const [saving, setSaving] = useState(false)
|
|
600
742
|
const [permission, setPermission] = useState(typeof Notification !== 'undefined' ? Notification
|
|
601
743
|
.permission : 'unsupported')
|
|
744
|
+
// 通知窗口编辑模式(样板窗拖拽/缩放)
|
|
745
|
+
const [editing, setEditing] = useState(false)
|
|
746
|
+
// 最新值镜像:卸载清理时读取(避免空依赖闭包拿到陈旧值)。
|
|
747
|
+
const editingRef = useRef(false)
|
|
748
|
+
editingRef.current = editing
|
|
749
|
+
useEffect(() => () => {
|
|
750
|
+
// 离开通知设置页(组件卸载)时若仍在编辑模式,自动关闭样板窗。
|
|
751
|
+
if (editingRef.current) void api.endEditMode()
|
|
752
|
+
}, [])
|
|
602
753
|
|
|
603
754
|
const saveTimerRef = useRef(null)
|
|
604
755
|
const save = useCallback((patch) => {
|
|
@@ -653,18 +804,18 @@ window.__ModuleLoader__.load({
|
|
|
653
804
|
const entry = config.colors?. [kind] ?? {}
|
|
654
805
|
const bg = entry.bg || config.bgColor || '#20272f'
|
|
655
806
|
const fg = entry.fg || config.textColor || '#e6edf3'
|
|
807
|
+
const enabled = entry.enabled !== false
|
|
656
808
|
return el('div', {
|
|
657
809
|
key: kind,
|
|
658
810
|
className: 'dn-kind-row'
|
|
659
811
|
},
|
|
812
|
+
// 样式预览:类型文字显示在配置色的纯色块内(内联样式,不受深色模式影响)
|
|
660
813
|
el('span', {
|
|
661
|
-
className: 'dn-kind-
|
|
814
|
+
className: 'dn-kind-preview',
|
|
662
815
|
style: {
|
|
663
|
-
background: bg
|
|
816
|
+
background: bg,
|
|
817
|
+
color: fg
|
|
664
818
|
}
|
|
665
|
-
}),
|
|
666
|
-
el('span', {
|
|
667
|
-
className: 'dn-kind-name'
|
|
668
819
|
}, kindLabel(kind, t)),
|
|
669
820
|
el('label', {
|
|
670
821
|
className: 'dn-kind-field'
|
|
@@ -701,7 +852,28 @@ window.__ModuleLoader__.load({
|
|
|
701
852
|
}
|
|
702
853
|
}
|
|
703
854
|
})
|
|
704
|
-
}), t('colorFg'))
|
|
855
|
+
}), t('colorFg')),
|
|
856
|
+
// 每类型开关:控制该类型通知是否产生(默认全开)
|
|
857
|
+
el('label', {
|
|
858
|
+
className: 'dn-kind-field'
|
|
859
|
+
},
|
|
860
|
+
el('button', {
|
|
861
|
+
type: 'button',
|
|
862
|
+
role: 'switch',
|
|
863
|
+
'aria-checked': enabled,
|
|
864
|
+
'aria-label': kindLabel(kind, t),
|
|
865
|
+
className: 'dn-switch',
|
|
866
|
+
'data-on': String(enabled),
|
|
867
|
+
disabled: controlsDisabled,
|
|
868
|
+
onClick: () => save({
|
|
869
|
+
colors: {
|
|
870
|
+
[kind]: {
|
|
871
|
+
...entry,
|
|
872
|
+
enabled: !enabled
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
})
|
|
876
|
+
})))
|
|
705
877
|
})
|
|
706
878
|
|
|
707
879
|
const customOpen = config.mode === 'custom'
|
|
@@ -764,21 +936,15 @@ window.__ModuleLoader__.load({
|
|
|
764
936
|
el('span', {
|
|
765
937
|
className: 'dn-label'
|
|
766
938
|
}, t('browserLabel')),
|
|
767
|
-
el(
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
value: ''
|
|
777
|
-
}, t('browserDefault')),
|
|
778
|
-
(state?.browsers ?? []).map((b) => el('option', {
|
|
779
|
-
key: b.id,
|
|
780
|
-
value: b.path
|
|
781
|
-
}, b.name)))),
|
|
939
|
+
el(BrowserSelect, {
|
|
940
|
+
value: config.browser || '',
|
|
941
|
+
options: state?.browsers ?? [],
|
|
942
|
+
disabled: controlsDisabled,
|
|
943
|
+
onChange: (path) => save({
|
|
944
|
+
browser: path
|
|
945
|
+
}),
|
|
946
|
+
t
|
|
947
|
+
})),
|
|
782
948
|
el('p', {
|
|
783
949
|
className: 'dn-note'
|
|
784
950
|
}, t('browserNote'))
|
|
@@ -868,7 +1034,70 @@ window.__ModuleLoader__.load({
|
|
|
868
1034
|
}, t('colorsTitle')),
|
|
869
1035
|
el('div', {
|
|
870
1036
|
className: 'dn-kind-list'
|
|
871
|
-
}, kindRows)
|
|
1037
|
+
}, kindRows),
|
|
1038
|
+
el('div', {
|
|
1039
|
+
className: 'dn-divider'
|
|
1040
|
+
}),
|
|
1041
|
+
el('p', {
|
|
1042
|
+
className: 'dn-title'
|
|
1043
|
+
}, t('toastStyleTitle')),
|
|
1044
|
+
el('div', {
|
|
1045
|
+
className: 'dn-row'
|
|
1046
|
+
},
|
|
1047
|
+
el('span', {
|
|
1048
|
+
className: 'dn-label'
|
|
1049
|
+
}, t('toastGapLabel')),
|
|
1050
|
+
el('input', {
|
|
1051
|
+
type: 'number',
|
|
1052
|
+
min: 0,
|
|
1053
|
+
max: 60,
|
|
1054
|
+
className: 'dn-input',
|
|
1055
|
+
value: config.toast?.gap ?? 12,
|
|
1056
|
+
disabled: controlsDisabled,
|
|
1057
|
+
onChange: (e) => {
|
|
1058
|
+
const g = Math.max(0, Math.min(60, Math.floor(Number(e
|
|
1059
|
+
.target.value) || 0)))
|
|
1060
|
+
save({
|
|
1061
|
+
toast: {
|
|
1062
|
+
...(config.toast ?? {}),
|
|
1063
|
+
gap: g
|
|
1064
|
+
}
|
|
1065
|
+
})
|
|
1066
|
+
}
|
|
1067
|
+
})),
|
|
1068
|
+
el('div', {
|
|
1069
|
+
className: 'dn-row'
|
|
1070
|
+
},
|
|
1071
|
+
el('button', {
|
|
1072
|
+
className: 'dn-btn primary',
|
|
1073
|
+
disabled: controlsDisabled,
|
|
1074
|
+
onClick: () => {
|
|
1075
|
+
if (editing) {
|
|
1076
|
+
void api.endEditMode()
|
|
1077
|
+
setEditing(false)
|
|
1078
|
+
} else {
|
|
1079
|
+
setEditing(true)
|
|
1080
|
+
void api.startEditMode()
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
}, editing ? t('endEdit') : t('enterEdit')),
|
|
1084
|
+
el('button', {
|
|
1085
|
+
className: 'dn-btn',
|
|
1086
|
+
disabled: controlsDisabled || !config.toast?.position,
|
|
1087
|
+
onClick: () => {
|
|
1088
|
+
save({
|
|
1089
|
+
toast: {
|
|
1090
|
+
...(config.toast ?? {}),
|
|
1091
|
+
position: null
|
|
1092
|
+
}
|
|
1093
|
+
})
|
|
1094
|
+
// 编辑模式下:重开样板窗,让其在默认位置(右下)刷新显示。
|
|
1095
|
+
if (editing) void api.startEditMode()
|
|
1096
|
+
}
|
|
1097
|
+
}, t('resetPosition'))),
|
|
1098
|
+
el('p', {
|
|
1099
|
+
className: 'dn-note'
|
|
1100
|
+
}, t('editHint')))
|
|
872
1101
|
)),
|
|
873
1102
|
el('div', {
|
|
874
1103
|
className: 'dn-divider'
|
|
@@ -965,6 +1194,18 @@ window.__ModuleLoader__.load({
|
|
|
965
1194
|
} catch {
|
|
966
1195
|
/* 静默 */ }
|
|
967
1196
|
},
|
|
1197
|
+
startEditMode: async () => {
|
|
1198
|
+
try {
|
|
1199
|
+
await notify.startEditMode()
|
|
1200
|
+
} catch {
|
|
1201
|
+
/* 静默 */ }
|
|
1202
|
+
},
|
|
1203
|
+
endEditMode: async () => {
|
|
1204
|
+
try {
|
|
1205
|
+
await notify.endEditMode()
|
|
1206
|
+
} catch {
|
|
1207
|
+
/* 静默 */ }
|
|
1208
|
+
},
|
|
968
1209
|
}
|
|
969
1210
|
|
|
970
1211
|
// ===== 现在设置 __localeService 并使用 api =====
|
|
@@ -1125,4 +1366,4 @@ window.__ModuleLoader__.load({
|
|
|
1125
1366
|
exports.inject = inject
|
|
1126
1367
|
return module.exports
|
|
1127
1368
|
},
|
|
1128
|
-
})
|
|
1369
|
+
})
|
package/lib/index.js
CHANGED
|
@@ -30,26 +30,38 @@ const DEFAULT_CONFIG = Object.freeze({
|
|
|
30
30
|
bgColor: '#20272f',
|
|
31
31
|
textColor: '#e6edf3',
|
|
32
32
|
locale: 'en',
|
|
33
|
+
// 通知窗口样式:width 窗口宽度(编辑模式拖拽边缘调整);gap 通知间距(px);
|
|
34
|
+
// position = null 表示默认右下贴边,{left,top} 为任意屏幕位置(工作区相对坐标)。
|
|
35
|
+
toast: {
|
|
36
|
+
width: 340,
|
|
37
|
+
gap: 12,
|
|
38
|
+
position: null,
|
|
39
|
+
},
|
|
33
40
|
colors: {
|
|
34
41
|
completed: {
|
|
35
42
|
bg: '#1f5138',
|
|
36
|
-
fg: '#e6f4ec'
|
|
43
|
+
fg: '#e6f4ec',
|
|
44
|
+
enabled: true
|
|
37
45
|
},
|
|
38
46
|
error: {
|
|
39
47
|
bg: '#5c1f24',
|
|
40
|
-
fg: '#fbe9ea'
|
|
48
|
+
fg: '#fbe9ea',
|
|
49
|
+
enabled: true
|
|
41
50
|
},
|
|
42
51
|
running: {
|
|
43
52
|
bg: '#203a5c',
|
|
44
|
-
fg: '#e8f0fb'
|
|
53
|
+
fg: '#e8f0fb',
|
|
54
|
+
enabled: true
|
|
45
55
|
},
|
|
46
56
|
approval: {
|
|
47
57
|
bg: '#4a3a12',
|
|
48
|
-
fg: '#f7efd8'
|
|
58
|
+
fg: '#f7efd8',
|
|
59
|
+
enabled: true
|
|
49
60
|
},
|
|
50
61
|
question: {
|
|
51
62
|
bg: '#3d2a55',
|
|
52
|
-
fg: '#f0e8fa'
|
|
63
|
+
fg: '#f0e8fa',
|
|
64
|
+
enabled: true
|
|
53
65
|
},
|
|
54
66
|
},
|
|
55
67
|
})
|
|
@@ -99,6 +111,12 @@ const TOAST_LABELS = {
|
|
|
99
111
|
/** 标题未生成时的占位文本(中/英):session/title 到达后原地替换。 */
|
|
100
112
|
const UNTITLED_NAMES = new Set([TOAST_LABELS.zh.unnamed, TOAST_LABELS.en.unnamed])
|
|
101
113
|
|
|
114
|
+
/** 编辑模式样板窗标题(中/英)。 */
|
|
115
|
+
const EDIT_TITLES = {
|
|
116
|
+
zh: '通知样式预览',
|
|
117
|
+
en: 'Notification preview'
|
|
118
|
+
}
|
|
119
|
+
|
|
102
120
|
const HEX_RE = /^#[0-9a-fA-F]{3,8}$/
|
|
103
121
|
const isHex = (v) => typeof v === 'string' && HEX_RE.test(v)
|
|
104
122
|
|
|
@@ -118,6 +136,22 @@ function normalizeConfig(input) {
|
|
|
118
136
|
if (isHex(src.textColor)) out.textColor = src.textColor
|
|
119
137
|
if (src.locale === 'zh' || src.locale === 'en') out.locale = src.locale
|
|
120
138
|
|
|
139
|
+
// 通知窗口样式:width 260–520;gap 0–60;position 缺省 null(默认右下贴边),合法 {left,top} 才采纳。
|
|
140
|
+
out.toast = { width: 340, gap: 12, position: null }
|
|
141
|
+
const srcToast = src.toast !== null && typeof src.toast === 'object' && !Array.isArray(src.toast) ? src.toast : {}
|
|
142
|
+
if (typeof srcToast.width === 'number' && Number.isFinite(srcToast.width)) {
|
|
143
|
+
out.toast.width = Math.max(260, Math.min(520, Math.round(srcToast.width)))
|
|
144
|
+
}
|
|
145
|
+
if (typeof srcToast.gap === 'number' && Number.isFinite(srcToast.gap)) {
|
|
146
|
+
out.toast.gap = Math.max(0, Math.min(60, Math.round(srcToast.gap)))
|
|
147
|
+
}
|
|
148
|
+
const pos = srcToast.position
|
|
149
|
+
if (pos !== null && typeof pos === 'object' && !Array.isArray(pos) &&
|
|
150
|
+
typeof pos.left === 'number' && Number.isFinite(pos.left) &&
|
|
151
|
+
typeof pos.top === 'number' && Number.isFinite(pos.top)) {
|
|
152
|
+
out.toast.position = { left: Math.round(pos.left), top: Math.round(pos.top) }
|
|
153
|
+
}
|
|
154
|
+
|
|
121
155
|
const srcColors = src.colors !== null && typeof src.colors === 'object' && !Array.isArray(src.colors) ? src.colors :
|
|
122
156
|
{}
|
|
123
157
|
out.colors = {}
|
|
@@ -127,6 +161,7 @@ function normalizeConfig(input) {
|
|
|
127
161
|
out.colors[kind] = {
|
|
128
162
|
bg: isHex(entry?.bg) ? entry.bg : def.bg,
|
|
129
163
|
fg: isHex(entry?.fg) ? entry.fg : def.fg,
|
|
164
|
+
enabled: entry?.enabled !== false,
|
|
130
165
|
}
|
|
131
166
|
}
|
|
132
167
|
return out
|
|
@@ -139,7 +174,8 @@ function migrateLegacyColors(input) {
|
|
|
139
174
|
const colors = {}
|
|
140
175
|
for (const kind of KIND_KEYS) colors[kind] = {
|
|
141
176
|
bg: input.bgColor,
|
|
142
|
-
fg: input.textColor
|
|
177
|
+
fg: input.textColor,
|
|
178
|
+
enabled: true
|
|
143
179
|
}
|
|
144
180
|
return {
|
|
145
181
|
...input,
|
|
@@ -162,6 +198,13 @@ function mergeConfigPatch(base, patch) {
|
|
|
162
198
|
}
|
|
163
199
|
out.colors = next
|
|
164
200
|
}
|
|
201
|
+
if (patch !== null && typeof patch === 'object' && patch.toast !== null && typeof patch.toast === 'object' && !
|
|
202
|
+
Array.isArray(patch.toast)) {
|
|
203
|
+
out.toast = {
|
|
204
|
+
...(base.toast !== null && typeof base.toast === 'object' && !Array.isArray(base.toast) ? base.toast : {}),
|
|
205
|
+
...patch.toast
|
|
206
|
+
}
|
|
207
|
+
}
|
|
165
208
|
return out
|
|
166
209
|
}
|
|
167
210
|
|
|
@@ -223,6 +266,7 @@ function openConfig() {
|
|
|
223
266
|
const MAX_QUEUE = 500
|
|
224
267
|
const RUNNING_THROTTLE_MS = 400
|
|
225
268
|
const KIND_DEBOUNCE_MS = 2000
|
|
269
|
+
/** 通知间距默认值(px);实际使用 config.toast.gap(0–60,可调)。 */
|
|
226
270
|
const TOAST_GAP = 12
|
|
227
271
|
const TEXT_MAX = 300
|
|
228
272
|
|
|
@@ -445,6 +489,21 @@ export function apply(ctx) {
|
|
|
445
489
|
if (idx !== -1) toastOrder.splice(idx, 1)
|
|
446
490
|
reflowToasts()
|
|
447
491
|
}
|
|
492
|
+
} else if (msg.type === 'geometry' && typeof msg.left === 'number' && typeof msg.top === 'number') {
|
|
493
|
+
// 编辑模式:样板窗拖拽/缩放结束 → 写回配置,并应用到在屏浮窗。
|
|
494
|
+
const width = Math.max(260, Math.min(520, Math.round(Number(msg.width) || 340)))
|
|
495
|
+
const left = Math.round(Number(msg.left))
|
|
496
|
+
const top = Math.round(Number(msg.top))
|
|
497
|
+
store.set({
|
|
498
|
+
toast: {
|
|
499
|
+
width,
|
|
500
|
+
position: { left, top }
|
|
501
|
+
}
|
|
502
|
+
})
|
|
503
|
+
for (const rec of activeToasts.values()) {
|
|
504
|
+
sendCmd({ cmd: 'size', key: rec.key, width })
|
|
505
|
+
}
|
|
506
|
+
reflowToasts()
|
|
448
507
|
}
|
|
449
508
|
}
|
|
450
509
|
})
|
|
@@ -507,22 +566,53 @@ export function apply(ctx) {
|
|
|
507
566
|
}
|
|
508
567
|
|
|
509
568
|
function reflowToasts() {
|
|
569
|
+
const cfg = store.get()
|
|
570
|
+
const pos = cfg.toast?.position ?? null
|
|
571
|
+
const gap = typeof cfg.toast?.gap === 'number' ? cfg.toast.gap : TOAST_GAP
|
|
572
|
+
if (pos !== null) {
|
|
573
|
+
// 自定义位置:position.top 是"第一个(最新,最底部)浮窗的顶边",向上堆叠。
|
|
574
|
+
// 与宿主 Loaded 的 targetTop = pos.top - offsetY 语义一致,否则重排会把
|
|
575
|
+
// 浮窗再上移一个自身高度(先正确、随即上移的 bug)。横向同步 position.left,
|
|
576
|
+
// 编辑调整位置后,在屏浮窗立即左右移动到位。
|
|
577
|
+
let prevTop = pos.top
|
|
578
|
+
let first = true
|
|
579
|
+
for (let i = 0; i < toastOrder.length; i += 1) {
|
|
580
|
+
const rec = activeToasts.get(toastOrder[i])
|
|
581
|
+
if (rec === undefined) continue
|
|
582
|
+
const height = rec.meta?.height ?? 130
|
|
583
|
+
const newTop = first ? prevTop : Math.round(prevTop - gap - height)
|
|
584
|
+
first = false
|
|
585
|
+
const needsTop = rec.meta !== null && newTop !== rec.meta.top
|
|
586
|
+
const needsLeft = rec.meta !== null && rec.meta.left !== pos.left
|
|
587
|
+
if (needsTop || needsLeft) {
|
|
588
|
+
const move = { cmd: 'move', key: rec.key, top: newTop }
|
|
589
|
+
if (needsTop) rec.meta.top = newTop
|
|
590
|
+
if (needsLeft) { move.left = pos.left; rec.meta.left = pos.left }
|
|
591
|
+
sendCmd(move)
|
|
592
|
+
}
|
|
593
|
+
prevTop = newTop
|
|
594
|
+
}
|
|
595
|
+
return
|
|
596
|
+
}
|
|
597
|
+
// 默认右下:底边对齐(工作区底边 - 20),向上堆叠;横向回默认贴边(left 'default'),
|
|
598
|
+
// 覆盖"恢复默认位置/改宽度后需回到右下角"的场景。
|
|
510
599
|
if (globalWorkBottom === null) return
|
|
511
|
-
let
|
|
600
|
+
let cursor = globalWorkBottom - 20
|
|
512
601
|
for (let i = 0; i < toastOrder.length; i += 1) {
|
|
513
602
|
const rec = activeToasts.get(toastOrder[i])
|
|
514
603
|
if (rec === undefined) continue
|
|
515
604
|
const height = rec.meta?.height ?? 130
|
|
516
|
-
const newTop = Math.round(
|
|
605
|
+
const newTop = Math.round(cursor - height)
|
|
517
606
|
if (rec.meta !== null && newTop !== rec.meta.top) {
|
|
518
607
|
rec.meta.top = newTop
|
|
519
608
|
sendCmd({
|
|
520
609
|
cmd: 'move',
|
|
521
610
|
key: rec.key,
|
|
522
|
-
top: newTop
|
|
611
|
+
top: newTop,
|
|
612
|
+
left: 'default'
|
|
523
613
|
})
|
|
524
614
|
}
|
|
525
|
-
|
|
615
|
+
cursor = newTop - gap
|
|
526
616
|
}
|
|
527
617
|
}
|
|
528
618
|
|
|
@@ -582,10 +672,11 @@ export function apply(ctx) {
|
|
|
582
672
|
toastOrder.splice(replaceIndex, 0, key)
|
|
583
673
|
}
|
|
584
674
|
const insertIndex = replaceIndex !== -1 ? replaceIndex : 0
|
|
675
|
+
const gap = typeof cfg.toast?.gap === 'number' ? cfg.toast.gap : TOAST_GAP
|
|
585
676
|
let offsetY = 0
|
|
586
677
|
for (let i = 0; i < insertIndex; i += 1) {
|
|
587
678
|
const r = activeToasts.get(toastOrder[i])
|
|
588
|
-
offsetY += (r?.meta?.height ?? 130) +
|
|
679
|
+
offsetY += (r?.meta?.height ?? 130) + gap
|
|
589
680
|
}
|
|
590
681
|
|
|
591
682
|
const lang = cfg.locale === 'zh' ? 'zh' : 'en'
|
|
@@ -608,6 +699,9 @@ export function apply(ctx) {
|
|
|
608
699
|
ignoreLabel: l.ignore,
|
|
609
700
|
jumpLabel: l.jump,
|
|
610
701
|
unnamedLabel: l.unnamed,
|
|
702
|
+
// 通知窗口样式:宽度 + 自定义位置(null = 默认右下贴边)
|
|
703
|
+
width: cfg.toast?.width ?? 340,
|
|
704
|
+
position: cfg.toast?.position ?? null,
|
|
611
705
|
}
|
|
612
706
|
if (typeof cfg.browser === 'string' && cfg.browser.length > 0) payload.browserPath = cfg.browser
|
|
613
707
|
|
|
@@ -673,6 +767,8 @@ export function apply(ctx) {
|
|
|
673
767
|
if (st === undefined) return
|
|
674
768
|
const at = Date.now()
|
|
675
769
|
const cfg = store.get()
|
|
770
|
+
// 每类型开关:该类型被关闭时完全不产生通知(桌面浮窗与系统通知都不发)。
|
|
771
|
+
if (cfg.colors?.[kind]?.enabled === false) return
|
|
676
772
|
const desktop = cfg.enabled === true && cfg.mode === 'custom'
|
|
677
773
|
const lang = cfg.locale === 'zh' ? 'zh' : 'en'
|
|
678
774
|
const l = TOAST_LABELS[lang]
|
|
@@ -933,7 +1029,19 @@ export function apply(ctx) {
|
|
|
933
1029
|
}
|
|
934
1030
|
},
|
|
935
1031
|
updateConfig(patch) {
|
|
936
|
-
|
|
1032
|
+
const prev = store.get()
|
|
1033
|
+
const next = store.set(patch)
|
|
1034
|
+
// toast 几何(位置/宽度/间距)变更:立即应用到在屏浮窗,无需等待下一个浮窗。
|
|
1035
|
+
const pToast = patch !== null && typeof patch === 'object' && !Array.isArray(patch) ? patch.toast : undefined
|
|
1036
|
+
if (pToast !== null && typeof pToast === 'object' && !Array.isArray(pToast)) {
|
|
1037
|
+
if (typeof pToast.width === 'number' && pToast.width !== prev.toast?.width) {
|
|
1038
|
+
for (const rec of activeToasts.values()) {
|
|
1039
|
+
sendCmd({ cmd: 'size', key: rec.key, width: next.toast.width })
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
reflowToasts()
|
|
1043
|
+
}
|
|
1044
|
+
return next
|
|
937
1045
|
},
|
|
938
1046
|
ackEvents(ids) {
|
|
939
1047
|
if (Array.isArray(ids)) {
|
|
@@ -955,6 +1063,23 @@ export function apply(ctx) {
|
|
|
955
1063
|
browsers: detectBrowsers()
|
|
956
1064
|
}
|
|
957
1065
|
},
|
|
1066
|
+
/** 进入通知窗口编辑模式:宿主弹样板窗(可拖拽/缩放),几何回传后写回配置。 */
|
|
1067
|
+
startEditMode() {
|
|
1068
|
+
const cfg = store.get()
|
|
1069
|
+
const lang = cfg.locale === 'zh' ? 'zh' : 'en'
|
|
1070
|
+
sendCmd({
|
|
1071
|
+
cmd: 'edit',
|
|
1072
|
+
title: EDIT_TITLES[lang],
|
|
1073
|
+
width: cfg.toast?.width ?? 340,
|
|
1074
|
+
position: cfg.toast?.position ?? null,
|
|
1075
|
+
})
|
|
1076
|
+
return { ok: true }
|
|
1077
|
+
},
|
|
1078
|
+
/** 退出编辑模式:样板窗淡出消失。 */
|
|
1079
|
+
endEditMode() {
|
|
1080
|
+
sendCmd({ cmd: 'edit-end' })
|
|
1081
|
+
return { ok: true }
|
|
1082
|
+
},
|
|
958
1083
|
}
|
|
959
1084
|
|
|
960
1085
|
Object.defineProperty(service, 'typertRemote', {
|