dsh-global-todo 0.6.2 → 0.6.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/CHANGELOG.md +29 -0
- package/README.md +3 -1
- package/README.zh.md +3 -1
- package/docs/screenshots/panel-detail.png +0 -0
- package/docs/screenshots/panel-select.png +0 -0
- package/docs/screenshots/panel-settings.png +0 -0
- package/docs/screenshots/panel.png +0 -0
- package/lib/client.js +227 -91
- package/lib/index.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,32 @@
|
|
|
1
|
+
## [0.6.4] — 2026-10-20
|
|
2
|
+
|
|
3
|
+
The settings panel, rebuilt as an inspector instead of a stack of labelled rows.
|
|
4
|
+
|
|
5
|
+
- **Header and footer, finally.** A title row with a close button, three labelled groups divided
|
|
6
|
+
by hairlines, and a save row pinned at the bottom — the token field no longer pushes the save
|
|
7
|
+
buttons into the middle of the form with a field stranded beneath them.
|
|
8
|
+
- **Every field says what it does.** Help text sits under each label (`settings.*Hint`) rather than
|
|
9
|
+
being implied by the label; the label-and-control grid keeps number inputs aligned at a fixed
|
|
10
|
+
76px right-aligned box.
|
|
11
|
+
- **Ingest fields dim when ingest is off**, and `New token` becomes a quiet text link on the token
|
|
12
|
+
label row (`Rotate token`) instead of a third equal-weight button.
|
|
13
|
+
- **About section** shows the data file and version on labelled rows; the card sizes to its content
|
|
14
|
+
and scrolls internally when the window is short.
|
|
15
|
+
- `npm run screenshots` now also photographs a settings variant, so the README image is
|
|
16
|
+
reproducible instead of hand-made.
|
|
17
|
+
|
|
18
|
+
## [0.6.3] — 2026-10-20
|
|
19
|
+
|
|
20
|
+
States that were still speaking to nobody.
|
|
21
|
+
|
|
22
|
+
- **The selection bar has one primary action.** Count and 全选/清空 sit on the left, `Assign` is
|
|
23
|
+
the only filled button, `Complete`/`Archive`/tag stay quiet, and the ambiguous `Done` becomes an
|
|
24
|
+
icon-only exit (it read as a second `Complete`).
|
|
25
|
+
- **The inbox explains itself**, and an empty filter now offers `Clear filter` instead of dead air.
|
|
26
|
+
- **The reminder bar says which set it is.** Copy moves to `Dismiss all` and the bar carries a note
|
|
27
|
+
that these reached their reminder time, unlike Overdue.
|
|
28
|
+
- **Selection bar is sticky and 36px tall**, so the batch actions stay reachable while scrolling.
|
|
29
|
+
|
|
1
30
|
## [0.6.2] — 2026-10-20
|
|
2
31
|
|
|
3
32
|
The narrow-width details the spec called for after the two-column work.
|
package/README.md
CHANGED
|
@@ -33,6 +33,8 @@ one record:
|
|
|
33
33
|
|
|
34
34
|

|
|
35
35
|
|
|
36
|
+

|
|
37
|
+
|
|
36
38
|
These are produced by `npm run screenshots` (which chains `npm run preview`): the real client
|
|
37
39
|
half is rendered to static HTML with no Host and no dependencies, then photographed at 1280×720
|
|
38
40
|
into `docs/screenshots/`. Playwright is not a dependency — the tool borrows the copy a DSH
|
|
@@ -339,7 +341,7 @@ npm run preview # render the panel to preview/panel.html (+ detail and sel
|
|
|
339
341
|
npm run screenshots # re-photograph those previews into docs/screenshots/, 1280×720
|
|
340
342
|
```
|
|
341
343
|
|
|
342
|
-
The
|
|
344
|
+
The PNGs under `docs/screenshots/` ship with the package and are embedded in both
|
|
343
345
|
READMEs, so re-run `npm run screenshots` whenever the panel UI changes.
|
|
344
346
|
|
|
345
347
|
```text
|
package/README.zh.md
CHANGED
|
@@ -30,6 +30,8 @@ DSH 自带的 `todo_write` 属于**某一个 agent 会话**:整表替换、三
|
|
|
30
30
|
|
|
31
31
|

|
|
32
32
|
|
|
33
|
+

