dsh-worktodo 0.1.3 → 0.2.0

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
@@ -36,6 +36,139 @@ __export(index_exports, {
36
36
  });
37
37
  module.exports = __toCommonJS(index_exports);
38
38
  var import_react = __toESM(require("react"), 1);
39
+
40
+ // src/parse-due.mjs
41
+ var DAY_MS = 24 * 60 * 60 * 1e3;
42
+ function dayKey(stamp) {
43
+ const d = new Date(stamp);
44
+ const pad = (n) => String(n).padStart(2, "0");
45
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
46
+ }
47
+ function weekStartKey(stamp) {
48
+ const d = new Date(stamp);
49
+ const day = (d.getDay() + 6) % 7;
50
+ const monday = new Date(d.getFullYear(), d.getMonth(), d.getDate() - day);
51
+ return dayKey(monday.getTime());
52
+ }
53
+ var CN_NUM = { \u96F6: 0, \u4E00: 1, \u4E24: 2, \u4E8C: 2, \u4E09: 3, \u56DB: 4, \u4E94: 5, \u516D: 6, \u4E03: 7, \u516B: 8, \u4E5D: 9, \u5341: 10 };
54
+ var WEEKDAY = { \u4E00: 1, \u4E8C: 2, \u4E09: 3, \u56DB: 4, \u4E94: 5, \u516D: 6, \u65E5: 7, \u5929: 7 };
55
+ var PERIOD_HOUR = { \u4ECA\u65E9: 9, \u65E9\u4E0A: 9, \u65E9\u6668: 9, \u4E0A\u5348: 9, \u4E2D\u5348: 12, \u4E0B\u5348: 15, \u508D\u665A: 18, \u4ECA\u665A: 20, \u660E\u665A: 20, \u665A\u4E0A: 20, \u665A: 20, \u591C\u91CC: 21 };
56
+ var toCnNumber = (raw) => {
57
+ if (/^\d+$/.test(raw)) return Number(raw);
58
+ if (raw === "\u5341") return 10;
59
+ if (raw.startsWith("\u5341")) return 10 + (CN_NUM[raw[1]] ?? 0);
60
+ if (raw.includes("\u5341")) {
61
+ const [a, b] = raw.split("\u5341");
62
+ return (CN_NUM[a] ?? 1) * 10 + (CN_NUM[b] ?? 0);
63
+ }
64
+ return CN_NUM[raw] ?? null;
65
+ };
66
+ function parseNaturalDue(text, now = Date.now()) {
67
+ const source = typeof text === "string" ? text : "";
68
+ const spans = [];
69
+ let due = null;
70
+ let dueTime = null;
71
+ let hourFromPeriod = null;
72
+ const today = dayKey(now);
73
+ const todayStart = (/* @__PURE__ */ new Date(`${today}T00:00:00`)).getTime();
74
+ let m = /(\d{4})[-/年](\d{1,2})[-/月](\d{1,2})日?/.exec(source);
75
+ if (m) {
76
+ due = dayKey(new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3])).getTime());
77
+ spans.push({ start: m.index, end: m.index + m[0].length, kind: "date" });
78
+ } else {
79
+ m = /(\d{1,2})月(\d{1,2})日?/.exec(source);
80
+ if (m) {
81
+ let year = new Date(now).getFullYear();
82
+ let candidate = dayKey(new Date(year, Number(m[1]) - 1, Number(m[2])).getTime());
83
+ if (candidate < today) candidate = dayKey(new Date(year + 1, Number(m[1]) - 1, Number(m[2])).getTime());
84
+ due = candidate;
85
+ spans.push({ start: m.index, end: m.index + m[0].length, kind: "date" });
86
+ }
87
+ }
88
+ const weekRe = /(下下|下个|下|本|这)?(?:周|星期|礼拜)([一二三四五六日天1-7])/g;
89
+ for (let w = weekRe.exec(source); w; w = weekRe.exec(source)) {
90
+ const offsetWeeks = w[1] === "\u4E0B" || w[1] === "\u4E0B\u4E2A" ? 1 : w[1] === "\u4E0B\u4E0B" ? 2 : 0;
91
+ const target = WEEKDAY[w[2]] ?? Number(w[2]);
92
+ const thisWeekStart = (/* @__PURE__ */ new Date(`${weekStartKey(now)}T00:00:00`)).getTime();
93
+ let stamp = thisWeekStart + (target - 1) * DAY_MS + offsetWeeks * 7 * DAY_MS;
94
+ if (offsetWeeks === 0 && stamp < todayStart) stamp += 7 * DAY_MS;
95
+ const candidate = dayKey(stamp);
96
+ if (due === null || spans.some((s) => s.kind === "date" && s.weekday)) {
97
+ spans.filter((s) => s.weekday).forEach((s) => {
98
+ s.dropped = true;
99
+ });
100
+ }
101
+ due = candidate;
102
+ spans.push({ start: w.index, end: w.index + w[0].length, kind: "date", weekday: true });
103
+ }
104
+ if (due === null) {
105
+ const relWords = { \u4ECA\u5929: 0, \u4ECA\u65E5: 0, \u660E\u5929: 1, \u660E\u65E5: 1, \u540E\u5929: 2, \u5927\u540E\u5929: 3, \u6628\u5929: -1, \u6628: -1 };
106
+ for (const [word, offset] of Object.entries(relWords)) {
107
+ const at = source.indexOf(word);
108
+ if (at !== -1) {
109
+ due = dayKey(now + offset * DAY_MS);
110
+ spans.push({ start: at, end: at + word.length, kind: "date" });
111
+ break;
112
+ }
113
+ }
114
+ }
115
+ if (due === null) {
116
+ const plus = /\+(\d{1,3})\s*d(?:ay)?s?/.exec(source);
117
+ const cn = /([0-9零一两二三四五六七八九十]+)\s*天\s*(?:后|以后|之后)/.exec(source);
118
+ if (plus) {
119
+ due = dayKey(now + Number(plus[1]) * DAY_MS);
120
+ spans.push({ start: plus.index, end: plus.index + plus[0].length, kind: "date" });
121
+ } else if (cn) {
122
+ const n = toCnNumber(cn[1]);
123
+ if (n !== null) {
124
+ due = dayKey(now + n * DAY_MS);
125
+ spans.push({ start: cn.index, end: cn.index + cn[0].length, kind: "date" });
126
+ }
127
+ }
128
+ }
129
+ const periodRe = /(今晚|明晚|明早|早上|早晨|上午|中午|下午|傍晚|晚上|夜里)/;
130
+ const period = periodRe.exec(source);
131
+ if (period) {
132
+ hourFromPeriod = PERIOD_HOUR[period[1]] ?? null;
133
+ if (due === null) {
134
+ if (period[1] === "\u4ECA\u665A" || period[1] === "\u660E\u665A") due = dayKey(now + (period[1] === "\u660E\u665A" ? 1 : 0) * DAY_MS);
135
+ if (period[1] === "\u660E\u65E9") due = dayKey(now + DAY_MS);
136
+ }
137
+ spans.push({ start: period.index, end: period.index + period[1].length, kind: "time" });
138
+ }
139
+ const hm = /(\d{1,2})[::](\d{2})/.exec(source);
140
+ const cnHour = /([0-9零一两二三四五六七八九十]+)\s*[点时](半|[0-9零一两二三四五六七八九十]{1,3}分)?/.exec(source);
141
+ if (hm) {
142
+ const h = Number(hm[1]);
143
+ if (h <= 23 && Number(hm[2]) <= 59) dueTime = `${String(h).padStart(2, "0")}:${hm[2]}`;
144
+ spans.push({ start: hm.index, end: hm.index + hm[0].length, kind: "time" });
145
+ } else if (cnHour) {
146
+ let h = toCnNumber(cnHour[1]);
147
+ if (h !== null && h <= 23) {
148
+ if (h <= 12 && hourFromPeriod !== null && hourFromPeriod >= 12) h = h === 12 ? 12 : h + 12;
149
+ const minute = cnHour[2] === "\u534A" ? 30 : cnHour[2] ? toCnNumber(cnHour[2].replace("\u5206", "")) ?? 0 : 0;
150
+ if (minute <= 59) dueTime = `${String(h).padStart(2, "0")}:${String(minute).padStart(2, "0")}`;
151
+ spans.push({ start: cnHour.index, end: cnHour.index + cnHour[0].length, kind: "time" });
152
+ }
153
+ } else if (hourFromPeriod !== null) {
154
+ dueTime = `${String(hourFromPeriod).padStart(2, "0")}:00`;
155
+ }
156
+ if (dueTime && !due) due = today;
157
+ const kept = spans.filter((s) => !s.dropped).sort((a, b) => a.start - b.start);
158
+ let title = source;
159
+ for (const span of [...kept].reverse()) {
160
+ title = title.slice(0, span.start) + " " + title.slice(span.end);
161
+ }
162
+ title = title.replace(/\s+/g, " ").trim();
163
+ return {
164
+ title: title || source.trim(),
165
+ due,
166
+ dueTime,
167
+ matched: kept.map((s) => source.slice(s.start, s.end))
168
+ };
169
+ }
170
+
171
+ // src/client/index.js
39
172
  var PKG_NAME = true ? "dsh-worktodo" : "dsh-worktodo";
