@ottttto/dsh-scheduled-send 0.3.6 → 0.3.9

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/dsh.plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "@ottttto/dsh-scheduled-send",
3
- "version": "0.3.4",
3
+ "version": "0.3.8",
4
4
  "main": "./src/index.js",
5
5
  "description": "定时发送:到点以你的身份把消息作为正常用户气泡发进当前会话,无人值守、会话隔离、断电补发。Scheduled send for DSH — fires as a normal user bubble into the bound conversation, unattended, per-session isolated, restart-safe.",
6
6
  "engines": {
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. */
@@ -236,10 +239,20 @@ function createScheduledClientState({ fetchState, postSchedule, cancelSchedule,
236
239
  },
237
240
 
238
241
  /** Cancel a pending task: DELETE on the host + drop locally. */
242
+ /** Cross-instance optimistic removal by id (no fetch). */
243
+ removeTask(id) {
244
+ tasks = tasks.filter((t) => t.id !== id);
245
+ recentAdds.delete(id);
246
+ },
247
+
239
248
  async cancelTask(id) {
240
- if (cancelSchedule) await cancelSchedule(id);
249
+ // optimistic: drop locally FIRST so the row disappears instantly; the
250
+ // DELETE then persists it. onChanged lets the sibling instance (dock ↔
251
+ // sidebar panel) refresh immediately instead of waiting for its poll.
241
252
  tasks = tasks.filter((t) => t.id !== id);
242
253
  recentAdds.delete(id);
254
+ if (typeof this.onChanged === 'function') this.onChanged(id);
255
+ if (cancelSchedule) await cancelSchedule(id);
243
256
  },
244
257
 
245
258
  start(intervalMs = 4_000) {
@@ -487,7 +500,7 @@ function createClientPluginBody(React) {
487
500
  h("div", { key: "src", style: srcStyle }, t.content),
488
501
  h("div", { key: "meta", style: metaStyle }, [
489
502
  h("span", { key: "at" }, formatLocalTime(t.sendAt)),
490
- h("span", { key: "cd" }, formatCountdown(t.sendAt, Date.now())),
503
+ h("span", { key: "cd" }, formatCountdown(t.sendAt, Date.now()) + "后发送"),
491
504
  h("span", { key: "sp", style: { marginLeft: "auto" } }, cancelBtn(t.id)),
492
505
  ]),
493
506
  ])),
@@ -678,6 +691,10 @@ function createClientPluginBody(React) {
678
691
  const panelCore = createScheduledClientState({
679
692
  fetchState: async () => {
680
693
  const res = await doFetch(stateRoutePath, { headers: { accept: "application/json" } });
694
+ // cross-instance sync: a cancel/create in either surface refreshes the
695
+ // other immediately (no 4s poll lag)
696
+ core.onChanged = (id) => { if (id) panelCore.removeTask(id); void panelCore.refresh(); };
697
+ panelCore.onChanged = () => { void core.refresh(); };
681
698
  if (!res.ok) throw new Error("state HTTP " + res.status);
682
699
  return res.json();
683
700
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ottttto/dsh-scheduled-send",
3
- "version": "0.3.6",
3
+ "version": "0.3.9",
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. */
@@ -227,10 +230,20 @@ export function createScheduledClientState({ fetchState, postSchedule, cancelSch
227
230
  },
228
231
 
229
232
  /** Cancel a pending task: DELETE on the host + drop locally. */
233
+ /** Cross-instance optimistic removal by id (no fetch). */
234
+ removeTask(id) {
235
+ tasks = tasks.filter((t) => t.id !== id);
236
+ recentAdds.delete(id);
237
+ },
238
+
230
239
  async cancelTask(id) {
231
- if (cancelSchedule) await cancelSchedule(id);
240
+ // optimistic: drop locally FIRST so the row disappears instantly; the
241
+ // DELETE then persists it. onChanged lets the sibling instance (dock ↔
242
+ // sidebar panel) refresh immediately instead of waiting for its poll.
232
243
  tasks = tasks.filter((t) => t.id !== id);
233
244
  recentAdds.delete(id);
245
+ if (typeof this.onChanged === 'function') this.onChanged(id);
246
+ if (cancelSchedule) await cancelSchedule(id);
234
247
  },
235
248
 
236
249
  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
  ])),
@@ -414,6 +414,10 @@ function createClientPluginBody(React) {
414
414
  const panelCore = createScheduledClientState({
415
415
  fetchState: async () => {
416
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 = (id) => { if (id) panelCore.removeTask(id); void panelCore.refresh(); };
420
+ panelCore.onChanged = () => { void core.refresh(); };
417
421
  if (!res.ok) throw new Error("state HTTP " + res.status);
418
422
  return res.json();
419
423
  },