dsh-plugin-update 0.3.1 → 0.5.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 +21 -0
- package/README.md +57 -13
- package/dist/client.d.ts +4 -4
- package/dist/client.js +2 -1
- package/dist/config.d.ts +4 -1
- package/dist/config.js +8 -1
- package/dist/entry.d.ts +9 -2
- package/dist/entry.js +47 -12
- package/dist/host.d.ts +4 -4
- package/dist/host.js +77 -6
- package/dist/http.d.ts +2 -2
- package/dist/http.js +3 -2
- package/dist/panel-batch.d.ts +9 -0
- package/dist/panel-batch.js +125 -13
- package/dist/panel.d.ts +29 -2
- package/dist/panel.js +170 -25
- package/dist/service.d.ts +14 -0
- package/dist/service.js +50 -0
- package/package.json +1 -1
package/dist/http.d.ts
CHANGED
|
@@ -8,8 +8,8 @@ export declare const HTTP_TRANSPORT_FAILED = "http-transport-failed";
|
|
|
8
8
|
export declare const DEFAULT_HTTP_CHECK_TIMEOUT_MS = 15000;
|
|
9
9
|
export declare const DEFAULT_HTTP_INSTALL_TIMEOUT_MS: number;
|
|
10
10
|
export declare const DEFAULT_HTTP_STATUS_TIMEOUT_MS = 15000;
|
|
11
|
-
/**
|
|
12
|
-
export declare const HTTP_PHONE_ACTIONS: readonly ["updateStatus", "updateCheck", "updateInstall"];
|
|
11
|
+
/** 单插件电话动作名(旧三电话与 src/config.ts PHONE_ACTIONS 同字面,不另起字面;#38 加 updateChangelog)。 */
|
|
12
|
+
export declare const HTTP_PHONE_ACTIONS: readonly ["updateStatus", "updateCheck", "updateInstall", "updateChangelog"];
|
|
13
13
|
export type HttpPhoneAction = (typeof HTTP_PHONE_ACTIONS)[number];
|
|
14
14
|
/** 批量五电话动作名(与 src/panel-batch.ts buildBatchPhoneNames 同字面,不另起字面;同一内核)。 */
|
|
15
15
|
export declare const HTTP_BATCH_ACTIONS: readonly ["batchStatus", "batchCheck", "batchInstall", "batchResume", "batchCancel"];
|
package/dist/http.js
CHANGED
|
@@ -10,7 +10,7 @@ const HTTP_TRANSPORT_FAILED = "http-transport-failed";
|
|
|
10
10
|
const DEFAULT_HTTP_CHECK_TIMEOUT_MS = 15e3;
|
|
11
11
|
const DEFAULT_HTTP_INSTALL_TIMEOUT_MS = 16 * 6e4;
|
|
12
12
|
const DEFAULT_HTTP_STATUS_TIMEOUT_MS = 15e3;
|
|
13
|
-
const HTTP_PHONE_ACTIONS = ["updateStatus", "updateCheck", "updateInstall"];
|
|
13
|
+
const HTTP_PHONE_ACTIONS = ["updateStatus", "updateCheck", "updateInstall", "updateChangelog"];
|
|
14
14
|
const HTTP_BATCH_ACTIONS = ["batchStatus", "batchCheck", "batchInstall", "batchResume", "batchCancel"];
|
|
15
15
|
function isAbsoluteUrl(value) {
|
|
16
16
|
return /^https?:\/\//i.test(value);
|
|
@@ -42,6 +42,7 @@ function allowedRouteKeys(prefix, batchPrefix) {
|
|
|
42
42
|
out.add(batchPrefix + "." + a);
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
|
+
out.add("updateChangelog");
|
|
45
46
|
return out;
|
|
46
47
|
}
|
|
47
48
|
function assertHttpRoutes(routes, prefix, batchPrefix) {
|
|
@@ -127,7 +128,7 @@ function timeoutForPhone(phoneName, timeout) {
|
|
|
127
128
|
if (typeof t.installMs === "number" && Number.isFinite(t.installMs) && t.installMs > 0) return t.installMs;
|
|
128
129
|
return DEFAULT_HTTP_INSTALL_TIMEOUT_MS;
|
|
129
130
|
}
|
|
130
|
-
if (phoneName.endsWith(".updateCheck") || phoneName.endsWith(".batchCheck")) {
|
|
131
|
+
if (phoneName.endsWith(".updateCheck") || phoneName.endsWith(".batchCheck") || phoneName.endsWith(".updateChangelog")) {
|
|
131
132
|
if (typeof t.checkMs === "number" && Number.isFinite(t.checkMs) && t.checkMs > 0) return t.checkMs;
|
|
132
133
|
return DEFAULT_HTTP_CHECK_TIMEOUT_MS;
|
|
133
134
|
}
|
package/dist/panel-batch.d.ts
CHANGED
|
@@ -44,6 +44,11 @@ export interface BatchPanelOptions {
|
|
|
44
44
|
onRestartRequested?: () => void | Promise<void>;
|
|
45
45
|
/** 复制文本的出口(与单插件面板同一口径):不传即试浏览器剪贴板,都没有也不抛错。 */
|
|
46
46
|
copyText?: (text: string) => void | Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* 详情行自动取日志(#38):默认开。展开行有新版时调该行自己的更新日志电话按需取;
|
|
49
|
+
* 老宿主没给行电话名即回中性提示;false 关闭。
|
|
50
|
+
*/
|
|
51
|
+
autoChangelog?: boolean;
|
|
47
52
|
}
|
|
48
53
|
/** 面板可点的动作(HTML 上 data-act 一一对应;测试走同一条路)。 */
|
|
49
54
|
export type BatchPanelActionKind = 'check' | 'install' | 'resume' | 'cancel' | 'row-install' | 'row-skip' | 'row-resume-skip' | 'row-cancel-queue' | 'row-copy-manual' | 'row-copy-diag' | 'toggle-details' | 'restart' | 'close';
|
|
@@ -125,6 +130,8 @@ export interface BatchPanelRenderInput {
|
|
|
125
130
|
notice?: string | null;
|
|
126
131
|
/** 面板正有一次电话在飞(本地忙守卫的一半)。 */
|
|
127
132
|
inFlight?: boolean;
|
|
133
|
+
/** 「取消这一批」已点过一次:按钮换确认文案,再点一次才真取消(防误触)。 */
|
|
134
|
+
confirmCancel?: boolean;
|
|
128
135
|
/** 是否已经拿到过第一次回包(决定总账那句是不是「正在读取…」)。 */
|
|
129
136
|
loaded?: boolean;
|
|
130
137
|
titles?: Record<string, string>;
|
|
@@ -132,6 +139,8 @@ export interface BatchPanelRenderInput {
|
|
|
132
139
|
skippedVersions?: Record<string, string | null>;
|
|
133
140
|
/** notice 归哪一家:等于某行键时那条回执画在该家详情里(否则画在面板底部)。 */
|
|
134
141
|
noticeKey?: string | null;
|
|
142
|
+
/** 键 -> 该行已取到的日志全文(只传有文本的;取不到与没展开即中性提示,不挡安装)。 */
|
|
143
|
+
changelogs?: Record<string, string | null>;
|
|
135
144
|
}
|
|
136
145
|
/** 整面板 HTML(含样式;重绘即整体替换 innerHTML,故每次都带 style 也只留一份)。 */
|
|
137
146
|
export declare function renderBatchPanelHTML(input: BatchPanelRenderInput): string;
|
package/dist/panel-batch.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// AUTO-GENERATED by node build.mjs — DO NOT EDIT. Source: src/panel-batch.ts
|
|
2
2
|
import { assertPrefix, MIN_PANEL_POLL_MS } from "./config.js";
|
|
3
|
+
import { validReleaseVersion } from "./service.js";
|
|
3
4
|
import {
|
|
4
5
|
isTerminalPhase,
|
|
5
6
|
normalizeBatchSession
|
|
@@ -11,6 +12,7 @@ import {
|
|
|
11
12
|
createBrowserSkipStore,
|
|
12
13
|
failureCopy,
|
|
13
14
|
isKnownFailureCode,
|
|
15
|
+
normalizePanelTheme,
|
|
14
16
|
renderUpdatePanelHTML
|
|
15
17
|
} from "./panel.js";
|
|
16
18
|
const BATCH_PANEL_POLL = {
|
|
@@ -91,7 +93,7 @@ function batchRowStatus(row, skippedVersion) {
|
|
|
91
93
|
}
|
|
92
94
|
function renderBatchPanelHTML(input) {
|
|
93
95
|
const rows = Array.isArray(input.rows) ? input.rows : [];
|
|
94
|
-
const theme = input.theme
|
|
96
|
+
const theme = normalizePanelTheme(input.theme);
|
|
95
97
|
const mode = input.mode === "dialog" ? "dialog" : "embedded";
|
|
96
98
|
const titles = input.titles && typeof input.titles === "object" ? input.titles : {};
|
|
97
99
|
const skippedVersions = input.skippedVersions && typeof input.skippedVersions === "object" ? input.skippedVersions : {};
|
|
@@ -111,6 +113,7 @@ function renderBatchPanelHTML(input) {
|
|
|
111
113
|
const notice = typeof input.notice === "string" && input.notice ? input.notice : null;
|
|
112
114
|
const noticeKey = typeof input.noticeKey === "string" && input.noticeKey ? input.noticeKey : null;
|
|
113
115
|
const detailNoticeKey = notice !== null && noticeKey !== null && noticeKey === expandedKey ? noticeKey : null;
|
|
116
|
+
const changelogs = input.changelogs && typeof input.changelogs === "object" ? input.changelogs : {};
|
|
114
117
|
const ctx = {
|
|
115
118
|
expanded: false,
|
|
116
119
|
busy: installing,
|
|
@@ -118,15 +121,16 @@ function renderBatchPanelHTML(input) {
|
|
|
118
121
|
theme,
|
|
119
122
|
titles,
|
|
120
123
|
skipped: null,
|
|
121
|
-
notice: null
|
|
124
|
+
notice: null,
|
|
125
|
+
changelogMarkdown: null
|
|
122
126
|
};
|
|
123
127
|
const parts = [];
|
|
124
128
|
parts.push('<div class="dsh-upd-batch-head">');
|
|
125
129
|
parts.push('<div class="dsh-upd-batch-title">\u66F4\u65B0\u6863\u6848 \xB7 <i>\u603B\u8D26</i></div>');
|
|
126
130
|
parts.push('<div class="dsh-upd-batch-macros">');
|
|
127
|
-
parts.push('<button type="button" data-act="check"' + disabled + ">\u68C0\u67E5\u66F4\u65B0</button>
|
|
128
|
-
parts.push('<button type="button" data-act="install" data-primary="1"' + disabled + ">\u5168\u90E8\u66F4\u65B0</button>
|
|
129
|
-
if (mode === "dialog") parts.push('<button type="button" data-act="close">\u5173\u95ED</button>');
|
|
131
|
+
parts.push('<button type="button" data-act="check"' + disabled + ' title="\u91CD\u65B0\u8BFB\u53D6\u6279\u91CF\u72B6\u6001\uFF08\u53EA\u8BFB\uFF09">\u68C0\u67E5\u66F4\u65B0</button>');
|
|
132
|
+
parts.push('<button type="button" data-act="install" data-primary="1"' + disabled + ' title="\u628A\u6709\u65B0\u7248\u7684\u51E0\u5BB6\u4E00\u6B21\u63D0\u4EA4\uFF1B\u540C\u4E00\u4F1A\u8BDD\u540C\u4E00\u5E42\u7B49\u7F16\u53F7">\u5168\u90E8\u66F4\u65B0</button>');
|
|
133
|
+
if (mode === "dialog") parts.push('<button type="button" data-act="close" title="\u5173\u95ED\u9762\u677F\uFF08\u6279\u91CF\u63A8\u8FDB\u5728\u5BBF\u4E3B\u4FA7\u7EE7\u7EED\u8DD1\uFF09">\u5173\u95ED</button>');
|
|
130
134
|
parts.push("</div>");
|
|
131
135
|
parts.push("</div>");
|
|
132
136
|
const summary = loaded ? batchSummaryText(counts, rows, total, installing, lastError !== null) : "\u6B63\u5728\u8BFB\u53D6\u6279\u91CF\u66F4\u65B0\u72B6\u6001\u2026";
|
|
@@ -142,7 +146,8 @@ function renderBatchPanelHTML(input) {
|
|
|
142
146
|
...ctx,
|
|
143
147
|
expanded,
|
|
144
148
|
skipped,
|
|
145
|
-
notice: expanded && row.key === detailNoticeKey ? notice : null
|
|
149
|
+
notice: expanded && row.key === detailNoticeKey ? notice : null,
|
|
150
|
+
changelogMarkdown: expanded && typeof changelogs[row.key] === "string" ? changelogs[row.key] : null
|
|
146
151
|
})
|
|
147
152
|
);
|
|
148
153
|
}
|
|
@@ -153,14 +158,17 @@ function renderBatchPanelHTML(input) {
|
|
|
153
158
|
if (total > 0 && !terminal) {
|
|
154
159
|
const more = [];
|
|
155
160
|
if (stalled) more.push('<button type="button" data-act="resume">\u63A5\u7740\u4E0A\u6B21</button>');
|
|
156
|
-
|
|
161
|
+
const confirmCancel = input.confirmCancel === true;
|
|
162
|
+
more.push(
|
|
163
|
+
'<button type="button" data-act="cancel"' + disabled + (confirmCancel ? ' data-confirm="1" title="\u518D\u70B9\u4E00\u6B21\u786E\u8BA4\u53D6\u6D88"' : ' title="\u53D6\u6D88\u8FD9\u4E00\u6279\uFF08\u8981\u70B9\u4E24\u6B21\u786E\u8BA4\uFF0C\u9632\u8BEF\u89E6\uFF09"') + ">" + (confirmCancel ? "\u786E\u8BA4\u53D6\u6D88\u8FD9\u4E00\u6279" : "\u53D6\u6D88\u8FD9\u4E00\u6279") + "</button>"
|
|
164
|
+
);
|
|
157
165
|
parts.push('<div class="dsh-upd-batch-more">' + more.join("") + "</div>");
|
|
158
166
|
}
|
|
159
167
|
if (notice !== null && detailNoticeKey === null) {
|
|
160
168
|
parts.push('<div class="dsh-upd-batch-notice" role="status">' + escapeHtml(notice) + "</div>");
|
|
161
169
|
}
|
|
162
170
|
const kernel = parts.join(String.fromCharCode(10));
|
|
163
|
-
const d5 = theme === "d5-paper";
|
|
171
|
+
const d5 = normalizePanelTheme(theme) === "d5-paper";
|
|
164
172
|
const attr = d5 ? ' data-theme="d5-paper"' : "";
|
|
165
173
|
const seal = batchSealOf(counts);
|
|
166
174
|
const root = '<div class="dsh-upd dsh-upd-batch" data-mode="' + mode + '" data-seal="' + escapeHtml(seal.text) + '" data-seal-tone="' + seal.tone + '"' + attr + ">\n" + kernel + "\n</div>";
|
|
@@ -263,6 +271,7 @@ function detailHTML(row, ctx) {
|
|
|
263
271
|
const html = renderUpdatePanelHTML({
|
|
264
272
|
snapshot: asSnapshotLike(row.snapshot),
|
|
265
273
|
manual: typeof row.manual === "string" ? row.manual : null,
|
|
274
|
+
changelogMarkdown: typeof ctx.changelogMarkdown === "string" ? ctx.changelogMarkdown : null,
|
|
266
275
|
queue: asQueueLike(row.queue),
|
|
267
276
|
// 跳过是面板本地记录(与单插件面板同一套语义):内核按「已跳过」画 tag 与提示行,
|
|
268
277
|
// 恢复按钮由上面的动作行提供,措辞与内核提示里的「恢复」对得上。
|
|
@@ -363,6 +372,8 @@ const UPDATE_BATCH_PANEL_CSS = [
|
|
|
363
372
|
".dsh-upd-batch-ledger{margin:8px 0 0;font-size:13px;opacity:.85}",
|
|
364
373
|
".dsh-upd-batch-more{margin-top:6px}",
|
|
365
374
|
".dsh-upd-batch-more button{font-size:12px}",
|
|
375
|
+
// 确认态:红框红字,与「关闭」等中性按钮一眼区分;disabled 照旧置灰。
|
|
376
|
+
'.dsh-upd-batch-more button[data-confirm="1"]{border-color:var(--dsh-upd-bad-line,#dc2626);color:var(--dsh-upd-bad-line,#dc2626)}',
|
|
366
377
|
".dsh-upd-batch-notice{margin-top:6px;font-size:12.5px;opacity:.85}",
|
|
367
378
|
".dsh-upd-bdetail-actions{margin:0 0 6px}",
|
|
368
379
|
".dsh-upd .dsh-upd-bdetail-actions button{font-size:12.5px}",
|
|
@@ -608,15 +619,20 @@ function mountUpdateBatchPanel(container, options) {
|
|
|
608
619
|
if (mode !== "embedded" && mode !== "dialog") {
|
|
609
620
|
throw new Error("[dsh-plugin-update] \u6446\u653E\u5F62\u6001\u975E\u6CD5\uFF1A\u53EA\u6536 embedded \u6216 dialog\uFF08\u6536\u5230 " + JSON.stringify(options.mode) + "\uFF09");
|
|
610
621
|
}
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
throw new Error("[dsh-plugin-update] \u4E3B\u9898\u975E\u6CD5\uFF1A\u53EA\u6536 default \u6216 d5-paper\uFF08\u6536\u5230 " + JSON.stringify(options.theme) + "\uFF09");
|
|
622
|
+
if (options.theme !== void 0 && options.theme !== "default" && options.theme !== "archive" && options.theme !== "d5-paper") {
|
|
623
|
+
throw new Error("[dsh-plugin-update] \u4E3B\u9898\u975E\u6CD5\uFF1A\u53EA\u6536 default \u6216 archive\uFF08d5-paper \u4E3A\u65E7\u522B\u540D\u4ECD\u53EF\u7528\uFF09\uFF08\u6536\u5230 " + JSON.stringify(options.theme) + "\uFF09");
|
|
614
624
|
}
|
|
625
|
+
let theme = normalizePanelTheme(options.theme ?? "default");
|
|
615
626
|
const call = options.call;
|
|
616
627
|
const titles = options.titles && typeof options.titles === "object" ? options.titles : {};
|
|
617
628
|
const onRestartRequested = options.onRestartRequested;
|
|
618
629
|
const copyText = options.copyText ?? defaultCopyText;
|
|
619
630
|
const skipStores = /* @__PURE__ */ new Map();
|
|
631
|
+
const autoChangelogEnabled = options.autoChangelog !== false;
|
|
632
|
+
const rowChangelog = /* @__PURE__ */ new Map();
|
|
633
|
+
const rowInflight = /* @__PURE__ */ new Set();
|
|
634
|
+
const rowFailed = /* @__PURE__ */ new Map();
|
|
635
|
+
let rowAutoSeq = 0;
|
|
620
636
|
let rows = [];
|
|
621
637
|
let expandedKey = null;
|
|
622
638
|
let lastError = null;
|
|
@@ -625,10 +641,64 @@ function mountUpdateBatchPanel(container, options) {
|
|
|
625
641
|
let inFlight = false;
|
|
626
642
|
let loaded = false;
|
|
627
643
|
let refreshing = false;
|
|
644
|
+
let confirmCancelArmed = false;
|
|
628
645
|
let mounted = true;
|
|
646
|
+
function changelogView() {
|
|
647
|
+
const out = {};
|
|
648
|
+
for (const row of rows) {
|
|
649
|
+
const cached = rowChangelog.get(row.key);
|
|
650
|
+
if (!cached || typeof cached.markdown !== "string") continue;
|
|
651
|
+
if (latestVersionOf(row) !== cached.version) continue;
|
|
652
|
+
out[row.key] = cached.markdown;
|
|
653
|
+
}
|
|
654
|
+
return out;
|
|
655
|
+
}
|
|
656
|
+
function changelogPhoneOf(row) {
|
|
657
|
+
const phones2 = isObject(row.phoneNames) ? row.phoneNames : null;
|
|
658
|
+
if (!phones2) return null;
|
|
659
|
+
const name = phones2["updateChangelog"];
|
|
660
|
+
return typeof name === "string" && name ? name : null;
|
|
661
|
+
}
|
|
662
|
+
function maybeAutoRowChangelog(key) {
|
|
663
|
+
if (!autoChangelogEnabled || !mounted || !key) return;
|
|
664
|
+
const row = rowOfKey(key);
|
|
665
|
+
if (!row || asBatchPhase(row.phase) !== "ready") return;
|
|
666
|
+
const version = latestVersionOf(row);
|
|
667
|
+
if (!version || !validReleaseVersion(version)) return;
|
|
668
|
+
try {
|
|
669
|
+
if (skipStoreFor(pluginIdOf(row)).has(version)) return;
|
|
670
|
+
} catch {
|
|
671
|
+
}
|
|
672
|
+
const cached = rowChangelog.get(key);
|
|
673
|
+
if (cached && cached.version === version) return;
|
|
674
|
+
if (rowInflight.has(key)) return;
|
|
675
|
+
if (rowFailed.get(key) === version) return;
|
|
676
|
+
const phone2 = changelogPhoneOf(row);
|
|
677
|
+
if (!phone2) return;
|
|
678
|
+
rowInflight.add(key);
|
|
679
|
+
const seq = rowAutoSeq;
|
|
680
|
+
void Promise.resolve().then(() => call(phone2, { version })).then(
|
|
681
|
+
(reply) => {
|
|
682
|
+
rowInflight.delete(key);
|
|
683
|
+
if (!mounted || seq !== rowAutoSeq) return;
|
|
684
|
+
if (!isObject(reply) || reply["ok"] !== true) {
|
|
685
|
+
rowFailed.set(key, version);
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
const md = reply["markdown"];
|
|
689
|
+
rowChangelog.set(key, { version, markdown: typeof md === "string" ? md : null });
|
|
690
|
+
if (typeof md === "string") render();
|
|
691
|
+
},
|
|
692
|
+
() => {
|
|
693
|
+
rowInflight.delete(key);
|
|
694
|
+
if (mounted && seq === rowAutoSeq) rowFailed.set(key, version);
|
|
695
|
+
}
|
|
696
|
+
);
|
|
697
|
+
}
|
|
698
|
+
let lastHTML = "";
|
|
629
699
|
function render() {
|
|
630
700
|
if (!mounted) return;
|
|
631
|
-
|
|
701
|
+
const nextHTML = renderBatchPanelHTML({
|
|
632
702
|
rows,
|
|
633
703
|
theme,
|
|
634
704
|
mode,
|
|
@@ -639,8 +709,14 @@ function mountUpdateBatchPanel(container, options) {
|
|
|
639
709
|
skippedVersions: skippedVersionsOf(),
|
|
640
710
|
inFlight,
|
|
641
711
|
loaded,
|
|
642
|
-
titles
|
|
712
|
+
titles,
|
|
713
|
+
confirmCancel: confirmCancelArmed,
|
|
714
|
+
changelogs: changelogView()
|
|
643
715
|
});
|
|
716
|
+
if (nextHTML !== lastHTML) {
|
|
717
|
+
lastHTML = nextHTML;
|
|
718
|
+
container.innerHTML = nextHTML;
|
|
719
|
+
}
|
|
644
720
|
}
|
|
645
721
|
function rowOfKey(key) {
|
|
646
722
|
return rows.find((row) => row.key === key) ?? null;
|
|
@@ -717,6 +793,7 @@ function mountUpdateBatchPanel(container, options) {
|
|
|
717
793
|
refreshing = false;
|
|
718
794
|
}
|
|
719
795
|
render();
|
|
796
|
+
maybeAutoRowChangelog(expandedKey);
|
|
720
797
|
}
|
|
721
798
|
async function phone(name, args, busyRowKey = null) {
|
|
722
799
|
if (!mounted || inFlight) return;
|
|
@@ -731,6 +808,7 @@ function mountUpdateBatchPanel(container, options) {
|
|
|
731
808
|
return;
|
|
732
809
|
}
|
|
733
810
|
applyReply(reply);
|
|
811
|
+
rowFailed.clear();
|
|
734
812
|
} catch {
|
|
735
813
|
if (!mounted) return;
|
|
736
814
|
lastError = "check-failed";
|
|
@@ -738,9 +816,14 @@ function mountUpdateBatchPanel(container, options) {
|
|
|
738
816
|
inFlight = false;
|
|
739
817
|
if (mounted) render();
|
|
740
818
|
}
|
|
819
|
+
maybeAutoRowChangelog(expandedKey);
|
|
741
820
|
}
|
|
742
821
|
async function act(kind, key) {
|
|
743
822
|
if (!mounted) return;
|
|
823
|
+
if (kind !== "cancel" && confirmCancelArmed) {
|
|
824
|
+
confirmCancelArmed = false;
|
|
825
|
+
render();
|
|
826
|
+
}
|
|
744
827
|
switch (kind) {
|
|
745
828
|
case "check":
|
|
746
829
|
return phone(phones.check, {});
|
|
@@ -757,7 +840,10 @@ function mountUpdateBatchPanel(container, options) {
|
|
|
757
840
|
render();
|
|
758
841
|
return;
|
|
759
842
|
}
|
|
843
|
+
// 程序调用(controller.act)即执行:调它就是明确意图,不上膛。
|
|
844
|
+
// 鼠标误触的防护在 onClick 那一层(第一次点击只上膛)。
|
|
760
845
|
case "cancel": {
|
|
846
|
+
confirmCancelArmed = false;
|
|
761
847
|
await phone(phones.cancel, {});
|
|
762
848
|
if (!mounted) return;
|
|
763
849
|
say("\u5DF2\u53D6\u6D88\u8FD9\u4E00\u6279\uFF1A\u5269\u4E0B\u7684\u4E0D\u518D\u63A8\u8FDB\uFF1B\u8981\u91CD\u6765\u70B9\u300C\u68C0\u67E5\u66F4\u65B0\u300D\u3002");
|
|
@@ -833,6 +919,7 @@ function mountUpdateBatchPanel(container, options) {
|
|
|
833
919
|
if (typeof key !== "string" || !key) return;
|
|
834
920
|
expandedKey = expandedKey === key ? null : key;
|
|
835
921
|
render();
|
|
922
|
+
maybeAutoRowChangelog(expandedKey);
|
|
836
923
|
return;
|
|
837
924
|
}
|
|
838
925
|
// 「重启宿主」:宿主没有重启自己的电话,只做入口——调用方给了 onRestartRequested 就交给它,
|
|
@@ -859,6 +946,14 @@ function mountUpdateBatchPanel(container, options) {
|
|
|
859
946
|
return;
|
|
860
947
|
}
|
|
861
948
|
}
|
|
949
|
+
function onKeyDown(ev) {
|
|
950
|
+
try {
|
|
951
|
+
const e = ev;
|
|
952
|
+
if (!mounted || mode !== "dialog" || !e || e.key !== "Escape") return;
|
|
953
|
+
unmount();
|
|
954
|
+
} catch {
|
|
955
|
+
}
|
|
956
|
+
}
|
|
862
957
|
function setTheme(next) {
|
|
863
958
|
if (next !== "default" && next !== "d5-paper") {
|
|
864
959
|
throw new Error("[dsh-plugin-update] \u4E3B\u9898\u975E\u6CD5\uFF1A\u53EA\u6536 default \u6216 d5-paper\uFF08\u6536\u5230 " + JSON.stringify(next) + "\uFF09");
|
|
@@ -879,6 +974,13 @@ function mountUpdateBatchPanel(container, options) {
|
|
|
879
974
|
const btn = t?.target && typeof t.target.closest === "function" ? t.target.closest("[data-act]") : null;
|
|
880
975
|
const kind = btn?.getAttribute ? btn.getAttribute("data-act") : null;
|
|
881
976
|
if (!kind) return;
|
|
977
|
+
if (kind === "cancel" && !confirmCancelArmed) {
|
|
978
|
+
confirmCancelArmed = true;
|
|
979
|
+
say("\u518D\u70B9\u4E00\u6B21\u300C\u786E\u8BA4\u53D6\u6D88\u8FD9\u4E00\u6279\u300D\u624D\u771F\u7684\u53D6\u6D88\uFF1B\u70B9\u522B\u7684\u6309\u94AE\u53EF\u64A4\u9500\u8FD9\u6B21\u786E\u8BA4\u3002");
|
|
980
|
+
render();
|
|
981
|
+
return;
|
|
982
|
+
}
|
|
983
|
+
if (kind === "cancel") confirmCancelArmed = false;
|
|
882
984
|
const key = btn?.getAttribute ? btn.getAttribute("data-key") : null;
|
|
883
985
|
void act(kind, key === null ? void 0 : key);
|
|
884
986
|
} catch {
|
|
@@ -887,6 +989,8 @@ function mountUpdateBatchPanel(container, options) {
|
|
|
887
989
|
function unmount() {
|
|
888
990
|
if (!mounted) return;
|
|
889
991
|
mounted = false;
|
|
992
|
+
rowAutoSeq++;
|
|
993
|
+
rowInflight.clear();
|
|
890
994
|
try {
|
|
891
995
|
timer.clear(handle);
|
|
892
996
|
} catch {
|
|
@@ -895,12 +999,20 @@ function mountUpdateBatchPanel(container, options) {
|
|
|
895
999
|
container.removeEventListener?.("click", onClick);
|
|
896
1000
|
} catch {
|
|
897
1001
|
}
|
|
1002
|
+
try {
|
|
1003
|
+
container.removeEventListener?.("keydown", onKeyDown);
|
|
1004
|
+
} catch {
|
|
1005
|
+
}
|
|
898
1006
|
}
|
|
899
1007
|
render();
|
|
900
1008
|
try {
|
|
901
1009
|
container.addEventListener?.("click", onClick);
|
|
902
1010
|
} catch {
|
|
903
1011
|
}
|
|
1012
|
+
try {
|
|
1013
|
+
container.addEventListener?.("keydown", onKeyDown);
|
|
1014
|
+
} catch {
|
|
1015
|
+
}
|
|
904
1016
|
const timer = getTimer();
|
|
905
1017
|
const handle = timer.set(() => {
|
|
906
1018
|
void refresh();
|
package/dist/panel.d.ts
CHANGED
|
@@ -3,7 +3,16 @@ import type { BlockedReason, UpdateSnapshot } from './ports.js';
|
|
|
3
3
|
/** 摆放形态:默认内嵌,切弹窗走同一参数。 */
|
|
4
4
|
export type UpdatePanelMode = 'embedded' | 'dialog';
|
|
5
5
|
/** 面板主题:默认最小可用样式;`d5-paper` 为可选 D5 档案卷专业主题(只换肤,不换 DOM 顺序)。 */
|
|
6
|
-
|
|
6
|
+
/**
|
|
7
|
+
* 面板主题:`default` 最小可用深色;`archive` 档案卷纸面浅色(原型敲定的案卷风格,只换肤)。
|
|
8
|
+
* `d5-paper` 是 archive 的旧别名(历史取值),仍可用,渲染逐字相同。
|
|
9
|
+
*/
|
|
10
|
+
export type UpdatePanelTheme = 'default' | 'archive' | 'd5-paper';
|
|
11
|
+
/**
|
|
12
|
+
* 主题归一:archive 与旧别名 d5-paper 走同一套渲染(DOM 属性仍为 d5-paper,既有覆盖样式不断);
|
|
13
|
+
* 其余一律回 default(纯渲染函数永不抛;挂载/setTheme 的非法值另行抛错)。
|
|
14
|
+
*/
|
|
15
|
+
export declare function normalizePanelTheme(value: unknown): 'default' | 'd5-paper';
|
|
7
16
|
/** 与宿主通话的传输函数:面板只认这个签名,不认任何宿主对象的具体形状。 */
|
|
8
17
|
export type UpdatePanelCall = (phoneName: string, args: Record<string, unknown>) => Promise<Record<string, unknown>>;
|
|
9
18
|
/** 复制文本的出口:不传即尝试浏览器剪贴板,都没有则只留“已生成、请手动复制”提示。 */
|
|
@@ -43,9 +52,20 @@ export interface UpdatePanelOptions {
|
|
|
43
52
|
/**
|
|
44
53
|
* 更新日志 Markdown(#23 包内 CHANGELOG 展示):
|
|
45
54
|
* 调用方按“已装版离线读、新版按需取 tarball”备好后传入(取不到传 null/空串即中性提示);
|
|
46
|
-
*
|
|
55
|
+
* 缺日志永不挡安装、不写 blockedReason。
|
|
56
|
+
* 显式传入即赢:自动链路(见 autoChangelog)永不覆盖它。
|
|
47
57
|
*/
|
|
48
58
|
changelogMarkdown?: string | null;
|
|
59
|
+
/**
|
|
60
|
+
* 自动取日志(#38):默认开。有新版时面板按 latestVersion 调一次宿主更新日志电话,
|
|
61
|
+
* 回来经内部通道换日志节;显式传过 changelogMarkdown 或传 false 即退回手动模式。
|
|
62
|
+
*/
|
|
63
|
+
autoChangelog?: boolean;
|
|
64
|
+
/**
|
|
65
|
+
* 弹窗关闭的落地:只在 dialog 下点「关闭」/按 Esc 时调用;调用方在此撤掉弹窗 DOM。
|
|
66
|
+
* 不传则回退为只停轮询(unmount),DOM 留给调用方处理。入口件打开的 dialog 已内置(收 dialog + 还原按钮)。
|
|
67
|
+
*/
|
|
68
|
+
onCloseRequested?: () => void | Promise<void>;
|
|
49
69
|
}
|
|
50
70
|
/** 挂载点:只要有 innerHTML 的容器即可(浏览器元素或测试替身都行)。 */
|
|
51
71
|
export interface UpdatePanelContainer {
|
|
@@ -221,6 +241,11 @@ export interface PanelRenderInput extends PanelViewInput {
|
|
|
221
241
|
profileName?: string | null;
|
|
222
242
|
/** 宿主种类:进诊断文本;缺省显示“未知”。 */
|
|
223
243
|
hostKind?: string | null;
|
|
244
|
+
/**
|
|
245
|
+
* 在途动作(仅挂载态内部用:点下查新版/安装到回包前的那一帧,按钮置忙)。
|
|
246
|
+
* 缺省即静默(输出与旧版一字不差);只在挂载器置忙的那次 render 里传。
|
|
247
|
+
*/
|
|
248
|
+
busyAct?: 'check' | 'install' | null;
|
|
224
249
|
/**
|
|
225
250
|
* 动作面归谁:`default`(缺省)由内核画动作按钮;`none` 只画内容、不画按钮。
|
|
226
251
|
* 给「调用方自己提供动作面」的场景(如批量面板的详情:动作由批量面板经自己的通道提供)。
|
|
@@ -242,6 +267,8 @@ export declare function renderUpdatePanelHTML(input: PanelRenderInput): string;
|
|
|
242
267
|
export declare function mountUpdatePanel(container: UpdatePanelContainer, options: UpdatePanelOptions): UpdatePanelController;
|
|
243
268
|
export { buildPhoneNames as buildPanelPhoneNames } from './config.js';
|
|
244
269
|
export type { PhoneAction as PanelPhoneAction } from './config.js';
|
|
270
|
+
export { buildChangelogPhoneName as buildPanelChangelogPhoneName } from './config.js';
|
|
271
|
+
export type { ChangelogPhoneAction as PanelChangelogPhoneAction } from './config.js';
|
|
245
272
|
/** 面板轮询口径(默认 1 秒、下限 250 毫秒,与宿主侧同一套)。 */
|
|
246
273
|
export declare const PANEL_POLL: {
|
|
247
274
|
readonly defaultMs: 1000;
|