40
173
  var DERIVED_NAME = PKG_NAME.replace(/^dsh-/, "");
41
174
  var SHORT_NAME = DERIVED_NAME === "worktodo" ? DERIVED_NAME : "worktodo";
@@ -79,6 +212,18 @@ var zh = {
79
212
  "sortPriority": "\u4F18\u5148\u7EA7",
80
213
  "sortCreated": "\u521B\u5EFA\u65F6\u95F4",
81
214
  "dragHint": "\u62D6\u52A8\u8C03\u6574\u987A\u5E8F\uFF08\u4F1A\u81EA\u52A8\u5207\u5230\u624B\u52A8\u6392\u5E8F\uFF09",
215
+ "versionLabel": "\u7248\u672C",
216
+ "versionHint": "\u5F53\u524D\u5B89\u88C5\u7684 dsh-worktodo \u7248\u672C",
217
+ "updateAvailable": "\u6709\u65B0\u7248\u672C",
218
+ "updateHint": "\u66F4\u65B0\u547D\u4EE4\uFF08\u91CD\u542F DSH \u751F\u6548\uFF09",
219
+ "addOpen": "\u65B0\u589E\u5F85\u529E",
220
+ "addCancel": "\u53D6\u6D88",
221
+ "prioNone": "\u65E0",
222
+ "dateUnset": "\u65E5\u671F \xB7 \u672A\u8BBE",
223
+ "timeUnset": "\u65F6\u95F4 \xB7 \u672A\u8BBE",
224
+ "todayWord": "\u4ECA\u5929",
225
+ "fromParsed": "\u8BC6\u522B",
226
+ "dueTimeLabel": "\u65F6\u95F4",
82
227
  "expand": "\u5C55\u5F00",
83
228
  "collapse": "\u6536\u8D77",
84
229
  "empty": "\u8FD8\u6CA1\u6709\u5F85\u529E",
@@ -145,6 +290,18 @@ var en = {
145
290
  "sortPriority": "Priority",
146
291
  "sortCreated": "Created",
147
292
  "dragHint": "Drag to reorder (switches to manual sort)",
293
+ "versionLabel": "Version",
294
+ "versionHint": "Installed dsh-worktodo version",
295
+ "updateAvailable": "Update available",
296
+ "updateHint": "Update command (restart DSH to apply)",
297
+ "addOpen": "New todo",
298
+ "addCancel": "Cancel",
299
+ "prioNone": "None",
300
+ "dateUnset": "Date \xB7 unset",
301
+ "timeUnset": "Time \xB7 unset",
302
+ "todayWord": "Today",
303
+ "fromParsed": "parsed",
304
+ "dueTimeLabel": "Time",
148
305
  "expand": "Expand",
149
306
  "collapse": "Collapse",
150
307
  "empty": "No to-dos yet",
@@ -175,6 +332,11 @@ var en = {
175
332
  "titleEmpty": "Type something first",
176
333
  "untitled": "(untitled)"
177
334
  };
335
+ function dayKey2(stamp) {
336
+ const d = new Date(stamp);
337
+ const pad = (n) => String(n).padStart(2, "0");
338
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
339
+ }
178
340
  var C = {
179
341
  label: "var(--dsw-alias-label-primary)",
180
342
  label2: "var(--dsw-alias-label-secondary)",
@@ -254,6 +416,20 @@ var STYLE_TEXT = `
254
416
  .wt-seg-btn:hover { color: ${C.label}; }
255
417
  .wt-row[draggable="true"] { cursor: grab; }
256
418
  .wt-row[draggable="true"]:active { cursor: grabbing; }
419
+ .wt-plus {
420
+ width: 100%; display: flex; align-items: center; justify-content: center; gap: 8px;
421
+ padding: 12px 14px; margin-bottom: 10px; cursor: pointer; font: inherit; font-size: 13.5px; font-weight: 600;
422
+ color: ${C.label2}; background: ${C.layer1};
423
+ border: 1px dashed ${C.border}; border-radius: 12px;
424
+ }
425
+ .wt-plus:hover { color: ${C.label}; border-color: ${C.brand}; background: ${tint(C.brand, 8)}; }
426
+ .wt-plus-mark { font-size: 24px; line-height: 1; font-weight: 400; color: ${C.brand}; }
427
+ .wt-flag-btn {
428
+ appearance: none; display: inline-flex; align-items: center; gap: 5px; cursor: pointer;
429
+ font: inherit; font-size: 11.5px; padding: 5px 9px; border-radius: 9px;
430
+ background: transparent; border: 1px solid ${C.border}; color: ${C.label2};
431
+ }
432
+ .wt-flag-btn.is-on { border-color: currentColor; background: color-mix(in srgb, currentColor 12%, transparent); color: inherit; }
257
433
  .wt-group-head {
258
434
  display: flex; align-items: center; gap: 7px; padding: 3px 8px; border-radius: 8px;
259
435
  cursor: pointer; user-select: none; color: ${C.label2};
@@ -418,72 +594,250 @@ function useList(sort) {
418
594
  const refresh = import_react.default.useCallback(() => setTick((n) => n + 1), []);
419
595
  return { ...state, refresh };
420
596
  }
597
+ function IconFlag({ filled, dashed }) {
598
+ return import_react.default.createElement("svg", { width: 13, height: 13, viewBox: "0 0 16 16", "aria-hidden": "true" }, [
599
+ import_react.default.createElement("path", {
600
+ key: "pole",
601
+ d: "M3.6 1.6v13",
602
+ stroke: "currentColor",
603
+ strokeWidth: 1.6,
604
+ strokeLinecap: "round",
605
+ fill: "none",
606
+ strokeDasharray: dashed ? "1.5 1.5" : void 0
607
+ }),
608
+ import_react.default.createElement("path", {
609
+ key: "cloth",
610
+ d: "M5.2 2.4h7.4L11 5.2l1.6 2.8H5.2z",
611
+ fill: filled ? "currentColor" : "none",
612
+ stroke: "currentColor",
613
+ strokeWidth: 1.3,
614
+ strokeLinejoin: "round",
615
+ strokeDasharray: dashed ? "1.5 1.5" : void 0
616
+ })
617
+ ]);
618
+ }
619
+ var IconCal = () => import_react.default.createElement("svg", { width: 13, height: 13, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.4, "aria-hidden": "true" }, [
620
+ import_react.default.createElement("rect", { key: "r", x: 2.2, y: 3.4, width: 11.6, height: 10.4, rx: 2 }),
621
+ import_react.default.createElement("path", { key: "p", d: "M2.2 6.6h11.6M5.4 2.2v2.4M10.6 2.2v2.4" })
622
+ ]);
623
+ var IconClock = () => import_react.default.createElement("svg", { width: 13, height: 13, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.4, "aria-hidden": "true" }, [
624
+ import_react.default.createElement("circle", { key: "c", cx: 8, cy: 8, r: 5.8 }),
625
+ import_react.default.createElement("path", { key: "p", d: "M8 4.9V8l2.2 1.4" })
626
+ ]);
421
627
  function QuickAdd({ t, onAdd, busy }) {
422
628
  const [title, setTitle] = import_react.default.useState("");
423
629
  const [due, setDue] = import_react.default.useState("");
424
- const [priority, setPriority] = import_react.default.useState("normal");
630
+ const [dueTime, setDueTime] = import_react.default.useState("");
631
+ const [priority, setPriority] = import_react.default.useState("none");
632
+ const [parsed, setParsed] = import_react.default.useState(null);
633
+ const [manual, setManual] = import_react.default.useState(false);
634
+ const [flagOpen, setFlagOpen] = import_react.default.useState(false);
635
+ const dateRef = import_react.default.useRef(null);
636
+ const timeRef = import_react.default.useRef(null);
637
+ const openPicker = (ref) => {
638
+ const el = ref.current;
639
+ if (!el) return;
640
+ if (typeof el.showPicker === "function") {
641
+ try {
642
+ el.showPicker();
643
+ return;
644
+ } catch {
645
+ }
646
+ }
647
+ el.focus();
648
+ try {
649
+ el.click();
650
+ } catch {
651
+ }
652
+ };
653
+ import_react.default.useEffect(() => {
654
+ if (manual) return void 0;
655
+ const text = title;
656
+ if (!text || !text.trim()) {
657
+ setParsed(null);
658
+ return void 0;
659
+ }
660
+ const timer = setTimeout(() => {
661
+ const result = parseNaturalDue(text, Date.now());
662
+ if (result.matched.length) {
663
+ setParsed(result);
664
+ setDue(result.due ?? "");
665
+ setDueTime(result.dueTime ?? "");
666
+ } else {
667
+ setParsed(null);
668
+ }
669
+ }, 300);
670
+ return () => clearTimeout(timer);
671
+ }, [title, manual]);
425
672
  const submit = () => {
426
673
  if (busy) return;
427
- Promise.resolve(onAdd({ title, due, priority })).then((ok) => {
428
- if (ok !== false) setTitle("");
674
+ let payloadTitle = title;
675
+ if (!manual) {
676
+ const result = parseNaturalDue(title, Date.now());
677
+ if (result.matched.length) payloadTitle = result.title;
678
+ }
679
+ Promise.resolve(onAdd({
680
+ title: payloadTitle,
681
+ due: due || null,
682
+ dueTime: dueTime || null,
683
+ priority
684
+ })).then((ok) => {
685
+ if (ok !== false) {
686
+ setTitle("");
687
+ setParsed(null);
688
+ setManual(false);
689
+ setFlagOpen(false);
690
+ }
429
691
  }).catch(() => {
430
692
  });
431
693
  };
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"),
694
+ const today = dayKey2(Date.now());
695
+ const dateLabel = due ? due === today ? t("todayWord") : `${due.slice(5).replace("-", "-")}` : t("dateUnset");
696
+ const timeLabel = dueTime || t("timeUnset");
697
+ const prioLabel = priority === "high" ? t("prioHigh") : priority === "normal" ? t("prioNormal") : priority === "low" ? t("prioLow") : t("prioNone");
698
+ const prioColor = priority === "high" ? C.error : priority === "normal" ? C.warn : C.label3;
699
+ const flags = [
700
+ { value: "none", label: t("prioNone"), color: C.label3, dashed: true },
701
+ { value: "high", label: t("prioHigh"), color: C.error },
702
+ { value: "normal", label: t("prioNormal"), color: C.warn },
703
+ { value: "low", label: t("prioLow"), color: C.label3 }
704
+ ];
705
+ const setDate = (value) => {
706
+ setManual(true);
707
+ setDue(value);
708
+ if (!value) setDueTime("");
709
+ setFlagOpen(false);
710
+ };
711
+ const setTime = (value) => {
712
+ setManual(true);
713
+ if (value && !due) setDue(dayKey2(Date.now()));
714
+ setDueTime(value);
715
+ };
716
+ return import_react.default.createElement("div", { key: "qa", style: { marginBottom: 12 } }, [
717
+ // ---- 第一行:内容 + 添加 ----
718
+ import_react.default.createElement("div", { key: "l1", style: { display: "flex", alignItems: "center", gap: 8 } }, [
453
719
  import_react.default.createElement("input", {
454
- key: "i",
720
+ key: "ti",
455
721
  className: "wt-input",
456
- type: "date",
457
- value: due,
458
- "aria-label": t("dueLabel"),
459
- onChange: (event) => setDue(event.target.value)
460
- })
722
+ style: { flex: "1 1 auto", fontSize: 13, padding: "7px 10px" },
723
+ value: title,
724
+ placeholder: t("addPlaceholder"),
725
+ "aria-label": t("addPlaceholder"),
726
+ onChange: (event) => {
727
+ const next = event.target.value;
728
+ setTitle(next);
729
+ if (!next.trim()) {
730
+ setManual(false);
731
+ setParsed(null);
732
+ }
733
+ },
734
+ onKeyDown: (event) => {
735
+ if (event.key === "Enter") submit();
736
+ }
737
+ }),
738
+ import_react.default.createElement("button", {
739
+ key: "add",
740
+ type: "button",
741
+ className: "wt-btn wt-btn-primary",
742
+ disabled: Boolean(busy),
743
+ onClick: submit
744
+ }, t("add"))
461
745
  ]),
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)
746
+ // ---- 第二行(更小):日期 / 时间 / 优先级 ----
747
+ import_react.default.createElement("div", { key: "l2", style: { display: "flex", alignItems: "center", gap: 10, marginTop: 8, fontSize: 11.5, flexWrap: "wrap" } }, [
748
+ // 日期:label 里套一个透明的原生 date 输入,点它即弹系统日历
749
+ import_react.default.createElement("label", {
750
+ key: "due",
751
+ className: `wt-chip${parsed && parsed.due ? " is-parsed" : ""}`,
752
+ title: t("dueLabel"),
753
+ onClick: (event) => {
754
+ event.preventDefault();
755
+ openPicker(dateRef);
756
+ },
757
+ style: { position: "relative", cursor: "pointer", color: due ? C.label : C.label3, borderColor: parsed && parsed.due ? C.brand : void 0 }
474
758
  }, [
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"))
759
+ import_react.default.createElement(IconCal, { key: "i" }),
760
+ import_react.default.createElement("span", { key: "v" }, due ? `${today === due ? t("todayWord") : due}${parsed && parsed.due ? ` \xB7 ${t("fromParsed")}` : ""}` : t("dateUnset")),
761
+ import_react.default.createElement("input", {
762
+ key: "in",
763
+ ref: dateRef,
764
+ type: "date",
765
+ value: due,
766
+ "aria-label": t("dueLabel"),
767
+ style: { position: "absolute", inset: 0, opacity: 0, border: 0, padding: 0, width: "100%", height: "100%", cursor: "pointer" },
768
+ onChange: (event) => setDate(event.target.value)
769
+ })
770
+ ]),
771
+ // 时间:同样用原生 time 输入;只选时间时自动补今天
772
+ import_react.default.createElement("label", {
773
+ key: "time",
774
+ className: `wt-chip${parsed && parsed.dueTime ? " is-parsed" : ""}`,
775
+ title: t("dueTimeLabel"),
776
+ onClick: (event) => {
777
+ event.preventDefault();
778
+ openPicker(timeRef);
779
+ },
780
+ style: { position: "relative", cursor: "pointer", color: dueTime ? C.label : C.label3, borderColor: parsed && parsed.dueTime ? C.brand : void 0 }
781
+ }, [
782
+ import_react.default.createElement(IconClock, { key: "i" }),
783
+ import_react.default.createElement("span", { key: "v" }, `${timeLabel}${parsed && parsed.dueTime ? ` \xB7 ${t("fromParsed")}` : ""}`),
784
+ import_react.default.createElement("input", {
785
+ key: "in",
786
+ ref: timeRef,
787
+ type: "time",
788
+ value: dueTime,
789
+ lang: "zh-CN",
790
+ "aria-label": t("dueTimeLabel"),
791
+ style: { position: "absolute", inset: 0, opacity: 0, border: 0, padding: 0, width: "100%", height: "100%", cursor: "pointer" },
792
+ onChange: (event) => setTime(event.target.value)
793
+ })
794
+ ]),
795
+ // 优先级:点开四个小旗
796
+ import_react.default.createElement("span", { key: "prio", style: { position: "relative" } }, [
797
+ import_react.default.createElement("button", {
798
+ key: "btn",
799
+ type: "button",
800
+ className: "wt-chip",
801
+ style: { color: prioColor, cursor: "pointer", font: "inherit", fontSize: 11.5 },
802
+ title: t("priorityLabel"),
803
+ "aria-expanded": flagOpen,
804
+ onClick: () => setFlagOpen((open) => !open)
805
+ }, [
806
+ import_react.default.createElement(IconFlag, { key: "f", dashed: priority === "none", filled: priority !== "none" }),
807
+ import_react.default.createElement("span", { key: "v" }, `${t("priorityLabel")} \xB7 ${prioLabel}`)
808
+ ]),
809
+ flagOpen ? import_react.default.createElement("div", {
810
+ key: "pop",
811
+ style: {
812
+ position: "absolute",
813
+ top: "100%",
814
+ left: 0,
815
+ zIndex: 5,
816
+ marginTop: 4,
817
+ padding: 6,
818
+ display: "flex",
819
+ gap: 4,
820
+ background: C.layer1,
821
+ border: `1px solid ${C.border}`,
822
+ borderRadius: 10,
823
+ boxShadow: "0 8px 24px rgba(0,0,0,.12)"
824
+ }
825
+ }, flags.map((flag) => import_react.default.createElement("button", {
826
+ key: flag.value,
827
+ type: "button",
828
+ className: "wt-chip",
829
+ style: { color: flag.color, font: "inherit", fontSize: 11.5, cursor: "pointer", borderColor: priority === flag.value ? flag.color : void 0 },
830
+ title: flag.label,
831
+ onClick: () => {
832
+ setPriority(flag.value);
833
+ setFlagOpen(false);
834
+ }
835
+ }, [
836
+ import_react.default.createElement(IconFlag, { key: "f", filled: priority === flag.value, dashed: flag.value === "none" }),
837
+ import_react.default.createElement("span", { key: "v" }, flag.label)
838
+ ]))) : null
478
839
  ])
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"))
840
+ ])
487
841
  ]);
488
842
  }
489
843
  function TodoRow({ todo, today, t, onToggle, onRemove, onSave, drag }) {
@@ -506,6 +860,7 @@ function TodoRow({ todo, today, t, onToggle, onRemove, onSave, drag }) {
506
860
  due,
507
861
  priority: row.priority === "high" || row.priority === "low" ? row.priority : "normal",
508
862
  project: typeof row.project === "string" ? row.project : "",
863
+ dueTime: typeof row.dueTime === "string" ? row.dueTime : "",
509
864
  notes: typeof row.notes === "string" ? row.notes : ""
510
865
  });
511
866
  setNotesOpen(Boolean(row.notes));
@@ -521,7 +876,12 @@ function TodoRow({ todo, today, t, onToggle, onRemove, onSave, drag }) {
521
876
  const title = String(draft.title ?? "").trim();
522
877
  if (title !== (row.title ?? "")) patch.title = title;
523
878
  const nextDue = draft.due || "";
524
- if (nextDue !== due) patch.due = nextDue;
879
+ if (nextDue !== due) {
880
+ patch.due = nextDue;
881
+ if (!nextDue) patch.dueTime = "none";
882
+ }
883
+ const nextDueTime = String(draft.dueTime ?? "").trim();
884
+ if (nextDue && nextDueTime !== (row.dueTime ?? "")) patch.dueTime = nextDueTime === "" ? "none" : nextDueTime;
525
885
  const priority = draft.priority || "normal";
526
886
  if (priority !== (row.priority ?? "normal")) patch.priority = priority;
527
887
  const project = String(draft.project ?? "").trim();
@@ -580,6 +940,15 @@ function TodoRow({ todo, today, t, onToggle, onRemove, onSave, drag }) {
580
940
  "aria-label": t("dueLabel"),
581
941
  onChange: (event) => setDraft((prev) => ({ ...prev, due: event.target.value }))
582
942
  })),
943
+ field("tm", t("dueTimeLabel"), import_react.default.createElement("input", {
944
+ className: "wt-input",
945
+ style: inputStyle,
946
+ type: "time",
947
+ value: draft.dueTime,
948
+ disabled: !draft.due,
949
+ "aria-label": t("dueTimeLabel"),
950
+ onChange: (event) => setDraft((prev) => ({ ...prev, dueTime: event.target.value }))
951
+ })),
583
952
  field("prio", t("priorityLabel"), import_react.default.createElement("select", {
584
953
  className: "wt-input",
585
954
  style: inputStyle,
@@ -678,7 +1047,17 @@ function TodoRow({ todo, today, t, onToggle, onRemove, onSave, drag }) {
678
1047
  title: drag.hint,
679
1048
  style: drag.dragging ? { opacity: 0.45 } : void 0
680
1049
  } : {};
681
- return import_react.default.createElement("div", { className: "wt-row", ...dragAttrs }, [
1050
+ return import_react.default.createElement("div", { className: "wt-row", ...dragAttrs, style: row.priority === "high" ? { borderLeft: `2px solid ${C.error}`, paddingLeft: 8 } : void 0 }, [
1051
+ /* 优先级呈现(设计稿 v5):高=红色左缘 + 实心红旗;中=琥珀;低=灰;无=什么都不显示 */
1052
+ row.priority && row.priority !== "none" ? import_react.default.createElement("span", {
1053
+ key: "prio-flag",
1054
+ title: row.priority === "high" ? t("prioHigh") : row.priority === "normal" ? t("prioNormal") : t("prioLow"),
1055
+ style: {
1056
+ display: "inline-flex",
1057
+ flex: "0 0 auto",
1058
+ color: row.priority === "high" ? C.error : row.priority === "normal" ? C.warn : C.label3
1059
+ }
1060
+ }, import_react.default.createElement(IconFlag, { filled: row.priority === "high" })) : null,
682
1061
  import_react.default.createElement("button", {
683
1062
  key: "check",
684
1063
  type: "button",
@@ -922,6 +1301,8 @@ function WorkTodoPanel(props) {
922
1301
  const stats = data?.stats ?? {};
923
1302
  const today = typeof data?.today === "string" && data.today ? data.today : localToday();
924
1303
  const store = data?.store ?? null;
1304
+ const version = typeof data?.version === "string" ? data.version : "";
1305
+ const update = data?.update ?? null;
925
1306
  const num = (value) => Number.isFinite(Number(value)) ? Number(value) : 0;
926
1307
  const overdueCount = num(stats.overdue);
927
1308
  const projectGroups = asArray(data?.projectGroups);
@@ -1084,11 +1465,30 @@ function WorkTodoPanel(props) {
1084
1465
  import_react.default.createElement("div", {
1085
1466
  key: "foot",
1086
1467
  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)
1468
+ }, [
1469
+ /* 角落信息:装了哪一版 + 有没有新版(一眼可见,不占主区) */
1470
+ import_react.default.createElement("div", {
1471
+ key: "ver",
1472
+ style: { display: "flex", alignItems: "center", gap: 6, flexWrap: "wrap", marginBottom: 2 }
1473
+ }, [
1474
+ import_react.default.createElement("span", {
1475
+ key: "v",
1476
+ title: t("versionHint"),
1477
+ style: { color: C.label3 }
1478
+ }, `${t("versionLabel")} ${version ? `v${version}` : "\u2014"}`),
1479
+ update?.hasUpdate ? import_react.default.createElement("span", {
1480
+ key: "u",
1481
+ className: "wt-chip",
1482
+ style: { fontSize: 10, color: C.ok, borderColor: tint(C.ok, 50), background: tint(C.ok, 12) },
1483
+ title: `${t("updateHint")}\uFF1Adsh plugin --profile desktop add dsh-worktodo@${update.latest}`
1484
+ }, `${t("updateAvailable")} v${update.latest}`) : null
1485
+ ]),
1486
+ store && store.location ? import_react.default.createElement("div", {
1487
+ key: "store",
1488
+ title: `${t("storeAt")}\uFF1A${store.location}`,
1489
+ style: { wordBreak: "break-all" }
1490
+ }, `${t("storeAt")} \xB7 ${store.kind === "domain" ? t("storeDomain") : t("storeFile")} \xB7 ${store.location}`) : null
1491
+ ])
1092
1492
  ]);
1093
1493
  }
1094
1494
  var inject = ["slots", "locale"];