@ottttto/dsh-scheduled-send 0.3.5 → 0.3.7

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
@@ -74,12 +74,15 @@ function formatCountdown(sendAt, now) {
74
74
  const ms = sendAt - now;
75
75
  if (ms <= 0) return '即将发送';
76
76
  const totalSec = Math.floor(ms / 1000);
77
- const h = Math.floor(totalSec / 3600);
77
+ const d = Math.floor(totalSec / 86400);
78
+ const h = Math.floor((totalSec % 86400) / 3600);
78
79
  const m = Math.floor((totalSec % 3600) / 60);
79
80
  const s = totalSec % 60;
80
- if (h > 0) return `${h}小时${m}分后`;
81
- if (m > 0) return `${m}分${s}秒后`;
82
- return `${s}秒后`;
81
+ // no trailing 后 — callers append their own verb (后发送 / 后重置 …)
82
+ if (d > 0) return `${d}天${h}小时`;
83
+ if (h > 0) return `${h}小时${m}分`;
84
+ if (m > 0) return `${m}分${s}秒`;
85
+ return `${s}秒`;
83
86
  }
84
87
 
85
88
  /** Default confirmation time = now + 5 minutes. */
@@ -237,9 +240,13 @@ function createScheduledClientState({ fetchState, postSchedule, cancelSchedule,
237
240
 
238
241
  /** Cancel a pending task: DELETE on the host + drop locally. */
239
242
  async cancelTask(id) {
240
- if (cancelSchedule) await cancelSchedule(id);
243
+ // optimistic: drop locally FIRST so the row disappears instantly; the
244
+ // DELETE then persists it. onChanged lets the sibling instance (dock ↔
245
+ // sidebar panel) refresh immediately instead of waiting for its poll.
241
246
  tasks = tasks.filter((t) => t.id !== id);
242
247
  recentAdds.delete(id);
248
+ if (typeof this.onChanged === 'function') this.onChanged();
249
+ if (cancelSchedule) await cancelSchedule(id);
243
250
  },
244
251
 
245
252
  start(intervalMs = 4_000) {
@@ -487,7 +494,7 @@ function createClientPluginBody(React) {
487
494
  h("div", { key: "src", style: srcStyle }, t.content),
488
495
  h("div", { key: "meta", style: metaStyle }, [
489
496
  h("span", { key: "at" }, formatLocalTime(t.sendAt)),
490
- h("span", { key: "cd" }, formatCountdown(t.sendAt, Date.now())),
497
+ h("span", { key: "cd" }, formatCountdown(t.sendAt, Date.now()) + "后发送"),
491
498
  h("span", { key: "sp", style: { marginLeft: "auto" } }, cancelBtn(t.id)),
492
499
  ]),
493
500
  ])),
@@ -626,11 +633,14 @@ function createClientPluginBody(React) {
626
633
  }, [
627
634
  h("div", { key: "c", style: { whiteSpace: "pre-wrap", fontFamily: "monospace" } },
628
635
  summarizeContent(t.content) || "(空内容)"),
629
- h("div", { key: "m", style: { display: "flex", alignItems: "center", gap: 8, color: "rgba(128,128,128,1)", flexWrap: "wrap" } }, [
630
- h("span", { key: "s", style: { maxWidth: "60%", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } },
636
+ h("div", { key: "m", style: { display: "flex", alignItems: "center", gap: 8, color: "rgba(128,128,128,1)" } }, [
637
+ h("span", { key: "s", style: { flex: 1, minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } },
631
638
  t.sessionExists ? t.sessionTitle : "会话不存在"),
632
639
  h("span", { key: "at" }, formatLocalTime(t.sendAt)),
633
- h("span", { key: "cd" }, formatCountdown(t.sendAt, Date.now())),
640
+ ]),
641
+ h("div", { key: "act", style: { display: "flex", alignItems: "center", gap: 8 } }, [
642
+ h("span", { key: "cd", style: { color: "#3b82f6", fontWeight: 600 } },
643
+ "⏳ " + formatCountdown(t.sendAt, Date.now()) + "后发送"),
634
644
  h("span", { key: "sp", style: { marginLeft: "auto" } }, cancelBtn(t)),
635
645
  ]),
636
646
  ])),
@@ -675,6 +685,10 @@ function createClientPluginBody(React) {
675
685
  const panelCore = createScheduledClientState({
676
686
  fetchState: async () => {
677
687
  const res = await doFetch(stateRoutePath, { headers: { accept: "application/json" } });
688
+ // cross-instance sync: a cancel/create in either surface refreshes the
689
+ // other immediately (no 4s poll lag)
690
+ core.onChanged = () => { void panelCore.refresh(); };
691
+ panelCore.onChanged = () => { void core.refresh(); };
678
692
  if (!res.ok) throw new Error("state HTTP " + res.status);
679
693
  return res.json();
680
694
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ottttto/dsh-scheduled-send",
3
- "version": "0.3.5",
3
+ "version": "0.3.7",
4
4
  "description": "DSH plugin: scheduled sending (⏰ composer button, dock list of pending tasks, sidebar 定时任务 panel with cross-session cancel, due-time delivery as a normal user bubble)",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -65,12 +65,15 @@ export function formatCountdown(sendAt, now) {
65
65
  const ms = sendAt - now;
66
66
  if (ms <= 0) return '即将发送';
67
67
  const totalSec = Math.floor(ms / 1000);
68
- const h = Math.floor(totalSec / 3600);
68
+ const d = Math.floor(totalSec / 86400);
69
+ const h = Math.floor((totalSec % 86400) / 3600);
69
70
  const m = Math.floor((totalSec % 3600) / 60);
70
71
  const s = totalSec % 60;
71
- if (h > 0) return `${h}小时${m}分后`;
72
- if (m > 0) return `${m}分${s}秒后`;
73
- return `${s}秒后`;
72
+ // no trailing 后 — callers append their own verb (后发送 / 后重置 …)
73
+ if (d > 0) return `${d}天${h}小时`;
74
+ if (h > 0) return `${h}小时${m}分`;
75
+ if (m > 0) return `${m}分${s}秒`;
76
+ return `${s}秒`;
74
77
  }
75
78
 
76
79
  /** Default confirmation time = now + 5 minutes. */
@@ -228,9 +231,13 @@ export function createScheduledClientState({ fetchState, postSchedule, cancelSch
228
231
 
229
232
  /** Cancel a pending task: DELETE on the host + drop locally. */
230
233
  async cancelTask(id) {
231
- if (cancelSchedule) await cancelSchedule(id);
234
+ // optimistic: drop locally FIRST so the row disappears instantly; the
235
+ // DELETE then persists it. onChanged lets the sibling instance (dock ↔
236
+ // sidebar panel) refresh immediately instead of waiting for its poll.
232
237
  tasks = tasks.filter((t) => t.id !== id);
233
238
  recentAdds.delete(id);
239
+ if (typeof this.onChanged === 'function') this.onChanged();
240
+ if (cancelSchedule) await cancelSchedule(id);
234
241
  },
235
242
 
236
243
  start(intervalMs = 4_000) {
@@ -223,7 +223,7 @@ function createClientPluginBody(React) {
223
223
  h("div", { key: "src", style: srcStyle }, t.content),
224
224
  h("div", { key: "meta", style: metaStyle }, [
225
225
  h("span", { key: "at" }, formatLocalTime(t.sendAt)),
226
- h("span", { key: "cd" }, formatCountdown(t.sendAt, Date.now())),
226
+ h("span", { key: "cd" }, formatCountdown(t.sendAt, Date.now()) + "后发送"),
227
227
  h("span", { key: "sp", style: { marginLeft: "auto" } }, cancelBtn(t.id)),
228
228
  ]),
229
229
  ])),
@@ -362,11 +362,14 @@ function createClientPluginBody(React) {
362
362
  }, [
363
363
  h("div", { key: "c", style: { whiteSpace: "pre-wrap", fontFamily: "monospace" } },
364
364
  summarizeContent(t.content) || "(空内容)"),
365
- h("div", { key: "m", style: { display: "flex", alignItems: "center", gap: 8, color: "rgba(128,128,128,1)", flexWrap: "wrap" } }, [
366
- h("span", { key: "s", style: { maxWidth: "60%", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } },
365
+ h("div", { key: "m", style: { display: "flex", alignItems: "center", gap: 8, color: "rgba(128,128,128,1)" } }, [
366
+ h("span", { key: "s", style: { flex: 1, minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } },
367
367
  t.sessionExists ? t.sessionTitle : "会话不存在"),
368
368
  h("span", { key: "at" }, formatLocalTime(t.sendAt)),
369
- h("span", { key: "cd" }, formatCountdown(t.sendAt, Date.now())),
369
+ ]),
370
+ h("div", { key: "act", style: { display: "flex", alignItems: "center", gap: 8 } }, [
371
+ h("span", { key: "cd", style: { color: "#3b82f6", fontWeight: 600 } },
372
+ "⏳ " + formatCountdown(t.sendAt, Date.now()) + "后发送"),
370
373
  h("span", { key: "sp", style: { marginLeft: "auto" } }, cancelBtn(t)),
371
374
  ]),
372
375
  ])),
@@ -411,6 +414,10 @@ function createClientPluginBody(React) {
411
414
  const panelCore = createScheduledClientState({
412
415
  fetchState: async () => {
413
416
  const res = await doFetch(stateRoutePath, { headers: { accept: "application/json" } });
417
+ // cross-instance sync: a cancel/create in either surface refreshes the
418
+ // other immediately (no 4s poll lag)
419
+ core.onChanged = () => { void panelCore.refresh(); };
420
+ panelCore.onChanged = () => { void core.refresh(); };
414
421
  if (!res.ok) throw new Error("state HTTP " + res.status);
415
422
  return res.json();
416
423
  },