dsh-plugin-update 0.5.7 → 0.6.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 +17 -4
- package/README.md +29 -10
- package/dist/bilingual.d.ts +23 -13
- package/dist/bilingual.js +324 -10
- package/dist/changelog.d.ts +19 -13
- package/dist/changelog.js +60 -22
- package/dist/diag.d.ts +3 -0
- package/dist/diag.js +32 -20
- package/dist/entry-batch.d.ts +22 -5
- package/dist/entry-batch.js +86 -19
- package/dist/entry.d.ts +37 -9
- package/dist/entry.js +105 -51
- package/dist/host-batch.js +104 -13
- package/dist/http.d.ts +2 -2
- package/dist/http.js +1 -1
- package/dist/lang.js +150 -0
- package/dist/panel-batch.d.ts +50 -9
- package/dist/panel-batch.js +484 -150
- package/dist/panel.d.ts +37 -22
- package/dist/panel.js +504 -362
- package/dist/store.d.ts +42 -0
- package/dist/store.js +139 -0
- package/package.json +1 -1
package/dist/entry.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type UpdatePanelContainer, type UpdatePanelTheme } from './panel.js';
|
|
2
2
|
import type { UpdateSnapshot } from './ports.js';
|
|
3
3
|
import { type BilingualKey } from './bilingual.js';
|
|
4
|
+
import { type AppLang, type LocaleOption } from './lang.js';
|
|
4
5
|
/** 入口件的形态。 */
|
|
5
6
|
export type EntryVariant = 'button' | 'badge' | 'inline';
|
|
6
7
|
/** 什么时候主动查一次(只读,联网一次)。 */
|
|
@@ -20,6 +21,24 @@ export interface UpdateEntryState {
|
|
|
20
21
|
snapshot: UpdateSnapshot | null;
|
|
21
22
|
error: string | null;
|
|
22
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* 入口件按钮尺寸覆盖(#69):只影响 button 本体(字号/内边距/圆角/整体缩放),不碰 dialog 面板。
|
|
26
|
+
* 缺省(不传)保持现状、零回归;badge 圆点与 inline 内嵌不读这些变量,天然不受影响。
|
|
27
|
+
* 实现口径与现有 `--dsh-upd-*` 颜色变量一致:本选项只是把同样的 CSS 变量以内联方式写到容器上,
|
|
28
|
+
* 手写 CSS 变量(`--dsh-update-entry-font-size` / `--dsh-update-entry-padding` /
|
|
29
|
+
* `--dsh-update-entry-border-radius` / `--dsh-update-entry-scale`)同样生效,两者等价。
|
|
30
|
+
* `scale` 即宿主临时方案里容器 `zoom: 1` 开关的正式形态:改一个数即缩放(默认 1)。
|
|
31
|
+
*/
|
|
32
|
+
export interface EntrySizing {
|
|
33
|
+
/** 按钮字号(CSS font-size 值,如 '12px'),不传即 13px 默认。 */
|
|
34
|
+
fontSize?: string;
|
|
35
|
+
/** 按钮内边距(CSS padding 值,如 '2px 8px'),不传即 '4px 12px' 默认。 */
|
|
36
|
+
padding?: string;
|
|
37
|
+
/** 按钮圆角(CSS border-radius 值,如 '8px'),不传即默认主题 6px / archive 主题 3px。 */
|
|
38
|
+
borderRadius?: string;
|
|
39
|
+
/** 整体缩放(对应 CSS `zoom`,如 1.2 放大、0.9 缩小),须为大于 0 的有限数,不传即 1。 */
|
|
40
|
+
scale?: number;
|
|
41
|
+
}
|
|
23
42
|
export interface UpdateEntryOptions {
|
|
24
43
|
pluginId: string;
|
|
25
44
|
/** 单插件电话前缀(与宿主侧一致)。 */
|
|
@@ -27,7 +46,7 @@ export interface UpdateEntryOptions {
|
|
|
27
46
|
/** 调宿主电话:(phoneName, args) => Promise<reply>。 */
|
|
28
47
|
call: (name: string, args: Record<string, unknown>) => Promise<unknown>;
|
|
29
48
|
variant?: EntryVariant;
|
|
30
|
-
/** 主题:`default` 最小可用深色,`archive`
|
|
49
|
+
/** 主题:`default` 最小可用深色,`archive` 档案卷纸面浅色(仍可用)。 */
|
|
31
50
|
theme?: UpdatePanelTheme;
|
|
32
51
|
/** 缺省 'mount':进页面静默查一次(只读)。'never' 则只在用户点击时查。 */
|
|
33
52
|
autoCheck?: EntryAutoCheck;
|
|
@@ -35,6 +54,10 @@ export interface UpdateEntryOptions {
|
|
|
35
54
|
openOn?: EntryOpenOn;
|
|
36
55
|
/** 覆盖默认按钮文案(不传就用状态联动文案)。 */
|
|
37
56
|
label?: string;
|
|
57
|
+
/** 按钮尺寸覆盖(#69):见 EntrySizing;不传即零回归,badge/inline 不受影响。 */
|
|
58
|
+
sizing?: EntrySizing;
|
|
59
|
+
/** 语言覆盖(#60 v2):'zh' | 'en' | { getActive(): string; subscribe?(cb): () => void },不传即跟随 html[lang] > navigator > zh。 */
|
|
60
|
+
locale?: LocaleOption;
|
|
38
61
|
profileName?: string;
|
|
39
62
|
pollMs?: number;
|
|
40
63
|
/**
|
|
@@ -68,16 +91,21 @@ export interface UpdateEntryController {
|
|
|
68
91
|
export declare function entryStateKind(state: UpdateEntryState | null | undefined): EntryStateKind;
|
|
69
92
|
/**
|
|
70
93
|
* 入口件双语 key(#54 底座调用链):与 entryStateKind 同一输入(快照 + 失败码),分支只认稳定码,永不读文案。
|
|
71
|
-
* 数据层只传 key + {version} 运行时值,表现层经
|
|
94
|
+
* 数据层只传 key + {version} 运行时值,表现层经 copyHTML 拼单语块。
|
|
72
95
|
*/
|
|
73
96
|
export declare function entryBilingualKeyFor(state: UpdateEntryState | null | undefined): BilingualKey;
|
|
74
|
-
/** 双语插值(运行时值永不翻译:同一 version
|
|
97
|
+
/** 双语插值(运行时值永不翻译:同一 version 在单语块里原样透传,冻结词元两语言逐字相同)。 */
|
|
75
98
|
export declare function entryBilingualValuesFor(state: UpdateEntryState | null | undefined): Record<string, string>;
|
|
76
|
-
/**
|
|
77
|
-
export declare function entryBilingualHTMLFor(state: UpdateEntryState | null | undefined): string;
|
|
78
|
-
/**
|
|
79
|
-
export declare function entryBilingualTextFor(state: UpdateEntryState | null | undefined): string;
|
|
80
|
-
/**
|
|
81
|
-
export declare function entryLabelFor(state: UpdateEntryState | null | undefined): string;
|
|
99
|
+
/** 一路调用链的单语块 HTML(entryHTML 按钮正文唯一来源,无覆盖时必走此函数;lang 显式入参,缺省跟随全局)。 */
|
|
100
|
+
export declare function entryBilingualHTMLFor(state: UpdateEntryState | null | undefined, lang?: AppLang | string | null): string;
|
|
101
|
+
/** 属性位纯文本单语(title / aria-label 用,放不下 HTML 时的同内容平面版;与 HTML 出口同语言)。 */
|
|
102
|
+
export declare function entryBilingualTextFor(state: UpdateEntryState | null | undefined, lang?: AppLang | string | null): string;
|
|
103
|
+
/** 入口件文案第二出口(唯一出处:测试与接入方都读它;与 HTML 出口同语言,v2 起按 lang 取字典,不再硬编码中文)。 */
|
|
104
|
+
export declare function entryLabelFor(state: UpdateEntryState | null | undefined, lang?: AppLang | string | null): string;
|
|
105
|
+
/**
|
|
106
|
+
* `sizing` → 容器 style 属性体(纯函数:同一输入永远算出同一串;空/缺省回空串,即不写 style)。
|
|
107
|
+
* 非法即抛(未知键、空串、注入字符、非正有限 scale 都不收)。
|
|
108
|
+
*/
|
|
109
|
+
export declare function entrySizingStyleFor(sizing: EntrySizing | null | undefined): string;
|
|
82
110
|
export declare const UPDATE_ENTRY_CSS: string;
|
|
83
111
|
export declare function mountUpdateEntry(container: UpdatePanelContainer, options: UpdateEntryOptions): UpdateEntryController;
|
package/dist/entry.js
CHANGED
|
@@ -5,11 +5,8 @@ import {
|
|
|
5
5
|
mountUpdatePanel,
|
|
6
6
|
normalizePanelTheme
|
|
7
7
|
} from "./panel.js";
|
|
8
|
-
import { BILINGUAL_CSS,
|
|
9
|
-
|
|
10
|
-
const LABEL_FAILED = "\u66F4\u65B0\u5931\u8D25\uFF0C\u70B9\u6B64\u67E5\u770B";
|
|
11
|
-
const LABEL_BUSY = "\u6B63\u5728\u5B89\u88C5\u2026";
|
|
12
|
-
const LABEL_RESTART = "\u5F85\u91CD\u542F";
|
|
8
|
+
import { BILINGUAL_CSS, copyHTML, copyText } from "./bilingual.js";
|
|
9
|
+
import { resolveLang, subscribeLang } from "./lang.js";
|
|
13
10
|
function hasUpdateOf(snapshot) {
|
|
14
11
|
if (!snapshot) return false;
|
|
15
12
|
const latest = snapshot.latestVersion;
|
|
@@ -53,33 +50,63 @@ function entryBilingualValuesFor(state) {
|
|
|
53
50
|
}
|
|
54
51
|
return {};
|
|
55
52
|
}
|
|
56
|
-
function entryBilingualHTMLFor(state) {
|
|
57
|
-
|
|
53
|
+
function entryBilingualHTMLFor(state, lang) {
|
|
54
|
+
const l = lang ?? resolveLang();
|
|
55
|
+
return copyHTML(entryBilingualKeyFor(state), l, entryBilingualValuesFor(state));
|
|
58
56
|
}
|
|
59
|
-
function entryBilingualTextFor(state) {
|
|
60
|
-
|
|
57
|
+
function entryBilingualTextFor(state, lang) {
|
|
58
|
+
const l = lang ?? resolveLang();
|
|
59
|
+
return copyText(entryBilingualKeyFor(state), l, entryBilingualValuesFor(state));
|
|
61
60
|
}
|
|
62
|
-
function entryLabelFor(state) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
61
|
+
function entryLabelFor(state, lang) {
|
|
62
|
+
const l = lang ?? resolveLang();
|
|
63
|
+
return copyText(entryBilingualKeyFor(state), l, entryBilingualValuesFor(state));
|
|
64
|
+
}
|
|
65
|
+
function entrySizingError(raw) {
|
|
66
|
+
return new Error(`[dsh-plugin-update] \u5165\u53E3\u4EF6\u5C3A\u5BF8\u53C2\u6570 sizing \u975E\u6CD5\uFF1A\u53EA\u6536 fontSize / padding / borderRadius\uFF08\u975E\u7A7A CSS \u503C\uFF09\u4E0E scale\uFF08\u5927\u4E8E 0 \u7684\u6709\u9650\u6570\uFF09\uFF08\u6536\u5230 ${JSON.stringify(raw ?? null)})`);
|
|
67
|
+
}
|
|
68
|
+
function isSafeEntryCssValue(value) {
|
|
69
|
+
const v = value.trim();
|
|
70
|
+
if (!v || v.length > 200) return false;
|
|
71
|
+
if (/[;"'<>`{}!&]/.test(v)) return false;
|
|
72
|
+
if (/url\s*\(/i.test(v)) return false;
|
|
73
|
+
if (/expression\s*\(/i.test(v)) return false;
|
|
74
|
+
if (/javascript\s*:/i.test(v)) return false;
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
function entrySizingStyleFor(sizing) {
|
|
78
|
+
if (sizing === void 0 || sizing === null) return "";
|
|
79
|
+
if (typeof sizing !== "object" || Array.isArray(sizing)) throw entrySizingError(sizing);
|
|
80
|
+
for (const key of Object.keys(sizing)) {
|
|
81
|
+
if (key !== "fontSize" && key !== "padding" && key !== "borderRadius" && key !== "scale") {
|
|
82
|
+
throw entrySizingError(sizing);
|
|
73
83
|
}
|
|
74
|
-
default:
|
|
75
|
-
return LABEL_IDLE;
|
|
76
84
|
}
|
|
85
|
+
const parts = [];
|
|
86
|
+
const { fontSize, padding, borderRadius, scale } = sizing;
|
|
87
|
+
if (fontSize !== void 0) {
|
|
88
|
+
if (typeof fontSize !== "string" || !isSafeEntryCssValue(fontSize)) throw entrySizingError(sizing);
|
|
89
|
+
parts.push(`--dsh-update-entry-font-size:${fontSize.trim()}`);
|
|
90
|
+
}
|
|
91
|
+
if (padding !== void 0) {
|
|
92
|
+
if (typeof padding !== "string" || !isSafeEntryCssValue(padding)) throw entrySizingError(sizing);
|
|
93
|
+
parts.push(`--dsh-update-entry-padding:${padding.trim()}`);
|
|
94
|
+
}
|
|
95
|
+
if (borderRadius !== void 0) {
|
|
96
|
+
if (typeof borderRadius !== "string" || !isSafeEntryCssValue(borderRadius)) throw entrySizingError(sizing);
|
|
97
|
+
parts.push(`--dsh-update-entry-border-radius:${borderRadius.trim()}`);
|
|
98
|
+
}
|
|
99
|
+
if (scale !== void 0) {
|
|
100
|
+
if (typeof scale !== "number" || !Number.isFinite(scale) || scale <= 0) throw entrySizingError(sizing);
|
|
101
|
+
parts.push(`--dsh-update-entry-scale:${String(scale)}`);
|
|
102
|
+
}
|
|
103
|
+
return parts.join(";");
|
|
77
104
|
}
|
|
78
105
|
const UPDATE_ENTRY_CSS = [
|
|
79
|
-
'.dsh-upd-entry{display:inline-flex;align-items:center;gap:8px;font:13px/1.6 system-ui,"Microsoft YaHei",sans-serif;color:var(--dsh-
|
|
80
|
-
".dsh-upd-entry-btn{font:inherit;border:1px solid var(--dsh-
|
|
81
|
-
"background:var(--dsh-
|
|
82
|
-
".dsh-upd-entry-btn:hover{border-color:var(--dsh-
|
|
106
|
+
'.dsh-upd-entry{display:inline-flex;align-items:center;gap:8px;font:13px/1.6 var(--dsh-update-font-sans,system-ui,"Microsoft YaHei",sans-serif);font-size:var(--dsh-update-entry-font-size,13px);color:var(--dsh-update-text,#1f2937)}',
|
|
107
|
+
".dsh-upd-entry-btn{font:inherit;border:1px solid var(--dsh-update-border,#d1d5db);border-radius:var(--dsh-update-entry-border-radius,6px);",
|
|
108
|
+
"background:var(--dsh-update-button-bg,#f9fafb);color:inherit;padding:var(--dsh-update-entry-padding,4px 12px);zoom:var(--dsh-update-entry-scale,1);cursor:pointer}",
|
|
109
|
+
".dsh-upd-entry-btn:hover{border-color:var(--dsh-update-primary,#2563eb)}",
|
|
83
110
|
".dsh-upd-entry-btn,.dsh-upd-entry-dot{transition:background-color .15s ease,border-color .15s ease,color .15s ease,transform .06s ease}",
|
|
84
111
|
".dsh-upd-entry-btn:active:not(:disabled){transform:translateY(1px)}",
|
|
85
112
|
".dsh-upd-entry-btn:disabled,.dsh-upd-entry-dot:disabled{opacity:.55;cursor:wait}",
|
|
@@ -87,25 +114,26 @@ const UPDATE_ENTRY_CSS = [
|
|
|
87
114
|
"border:2px solid currentColor;border-top-color:transparent;border-radius:50%;animation:dsh-upd-entry-spin .8s linear infinite}",
|
|
88
115
|
"@keyframes dsh-upd-entry-spin{to{transform:rotate(360deg)}}",
|
|
89
116
|
'@media (prefers-reduced-motion: reduce){.dsh-upd-entry-btn,.dsh-upd-entry-dot{transition:none}.dsh-upd-entry-btn[aria-busy="true"]::after{animation:none}}',
|
|
90
|
-
".dsh-upd-entry-btn:focus-visible,.dsh-upd-entry-dot:focus-visible{outline:2px solid var(--dsh-
|
|
91
|
-
'.dsh-upd-entry[data-state="update"] .dsh-upd-entry-btn{border-color:var(--dsh-
|
|
92
|
-
'.dsh-upd-entry[data-state="restart"] .dsh-upd-entry-btn{border-color:var(--dsh-
|
|
93
|
-
'.dsh-upd-entry[data-state="failed"] .dsh-upd-entry-btn{border-color:var(--dsh-
|
|
94
|
-
".dsh-upd-entry-dot{width:10px;height:10px;padding:0;border:0;border-radius:50%;background:var(--dsh-
|
|
95
|
-
'.dsh-upd-entry[data-state="update"] .dsh-upd-entry-dot{background:var(--dsh-
|
|
96
|
-
'.dsh-upd-entry[data-state="busy"] .dsh-upd-entry-dot,.dsh-upd-entry[data-state="restart"] .dsh-upd-entry-dot{background:var(--dsh-
|
|
97
|
-
'.dsh-upd-entry[data-state="failed"] .dsh-upd-entry-dot{background:var(--dsh-
|
|
117
|
+
".dsh-upd-entry-btn:focus-visible,.dsh-upd-entry-dot:focus-visible{outline:2px solid var(--dsh-update-focus,#2563eb);outline-offset:1px}",
|
|
118
|
+
'.dsh-upd-entry[data-state="update"] .dsh-upd-entry-btn{border-color:var(--dsh-update-ok-border,#059669);color:var(--dsh-update-ok-border,#059669)}',
|
|
119
|
+
'.dsh-upd-entry[data-state="restart"] .dsh-upd-entry-btn{border-color:var(--dsh-update-warn-border,#d97706);color:var(--dsh-update-warn-border,#d97706)}',
|
|
120
|
+
'.dsh-upd-entry[data-state="failed"] .dsh-upd-entry-btn{border-color:var(--dsh-update-bad-border,#dc2626);color:var(--dsh-update-bad-border,#dc2626)}',
|
|
121
|
+
".dsh-upd-entry-dot{width:10px;height:10px;padding:0;border:0;border-radius:50%;background:var(--dsh-update-border,#9ca3af);cursor:pointer}",
|
|
122
|
+
'.dsh-upd-entry[data-state="update"] .dsh-upd-entry-dot{background:var(--dsh-update-ok-border,#059669)}',
|
|
123
|
+
'.dsh-upd-entry[data-state="busy"] .dsh-upd-entry-dot,.dsh-upd-entry[data-state="restart"] .dsh-upd-entry-dot{background:var(--dsh-update-warn-border,#d97706)}',
|
|
124
|
+
'.dsh-upd-entry[data-state="failed"] .dsh-upd-entry-dot{background:var(--dsh-update-bad-border,#dc2626)}',
|
|
98
125
|
// 小字自带底(深色宿主 + 浅色变量时也读得出;浅底宿主上只是多一圈细线,不抢戏)。
|
|
99
|
-
".dsh-upd-entry-note{font-size:12.5px;opacity:.9;background:var(--dsh-
|
|
100
|
-
'.dsh-upd-entry[data-theme="archive"] .dsh-upd-entry-note{background:var(--
|
|
101
|
-
'.dsh-upd-entry[data-theme="archive"]{--
|
|
102
|
-
'font-
|
|
126
|
+
".dsh-upd-entry-note{font-size:12.5px;opacity:.9;background:var(--dsh-update-bg,#ffffff);border:1px solid var(--dsh-update-border,#e5e7eb);border-radius:var(--dsh-update-radius-badge,4px);padding:1px 8px}",
|
|
127
|
+
'.dsh-upd-entry[data-theme="archive"] .dsh-upd-entry-note{background:var(--dsh-update-bg);border-color:var(--dsh-update-border-strong);color:var(--dsh-update-text)}',
|
|
128
|
+
'.dsh-upd-entry[data-theme="archive"]{--dsh-update-text:#1a1a1a;--dsh-update-text-muted:#6f675a;--dsh-update-border-strong:#c4b896;--dsh-update-primary:#c8402a;--dsh-update-bg:#fffdf6;',
|
|
129
|
+
'--dsh-update-font-serif:Georgia,"Songti SC","STSong","SimSun",serif;',
|
|
130
|
+
'font-family:var(--dsh-update-font-serif,Georgia,"Songti SC","STSong","SimSun",serif);color:var(--dsh-update-text)}',
|
|
103
131
|
// 按钮脸自己不透明(深色宿主 + 浅色系统变量时也读得出;hover 红在深浅底上都可见)。
|
|
104
|
-
'.dsh-upd-entry[data-theme="archive"] .dsh-upd-entry-btn{border-color:var(--
|
|
105
|
-
'.dsh-upd-entry[data-theme="archive"] .dsh-upd-entry-btn:hover{border-color:var(--
|
|
106
|
-
'@media (prefers-color-scheme: dark){.dsh-upd-entry[data-theme="archive"]{--
|
|
132
|
+
'.dsh-upd-entry[data-theme="archive"] .dsh-upd-entry-btn{border-color:var(--dsh-update-border-strong);background:var(--dsh-update-bg);color:var(--dsh-update-text);border-radius:var(--dsh-update-entry-border-radius,3px)}',
|
|
133
|
+
'.dsh-upd-entry[data-theme="archive"] .dsh-upd-entry-btn:hover{border-color:var(--dsh-update-primary);color:var(--dsh-update-primary)}',
|
|
134
|
+
'@media (prefers-color-scheme: dark){.dsh-upd-entry[data-theme="archive"]{--dsh-update-text:#ece5d3;--dsh-update-text-muted:#a89c83;--dsh-update-border-strong:#5c4e3b;--dsh-update-primary:#e0684e;--dsh-update-bg:#1e1a15}}',
|
|
107
135
|
"@media (prefers-color-scheme: dark){.dsh-upd-entry{color:#e5e7eb}",
|
|
108
|
-
".dsh-upd-entry-btn{--dsh-
|
|
136
|
+
".dsh-upd-entry-btn{--dsh-update-button-bg:#1f2937;--dsh-update-border:#374151}}"
|
|
109
137
|
].join("\n");
|
|
110
138
|
const ENTRY_ATTR = "data-dsh-upd-entry";
|
|
111
139
|
const ENTRY_SELECTOR = "[data-dsh-upd-entry]";
|
|
@@ -150,8 +178,8 @@ function mountUpdateEntry(container, options) {
|
|
|
150
178
|
if (openOn !== "has-update" && openOn !== "always" && openOn !== "manual" && openOn !== "direct") {
|
|
151
179
|
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`);
|
|
152
180
|
}
|
|
153
|
-
if (options.theme !== void 0 && options.theme !== "default" && options.theme !== "archive"
|
|
154
|
-
throw new Error(`[dsh-plugin-update] \u4E3B\u9898\u975E\u6CD5\uFF1A\u53EA\u6536 default \u6216 archive\
|
|
181
|
+
if (options.theme !== void 0 && options.theme !== "default" && options.theme !== "archive") {
|
|
182
|
+
throw new Error(`[dsh-plugin-update] \u4E3B\u9898\u975E\u6CD5\uFF1A\u53EA\u6536 default \u6216 archive\uFF08\u6536\u5230 ${JSON.stringify(options.theme)}\uFF09`);
|
|
155
183
|
}
|
|
156
184
|
let theme = normalizePanelTheme(options.theme ?? "default");
|
|
157
185
|
const pollMs = options.pollMs;
|
|
@@ -159,6 +187,22 @@ function mountUpdateEntry(container, options) {
|
|
|
159
187
|
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`);
|
|
160
188
|
}
|
|
161
189
|
const labelOverride = typeof options.label === "string" && options.label ? options.label : null;
|
|
190
|
+
const sizingStyle = entrySizingStyleFor(options.sizing ?? void 0);
|
|
191
|
+
const sizingAttr = sizingStyle ? ` style="${sizingStyle}"` : "";
|
|
192
|
+
const localeOpt = options.locale ?? void 0;
|
|
193
|
+
if (localeOpt !== void 0 && localeOpt !== null) {
|
|
194
|
+
const isStr = typeof localeOpt === "string";
|
|
195
|
+
const isObj = typeof localeOpt === "object" && typeof localeOpt.getActive === "function";
|
|
196
|
+
if (!isStr && !isObj) {
|
|
197
|
+
throw new Error("[dsh-plugin-update] invalid locale: expected zh / en / BCP47 or { getActive(), subscribe? }");
|
|
198
|
+
}
|
|
199
|
+
if (isStr && !localeOpt.trim()) {
|
|
200
|
+
throw new Error("[dsh-plugin-update] invalid locale: empty string");
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
function currentLang() {
|
|
204
|
+
return resolveLang(localeOpt);
|
|
205
|
+
}
|
|
162
206
|
const profileName = typeof options.profileName === "string" && options.profileName ? options.profileName : null;
|
|
163
207
|
const onActivate = typeof options.onActivate === "function" ? options.onActivate : null;
|
|
164
208
|
const call = options.call;
|
|
@@ -191,7 +235,7 @@ function mountUpdateEntry(container, options) {
|
|
|
191
235
|
return { snapshot, error };
|
|
192
236
|
}
|
|
193
237
|
function currentLabel() {
|
|
194
|
-
return labelOverride ?? entryLabelFor(stateOf());
|
|
238
|
+
return labelOverride ?? entryLabelFor(stateOf(), currentLang());
|
|
195
239
|
}
|
|
196
240
|
function hasUpdate() {
|
|
197
241
|
return hasUpdateOf(snapshot);
|
|
@@ -201,15 +245,16 @@ function mountUpdateEntry(container, options) {
|
|
|
201
245
|
}
|
|
202
246
|
function entryHTML() {
|
|
203
247
|
const kind = entryStateKind(stateOf());
|
|
204
|
-
const
|
|
205
|
-
const
|
|
248
|
+
const lang = currentLang();
|
|
249
|
+
const labelHTML = labelOverride ? escapeHtml(labelOverride) : activating ? copyHTML("entry.action.checking", lang) : copyHTML(entryBilingualKeyFor(stateOf()), lang, entryBilingualValuesFor(stateOf()));
|
|
250
|
+
const labelText = labelOverride ?? (activating ? copyText("entry.action.checking", lang) : copyText(entryBilingualKeyFor(stateOf()), lang, entryBilingualValuesFor(stateOf())));
|
|
206
251
|
const busyAttr = activating ? ' disabled aria-busy="true"' : "";
|
|
207
252
|
const themeAttr = theme === "archive" ? ' data-theme="archive"' : "";
|
|
208
253
|
const control = variant === "badge" ? `<button type="button" class="dsh-upd-entry-dot" ${ENTRY_ATTR}="activate" title="${escapeHtml(labelText)}" aria-label="${escapeHtml(labelText)}"${busyAttr}></button>` : `<button type="button" class="dsh-upd-entry-btn" ${ENTRY_ATTR}="activate"${busyAttr}>${labelHTML}</button>`;
|
|
209
|
-
const noteHTML = noteVersion ? `<span class="dsh-upd-entry-note" data-dsh-upd-note="1">${
|
|
254
|
+
const noteHTML = noteVersion ? `<span class="dsh-upd-entry-note" data-dsh-upd-note="1">${copyHTML("entry.note.up-to-date", lang, { version: noteVersion })}</span>` : "";
|
|
210
255
|
return `<style>${UPDATE_ENTRY_CSS}
|
|
211
256
|
${BILINGUAL_CSS}</style>
|
|
212
|
-
<span class="dsh-upd-entry" data-variant="${variant}" data-state="${kind}"${themeAttr}>${control}${noteHTML}</span>`;
|
|
257
|
+
<span class="dsh-upd-entry" data-variant="${variant}" data-state="${kind}"${themeAttr}${sizingAttr}>${control}${noteHTML}</span>`;
|
|
213
258
|
}
|
|
214
259
|
function render() {
|
|
215
260
|
if (!mounted) return;
|
|
@@ -258,6 +303,7 @@ ${BILINGUAL_CSS}</style>
|
|
|
258
303
|
profileName,
|
|
259
304
|
autoChangelog: options.autoChangelog,
|
|
260
305
|
changelogMarkdown: options.changelogMarkdown ?? null,
|
|
306
|
+
locale: localeOpt,
|
|
261
307
|
// 面板点「关闭」即走入口件的完整关闭(收 dialog + 还原按钮 + 重查一次),不再是面板自己停轮询。
|
|
262
308
|
onCloseRequested: () => close(),
|
|
263
309
|
call: panelCall
|
|
@@ -313,8 +359,8 @@ ${BILINGUAL_CSS}</style>
|
|
|
313
359
|
render();
|
|
314
360
|
}
|
|
315
361
|
function setTheme(next) {
|
|
316
|
-
if (next !== "default" && next !== "archive"
|
|
317
|
-
throw new Error(`[dsh-plugin-update] \u4E3B\u9898\u975E\u6CD5\uFF1A\u53EA\u6536 default \u6216 archive\
|
|
362
|
+
if (next !== "default" && next !== "archive") {
|
|
363
|
+
throw new Error(`[dsh-plugin-update] \u4E3B\u9898\u975E\u6CD5\uFF1A\u53EA\u6536 default \u6216 archive\uFF08\u6536\u5230 ${JSON.stringify(next)}\uFF09`);
|
|
318
364
|
}
|
|
319
365
|
theme = normalizePanelTheme(next);
|
|
320
366
|
if (panel) void panel.setTheme(theme);
|
|
@@ -344,6 +390,10 @@ ${BILINGUAL_CSS}</style>
|
|
|
344
390
|
function unmount() {
|
|
345
391
|
if (!mounted) return;
|
|
346
392
|
mounted = false;
|
|
393
|
+
try {
|
|
394
|
+
unsubLang();
|
|
395
|
+
} catch {
|
|
396
|
+
}
|
|
347
397
|
const opened = panel;
|
|
348
398
|
panel = null;
|
|
349
399
|
panelMode = null;
|
|
@@ -360,6 +410,9 @@ ${BILINGUAL_CSS}</style>
|
|
|
360
410
|
} catch {
|
|
361
411
|
}
|
|
362
412
|
}
|
|
413
|
+
const unsubLang = subscribeLang(() => {
|
|
414
|
+
render();
|
|
415
|
+
}, localeOpt);
|
|
363
416
|
try {
|
|
364
417
|
container.addEventListener?.("click", onClick);
|
|
365
418
|
} catch {
|
|
@@ -380,6 +433,7 @@ export {
|
|
|
380
433
|
entryBilingualTextFor,
|
|
381
434
|
entryBilingualValuesFor,
|
|
382
435
|
entryLabelFor,
|
|
436
|
+
entrySizingStyleFor,
|
|
383
437
|
entryStateKind,
|
|
384
438
|
mountUpdateEntry
|
|
385
439
|
};
|
package/dist/host-batch.js
CHANGED
|
@@ -19,10 +19,13 @@ import { createHostUpdate } from "./host.js";
|
|
|
19
19
|
import { defaultHomeDir } from "./reader.js";
|
|
20
20
|
import { compareReleaseVersions } from "./service.js";
|
|
21
21
|
import {
|
|
22
|
-
|
|
22
|
+
createBatchOwnerPorts,
|
|
23
23
|
createSkipDiskPorts,
|
|
24
24
|
createUpdateQueuePorts,
|
|
25
|
-
|
|
25
|
+
emptyBatchInventory,
|
|
26
|
+
isVersionSkipped,
|
|
27
|
+
normalizeBatchInventory,
|
|
28
|
+
normalizeBatchPrefs
|
|
26
29
|
} from "./store.js";
|
|
27
30
|
const DEFAULT_DRAIN_INTERVAL_MS = 5e3;
|
|
28
31
|
const MIN_DRAIN_INTERVAL_MS = 1e3;
|
|
@@ -405,31 +408,56 @@ function createMultiHostUpdate(deps = {}, options) {
|
|
|
405
408
|
return runtime;
|
|
406
409
|
});
|
|
407
410
|
const runtimeByKey = new Map(runtimes.map((rt) => [rt.spec.key, rt]));
|
|
408
|
-
const
|
|
411
|
+
const ownerPorts = batchScope && batchScope.profileDir ? createBatchOwnerPorts(batchScope.homeDir, batchScope.profileDir, prefix) : null;
|
|
409
412
|
let memSession = null;
|
|
413
|
+
let memInventory = null;
|
|
414
|
+
let memPrefs = {};
|
|
410
415
|
let driveActive = false;
|
|
411
416
|
let liveSession = null;
|
|
412
417
|
let lastRun = null;
|
|
413
418
|
let drainTimer = null;
|
|
414
419
|
let disposed = false;
|
|
415
420
|
async function readSession() {
|
|
416
|
-
if (
|
|
421
|
+
if (ownerPorts) return await ownerPorts.readBatch();
|
|
417
422
|
return memSession ?? emptyBatchSession();
|
|
418
423
|
}
|
|
419
424
|
async function saveSession(session) {
|
|
420
|
-
if (
|
|
421
|
-
await
|
|
425
|
+
if (ownerPorts) {
|
|
426
|
+
await ownerPorts.writeBatch(session);
|
|
422
427
|
return;
|
|
423
428
|
}
|
|
424
429
|
memSession = session;
|
|
425
430
|
}
|
|
426
431
|
async function clearSession() {
|
|
427
|
-
if (
|
|
428
|
-
await
|
|
432
|
+
if (ownerPorts) {
|
|
433
|
+
await ownerPorts.clearBatch();
|
|
429
434
|
return;
|
|
430
435
|
}
|
|
431
436
|
memSession = emptyBatchSession();
|
|
432
437
|
}
|
|
438
|
+
async function readInventory() {
|
|
439
|
+
if (ownerPorts) return await ownerPorts.readInventory();
|
|
440
|
+
return memInventory ?? emptyBatchInventory();
|
|
441
|
+
}
|
|
442
|
+
async function writeInventory(inventory) {
|
|
443
|
+
if (ownerPorts) {
|
|
444
|
+
await ownerPorts.writeInventory(inventory);
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
memInventory = inventory;
|
|
448
|
+
}
|
|
449
|
+
async function readPrefs() {
|
|
450
|
+
if (ownerPorts) return await ownerPorts.readPrefs();
|
|
451
|
+
return memPrefs;
|
|
452
|
+
}
|
|
453
|
+
async function writePrefs(prefs) {
|
|
454
|
+
const normalized = normalizeBatchPrefs(prefs);
|
|
455
|
+
if (ownerPorts) {
|
|
456
|
+
await ownerPorts.writePrefs(normalized);
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
memPrefs = normalized;
|
|
460
|
+
}
|
|
433
461
|
function newSessionId() {
|
|
434
462
|
const suffix = String(randomId() ?? "").replace(/[^A-Za-z0-9._~-]/g, "").slice(0, 24);
|
|
435
463
|
return now().toString(36) + "-" + (suffix || hash8(String(now())));
|
|
@@ -499,7 +527,19 @@ function createMultiHostUpdate(deps = {}, options) {
|
|
|
499
527
|
}
|
|
500
528
|
async function table(session, refresh = true) {
|
|
501
529
|
const built = await buildRows(session, refresh);
|
|
502
|
-
|
|
530
|
+
let inventory = emptyBatchInventory();
|
|
531
|
+
let prefs = {};
|
|
532
|
+
try {
|
|
533
|
+
inventory = await readInventory();
|
|
534
|
+
} catch {
|
|
535
|
+
inventory = emptyBatchInventory();
|
|
536
|
+
}
|
|
537
|
+
try {
|
|
538
|
+
prefs = await readPrefs();
|
|
539
|
+
} catch {
|
|
540
|
+
prefs = {};
|
|
541
|
+
}
|
|
542
|
+
return { ok: true, session: built.session, rows: built.rows, progress: progressOf(built.session), inventory, prefs };
|
|
503
543
|
}
|
|
504
544
|
function denied() {
|
|
505
545
|
if (!scopeError) return null;
|
|
@@ -609,14 +649,42 @@ function createMultiHostUpdate(deps = {}, options) {
|
|
|
609
649
|
if (refuse) return refuse;
|
|
610
650
|
const session = await readSession();
|
|
611
651
|
if (!driveActive) {
|
|
652
|
+
const at = now();
|
|
653
|
+
let prev = emptyBatchInventory();
|
|
654
|
+
try {
|
|
655
|
+
prev = await readInventory();
|
|
656
|
+
} catch {
|
|
657
|
+
prev = emptyBatchInventory();
|
|
658
|
+
}
|
|
659
|
+
const entries = { ...prev.entries };
|
|
612
660
|
for (const rt of runtimes) {
|
|
613
661
|
try {
|
|
614
662
|
const outcome = await rt.check(rt.spec.key, rt.spec);
|
|
615
663
|
rt.cache = { ...rt.cache, error: outcome.kind === "failed" ? outcome.error : null };
|
|
664
|
+
const snapshot = asRecord(rt.cache.snapshot);
|
|
665
|
+
const installed = firstText(snapshot["installedVersion"], snapshot["runningVersion"]);
|
|
666
|
+
const canInstallRaw = snapshot["canInstall"];
|
|
667
|
+
const canInstall = typeof canInstallRaw === "boolean" ? canInstallRaw : null;
|
|
668
|
+
if (outcome.kind === "update") {
|
|
669
|
+
entries[rt.spec.key] = { lastCheckedAt: at, installedVersion: installed, latestVersion: outcome.version, canInstall, error: null };
|
|
670
|
+
} else if (outcome.kind === "current") {
|
|
671
|
+
entries[rt.spec.key] = { lastCheckedAt: at, installedVersion: installed, latestVersion: installed, canInstall: false, error: null };
|
|
672
|
+
} else if (outcome.kind === "skipped") {
|
|
673
|
+
entries[rt.spec.key] = { lastCheckedAt: at, installedVersion: installed, latestVersion: outcome.version, canInstall: false, error: null };
|
|
674
|
+
} else {
|
|
675
|
+
entries[rt.spec.key] = { lastCheckedAt: at, installedVersion: installed, latestVersion: firstText(snapshot["latestVersion"]), canInstall, error: outcome.error };
|
|
676
|
+
}
|
|
616
677
|
} catch (error) {
|
|
617
|
-
|
|
678
|
+
const code = errorPayloadOf(error).error;
|
|
679
|
+
rt.cache = { ...rt.cache, error: code };
|
|
680
|
+
const snapshot = asRecord(rt.cache.snapshot);
|
|
681
|
+
entries[rt.spec.key] = { lastCheckedAt: at, installedVersion: firstText(snapshot["installedVersion"], snapshot["runningVersion"]), latestVersion: firstText(snapshot["latestVersion"]), canInstall: typeof snapshot["canInstall"] === "boolean" ? snapshot["canInstall"] : null, error: code };
|
|
618
682
|
}
|
|
619
683
|
}
|
|
684
|
+
try {
|
|
685
|
+
await writeInventory(normalizeBatchInventory({ version: 1, updatedAt: at, entries }, at));
|
|
686
|
+
} catch {
|
|
687
|
+
}
|
|
620
688
|
}
|
|
621
689
|
return await table(session, false);
|
|
622
690
|
}
|
|
@@ -682,11 +750,12 @@ function createMultiHostUpdate(deps = {}, options) {
|
|
|
682
750
|
if (refuse) return refuse;
|
|
683
751
|
if (driveActive) return { ok: false, error: "update-busy", errorKind: "update-busy" };
|
|
684
752
|
const disk = await readSession();
|
|
685
|
-
|
|
753
|
+
const hadUnfinished = disk.entries.length > 0 && !isBatchFinished(disk);
|
|
754
|
+
if (!hadUnfinished) return { ...await table(disk), resumed: false };
|
|
686
755
|
const resumed = resumeBatchSession(disk, now());
|
|
687
756
|
if (resumed !== disk) await saveSession(resumed);
|
|
688
757
|
try {
|
|
689
|
-
return await table(await drive(resumed, null, Number.POSITIVE_INFINITY));
|
|
758
|
+
return { ...await table(await drive(resumed, null, Number.POSITIVE_INFINITY)), resumed: true };
|
|
690
759
|
} catch (error) {
|
|
691
760
|
return { ok: false, ...errorPayloadOf(error) };
|
|
692
761
|
}
|
|
@@ -727,12 +796,32 @@ function createMultiHostUpdate(deps = {}, options) {
|
|
|
727
796
|
void tickDrain();
|
|
728
797
|
}, drainIntervalMs);
|
|
729
798
|
}
|
|
799
|
+
async function prefsPhone() {
|
|
800
|
+
const refuse = denied();
|
|
801
|
+
if (refuse) return refuse;
|
|
802
|
+
return { ok: true, prefs: await readPrefs() };
|
|
803
|
+
}
|
|
804
|
+
async function prefsSavePhone(args) {
|
|
805
|
+
const refuse = denied();
|
|
806
|
+
if (refuse) return refuse;
|
|
807
|
+
const current = await readPrefs();
|
|
808
|
+
const next = { ...current };
|
|
809
|
+
if ("checkOnOpen" in args) {
|
|
810
|
+
const v = args["checkOnOpen"];
|
|
811
|
+
if (typeof v !== "boolean") return { ok: false, error: "check-failed", errorKind: "check-failed" };
|
|
812
|
+
next.checkOnOpen = v;
|
|
813
|
+
}
|
|
814
|
+
await writePrefs(next);
|
|
815
|
+
return { ok: true, prefs: next };
|
|
816
|
+
}
|
|
730
817
|
const phoneNames = {
|
|
731
818
|
status: prefix + ".batchStatus",
|
|
732
819
|
check: prefix + ".batchCheck",
|
|
733
820
|
install: prefix + ".batchInstall",
|
|
734
821
|
resume: prefix + ".batchResume",
|
|
735
|
-
cancel: prefix + ".batchCancel"
|
|
822
|
+
cancel: prefix + ".batchCancel",
|
|
823
|
+
prefs: prefix + ".batchPrefs",
|
|
824
|
+
prefsSave: prefix + ".batchPrefsSave"
|
|
736
825
|
};
|
|
737
826
|
function phone(fn) {
|
|
738
827
|
return async (args) => {
|
|
@@ -749,6 +838,8 @@ function createMultiHostUpdate(deps = {}, options) {
|
|
|
749
838
|
handlers[phoneNames.install] = phone((args) => installPhone(args));
|
|
750
839
|
handlers[phoneNames.resume] = phone(() => resumePhone());
|
|
751
840
|
handlers[phoneNames.cancel] = phone(() => cancelPhone());
|
|
841
|
+
handlers[phoneNames.prefs] = phone(() => prefsPhone());
|
|
842
|
+
handlers[phoneNames.prefsSave] = phone((args) => prefsSavePhone(args));
|
|
752
843
|
for (const rt of runtimes) {
|
|
753
844
|
for (const name of Object.keys(rt.host.handlers)) {
|
|
754
845
|
const handler = rt.host.handlers[name];
|
package/dist/http.d.ts
CHANGED
|
@@ -11,8 +11,8 @@ export declare const DEFAULT_HTTP_STATUS_TIMEOUT_MS = 15000;
|
|
|
11
11
|
/** 单插件电话动作名(旧三电话与 src/config.ts PHONE_ACTIONS 同字面,不另起字面;#38 加 updateChangelog)。 */
|
|
12
12
|
export declare const HTTP_PHONE_ACTIONS: readonly ["updateStatus", "updateCheck", "updateInstall", "updateChangelog"];
|
|
13
13
|
export type HttpPhoneAction = (typeof HTTP_PHONE_ACTIONS)[number];
|
|
14
|
-
/**
|
|
15
|
-
export declare const HTTP_BATCH_ACTIONS: readonly ["batchStatus", "batchCheck", "batchInstall", "batchResume", "batchCancel"];
|
|
14
|
+
/** 批量七电话动作名(与 src/panel-batch.ts buildBatchPhoneNames 同字面,不另起字面;同一内核)。 */
|
|
15
|
+
export declare const HTTP_BATCH_ACTIONS: readonly ["batchStatus", "batchCheck", "batchInstall", "batchResume", "batchCancel", "batchPrefs", "batchPrefsSave"];
|
|
16
16
|
export type HttpBatchAction = (typeof HTTP_BATCH_ACTIONS)[number];
|
|
17
17
|
/** 逐电话覆写表:键为全电话名(prefix.动作)或短动作名;未知键拒绝。 */
|
|
18
18
|
export type HttpRoutes = Record<string, string>;
|
package/dist/http.js
CHANGED
|
@@ -11,7 +11,7 @@ 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
13
|
const HTTP_PHONE_ACTIONS = ["updateStatus", "updateCheck", "updateInstall", "updateChangelog"];
|
|
14
|
-
const HTTP_BATCH_ACTIONS = ["batchStatus", "batchCheck", "batchInstall", "batchResume", "batchCancel"];
|
|
14
|
+
const HTTP_BATCH_ACTIONS = ["batchStatus", "batchCheck", "batchInstall", "batchResume", "batchCancel", "batchPrefs", "batchPrefsSave"];
|
|
15
15
|
function isAbsoluteUrl(value) {
|
|
16
16
|
return /^https?:\/\//i.test(value);
|
|
17
17
|
}
|