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/CHANGELOG.md +75 -1
- package/README.en.md +1 -1
- package/lib/client.js +460 -60
- package/lib/client.js.map +4 -4
- package/lib/index.js +57 -1
- package/package.json +1 -1
- package/src/client/index.js +298 -60
- package/src/parse-due.mjs +170 -0
- package/src/records.mjs +46 -7
- package/src/schema.mjs +6 -1
- package/src/store.mjs +4 -1
- package/src/tools.mjs +33 -5
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 [
|
|
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
|
-
|
|
428
|
-
|
|
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
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
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: "
|
|
720
|
+
key: "ti",
|
|
455
721
|
className: "wt-input",
|
|
456
|
-
|
|
457
|
-
value:
|
|
458
|
-
|
|
459
|
-
|
|
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
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
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(
|
|
476
|
-
import_react.default.createElement("
|
|
477
|
-
import_react.default.createElement("
|
|
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)
|
|
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
|
-
},
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
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"];
|