@1e0zj/dsh-plugin-mall 0.4.5 → 0.4.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/src/client.js CHANGED
@@ -16,9 +16,6 @@ window.__ModuleLoader__.load({
16
16
  // ── styles ──────────────────────────────────────────────────────────────
17
17
  var css = [
18
18
  ".mkt_root{display:flex;flex-direction:column;gap:12px;max-width:1040px}",
19
- ".mkt_head{display:flex;flex-direction:column;gap:2px}",
20
- ".mkt_title{font-size:16px;font-weight:600;color:var(--dsw-alias-label-primary)}",
21
- ".mkt_sub{font-size:12px;color:var(--dsw-alias-label-tertiary)}",
22
19
  ".mkt_row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}",
23
20
  ".mkt_input{flex:1;min-width:200px;background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);border-radius:6px;padding:6px 10px;font-size:13px;color:var(--dsw-alias-label-primary)}",
24
21
  ".mkt_input::placeholder{color:var(--dsw-alias-label-caption)}",
@@ -31,6 +28,7 @@ window.__ModuleLoader__.load({
31
28
  ".mkt_btnDanger:hover:not(:disabled){border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}",
32
29
  ".mkt_btnSm{padding:3px 10px;font-size:12px}",
33
30
  ".mkt_error{color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px}",
31
+ ".mkt_notice{border-left:3px solid var(--dsw-alias-state-error-primary);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-state-error-primary);padding:7px 10px;font-size:12px;line-height:18px;overflow-wrap:anywhere}",
34
32
  ".mkt_list{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;align-items:stretch;min-width:0}",
35
33
  "@media (max-width:820px){.mkt_list{grid-template-columns:minmax(0,1fr)}}",
36
34
  ".mkt_listHead{grid-column:1/-1;font-size:12px;color:var(--dsw-alias-label-tertiary)}",
@@ -46,8 +44,9 @@ window.__ModuleLoader__.load({
46
44
  ".mkt_panelTitle{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary);margin:0}",
47
45
  ".mkt_panelRow{display:flex;align-items:center;gap:8px}",
48
46
  ".mkt_panelRow .mkt_link{margin-left:auto}",
49
- ".mkt_pre{font-family:Consolas,Monaco,monospace;font-size:11.5px;line-height:16px;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-markdown-code-block);border-radius:6px;padding:8px;max-height:220px;overflow:auto;white-space:pre-wrap;word-break:break-all}",
47
+ ".mkt_pre{box-sizing:border-box;width:100%;max-width:100%;min-width:0;font-family:Consolas,Monaco,monospace;font-size:11.5px;line-height:16px;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-markdown-code-block);border-radius:6px;padding:8px;max-height:280px;overflow:auto;white-space:pre;word-break:normal}",
50
48
  ".mkt_ok{color:var(--dsw-alias-state-success-primary)}",
49
+ ".mkt_warn{color:var(--dsw-alias-state-warn-label)}",
51
50
  ".mkt_badge{display:inline-block;border-radius:999px;padding:1px 8px;font-size:11px;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-tertiary)}",
52
51
  ".mkt_badgeOk{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary)}",
53
52
  ".mkt_badgeWarn{border-color:var(--dsw-alias-state-warn-primary);color:var(--dsw-alias-state-warn-label)}",
@@ -60,13 +59,31 @@ window.__ModuleLoader__.load({
60
59
  ".mkt_depList{display:flex;flex-direction:column;gap:6px;margin-top:8px}",
61
60
  ".mkt_depRow{display:flex;justify-content:space-between;align-items:center;gap:8px}",
62
61
  ".mkt_depActions{display:flex;align-items:center;gap:6px;flex-wrap:wrap}",
63
- ".mkt_approve{display:flex;flex-direction:column;gap:8px;border:1px solid var(--dsw-alias-state-error-primary);border-radius:8px;padding:10px 12px;margin:4px 0}",
64
- ".mkt_approveHead{font-size:13px;font-weight:600;color:var(--dsw-alias-state-error-primary)}",
62
+ ".mkt_decision{display:flex;flex-direction:column;border:1px solid var(--dsw-alias-state-warn-secondary);border-radius:6px;overflow:hidden;margin-top:4px}",
63
+ ".mkt_decisionError{border-color:var(--dsw-alias-state-error-primary)}",
64
+ ".mkt_decisionHead{display:flex;align-items:baseline;gap:8px;flex-wrap:wrap;padding:8px 10px;background:var(--dsw-alias-state-warn-tertiary);color:var(--dsw-alias-state-warn-label)}",
65
+ ".mkt_decisionError .mkt_decisionHead{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-state-error-primary)}",
66
+ ".mkt_decisionTitle{font-size:13px;font-weight:600}",
67
+ ".mkt_decisionBody{display:flex;flex-direction:column;gap:8px;min-width:0;padding:10px;max-height:300px;overflow:auto}",
68
+ ".mkt_decisionActions{display:flex;justify-content:flex-end;align-items:center;gap:8px;padding:8px 10px;border-top:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3)}",
65
69
  ".mkt_approvePkg{display:flex;flex-direction:column;gap:4px}",
66
70
  ".mkt_approveName{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary);margin-right:8px;overflow-wrap:anywhere}",
67
71
  ".mkt_approveCmd{max-height:none;margin:0}",
68
72
  ".mkt_jobDone{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-top:2px}",
69
- ".mkt_logBlock{display:flex;flex-direction:column;gap:4px;align-items:flex-start}",
73
+ ".mkt_logBlock{display:flex;flex-direction:column;gap:4px;align-items:stretch;min-width:0;max-width:100%}",
74
+ ".mkt_linkButton{align-self:flex-start;border:0;background:none;padding:0;color:var(--dsw-alias-state-business-primary);font:inherit;font-size:12px;cursor:pointer}",
75
+ ".mkt_job{display:flex;flex-direction:column;gap:6px;min-width:0;max-width:100%;padding-top:10px;border-top:1px solid var(--dsw-alias-border-l2)}",
76
+ ".mkt_panelRow + .mkt_job{padding-top:0;border-top:0}",
77
+ ".mkt_jobHead{display:flex;align-items:center;gap:8px;min-width:0}",
78
+ ".mkt_jobIdentity{display:flex;align-items:center;gap:7px;min-width:0;font-size:12px;color:var(--dsw-alias-label-tertiary)}",
79
+ ".mkt_jobHead .mkt_btn{margin-left:auto;flex:none}",
80
+ ".mkt_stateDot{width:7px;height:7px;border-radius:50%;background:var(--dsw-alias-label-caption);flex:none}",
81
+ ".mkt_stateRunning{background:var(--dsw-alias-state-business-primary);animation:mktPulse 1.6s ease-in-out infinite}",
82
+ ".mkt_stateDone{background:var(--dsw-alias-state-success-primary)}",
83
+ ".mkt_stateWarn{background:var(--dsw-alias-state-warn-primary)}",
84
+ ".mkt_stateError{background:var(--dsw-alias-state-error-primary)}",
85
+ "@keyframes mktPulse{0%,100%{opacity:.45}50%{opacity:1}}",
86
+ "@media (prefers-reduced-motion:reduce){.mkt_stateRunning{animation:none}.mkt_switch,.mkt_switchKnob{transition:none}}",
70
87
  ".mkt_depOff{opacity:.5;text-decoration:line-through}",
71
88
  ".mkt_switch{position:relative;width:34px;height:18px;border-radius:999px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-module-platform);cursor:pointer;padding:0;transition:background .15s,border-color .15s}",
72
89
  ".mkt_switch:disabled{opacity:.55;cursor:default}",
@@ -74,11 +91,16 @@ window.__ModuleLoader__.load({
74
91
  ".mkt_switchKnob{position:absolute;top:1px;left:1px;width:14px;height:14px;border-radius:50%;background:var(--dsw-alias-label-primary-foreground);transition:transform .15s;box-shadow:var(--dsw-shadow-lv1)}",
75
92
  ".mkt_switchOn .mkt_switchKnob{transform:translateX(16px)}",
76
93
  ".mkt_issueList{list-style:none;display:flex;flex-direction:column;gap:8px;margin:0;padding:0}",
77
- ".mkt_issue{border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:8px 10px;background:var(--dsw-alias-bg-layer-3)}",
94
+ ".mkt_issue{border:1px solid var(--dsw-alias-border-l2);border-radius:6px;padding:8px 10px;background:var(--dsw-alias-bg-layer-3)}",
78
95
  ".mkt_issueBlock{border-color:var(--dsw-alias-state-error-primary)}",
79
96
  ".mkt_issueWarn{border-color:var(--dsw-alias-state-warn-primary)}",
80
97
  ".mkt_issueTitle{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary)}",
81
98
  ".mkt_issueDetail{font-size:12px;color:var(--dsw-alias-label-secondary);margin-top:4px;line-height:18px;overflow-wrap:anywhere}",
99
+ ".mkt_modalMask{position:fixed;inset:0;z-index:1000;display:flex;align-items:center;justify-content:center;padding:20px;background:var(--dsw-alias-bg-mask-3)}",
100
+ ".mkt_modal{width:min(420px,100%);max-height:min(520px,calc(100vh - 40px));display:flex;flex-direction:column;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-3);box-shadow:var(--dsw-shadow-lv3)}",
101
+ ".mkt_modalHead{padding:14px 16px 8px;font-size:15px;font-weight:600;color:var(--dsw-alias-label-primary)}",
102
+ ".mkt_modalBody{padding:0 16px 14px;font-size:12.5px;line-height:19px;color:var(--dsw-alias-label-secondary);overflow:auto}",
103
+ ".mkt_modalActions{display:flex;justify-content:flex-end;gap:8px;padding:10px 16px;border-top:1px solid var(--dsw-alias-border-l2)}",
82
104
  ].join("\n");
83
105
  var tagId = "@1e0zj/dsh-plugin-mall/market-tab.css";
84
106
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
@@ -105,6 +127,10 @@ window.__ModuleLoader__.load({
105
127
  return String(e && e.message ? e.message : e);
106
128
  }
107
129
 
130
+ function isJobActive(status) {
131
+ return status === "running" || status === "stopping";
132
+ }
133
+
108
134
  function kindLabel(kind) {
109
135
  if (kind === "bundle") return "宿主插件层";
110
136
  if (kind === "client") return "浏览器UI插件";
@@ -162,7 +188,7 @@ window.__ModuleLoader__.load({
162
188
  var current = jobsRef.current;
163
189
  var running = Object.keys(current).filter(function (id) {
164
190
  var job = current[id];
165
- return job && job.status !== "completed" && job.status !== "failed" && job.status !== "killed";
191
+ return job && isJobActive(job.status);
166
192
  });
167
193
  if (running.length === 0) return;
168
194
  running.forEach(function (id) {
@@ -214,21 +240,23 @@ window.__ModuleLoader__.load({
214
240
  delete next[carryFromId];
215
241
  }
216
242
  for (var key in next) {
217
- if (key !== id && next[key] && next[key].spec === spec && next[key].status !== "running") {
243
+ if (key !== id && next[key] && next[key].spec === spec && !isJobActive(next[key].status)) {
218
244
  delete next[key];
219
245
  }
220
246
  }
221
247
  next[id] = { status: "running", spec: spec, output: carried };
222
248
  commit(next);
223
249
  }, [commit]);
224
- var clear = useCallback(function () {
225
- commit({});
226
- }, [commit]);
227
250
  var drop = useCallback(function (id) {
228
251
  var next = Object.assign({}, jobsRef.current);
229
252
  delete next[id];
230
253
  commit(next);
231
254
  }, [commit]);
255
+ var setStatus = useCallback(function (id, status) {
256
+ var current = jobsRef.current[id];
257
+ if (!current) return;
258
+ commit(Object.assign({}, jobsRef.current, { [id]: Object.assign({}, current, { status: status }) }));
259
+ }, [commit]);
232
260
  // 恢复后端任务记录(tracker.list 的形状):安装事务改写
233
261
  // cordis.patch.yml 会让 dsh 重放装配树、整个市场 UI 重挂载,React
234
262
  // state 全丢——任务面板、完成提醒、重启按钮一起消失(真实事故:
@@ -272,7 +300,7 @@ window.__ModuleLoader__.load({
272
300
  for (var key in next) {
273
301
  if (serverIds[key]) continue;
274
302
  var stale = next[key];
275
- if (stale.status === "running") {
303
+ if (isJobActive(stale.status)) {
276
304
  next[key] = Object.assign({}, stale, {
277
305
  status: "killed",
278
306
  detail: "宿主进程已重启,该任务的记录随之丢失",
@@ -285,7 +313,7 @@ window.__ModuleLoader__.load({
285
313
  }
286
314
  commit(next);
287
315
  }, [commit]);
288
- return { jobs: jobs, track: track, clear: clear, drop: drop, restore: restore };
316
+ return { jobs: jobs, track: track, drop: drop, setStatus: setStatus, restore: restore };
289
317
  }
290
318
 
291
319
  // ── plugin verification badge ───────────────────────────────────────────
@@ -322,7 +350,10 @@ window.__ModuleLoader__.load({
322
350
  var installLabel = props.alreadyInstalled === true ? "已装" : "安装";
323
351
  var installDisabled = installing || props.alreadyInstalled === true;
324
352
  if (!installing && job) {
325
- if (job.status === "running") { installLabel = "安装中…"; installDisabled = true; }
353
+ if (isJobActive(job.status)) {
354
+ installLabel = job.status === "stopping" ? "停止中…" : "安装中…";
355
+ installDisabled = true;
356
+ }
326
357
  else if (job.status === "completed") { installLabel = "已装 · 重启生效"; installDisabled = true; }
327
358
  else if (job.status === "failed") { installLabel = "安装失败 · 见任务日志"; }
328
359
  else { installLabel = "已取消 · 重试"; }
@@ -349,11 +380,62 @@ window.__ModuleLoader__.load({
349
380
  );
350
381
  }
351
382
 
383
+ function DecisionPanel(props) {
384
+ var tone = props.tone === "error" ? " mkt_decisionError" : "";
385
+ return h("div", { className: "mkt_decision" + tone },
386
+ h("div", { className: "mkt_decisionHead" },
387
+ h("span", { className: "mkt_decisionTitle" }, props.title),
388
+ props.meta ? h("span", { className: "mkt_meta" }, props.meta) : null),
389
+ h("div", { className: "mkt_decisionBody" }, props.children),
390
+ h("div", { className: "mkt_decisionActions" }, props.actions));
391
+ }
392
+
393
+ function ErrorNotice(props) {
394
+ if (!props.message) return null;
395
+ return h("div", { className: "mkt_notice", role: "alert" }, props.message);
396
+ }
397
+
398
+ function RestartDialog(props) {
399
+ useEffect(function () {
400
+ function onKeyDown(event) {
401
+ if (event.key === "Escape" && props.busy !== true) props.onCancel();
402
+ }
403
+ document.addEventListener("keydown", onKeyDown);
404
+ return function () { document.removeEventListener("keydown", onKeyDown); };
405
+ }, [props.busy, props.onCancel]);
406
+ return h("div", {
407
+ className: "mkt_modalMask",
408
+ onMouseDown: function (event) {
409
+ if (event.target === event.currentTarget && props.busy !== true) props.onCancel();
410
+ },
411
+ }, h("div", { className: "mkt_modal", role: "dialog", "aria-modal": "true", "aria-labelledby": "mkt-restart-title" },
412
+ h("div", { id: "mkt-restart-title", className: "mkt_modalHead" }, "重启 dsh"),
413
+ h("div", { className: "mkt_modalBody" }, props.activeCount > 0
414
+ ? "当前有 " + props.activeCount + " 个任务仍在进行。重启会中断这些任务,未完成的改动将由事务恢复逻辑处理。"
415
+ : "重启后,已经完成的插件安装、更新或卸载才会生效。"),
416
+ h("div", { className: "mkt_modalActions" },
417
+ h("button", { className: "mkt_btn", disabled: props.busy === true, autoFocus: true, onClick: props.onCancel }, "取消"),
418
+ h("button", { className: "mkt_btn mkt_btnPrimary", disabled: props.busy === true, onClick: props.onConfirm }, props.busy ? "重启中…" : "确认重启"))));
419
+ }
420
+
352
421
  // ── install-script approval ─────────────────────────────────────────────
353
422
  function ApprovalRequest(props) {
354
423
  var pkgs = props.needsApproval || [];
355
- return h("div", { className: "mkt_approve" },
356
- h("div", { className: "mkt_approveHead" }, "需要你确认:这次安装会执行安装期代码"),
424
+ var actions = [
425
+ h("button", { key: "cancel", className: "mkt_btn mkt_btnSm", disabled: props.busy === true, onClick: props.onDismiss }, "取消"),
426
+ h("button", {
427
+ key: "allow",
428
+ className: "mkt_btn mkt_btnPrimary mkt_btnSm",
429
+ disabled: props.busy === true,
430
+ onClick: function () { props.onApprove(pkgs.map(function (p) { return p.name; })); },
431
+ }, props.busy ? "继续中…" : "允许并继续"),
432
+ ];
433
+ return h(DecisionPanel, {
434
+ title: "安装期代码需要确认",
435
+ meta: clip(props.spec || "", 50),
436
+ actions: actions,
437
+ },
438
+ props.detail ? h("div", { className: "mkt_desc" }, props.detail) : null,
357
439
  pkgs.map(function (p, index) {
358
440
  var facts = [];
359
441
  if (typeof p.weeklyDownloads === "number") facts.push("周下载 " + p.weeklyDownloads.toLocaleString());
@@ -373,16 +455,7 @@ window.__ModuleLoader__.load({
373
455
  h("div", { className: "mkt_meta" }, facts.join(" · "))
374
456
  );
375
457
  }),
376
- h("div", { className: "mkt_meta" }, "这些命令会以你的权限在你的机器上运行,早于任何插件代码加载。"),
377
- h("div", { className: "mkt_row" },
378
- h("button", {
379
- className: "mkt_btn mkt_btnPrimary mkt_btnSm",
380
- disabled: props.busy === true,
381
- onClick: function () { props.onApprove(pkgs.map(function (p) { return p.name; })); },
382
- }, props.busy ? "继续中…" : "允许并继续"),
383
- h("button", { className: "mkt_btn mkt_btnSm", onClick: props.onDismiss }, "取消")
384
- )
385
- );
458
+ h("div", { className: "mkt_meta" }, "这些命令会以你的权限在你的机器上运行,早于任何插件代码加载。"));
386
459
  }
387
460
 
388
461
  // ── preflight result card ────────────────────────────────────────────────
@@ -394,11 +467,23 @@ window.__ModuleLoader__.load({
394
467
  var blocked = report.verdict === "blocked";
395
468
  var title = blocked ? "安装被阻止" : "安装存在风险";
396
469
  var issues = report.issues || [];
397
- return h("div", { className: "mkt_card" },
398
- h("div", { className: "mkt_panelRow" },
399
- h("p", { className: "mkt_panelTitle" + (blocked ? " mkt_error" : "") }, title),
400
- h("span", { className: "mkt_meta" }, clip(props.spec, 50))
401
- ),
470
+ var actions = blocked
471
+ ? h("button", { className: "mkt_btn mkt_btnSm", onClick: props.onClose }, "关闭")
472
+ : [
473
+ h("button", { key: "cancel", className: "mkt_btn mkt_btnSm", disabled: props.busy === true, onClick: props.onClose }, "取消"),
474
+ h("button", {
475
+ key: "confirm",
476
+ className: "mkt_btn mkt_btnPrimary mkt_btnSm",
477
+ disabled: props.busy === true,
478
+ onClick: props.onConfirm,
479
+ }, props.busy ? "安装中…" : "了解风险并继续"),
480
+ ];
481
+ return h(DecisionPanel, {
482
+ tone: blocked ? "error" : "warning",
483
+ title: title,
484
+ meta: clip(props.spec, 50),
485
+ actions: actions,
486
+ },
402
487
  report.summary ? h("div", { className: "mkt_desc" }, report.summary) : null,
403
488
  issues.length > 0 ? h("ul", { className: "mkt_issueList" },
404
489
  issues.map(function (issue) {
@@ -409,21 +494,7 @@ window.__ModuleLoader__.load({
409
494
  h("div", { className: "mkt_issueTitle" }, issue.title),
410
495
  issue.detail ? h("div", { className: "mkt_issueDetail" }, issue.detail) : null);
411
496
  })
412
- ) : null,
413
- h("div", { className: "mkt_row" },
414
- blocked
415
- ? h("button", { className: "mkt_btn mkt_btnSm", onClick: props.onClose }, "关闭")
416
- : [
417
- h("button", { key: "cancel", className: "mkt_btn mkt_btnSm", onClick: props.onClose }, "取消"),
418
- h("button", {
419
- key: "confirm",
420
- className: "mkt_btn mkt_btnPrimary mkt_btnSm",
421
- disabled: props.busy === true,
422
- onClick: props.onConfirm,
423
- }, props.busy ? "安装中…" : "我已了解风险,继续安装"),
424
- ]
425
- )
426
- );
497
+ ) : null);
427
498
  }
428
499
 
429
500
  var MARKET_PACKAGE = "@1e0zj/dsh-plugin-mall";
@@ -438,64 +509,99 @@ window.__ModuleLoader__.load({
438
509
  if (status === "completed") return "完成";
439
510
  if (status === "failed") return "失败";
440
511
  if (status === "killed") return "已取消";
512
+ if (status === "stopping") return "停止中";
441
513
  return "进行中";
442
514
  }
443
515
 
444
516
  function jobStatusClass(status) {
445
517
  if (status === "completed") return "mkt_ok";
446
- if (status === "failed" || status === "killed") return "mkt_error";
518
+ if (status === "failed") return "mkt_error";
519
+ if (status === "killed" || status === "stopping") return "mkt_warn";
447
520
  return "";
448
521
  }
449
522
 
523
+ function jobStateDotClass(status, awaitingDecision) {
524
+ if (awaitingDecision || status === "killed" || status === "stopping") return "mkt_stateWarn";
525
+ if (status === "completed") return "mkt_stateDone";
526
+ if (status === "failed") return "mkt_stateError";
527
+ return "mkt_stateRunning";
528
+ }
529
+
450
530
  // 任务日志:跑的时候只露尾部几行(够看进度,不霸屏),落定后默认折叠
451
531
  // ——成功的日志没人回头读,失败的才需要,点开即可。
452
- var LOG_TAIL_LINES = 8;
532
+ var LOG_TAIL_LINES = 12;
453
533
  function JobLog(props) {
454
534
  var _open = useState(false);
455
535
  var open = _open[0];
456
536
  var setOpen = _open[1];
457
537
  var text = String(props.output || "").trimEnd();
538
+ var failed = props.status === "failed";
539
+ useEffect(function () {
540
+ if (failed) setOpen(true);
541
+ }, [failed]);
458
542
  if (text.length === 0) return null;
459
543
  var lines = text.split("\n");
460
- var collapsed = props.done && !open;
461
- var shown = (props.done || lines.length <= LOG_TAIL_LINES) ? lines : lines.slice(-LOG_TAIL_LINES);
544
+ var settled = !isJobActive(props.status);
545
+ var collapsed = settled && !open;
546
+ var shown = settled || lines.length <= LOG_TAIL_LINES ? lines : lines.slice(-LOG_TAIL_LINES);
547
+ var rendered = shown.join("\n");
548
+ if (rendered.length > 64000) rendered = "…前部日志已截断…\n" + rendered.slice(-64000);
462
549
  // 展开/收起始终在日志块上方:放在下方的话,点开后按钮会被推到长长的
463
550
  // 日志末尾,想收起还得先滚回去。
464
551
  return h("div", { className: "mkt_logBlock" },
465
- props.done
466
- ? h("span", {
467
- className: "mkt_link",
552
+ settled
553
+ ? h("button", {
554
+ type: "button",
555
+ className: "mkt_linkButton",
556
+ "aria-expanded": open ? "true" : "false",
468
557
  onClick: function () { setOpen(!open); },
469
558
  }, open ? "收起日志" : "查看日志(" + lines.length + " 行)")
470
559
  : lines.length > LOG_TAIL_LINES
471
560
  ? h("div", { className: "mkt_meta" }, "只显示最后 " + LOG_TAIL_LINES + " 行")
472
561
  : null,
473
- collapsed ? null : h("pre", { className: "mkt_pre" }, shown.join("\n").slice(-4000))
562
+ collapsed ? null : h("pre", { className: "mkt_pre", "aria-label": "任务日志" }, rendered)
474
563
  );
475
564
  }
476
565
 
477
566
  // ── jobs panel ──────────────────────────────────────────────────────────
478
567
  function JobsPanel(props) {
479
568
  var ids = Object.keys(props.jobs);
480
- if (ids.length === 0) return null;
569
+ if (ids.length === 0 && !props.error) return null;
570
+ var hasSettled = ids.some(function (id) {
571
+ var job = props.jobs[id];
572
+ var hasDecision = (props.preflight && props.preflight.jobId === id)
573
+ || (job.needsApproval && job.needsApproval.length > 0);
574
+ return !isJobActive(job.status) && !hasDecision;
575
+ });
481
576
  return h("div", { className: "mkt_card" },
482
577
  h("div", { className: "mkt_panelRow" },
483
578
  h("p", { className: "mkt_panelTitle" }, "任务"),
484
- props.onClear ? h("span", { className: "mkt_link", onClick: props.onClear }, "清空") : null
579
+ props.onClear && hasSettled ? h("span", { className: "mkt_link", onClick: props.onClear }, "清空已结束") : null
485
580
  ),
581
+ h(ErrorNotice, { message: props.error }),
486
582
  ids.map(function (id) {
487
583
  var job = props.jobs[id];
488
584
  var done = job.status === "completed" || job.status === "failed" || job.status === "killed";
489
- return h("div", { key: id, style: { display: "flex", flexDirection: "column", gap: "4px" } },
585
+ var preflightDecision = props.preflight && props.preflight.jobId === id ? props.preflight : null;
586
+ var awaitingDecision = preflightDecision !== null || (job.needsApproval && job.needsApproval.length > 0);
587
+ return h("div", { key: id, className: "mkt_job" },
490
588
  // 不再显示 market-N 这种内部 id——对用户没有意义,反而让人以为
491
589
  // 自己点了两次。只说清「在做什么 · 到哪一步了」。
492
- // 状态词单独上色(完成绿、失败/取消红),前半段保持灰——整行
493
- // 都染色会喧宾夺主,只有结论需要一眼看见。
494
- h("div", { className: "mkt_meta" },
495
- jobKindLabel(job.kind) + clip(job.spec || "", 44) + " · ",
496
- h("span", { className: jobStatusClass(job.status) }, jobStatusLabel(job.status))),
590
+ // 状态词和状态点表达结论,任务类型与目标保持次要色。
591
+ h("div", { className: "mkt_jobHead" },
592
+ h("div", { className: "mkt_jobIdentity" },
593
+ h("span", { className: "mkt_stateDot " + jobStateDotClass(job.status, awaitingDecision), "aria-hidden": "true" }),
594
+ h("span", null, jobKindLabel(job.kind) + clip(job.spec || "", 44) + " · "),
595
+ h("span", { className: awaitingDecision ? "mkt_warn" : jobStatusClass(job.status) }, awaitingDecision ? "待确认" : jobStatusLabel(job.status))),
596
+ isJobActive(job.status) ? h("button", {
597
+ className: "mkt_btn mkt_btnSm",
598
+ disabled: job.status === "stopping",
599
+ onClick: function () { props.onCancel(id); },
600
+ }, job.status === "stopping" ? "停止中…" : "停止") : null),
497
601
  job.needsApproval && job.needsApproval.length > 0
498
602
  ? h(ApprovalRequest, {
603
+ spec: job.spec,
604
+ detail: job.detail,
499
605
  needsApproval: job.needsApproval,
500
606
  busy: props.approving === job.spec,
501
607
  onApprove: function (names) {
@@ -508,7 +614,15 @@ window.__ModuleLoader__.load({
508
614
  },
509
615
  onDismiss: function () { props.onDismiss(id); },
510
616
  })
511
- : job.detail ? h("div", { className: "mkt_desc" }, job.detail) : null,
617
+ : preflightDecision
618
+ ? h(PreflightCard, {
619
+ spec: preflightDecision.spec,
620
+ report: preflightDecision.report,
621
+ busy: props.preflightBusy === true,
622
+ onConfirm: props.onConfirmPreflight,
623
+ onClose: props.onClosePreflight,
624
+ })
625
+ : job.detail ? h("div", { className: job.status === "failed" ? "mkt_error" : "mkt_desc" }, job.detail) : null,
512
626
  // 预检完成不提示重启——它没有改动任何东西,接下来才是安装。
513
627
  // 也不再重复一个绿色「完成」:状态行已经写了「· 完成」。
514
628
  done && job.status === "completed" && job.kind !== "dsh-plugin-preflight"
@@ -523,10 +637,8 @@ window.__ModuleLoader__.load({
523
637
  disabled: props.restarting === true,
524
638
  onClick: props.onRestart,
525
639
  }, props.restarting ? "重启中…" : "重启 dsh 生效"))
526
- : job.status === "failed" && !(job.needsApproval && job.needsApproval.length > 0)
527
- ? h("div", { className: "mkt_error" }, "失败,见下方输出")
528
- : null,
529
- h(JobLog, { output: job.output, done: done })
640
+ : null,
641
+ h(JobLog, { output: job.output, status: job.status })
530
642
  );
531
643
  })
532
644
  );
@@ -551,6 +663,7 @@ window.__ModuleLoader__.load({
551
663
  h("span", { className: "mkt_badge" }, count + " 个"),
552
664
  h("span", { className: "mkt_meta mkt_installedToggle" }, open ? "收起" : "展开")
553
665
  ),
666
+ h(ErrorNotice, { message: props.error }),
554
667
  installed.error
555
668
  ? h("div", { className: "mkt_error" }, installed.error)
556
669
  : !open ? null
@@ -607,6 +720,9 @@ window.__ModuleLoader__.load({
607
720
  var _error = useState(null);
608
721
  var error = _error[0];
609
722
  var setError = _error[1];
723
+ var reportError = useCallback(function (scope, value) {
724
+ setError({ scope: scope, message: errorText(value) });
725
+ }, []);
610
726
  var _installed = useState(null);
611
727
  var installed = _installed[0];
612
728
  var setInstalled = _installed[1];
@@ -648,6 +764,9 @@ window.__ModuleLoader__.load({
648
764
  var _restarting = useState(false);
649
765
  var restarting = _restarting[0];
650
766
  var setRestarting = _restarting[1];
767
+ var _restartConfirm = useState(false);
768
+ var restartConfirm = _restartConfirm[0];
769
+ var setRestartConfirm = _restartConfirm[1];
651
770
  // 本次宿主进程的启动时间(jobs 端点带回):完成时间早于它的任务,
652
771
  // 其「重启 dsh 生效」按钮已经兑现,改显示「重启已生效」。
653
772
  var _hostStartedAt = useState(0);
@@ -717,11 +836,11 @@ window.__ModuleLoader__.load({
717
836
  setCompat({});
718
837
  verifyPage(value.items);
719
838
  }).catch(function (e) {
720
- setError(errorText(e));
839
+ reportError("search", e);
721
840
  }).finally(function () {
722
841
  setLoading(false);
723
842
  });
724
- }, [call, query, sort, verifyPage]);
843
+ }, [call, query, reportError, sort, verifyPage]);
725
844
 
726
845
  var canLoadMore = results !== null && results.items.length < results.total && !reachedLimit;
727
846
  var loadMoreLock = useRef(false);
@@ -747,13 +866,13 @@ window.__ModuleLoader__.load({
747
866
  return { total: value.total, items: merged };
748
867
  });
749
868
  }).catch(function (e) {
750
- setError(errorText(e));
869
+ reportError("search", e);
751
870
  setRetryAt(Date.now() + 60000);
752
871
  }).finally(function () {
753
872
  loadMoreLock.current = false;
754
873
  setLoadingMore(false);
755
874
  });
756
- }, [call, canLoadMore, loading, loadingMore, page, query, sort, retryAt, verifyPage]);
875
+ }, [call, canLoadMore, loading, loadingMore, page, query, reportError, sort, retryAt, verifyPage]);
757
876
 
758
877
  useEffect(function () {
759
878
  var node = sentinelRef.current;
@@ -778,7 +897,7 @@ window.__ModuleLoader__.load({
778
897
  return prev && !prev.error ? Object.assign({}, prev, { entries: value.entries }) : prev;
779
898
  });
780
899
  }).catch(function (e) {
781
- setError(errorText(e));
900
+ reportError("installed", e);
782
901
  }).finally(function () {
783
902
  setToggling(function (prev) {
784
903
  var next = Object.assign({}, prev);
@@ -786,7 +905,7 @@ window.__ModuleLoader__.load({
786
905
  return next;
787
906
  });
788
907
  });
789
- }, [call]);
908
+ }, [call, reportError]);
790
909
 
791
910
  var refreshInstalled = useCallback(function () {
792
911
  call("installed", {}).then(function (value) {
@@ -824,8 +943,8 @@ window.__ModuleLoader__.load({
824
943
  var polling = useJobPolling(call, onJobSettled, onApprovalToken, onPreflightSettled);
825
944
  var track = polling.track;
826
945
  var jobs = polling.jobs;
827
- var clearJobs = polling.clear;
828
946
  var dropJob = polling.drop;
947
+ var setJobStatus = polling.setStatus;
829
948
 
830
949
  useEffect(function () {
831
950
  doSearch();
@@ -851,7 +970,7 @@ window.__ModuleLoader__.load({
851
970
  track(value.jobId, spec, carryFromId);
852
971
  }).catch(function (e) {
853
972
  delete approvalTokensRef.current[spec];
854
- setError(errorText(e));
973
+ reportError("tasks", e);
855
974
  }).finally(function () {
856
975
  setInstalling(function (prev) {
857
976
  var next = Object.assign({}, prev);
@@ -859,7 +978,7 @@ window.__ModuleLoader__.load({
859
978
  return next;
860
979
  });
861
980
  });
862
- }, [call, track]);
981
+ }, [call, reportError, track]);
863
982
 
864
983
  var doApprove = useCallback(function (spec, names, token, carryFromId) {
865
984
  var extra = { allowBuildScripts: names };
@@ -875,7 +994,7 @@ window.__ModuleLoader__.load({
875
994
  useEffect(function () {
876
995
  preflightHandlerRef.current = function (spec, report, jobId) {
877
996
  if (!report || !report.verdict) {
878
- setError("预检没有返回结论,请重试");
997
+ reportError("tasks", "预检没有返回结论,请重试");
879
998
  return;
880
999
  }
881
1000
  if (report.verdict === "safe") {
@@ -885,7 +1004,7 @@ window.__ModuleLoader__.load({
885
1004
  setPreflight({ spec: spec, report: report, jobId: jobId });
886
1005
  }
887
1006
  };
888
- }, [doRawInstall]);
1007
+ }, [doRawInstall, reportError]);
889
1008
 
890
1009
  // 点安装 = 立刻起一个预检 job,面板马上有东西看、日志实时流。
891
1010
  // 结论由轮询经 onPreflightSettled 交回上面的 handler,这里不等待。
@@ -895,18 +1014,15 @@ window.__ModuleLoader__.load({
895
1014
  call("preflight", { spec: spec }).then(function (value) {
896
1015
  track(value.jobId, value.spec || spec);
897
1016
  }).catch(function (e) {
898
- setError(errorText(e));
1017
+ reportError("tasks", e);
899
1018
  });
900
- }, [call, track]);
1019
+ }, [call, reportError, track]);
901
1020
 
902
1021
  var doInstall = useCallback(function (repo) {
903
1022
  preflightAndInstall("github:" + repo);
904
1023
  }, [preflightAndInstall]);
905
1024
 
906
1025
  var doRestart = useCallback(function () {
907
- if (typeof window !== "undefined" && typeof window.confirm === "function") {
908
- if (window.confirm("重启 dsh?正在进行的任务会中断。") !== true) return;
909
- }
910
1026
  clearRestartPing();
911
1027
  setRestarting(true);
912
1028
  setError(null);
@@ -924,7 +1040,7 @@ window.__ModuleLoader__.load({
924
1040
  if (tries > 40) {
925
1041
  clearRestartPing();
926
1042
  setRestarting(false);
927
- setError("2 分钟内未检测到 dsh 重启完成,请手动检查 dsh 状态后刷新页面。");
1043
+ reportError("restart", "2 分钟内未检测到 dsh 重启完成,请手动检查 dsh 状态后刷新页面。");
928
1044
  return;
929
1045
  }
930
1046
  // Prefer identity change over wall-clock ordering: NTP may move the
@@ -947,9 +1063,9 @@ window.__ModuleLoader__.load({
947
1063
  if (restartControlRef.current.mounted !== true) return;
948
1064
  clearRestartPing();
949
1065
  setRestarting(false);
950
- setError(errorText(e));
1066
+ reportError("restart", e);
951
1067
  });
952
- }, [call, clearRestartPing, hostStartedAt]);
1068
+ }, [call, clearRestartPing, hostStartedAt, reportError]);
953
1069
 
954
1070
  var doUninstall = useCallback(function (name) {
955
1071
  delete approvalTokensRef.current[name];
@@ -961,7 +1077,7 @@ window.__ModuleLoader__.load({
961
1077
  call("uninstall", { package: name }).then(function (value) {
962
1078
  track(value.jobId, name);
963
1079
  }).catch(function (e) {
964
- setError(errorText(e));
1080
+ reportError("installed", e);
965
1081
  }).finally(function () {
966
1082
  setRemoving(function (prev) {
967
1083
  var next = Object.assign({}, prev);
@@ -969,7 +1085,7 @@ window.__ModuleLoader__.load({
969
1085
  return next;
970
1086
  });
971
1087
  });
972
- }, [call, track]);
1088
+ }, [call, reportError, track]);
973
1089
 
974
1090
  useEffect(function () {
975
1091
  refreshInstalled();
@@ -981,14 +1097,13 @@ window.__ModuleLoader__.load({
981
1097
  return v !== undefined && (v.kind === "bundle" || v.kind === "client")
982
1098
  && !(v.hostDeps !== undefined && v.hostDeps.length > 0);
983
1099
  });
1100
+ var activeJobCount = Object.keys(jobs).filter(function (id) {
1101
+ return jobs[id] && isJobActive(jobs[id].status);
1102
+ }).length;
984
1103
  resultsRef.current = results;
985
1104
  var verifyPending = results !== null && results.items.some(function (it) { return verified[it.fullName] === undefined; });
986
1105
 
987
1106
  return h("div", { className: "mkt_root" },
988
- h("div", { className: "mkt_head" },
989
- h("div", { className: "mkt_title" }, "插件市场"),
990
- h("div", { className: "mkt_sub" }, "搜索 GitHub dsh-plugin 话题插件 · 装/卸后需重启 dsh 生效")
991
- ),
992
1107
  h("div", { className: "mkt_row" },
993
1108
  h("input", {
994
1109
  className: "mkt_input",
@@ -1007,13 +1122,12 @@ window.__ModuleLoader__.load({
1007
1122
  h("option", { value: "forks" }, "按 fork")
1008
1123
  ),
1009
1124
  h("button", { className: "mkt_btn mkt_btnPrimary", disabled: loading, onClick: doSearch }, loading ? "搜索中…" : "搜索"),
1010
- h("button", { className: "mkt_btn", onClick: refreshInstalled }, "刷新已装"),
1011
- h("button", { className: "mkt_btn mkt_btnDanger", disabled: restarting, onClick: doRestart }, restarting ? "重启中…" : "重启 dsh"),
1125
+ h("button", { className: "mkt_btn mkt_btnDanger", disabled: restarting, onClick: function () { setRestartConfirm(true); } }, restarting ? "重启中…" : "重启 dsh"),
1012
1126
  h("label", { className: "mkt_check" },
1013
1127
  h("input", { type: "checkbox", checked: verifiedOnly, onChange: function (e) { setVerifiedOnly(e.target.checked); } }),
1014
1128
  "只看已验证插件")
1015
1129
  ),
1016
- error ? h("div", { className: "mkt_error" }, error) : null,
1130
+ h(ErrorNotice, { message: error && (error.scope === "search" || error.scope === "restart") ? error.message : null }),
1017
1131
  h(InstalledPanel, {
1018
1132
  installed: installed,
1019
1133
  removing: removing,
@@ -1023,19 +1137,50 @@ window.__ModuleLoader__.load({
1023
1137
  onToggle: doToggle,
1024
1138
  onUninstall: doUninstall,
1025
1139
  onInstallSpec: preflightAndInstall,
1140
+ error: error && error.scope === "installed" ? error.message : null,
1026
1141
  }),
1027
1142
  h(JobsPanel, {
1028
1143
  jobs: jobs,
1144
+ error: error && error.scope === "tasks" ? error.message : null,
1145
+ preflight: preflight,
1146
+ preflightBusy: preflight ? installing[preflight.spec] === true : false,
1029
1147
  onClear: function () {
1030
1148
  Object.keys(jobs).forEach(function (id) {
1031
1149
  var job = jobs[id];
1150
+ var hasDecision = job && ((preflight && preflight.jobId === id)
1151
+ || (job.needsApproval && job.needsApproval.length > 0));
1152
+ if (!job || isJobActive(job.status) || hasDecision) return;
1032
1153
  var token = job && job.approvalToken ? job.approvalToken : (job && job.spec ? approvalTokensRef.current[job.spec] : undefined);
1033
1154
  call("jobDismiss", { jobId: id, token: token }).catch(function () {});
1155
+ if (job.spec) delete approvalTokensRef.current[job.spec];
1156
+ dropJob(id);
1157
+ });
1158
+ },
1159
+ onCancel: function (id) {
1160
+ var job = jobs[id];
1161
+ if (!job || job.status !== "running") return;
1162
+ setJobStatus(id, "stopping");
1163
+ call("jobCancel", { jobId: id }).catch(function (e) {
1164
+ setJobStatus(id, "running");
1165
+ reportError("tasks", e);
1034
1166
  });
1035
- approvalTokensRef.current = {};
1036
- clearJobs();
1037
1167
  },
1038
1168
  onApprove: doApprove,
1169
+ onConfirmPreflight: function () {
1170
+ if (!preflight) return;
1171
+ var spec = preflight.spec;
1172
+ var carry = preflight.jobId;
1173
+ var report = preflight.report;
1174
+ setPreflight(null);
1175
+ // consentDigest 来自预检 job 的 extras,装的时候与当前报告比对:
1176
+ // 用户点「继续」到安装真正开跑之间候选包或 profile 变了,
1177
+ // 布尔同意不得沿用,要重新看新的警告。
1178
+ doRawInstall(spec, { acceptWarnings: true, acceptedReportDigest: report.consentDigest }, carry);
1179
+ },
1180
+ onClosePreflight: function () {
1181
+ if (preflight && preflight.spec) delete approvalTokensRef.current[preflight.spec];
1182
+ setPreflight(null);
1183
+ },
1039
1184
  onDismiss: function (id) {
1040
1185
  var job = jobs[id];
1041
1186
  var token = job && job.approvalToken ? job.approvalToken : (job && job.spec ? approvalTokensRef.current[job.spec] : undefined);
@@ -1044,29 +1189,11 @@ window.__ModuleLoader__.load({
1044
1189
  dropJob(id);
1045
1190
  },
1046
1191
  onDrop: dropJob,
1047
- onRestart: doRestart,
1192
+ onRestart: function () { setRestartConfirm(true); },
1048
1193
  restarting: restarting,
1049
1194
  hostStartedAt: hostStartedAt,
1050
1195
  approving: Object.keys(installing).filter(function (s) { return installing[s]; })[0],
1051
1196
  }),
1052
- preflight ? h(PreflightCard, {
1053
- spec: preflight.spec,
1054
- report: preflight.report,
1055
- busy: installing[preflight.spec] === true,
1056
- onConfirm: function () {
1057
- var spec = preflight.spec;
1058
- var carry = preflight.jobId;
1059
- setPreflight(null);
1060
- // consentDigest 来自预检 job 的 extras,装的时候与当前报告比对:
1061
- // 用户点「继续」到安装真正开跑之间候选包或 profile 变了,
1062
- // 布尔同意不得沿用,要重新看新的警告。
1063
- doRawInstall(spec, { acceptWarnings: true, acceptedReportDigest: preflight.report.consentDigest }, carry);
1064
- },
1065
- onClose: function () {
1066
- if (preflight && preflight.spec) delete approvalTokensRef.current[preflight.spec];
1067
- setPreflight(null);
1068
- },
1069
- }) : null,
1070
1197
  h("div", { className: "mkt_list" },
1071
1198
  results == null
1072
1199
  ? h("div", { className: "mkt_meta mkt_listHead" }, loading ? "正在加载最热插件…" : "—")
@@ -1103,7 +1230,16 @@ window.__ModuleLoader__.load({
1103
1230
  ? h("div", { className: "mkt_loadMore", ref: sentinelRef }, loadingMore ? "加载中…" : Date.now() < retryAt ? "GitHub 限流中,稍后再下滑加载" : "下滑加载更多")
1104
1231
  : h("div", { className: "mkt_loadMore" }, reachedLimit ? "已达 GitHub 搜索上限(前 1000 个结果)" : "已显示全部 " + results.items.length + " 个")
1105
1232
  )
1106
- )
1233
+ ),
1234
+ restartConfirm ? h(RestartDialog, {
1235
+ activeCount: activeJobCount,
1236
+ busy: restarting,
1237
+ onCancel: function () { setRestartConfirm(false); },
1238
+ onConfirm: function () {
1239
+ setRestartConfirm(false);
1240
+ doRestart();
1241
+ },
1242
+ }) : null
1107
1243
  );
1108
1244
  }
1109
1245