dsh-notify-yimit 0.1.3 → 0.1.5

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/lib/client.js CHANGED
@@ -40,7 +40,8 @@ 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
- '.dn-seg-btn[data-on="true"]{background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-primary);box-shadow:var(--dsw-shadow-lv1)}',
43
+ '.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)}',
44
+ '.dn-seg-btn[data-on="true"]:hover{background:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-label-primary-inverted)}',
44
45
  '.dn-seg-btn:disabled{opacity:.45;cursor:not-allowed}',
45
46
  '.dn-seg-btn:disabled:hover{background:transparent;color:var(--dsw-alias-label-secondary)}',
46
47
  '.dn-collapse{display:grid;grid-template-rows:1fr;opacity:1;transition:grid-template-rows .24s ease,opacity .2s ease}',
@@ -49,14 +50,24 @@ window.__ModuleLoader__.load({
49
50
  '.dn-collapse>.dn-collapse-body>.dn-block{min-width:0}',
50
51
  '.dn-kind-list{display:flex;flex-direction:column;gap:8px}',
51
52
  '.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
- '.dn-kind-swatch{flex:none;width:20px;height:20px;border-radius:6px;border:1px solid var(--dsw-alias-border-l2);box-shadow:inset 0 0 0 1px rgba(0,0,0,.12)}',
53
- '.dn-kind-name{flex:1;font-size:13px;font-weight:600;min-width:0}',
53
+ '.dn-kind-row>.dn-kind-field:first-of-type{margin-left:auto}',
54
+ '.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
55
  '.dn-kind-field{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--dsw-alias-label-tertiary);flex:none}',
55
56
  '.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
57
  '.dn-input:focus{border-color:var(--dsw-alias-state-business-primary)}',
57
58
  '.dn-input[type=color]{padding:2px 4px;width:48px;height:28px;cursor:pointer;max-width:none}',
58
59
  '.dn-input:disabled{opacity:.45;cursor:not-allowed}',
59
- '.dn-select{max-width:260px;cursor:pointer}',
60
+ '.dn-bselect{position:relative;width:280px;max-width:100%;flex:none}',
61
+ '.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}',
62
+ '.dn-bselect-trigger:hover:not(:disabled){border-color:var(--dsw-alias-border-l2)}',
63
+ '.dn-bselect-trigger.open{border-color:var(--dsw-alias-state-business-primary)}',
64
+ '.dn-bselect-trigger:disabled{opacity:.45;cursor:not-allowed}',
65
+ '.dn-bselect-value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
66
+ '.dn-bselect-arrow{flex:none;font-size:10px;color:var(--dsw-alias-label-tertiary)}',
67
+ '.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}',
68
+ '.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}',
69
+ '.dn-bselect-opt:hover{background:var(--dsw-alias-interactive-bg-hover)}',
70
+ '.dn-bselect-opt.sel{color:var(--dsw-alias-state-business-primary);font-weight:600}',
60
71
  '.dn-color-preview{font-size:12px;color:var(--dsw-alias-label-tertiary);font-family:var(--dsw-font-mono)}',
61
72
  '.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
73
  '.dn-btn:hover{background:var(--dsw-alias-interactive-bg-hover)}',
@@ -120,6 +131,12 @@ window.__ModuleLoader__.load({
120
131
  permissionDefault: '系统通知需要浏览器通知权限',
121
132
  requestPermission: '申请通知权限',
122
133
  triggerNote: '触发场景:任务完成、任务出错、运行中(实时内容)、等待审批、等待回答。通知标题为对话标题;点击通知或跳转按钮可打开对应会话。',
134
+ toastStyleTitle: '通知窗口样式',
135
+ toastGapLabel: '通知间距(px)',
136
+ enterEdit: '进入编辑模式',
137
+ endEdit: '完成编辑',
138
+ editHint: '拖动样板窗调整位置;拖拽窗口边缘调整宽度;完成后点击「完成编辑」保存。',
139
+ resetPosition: '恢复默认位置',
123
140
  saveHint: '所有设置修改后自动保存。',
124
141
  saved: '已保存',
125
142
  saveFailed: '保存失败:{message}',
@@ -160,6 +177,12 @@ window.__ModuleLoader__.load({
160
177
  permissionDefault: 'System notifications need browser notification permission',
161
178
  requestPermission: 'Request permission',
162
179
  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.',
180
+ toastStyleTitle: 'Toast window style',
181
+ toastGapLabel: 'Toast gap (px)',
182
+ enterEdit: 'Enter edit mode',
183
+ endEdit: 'Finish editing',
184
+ editHint: 'Drag the sample window to reposition; drag its edges to resize the width; click "Finish editing" to save.',
185
+ resetPosition: 'Reset to default position',
163
186
  saveHint: 'All settings changes are auto-saved.',
164
187
  saved: 'Saved',
165
188
  saveFailed: 'Save failed: {message}',
@@ -283,7 +306,8 @@ window.__ModuleLoader__.load({
283
306
  .colors) ? v.colors[key] : undefined
284
307
  return {
285
308
  bg: typeof entry?.bg === 'string' ? entry.bg : fallbackBg,
286
- fg: typeof entry?.fg === 'string' ? entry.fg : fallbackFg
309
+ fg: typeof entry?.fg === 'string' ? entry.fg : fallbackFg,
310
+ enabled: entry?.enabled !== false
287
311
  }
288
312
  }
289
313
  const topBg = typeof v.bgColor === 'string' ? v.bgColor : '#20272f'
@@ -297,6 +321,15 @@ window.__ModuleLoader__.load({
297
321
  bgColor: topBg,
298
322
  textColor: topFg,
299
323
  locale: v.locale === 'zh' || v.locale === 'en' ? v.locale : 'en',
324
+ toast: {
325
+ width: typeof v.toast?.width === 'number' ? v.toast.width : 340,
326
+ gap: typeof v.toast?.gap === 'number' ? v.toast.gap : 12,
327
+ position: v.toast !== null && typeof v.toast === 'object' && v.toast.position !== null &&
328
+ typeof v.toast.position === 'object' && !Array.isArray(v.toast.position) &&
329
+ typeof v.toast.position.left === 'number' && typeof v.toast.position.top === 'number'
330
+ ? { left: v.toast.position.left, top: v.toast.position.top }
331
+ : null,
332
+ },
300
333
  colors: {
301
334
  completed: kindColors('completed', topBg, topFg),
302
335
  error: kindColors('error', topBg, topFg),
@@ -433,6 +466,34 @@ window.__ModuleLoader__.load({
433
466
  typeSymbol: 'dsh-notify-yimit#BrowserList',
434
467
  schema: browsersCodec
435
468
  }
469
+ }, {
470
+ id: 'dsh-notify-yimit#notify/startEditMode',
471
+ service: 'notify',
472
+ namespace: 'notify',
473
+ method: 'startEditMode',
474
+ invocation: {
475
+ kind: 'direct'
476
+ },
477
+ parameters: [],
478
+ result: {
479
+ mode: 'strict',
480
+ typeSymbol: 'dsh-notify-yimit#AckResult',
481
+ schema: ackCodec
482
+ }
483
+ }, {
484
+ id: 'dsh-notify-yimit#notify/endEditMode',
485
+ service: 'notify',
486
+ namespace: 'notify',
487
+ method: 'endEditMode',
488
+ invocation: {
489
+ kind: 'direct'
490
+ },
491
+ parameters: [],
492
+ result: {
493
+ mode: 'strict',
494
+ typeSymbol: 'dsh-notify-yimit#AckResult',
495
+ schema: ackCodec
496
+ }
436
497
  }]
437
498
  }
438
499
 
@@ -510,11 +571,8 @@ window.__ModuleLoader__.load({
510
571
  }
511
572
 
512
573
  if (config.mode === 'custom') {
513
- if (typeof Notification === 'undefined' || Notification.permission !==
514
- 'granted') {
515
- ack();
516
- return
517
- }
574
+ ack();
575
+ return
518
576
  } else if (config.mode === 'system') {
519
577
  if (typeof Notification === 'undefined' || Notification.permission ===
520
578
  'denied') {
@@ -585,6 +643,81 @@ window.__ModuleLoader__.load({
585
643
  }, entry[1])))
586
644
  }
587
645
 
646
+ /** 跳转浏览器选择器:原生 select 的展开列表无法贴合主题,自绘下拉(触发器 + 弹层)。 */
647
+ function BrowserSelect(props) {
648
+ const {
649
+ value,
650
+ options,
651
+ disabled,
652
+ onChange,
653
+ t
654
+ } = props
655
+ const [open, setOpen] = useState(false)
656
+ const wrapRef = useRef(null)
657
+ // 点击外部 / Esc 关闭弹层。
658
+ useEffect(() => {
659
+ if (!open) return
660
+ const onDown = (e) => {
661
+ if (wrapRef.current !== null && !wrapRef.current.contains(e.target)) setOpen(false)
662
+ }
663
+ const onKey = (e) => {
664
+ if (e.key === 'Escape') setOpen(false)
665
+ }
666
+ document.addEventListener('mousedown', onDown)
667
+ document.addEventListener('keydown', onKey)
668
+ return () => {
669
+ document.removeEventListener('mousedown', onDown)
670
+ document.removeEventListener('keydown', onKey)
671
+ }
672
+ }, [open])
673
+
674
+ const current = options.find((b) => b.path === value)
675
+ const pick = (path) => {
676
+ onChange(path)
677
+ setOpen(false)
678
+ }
679
+ return el('div', {
680
+ className: 'dn-bselect',
681
+ ref: wrapRef
682
+ },
683
+ el('button', {
684
+ type: 'button',
685
+ className: 'dn-bselect-trigger' + (open ? ' open' : ''),
686
+ disabled: disabled === true,
687
+ 'aria-haspopup': 'listbox',
688
+ 'aria-expanded': String(open),
689
+ onClick: () => {
690
+ if (disabled !== true) setOpen(!open)
691
+ },
692
+ },
693
+ el('span', {
694
+ className: 'dn-bselect-value'
695
+ }, current !== undefined ? current.name : t('browserDefault')),
696
+ el('span', {
697
+ className: 'dn-bselect-arrow'
698
+ }, open ? '▴' : '▾')),
699
+ open ? el('div', {
700
+ className: 'dn-bselect-pop',
701
+ role: 'listbox'
702
+ },
703
+ el('button', {
704
+ type: 'button',
705
+ role: 'option',
706
+ 'aria-selected': String(value === ''),
707
+ className: 'dn-bselect-opt' + (value === '' ? ' sel' : ''),
708
+ onClick: () => pick(''),
709
+ }, t('browserDefault')),
710
+ options.map((b) => el('button', {
711
+ type: 'button',
712
+ role: 'option',
713
+ key: b.id,
714
+ 'aria-selected': String(value === b.path),
715
+ className: 'dn-bselect-opt' + (value === b.path ? ' sel' : ''),
716
+ onClick: () => pick(b.path),
717
+ }, b.name)))
718
+ : null)
719
+ }
720
+
588
721
  const KIND_ORDER = ['completed', 'error', 'running', 'approval', 'question']
589
722
 
590
723
  function NotifySection(props) {
@@ -599,6 +732,15 @@ window.__ModuleLoader__.load({
599
732
  const [saving, setSaving] = useState(false)
600
733
  const [permission, setPermission] = useState(typeof Notification !== 'undefined' ? Notification
601
734
  .permission : 'unsupported')
735
+ // 通知窗口编辑模式(样板窗拖拽/缩放)
736
+ const [editing, setEditing] = useState(false)
737
+ // 最新值镜像:卸载清理时读取(避免空依赖闭包拿到陈旧值)。
738
+ const editingRef = useRef(false)
739
+ editingRef.current = editing
740
+ useEffect(() => () => {
741
+ // 离开通知设置页(组件卸载)时若仍在编辑模式,自动关闭样板窗。
742
+ if (editingRef.current) void api.endEditMode()
743
+ }, [])
602
744
 
603
745
  const saveTimerRef = useRef(null)
604
746
  const save = useCallback((patch) => {
@@ -650,21 +792,22 @@ window.__ModuleLoader__.load({
650
792
  const controlsDisabled = !config.enabled
651
793
 
652
794
  const kindRows = KIND_ORDER.map((kind) => {
653
- const entry = config.colors?. [kind] ?? {}
795
+ // 修正:去掉 `?.` 和 `[kind]` 之间的空格
796
+ const entry = config.colors?.[kind] ?? {}
654
797
  const bg = entry.bg || config.bgColor || '#20272f'
655
798
  const fg = entry.fg || config.textColor || '#e6edf3'
799
+ const enabled = entry.enabled !== false
656
800
  return el('div', {
657
801
  key: kind,
658
802
  className: 'dn-kind-row'
659
803
  },
804
+ // 样式预览:类型文字显示在配置色的纯色块内(内联样式,不受深色模式影响)
660
805
  el('span', {
661
- className: 'dn-kind-swatch',
806
+ className: 'dn-kind-preview',
662
807
  style: {
663
- background: bg
808
+ background: bg,
809
+ color: fg
664
810
  }
665
- }),
666
- el('span', {
667
- className: 'dn-kind-name'
668
811
  }, kindLabel(kind, t)),
669
812
  el('label', {
670
813
  className: 'dn-kind-field'
@@ -701,7 +844,28 @@ window.__ModuleLoader__.load({
701
844
  }
702
845
  }
703
846
  })
704
- }), t('colorFg')))
847
+ }), t('colorFg')),
848
+ // 每类型开关:控制该类型通知是否产生(默认全开)
849
+ el('label', {
850
+ className: 'dn-kind-field'
851
+ },
852
+ el('button', {
853
+ type: 'button',
854
+ role: 'switch',
855
+ 'aria-checked': enabled,
856
+ 'aria-label': kindLabel(kind, t),
857
+ className: 'dn-switch',
858
+ 'data-on': String(enabled),
859
+ disabled: controlsDisabled,
860
+ onClick: () => save({
861
+ colors: {
862
+ [kind]: {
863
+ ...entry,
864
+ enabled: !enabled
865
+ }
866
+ }
867
+ })
868
+ })))
705
869
  })
706
870
 
707
871
  const customOpen = config.mode === 'custom'
@@ -764,21 +928,15 @@ window.__ModuleLoader__.load({
764
928
  el('span', {
765
929
  className: 'dn-label'
766
930
  }, t('browserLabel')),
767
- el('select', {
768
- className: 'dn-input dn-select',
769
- value: config.browser || '',
770
- disabled: controlsDisabled,
771
- onChange: (e) => save({
772
- browser: e.target.value
773
- })
774
- },
775
- el('option', {
776
- value: ''
777
- }, t('browserDefault')),
778
- (state?.browsers ?? []).map((b) => el('option', {
779
- key: b.id,
780
- value: b.path
781
- }, b.name)))),
931
+ el(BrowserSelect, {
932
+ value: config.browser || '',
933
+ options: state?.browsers ?? [],
934
+ disabled: controlsDisabled,
935
+ onChange: (path) => save({
936
+ browser: path
937
+ }),
938
+ t
939
+ })),
782
940
  el('p', {
783
941
  className: 'dn-note'
784
942
  }, t('browserNote'))
@@ -868,7 +1026,70 @@ window.__ModuleLoader__.load({
868
1026
  }, t('colorsTitle')),
869
1027
  el('div', {
870
1028
  className: 'dn-kind-list'
871
- }, kindRows))
1029
+ }, kindRows),
1030
+ el('div', {
1031
+ className: 'dn-divider'
1032
+ }),
1033
+ el('p', {
1034
+ className: 'dn-title'
1035
+ }, t('toastStyleTitle')),
1036
+ el('div', {
1037
+ className: 'dn-row'
1038
+ },
1039
+ el('span', {
1040
+ className: 'dn-label'
1041
+ }, t('toastGapLabel')),
1042
+ el('input', {
1043
+ type: 'number',
1044
+ min: 0,
1045
+ max: 60,
1046
+ className: 'dn-input',
1047
+ value: config.toast?.gap ?? 12,
1048
+ disabled: controlsDisabled,
1049
+ onChange: (e) => {
1050
+ const g = Math.max(0, Math.min(60, Math.floor(Number(e
1051
+ .target.value) || 0)))
1052
+ save({
1053
+ toast: {
1054
+ ...(config.toast ?? {}),
1055
+ gap: g
1056
+ }
1057
+ })
1058
+ }
1059
+ })),
1060
+ el('div', {
1061
+ className: 'dn-row'
1062
+ },
1063
+ el('button', {
1064
+ className: 'dn-btn primary',
1065
+ disabled: controlsDisabled,
1066
+ onClick: () => {
1067
+ if (editing) {
1068
+ void api.endEditMode()
1069
+ setEditing(false)
1070
+ } else {
1071
+ setEditing(true)
1072
+ void api.startEditMode()
1073
+ }
1074
+ }
1075
+ }, editing ? t('endEdit') : t('enterEdit')),
1076
+ el('button', {
1077
+ className: 'dn-btn',
1078
+ disabled: controlsDisabled || !config.toast?.position,
1079
+ onClick: () => {
1080
+ save({
1081
+ toast: {
1082
+ ...(config.toast ?? {}),
1083
+ position: null
1084
+ }
1085
+ })
1086
+ // 编辑模式下:重开样板窗,让其在默认位置(右下)刷新显示。
1087
+ if (editing) void api.startEditMode()
1088
+ }
1089
+ }, t('resetPosition'))),
1090
+ el('p', {
1091
+ className: 'dn-note'
1092
+ }, t('editHint')))
872
1093
  )),
873
1094
  el('div', {
874
1095
  className: 'dn-divider'
@@ -965,6 +1186,18 @@ window.__ModuleLoader__.load({
965
1186
  } catch {
966
1187
  /* 静默 */ }
967
1188
  },
1189
+ startEditMode: async () => {
1190
+ try {
1191
+ await notify.startEditMode()
1192
+ } catch {
1193
+ /* 静默 */ }
1194
+ },
1195
+ endEditMode: async () => {
1196
+ try {
1197
+ await notify.endEditMode()
1198
+ } catch {
1199
+ /* 静默 */ }
1200
+ },
968
1201
  }
969
1202
 
970
1203
  // ===== 现在设置 __localeService 并使用 api =====
@@ -1125,4 +1358,4 @@ window.__ModuleLoader__.load({
1125
1358
  exports.inject = inject
1126
1359
  return module.exports
1127
1360
  },
1128
- })
1361
+ })