dsh-remote-control 1.2.0 → 2.0.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.
@@ -36,6 +36,7 @@ module.exports = __toCommonJS(main_exports);
36
36
  var STATUS_ROUTE = "/plugins/dsh-remote-control/status";
37
37
  var NEW_ROUTE = "/plugins/dsh-remote-control/pairing/new";
38
38
  var IMAGE_ROUTE = "/plugins/dsh-remote-control/pairing.png";
39
+ var UNPAIR_ROUTE = "/plugins/dsh-remote-control/unpair";
39
40
  var SLOT_NAME = "conversation.composer.dock";
40
41
  var PILL_ID = "dsh-remote-control";
41
42
  var PILL_ORDER = 20;
@@ -65,34 +66,53 @@ function panelViewFor(answer, httpStatus) {
65
66
  function pillLabel(status) {
66
67
  const relay = status?.relay;
67
68
  if (relay === "idle") return { text: "\u8FDC\u7A0B\u672A\u542F\u52A8", tone: "off" };
68
- if (relay === "offline") return { text: "\u8FDC\u7A0B\u672A\u8FDE\u63A5", tone: "off" };
69
+ if (relay === "offline") return { text: "\u5DF2\u65AD\u5F00\u8FDE\u63A5", tone: "error" };
69
70
  if (relay === "connecting") return { text: "\u8FDE\u63A5\u4E2D", tone: "wait" };
70
71
  if (relay !== "online") return { text: "\u8FDC\u7A0B\u63A7\u5236", tone: "off" };
71
72
  const paired = typeof status?.paired === "number" ? status.paired : 0;
72
- return paired > 0 ? { text: "\u5DF2\u914D\u5BF9", tone: "on" } : { text: "\u672A\u914D\u5BF9", tone: "off" };
73
+ if (paired > 0) {
74
+ const waiting = waitingCount(status);
75
+ if (waiting > 0) return { text: `\u7B49 ${waiting} \u4EF6\u4E8B`, tone: "wait" };
76
+ return { text: "\u5DF2\u914D\u5BF9", tone: "on" };
77
+ }
78
+ return { text: "\u672A\u914D\u5BF9", tone: "off" };
79
+ }
80
+ function waitingCount(status) {
81
+ const count = status?.waiting;
82
+ return typeof count === "number" && count > 0 ? Math.min(Math.floor(count), 99) : 0;
83
+ }
84
+ function humanDuration(sec) {
85
+ const total = typeof sec === "number" && sec > 0 ? Math.floor(sec) : 0;
86
+ if (total < 60) return `${total} \u79D2`;
87
+ const minutes = Math.floor(total / 60);
88
+ return minutes >= 10 ? `${minutes} \u5206` : `${minutes} \u5206 ${total % 60} \u79D2`;
89
+ }
90
+ function waitingRow(status) {
91
+ const count = waitingCount(status);
92
+ if (count === 0) return "";
93
+ const oldest = typeof status?.waitingOldestSec === "number" ? status.waitingOldestSec : 0;
94
+ return `${count} \u4EF6${oldest > 0 ? ` \xB7 \u6700\u4E45 ${humanDuration(oldest)}` : ""}`.slice(0, 40);
73
95
  }
74
- var RELAY_TEXT = {
75
- online: "\u5DF2\u8FDE\u63A5",
76
- connecting: "\u8FDE\u63A5\u4E2D",
77
- offline: "\u672A\u8FDE\u63A5",
78
- idle: "\u672A\u542F\u52A8"
79
- };
80
96
  function relayAddress(url) {
81
97
  if (typeof url !== "string" || url.trim() === "") return "";
82
98
  const withoutScheme = url.trim().replace(/^[a-z][a-z0-9+.-]*:\/\//i, "");
83
99
  const authority = /^[^/?]*/.exec(withoutScheme)?.[0] ?? "";
84
100
  return authority.slice(0, 40);
85
101
  }
86
- function infoRows(status) {
87
- const rows = [];
88
- const label = typeof status?.hostLabel === "string" ? status.hostLabel.trim() : "";
89
- if (label !== "") rows.push(["\u672C\u673A", label]);
90
- const server = relayAddress(status?.serverUrl);
91
- if (server !== "") rows.push(["\u4E2D\u7EE7", server]);
92
- const relay = typeof status?.relay === "string" ? status.relay : "";
93
- rows.push(["\u72B6\u6001", RELAY_TEXT[relay] ?? "\u672A\u77E5"]);
94
- rows.push(["\u5DF2\u914D\u5BF9", `${typeof status?.paired === "number" ? status.paired : 0} \u53F0`]);
95
- return rows;
102
+ function relayRow(status) {
103
+ return relayAddress(status?.serverUrl);
104
+ }
105
+ function relayStateRow(status) {
106
+ const relay = status?.relay;
107
+ if (relay === "online") return "\u5DF2\u8FDE\u63A5";
108
+ if (relay === "connecting") return "\u8FDE\u63A5\u4E2D";
109
+ if (relay === "offline") return "\u5DF2\u65AD\u5F00";
110
+ if (relay === "idle") return "\u672A\u542F\u52A8";
111
+ return "";
112
+ }
113
+ function versionRow(status) {
114
+ const version = status?.version;
115
+ return typeof version === "string" && version.trim() !== "" ? version.trim().slice(0, 24) : "";
96
116
  }
97
117
  function secondsText(ms) {
98
118
  const whole = Math.max(0, Math.round(ms / 1e3));
@@ -112,24 +132,36 @@ var CSS = `
112
132
  .drc-dot { width: 6px; height: 6px; flex: 0 0 auto; border-radius: 50%; background: var(--dsw-alias-label-dimmed, #9aa0a6); }
113
133
  .drc-dot[data-tone="wait"] { background: var(--dsw-alias-state-warn-primary, #faad14); }
114
134
  .drc-dot[data-tone="on"] { background: var(--dsw-alias-state-success-primary, #52c41a); }
135
+ .drc-dot[data-tone="error"] { background: var(--dsw-alias-state-error-primary, #ff4d4f); }
115
136
  .drc-panel { position: absolute; bottom: calc(100% + 8px); right: 0; z-index: 30; width: 240px;
116
- padding: 12px 14px; border: 1px solid var(--dsw-alias-border-l1, rgba(0, 0, 0, 0.1)); border-radius: 12px;
137
+ padding: 10px 12px; border: 1px solid var(--dsw-alias-border-l1, rgba(0, 0, 0, 0.1)); border-radius: 12px;
117
138
  background: var(--dsw-specific-tip, var(--dsw-alias-bg-layer-1, #ffffff));
118
139
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.14), 0 2px 6px rgba(0, 0, 0, 0.06);
119
140
  color: var(--dsw-alias-label-primary); font-size: 12px; line-height: 18px; }
120
- /* \u8FDE\u63A5\u4FE1\u606F\u90A3\u4E00\u680F\uFF1A\u952E\u5DE6\u503C\u53F3\uFF0C\u503C\u88AB\u622A\u65AD\u65F6\u7701\u7565\u53F7\u2014\u2014\u548C\u90A3\u9897 pill \u540C\u4E00\u5957"\u6324\u4E0D\u4E0B\u5C31\u622A\u6587\u5B57\u4E0D\u6491\u5BB9\u5668"\u3002 */
121
- .drc-info { display: flex; flex-direction: column; gap: 2px; margin-bottom: 8px;
122
- padding-bottom: 6px; border-bottom: 1px solid var(--dsw-alias-border-l1, rgba(0, 0, 0, 0.08)); }
123
- .drc-row { display: flex; align-items: baseline; gap: 8px; white-space: nowrap; }
141
+ /* \u62AC\u5934\u4E00\u884C\uFF1A\u5DE6\u4FA7\u72B6\u6001\uFF0C\u53F3\u4FA7\u52A8\u4F5C\u3002\u4E24\u9897\u6309\u94AE\u90FD\u6536\u5728\u8FD9\u4E2A\u53F3\u4E0A\u89D2\uFF082026-10-03 \u91CD\u8BBE\u8BA1\u7684\u8981\u6C42\uFF09\u3002 */
142
+ .drc-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
143
+ .drc-head-label { font-weight: 600; }
144
+ .drc-head-label[data-tone="off"] { color: var(--dsw-alias-label-secondary, #6b7280); }
145
+ .drc-head-label[data-tone="wait"] { color: var(--dsw-alias-state-warn-primary, #faad14); }
146
+ .drc-head-label[data-tone="on"] { color: var(--dsw-alias-state-success-primary, #52c41a); }
147
+ .drc-head-label[data-tone="error"] { color: var(--dsw-alias-state-error-primary, #ff4d4f); }
148
+ .drc-actions { display: flex; gap: 6px; flex: 0 0 auto; }
149
+ .drc-btn { padding: 2px 10px; border: 1px solid var(--dsw-alias-border-l1, rgba(0, 0, 0, 0.1));
150
+ border-radius: 8px; background: transparent; color: inherit; font: inherit; font-size: 12px; cursor: pointer;
151
+ white-space: nowrap; }
152
+ .drc-btn:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.04)); }
153
+ /* \u6B63\u6587\u90A3\u4E09\u884C\uFF08\u4E2D\u7EE7 / \u72B6\u6001 / \u7248\u672C\uFF09\uFF1A\u952E\u5DE6\u503C\u53F3\uFF08space-between\uFF09\uFF0C\u503C\u88AB\u622A\u65AD\u65F6\u7701\u7565\u53F7\u2014\u2014
154
+ \u548C\u90A3\u9897 pill \u540C\u4E00\u5957"\u6324\u4E0D\u4E0B\u5C31\u622A\u6587\u5B57\u4E0D\u6491\u5BB9\u5668"\u3002\u7B2C\u4E00\u884C\u5E26\u90A3\u6761\u5206\u9694\u7EBF\uFF0C\u540E\u4E24\u884C\u53EA\u7559\u884C\u8DDD\u3002 */
155
+ .drc-info { display: flex; align-items: baseline; justify-content: space-between; gap: 8px;
156
+ white-space: nowrap; margin-top: 8px; padding-top: 6px;
157
+ border-top: 1px solid var(--dsw-alias-border-l1, rgba(0, 0, 0, 0.08)); }
158
+ .drc-info + .drc-info { margin-top: 2px; padding-top: 0; border-top: 0; }
124
159
  .drc-key { flex: 0 0 auto; color: var(--dsw-alias-label-tertiary); }
125
- .drc-value { min-width: 0; overflow: hidden; text-overflow: ellipsis; text-align: right; }
126
- .drc-qr { display: block; width: 200px; height: 200px; margin: 0 auto; image-rendering: pixelated; }
160
+ .drc-value { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
161
+ .drc-qr { display: block; width: 200px; height: 200px; margin: 8px auto 0; image-rendering: pixelated; }
127
162
  .drc-code { margin: 8px 0 0; text-align: center; font-weight: 600; font-size: 14px; letter-spacing: 2px; }
128
163
  .drc-note { margin: 4px 0 0; text-align: center; color: var(--dsw-alias-label-tertiary); }
129
164
  .drc-note[data-kind="failed"] { color: var(--dsw-alias-state-error-primary, #ff4d4f); }
130
- .drc-actions { display: flex; gap: 6px; justify-content: center; margin-top: 8px; }
131
- .drc-btn { padding: 2px 10px; border: 1px solid var(--dsw-alias-border-l1, rgba(0, 0, 0, 0.1));
132
- border-radius: 8px; background: transparent; color: inherit; font: inherit; font-size: 12px; cursor: pointer; }
133
165
  `;
134
166
  function isSlots(value) {
135
167
  if (!value || typeof value !== "object") return false;
@@ -168,7 +200,11 @@ function warn(message, error) {
168
200
  }
169
201
  function ensureStyle(doc) {
170
202
  try {
171
- if (doc.querySelector(`style[data-plugin-css="${STYLE_ID}"]`)) return;
203
+ const existing = doc.querySelector(`style[data-plugin-css="${STYLE_ID}"]`);
204
+ if (existing) {
205
+ if (existing.textContent !== CSS) existing.textContent = CSS;
206
+ return;
207
+ }
172
208
  const element = doc.createElement("style");
173
209
  element.setAttribute("data-plugin-css", STYLE_ID);
174
210
  element.textContent = CSS;
@@ -222,59 +258,61 @@ function buildPill(root, deps) {
222
258
  node.addEventListener("click", onClick);
223
259
  return node;
224
260
  };
225
- const infoBlock = () => {
226
- if (!lastStatus) return void 0;
227
- const box = doc.createElement("div");
228
- box.className = "drc-info";
229
- for (const [key, value] of infoRows(lastStatus)) {
261
+ const qrNote = () => expiresInMs > 0 ? `\u626B\u7801\u914D\u5BF9 \xB7 ${secondsText(expiresInMs)}\u540E\u8FC7\u671F` : "\u914D\u5BF9\u7801\u5DF2\u8FC7\u671F\uFF0C\u70B9\u53F3\u4E0A\u89D2\u300C\u5237\u65B0\u300D\u91CD\u65B0\u751F\u6210";
262
+ const header = () => {
263
+ const head = doc.createElement("div");
264
+ head.className = "drc-head";
265
+ const state = pillLabel(lastStatus);
266
+ const stateText = text("span", "drc-head-label", state.text);
267
+ stateText.setAttribute("data-tone", state.tone);
268
+ head.appendChild(stateText);
269
+ const actions = doc.createElement("div");
270
+ actions.className = "drc-actions";
271
+ const action = pairedNow() > 0 ? { label: "\u9000\u51FA\u914D\u5BF9", run: requestUnpair } : { label: "\u5237\u65B0", run: requestPairing };
272
+ actions.appendChild(buttonOf(action.label, () => void action.run()));
273
+ head.appendChild(actions);
274
+ return head;
275
+ };
276
+ const infoRows = () => {
277
+ if (!lastStatus) return [];
278
+ const rows = [
279
+ // 排第一:它是唯一一条"看完要做点什么"的,其余三条都是"知道就行"。
280
+ ["\u5F85\u5904\u7406", waitingRow(lastStatus)],
281
+ ["\u4E2D\u7EE7", relayRow(lastStatus)],
282
+ ["\u72B6\u6001", relayStateRow(lastStatus)],
283
+ ["\u7248\u672C", versionRow(lastStatus)]
284
+ ];
285
+ return rows.filter(([, value]) => value !== "").map(([key, value]) => {
230
286
  const row = doc.createElement("div");
231
- row.className = "drc-row";
287
+ row.className = "drc-info";
232
288
  row.appendChild(text("span", "drc-key", key));
233
289
  row.appendChild(text("span", "drc-value", value));
234
- box.appendChild(row);
235
- }
236
- return box;
290
+ return row;
291
+ });
237
292
  };
238
293
  const paint = () => {
239
294
  if (!panel) return;
240
295
  panel.replaceChildren();
241
- const info = infoBlock();
242
- if (info) panel.appendChild(info);
243
- if (view.kind === "info") {
244
- const actions2 = doc.createElement("div");
245
- actions2.className = "drc-actions";
246
- actions2.appendChild(buttonOf(pairedNow() > 0 ? "\u518D\u914D\u4E00\u53F0" : "\u751F\u6210\u914D\u5BF9\u7801", () => void requestPairing()));
247
- panel.appendChild(actions2);
248
- return;
249
- }
296
+ panel.appendChild(header());
250
297
  if (view.kind === "loading") {
251
298
  panel.appendChild(text("p", "drc-note", "\u6B63\u5728\u751F\u6210\u914D\u5BF9\u7801\u2026"));
252
- return;
253
- }
254
- if (view.kind === "qr") {
299
+ } else if (view.kind === "qr") {
255
300
  const image = doc.createElement("img");
256
301
  image.className = "drc-qr";
257
302
  image.setAttribute("alt", "\u914D\u5BF9\u4E8C\u7EF4\u7801");
258
303
  image.src = view.imageSrc;
259
304
  panel.appendChild(image);
260
305
  panel.appendChild(text("p", "drc-code", view.token));
261
- panel.appendChild(text("p", "drc-note", `\u626B\u7801\u914D\u5BF9 \xB7 ${secondsText(expiresInMs)}\u540E\u8FC7\u671F`));
262
- const actions2 = doc.createElement("div");
263
- actions2.className = "drc-actions";
264
- actions2.appendChild(buttonOf("\u6362\u4E00\u5F20", () => void requestPairing()));
265
- panel.appendChild(actions2);
266
- return;
306
+ panel.appendChild(text("p", "drc-note", qrNote()));
307
+ } else if (view.kind === "unavailable" || view.kind === "failed") {
308
+ const note = view.kind === "unavailable" ? text("p", "drc-note", view.reason) : (() => {
309
+ const node = text("p", "drc-note", `\u914D\u5BF9\u8BF7\u6C42\u6CA1\u6210\u529F\uFF1A${view.detail}`);
310
+ node.setAttribute("data-kind", "failed");
311
+ return node;
312
+ })();
313
+ panel.appendChild(note);
267
314
  }
268
- const note = view.kind === "unavailable" ? text("p", "drc-note", view.reason) : (() => {
269
- const node = text("p", "drc-note", `\u914D\u5BF9\u8BF7\u6C42\u6CA1\u6210\u529F\uFF1A${view.detail}`);
270
- node.setAttribute("data-kind", "failed");
271
- return node;
272
- })();
273
- panel.appendChild(note);
274
- const actions = doc.createElement("div");
275
- actions.className = "drc-actions";
276
- actions.appendChild(buttonOf("\u518D\u8BD5\u4E00\u6B21", () => void requestPairing()));
277
- panel.appendChild(actions);
315
+ for (const row of infoRows()) panel.appendChild(row);
278
316
  };
279
317
  const closePanel = () => {
280
318
  if (countTimer !== void 0) {
@@ -313,24 +351,49 @@ function buildPill(root, deps) {
313
351
  if (countTimer === void 0) {
314
352
  countTimer = setInterval(() => {
315
353
  expiresInMs -= 1e3;
316
- if (expiresInMs <= 0) void requestPairing();
317
- else paint();
354
+ if (expiresInMs <= 0) {
355
+ clearInterval(countTimer);
356
+ countTimer = void 0;
357
+ }
358
+ paint();
318
359
  }, 1e3);
319
360
  }
320
361
  }
321
362
  paint();
322
363
  }
364
+ async function requestUnpair() {
365
+ if (disposed || !panel) return;
366
+ try {
367
+ await deps.fetchImpl(UNPAIR_ROUTE, {
368
+ method: "POST",
369
+ headers: { "x-drc-pair": "1" },
370
+ credentials: "same-origin"
371
+ });
372
+ } catch (error) {
373
+ warn("\u9000\u51FA\u914D\u5BF9\u8BF7\u6C42\u5931\u8D25\uFF08\u9762\u677F\u4F1A\u9760\u4E0B\u4E00\u6B21\u8F6E\u8BE2\u5BF9\u8D26\uFF09", error);
374
+ }
375
+ if (disposed || !panel) return;
376
+ if (lastStatus) lastStatus = { ...lastStatus, paired: 0 };
377
+ write();
378
+ view = { kind: "loading" };
379
+ paint();
380
+ void requestPairing();
381
+ }
323
382
  const openPanel = () => {
324
383
  if (panel || disposed) return;
325
384
  panel = doc.createElement("div");
326
385
  panel.className = "drc-panel";
327
386
  panel.setAttribute("role", "dialog");
328
- panel.setAttribute("aria-label", "dsh-remote-control \u8FDE\u63A5\u4FE1\u606F");
387
+ panel.setAttribute("aria-label", "dsh-remote-control \u914D\u5BF9");
329
388
  panel.tabIndex = -1;
330
389
  root.appendChild(panel);
331
390
  button.setAttribute("aria-expanded", "true");
332
- view = { kind: "info" };
333
- paint();
391
+ if (pairedNow() > 0) {
392
+ view = { kind: "info" };
393
+ paint();
394
+ } else {
395
+ void requestPairing();
396
+ }
334
397
  try {
335
398
  panel.focus();
336
399
  } catch {
@@ -367,6 +430,14 @@ function buildPill(root, deps) {
367
430
  const body = await response.json?.();
368
431
  if (!body || typeof body !== "object") return;
369
432
  lastStatus = body;
433
+ if (view.kind === "qr" && pairedNow() > 0) {
434
+ view = { kind: "info" };
435
+ if (countTimer !== void 0) {
436
+ clearInterval(countTimer);
437
+ countTimer = void 0;
438
+ }
439
+ if (panel) paint();
440
+ }
370
441
  write();
371
442
  } catch (error) {
372
443
  deps.warn?.("status poll failed");