@huaqiu/dsh-tool-plugin-update 0.6.11 → 0.6.13
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/lib/client.js +96 -112
- package/lib/client.js.map +1 -1
- package/package.json +3 -3
- package/src/client/api.ts +11 -2
- package/src/client/copy.ts +5 -31
- package/src/client/index.ts +30 -35
- package/src/client/update-view.ts +63 -84
- package/src/client/version.ts +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@huaqiu/dsh-tool-plugin-update",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.13",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./lib/index.mjs",
|
|
6
6
|
"types": "./lib/index.d.mts",
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
"@deepseek-ai/dsh-host-webserver": "^0.2.1-rc.2"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@huaqiu/dsh-
|
|
35
|
-
"@huaqiu/dsh-
|
|
34
|
+
"@huaqiu/dsh-plugin-log": "0.6.13",
|
|
35
|
+
"@huaqiu/dsh-sidebar-entry": "0.6.13"
|
|
36
36
|
},
|
|
37
37
|
"files": [
|
|
38
38
|
"lib",
|
package/src/client/api.ts
CHANGED
|
@@ -420,13 +420,22 @@ export interface InstallResult {
|
|
|
420
420
|
}
|
|
421
421
|
|
|
422
422
|
/** POST the install transaction for the confirmed set version. */
|
|
423
|
-
export function installSet(setVersion: string): Promise<InstallResult> {
|
|
423
|
+
export function installSet(setVersion: string, pluginNames?: string[]): Promise<InstallResult> {
|
|
424
424
|
return request<InstallResult>('/install', {
|
|
425
425
|
method: 'POST',
|
|
426
|
-
body: JSON.stringify({ setVersion }),
|
|
426
|
+
body: JSON.stringify({ setVersion, ...(pluginNames ? { pluginNames } : {}) }),
|
|
427
427
|
})
|
|
428
428
|
}
|
|
429
429
|
|
|
430
|
+
/** Persist a per-plugin decision for this release set. */
|
|
431
|
+
export function ignorePlugin(setVersion: string, name: string): Promise<unknown> {
|
|
432
|
+
return request<unknown>('/plugin-ignore', { method: 'POST', body: JSON.stringify({ setVersion, name }) })
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
export function unignorePlugin(setVersion: string, name: string): Promise<unknown> {
|
|
436
|
+
return request<unknown>('/plugin-unignore', { method: 'POST', body: JSON.stringify({ setVersion, name }) })
|
|
437
|
+
}
|
|
438
|
+
|
|
430
439
|
/** POST ignore for the current offer (set-scoped, persists across restarts). */
|
|
431
440
|
export function ignoreOffer(setVersion: string): Promise<unknown> {
|
|
432
441
|
return request<unknown>('/ignore', { method: 'POST', body: JSON.stringify({ setVersion }) })
|
package/src/client/copy.ts
CHANGED
|
@@ -14,7 +14,7 @@ const zh = {
|
|
|
14
14
|
entryTooltip: '查看可用的插件更新',
|
|
15
15
|
viewTitle: '插件更新',
|
|
16
16
|
viewIntro: '检查并安装可用的插件更新',
|
|
17
|
-
viewInfo: '
|
|
17
|
+
viewInfo: '在这里查看插件集的更新;已是最新的插件无需操作。',
|
|
18
18
|
refresh: '刷新',
|
|
19
19
|
close: '关闭',
|
|
20
20
|
loading: '正在读取插件更新…',
|
|
@@ -24,6 +24,7 @@ const zh = {
|
|
|
24
24
|
// it honestly rather than per-plugin.
|
|
25
25
|
installAll: (count: number) => `安装整套更新(${count} 项)`,
|
|
26
26
|
installOne: '安装整套',
|
|
27
|
+
installOnePlugin: '安装此插件',
|
|
27
28
|
installOneTip: '将安装整套 N 项更新并自动重启 DSH,而不仅是此插件',
|
|
28
29
|
installing: '正在安装…(DSH 将自动重启)',
|
|
29
30
|
retry: '重试',
|
|
@@ -37,22 +38,9 @@ const zh = {
|
|
|
37
38
|
groupCount: (count: number) => `${count} 个插件`,
|
|
38
39
|
group: {
|
|
39
40
|
actionable: '待处理',
|
|
40
|
-
blocked: '无法安装',
|
|
41
41
|
ignored: '已忽略',
|
|
42
42
|
current: '已是最新',
|
|
43
43
|
},
|
|
44
|
-
state: {
|
|
45
|
-
current: '当前',
|
|
46
|
-
update: '可更新',
|
|
47
|
-
install: '待安装',
|
|
48
|
-
ignored: '已忽略',
|
|
49
|
-
blocked: '被阻止',
|
|
50
|
-
},
|
|
51
|
-
blockedReason: {
|
|
52
|
-
host: '不适用于当前 EDA 主机',
|
|
53
|
-
'hq-edge-version': '当前 HQ Edge 版本不兼容',
|
|
54
|
-
'runtime-unknown': 'HQ Edge 版本未知,无法校验',
|
|
55
|
-
},
|
|
56
44
|
runtime: {
|
|
57
45
|
title: 'HQ Edge 运行时',
|
|
58
46
|
current: '当前版本',
|
|
@@ -197,11 +185,10 @@ const zh = {
|
|
|
197
185
|
allChangelog: '展开全部更新日志',
|
|
198
186
|
changelogBtn: '日志',
|
|
199
187
|
changelogTitle: '更新内容:',
|
|
200
|
-
blockedBadge: '无法安装',
|
|
201
188
|
// Footer
|
|
202
189
|
footerCount: (n: number) => `准备安装 ${n} 项兼容插件`,
|
|
203
190
|
footerSet: (v: string) => `版本集: v${v}`,
|
|
204
|
-
installAllSet: (v: string) =>
|
|
191
|
+
installAllSet: (v: string) => `更新插件集 (v${v})`,
|
|
205
192
|
ignoreBtn: '忽略',
|
|
206
193
|
footnote: '更新将自动应用并重启 DSH 视图,不影响 EDA 图纸数据',
|
|
207
194
|
},
|
|
@@ -222,7 +209,7 @@ const en: UpdateCopy = {
|
|
|
222
209
|
entryTooltip: 'Review available plugin updates',
|
|
223
210
|
viewTitle: 'Plugin Updates',
|
|
224
211
|
viewIntro: 'Check for and install available plugin updates',
|
|
225
|
-
viewInfo: 'Review the plugin set updates here.
|
|
212
|
+
viewInfo: 'Review the plugin set updates here. Plugins that are up to date need no action.',
|
|
226
213
|
refresh: 'Refresh',
|
|
227
214
|
close: 'Close',
|
|
228
215
|
loading: 'Reading plugin updates…',
|
|
@@ -230,6 +217,7 @@ const en: UpdateCopy = {
|
|
|
230
217
|
checkFailed: 'Failed to check for updates — please retry',
|
|
231
218
|
installAll: (count: number) => `Install whole set (${count})`,
|
|
232
219
|
installOne: 'Install set',
|
|
220
|
+
installOnePlugin: 'Install this plugin',
|
|
233
221
|
installOneTip: 'Installs the whole set of N updates and restarts DSH — not just this plugin',
|
|
234
222
|
installing: 'Installing… (DSH will restart)',
|
|
235
223
|
retry: 'Retry',
|
|
@@ -243,22 +231,9 @@ const en: UpdateCopy = {
|
|
|
243
231
|
groupCount: (count: number) => `${count} plugin${count === 1 ? '' : 's'}`,
|
|
244
232
|
group: {
|
|
245
233
|
actionable: 'Actionable',
|
|
246
|
-
blocked: 'Blocked',
|
|
247
234
|
ignored: 'Ignored',
|
|
248
235
|
current: 'Up to date',
|
|
249
236
|
},
|
|
250
|
-
state: {
|
|
251
|
-
current: 'Current',
|
|
252
|
-
update: 'Update',
|
|
253
|
-
install: 'Install',
|
|
254
|
-
ignored: 'Ignored',
|
|
255
|
-
blocked: 'Blocked',
|
|
256
|
-
},
|
|
257
|
-
blockedReason: {
|
|
258
|
-
host: 'Not applicable to the current EDA host',
|
|
259
|
-
'hq-edge-version': 'Incompatible with the current HQ Edge version',
|
|
260
|
-
'runtime-unknown': 'HQ Edge version unknown — cannot verify',
|
|
261
|
-
},
|
|
262
237
|
runtime: {
|
|
263
238
|
title: 'HQ Edge Runtime',
|
|
264
239
|
current: 'Current version',
|
|
@@ -397,7 +372,6 @@ const en: UpdateCopy = {
|
|
|
397
372
|
allChangelog: 'Expand all changelogs',
|
|
398
373
|
changelogBtn: 'Log',
|
|
399
374
|
changelogTitle: 'What changed:',
|
|
400
|
-
blockedBadge: 'Cannot install',
|
|
401
375
|
footerCount: (n: number) => `Ready to install ${n} compatible plugin${n === 1 ? '' : 's'}`,
|
|
402
376
|
footerSet: (v: string) => `Set: v${v}`,
|
|
403
377
|
installAllSet: (v: string) => `Update all plugins (v${v})`,
|
package/src/client/index.ts
CHANGED
|
@@ -1379,6 +1379,7 @@ const VIEW_CSS_TEXT = `
|
|
|
1379
1379
|
align-items: start;
|
|
1380
1380
|
}
|
|
1381
1381
|
.hqpu-card {
|
|
1382
|
+
position: relative;
|
|
1382
1383
|
margin: 0;
|
|
1383
1384
|
padding: 10px;
|
|
1384
1385
|
border-radius: 8px;
|
|
@@ -1444,6 +1445,30 @@ const VIEW_CSS_TEXT = `
|
|
|
1444
1445
|
.hqpu-log-toggle:hover { color: var(--hq-text); }
|
|
1445
1446
|
.hqpu-log-toggle svg { width: 12px; height: 12px; transition: transform 0.15s ease; }
|
|
1446
1447
|
.hqpu-log-toggle[data-open='true'] svg { transform: rotate(180deg); }
|
|
1448
|
+
.hqpu-changelog-actions {
|
|
1449
|
+
display: flex;
|
|
1450
|
+
gap: 8px;
|
|
1451
|
+
margin-top: 10px;
|
|
1452
|
+
padding-top: 8px;
|
|
1453
|
+
border-top: 0.5px solid var(--hq-border);
|
|
1454
|
+
}
|
|
1455
|
+
.hqpu-card-action {
|
|
1456
|
+
height: 28px;
|
|
1457
|
+
padding: 0 10px;
|
|
1458
|
+
border: 0.5px solid var(--hq-border-strong);
|
|
1459
|
+
border-radius: 6px;
|
|
1460
|
+
background: var(--hq-card);
|
|
1461
|
+
color: var(--hq-text);
|
|
1462
|
+
font-size: 10px;
|
|
1463
|
+
cursor: pointer;
|
|
1464
|
+
}
|
|
1465
|
+
.hqpu-card-action:hover { border-color: var(--hq-brand-soft); color: var(--hq-brand-soft); }
|
|
1466
|
+
.hqpu-card-action--install {
|
|
1467
|
+
border-color: var(--hq-brand);
|
|
1468
|
+
background: var(--hq-brand);
|
|
1469
|
+
color: var(--hq-brand-fg);
|
|
1470
|
+
}
|
|
1471
|
+
.hqpu-card-action--install:hover { border-color: var(--hq-brand-soft); background: var(--hq-brand-soft); color: var(--hq-brand-fg); }
|
|
1447
1472
|
.hqpu-changelog-drawer {
|
|
1448
1473
|
margin-top: 8px;
|
|
1449
1474
|
padding: 8px;
|
|
@@ -1466,40 +1491,6 @@ const VIEW_CSS_TEXT = `
|
|
|
1466
1491
|
line-height: 16px;
|
|
1467
1492
|
}
|
|
1468
1493
|
|
|
1469
|
-
/* Blocked / incompatible card: dimmed, rose badge, no install button */
|
|
1470
|
-
.hqpu-card--blocked {
|
|
1471
|
-
opacity: 0.8;
|
|
1472
|
-
background: color-mix(in srgb, var(--hq-card) 60%, transparent);
|
|
1473
|
-
border-color: var(--hq-border);
|
|
1474
|
-
}
|
|
1475
|
-
.hqpu-card--blocked .hqpu-avatar {
|
|
1476
|
-
background: color-mix(in srgb, var(--hq-card) 50%, transparent);
|
|
1477
|
-
border-color: var(--hq-border);
|
|
1478
|
-
color: var(--hq-faint);
|
|
1479
|
-
}
|
|
1480
|
-
.hqpu-card--blocked .hqpu-card-title { color: var(--hq-muted); }
|
|
1481
|
-
.hqpu-blocked-badge {
|
|
1482
|
-
font-size: 9px;
|
|
1483
|
-
padding: 2px 6px;
|
|
1484
|
-
border-radius: 4px;
|
|
1485
|
-
color: var(--hq-rose);
|
|
1486
|
-
background: color-mix(in srgb, var(--hq-rose) 18%, transparent);
|
|
1487
|
-
border: 0.5px solid color-mix(in srgb, var(--hq-rose) 40%, transparent);
|
|
1488
|
-
}
|
|
1489
|
-
.hqpu-reason-line {
|
|
1490
|
-
display: flex;
|
|
1491
|
-
align-items: flex-start;
|
|
1492
|
-
gap: 4px;
|
|
1493
|
-
margin: 8px 0 0;
|
|
1494
|
-
padding: 6px;
|
|
1495
|
-
border-radius: 5px;
|
|
1496
|
-
border: 0.5px solid color-mix(in srgb, var(--hq-rose) 20%, transparent);
|
|
1497
|
-
background: color-mix(in srgb, var(--hq-rose) 8%, transparent);
|
|
1498
|
-
font-size: 10px;
|
|
1499
|
-
color: var(--hq-rose);
|
|
1500
|
-
}
|
|
1501
|
-
.hqpu-reason-line svg { width: 12px; height: 12px; flex: none; margin-top: 1px; }
|
|
1502
|
-
|
|
1503
1494
|
/* Sticky footer */
|
|
1504
1495
|
.hqpu-footer {
|
|
1505
1496
|
flex: none;
|
|
@@ -1755,7 +1746,11 @@ class UpdateStore implements UpdateStoreLike {
|
|
|
1755
1746
|
if (report.runtime?.updateAvailable) count += 1
|
|
1756
1747
|
if (count === 0) {
|
|
1757
1748
|
const latest = this.recent[0]
|
|
1758
|
-
|
|
1749
|
+
// Keep a visible attention dot for every terminal outcome, including
|
|
1750
|
+
// success. Without this, a background update could finish while the
|
|
1751
|
+
// panel was closed and the user would have no indication that a result
|
|
1752
|
+
// is waiting in the update centre.
|
|
1753
|
+
if (latest !== undefined && TERMINAL_PHASES.has(latest.phase)) {
|
|
1759
1754
|
count = 1
|
|
1760
1755
|
}
|
|
1761
1756
|
}
|
|
@@ -20,12 +20,14 @@
|
|
|
20
20
|
import {
|
|
21
21
|
checkUpdates,
|
|
22
22
|
ignoreOffer,
|
|
23
|
+
ignorePlugin,
|
|
23
24
|
installSet,
|
|
24
25
|
requestRuntimeUpdate,
|
|
25
26
|
runtimeLatestVersion,
|
|
26
27
|
setPluginUpdateSource,
|
|
27
28
|
setRuntimeUpdateSource,
|
|
28
29
|
unignoreOffer,
|
|
30
|
+
unignorePlugin,
|
|
29
31
|
TERMINAL_PHASES,
|
|
30
32
|
type PluginUpdateItem,
|
|
31
33
|
type PluginUpdateReport,
|
|
@@ -101,44 +103,6 @@ export function shortName(name: string): string {
|
|
|
101
103
|
return unscoped.replace(/^dsh-(?:host-|client-)?/, '')
|
|
102
104
|
}
|
|
103
105
|
|
|
104
|
-
/** Normalize the server's block reason to a list (it may arrive as a string). */
|
|
105
|
-
function blockedByList(item: PluginUpdateItem): string[] {
|
|
106
|
-
const by = item.blockedBy
|
|
107
|
-
if (Array.isArray(by)) return by.filter((entry): entry is string => typeof entry === 'string')
|
|
108
|
-
return typeof by === 'string' && by.length > 0 ? [by] : []
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/** The tag tone a state reads as, matching the shell's Tag palette. */
|
|
112
|
-
function stateTone(item: PluginUpdateItem): 'success' | 'info' | 'warning' | 'danger' | 'neutral' {
|
|
113
|
-
switch (item.state) {
|
|
114
|
-
case 'update':
|
|
115
|
-
return 'info'
|
|
116
|
-
case 'install':
|
|
117
|
-
return 'warning'
|
|
118
|
-
case 'blocked':
|
|
119
|
-
return 'danger'
|
|
120
|
-
case 'ignored':
|
|
121
|
-
return 'neutral'
|
|
122
|
-
case 'current':
|
|
123
|
-
return 'success'
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function stateLabel(item: PluginUpdateItem, copy: UpdateCopy): string {
|
|
128
|
-
switch (item.state) {
|
|
129
|
-
case 'current':
|
|
130
|
-
return copy.state.current
|
|
131
|
-
case 'update':
|
|
132
|
-
return copy.state.update
|
|
133
|
-
case 'install':
|
|
134
|
-
return copy.state.install
|
|
135
|
-
case 'ignored':
|
|
136
|
-
return copy.state.ignored
|
|
137
|
-
case 'blocked':
|
|
138
|
-
return copy.state.blocked
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
106
|
/** The one-line version transition under a card's title. */
|
|
143
107
|
function versionLine(item: PluginUpdateItem): string {
|
|
144
108
|
switch (item.state) {
|
|
@@ -154,14 +118,6 @@ function versionLine(item: PluginUpdateItem): string {
|
|
|
154
118
|
}
|
|
155
119
|
}
|
|
156
120
|
|
|
157
|
-
function blockedReason(item: PluginUpdateItem, copy: UpdateCopy): string | undefined {
|
|
158
|
-
const by = blockedByList(item)
|
|
159
|
-
if (by.includes('host')) return copy.blockedReason.host
|
|
160
|
-
if (by.includes('hq-edge-version')) return copy.blockedReason['hq-edge-version']
|
|
161
|
-
if (by.includes('runtime-unknown')) return copy.blockedReason['runtime-unknown']
|
|
162
|
-
return by.length > 0 ? by.join(', ') : undefined
|
|
163
|
-
}
|
|
164
|
-
|
|
165
121
|
/** Two-letter avatar initials for a card (CJK-safe). */
|
|
166
122
|
function avatarInitials(item: PluginUpdateItem): string {
|
|
167
123
|
const src =
|
|
@@ -175,7 +131,13 @@ function avatarInitials(item: PluginUpdateItem): string {
|
|
|
175
131
|
}
|
|
176
132
|
|
|
177
133
|
/** Shared per-card changelog toggle drawer (the per-plugin changes[] list). */
|
|
178
|
-
function changelogDrawer(
|
|
134
|
+
function changelogDrawer(
|
|
135
|
+
item: PluginUpdateItem,
|
|
136
|
+
copy: UpdateCopy,
|
|
137
|
+
open: boolean,
|
|
138
|
+
onInstall: () => void,
|
|
139
|
+
onIgnore: () => void,
|
|
140
|
+
): HTMLElement {
|
|
179
141
|
const drawer = el('div', 'hqpu-changelog-drawer')
|
|
180
142
|
drawer.style.display = open ? '' : 'none'
|
|
181
143
|
drawer.append(el('div', 'hqpu-changelog-drawer-title', copy.ui.changelogTitle))
|
|
@@ -183,22 +145,39 @@ function changelogDrawer(item: PluginUpdateItem, copy: UpdateCopy, open: boolean
|
|
|
183
145
|
for (const change of item.changes ?? []) {
|
|
184
146
|
if (typeof change === 'string' && change.length > 0) ul.append(el('li', '', change))
|
|
185
147
|
}
|
|
186
|
-
drawer.append(ul)
|
|
148
|
+
if (ul.childElementCount > 0) drawer.append(ul)
|
|
149
|
+
const actions = el('div', 'hqpu-changelog-actions')
|
|
150
|
+
if (item.state === 'update' || item.state === 'install') {
|
|
151
|
+
const install = el('button', 'hqpu-card-action hqpu-card-action--install', copy.installOnePlugin)
|
|
152
|
+
install.type = 'button'
|
|
153
|
+
install.title = copy.installOneTip
|
|
154
|
+
install.addEventListener('click', onInstall)
|
|
155
|
+
const ignore = el('button', 'hqpu-card-action', copy.ignore)
|
|
156
|
+
ignore.type = 'button'
|
|
157
|
+
ignore.addEventListener('click', onIgnore)
|
|
158
|
+
actions.append(install, ignore)
|
|
159
|
+
} else if (item.state === 'ignored') {
|
|
160
|
+
const show = el('button', 'hqpu-card-action hqpu-card-action--install', copy.unignore)
|
|
161
|
+
show.type = 'button'
|
|
162
|
+
show.addEventListener('click', onIgnore)
|
|
163
|
+
actions.append(show)
|
|
164
|
+
}
|
|
165
|
+
if (actions.childElementCount > 0) drawer.append(actions)
|
|
187
166
|
return drawer
|
|
188
167
|
}
|
|
189
168
|
|
|
190
169
|
/** One package card per the mockup: avatar initials, display name + package
|
|
191
170
|
* subtitle, old→new version row (new in emerald), a 日志 toggle expanding the
|
|
192
|
-
* inline changelog.
|
|
193
|
-
* 无法安装 badge and a reason line, and carry NO install button. */
|
|
171
|
+
* inline changelog. */
|
|
194
172
|
function packageCard(
|
|
195
173
|
item: PluginUpdateItem,
|
|
196
174
|
copy: UpdateCopy,
|
|
197
175
|
open: boolean,
|
|
198
176
|
onToggle: () => void,
|
|
177
|
+
onInstall: () => void,
|
|
178
|
+
onIgnore: () => void,
|
|
199
179
|
): HTMLLIElement {
|
|
200
|
-
const
|
|
201
|
-
const card = el('li', blocked ? 'hqpu-card hqpu-card--blocked' : 'hqpu-card')
|
|
180
|
+
const card = el('li', 'hqpu-card')
|
|
202
181
|
card.dataset.pluginName = item.name
|
|
203
182
|
card.dataset.state = item.state
|
|
204
183
|
|
|
@@ -229,9 +208,9 @@ function packageCard(
|
|
|
229
208
|
left.append(avatar, main)
|
|
230
209
|
|
|
231
210
|
const right = el('div', 'hqpu-card-end')
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
211
|
+
const hasDetails = (Array.isArray(item.changes) && item.changes.length > 0) ||
|
|
212
|
+
item.state === 'update' || item.state === 'install' || item.state === 'ignored'
|
|
213
|
+
if (hasDetails) {
|
|
235
214
|
const toggle = el('button', 'hqpu-log-toggle')
|
|
236
215
|
toggle.type = 'button'
|
|
237
216
|
toggle.dataset.open = open ? 'true' : 'false'
|
|
@@ -242,26 +221,10 @@ function packageCard(
|
|
|
242
221
|
head.append(left, right)
|
|
243
222
|
card.append(head)
|
|
244
223
|
|
|
245
|
-
//
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
// Blocked reason line(s): mapped copy plus the server's full reasons/notes.
|
|
251
|
-
if (blocked) {
|
|
252
|
-
const reason = blockedReason(item, copy)
|
|
253
|
-
const entries = [
|
|
254
|
-
...(reason !== undefined ? [reason] : []),
|
|
255
|
-
...[...(item.reasons ?? []), ...(item.notes ?? [])].filter(
|
|
256
|
-
(e): e is string => typeof e === 'string' && e.length > 0,
|
|
257
|
-
),
|
|
258
|
-
]
|
|
259
|
-
for (const entry of entries) {
|
|
260
|
-
const line = el('p', 'hqpu-reason-line')
|
|
261
|
-
line.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 5V8.5M8 11.25h.01M14.5 8a6.5 6.5 0 11-13 0 6.5 6.5 0 0113 0Z"/></svg>`
|
|
262
|
-
line.append(document.createTextNode(entry))
|
|
263
|
-
card.append(line)
|
|
264
|
-
}
|
|
224
|
+
// The actions live in the expanded changelog/details drawer so cards keep
|
|
225
|
+
// their compact layout until a person asks for more information.
|
|
226
|
+
if (hasDetails) {
|
|
227
|
+
card.append(changelogDrawer(item, copy, open, onInstall, onIgnore))
|
|
265
228
|
}
|
|
266
229
|
return card
|
|
267
230
|
}
|
|
@@ -275,6 +238,8 @@ function group(
|
|
|
275
238
|
allOpen: boolean,
|
|
276
239
|
onToggle: (name: string) => void,
|
|
277
240
|
onToggleAll: () => void,
|
|
241
|
+
onInstall: (name: string) => void,
|
|
242
|
+
onIgnore: (item: PluginUpdateItem) => void,
|
|
278
243
|
): HTMLElement | null {
|
|
279
244
|
if (items.length === 0) return null
|
|
280
245
|
const section = el('section', 'hqpu-group')
|
|
@@ -286,7 +251,7 @@ function group(
|
|
|
286
251
|
titleWrap.append(el('span', 'hqpu-count-pill', String(items.length)))
|
|
287
252
|
head.append(titleWrap)
|
|
288
253
|
// Expand-all only makes sense over actionable, changelog-bearing cards.
|
|
289
|
-
const actionable = items.some((i) =>
|
|
254
|
+
const actionable = items.some((i) => Array.isArray(i.changes) && i.changes.length > 0)
|
|
290
255
|
if (actionable) {
|
|
291
256
|
const all = el('button', 'hqpu-all-changelog', copy.ui.allChangelog)
|
|
292
257
|
all.type = 'button'
|
|
@@ -296,7 +261,7 @@ function group(
|
|
|
296
261
|
|
|
297
262
|
const list = el('ul', 'hqpu-cards')
|
|
298
263
|
for (const item of items) {
|
|
299
|
-
list.append(packageCard(item, copy, allOpen || changelogOpen.has(item.name), () => onToggle(item.name)))
|
|
264
|
+
list.append(packageCard(item, copy, allOpen || changelogOpen.has(item.name), () => onToggle(item.name), () => onInstall(item.name), () => onIgnore(item)))
|
|
300
265
|
}
|
|
301
266
|
|
|
302
267
|
section.append(head, list)
|
|
@@ -1119,6 +1084,8 @@ function renderList(
|
|
|
1119
1084
|
onToggleCard: (name: string) => void,
|
|
1120
1085
|
onToggleAll: () => void,
|
|
1121
1086
|
onRetryCheck: () => void,
|
|
1087
|
+
onInstall: (name: string) => void,
|
|
1088
|
+
onIgnore: (item: PluginUpdateItem) => void,
|
|
1122
1089
|
): void {
|
|
1123
1090
|
section.textContent = ''
|
|
1124
1091
|
if (report === null) {
|
|
@@ -1144,16 +1111,17 @@ function renderList(
|
|
|
1144
1111
|
const byState = (state: PluginUpdateItem['state']): PluginUpdateItem[] =>
|
|
1145
1112
|
report.items.filter((item) => item.state === state)
|
|
1146
1113
|
|
|
1147
|
-
// Most important first: what can be acted on, then what
|
|
1148
|
-
//
|
|
1114
|
+
// Most important first: what can be acted on, then what was muted, then what
|
|
1115
|
+
// is already settled. Blocked plugins are intentionally skipped — the server
|
|
1116
|
+
// still reports them, but they carry no user-actionable offer, so surfacing
|
|
1117
|
+
// them only adds noise without a path forward.
|
|
1149
1118
|
const order: Array<[string, PluginUpdateItem[]]> = [
|
|
1150
1119
|
[copy.group.actionable, [...byState('update'), ...byState('install')]],
|
|
1151
|
-
[copy.group.blocked, byState('blocked')],
|
|
1152
1120
|
[copy.group.ignored, byState('ignored')],
|
|
1153
1121
|
[copy.group.current, byState('current')],
|
|
1154
1122
|
]
|
|
1155
1123
|
for (const [heading, items] of order) {
|
|
1156
|
-
const node = group(heading, items, copy, changelogOpen, allOpen, onToggleCard, onToggleAll)
|
|
1124
|
+
const node = group(heading, items, copy, changelogOpen, allOpen, onToggleCard, onToggleAll, onInstall, onIgnore)
|
|
1157
1125
|
if (node !== null) section.append(node)
|
|
1158
1126
|
}
|
|
1159
1127
|
}
|
|
@@ -1190,7 +1158,7 @@ function renderPluginSource(
|
|
|
1190
1158
|
}
|
|
1191
1159
|
|
|
1192
1160
|
/** Run the install transaction (a new durable job), then let the store poll it. */
|
|
1193
|
-
async function runInstall(store: UpdateStoreLike, copy: UpdateCopy): Promise<void> {
|
|
1161
|
+
async function runInstall(store: UpdateStoreLike, copy: UpdateCopy, pluginNames?: string[]): Promise<void> {
|
|
1194
1162
|
const report = store.report()
|
|
1195
1163
|
if (report === null) return
|
|
1196
1164
|
// Without an evaluated set there is nothing to install: hq-edge has not read
|
|
@@ -1204,7 +1172,7 @@ async function runInstall(store: UpdateStoreLike, copy: UpdateCopy): Promise<voi
|
|
|
1204
1172
|
// dead code — runInstall never called it).
|
|
1205
1173
|
store.setInstalling(true)
|
|
1206
1174
|
try {
|
|
1207
|
-
const result = await installSet(report.manifestVersion)
|
|
1175
|
+
const result = await installSet(report.manifestVersion, pluginNames)
|
|
1208
1176
|
if (result.error !== undefined) {
|
|
1209
1177
|
store.setInstalling(false)
|
|
1210
1178
|
showToast(`${copy.installError}: ${result.error}`, 'error')
|
|
@@ -1287,7 +1255,7 @@ function renderSettingsPanel(
|
|
|
1287
1255
|
* The sticky footer: left meta ("准备安装 N 项兼容插件" / "版本集: vX"), a
|
|
1288
1256
|
* full-width primary "一键更新全套插件 (vX)" (install = one durable job),
|
|
1289
1257
|
* a secondary 忽略 wired to the existing ignore/unignore affordance, and the
|
|
1290
|
-
* footnote. The count EXCLUDES
|
|
1258
|
+
* footnote. The count EXCLUDES ignored / already-current entries.
|
|
1291
1259
|
*/
|
|
1292
1260
|
function renderFooter(
|
|
1293
1261
|
footer: HTMLElement,
|
|
@@ -1685,6 +1653,17 @@ export function openUpdateView(store: UpdateStoreLike, copy: UpdateCopy): void {
|
|
|
1685
1653
|
() => {
|
|
1686
1654
|
void store.check()
|
|
1687
1655
|
},
|
|
1656
|
+
(name) => {
|
|
1657
|
+
void runInstall(store, copy, [name])
|
|
1658
|
+
},
|
|
1659
|
+
(item) => {
|
|
1660
|
+
const report = store.report()
|
|
1661
|
+
if (report?.manifestVersion === null || report?.manifestVersion === undefined) return
|
|
1662
|
+
const action = item.state === 'ignored'
|
|
1663
|
+
? unignorePlugin(report.manifestVersion, item.name)
|
|
1664
|
+
: ignorePlugin(report.manifestVersion, item.name)
|
|
1665
|
+
void action.then(() => store.check()).catch(() => undefined)
|
|
1666
|
+
},
|
|
1688
1667
|
)
|
|
1689
1668
|
}
|
|
1690
1669
|
renderRuntime(runtimeSection, report, copy, runtimeActions, dismissedAlert, () => {
|
package/src/client/version.ts
CHANGED
|
@@ -120,8 +120,8 @@ export function isVersionUpgrade(currentVersion: string | null, availableVersion
|
|
|
120
120
|
* to `current`: the plugin is at (or ahead of) the offered release, there is
|
|
121
121
|
* nothing to install, and no install control may be offered for it. Every
|
|
122
122
|
* other state passes through untouched — `install` (plugin absent locally)
|
|
123
|
-
* has no local version to compare, and `
|
|
124
|
-
*
|
|
123
|
+
* has no local version to compare, and `ignored`/`current` are already
|
|
124
|
+
* non-actionable. Unknown server fields are preserved verbatim: the
|
|
125
125
|
* client stays a thin reader of the hq-edge report shape.
|
|
126
126
|
*/
|
|
127
127
|
export function normalizePluginUpdateItem<T extends PluginUpdateItem>(item: T): T {
|