dsh-worktodo 0.0.0-stage → 0.1.3

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 ADDED
@@ -0,0 +1,1111 @@
1
+ window.__ModuleLoader__.load({ id: "dsh-worktodo", factory: (require) => {
2
+ var module = { exports: {} }; var exports = module.exports;
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
29
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
+
31
+ // src/client/index.js
32
+ var index_exports = {};
33
+ __export(index_exports, {
34
+ apply: () => apply,
35
+ inject: () => inject
36
+ });
37
+ module.exports = __toCommonJS(index_exports);
38
+ var import_react = __toESM(require("react"), 1);
39
+ var PKG_NAME = true ? "dsh-worktodo" : "dsh-worktodo";
40
+ var DERIVED_NAME = PKG_NAME.replace(/^dsh-/, "");
41
+ var SHORT_NAME = DERIVED_NAME === "worktodo" ? DERIVED_NAME : "worktodo";
42
+ var SLOT_ID = SHORT_NAME;
43
+ var ROUTE_BASE = `/${SHORT_NAME}`;
44
+ var LOCALE_NS = SHORT_NAME;
45
+ var ROUTE_LIST = `${ROUTE_BASE}/list`;
46
+ var zh = {
47
+ "nav": "\u5F85\u529E",
48
+ "title": "\u5DE5\u4F5C\u5F85\u529E",
49
+ "statOpen": "\u672A\u5B8C\u6210",
50
+ "statOverdue": "\u903E\u671F",
51
+ "statToday": "\u4ECA\u5929",
52
+ "statDoing": "\u8FDB\u884C\u4E2D",
53
+ "statDoneToday": "\u4ECA\u65E5\u5B8C\u6210",
54
+ "refresh": "\u5237\u65B0",
55
+ "loading": "\u8BFB\u53D6\u5F85\u529E\u2026",
56
+ "failed": "\u8BFB\u53D6\u5931\u8D25",
57
+ "retry": "\u91CD\u8BD5",
58
+ "dismiss": "\u5173\u95ED",
59
+ "storeAt": "\u5B58\u50A8\u4F4D\u7F6E",
60
+ "storeDomain": "\u5B98\u65B9 storage",
61
+ "storeFile": "JSON \u6587\u4EF6",
62
+ "addPlaceholder": "\u52A0\u4E00\u4EF6\u8981\u505A\u7684\u4E8B\uFF0C\u56DE\u8F66\u4FDD\u5B58",
63
+ "add": "\u6DFB\u52A0",
64
+ "dueLabel": "\u622A\u6B62",
65
+ "priorityLabel": "\u4F18\u5148\u7EA7",
66
+ "prioHigh": "\u9AD8",
67
+ "prioNormal": "\u666E\u901A",
68
+ "prioLow": "\u4F4E",
69
+ "groupOverdue": "\u903E\u671F",
70
+ "groupToday": "\u4ECA\u5929",
71
+ "groupWeek": "\u672C\u5468",
72
+ "groupLater": "\u4EE5\u540E",
73
+ "groupCompleted": "\u5DF2\u5B8C\u6210",
74
+ "viewDue": "\u6309\u622A\u6B62",
75
+ "viewProject": "\u6309\u9879\u76EE",
76
+ "noProject": "\u672A\u5F52\u7C7B",
77
+ "sortLabel": "\u6392\u5E8F",
78
+ "sortManual": "\u624B\u52A8",
79
+ "sortPriority": "\u4F18\u5148\u7EA7",
80
+ "sortCreated": "\u521B\u5EFA\u65F6\u95F4",
81
+ "dragHint": "\u62D6\u52A8\u8C03\u6574\u987A\u5E8F\uFF08\u4F1A\u81EA\u52A8\u5207\u5230\u624B\u52A8\u6392\u5E8F\uFF09",
82
+ "expand": "\u5C55\u5F00",
83
+ "collapse": "\u6536\u8D77",
84
+ "empty": "\u8FD8\u6CA1\u6709\u5F85\u529E",
85
+ "emptyHint": "\u5728\u4E0A\u9762\u8F93\u5165\u6846\u52A0\u7B2C\u4E00\u4EF6\u4E8B",
86
+ "overdueTag": "\u903E\u671F",
87
+ "sourceAgent": "AI",
88
+ "sourceSession": "\u4F1A\u8BDD",
89
+ "markDone": "\u6807\u4E3A\u5B8C\u6210",
90
+ "unmarkDone": "\u53D6\u6D88\u5B8C\u6210",
91
+ "editHint": "\u70B9\u51FB\u6807\u9898\u53EF\u7F16\u8F91",
92
+ "deleteBtn": "\u5220\u9664",
93
+ "confirmDelete": "\u786E\u8BA4\u5220\u9664\uFF1F",
94
+ "save": "\u4FDD\u5B58",
95
+ "cancel": "\u53D6\u6D88",
96
+ "projectLabel": "\u9879\u76EE",
97
+ "projectPlaceholder": "\u9879\u76EE\u540D\uFF08\u53EF\u9009\uFF09",
98
+ "titlePlaceholder": "\u5F85\u529E\u6807\u9898",
99
+ "notesLabel": "\u5907\u6CE8",
100
+ "notesPlaceholder": "\u5907\u6CE8\uFF08\u53EF\u9009\uFF09",
101
+ "notesAdd": "\u52A0\u5907\u6CE8",
102
+ "notesHide": "\u6536\u8D77\u5907\u6CE8",
103
+ "adoptPre": "\u628A\u672C\u4F1A\u8BDD\u7684",
104
+ "adoptPost": "\u6761\u5F85\u529E\u63D0\u5347\u4E3A\u5DE5\u4F5C\u5F85\u529E",
105
+ "adoptHint": "\u8FD9\u662F\u6A21\u578B\u5728\u672C\u4F1A\u8BDD\u91CC\u5217\u7684\u5F85\u529E\u3002\u63D0\u5347\u540E\u5F52\u4F60\u6240\u6709\uFF1A\u8DE8\u4F1A\u8BDD\u4FDD\u7559\uFF0C\u8FD8\u80FD\u52A0\u4F18\u5148\u7EA7\u548C\u622A\u6B62\u65E5\u671F\u3002",
106
+ "adoptPending": "\u5F85\u5B8C\u6210",
107
+ "adoptDone": "\u5DF2\u63D0\u5347",
108
+ "adoptSkipped": "\u8DF3\u8FC7",
109
+ "titleEmpty": "\u8BF7\u5148\u5199\u70B9\u4EC0\u4E48",
110
+ "untitled": "\uFF08\u65E0\u6807\u9898\uFF09"
111
+ };
112
+ var en = {
113
+ "nav": "To-dos",
114
+ "title": "Work to-dos",
115
+ "statOpen": "Open",
116
+ "statOverdue": "Overdue",
117
+ "statToday": "Today",
118
+ "statDoing": "In progress",
119
+ "statDoneToday": "Done today",
120
+ "refresh": "Refresh",
121
+ "loading": "Loading to-dos\u2026",
122
+ "failed": "Failed to load",
123
+ "retry": "Retry",
124
+ "dismiss": "Dismiss",
125
+ "storeAt": "Stored at",
126
+ "storeDomain": "official storage",
127
+ "storeFile": "JSON file",
128
+ "addPlaceholder": "Add something to do \u2014 Enter saves",
129
+ "add": "Add",
130
+ "dueLabel": "Due",
131
+ "priorityLabel": "Priority",
132
+ "prioHigh": "High",
133
+ "prioNormal": "Normal",
134
+ "prioLow": "Low",
135
+ "groupOverdue": "Overdue",
136
+ "groupToday": "Today",
137
+ "groupWeek": "This week",
138
+ "groupLater": "Later",
139
+ "groupCompleted": "Completed",
140
+ "viewDue": "By due",
141
+ "viewProject": "By project",
142
+ "noProject": "Uncategorized",
143
+ "sortLabel": "Sort",
144
+ "sortManual": "Manual",
145
+ "sortPriority": "Priority",
146
+ "sortCreated": "Created",
147
+ "dragHint": "Drag to reorder (switches to manual sort)",
148
+ "expand": "Expand",
149
+ "collapse": "Collapse",
150
+ "empty": "No to-dos yet",
151
+ "emptyHint": "Add your first one in the box above",
152
+ "overdueTag": "overdue",
153
+ "sourceAgent": "AI",
154
+ "sourceSession": "Session",
155
+ "markDone": "Mark as done",
156
+ "unmarkDone": "Mark as not done",
157
+ "editHint": "Click the title to edit",
158
+ "deleteBtn": "Delete",
159
+ "confirmDelete": "Delete?",
160
+ "save": "Save",
161
+ "cancel": "Cancel",
162
+ "projectLabel": "Project",
163
+ "projectPlaceholder": "project (optional)",
164
+ "titlePlaceholder": "To-do title",
165
+ "notesLabel": "Notes",
166
+ "notesPlaceholder": "Notes (optional)",
167
+ "notesAdd": "Add notes",
168
+ "notesHide": "Hide notes",
169
+ "adoptPre": "Promote this session's ",
170
+ "adoptPost": " to-dos into your work list",
171
+ "adoptHint": "These are the to-dos the model listed in this session. Promoting makes them yours: they survive across sessions and take priorities and due dates.",
172
+ "adoptPending": "open",
173
+ "adoptDone": "Promoted",
174
+ "adoptSkipped": "skipped",
175
+ "titleEmpty": "Type something first",
176
+ "untitled": "(untitled)"
177
+ };
178
+ var C = {
179
+ label: "var(--dsw-alias-label-primary)",
180
+ label2: "var(--dsw-alias-label-secondary)",
181
+ label3: "var(--dsw-alias-label-tertiary)",
182
+ border: "var(--dsw-alias-border-l2)",
183
+ border1: "var(--dsw-alias-border-l1)",
184
+ layer1: "var(--dsw-alias-bg-layer-1)",
185
+ hover: "var(--dsw-alias-interactive-bg-hover)",
186
+ brand: "var(--dsw-alias-brand-primary)",
187
+ warn: "var(--dsw-alias-state-warn-primary)",
188
+ error: "var(--dsw-alias-state-error-primary)",
189
+ ok: "var(--dsw-alias-state-success-primary)"
190
+ };
191
+ var tint = (color, pct) => `color-mix(in srgb, ${color} ${pct}%, transparent)`;
192
+ var STYLE_TEXT = `
193
+ @keyframes wt-spin { to { transform: rotate(360deg); } }
194
+ .wt-root {
195
+ height: 100%; overflow: auto; padding: 16px 18px 40px; box-sizing: border-box;
196
+ color: ${C.label}; font-family: var(--dsw-font-family, system-ui, sans-serif); font-size: 13px;
197
+ }
198
+ .wt-input {
199
+ min-width: 0; font-family: inherit; font-size: 12px; line-height: 1.5; padding: 4px 8px;
200
+ border-radius: 8px; border: 1px solid ${C.border}; background: transparent; color: ${C.label};
201
+ color-scheme: light dark;
202
+ }
203
+ .wt-input::placeholder { color: ${C.label3}; }
204
+ .wt-input:focus {
205
+ outline: none; border-color: ${C.brand};
206
+ box-shadow: 0 0 0 2px ${tint(C.brand, 22)};
207
+ }
208
+ .wt-btn {
209
+ display: inline-flex; align-items: center; gap: 4px; font-family: inherit; font-size: 12px;
210
+ line-height: 1.6; padding: 3px 10px; border-radius: 8px; cursor: pointer; user-select: none;
211
+ border: 1px solid ${C.border}; background: transparent; color: ${C.label2}; white-space: nowrap;
212
+ }
213
+ .wt-btn:hover { background: ${C.hover}; color: ${C.label}; }
214
+ .wt-btn:disabled { opacity: .55; cursor: default; }
215
+ .wt-btn:disabled:hover { background: transparent; color: ${C.label2}; }
216
+ .wt-btn-primary {
217
+ border-color: ${tint(C.brand, 50)}; color: ${C.brand}; background: ${tint(C.brand, 10)};
218
+ }
219
+ .wt-btn-primary:hover { background: ${tint(C.brand, 18)}; color: ${C.brand}; }
220
+ .wt-btn-primary:disabled:hover { background: ${tint(C.brand, 10)}; color: ${C.brand}; }
221
+ .wt-btn-danger { color: ${C.error}; border-color: ${tint(C.error, 45)}; }
222
+ .wt-btn-danger:hover { background: var(--dsw-alias-interactive-bg-hover-danger); color: ${C.error}; }
223
+ .wt-icon-btn {
224
+ display: inline-flex; align-items: center; justify-content: center; flex: none;
225
+ width: 22px; height: 22px; padding: 0; border: none; border-radius: 6px;
226
+ background: transparent; color: ${C.label3}; cursor: pointer;
227
+ }
228
+ .wt-icon-btn:hover { background: ${C.hover}; color: ${C.label}; }
229
+ .wt-icon-btn-danger:hover { background: var(--dsw-alias-interactive-bg-hover-danger); color: ${C.error}; }
230
+ .wt-check {
231
+ display: inline-flex; align-items: center; justify-content: center; flex: none;
232
+ width: 17px; height: 17px; margin-top: 1px; padding: 0; border-radius: 5px;
233
+ border: 1px solid ${C.border}; background: transparent; color: transparent; cursor: pointer;
234
+ }
235
+ .wt-check:hover { border-color: ${C.brand}; }
236
+ .wt-check-done {
237
+ border-color: ${tint(C.ok, 55)}; background: ${tint(C.ok, 14)}; color: ${C.ok};
238
+ }
239
+ .wt-row { display: flex; align-items: flex-start; gap: 8px; padding: 5px 8px; border-radius: 10px; }
240
+ .wt-row:hover { background: ${C.hover}; }
241
+ .wt-row-edit {
242
+ flex-direction: column; align-items: stretch; gap: 6px;
243
+ border: 1px solid ${C.border1}; background: ${C.layer1};
244
+ }
245
+ .wt-seg {
246
+ display: inline-flex; align-items: center; border-radius: 8px; overflow: hidden;
247
+ border: 1px solid ${C.border}; background: ${C.bg2};
248
+ }
249
+ .wt-seg-btn {
250
+ appearance: none; border: 0; background: transparent; color: ${C.label2};
251
+ font: inherit; font-size: 11.5px; line-height: 1; padding: 5px 9px; cursor: pointer;
252
+ }
253
+ .wt-seg-btn.is-on { background: ${C.hover}; color: ${C.label}; font-weight: 600; }
254
+ .wt-seg-btn:hover { color: ${C.label}; }
255
+ .wt-row[draggable="true"] { cursor: grab; }
256
+ .wt-row[draggable="true"]:active { cursor: grabbing; }
257
+ .wt-group-head {
258
+ display: flex; align-items: center; gap: 7px; padding: 3px 8px; border-radius: 8px;
259
+ cursor: pointer; user-select: none; color: ${C.label2};
260
+ }
261
+ .wt-group-head:hover { background: ${C.hover}; }
262
+ .wt-chip {
263
+ display: inline-flex; align-items: baseline; gap: 4px; font-size: 11px; line-height: 1.7;
264
+ padding: 0 8px; border-radius: 999px; border: 1px solid ${C.border1}; color: ${C.label3};
265
+ white-space: nowrap;
266
+ }
267
+ .wt-tag {
268
+ display: inline-flex; align-items: center; gap: 3px; font-size: 10.5px; line-height: 1.6;
269
+ padding: 0 6px; border-radius: 999px; border: 1px solid ${C.border1}; color: ${C.label3};
270
+ white-space: nowrap;
271
+ }
272
+ .wt-link { cursor: pointer; user-select: none; color: var(--dsw-alias-link); font-size: 11px; }
273
+ .wt-link:hover { text-decoration: underline; }
274
+ .wt-title { cursor: pointer; word-break: break-word; }
275
+ .wt-title:hover { text-decoration: underline; text-decoration-style: dotted; text-underline-offset: 3px; }
276
+ .wt-spin {
277
+ display: inline-block; width: 12px; height: 12px; border-radius: 50%;
278
+ border: 2px solid ${C.border}; border-top-color: ${C.brand}; animation: wt-spin .8s linear infinite;
279
+ }
280
+ `;
281
+ function WorkTodoIcon({ size = 16, active }) {
282
+ return import_react.default.createElement("svg", {
283
+ width: size,
284
+ height: size,
285
+ viewBox: "0 0 16 16",
286
+ fill: "none",
287
+ stroke: "currentColor",
288
+ strokeWidth: 1.3,
289
+ strokeLinecap: "round",
290
+ strokeLinejoin: "round",
291
+ style: { opacity: active ? 1 : 0.82 }
292
+ }, [
293
+ import_react.default.createElement("rect", { key: "a", x: "2.7", y: "3.1", width: "10.6", height: "10.4", rx: "2" }),
294
+ import_react.default.createElement("path", { key: "b", d: "M5.6 1.7h4.8v2.7H5.6z" }),
295
+ import_react.default.createElement("path", { key: "c", d: "M5.7 9.3l1.7 1.7 3.1-3.5" })
296
+ ]);
297
+ }
298
+ function IconCheck({ size = 11 }) {
299
+ return import_react.default.createElement("svg", {
300
+ width: size,
301
+ height: size,
302
+ viewBox: "0 0 12 12",
303
+ fill: "none",
304
+ stroke: "currentColor",
305
+ strokeWidth: 2,
306
+ strokeLinecap: "round",
307
+ strokeLinejoin: "round"
308
+ }, import_react.default.createElement("path", { d: "M2 6.3 4.6 8.9 10 3.1" }));
309
+ }
310
+ function IconChevron({ open, size = 10 }) {
311
+ return import_react.default.createElement("svg", {
312
+ width: size,
313
+ height: size,
314
+ viewBox: "0 0 14 14",
315
+ fill: "none",
316
+ stroke: "currentColor",
317
+ strokeWidth: 1.8,
318
+ strokeLinecap: "round",
319
+ strokeLinejoin: "round",
320
+ // 收起时把「向下」的箭头转成「向右」:比画两个图标便宜,也不会因为尺寸不同而跳一下
321
+ style: { flex: "none", transform: open ? "none" : "rotate(-90deg)", transition: "transform .15s ease" }
322
+ }, import_react.default.createElement("path", { d: "m3.4 5.2 3.6 3.6 3.6-3.6" }));
323
+ }
324
+ function IconTrash({ size = 13 }) {
325
+ return import_react.default.createElement("svg", {
326
+ width: size,
327
+ height: size,
328
+ viewBox: "0 0 16 16",
329
+ fill: "none",
330
+ stroke: "currentColor",
331
+ strokeWidth: 1.3,
332
+ strokeLinecap: "round",
333
+ strokeLinejoin: "round"
334
+ }, [
335
+ import_react.default.createElement("path", { key: "a", d: "M2.8 4.4h10.4" }),
336
+ import_react.default.createElement("path", { key: "b", d: "M6.4 4.4V2.9h3.2v1.5" }),
337
+ import_react.default.createElement("path", { key: "c", d: "M4.5 4.4l.7 8.4h5.6l.7-8.4" }),
338
+ import_react.default.createElement("path", { key: "d", d: "M6.9 6.8v3.9M9.1 6.8v3.9" })
339
+ ]);
340
+ }
341
+ function IconClose({ size = 11 }) {
342
+ return import_react.default.createElement("svg", {
343
+ width: size,
344
+ height: size,
345
+ viewBox: "0 0 12 12",
346
+ fill: "none",
347
+ stroke: "currentColor",
348
+ strokeWidth: 1.6,
349
+ strokeLinecap: "round"
350
+ }, [
351
+ import_react.default.createElement("path", { key: "a", d: "M3 3l6 6" }),
352
+ import_react.default.createElement("path", { key: "b", d: "M9 3l-6 6" })
353
+ ]);
354
+ }
355
+ function IconRefresh({ size = 12 }) {
356
+ return import_react.default.createElement("svg", {
357
+ width: size,
358
+ height: size,
359
+ viewBox: "0 0 16 16",
360
+ fill: "none",
361
+ stroke: "currentColor",
362
+ strokeWidth: 1.4,
363
+ strokeLinecap: "round",
364
+ strokeLinejoin: "round"
365
+ }, [
366
+ import_react.default.createElement("path", { key: "a", d: "M2.6 8a5.4 5.4 0 0 1 9.1-3.9" }),
367
+ import_react.default.createElement("path", { key: "b", d: "M13.4 8a5.4 5.4 0 0 1-9.1 3.9" }),
368
+ import_react.default.createElement("path", { key: "c", d: "M12.3 1.9v2.6H9.7" }),
369
+ import_react.default.createElement("path", { key: "d", d: "M3.7 14.1v-2.6h2.6" })
370
+ ]);
371
+ }
372
+ function localToday() {
373
+ const d = /* @__PURE__ */ new Date();
374
+ const pad = (n) => String(n).padStart(2, "0");
375
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
376
+ }
377
+ var asArray = (value) => Array.isArray(value) ? value : [];
378
+ async function postJson(path, body) {
379
+ const response = await fetch(`${ROUTE_BASE}${path}`, {
380
+ method: "POST",
381
+ headers: { "content-type": "application/json" },
382
+ cache: "no-store",
383
+ credentials: "same-origin",
384
+ body: JSON.stringify(body ?? {})
385
+ });
386
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
387
+ const parsed = await response.json().catch(() => null);
388
+ if (!parsed) throw new Error("\u54CD\u5E94\u4E0D\u662F JSON");
389
+ if (!parsed.ok) throw new Error(parsed.error ?? parsed.reason ?? "\u672A\u77E5\u9519\u8BEF");
390
+ return parsed.data ?? {};
391
+ }
392
+ var lastList = null;
393
+ var lastSort = "manual";
394
+ function useList(sort) {
395
+ const [state, setState] = import_react.default.useState(() => lastList ? { status: "ready", data: lastList, error: null } : { status: "loading", data: null, error: null });
396
+ const [tick, setTick] = import_react.default.useState(0);
397
+ import_react.default.useEffect(() => {
398
+ let alive = true;
399
+ fetch(`${ROUTE_LIST}?filter=all&sort=${encodeURIComponent(sort || "manual")}`, { cache: "no-store", credentials: "same-origin" }).then(async (response) => {
400
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
401
+ const body = await response.json().catch(() => null);
402
+ if (!body?.ok) throw new Error(body?.error ?? body?.reason ?? "\u672A\u77E5\u9519\u8BEF");
403
+ lastList = body.data;
404
+ if (alive) setState({ status: "ready", data: body.data, error: null });
405
+ }).catch((error) => {
406
+ if (alive) {
407
+ setState((prev) => ({
408
+ ...prev,
409
+ status: prev.data ? "ready" : "failed",
410
+ error: String(error?.message ?? error)
411
+ }));
412
+ }
413
+ });
414
+ return () => {
415
+ alive = false;
416
+ };
417
+ }, [tick, sort]);
418
+ const refresh = import_react.default.useCallback(() => setTick((n) => n + 1), []);
419
+ return { ...state, refresh };
420
+ }
421
+ function QuickAdd({ t, onAdd, busy }) {
422
+ const [title, setTitle] = import_react.default.useState("");
423
+ const [due, setDue] = import_react.default.useState("");
424
+ const [priority, setPriority] = import_react.default.useState("normal");
425
+ const submit = () => {
426
+ if (busy) return;
427
+ Promise.resolve(onAdd({ title, due, priority })).then((ok) => {
428
+ if (ok !== false) setTitle("");
429
+ }).catch(() => {
430
+ });
431
+ };
432
+ return import_react.default.createElement("div", {
433
+ style: { display: "flex", alignItems: "center", gap: 6, flexWrap: "wrap", marginBottom: 10 }
434
+ }, [
435
+ import_react.default.createElement("input", {
436
+ key: "ti",
437
+ className: "wt-input",
438
+ style: { flex: "1 1 200px", fontSize: 12.5, padding: "5px 9px" },
439
+ value: title,
440
+ placeholder: t("addPlaceholder"),
441
+ "aria-label": t("addPlaceholder"),
442
+ onChange: (event) => setTitle(event.target.value),
443
+ onKeyDown: (event) => {
444
+ if (event.key === "Enter") submit();
445
+ }
446
+ }),
447
+ import_react.default.createElement("label", {
448
+ key: "due",
449
+ title: t("dueLabel"),
450
+ style: { display: "inline-flex", alignItems: "center", gap: 4, fontSize: 11, color: C.label3 }
451
+ }, [
452
+ t("dueLabel"),
453
+ import_react.default.createElement("input", {
454
+ key: "i",
455
+ className: "wt-input",
456
+ type: "date",
457
+ value: due,
458
+ "aria-label": t("dueLabel"),
459
+ onChange: (event) => setDue(event.target.value)
460
+ })
461
+ ]),
462
+ import_react.default.createElement("label", {
463
+ key: "prio",
464
+ title: t("priorityLabel"),
465
+ style: { display: "inline-flex", alignItems: "center", gap: 4, fontSize: 11, color: C.label3 }
466
+ }, [
467
+ t("priorityLabel"),
468
+ import_react.default.createElement("select", {
469
+ key: "s",
470
+ className: "wt-input",
471
+ value: priority,
472
+ "aria-label": t("priorityLabel"),
473
+ onChange: (event) => setPriority(event.target.value)
474
+ }, [
475
+ import_react.default.createElement("option", { key: "high", value: "high" }, t("prioHigh")),
476
+ import_react.default.createElement("option", { key: "normal", value: "normal" }, t("prioNormal")),
477
+ import_react.default.createElement("option", { key: "low", value: "low" }, t("prioLow"))
478
+ ])
479
+ ]),
480
+ import_react.default.createElement("button", {
481
+ key: "add",
482
+ type: "button",
483
+ className: "wt-btn wt-btn-primary",
484
+ disabled: Boolean(busy),
485
+ onClick: submit
486
+ }, t("add"))
487
+ ]);
488
+ }
489
+ function TodoRow({ todo, today, t, onToggle, onRemove, onSave, drag }) {
490
+ const [editing, setEditing] = import_react.default.useState(false);
491
+ const [draft, setDraft] = import_react.default.useState(null);
492
+ const [notesOpen, setNotesOpen] = import_react.default.useState(false);
493
+ const [confirming, setConfirming] = import_react.default.useState(false);
494
+ import_react.default.useEffect(() => {
495
+ if (!confirming) return void 0;
496
+ const id = setTimeout(() => setConfirming(false), 5e3);
497
+ return () => clearTimeout(id);
498
+ }, [confirming]);
499
+ const row = todo ?? {};
500
+ const done = row.status === "completed";
501
+ const due = typeof row.due === "string" ? row.due : "";
502
+ const overdue = !done && Boolean(due) && Boolean(today) && due < today;
503
+ const beginEdit = () => {
504
+ setDraft({
505
+ title: typeof row.title === "string" ? row.title : "",
506
+ due,
507
+ priority: row.priority === "high" || row.priority === "low" ? row.priority : "normal",
508
+ project: typeof row.project === "string" ? row.project : "",
509
+ notes: typeof row.notes === "string" ? row.notes : ""
510
+ });
511
+ setNotesOpen(Boolean(row.notes));
512
+ setEditing(true);
513
+ };
514
+ const stopEdit = () => {
515
+ setEditing(false);
516
+ setDraft(null);
517
+ };
518
+ const submit = () => {
519
+ if (!draft) return;
520
+ const patch = {};
521
+ const title = String(draft.title ?? "").trim();
522
+ if (title !== (row.title ?? "")) patch.title = title;
523
+ const nextDue = draft.due || "";
524
+ if (nextDue !== due) patch.due = nextDue;
525
+ const priority = draft.priority || "normal";
526
+ if (priority !== (row.priority ?? "normal")) patch.priority = priority;
527
+ const project = String(draft.project ?? "").trim();
528
+ if (project !== (row.project ?? "")) patch.project = project;
529
+ const notes = String(draft.notes ?? "").trim();
530
+ if (notes !== (row.notes ?? "")) patch.notes = notes;
531
+ stopEdit();
532
+ if (!Object.keys(patch).length) return;
533
+ Promise.resolve(onSave(row.id, patch)).catch(() => {
534
+ });
535
+ };
536
+ const inputStyle = { fontSize: 12, padding: "3px 7px" };
537
+ if (editing && draft) {
538
+ const field = (key, label, control) => import_react.default.createElement("label", {
539
+ key,
540
+ style: { display: "inline-flex", alignItems: "center", gap: 4, fontSize: 11, color: C.label3 }
541
+ }, [label, control]);
542
+ return import_react.default.createElement("div", { className: "wt-row wt-row-edit" }, [
543
+ import_react.default.createElement("div", { key: "top", style: { display: "flex", gap: 6, alignItems: "center" } }, [
544
+ import_react.default.createElement("input", {
545
+ key: "ti",
546
+ className: "wt-input",
547
+ style: { ...inputStyle, flex: 1, minWidth: 0 },
548
+ value: draft.title,
549
+ autoFocus: true,
550
+ placeholder: t("titlePlaceholder"),
551
+ "aria-label": t("titlePlaceholder"),
552
+ onChange: (event) => setDraft((prev) => ({ ...prev, title: event.target.value })),
553
+ onKeyDown: (event) => {
554
+ if (event.key === "Enter") submit();
555
+ if (event.key === "Escape") stopEdit();
556
+ }
557
+ }),
558
+ import_react.default.createElement("button", {
559
+ key: "save",
560
+ type: "button",
561
+ className: "wt-btn wt-btn-primary",
562
+ onClick: submit
563
+ }, t("save")),
564
+ import_react.default.createElement("button", {
565
+ key: "cancel",
566
+ type: "button",
567
+ className: "wt-btn",
568
+ onClick: stopEdit
569
+ }, t("cancel"))
570
+ ]),
571
+ import_react.default.createElement("div", {
572
+ key: "fields",
573
+ style: { display: "flex", gap: 10, flexWrap: "wrap", alignItems: "center" }
574
+ }, [
575
+ field("due", t("dueLabel"), import_react.default.createElement("input", {
576
+ className: "wt-input",
577
+ style: inputStyle,
578
+ type: "date",
579
+ value: draft.due,
580
+ "aria-label": t("dueLabel"),
581
+ onChange: (event) => setDraft((prev) => ({ ...prev, due: event.target.value }))
582
+ })),
583
+ field("prio", t("priorityLabel"), import_react.default.createElement("select", {
584
+ className: "wt-input",
585
+ style: inputStyle,
586
+ value: draft.priority,
587
+ "aria-label": t("priorityLabel"),
588
+ onChange: (event) => setDraft((prev) => ({ ...prev, priority: event.target.value }))
589
+ }, [
590
+ import_react.default.createElement("option", { key: "high", value: "high" }, t("prioHigh")),
591
+ import_react.default.createElement("option", { key: "normal", value: "normal" }, t("prioNormal")),
592
+ import_react.default.createElement("option", { key: "low", value: "low" }, t("prioLow"))
593
+ ])),
594
+ field("project", t("projectLabel"), import_react.default.createElement("input", {
595
+ className: "wt-input",
596
+ style: { ...inputStyle, width: 130 },
597
+ value: draft.project,
598
+ placeholder: t("projectPlaceholder"),
599
+ "aria-label": t("projectPlaceholder"),
600
+ onChange: (event) => setDraft((prev) => ({ ...prev, project: event.target.value }))
601
+ }))
602
+ ]),
603
+ import_react.default.createElement("div", { key: "notes" }, [
604
+ notesOpen ? import_react.default.createElement("div", { key: "box", style: { display: "flex", flexDirection: "column", gap: 4 } }, [
605
+ import_react.default.createElement("textarea", {
606
+ key: "ta",
607
+ className: "wt-input",
608
+ style: { width: "100%", boxSizing: "border-box", resize: "vertical", minHeight: 54 },
609
+ rows: 3,
610
+ value: draft.notes,
611
+ placeholder: t("notesPlaceholder"),
612
+ "aria-label": t("notesLabel"),
613
+ onChange: (event) => setDraft((prev) => ({ ...prev, notes: event.target.value })),
614
+ // textarea 里回车要换行,所以保存用 Ctrl/Cmd+Enter
615
+ onKeyDown: (event) => {
616
+ if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) submit();
617
+ if (event.key === "Escape") stopEdit();
618
+ }
619
+ }),
620
+ import_react.default.createElement("span", {
621
+ key: "hide",
622
+ className: "wt-link",
623
+ onClick: () => setNotesOpen(false)
624
+ }, t("notesHide"))
625
+ ]) : import_react.default.createElement("span", {
626
+ key: "add",
627
+ className: "wt-link",
628
+ onClick: () => setNotesOpen(true)
629
+ }, t("notesAdd"))
630
+ ])
631
+ ]);
632
+ }
633
+ const meta = [];
634
+ if (row.priority === "high") {
635
+ meta.push(import_react.default.createElement("span", {
636
+ key: "p",
637
+ className: "wt-tag",
638
+ style: { color: C.warn, borderColor: tint(C.warn, 50), background: tint(C.warn, 12), fontWeight: 600 }
639
+ }, t("prioHigh")));
640
+ } else if (row.priority === "low") {
641
+ meta.push(import_react.default.createElement("span", { key: "p", className: "wt-tag" }, t("prioLow")));
642
+ }
643
+ if (due) {
644
+ meta.push(import_react.default.createElement("span", {
645
+ key: "d",
646
+ style: { color: overdue ? C.warn : C.label3, fontSize: 11 }
647
+ }, overdue ? `${t("overdueTag")} ${due}` : due));
648
+ }
649
+ if (row.project) {
650
+ meta.push(import_react.default.createElement("span", { key: "j", className: "wt-tag" }, `#${row.project}`));
651
+ }
652
+ if (row.source === "agent" || row.source === "session") {
653
+ meta.push(import_react.default.createElement("span", {
654
+ key: "s",
655
+ className: "wt-tag",
656
+ title: row.source === "agent" ? t("sourceAgent") : t("sourceSession")
657
+ }, row.source === "agent" ? t("sourceAgent") : t("sourceSession")));
658
+ }
659
+ const dragAttrs = drag ? {
660
+ draggable: true,
661
+ onDragStart: (event) => {
662
+ try {
663
+ event.dataTransfer?.setData("text/plain", row.id ?? "");
664
+ if (event.dataTransfer) event.dataTransfer.effectAllowed = "move";
665
+ } catch {
666
+ }
667
+ drag.onStart?.(row.id);
668
+ },
669
+ onDragOver: (event) => {
670
+ event.preventDefault();
671
+ drag.onOver?.(row.id);
672
+ },
673
+ onDrop: (event) => {
674
+ event.preventDefault();
675
+ drag.onDrop?.(row.id);
676
+ },
677
+ onDragEnd: () => drag.onEnd?.(),
678
+ title: drag.hint,
679
+ style: drag.dragging ? { opacity: 0.45 } : void 0
680
+ } : {};
681
+ return import_react.default.createElement("div", { className: "wt-row", ...dragAttrs }, [
682
+ import_react.default.createElement("button", {
683
+ key: "check",
684
+ type: "button",
685
+ className: `wt-check${done ? " wt-check-done" : ""}`,
686
+ "aria-label": done ? t("unmarkDone") : t("markDone"),
687
+ title: done ? t("unmarkDone") : t("markDone"),
688
+ onClick: () => {
689
+ Promise.resolve(onToggle(row.id)).catch(() => {
690
+ });
691
+ }
692
+ }, done ? import_react.default.createElement(IconCheck, { key: "i" }) : null),
693
+ import_react.default.createElement("div", { key: "main", style: { flex: 1, minWidth: 0 } }, [
694
+ import_react.default.createElement("div", {
695
+ key: "title",
696
+ className: "wt-title",
697
+ title: t("editHint"),
698
+ onClick: beginEdit,
699
+ style: {
700
+ color: done ? C.label3 : C.label,
701
+ textDecoration: done ? "line-through" : "none",
702
+ fontWeight: row.priority === "high" && !done ? 650 : 400
703
+ }
704
+ }, typeof row.title === "string" && row.title ? row.title : t("untitled")),
705
+ meta.length ? import_react.default.createElement("div", {
706
+ key: "meta",
707
+ style: { display: "flex", alignItems: "center", gap: 6, flexWrap: "wrap", marginTop: 2 }
708
+ }, meta) : null
709
+ ]),
710
+ import_react.default.createElement("button", {
711
+ key: "del",
712
+ type: "button",
713
+ className: confirming ? "wt-btn wt-btn-danger" : "wt-icon-btn wt-icon-btn-danger",
714
+ title: confirming ? t("confirmDelete") : t("deleteBtn"),
715
+ "aria-label": t("deleteBtn"),
716
+ onClick: () => {
717
+ if (!confirming) {
718
+ setConfirming(true);
719
+ return;
720
+ }
721
+ setConfirming(false);
722
+ Promise.resolve(onRemove(row.id)).catch(() => {
723
+ });
724
+ }
725
+ }, confirming ? t("confirmDelete") : import_react.default.createElement(IconTrash, { key: "i" }))
726
+ ]);
727
+ }
728
+ function Group({ label, items, meta, closed, t, today, onToggleOpen, onToggleTodo, onRemove, onSave, drag }) {
729
+ const list = asArray(items);
730
+ if (!list.length) return null;
731
+ return import_react.default.createElement("div", { style: { marginTop: 6 } }, [
732
+ import_react.default.createElement("div", {
733
+ key: "head",
734
+ className: "wt-group-head",
735
+ onClick: onToggleOpen,
736
+ title: closed ? t("expand") : t("collapse"),
737
+ role: "button",
738
+ "aria-expanded": !closed
739
+ }, [
740
+ import_react.default.createElement(IconChevron, { key: "c", open: !closed }),
741
+ import_react.default.createElement("span", { key: "l", style: { fontWeight: 600, fontSize: 12 } }, label),
742
+ import_react.default.createElement("span", { key: "n", style: { fontSize: 11, color: C.label3 } }, meta || String(list.length)),
743
+ import_react.default.createElement("span", { key: "sp", style: { flex: 1 } }),
744
+ import_react.default.createElement("span", { key: "h", style: { fontSize: 11, color: C.label3 } }, closed ? t("expand") : t("collapse"))
745
+ ]),
746
+ closed ? null : import_react.default.createElement("div", { key: "body" }, list.map((todo, index) => import_react.default.createElement(TodoRow, {
747
+ key: todo && todo.id || `row-${index}`,
748
+ todo,
749
+ today,
750
+ t,
751
+ onToggle: onToggleTodo,
752
+ onRemove,
753
+ onSave,
754
+ drag: drag ? {
755
+ dragging: drag.dragId === todo?.id,
756
+ hint: drag.hint,
757
+ onStart: drag.onStart,
758
+ onOver: drag.onOver,
759
+ onDrop: drag.onDrop,
760
+ onEnd: drag.onEnd
761
+ } : void 0
762
+ })))
763
+ ]);
764
+ }
765
+ function SessionAdopt({ useProjection, t, onAdopt, busy }) {
766
+ let projected;
767
+ try {
768
+ projected = useProjection("todos");
769
+ } catch {
770
+ projected = null;
771
+ }
772
+ const raw = Array.isArray(projected) ? projected : asArray(projected?.todos);
773
+ const items = raw.map((item) => ({
774
+ content: typeof item?.content === "string" ? item.content : typeof item?.title === "string" ? item.title : "",
775
+ status: typeof item?.status === "string" ? item.status : "pending"
776
+ })).filter((item) => item.content.trim());
777
+ if (!items.length) return null;
778
+ const openCount = items.filter((item) => item.status !== "completed").length;
779
+ return import_react.default.createElement("div", {
780
+ style: { display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap", marginBottom: 10 }
781
+ }, [
782
+ import_react.default.createElement("button", {
783
+ key: "b",
784
+ type: "button",
785
+ className: "wt-btn wt-btn-primary",
786
+ disabled: Boolean(busy),
787
+ title: t("adoptHint"),
788
+ onClick: () => {
789
+ Promise.resolve(onAdopt(items)).catch(() => {
790
+ });
791
+ }
792
+ }, `${t("adoptPre")}${items.length}${t("adoptPost")}`),
793
+ import_react.default.createElement("span", {
794
+ key: "h",
795
+ title: t("adoptHint"),
796
+ style: { fontSize: 11, color: C.label3 }
797
+ }, `${t("adoptPending")} ${openCount}`)
798
+ ]);
799
+ }
800
+ var GROUPS = [
801
+ { key: "overdue", label: "groupOverdue" },
802
+ { key: "today", label: "groupToday" },
803
+ { key: "week", label: "groupWeek" },
804
+ { key: "later", label: "groupLater" },
805
+ { key: "completed", label: "groupCompleted" }
806
+ ];
807
+ function WorkTodoPanel(props) {
808
+ const t = typeof props?.t === "function" ? props.t : (key) => zh[key] ?? key;
809
+ const [sort, setSortState] = import_react.default.useState(lastSort);
810
+ const setSort = import_react.default.useCallback((next) => {
811
+ lastSort = next;
812
+ setSortState(next);
813
+ }, []);
814
+ const [dragId, setDragId] = import_react.default.useState(null);
815
+ const { status, data, error, refresh } = useList(sort);
816
+ const [barError, setBarError] = import_react.default.useState(null);
817
+ const [dismissedReadError, setDismissedReadError] = import_react.default.useState(null);
818
+ const readError = error && error !== dismissedReadError ? error : null;
819
+ const shownError = barError ?? readError;
820
+ const [notice, setNotice] = import_react.default.useState(null);
821
+ const [pending, setPending] = import_react.default.useState(0);
822
+ const [closed, setClosed] = import_react.default.useState({ completed: true });
823
+ const [view, setView] = import_react.default.useState("due");
824
+ const runWrite = import_react.default.useCallback(async (job) => {
825
+ setPending((n) => n + 1);
826
+ try {
827
+ const data2 = await job();
828
+ setBarError(null);
829
+ return { ok: true, data: data2 ?? {} };
830
+ } catch (writeError) {
831
+ setBarError(String(writeError?.message ?? writeError));
832
+ return { ok: false, data: {} };
833
+ } finally {
834
+ setPending((n) => Math.max(0, n - 1));
835
+ refresh();
836
+ }
837
+ }, [refresh]);
838
+ const handleAdd = import_react.default.useCallback((input) => {
839
+ const title = String(input?.title ?? "").trim();
840
+ if (!title) {
841
+ setBarError(t("titleEmpty"));
842
+ return Promise.resolve(false);
843
+ }
844
+ return runWrite(() => postJson("/create", {
845
+ title,
846
+ due: input?.due ? String(input.due) : null,
847
+ priority: input?.priority ?? "normal"
848
+ })).then((result) => result.ok);
849
+ }, [runWrite, t]);
850
+ const handleToggle = import_react.default.useCallback((ref) => runWrite(() => postJson("/status", { ref, toggle: true })), [runWrite]);
851
+ const handleRemove = import_react.default.useCallback((ref) => runWrite(() => postJson("/remove", { ref })), [runWrite]);
852
+ const handleSave = import_react.default.useCallback((ref, patch) => runWrite(() => postJson("/update", { ref, ...patch })), [runWrite]);
853
+ const handleDropOn = import_react.default.useCallback((targetId) => {
854
+ const dragging = dragId;
855
+ setDragId(null);
856
+ if (!dragging || !targetId || dragging === targetId) return Promise.resolve(false);
857
+ const ids = asArray(data?.todos).map((todo) => todo?.id).filter(Boolean);
858
+ const from = ids.indexOf(dragging);
859
+ if (from === -1) return Promise.resolve(false);
860
+ ids.splice(from, 1);
861
+ const to = ids.indexOf(targetId);
862
+ ids.splice(to === -1 ? ids.length : to, 0, dragging);
863
+ return runWrite(() => postJson("/reorder", { ids })).then((result) => result.ok);
864
+ }, [dragId, data, runWrite]);
865
+ const dragHandlers = import_react.default.useMemo(() => ({
866
+ dragId,
867
+ hint: t("dragHint"),
868
+ onStart: (id) => {
869
+ setDragId(id);
870
+ if (sort !== "manual") setSort("manual");
871
+ },
872
+ onOver: () => {
873
+ },
874
+ onDrop: (id) => handleDropOn(id),
875
+ onEnd: () => setDragId(null)
876
+ }), [dragId, sort, t, setSort, handleDropOn]);
877
+ const handleAdopt = import_react.default.useCallback((sessionTodos) => runWrite(() => postJson("/adopt", { todos: sessionTodos })).then((result) => {
878
+ if (result.ok) {
879
+ const created = Array.isArray(result.data?.created) ? result.data.created.length : 0;
880
+ const skipped = Number.isFinite(Number(result.data?.skipped)) ? Number(result.data.skipped) : 0;
881
+ setNotice(`${t("adoptDone")} ${created} \xB7 ${t("adoptSkipped")} ${skipped}`);
882
+ }
883
+ return result.ok;
884
+ }), [runWrite, t]);
885
+ const useProjection = typeof props?.useProjection === "function" ? props.useProjection : void 0;
886
+ const rootStyle = {
887
+ height: "100%",
888
+ overflow: "auto",
889
+ padding: "16px 18px 40px",
890
+ boxSizing: "border-box",
891
+ color: C.label,
892
+ fontFamily: "var(--dsw-font-family, system-ui, sans-serif)",
893
+ fontSize: 13
894
+ };
895
+ if (status === "loading" && !data) {
896
+ return import_react.default.createElement("div", {
897
+ className: "wt-root",
898
+ style: { ...rootStyle, display: "flex", alignItems: "center", gap: 8, color: C.label3, padding: 28 }
899
+ }, [
900
+ import_react.default.createElement("style", { key: "css" }, STYLE_TEXT),
901
+ import_react.default.createElement("span", { key: "sp", className: "wt-spin" }),
902
+ import_react.default.createElement("span", { key: "t" }, t("loading"))
903
+ ]);
904
+ }
905
+ if (status === "failed" && !data) {
906
+ return import_react.default.createElement("div", {
907
+ className: "wt-root",
908
+ style: { ...rootStyle, padding: 28, color: C.error }
909
+ }, [
910
+ import_react.default.createElement("style", { key: "css" }, STYLE_TEXT),
911
+ import_react.default.createElement("div", { key: "m" }, `${t("failed")}\uFF1A${error ?? ""}`),
912
+ import_react.default.createElement("button", {
913
+ key: "r",
914
+ type: "button",
915
+ className: "wt-btn",
916
+ style: { marginTop: 10 },
917
+ onClick: refresh
918
+ }, t("retry"))
919
+ ]);
920
+ }
921
+ const groups = data?.groups ?? {};
922
+ const stats = data?.stats ?? {};
923
+ const today = typeof data?.today === "string" && data.today ? data.today : localToday();
924
+ const store = data?.store ?? null;
925
+ const num = (value) => Number.isFinite(Number(value)) ? Number(value) : 0;
926
+ const overdueCount = num(stats.overdue);
927
+ const projectGroups = asArray(data?.projectGroups);
928
+ const viewGroups = view === "project" ? projectGroups.map((bucket) => ({
929
+ key: `p:${bucket?.key ?? ""}`,
930
+ label: bucket?.project ? String(bucket.project) : t("noProject"),
931
+ meta: `${num(bucket?.open)}/${num(bucket?.total)}${num(bucket?.overdue) ? ` \xB7 ${t("statOverdue")} ${num(bucket.overdue)}` : ""}`,
932
+ items: bucket?.todos
933
+ })) : GROUPS.map((group) => ({ key: group.key, label: t(group.label), items: groups[group.key] }));
934
+ const totalVisible = viewGroups.reduce((sum, group) => sum + asArray(group.items).length, 0);
935
+ const chips = [
936
+ { key: "open", label: t("statOpen"), value: num(stats.open) },
937
+ { key: "overdue", label: t("statOverdue"), value: overdueCount, alert: overdueCount > 0 },
938
+ { key: "today", label: t("statToday"), value: num(stats.today) },
939
+ { key: "doing", label: t("statDoing"), value: num(stats.doing) },
940
+ { key: "done", label: t("statDoneToday"), value: num(stats.completedToday) }
941
+ ];
942
+ return import_react.default.createElement("div", { className: "wt-root", style: rootStyle }, [
943
+ import_react.default.createElement("style", { key: "css" }, STYLE_TEXT),
944
+ /* 顶部:标题 + 统计 + 刷新 */
945
+ import_react.default.createElement("div", {
946
+ key: "head",
947
+ style: { display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap", marginBottom: 10 }
948
+ }, [
949
+ import_react.default.createElement("div", { key: "ti", style: { fontWeight: 700, fontSize: 15 } }, t("title")),
950
+ ...chips.map((chip) => import_react.default.createElement("span", {
951
+ key: chip.key,
952
+ className: "wt-chip",
953
+ style: chip.alert ? { color: C.warn, borderColor: tint(C.warn, 50), background: tint(C.warn, 12) } : void 0
954
+ }, [
955
+ import_react.default.createElement("span", { key: "l", style: { color: C.label3 } }, chip.label),
956
+ import_react.default.createElement("b", { key: "v", style: { fontWeight: 650 } }, String(chip.value))
957
+ ])),
958
+ import_react.default.createElement("span", { key: "sort", className: "wt-seg", title: t("sortLabel") }, [
959
+ ...["manual", "priority", "created"].map((mode) => import_react.default.createElement("button", {
960
+ key: mode,
961
+ type: "button",
962
+ className: `wt-seg-btn${sort === mode ? " is-on" : ""}`,
963
+ title: mode === "manual" ? t("dragHint") : `${t("sortLabel")}\uFF1A${t(`sort${mode === "manual" ? "Manual" : mode === "priority" ? "Priority" : "Created"}`)}`,
964
+ onClick: () => setSort(mode)
965
+ }, t(`sort${mode === "manual" ? "Manual" : mode === "priority" ? "Priority" : "Created"}`)))
966
+ ]),
967
+ import_react.default.createElement("span", { key: "view", className: "wt-seg" }, [
968
+ import_react.default.createElement("button", {
969
+ key: "due",
970
+ type: "button",
971
+ className: `wt-seg-btn${view === "due" ? " is-on" : ""}`,
972
+ title: t("viewDue"),
973
+ onClick: () => setView("due")
974
+ }, t("viewDue")),
975
+ import_react.default.createElement("button", {
976
+ key: "proj",
977
+ type: "button",
978
+ className: `wt-seg-btn${view === "project" ? " is-on" : ""}`,
979
+ title: t("viewProject"),
980
+ onClick: () => setView("project")
981
+ }, t("viewProject"))
982
+ ]),
983
+ import_react.default.createElement("span", { key: "sp", style: { flex: 1 } }),
984
+ import_react.default.createElement("button", {
985
+ key: "rf",
986
+ type: "button",
987
+ className: "wt-btn",
988
+ disabled: status === "loading" || pending > 0,
989
+ title: t("refresh"),
990
+ onClick: refresh
991
+ }, [
992
+ import_react.default.createElement(IconRefresh, { key: "i" }),
993
+ t("refresh")
994
+ ])
995
+ ]),
996
+ /* 错误条:宿主的 error 原文照贴,绝不吞掉 */
997
+ shownError ? import_react.default.createElement("div", {
998
+ key: "err",
999
+ style: {
1000
+ display: "flex",
1001
+ alignItems: "flex-start",
1002
+ gap: 8,
1003
+ marginBottom: 10,
1004
+ padding: "6px 9px",
1005
+ borderRadius: 8,
1006
+ border: `1px solid ${tint(C.error, 45)}`,
1007
+ background: tint(C.error, 12),
1008
+ color: C.error,
1009
+ fontSize: 12,
1010
+ lineHeight: 1.6
1011
+ }
1012
+ }, [
1013
+ import_react.default.createElement("span", {
1014
+ key: "m",
1015
+ style: { flex: 1, minWidth: 0, wordBreak: "break-word" }
1016
+ }, `${t("failed")}\uFF1A${shownError}`),
1017
+ import_react.default.createElement("button", {
1018
+ key: "x",
1019
+ type: "button",
1020
+ className: "wt-icon-btn",
1021
+ style: { width: 18, height: 18, color: C.error },
1022
+ title: t("dismiss"),
1023
+ "aria-label": t("dismiss"),
1024
+ onClick: () => {
1025
+ setBarError(null);
1026
+ setDismissedReadError(error ?? null);
1027
+ }
1028
+ }, import_react.default.createElement(IconClose, { key: "i" }))
1029
+ ]) : null,
1030
+ /* 提升结果提示:点完按钮要有个回执 */
1031
+ notice ? import_react.default.createElement("div", {
1032
+ key: "notice",
1033
+ style: {
1034
+ display: "flex",
1035
+ alignItems: "center",
1036
+ gap: 8,
1037
+ marginBottom: 10,
1038
+ padding: "5px 9px",
1039
+ borderRadius: 8,
1040
+ border: `1px solid ${tint(C.ok, 40)}`,
1041
+ background: tint(C.ok, 10),
1042
+ color: C.label2,
1043
+ fontSize: 11.5
1044
+ }
1045
+ }, [
1046
+ import_react.default.createElement("span", { key: "m", style: { flex: 1 } }, notice),
1047
+ import_react.default.createElement("span", {
1048
+ key: "x",
1049
+ className: "wt-link",
1050
+ onClick: () => setNotice(null)
1051
+ }, t("dismiss"))
1052
+ ]) : null,
1053
+ /* 会话待办:只在拿得到 useProjection 时挂载(见 SessionAdopt 的说明) */
1054
+ useProjection ? import_react.default.createElement(SessionAdopt, {
1055
+ key: "adopt",
1056
+ useProjection,
1057
+ t,
1058
+ busy: pending > 0,
1059
+ onAdopt: handleAdopt
1060
+ }) : null,
1061
+ import_react.default.createElement(QuickAdd, { key: "quick", t, busy: pending > 0, onAdd: handleAdd }),
1062
+ /* 分组列表 */
1063
+ totalVisible ? import_react.default.createElement("div", { key: "groups" }, viewGroups.map((group) => import_react.default.createElement(Group, {
1064
+ key: group.key,
1065
+ label: group.label,
1066
+ meta: group.meta,
1067
+ items: group.items,
1068
+ closed: Boolean(closed[group.key]),
1069
+ t,
1070
+ today,
1071
+ onToggleOpen: () => setClosed((prev) => ({ ...prev, [group.key]: !prev[group.key] })),
1072
+ onToggleTodo: handleToggle,
1073
+ onRemove: handleRemove,
1074
+ onSave: handleSave,
1075
+ drag: dragHandlers
1076
+ }))) : import_react.default.createElement("div", {
1077
+ key: "empty",
1078
+ style: { padding: "22px 12px", textAlign: "center", color: C.label3 }
1079
+ }, [
1080
+ import_react.default.createElement("div", { key: "a", style: { fontSize: 13, color: C.label2 } }, t("empty")),
1081
+ import_react.default.createElement("div", { key: "b", style: { fontSize: 11.5, marginTop: 4 } }, t("emptyHint"))
1082
+ ]),
1083
+ /* 底部:数据到底存在哪(排查「我的待办不见了」时第一眼要看的东西) */
1084
+ import_react.default.createElement("div", {
1085
+ key: "foot",
1086
+ style: { marginTop: 14, paddingTop: 8, borderTop: `1px solid ${C.border1}`, fontSize: 10.5, color: C.label3, lineHeight: 1.7 }
1087
+ }, store && store.location ? import_react.default.createElement("div", {
1088
+ key: "store",
1089
+ title: `${t("storeAt")}\uFF1A${store.location}`,
1090
+ style: { wordBreak: "break-all" }
1091
+ }, `${t("storeAt")} \xB7 ${store.kind === "domain" ? t("storeDomain") : t("storeFile")} \xB7 ${store.location}`) : null)
1092
+ ]);
1093
+ }
1094
+ var inject = ["slots", "locale"];
1095
+ function apply(ctx) {
1096
+ ctx.effect(() => ctx.locale.register(LOCALE_NS, { zh, en }), `${LOCALE_NS}: dictionaries`);
1097
+ ctx.slots.inject("sidebar.panellist", () => ctx.slots.register({
1098
+ name: "sidebar.panellist",
1099
+ id: SLOT_ID,
1100
+ order: 88,
1101
+ label: () => "\u5F85\u529E",
1102
+ locale: LOCALE_NS
1103
+ }, (props) => import_react.default.createElement(WorkTodoIcon, { size: props?.size ?? 16, active: props?.active })));
1104
+ ctx.slots.inject("main", () => ctx.slots.register({
1105
+ name: "main",
1106
+ key: SLOT_ID,
1107
+ locale: LOCALE_NS
1108
+ }, WorkTodoPanel));
1109
+ }
1110
+ return module.exports; } });
1111
+ //# sourceMappingURL=client.js.map