dsh-plugin-update 0.5.3 → 0.5.4
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 +6 -0
- package/README.md +22 -0
- package/dist/entry-batch.d.ts +92 -0
- package/dist/entry-batch.js +355 -0
- package/dist/panel-batch.d.ts +5 -0
- package/dist/panel-batch.js +13 -2
- package/package.json +5 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
格式为 Keep-a-Changelog 子集:`Added/Fixed/Changed` 必写,`Deprecated/Removed/Security` 有则透传,`Unreleased` 面板忽略,禁止 git-log 直倒。面板缺日志时中性提示,不挡安装。
|
|
4
4
|
|
|
5
|
+
## [0.5.4] - 2026-10-06
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- 批量感知入口件(#49):新子路径 `dsh-plugin-update/entry-batch`,一颗按钮看 N 家聚合(button/badge/inline;openOn 缺省 always,autoCheck 缺省 mount;只调 batchStatus/batchCheck,绝不自动装;聚合与批量总账同一份数法,忙失败占位翻回可更新)。
|
|
9
|
+
- 批量 dialog 关闭落地与单面板同口径(#50):`BatchPanelOptions` 新增可选 `onCloseRequested`,点关闭/Esc 先交调用方撤 DOM 再停轮询;入口件打开的 dialog 已内置;不传回退只停轮询;HTTP 直挂透传。
|
|
10
|
+
|
|
5
11
|
## [0.5.3] - 2026-10-06
|
|
6
12
|
|
|
7
13
|
### Added
|
package/README.md
CHANGED
|
@@ -195,6 +195,27 @@ mountUpdateEntry(el, { pluginId: 'p', prefix: 'notes', call, variant: 'badge',
|
|
|
195
195
|
onActivate: ({ hasUpdate, latestVersion }) => { /* 自己跳自己的页面 */ } })
|
|
196
196
|
```
|
|
197
197
|
|
|
198
|
+
#### 批量感知的入口件(一颗按钮看 N 家,点开即批量面板)
|
|
199
|
+
|
|
200
|
+
一个总管替 N 家管更新时,别再用单入口的 `manual` 桥接批量面板——徽标只反映总管自己一行,不是七家聚合。换这一颗:
|
|
201
|
+
|
|
202
|
+
```js
|
|
203
|
+
import { mountUpdateBatchEntry } from 'dsh-plugin-update/entry-batch'
|
|
204
|
+
|
|
205
|
+
const entry = mountUpdateBatchEntry(document.getElementById('upd-entry'), {
|
|
206
|
+
prefix: 'life', // 批量电话前缀(五个批量电话从它派生)
|
|
207
|
+
call: (name, args) => host.call(name, args),
|
|
208
|
+
theme: 'archive', // 与单入口/面板同一套皮肤
|
|
209
|
+
})
|
|
210
|
+
// 点击先调 `life.batchCheck` 查一次 N 家,再以 dialog 形态开批量面板;徽标即七家聚合。
|
|
211
|
+
// 下游删掉 `openOn: 'manual' + onActivate` 桥接即跑,总管单行不再当状态源。
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
聚合口径与批量面板总账同一份数法(含「忙失败占位翻回可更新」):`忙 > 失败 > 待重启 > 可更新 > 待查`,
|
|
215
|
+
文案为 `检查更新` / `N 家可更新` / `正在安装…` / `N 家待重启` / `N 家失败,点此查看`。
|
|
216
|
+
三自由度与单入口同取值,缺省不同:`openOn` 缺省 `'always'`(查完总是开批量面板;`has-update` 下有事才开,否则原地给总账一句)。
|
|
217
|
+
`autoCheck` 缺省 `'mount'`(只调 `.batchStatus`,只读);铁律同单入口:任何路径都不自动安装。
|
|
218
|
+
|
|
198
219
|
### 第 2.6 节:多目标批量更新(一个插件管 N 个插件的更新)
|
|
199
220
|
|
|
200
221
|
一个插件替自己**和另外几个插件**管更新时,别把 N 个面板并排——用户在那块界面上只问三件事:
|
|
@@ -239,6 +260,7 @@ const panel = mountUpdateBatchPanel(el, {
|
|
|
239
260
|
theme: 'archive', // 与单插件面板同一套皮肤(archive = 档案卷,旧值 d5-paper 仍可用)
|
|
240
261
|
})
|
|
241
262
|
```
|
|
263
|
+
// 弹窗版与单面板同口径:传 `onCloseRequested`(点关闭/Esc 时先调它撤 DOM,再停轮询;入口件打开的 dialog 已内置;不传即只停轮询)。
|
|
242
264
|
|
|
243
265
|
批量面板 HTTP 版(无 `host.call` 环境即跑:同一传输内核,五电话走同一映射,取消走 `batchCancel`):
|
|
244
266
|
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { type BatchRowView } from './panel-batch.js';
|
|
2
|
+
import { type UpdatePanelTheme } from './panel.js';
|
|
3
|
+
/** 批量入口件的形态(与单入口同一套取值)。 */
|
|
4
|
+
export type BatchEntryVariant = 'button' | 'badge' | 'inline';
|
|
5
|
+
/** 什么时候主动查一次(只读;与单入口同一口径)。 */
|
|
6
|
+
export type BatchEntryAutoCheck = 'mount' | 'never';
|
|
7
|
+
/**
|
|
8
|
+
* 用户点下去之后做什么(与单入口同一套取值,缺省不同:批量缺省 'always')。
|
|
9
|
+
* 单入口缺省 'has-update'(有新版才开单面板);批量入口缺省 'always'(查完总是开批量面板——
|
|
10
|
+
* 批量面板的总账本身就是答案,空着也值得看一眼;下游删 manual 桥接即按此缺省跑)。
|
|
11
|
+
* 'has-update' 下只有聚合档非 idle 才开面板(忙/失败/待重启/可更新都算有事),否则原地给总账一句。
|
|
12
|
+
*/
|
|
13
|
+
export type BatchEntryOpenOn = 'has-update' | 'always' | 'manual' | 'direct';
|
|
14
|
+
/** 批量入口件的状态档:只用来算文案与 data-state,不另立状态机。 */
|
|
15
|
+
export type BatchEntryStateKind = 'idle' | 'update' | 'busy' | 'restart' | 'failed';
|
|
16
|
+
/** 算文案要的全部输入:批量七行 + 最近一次批量电话的失败稳定码(没查过、没失败为 null)。 */
|
|
17
|
+
export interface UpdateBatchEntryState {
|
|
18
|
+
rows: readonly BatchRowView[] | null;
|
|
19
|
+
error: string | null;
|
|
20
|
+
}
|
|
21
|
+
/** 聚合后的读数(onActivate 与测试共用同一份,不各算一份)。 */
|
|
22
|
+
export interface BatchEntrySummary {
|
|
23
|
+
kind: BatchEntryStateKind;
|
|
24
|
+
label: string;
|
|
25
|
+
updatable: number;
|
|
26
|
+
installing: number;
|
|
27
|
+
pending: number;
|
|
28
|
+
restart: number;
|
|
29
|
+
failed: number;
|
|
30
|
+
skipped: number;
|
|
31
|
+
settled: number;
|
|
32
|
+
total: number;
|
|
33
|
+
hasUpdate: boolean;
|
|
34
|
+
}
|
|
35
|
+
export interface UpdateBatchEntryOptions {
|
|
36
|
+
/** 批量电话前缀(与宿主侧一致,例 'life')。 */
|
|
37
|
+
prefix: string;
|
|
38
|
+
/** 调宿主电话:(phoneName, args) => Promise<reply>。 */
|
|
39
|
+
call: (name: string, args: Record<string, unknown>) => Promise<unknown>;
|
|
40
|
+
variant?: BatchEntryVariant;
|
|
41
|
+
/** 主题:与单入口/面板同一套(default/archive,d5-paper 为旧别名仍可用)。 */
|
|
42
|
+
theme?: UpdatePanelTheme;
|
|
43
|
+
/** 缺省 'mount':进页面静默查一次(只调 .batchStatus,只读)。'never' 则只在用户点击时查。 */
|
|
44
|
+
autoCheck?: BatchEntryAutoCheck;
|
|
45
|
+
/** 缺省 'always':查完总是开批量面板(见本类型说明)。 */
|
|
46
|
+
openOn?: BatchEntryOpenOn;
|
|
47
|
+
/** 覆盖默认按钮文案(不传就用聚合文案)。 */
|
|
48
|
+
label?: string;
|
|
49
|
+
/** 可选:键 -> 中文名覆盖(透传给批量面板)。 */
|
|
50
|
+
titles?: Record<string, string>;
|
|
51
|
+
/** 轮询间隔(毫秒,透传给批量面板;缺省 1500,下限 250)。 */
|
|
52
|
+
pollMs?: number;
|
|
53
|
+
/**
|
|
54
|
+
* 详情行自动取日志(透传给批量面板,缺省开)。
|
|
55
|
+
* 展开行有新版时调该行自己的更新日志电话按需取;false 关闭。
|
|
56
|
+
*/
|
|
57
|
+
autoChangelog?: boolean;
|
|
58
|
+
/** 「重启宿主」的落地(透传给批量面板):不传就只提示手动重启,不假装能重启。 */
|
|
59
|
+
onRestartRequested?: () => void | Promise<void>;
|
|
60
|
+
/** 复制文本的出口(透传给批量面板):不传即试浏览器剪贴板,都没有也不抛错。 */
|
|
61
|
+
copyText?: (text: string) => void | Promise<void>;
|
|
62
|
+
/** variant='badge' 或 openOn='manual' 时,点击交给接入方(自己跳自己的页面)。 */
|
|
63
|
+
onActivate?: (state: BatchEntrySummary) => void;
|
|
64
|
+
}
|
|
65
|
+
export interface UpdateBatchEntryController {
|
|
66
|
+
/** 立刻查一次并把状态刷到界面上(只读:调 .batchStatus)。 */
|
|
67
|
+
refresh(): Promise<void>;
|
|
68
|
+
/** 打开批量面板(dialog 形态)。 */
|
|
69
|
+
open(): void;
|
|
70
|
+
/** 关掉批量面板。 */
|
|
71
|
+
close(): void;
|
|
72
|
+
/** 当前按钮上的聚合文案(接入方做自定义排版时读它)。 */
|
|
73
|
+
label(): string;
|
|
74
|
+
/** 当前聚合读数(含七档计数,接入方排版与 onActivate 共用)。 */
|
|
75
|
+
summary(): BatchEntrySummary;
|
|
76
|
+
setTheme(theme: UpdatePanelTheme): void;
|
|
77
|
+
unmount(): void;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* 聚合读数:计数唯一出处 batchLedgerCounts(与面板总账同一份,忙失败占位翻回可更新)。
|
|
81
|
+
* hasUpdate = 忙/失败/待重启/可更新任一非零(openOn has-update 按它开面板;全 settled/待查/跳过即 idle)。
|
|
82
|
+
*/
|
|
83
|
+
export declare function batchEntrySummary(state: UpdateBatchEntryState | null | undefined): BatchEntrySummary;
|
|
84
|
+
/** 状态档(唯一出处:batchEntrySummary,不各写一份)。 */
|
|
85
|
+
export declare function batchEntryStateKind(state: UpdateBatchEntryState | null | undefined): BatchEntryStateKind;
|
|
86
|
+
/** 批量入口件文案(唯一出处:测试与接入方都读它,不各写一份)。 */
|
|
87
|
+
export declare function batchEntryLabelFor(state: UpdateBatchEntryState | null | undefined): string;
|
|
88
|
+
export declare function mountUpdateBatchEntry(container: {
|
|
89
|
+
innerHTML: string;
|
|
90
|
+
addEventListener?: (type: string, listener: (ev: unknown) => void) => void;
|
|
91
|
+
removeEventListener?: (type: string, listener: (ev: unknown) => void) => void;
|
|
92
|
+
}, options: UpdateBatchEntryOptions): UpdateBatchEntryController;
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
// AUTO-GENERATED by node build.mjs — DO NOT EDIT. Source: src/entry-batch.ts
|
|
2
|
+
import { MIN_PANEL_POLL_MS } from "./config.js";
|
|
3
|
+
import { UPDATE_ENTRY_CSS } from "./entry.js";
|
|
4
|
+
import {
|
|
5
|
+
batchLedgerCounts,
|
|
6
|
+
batchLedgerText,
|
|
7
|
+
buildBatchPhoneNames,
|
|
8
|
+
mountUpdateBatchPanel
|
|
9
|
+
} from "./panel-batch.js";
|
|
10
|
+
import {
|
|
11
|
+
failureCodeOf,
|
|
12
|
+
normalizePanelTheme
|
|
13
|
+
} from "./panel.js";
|
|
14
|
+
const LABEL_IDLE = "\u68C0\u67E5\u66F4\u65B0";
|
|
15
|
+
const LABEL_BUSY = "\u6B63\u5728\u5B89\u88C5\u2026";
|
|
16
|
+
function isObject(value) {
|
|
17
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
18
|
+
}
|
|
19
|
+
function asRows(value) {
|
|
20
|
+
if (!Array.isArray(value)) return null;
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
function escapeHtml(value) {
|
|
24
|
+
return String(value).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
25
|
+
}
|
|
26
|
+
function batchEntrySummary(state) {
|
|
27
|
+
const rows = state?.rows ?? null;
|
|
28
|
+
const counts = batchLedgerCounts(Array.isArray(rows) ? rows : []);
|
|
29
|
+
const total = Array.isArray(rows) ? rows.length : 0;
|
|
30
|
+
let kind;
|
|
31
|
+
if (!Array.isArray(rows) || rows.length === 0) {
|
|
32
|
+
kind = state?.error ? "failed" : "idle";
|
|
33
|
+
} else if (counts.installing > 0) {
|
|
34
|
+
kind = "busy";
|
|
35
|
+
} else if (counts.failed > 0) {
|
|
36
|
+
kind = "failed";
|
|
37
|
+
} else if (counts.restart > 0) {
|
|
38
|
+
kind = "restart";
|
|
39
|
+
} else if (counts.updatable > 0) {
|
|
40
|
+
kind = "update";
|
|
41
|
+
} else {
|
|
42
|
+
kind = "idle";
|
|
43
|
+
}
|
|
44
|
+
if (kind !== "busy" && state?.error) {
|
|
45
|
+
if (Array.isArray(rows) && rows.length > 0 && counts.failed === 0 && counts.installing === 0) {
|
|
46
|
+
kind = "failed";
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const hasUpdate = counts.installing > 0 || counts.failed > 0 || counts.restart > 0 || counts.updatable > 0;
|
|
50
|
+
let label;
|
|
51
|
+
switch (kind) {
|
|
52
|
+
case "busy":
|
|
53
|
+
label = LABEL_BUSY;
|
|
54
|
+
break;
|
|
55
|
+
case "failed":
|
|
56
|
+
label = counts.failed > 0 ? counts.failed + " \u5BB6\u5931\u8D25\uFF0C\u70B9\u6B64\u67E5\u770B" : "\u66F4\u65B0\u5931\u8D25\uFF0C\u70B9\u6B64\u67E5\u770B";
|
|
57
|
+
break;
|
|
58
|
+
case "restart":
|
|
59
|
+
label = counts.restart + " \u5BB6\u5F85\u91CD\u542F";
|
|
60
|
+
break;
|
|
61
|
+
case "update":
|
|
62
|
+
label = counts.updatable + " \u5BB6\u53EF\u66F4\u65B0";
|
|
63
|
+
break;
|
|
64
|
+
default:
|
|
65
|
+
label = LABEL_IDLE;
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
kind,
|
|
70
|
+
label,
|
|
71
|
+
updatable: counts.updatable,
|
|
72
|
+
installing: counts.installing,
|
|
73
|
+
pending: counts.pending,
|
|
74
|
+
restart: counts.restart,
|
|
75
|
+
failed: counts.failed,
|
|
76
|
+
skipped: counts.skipped,
|
|
77
|
+
settled: counts.settled,
|
|
78
|
+
total,
|
|
79
|
+
hasUpdate
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function batchEntryStateKind(state) {
|
|
83
|
+
return batchEntrySummary(state).kind;
|
|
84
|
+
}
|
|
85
|
+
function batchEntryLabelFor(state) {
|
|
86
|
+
return batchEntrySummary(state).label;
|
|
87
|
+
}
|
|
88
|
+
function mountUpdateBatchEntry(container, options) {
|
|
89
|
+
if (!container || typeof container.innerHTML !== "string") {
|
|
90
|
+
throw new Error("[dsh-plugin-update] \u6302\u6279\u91CF\u5165\u53E3\u4EF6\u9700\u8981\u4E00\u4E2A\u6709 innerHTML \u7684\u5BB9\u5668");
|
|
91
|
+
}
|
|
92
|
+
if (!options || typeof options !== "object") {
|
|
93
|
+
throw new Error("[dsh-plugin-update] \u6302\u6279\u91CF\u5165\u53E3\u4EF6\u7F3A\u5C11\u914D\u7F6E\uFF1A\u6279\u91CF\u7535\u8BDD\u524D\u7F00 prefix \u5FC5\u586B");
|
|
94
|
+
}
|
|
95
|
+
if (typeof options.call !== "function") {
|
|
96
|
+
throw new Error("[dsh-plugin-update] \u6302\u6279\u91CF\u5165\u53E3\u4EF6\u9700\u8981\u4F20\u8F93\u51FD\u6570 call\uFF08\u5165\u53E3\u4EF6\u8C03\u5BBF\u4E3B\u7535\u8BDD\u7684\u552F\u4E00\u63A5\u89E6\u9762\uFF09");
|
|
97
|
+
}
|
|
98
|
+
const phones = buildBatchPhoneNames(options.prefix);
|
|
99
|
+
const variant = options.variant ?? "button";
|
|
100
|
+
if (variant !== "button" && variant !== "badge" && variant !== "inline") {
|
|
101
|
+
throw new Error("[dsh-plugin-update] \u5165\u53E3\u4EF6\u5F62\u6001\u975E\u6CD5\uFF1A\u53EA\u6536 button / badge / inline\uFF08\u6536\u5230 " + JSON.stringify(options.variant) + "\uFF09");
|
|
102
|
+
}
|
|
103
|
+
const autoCheck = options.autoCheck ?? "mount";
|
|
104
|
+
if (autoCheck !== "mount" && autoCheck !== "never") {
|
|
105
|
+
throw new Error("[dsh-plugin-update] \u81EA\u52A8\u68C0\u67E5\u65F6\u673A\u975E\u6CD5\uFF1A\u53EA\u6536 mount \u6216 never\uFF08\u6536\u5230 " + JSON.stringify(options.autoCheck) + "\uFF09");
|
|
106
|
+
}
|
|
107
|
+
const openOn = options.openOn ?? "always";
|
|
108
|
+
if (openOn !== "has-update" && openOn !== "always" && openOn !== "manual" && openOn !== "direct") {
|
|
109
|
+
throw new Error("[dsh-plugin-update] \u70B9\u51FB\u53BB\u5411\u975E\u6CD5\uFF1A\u53EA\u6536 has-update / always / manual / direct\uFF08\u6536\u5230 " + JSON.stringify(options.openOn) + "\uFF09");
|
|
110
|
+
}
|
|
111
|
+
if (options.theme !== void 0 && options.theme !== "default" && options.theme !== "archive" && options.theme !== "d5-paper") {
|
|
112
|
+
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");
|
|
113
|
+
}
|
|
114
|
+
let theme = normalizePanelTheme(options.theme ?? "default");
|
|
115
|
+
const pollMs = options.pollMs;
|
|
116
|
+
if (pollMs !== void 0 && (typeof pollMs !== "number" || !Number.isFinite(pollMs) || pollMs < MIN_PANEL_POLL_MS)) {
|
|
117
|
+
throw new Error("[dsh-plugin-update] \u9762\u677F\u8F6E\u8BE2\u95F4\u9694\u975E\u6CD5\uFF1A\u4E0D\u5F97\u5C0F\u4E8E 250 \u6BEB\u79D2\uFF08\u6536\u5230 " + JSON.stringify(options.pollMs) + "\uFF09");
|
|
118
|
+
}
|
|
119
|
+
const labelOverride = typeof options.label === "string" && options.label ? options.label : null;
|
|
120
|
+
const titles = options.titles && typeof options.titles === "object" ? options.titles : {};
|
|
121
|
+
const onActivate = typeof options.onActivate === "function" ? options.onActivate : null;
|
|
122
|
+
const call = options.call;
|
|
123
|
+
const ENTRY_ATTR = "data-dsh-upd-entry";
|
|
124
|
+
const ENTRY_SELECTOR = "[data-dsh-upd-entry]";
|
|
125
|
+
let rows = null;
|
|
126
|
+
let error = null;
|
|
127
|
+
let note = null;
|
|
128
|
+
let loaded = false;
|
|
129
|
+
let activating = false;
|
|
130
|
+
let mounted = true;
|
|
131
|
+
let panel = null;
|
|
132
|
+
let panelMode = null;
|
|
133
|
+
const panelHost = {
|
|
134
|
+
get innerHTML() {
|
|
135
|
+
return container.innerHTML;
|
|
136
|
+
},
|
|
137
|
+
set innerHTML(value) {
|
|
138
|
+
container.innerHTML = value;
|
|
139
|
+
},
|
|
140
|
+
addEventListener(type, listener) {
|
|
141
|
+
;
|
|
142
|
+
container.addEventListener?.(type, listener);
|
|
143
|
+
},
|
|
144
|
+
removeEventListener(type, listener) {
|
|
145
|
+
;
|
|
146
|
+
container.removeEventListener?.(type, listener);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
async function panelCall(name, args) {
|
|
150
|
+
const reply = await call(name, args);
|
|
151
|
+
return isObject(reply) ? reply : {};
|
|
152
|
+
}
|
|
153
|
+
function stateOf() {
|
|
154
|
+
return { rows, error };
|
|
155
|
+
}
|
|
156
|
+
function summaryOf() {
|
|
157
|
+
return batchEntrySummary(stateOf());
|
|
158
|
+
}
|
|
159
|
+
function currentLabel() {
|
|
160
|
+
return labelOverride ?? summaryOf().label;
|
|
161
|
+
}
|
|
162
|
+
function phoneArgs() {
|
|
163
|
+
return {};
|
|
164
|
+
}
|
|
165
|
+
function entryHTML() {
|
|
166
|
+
const summary = summaryOf();
|
|
167
|
+
const text = activating ? "\u6B63\u5728\u67E5\u65B0\u7248\u2026" : currentLabel();
|
|
168
|
+
const busyAttr = activating ? ' disabled aria-busy="true"' : "";
|
|
169
|
+
const themeAttr = theme === "d5-paper" ? ' data-theme="d5-paper"' : "";
|
|
170
|
+
const control = variant === "badge" ? '<button type="button" class="dsh-upd-entry-dot" ' + ENTRY_ATTR + '="activate" title="' + escapeHtml(text) + '" aria-label="' + escapeHtml(text) + '"' + busyAttr + "></button>" : '<button type="button" class="dsh-upd-entry-btn" ' + ENTRY_ATTR + '="activate"' + busyAttr + ">" + escapeHtml(text) + "</button>";
|
|
171
|
+
const noteHTML = note ? '<span class="dsh-upd-entry-note" data-dsh-upd-note="1">' + escapeHtml(note) + "</span>" : "";
|
|
172
|
+
return "<style>" + UPDATE_ENTRY_CSS + '</style>\n<span class="dsh-upd-entry" data-variant="' + variant + '" data-state="' + summary.kind + '"' + themeAttr + ">" + control + noteHTML + "</span>";
|
|
173
|
+
}
|
|
174
|
+
function render() {
|
|
175
|
+
if (!mounted) return;
|
|
176
|
+
if (panelMode !== null) return;
|
|
177
|
+
container.innerHTML = entryHTML();
|
|
178
|
+
}
|
|
179
|
+
function applyReply(reply) {
|
|
180
|
+
if (!isObject(reply) || reply["ok"] !== true) {
|
|
181
|
+
error = failureCodeOf(isObject(reply) ? reply : null, "check-failed");
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
const next = asRows(reply["rows"]);
|
|
185
|
+
if (next) rows = next;
|
|
186
|
+
error = null;
|
|
187
|
+
loaded = true;
|
|
188
|
+
}
|
|
189
|
+
async function refresh() {
|
|
190
|
+
if (!mounted) return;
|
|
191
|
+
try {
|
|
192
|
+
const reply = await call(phones.status, phoneArgs());
|
|
193
|
+
if (!mounted) return;
|
|
194
|
+
applyReply(reply);
|
|
195
|
+
} catch {
|
|
196
|
+
if (!mounted) return;
|
|
197
|
+
error = "check-failed";
|
|
198
|
+
}
|
|
199
|
+
render();
|
|
200
|
+
}
|
|
201
|
+
async function checkNow() {
|
|
202
|
+
try {
|
|
203
|
+
const reply = await call(phones.check, phoneArgs());
|
|
204
|
+
if (!mounted) return;
|
|
205
|
+
applyReply(reply);
|
|
206
|
+
} catch {
|
|
207
|
+
if (!mounted) return;
|
|
208
|
+
error = "check-failed";
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
function mountPanel(mode) {
|
|
212
|
+
if (!mounted || panelMode !== null) return;
|
|
213
|
+
panel = mountUpdateBatchPanel(panelHost, {
|
|
214
|
+
prefix: options.prefix,
|
|
215
|
+
mode,
|
|
216
|
+
theme,
|
|
217
|
+
pollMs,
|
|
218
|
+
titles,
|
|
219
|
+
onRestartRequested: options.onRestartRequested,
|
|
220
|
+
copyText: options.copyText,
|
|
221
|
+
autoChangelog: options.autoChangelog,
|
|
222
|
+
// 面板点「关闭」即走入口件的完整关闭(收 dialog + 还原按钮 + 重查一次),不再是面板自己停轮询。
|
|
223
|
+
onCloseRequested: () => close(),
|
|
224
|
+
call: panelCall
|
|
225
|
+
});
|
|
226
|
+
panelMode = mode;
|
|
227
|
+
}
|
|
228
|
+
function openDialog() {
|
|
229
|
+
mountPanel("dialog");
|
|
230
|
+
}
|
|
231
|
+
function open() {
|
|
232
|
+
if (!mounted || panelMode !== null) return;
|
|
233
|
+
if (variant === "inline") return;
|
|
234
|
+
openDialog();
|
|
235
|
+
}
|
|
236
|
+
function close() {
|
|
237
|
+
if (!mounted || panelMode !== "dialog" || !panel) return;
|
|
238
|
+
const opened = panel;
|
|
239
|
+
panel = null;
|
|
240
|
+
panelMode = null;
|
|
241
|
+
try {
|
|
242
|
+
opened.unmount();
|
|
243
|
+
} catch {
|
|
244
|
+
}
|
|
245
|
+
render();
|
|
246
|
+
void refresh();
|
|
247
|
+
}
|
|
248
|
+
async function activate() {
|
|
249
|
+
if (!mounted || panelMode !== null || activating) return;
|
|
250
|
+
if (openOn === "direct" && variant === "button") {
|
|
251
|
+
openDialog();
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
activating = true;
|
|
255
|
+
note = null;
|
|
256
|
+
render();
|
|
257
|
+
try {
|
|
258
|
+
await checkNow();
|
|
259
|
+
} finally {
|
|
260
|
+
activating = false;
|
|
261
|
+
}
|
|
262
|
+
if (!mounted) return;
|
|
263
|
+
if (variant === "badge" || openOn === "manual") {
|
|
264
|
+
if (onActivate) onActivate(summaryOf());
|
|
265
|
+
else openDialog();
|
|
266
|
+
render();
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
const summary = summaryOf();
|
|
270
|
+
if (openOn === "always" || summary.hasUpdate || !loaded || error) {
|
|
271
|
+
openDialog();
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
note = batchLedgerText({
|
|
275
|
+
updatable: summary.updatable,
|
|
276
|
+
installing: summary.installing,
|
|
277
|
+
pending: summary.pending,
|
|
278
|
+
restart: summary.restart,
|
|
279
|
+
failed: summary.failed,
|
|
280
|
+
skipped: summary.skipped,
|
|
281
|
+
settled: summary.settled
|
|
282
|
+
});
|
|
283
|
+
render();
|
|
284
|
+
}
|
|
285
|
+
function setTheme(next) {
|
|
286
|
+
if (next !== "default" && next !== "archive" && next !== "d5-paper") {
|
|
287
|
+
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(next) + "\uFF09");
|
|
288
|
+
}
|
|
289
|
+
theme = normalizePanelTheme(next);
|
|
290
|
+
if (panel) void panel.setTheme(theme);
|
|
291
|
+
else render();
|
|
292
|
+
}
|
|
293
|
+
function onClick(ev) {
|
|
294
|
+
if (!mounted) return;
|
|
295
|
+
try {
|
|
296
|
+
const target = ev?.target;
|
|
297
|
+
const closest = target && typeof target.closest === "function" ? target.closest.bind(target) : null;
|
|
298
|
+
if (!closest) return;
|
|
299
|
+
if (closest(ENTRY_SELECTOR)) {
|
|
300
|
+
void activate();
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
} catch {
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
function onKeyDown(ev) {
|
|
307
|
+
try {
|
|
308
|
+
const e = ev;
|
|
309
|
+
if (!mounted || panelMode !== "dialog" || !e || e.key !== "Escape") return;
|
|
310
|
+
close();
|
|
311
|
+
} catch {
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
function unmount() {
|
|
315
|
+
if (!mounted) return;
|
|
316
|
+
mounted = false;
|
|
317
|
+
const opened = panel;
|
|
318
|
+
panel = null;
|
|
319
|
+
panelMode = null;
|
|
320
|
+
try {
|
|
321
|
+
opened?.unmount();
|
|
322
|
+
} catch {
|
|
323
|
+
}
|
|
324
|
+
try {
|
|
325
|
+
;
|
|
326
|
+
container.removeEventListener?.("click", onClick);
|
|
327
|
+
} catch {
|
|
328
|
+
}
|
|
329
|
+
try {
|
|
330
|
+
;
|
|
331
|
+
container.removeEventListener?.("keydown", onKeyDown);
|
|
332
|
+
} catch {
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
try {
|
|
336
|
+
;
|
|
337
|
+
container.addEventListener?.("click", onClick);
|
|
338
|
+
} catch {
|
|
339
|
+
}
|
|
340
|
+
try {
|
|
341
|
+
;
|
|
342
|
+
container.addEventListener?.("keydown", onKeyDown);
|
|
343
|
+
} catch {
|
|
344
|
+
}
|
|
345
|
+
if (variant === "inline") mountPanel("embedded");
|
|
346
|
+
else render();
|
|
347
|
+
if (autoCheck === "mount") void refresh();
|
|
348
|
+
return { refresh, open, close, label: currentLabel, summary: summaryOf, setTheme, unmount };
|
|
349
|
+
}
|
|
350
|
+
export {
|
|
351
|
+
batchEntryLabelFor,
|
|
352
|
+
batchEntryStateKind,
|
|
353
|
+
batchEntrySummary,
|
|
354
|
+
mountUpdateBatchEntry
|
|
355
|
+
};
|
package/dist/panel-batch.d.ts
CHANGED
|
@@ -49,6 +49,11 @@ export interface BatchPanelOptions {
|
|
|
49
49
|
* 老宿主没给行电话名即回中性提示;false 关闭。
|
|
50
50
|
*/
|
|
51
51
|
autoChangelog?: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* 弹窗关闭的落地(与单插件面板同一口径):只在 dialog 下点「关闭」/按 Esc 时调用;调用方在此撤掉弹窗 DOM。
|
|
54
|
+
* 不传则回退为只停轮询(unmount),DOM 留给调用方处理。入口件打开的 dialog 已内置(收 dialog + 还原按钮)。
|
|
55
|
+
*/
|
|
56
|
+
onCloseRequested?: () => void | Promise<void>;
|
|
52
57
|
}
|
|
53
58
|
/** 面板可点的动作(HTML 上 data-act 一一对应;测试走同一条路)。 */
|
|
54
59
|
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';
|
package/dist/panel-batch.js
CHANGED
|
@@ -627,6 +627,7 @@ function mountUpdateBatchPanel(container, options) {
|
|
|
627
627
|
const call = options.call;
|
|
628
628
|
const titles = options.titles && typeof options.titles === "object" ? options.titles : {};
|
|
629
629
|
const onRestartRequested = options.onRestartRequested;
|
|
630
|
+
const onCloseRequested = typeof options.onCloseRequested === "function" ? options.onCloseRequested : null;
|
|
630
631
|
const copyText = options.copyText ?? defaultCopyText;
|
|
631
632
|
const skipStores = /* @__PURE__ */ new Map();
|
|
632
633
|
const autoChangelogEnabled = options.autoChangelog !== false;
|
|
@@ -968,18 +969,28 @@ function mountUpdateBatchPanel(container, options) {
|
|
|
968
969
|
return;
|
|
969
970
|
}
|
|
970
971
|
case "close": {
|
|
971
|
-
|
|
972
|
+
await requestDialogClose();
|
|
972
973
|
return;
|
|
973
974
|
}
|
|
974
975
|
default:
|
|
975
976
|
return;
|
|
976
977
|
}
|
|
977
978
|
}
|
|
979
|
+
async function requestDialogClose() {
|
|
980
|
+
if (!mounted || mode !== "dialog") return;
|
|
981
|
+
if (onCloseRequested) {
|
|
982
|
+
try {
|
|
983
|
+
await onCloseRequested();
|
|
984
|
+
} catch {
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
unmount();
|
|
988
|
+
}
|
|
978
989
|
function onKeyDown(ev) {
|
|
979
990
|
try {
|
|
980
991
|
const e = ev;
|
|
981
992
|
if (!mounted || mode !== "dialog" || !e || e.key !== "Escape") return;
|
|
982
|
-
|
|
993
|
+
void requestDialogClose();
|
|
983
994
|
} catch {
|
|
984
995
|
}
|
|
985
996
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-update",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.4",
|
|
4
4
|
"description": "可复用的更新系统装成的 npm 包:任何 DSH 插件照文档能集成检查更新等功能(地图 #579)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "王辰浩",
|
|
@@ -38,6 +38,10 @@
|
|
|
38
38
|
"types": "./dist/entry.d.ts",
|
|
39
39
|
"default": "./dist/entry.js"
|
|
40
40
|
},
|
|
41
|
+
"./entry-batch": {
|
|
42
|
+
"types": "./dist/entry-batch.d.ts",
|
|
43
|
+
"default": "./dist/entry-batch.js"
|
|
44
|
+
},
|
|
41
45
|
"./http": {
|
|
42
46
|
"types": "./dist/http.d.ts",
|
|
43
47
|
"default": "./dist/http.js"
|