|
|
34
|
+
|
|
33
35
|
图都由 `npm run screenshots`(内部串起 `npm run preview`)生成:先把真实的客户端半区渲染成静态
|
|
34
36
|
HTML(不依赖宿主、零依赖),再以 1280×720 拍进 `docs/screenshots/`。Playwright 不是依赖——工具
|
|
35
37
|
借的是 DSH profile 里已经装好的那份,或 `DSH_PLAYWRIGHT` 指向的那份。
|
|
@@ -230,7 +232,7 @@ npm run preview # 把面板渲染成 preview/panel.html(另附详情态
|
|
|
230
232
|
npm run screenshots # 把这些预览重新拍成 docs/screenshots/,1280×720
|
|
231
233
|
```
|
|
232
234
|
|
|
233
|
-
`docs/screenshots/`
|
|
235
|
+
`docs/screenshots/` 下那几张 PNG 会随包发布、并嵌在两份 README 里,所以面板 UI 一改就要重跑
|
|
234
236
|
`npm run screenshots`。
|
|
235
237
|
|
|
236
238
|
```text
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/lib/client.js
CHANGED
|
@@ -50,7 +50,7 @@ window.__ModuleLoader__.load({
|
|
|
50
50
|
* different mechanisms (restart vs. hot reload), so "which one am I looking
|
|
51
51
|
* at" needs an answer that the bundle itself carries.
|
|
52
52
|
*/
|
|
53
|
-
const UI_REV = '
|
|
53
|
+
const UI_REV = 'ui40'
|
|
54
54
|
/** Last language resolved for the shell; module-level so a fetch failure can be worded too. */
|
|
55
55
|
let activeLang = 'zh'
|
|
56
56
|
|
|
@@ -100,12 +100,18 @@ window.__ModuleLoader__.load({
|
|
|
100
100
|
'settings.ingestEnable': '启用外部写入',
|
|
101
101
|
'settings.token': '访问 token',
|
|
102
102
|
'settings.tokenSet': '已设置,留空保持不变',
|
|
103
|
-
'settings.generate': '
|
|
104
|
-
'settings.confirmBelow': '
|
|
103
|
+
'settings.generate': '轮换 token',
|
|
104
|
+
'settings.confirmBelow': '置信度阈值',
|
|
105
105
|
'settings.save': '保存',
|
|
106
106
|
'settings.cancel': '取消',
|
|
107
107
|
'settings.saved': '设置已保存',
|
|
108
108
|
'settings.about': '关于',
|
|
109
|
+
'settings.remindersHint': '到期时在面板顶部提示,并按设置提前。',
|
|
110
|
+
'settings.ingestHint': '允许其他工具带 token 写入,先进收件箱。',
|
|
111
|
+
'settings.tokenHint': '留空表示不改动;轮换后旧 token 立刻失效。',
|
|
112
|
+
'settings.confirmBelowHint': '置信度低于这个值的条目进收件箱,不进清单。',
|
|
113
|
+
'settings.storeFile': '数据文件',
|
|
114
|
+
'settings.version': '版本',
|
|
109
115
|
'inbox.explain': '以下由外部写入或 Agent 提交,确认后才进入清单。',
|
|
110
116
|
'notice.exported': '已导出 {n} 条待办',
|
|
111
117
|
'notice.imported': '已导入 {n} 条(合并,现有共 {total} 条)',
|
|
@@ -129,6 +135,7 @@ window.__ModuleLoader__.load({
|
|
|
129
135
|
'notice.bulkPartial': '已更新 {n} 条,跳过 {m} 条',
|
|
130
136
|
'select.clear': '清除',
|
|
131
137
|
'select.done': '退出选择',
|
|
138
|
+
'select.clear': '清空',
|
|
132
139
|
'select.hint': '先勾几条待办,再点「一键分配」:优先回到写下它的那个对话,其次同工作区的对话,都找不到才让你选。',
|
|
133
140
|
'assign.working': '正在分配…',
|
|
134
141
|
'assign.summary': '已分配 {n} 条(原对话 {origin} · 同工作区 {workspace} · 你选的 {chosen})',
|
|
@@ -154,6 +161,8 @@ window.__ModuleLoader__.load({
|
|
|
154
161
|
'notice.autoCollectOff': '已关闭自动获取',
|
|
155
162
|
'notice.externalChange': '清单在别处被改过,已载入最新内容',
|
|
156
163
|
'reminder.title': '到点提醒({n})',
|
|
164
|
+
'reminder.note': '这些是到了设定的提醒时间,和「逾期」不是同一批。',
|
|
165
|
+
'reminder.dismissAll': '全部知道了',
|
|
157
166
|
'reminder.dismiss': '知道了',
|
|
158
167
|
'reminder.enableNotifications': '开启系统通知',
|
|
159
168
|
'reminder.notifyTitle': '待办到点',
|
|
@@ -245,6 +254,8 @@ window.__ModuleLoader__.load({
|
|
|
245
254
|
'empty.none': '还没有待办。上面写一句,回车就记下了。',
|
|
246
255
|
'empty.add': '记第一条',
|
|
247
256
|
'empty.filtered': '这个筛选下没有待办。',
|
|
257
|
+
'empty.clearFilter': '清除筛选',
|
|
258
|
+
'empty.filterHint': '换个筛选,或者清空搜索关键词。',
|
|
248
259
|
'meta.added': '加入于',
|
|
249
260
|
'meta.due': '截止',
|
|
250
261
|
'meta.overdue': '逾期 {n} 天',
|
|
@@ -342,12 +353,18 @@ window.__ModuleLoader__.load({
|
|
|
342
353
|
'settings.ingestEnable': 'Accept external writes',
|
|
343
354
|
'settings.token': 'Access token',
|
|
344
355
|
'settings.tokenSet': 'Set — leave blank to keep',
|
|
345
|
-
'settings.generate': '
|
|
346
|
-
'settings.confirmBelow': '
|
|
356
|
+
'settings.generate': 'Rotate token',
|
|
357
|
+
'settings.confirmBelow': 'Confidence threshold',
|
|
347
358
|
'settings.save': 'Save',
|
|
348
359
|
'settings.cancel': 'Cancel',
|
|
349
360
|
'settings.saved': 'Settings saved',
|
|
350
361
|
'settings.about': 'About',
|
|
362
|
+
'settings.remindersHint': 'Flag a deadline at the top of the panel when it comes due.',
|
|
363
|
+
'settings.ingestHint': 'Let other tools POST todos with a token; they start in the inbox.',
|
|
364
|
+
'settings.tokenHint': 'Leave blank to keep the current one; rotating invalidates the old token.',
|
|
365
|
+
'settings.confirmBelowHint': 'Items below this confidence go to the inbox instead of the list.',
|
|
366
|
+
'settings.storeFile': 'Data file',
|
|
367
|
+
'settings.version': 'Version',
|
|
351
368
|
'inbox.explain': 'Written by external sources or the agent. Confirm it before it joins the list.',
|
|
352
369
|
'notice.exported': 'Exported {n} todos',
|
|
353
370
|
'notice.imported': 'Imported {n} (merged; {total} in the list now)',
|
|
@@ -370,7 +387,7 @@ window.__ModuleLoader__.load({
|
|
|
370
387
|
'notice.bulk': 'Updated {n}',
|
|
371
388
|
'notice.bulkPartial': 'Updated {n}, skipped {m}',
|
|
372
389
|
'select.clear': 'Clear',
|
|
373
|
-
'select.done': '
|
|
390
|
+
'select.done': 'Exit selection',
|
|
374
391
|
'select.hint': 'Pick a few todos, then Assign: back to the conversation that wrote each one, else a conversation in the same workspace, else you choose.',
|
|
375
392
|
'assign.working': 'Assigning…',
|
|
376
393
|
'assign.summary': 'Assigned {n} ({origin} to origin · {workspace} same workspace · {chosen} chosen)',
|
|
@@ -396,6 +413,8 @@ window.__ModuleLoader__.load({
|
|
|
396
413
|
'notice.autoCollectOff': 'Auto-collect off',
|
|
397
414
|
'notice.externalChange': 'The list changed somewhere else; the latest content is loaded',
|
|
398
415
|
'reminder.title': 'Due now ({n})',
|
|
416
|
+
'reminder.note': 'These reached their reminder time — a different set from Overdue.',
|
|
417
|
+
'reminder.dismissAll': 'Dismiss all',
|
|
399
418
|
'reminder.dismiss': 'Dismiss',
|
|
400
419
|
'reminder.enableNotifications': 'Enable notifications',
|
|
401
420
|
'reminder.notifyTitle': 'Todo due',
|
|
@@ -488,6 +507,8 @@ window.__ModuleLoader__.load({
|
|
|
488
507
|
'empty.none': 'No todos yet. Write one above and press Enter.',
|
|
489
508
|
'empty.add': 'Add your first todo',
|
|
490
509
|
'empty.filtered': 'Nothing matches this filter.',
|
|
510
|
+
'empty.clearFilter': 'Clear filter',
|
|
511
|
+
'empty.filterHint': 'Try another filter, or clear the search box.',
|
|
491
512
|
'meta.added': 'added',
|
|
492
513
|
'meta.due': 'due',
|
|
493
514
|
'meta.overdue': '{n} d overdue',
|
|
@@ -1071,8 +1092,11 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
1071
1092
|
display: inline-flex; align-items: center; justify-content: center; padding: 0; }
|
|
1072
1093
|
.gt-pick[data-on="1"] { border-color: var(--dsw-alias-brand-primary); background: var(--dsw-alias-brand-primary); color: var(--dsw-alias-bg-base); }
|
|
1073
1094
|
.gt-item[data-selected="1"] { border-color: var(--dsw-alias-brand-primary); }
|
|
1074
|
-
.gt-selectbar {
|
|
1075
|
-
|
|
1095
|
+
.gt-selectbar { position: sticky; top: 0; z-index: 10; display: flex; align-items: center;
|
|
1096
|
+
gap: 8px; flex-wrap: wrap; padding: 0 12px; min-height: 36px; box-sizing: border-box;
|
|
1097
|
+
border: 1px solid var(--dsw-alias-border-l2); border-radius: 10px;
|
|
1098
|
+
background: var(--dsw-alias-bg-layer-1); font-size: 13px; }
|
|
1099
|
+
.gt-selectbar-count { font-weight: 600; white-space: nowrap; }
|
|
1076
1100
|
.gt-item-body { flex: 1; display: flex; flex-direction: column; gap: 5px; min-width: 0; cursor: pointer; }
|
|
1077
1101
|
.gt-item-body:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 2px; border-radius: 6px; }
|
|
1078
1102
|
.gt-item-title { font-weight: 600; font-size: 14px; word-break: break-word; line-height: 22px;
|
|
@@ -1090,9 +1114,35 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
1090
1114
|
border-radius: 6px; cursor: pointer; max-width: 300px; overflow: hidden; text-overflow: ellipsis;
|
|
1091
1115
|
white-space: nowrap; }
|
|
1092
1116
|
.gt-reminder-item:hover { background: var(--dsw-alias-button-ghost-active-fill); }
|
|
1093
|
-
.gt-settings { display: flex; flex-direction: column;
|
|
1094
|
-
box-sizing: border-box; border: 1px solid var(--dsw-alias-border-l2);
|
|
1095
|
-
background: var(--dsw-alias-bg-overlay); font-size: 13px; line-height: 20px; }
|
|
1117
|
+
.gt-settings { display: flex; flex-direction: column; width: 360px; max-width: 100%;
|
|
1118
|
+
box-sizing: border-box; overflow: hidden; border: 1px solid var(--dsw-alias-border-l2);
|
|
1119
|
+
border-radius: 12px; background: var(--dsw-alias-bg-overlay); font-size: 13px; line-height: 20px; }
|
|
1120
|
+
.gt-settings-head { display: flex; flex: 0 0 auto; align-items: center; justify-content: space-between;
|
|
1121
|
+
gap: 8px; padding: 10px 10px 10px 16px; border-bottom: 1px solid var(--dsw-alias-border-l1); }
|
|
1122
|
+
.gt-settings-title { font-size: 14px; font-weight: 600; }
|
|
1123
|
+
.gt-settings-body { display: flex; flex: 1 1 auto; flex-direction: column; gap: 16px;
|
|
1124
|
+
padding: 14px 16px; overflow: auto; min-height: 0; }
|
|
1125
|
+
.gt-settings-group { display: flex; flex-direction: column; gap: 8px; }
|
|
1126
|
+
.gt-settings-group + .gt-settings-group { border-top: 1px solid var(--dsw-alias-border-l1); padding-top: 14px; }
|
|
1127
|
+
.gt-settings-group-title { font-size: 12px; line-height: 18px; font-weight: 600;
|
|
1128
|
+
color: var(--dsw-alias-label-secondary); }
|
|
1129
|
+
.gt-check-row { display: flex; align-items: flex-start; gap: 8px; cursor: pointer; }
|
|
1130
|
+
.gt-check-row input { margin: 3px 0 0; flex: 0 0 auto; }
|
|
1131
|
+
.gt-check-main { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
|
|
1132
|
+
.gt-check-label { color: var(--dsw-alias-label-primary); }
|
|
1133
|
+
.gt-hint { font-size: 12px; line-height: 18px; color: var(--dsw-alias-label-secondary); }
|
|
1134
|
+
.gt-field { display: flex; flex-direction: column; gap: 6px; }
|
|
1135
|
+
.gt-field[data-muted="1"], .gt-field-row[data-muted="1"] { opacity: .55; }
|
|
1136
|
+
.gt-field-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
|
|
1137
|
+
.gt-field-label { font-size: 13px; color: var(--dsw-alias-label-primary); }
|
|
1138
|
+
.gt-field-row { display: grid; grid-template-columns: 1fr auto; align-items: center; gap: 8px; }
|
|
1139
|
+
.gt-input-num { width: 76px; text-align: right; font-variant-numeric: tabular-nums; }
|
|
1140
|
+
.gt-link-btn { border: none; background: transparent; padding: 0; font: inherit; font-size: 12px;
|
|
1141
|
+
color: var(--dsw-alias-link); cursor: pointer; }
|
|
1142
|
+
.gt-link-btn:disabled { color: var(--dsw-alias-label-secondary); cursor: default; }
|
|
1143
|
+
.gt-about-row { display: grid; grid-template-columns: 88px minmax(0, 1fr); align-items: baseline; gap: 8px; }
|
|
1144
|
+
.gt-settings-foot { display: flex; flex: 0 0 auto; justify-content: flex-end; gap: 8px;
|
|
1145
|
+
padding: 12px 16px; border-top: 1px solid var(--dsw-alias-border-l1); }
|
|
1096
1146
|
.gt-group-select { display: none; min-height: 28px; max-width: 150px; padding: 0 8px; font: inherit;
|
|
1097
1147
|
font-size: 12.5px; color: var(--dsw-alias-label-primary); background: var(--dsw-alias-bg-layer-1);
|
|
1098
1148
|
border: 1px solid var(--dsw-alias-border-l1); border-radius: 8px; }
|
|
@@ -1102,8 +1152,7 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
1102
1152
|
line-height: 20px; }
|
|
1103
1153
|
.gt-settings-path { font-size: 11.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1104
1154
|
.gt-settings .gt-input { flex: 0 0 auto; width: 100%; min-height: 32px; }
|
|
1105
|
-
.gt-settings .gt-
|
|
1106
|
-
.gt-settings-head { font-weight: 650; font-size: 14px; }
|
|
1155
|
+
.gt-settings .gt-input-num { width: 76px; }
|
|
1107
1156
|
.gt-check-line { display: flex; align-items: center; gap: 8px; justify-content: flex-start;
|
|
1108
1157
|
color: var(--dsw-alias-label-primary); }
|
|
1109
1158
|
.gt-settings-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; justify-content: flex-end; }
|
|
@@ -2157,6 +2206,9 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
2157
2206
|
// The preview harness cannot re-render after a click, so it may ask to start in
|
|
2158
2207
|
// selection mode; a person toggles it with the `选择` control.
|
|
2159
2208
|
const [selectMode, setSelectMode] = useState(props.initialSelectMode === true)
|
|
2209
|
+
// Only the static preview asks for this: the panel opens settings by click, but the
|
|
2210
|
+
// SSR harness cannot click, so the screenshot variant has to start there.
|
|
2211
|
+
const initialSettingsOpen = props.initialSettingsOpen === true
|
|
2160
2212
|
// The picker's filters mirror the shell's own visibility rule, but the shape of the
|
|
2161
2213
|
// real snapshot has never been observable from here — a preview fixture is written by
|
|
2162
2214
|
// the same hand as the filter, so it can only confirm itself. Report the shape once per
|
|
@@ -2175,7 +2227,7 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
2175
2227
|
const [importAsk, setImportAsk] = useState(null)
|
|
2176
2228
|
// One settings popover: reminders and the external door both need a place a person can
|
|
2177
2229
|
// reach without curl.
|
|
2178
|
-
const [settingsOpen, setSettingsOpen] = useState(
|
|
2230
|
+
const [settingsOpen, setSettingsOpen] = useState(initialSettingsOpen)
|
|
2179
2231
|
const [settingsBox, setSettingsBox] = useState(null)
|
|
2180
2232
|
// The detail card's breakpoint follows the panel's own width, not the viewport: the
|
|
2181
2233
|
// slot is ~265px narrower than the window, so a viewport query opens the two-column
|
|
@@ -2424,13 +2476,15 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
2424
2476
|
useEffect(() => {
|
|
2425
2477
|
const element = innerRef.current
|
|
2426
2478
|
if (element === null || typeof window === 'undefined') return undefined
|
|
2479
|
+
// The static preview supplies a `window` stub without event plumbing.
|
|
2480
|
+
const listen = typeof window.addEventListener === 'function'
|
|
2427
2481
|
const measure = () => setPanelWidth(element.getBoundingClientRect().width)
|
|
2428
2482
|
measure()
|
|
2429
|
-
window.addEventListener('resize', measure)
|
|
2483
|
+
if (listen === true) window.addEventListener('resize', measure)
|
|
2430
2484
|
const observer = typeof window.ResizeObserver === 'function' ? new window.ResizeObserver(measure) : null
|
|
2431
2485
|
if (observer !== null) observer.observe(element)
|
|
2432
2486
|
return () => {
|
|
2433
|
-
window.removeEventListener('resize', measure)
|
|
2487
|
+
if (listen === true) window.removeEventListener('resize', measure)
|
|
2434
2488
|
if (observer !== null) observer.disconnect()
|
|
2435
2489
|
}
|
|
2436
2490
|
}, [])
|
|
@@ -2445,14 +2499,19 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
2445
2499
|
if (element === null) return
|
|
2446
2500
|
const rect = element.getBoundingClientRect()
|
|
2447
2501
|
setSettingsBox({
|
|
2448
|
-
|
|
2502
|
+
// Just under the title block: high enough that the whole card fits, low enough
|
|
2503
|
+
// that it never covers the panel title or the header actions.
|
|
2504
|
+
top: Math.round(rect.top + 56),
|
|
2449
2505
|
right: Math.round(window.innerWidth - rect.right + 20),
|
|
2450
2506
|
bottom: Math.round(window.innerHeight - rect.bottom + 20),
|
|
2451
2507
|
})
|
|
2452
2508
|
}
|
|
2509
|
+
const listen = typeof window.addEventListener === 'function'
|
|
2453
2510
|
measure()
|
|
2454
|
-
window.addEventListener('resize', measure)
|
|
2455
|
-
return () =>
|
|
2511
|
+
if (listen === true) window.addEventListener('resize', measure)
|
|
2512
|
+
return () => {
|
|
2513
|
+
if (listen === true) window.removeEventListener('resize', measure)
|
|
2514
|
+
}
|
|
2456
2515
|
}, [settingsOpen, lang])
|
|
2457
2516
|
|
|
2458
2517
|
useEffect(() => {
|
|
@@ -3274,77 +3333,141 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
3274
3333
|
position: 'fixed',
|
|
3275
3334
|
top: `${settingsBox.top}px`,
|
|
3276
3335
|
right: `${settingsBox.right}px`,
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3336
|
+
// Height follows the content, capped by the room below: stretching to
|
|
3337
|
+
// `bottom` left a tall empty card with the footer stranded at the floor.
|
|
3338
|
+
maxHeight: `calc(100dvh - ${settingsBox.top + 20}px)`,
|
|
3280
3339
|
zIndex: 40,
|
|
3281
3340
|
},
|
|
3282
3341
|
},
|
|
3283
|
-
h('div', { className: 'gt-settings-head' }, t('settings.title')),
|
|
3284
|
-
h('div', { className: 'gt-section-title' }, t('settings.reminders')),
|
|
3285
3342
|
h(
|
|
3286
|
-
'
|
|
3287
|
-
{ className: 'gt-
|
|
3288
|
-
h('
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
value: leadDraft,
|
|
3301
|
-
onChange: (event) => setLeadDraft(event.target.value),
|
|
3302
|
-
}),
|
|
3343
|
+
'div',
|
|
3344
|
+
{ className: 'gt-settings-head' },
|
|
3345
|
+
h('span', { className: 'gt-settings-title' }, t('settings.title')),
|
|
3346
|
+
h(
|
|
3347
|
+
'button',
|
|
3348
|
+
{
|
|
3349
|
+
type: 'button',
|
|
3350
|
+
className: 'gt-iconbtn',
|
|
3351
|
+
title: t('settings.cancel'),
|
|
3352
|
+
'aria-label': t('settings.cancel'),
|
|
3353
|
+
onClick: () => setSettingsOpen(false),
|
|
3354
|
+
},
|
|
3355
|
+
h(Icon, { name: 'close', size: 13 }),
|
|
3356
|
+
),
|
|
3303
3357
|
),
|
|
3304
|
-
h('div', { className: 'gt-section-title' }, t('settings.ingest')),
|
|
3305
3358
|
h(
|
|
3306
|
-
'
|
|
3307
|
-
{ className: 'gt-
|
|
3308
|
-
h(
|
|
3309
|
-
|
|
3359
|
+
'div',
|
|
3360
|
+
{ className: 'gt-settings-body' },
|
|
3361
|
+
h(
|
|
3362
|
+
'div',
|
|
3363
|
+
{ className: 'gt-settings-group' },
|
|
3364
|
+
h('div', { className: 'gt-settings-group-title' }, t('settings.reminders')),
|
|
3365
|
+
h(
|
|
3366
|
+
'label',
|
|
3367
|
+
{ className: 'gt-check-row' },
|
|
3368
|
+
h('input', { type: 'checkbox', checked: remindersDraft, onChange: (event) => setRemindersDraft(event.target.checked) }),
|
|
3369
|
+
h(
|
|
3370
|
+
'span',
|
|
3371
|
+
{ className: 'gt-check-main' },
|
|
3372
|
+
h('span', { className: 'gt-check-label' }, t('settings.remindersEnable')),
|
|
3373
|
+
h('span', { className: 'gt-hint' }, t('settings.remindersHint')),
|
|
3374
|
+
),
|
|
3375
|
+
),
|
|
3376
|
+
h(
|
|
3377
|
+
'div',
|
|
3378
|
+
{ className: 'gt-field-row' },
|
|
3379
|
+
h('label', { className: 'gt-field-label', htmlFor: 'gt-lead' }, t('settings.lead')),
|
|
3380
|
+
h('input', {
|
|
3381
|
+
id: 'gt-lead',
|
|
3382
|
+
type: 'number',
|
|
3383
|
+
min: 0,
|
|
3384
|
+
max: 1440,
|
|
3385
|
+
className: 'gt-input gt-input-num',
|
|
3386
|
+
disabled: remindersDraft !== true,
|
|
3387
|
+
value: leadDraft,
|
|
3388
|
+
onChange: (event) => setLeadDraft(event.target.value),
|
|
3389
|
+
}),
|
|
3390
|
+
),
|
|
3391
|
+
),
|
|
3392
|
+
h(
|
|
3393
|
+
'div',
|
|
3394
|
+
{ className: 'gt-settings-group' },
|
|
3395
|
+
h('div', { className: 'gt-settings-group-title' }, t('settings.ingest')),
|
|
3396
|
+
h(
|
|
3397
|
+
'label',
|
|
3398
|
+
{ className: 'gt-check-row' },
|
|
3399
|
+
h('input', { type: 'checkbox', checked: ingestDraft, onChange: (event) => setIngestDraft(event.target.checked) }),
|
|
3400
|
+
h(
|
|
3401
|
+
'span',
|
|
3402
|
+
{ className: 'gt-check-main' },
|
|
3403
|
+
h('span', { className: 'gt-check-label' }, t('settings.ingestEnable')),
|
|
3404
|
+
h('span', { className: 'gt-hint' }, t('settings.ingestHint')),
|
|
3405
|
+
),
|
|
3406
|
+
),
|
|
3407
|
+
h(
|
|
3408
|
+
'div',
|
|
3409
|
+
{ className: 'gt-field', 'data-muted': ingestDraft === true ? '0' : '1' },
|
|
3410
|
+
h(
|
|
3411
|
+
'div',
|
|
3412
|
+
{ className: 'gt-field-head' },
|
|
3413
|
+
h('label', { className: 'gt-field-label', htmlFor: 'gt-token' }, t('settings.token')),
|
|
3414
|
+
h('button', { type: 'button', className: 'gt-link-btn', disabled: ingestDraft !== true, onClick: generateToken }, t('settings.generate')),
|
|
3415
|
+
),
|
|
3416
|
+
h('input', {
|
|
3417
|
+
id: 'gt-token',
|
|
3418
|
+
className: 'gt-input',
|
|
3419
|
+
type: 'password',
|
|
3420
|
+
disabled: ingestDraft !== true,
|
|
3421
|
+
value: tokenDraft,
|
|
3422
|
+
placeholder:
|
|
3423
|
+
state.health?.settings?.ingest?.tokenSet === true ? t('settings.tokenSet') : t('settings.token'),
|
|
3424
|
+
autoComplete: 'new-password',
|
|
3425
|
+
onChange: (event) => setTokenDraft(event.target.value),
|
|
3426
|
+
}),
|
|
3427
|
+
h('span', { className: 'gt-hint' }, t('settings.tokenHint')),
|
|
3428
|
+
),
|
|
3429
|
+
h(
|
|
3430
|
+
'div',
|
|
3431
|
+
{ className: 'gt-field-row', 'data-muted': ingestDraft === true ? '0' : '1' },
|
|
3432
|
+
h('label', { className: 'gt-field-label', htmlFor: 'gt-below' }, t('settings.confirmBelow')),
|
|
3433
|
+
h('input', {
|
|
3434
|
+
id: 'gt-below',
|
|
3435
|
+
type: 'number',
|
|
3436
|
+
min: 0,
|
|
3437
|
+
max: 1,
|
|
3438
|
+
step: 0.05,
|
|
3439
|
+
className: 'gt-input gt-input-num',
|
|
3440
|
+
disabled: ingestDraft !== true,
|
|
3441
|
+
value: belowDraft,
|
|
3442
|
+
onChange: (event) => setBelowDraft(event.target.value),
|
|
3443
|
+
}),
|
|
3444
|
+
),
|
|
3445
|
+
h('span', { className: 'gt-hint' }, t('settings.confirmBelowHint')),
|
|
3446
|
+
),
|
|
3447
|
+
h(
|
|
3448
|
+
'div',
|
|
3449
|
+
{ className: 'gt-settings-group' },
|
|
3450
|
+
h('div', { className: 'gt-settings-group-title' }, t('settings.about')),
|
|
3451
|
+
h(
|
|
3452
|
+
'div',
|
|
3453
|
+
{ className: 'gt-about-row' },
|
|
3454
|
+
h('span', { className: 'gt-field-label' }, t('settings.storeFile')),
|
|
3455
|
+
h('span', { className: 'gt-mono gt-settings-path', title: state.health?.store?.file ?? '' }, state.health?.store?.file ?? ''),
|
|
3456
|
+
),
|
|
3457
|
+
h(
|
|
3458
|
+
'div',
|
|
3459
|
+
{ className: 'gt-about-row' },
|
|
3460
|
+
h('span', { className: 'gt-field-label' }, t('settings.version')),
|
|
3461
|
+
h('span', { className: 'gt-mono gt-hint' }, `v${state.health?.version ?? ''} · ${UI_REV}`),
|
|
3462
|
+
),
|
|
3463
|
+
),
|
|
3310
3464
|
),
|
|
3311
|
-
h('input', {
|
|
3312
|
-
className: 'gt-input',
|
|
3313
|
-
type: 'password',
|
|
3314
|
-
value: tokenDraft,
|
|
3315
|
-
placeholder:
|
|
3316
|
-
state.health?.settings?.ingest?.tokenSet === true ? t('settings.tokenSet') : t('settings.token'),
|
|
3317
|
-
autoComplete: 'new-password',
|
|
3318
|
-
onChange: (event) => setTokenDraft(event.target.value),
|
|
3319
|
-
}),
|
|
3320
3465
|
h(
|
|
3321
3466
|
'div',
|
|
3322
|
-
{ className: 'gt-settings-
|
|
3323
|
-
h('button', { type: 'button', className: 'gt-btn gt-btn-quiet', onClick: generateToken }, t('settings.generate')),
|
|
3467
|
+
{ className: 'gt-settings-foot' },
|
|
3324
3468
|
h('button', { type: 'button', className: 'gt-btn gt-btn-quiet', onClick: () => setSettingsOpen(false) }, t('settings.cancel')),
|
|
3325
3469
|
h('button', { type: 'button', className: 'gt-btn gt-btn-primary', onClick: () => void saveSettings() }, t('settings.save')),
|
|
3326
3470
|
),
|
|
3327
|
-
h(
|
|
3328
|
-
'label',
|
|
3329
|
-
{ className: 'gt-check-line' },
|
|
3330
|
-
t('settings.confirmBelow'),
|
|
3331
|
-
h('input', {
|
|
3332
|
-
type: 'number',
|
|
3333
|
-
min: 0,
|
|
3334
|
-
max: 1,
|
|
3335
|
-
step: 0.05,
|
|
3336
|
-
className: 'gt-input gt-input-xs',
|
|
3337
|
-
value: belowDraft,
|
|
3338
|
-
onChange: (event) => setBelowDraft(event.target.value),
|
|
3339
|
-
}),
|
|
3340
|
-
),
|
|
3341
|
-
h('div', { className: 'gt-section-title' }, t('settings.about')),
|
|
3342
|
-
h(
|
|
3343
|
-
'div',
|
|
3344
|
-
{ className: 'gt-muted gt-mono gt-settings-path', title: state.health?.store?.file ?? '' },
|
|
3345
|
-
state.health?.store?.file ?? '',
|
|
3346
|
-
),
|
|
3347
|
-
h('div', { className: 'gt-muted' }, `v${state.health?.version ?? ''} · ${UI_REV}`),
|
|
3348
3471
|
)
|
|
3349
3472
|
: null,
|
|
3350
3473
|
|
|
@@ -3692,7 +3815,7 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
3692
3815
|
? h(
|
|
3693
3816
|
'div',
|
|
3694
3817
|
{ className: 'gt-selectbar' },
|
|
3695
|
-
h('span',
|
|
3818
|
+
h('span', { className: 'gt-selectbar-count' }, t('select.count', { n: Object.keys(selected).length })),
|
|
3696
3819
|
h(
|
|
3697
3820
|
'button',
|
|
3698
3821
|
{
|
|
@@ -3714,11 +3837,17 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
3714
3837
|
? t('select.allOn')
|
|
3715
3838
|
: t('select.all'),
|
|
3716
3839
|
),
|
|
3840
|
+
h(
|
|
3841
|
+
'button',
|
|
3842
|
+
{ type: 'button', className: 'gt-btn gt-btn-quiet', onClick: () => setSelected({}) },
|
|
3843
|
+
t('select.clear'),
|
|
3844
|
+
),
|
|
3845
|
+
h('span', { className: 'gt-spacer' }),
|
|
3717
3846
|
h(
|
|
3718
3847
|
'button',
|
|
3719
3848
|
{
|
|
3720
3849
|
type: 'button',
|
|
3721
|
-
className: 'gt-btn gt-btn-
|
|
3850
|
+
className: 'gt-btn gt-btn-primary',
|
|
3722
3851
|
disabled: assignBusy || Object.keys(selected).length === 0,
|
|
3723
3852
|
onClick: () => void runAssign(Object.keys(selected)),
|
|
3724
3853
|
},
|
|
@@ -3792,22 +3921,19 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
3792
3921
|
},
|
|
3793
3922
|
t('select.addTag'),
|
|
3794
3923
|
),
|
|
3795
|
-
h(
|
|
3796
|
-
'button',
|
|
3797
|
-
{ type: 'button', className: 'gt-btn gt-btn-quiet', onClick: () => setSelected({}) },
|
|
3798
|
-
t('select.clear'),
|
|
3799
|
-
),
|
|
3800
3924
|
h(
|
|
3801
3925
|
'button',
|
|
3802
3926
|
{
|
|
3803
3927
|
type: 'button',
|
|
3804
|
-
className: 'gt-
|
|
3928
|
+
className: 'gt-iconbtn',
|
|
3929
|
+
title: t('select.done'),
|
|
3930
|
+
'aria-label': t('select.done'),
|
|
3805
3931
|
onClick: () => {
|
|
3806
3932
|
setSelectMode(false)
|
|
3807
3933
|
setSelected({})
|
|
3808
3934
|
},
|
|
3809
3935
|
},
|
|
3810
|
-
|
|
3936
|
+
h(Icon, { name: 'close', size: 13 }),
|
|
3811
3937
|
),
|
|
3812
3938
|
)
|
|
3813
3939
|
: null,
|
|
@@ -3818,7 +3944,7 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
3818
3944
|
? null
|
|
3819
3945
|
: h(
|
|
3820
3946
|
'div',
|
|
3821
|
-
{ className: 'gt-reminders', role: 'status' },
|
|
3947
|
+
{ className: 'gt-reminders', role: 'status', title: t('reminder.note') },
|
|
3822
3948
|
h('span', { className: 'gt-reminders-title' }, t('reminder.title', { n: remindersDue.length })),
|
|
3823
3949
|
...remindersDue.slice(0, 3).map((item) =>
|
|
3824
3950
|
h(
|
|
@@ -3852,7 +3978,7 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
3852
3978
|
h(
|
|
3853
3979
|
'button',
|
|
3854
3980
|
{ type: 'button', className: 'gt-btn gt-btn-quiet', onClick: () => void ackReminders(remindersDue.map((item) => item.id)) },
|
|
3855
|
-
t('reminder.
|
|
3981
|
+
t('reminder.dismissAll'),
|
|
3856
3982
|
),
|
|
3857
3983
|
),
|
|
3858
3984
|
|
|
@@ -3927,8 +4053,18 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
3927
4053
|
},
|
|
3928
4054
|
t('empty.add'),
|
|
3929
4055
|
)
|
|
3930
|
-
:
|
|
3931
|
-
|
|
4056
|
+
: h(
|
|
4057
|
+
'button',
|
|
4058
|
+
{
|
|
4059
|
+
type: 'button',
|
|
4060
|
+
className: 'gt-btn gt-btn-quiet',
|
|
4061
|
+
onClick: () => {
|
|
4062
|
+
setQuery('')
|
|
4063
|
+
void reload({ filter: 'active', query: '' })
|
|
4064
|
+
},
|
|
4065
|
+
},
|
|
4066
|
+
t('empty.clearFilter'),
|
|
4067
|
+
),
|
|
3932
4068
|
)
|
|
3933
4069
|
: h(
|
|
3934
4070
|
'div',
|
package/lib/index.js
CHANGED
|
@@ -36,7 +36,7 @@ import { createStore, resolveStorePath } from './store.js'
|
|
|
36
36
|
import { createTools } from './tools.js'
|
|
37
37
|
|
|
38
38
|
/** Must equal `package.json`'s version; `npm run check` enforces that. */
|
|
39
|
-
export const VERSION = '0.6.
|
|
39
|
+
export const VERSION = '0.6.4'
|
|
40
40
|
|
|
41
41
|
/** Plugin id. Also the Loader row id in `cordis.patch.yml`. */
|
|
42
42
|
export const name = 'dsh-global-todo'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-global-todo",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.4",
|
|
4
4
|
"icon": "./docs/icon.svg",
|
|
5
5
|
"description": "Global, cross-session todos for DeepSeek Harness: one list owned by the human and the agent together, with creation time, attached documents and links, and one-click hand-off to an agent session. 全局待办:跨会话一份清单,记录加入时间,可挂文档与地址,可一键交给 Agent 继续干。",
|
|
6
6
|
"type": "module",
|