dsh-plugin 2.2.7 → 2.3.1

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.
@@ -221,7 +221,7 @@
221
221
  background: var(--hub-text-tertiary);
222
222
  }
223
223
 
224
- /* 主信息:名称 + 已装版本 + 有更新徽标 */
224
+ /* 主信息:名称 + 已装版本 + 有更新徽标(同一行,名称超长时省略,徽标不被挤走) */
225
225
  .rowMain {
226
226
  display: flex;
227
227
  align-items: baseline;
@@ -230,25 +230,56 @@
230
230
  flex: 1 1 auto;
231
231
  }
232
232
 
233
+ /* 右上角 Star / Fork:靠右单行,配色与插件市场列表一致(星标琥珀、Fork 次级灰) */
234
+ .rowStats {
235
+ display: flex;
236
+ align-items: center;
237
+ gap: 10px;
238
+ margin-left: auto;
239
+ flex-shrink: 0;
240
+ white-space: nowrap;
241
+ }
242
+
243
+ .rowStar {
244
+ font-size: 11px;
245
+ line-height: 16px;
246
+ color: var(--hub-warn);
247
+ }
248
+
249
+ .rowFork {
250
+ font-size: 11px;
251
+ line-height: 16px;
252
+ color: var(--hub-text-tertiary);
253
+ }
254
+
233
255
  .rowTitle {
234
256
  font-size: 12px;
235
257
  line-height: 18px;
236
258
  font-weight: 600;
237
259
  color: var(--hub-text-primary);
260
+ flex: 0 1 auto;
238
261
  min-width: 0;
239
262
  white-space: nowrap;
240
263
  overflow: hidden;
241
264
  text-overflow: ellipsis;
265
+ /* 名称是主站详情页链接:不做成蓝色下划线,悬停才点明可点 */
266
+ text-decoration: none;
242
267
  }
243
268
 
244
- /* 已装版本:等宽弱化小字 */
269
+ a.rowTitle:hover {
270
+ color: var(--hub-brand);
271
+ text-decoration: underline;
272
+ }
273
+
274
+ /* 已装版本:等宽弱化小字。描边透明但保留 1px,与同行的「有更新」「插件市场」等高 */
245
275
  .versionBadge {
246
276
  flex-shrink: 0;
247
277
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
248
- font-size: 11px;
249
- line-height: 16px;
278
+ font-size: 10px;
279
+ line-height: 14px;
250
280
  color: var(--hub-text-tertiary);
251
281
  background: var(--hub-bg-hover);
282
+ border: 1px solid transparent;
252
283
  border-radius: 4px;
253
284
  padding: 0 5px;
254
285
  }
@@ -256,8 +287,8 @@
256
287
  /* 有更新徽标:品牌色实底小圆标 */
257
288
  .updateBadge {
258
289
  flex-shrink: 0;
259
- font-size: 11px;
260
- line-height: 16px;
290
+ font-size: 10px;
291
+ line-height: 14px;
261
292
  font-weight: 500;
262
293
  color: var(--hub-warn);
263
294
  background: var(--hub-warn-tint);
@@ -274,8 +305,8 @@
274
305
  width: 60px;
275
306
  box-sizing: border-box;
276
307
  text-align: center;
277
- font-size: 11px;
278
- line-height: 16px;
308
+ font-size: 10px;
309
+ line-height: 14px;
279
310
  font-weight: 500;
280
311
  border-radius: 4px;
281
312
  padding: 0 4px;
@@ -300,8 +331,8 @@
300
331
  与「有更新」实底区分,表达「这是示例项目,宿主不加载」 */
301
332
  .exampleBadge {
302
333
  flex-shrink: 0;
303
- font-size: 11px;
304
- line-height: 16px;
334
+ font-size: 10px;
335
+ line-height: 14px;
305
336
  font-weight: 500;
306
337
  color: var(--hub-brand);
307
338
  background: var(--hub-brand-tint);
@@ -338,16 +369,34 @@
338
369
  white-space: nowrap;
339
370
  overflow: hidden;
340
371
  text-overflow: ellipsis;
372
+ /* 仓库名是主站详情页链接:默认与普通文字无异,悬停才提示可点 */
373
+ text-decoration: none;
374
+ }
375
+
376
+ a.rowRepo:hover {
377
+ color: var(--hub-brand);
378
+ text-decoration: underline;
341
379
  }
342
380
 
343
381
  .rowCategory {
344
382
  flex-shrink: 0;
345
- font-size: 11px;
346
- line-height: 16px;
383
+ font-size: 10px;
384
+ line-height: 14px;
347
385
  color: var(--hub-text-tertiary);
348
386
  background: var(--hub-bg-hover);
387
+ border: 1px solid transparent;
349
388
  border-radius: 4px;
350
- padding: 0 6px;
389
+ padding: 0 5px;
390
+ }
391
+
392
+ /* 来源标签 + 操作按钮行:独立成行靠右成组,名称行只留名称/版本/更新状态 */
393
+ .rowActionLine {
394
+ display: flex;
395
+ align-items: center;
396
+ justify-content: flex-end;
397
+ gap: 8px;
398
+ padding-left: 16px;
399
+ min-width: 0;
351
400
  }
352
401
 
353
402
  /* —— 行操作:详情 / 更新 / 卸载 —— */
@@ -359,14 +408,16 @@
359
408
  flex-shrink: 0;
360
409
  }
361
410
 
362
- /* 详情:中性细边框按钮,与卸载同款描边样式(视觉成组) */
411
+ /* 详情:默认无边框(低频操作不抢视觉),悬停才出现描边与底色成为按钮形状。
412
+ 描边保留 1px 透明而非 none,保证与同行的更新/卸载按钮同高、文字基线对齐 */
363
413
  .rowDetail {
364
414
  font-family: inherit;
365
415
  font-size: 12px;
366
416
  line-height: 18px;
417
+ font-weight: 500;
367
418
  color: var(--hub-text-secondary);
368
419
  background: transparent;
369
- border: 1px solid var(--hub-border-2);
420
+ border: 1px solid transparent;
370
421
  border-radius: 6px;
371
422
  padding: 2px 10px;
372
423
  cursor: pointer;
@@ -376,7 +427,7 @@
376
427
 
377
428
  .rowDetail:hover {
378
429
  color: var(--hub-text-primary);
379
- border-color: var(--hub-border-ghost);
430
+ border-color: var(--hub-border-2);
380
431
  background: var(--hub-bg-hover);
381
432
  }
382
433
 
@@ -403,7 +454,8 @@
403
454
  border-color: var(--hub-warning);
404
455
  }
405
456
 
406
- /* 更新:琥珀色实底小按钮(与插件市场「可更新」同色),仅在「有更新」时出现 */
457
+ /* 更新:琥珀色实底小按钮(与插件市场「可更新」同色),仅在「有更新」时出现。
458
+ 与详情/卸载同度量(1px 描边 + 2px 内距),否则实底按钮会比描边按钮窄一圈 */
407
459
  .rowUpdate {
408
460
  font-family: inherit;
409
461
  font-size: 12px;
@@ -411,9 +463,9 @@
411
463
  font-weight: 500;
412
464
  color: #fff;
413
465
  background: var(--hub-warn);
414
- border: none;
466
+ border: 1px solid var(--hub-warn);
415
467
  border-radius: 6px;
416
- padding: 3px 10px;
468
+ padding: 2px 10px;
417
469
  cursor: pointer;
418
470
  user-select: none;
419
471
  transition: background 120ms ease;
@@ -428,6 +480,7 @@
428
480
  font-family: inherit;
429
481
  font-size: 12px;
430
482
  line-height: 18px;
483
+ font-weight: 500;
431
484
  color: var(--hub-danger);
432
485
  background: transparent;
433
486
  border: 1px solid var(--hub-danger-border-soft);
@@ -52,7 +52,8 @@
52
52
  gap: 4px;
53
53
  }
54
54
 
55
- .cardHead {
55
+ /* 名称行:名称可收缩到省略号,版本与更新徽标不收缩 —— 长仓库名不会把状态挤没 */
56
+ .cardTitleRow {
56
57
  display: flex;
57
58
  align-items: center;
58
59
  gap: 6px;
@@ -60,26 +61,55 @@
60
61
  }
61
62
 
62
63
  .cardTitle {
64
+ flex: 0 1 auto;
65
+ min-width: 0;
63
66
  font-size: 13px;
64
67
  line-height: 18px;
65
68
  font-weight: 600;
66
69
  white-space: nowrap;
67
70
  overflow: hidden;
68
71
  text-overflow: ellipsis;
72
+ /* 链接形态但不做成蓝色下划线:沿用标题颜色,悬停才点明可点 */
73
+ color: inherit;
74
+ text-decoration: none;
75
+ transition: color 120ms ease;
76
+ }
77
+
78
+ a.cardTitle:hover {
79
+ color: var(--hub-brand);
80
+ text-decoration: underline;
69
81
  }
70
82
 
83
+ /* 分类 / 验证徽标行:与名称行分开,长名称与多枚徽标互不抢宽度 */
84
+ .cardBadgeRow {
85
+ display: flex;
86
+ align-items: center;
87
+ flex-wrap: wrap;
88
+ gap: 6px;
89
+ }
90
+
91
+ /* 徽章统一度量:字号 / 行高 / 内距 / 描边一致,同屏并排时不会一大一小。
92
+ 以 14px 行高 + 1px 描边收成 16px 高的轻量标签,不与名称行争视觉重量 */
71
93
  .categoryBadge,
72
94
  .verified,
95
+ .authorBadge,
73
96
  .versionBadge,
74
97
  .updateBadge {
75
98
  flex-shrink: 0;
76
99
  font-size: 10px;
77
- line-height: 16px;
100
+ line-height: 14px;
78
101
  border-radius: 4px;
79
- padding: 0 6px;
102
+ padding: 0 5px;
80
103
  border: 1px solid transparent;
81
104
  }
82
105
 
106
+ /* 作者:等宽字体 + 中性灰,与分类(品牌蓝)、已验证(绿)区分,避免一行里多个彩色标签抢注意力 */
107
+ .authorBadge {
108
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
109
+ color: var(--hub-text-tertiary);
110
+ border-color: var(--hub-border-ghost);
111
+ }
112
+
83
113
  .categoryBadge {
84
114
  color: var(--hub-brand);
85
115
  border-color: var(--hub-border-ghost);
@@ -1487,17 +1487,54 @@ a.failRepo:hover .failRepoUrl {
1487
1487
  max-width: calc(100vw - 48px);
1488
1488
  }
1489
1489
 
1490
- /* 版本 / 发布时间元信息行:小号标签并排,弱化但可读 */
1491
- .hubUpdateMeta {
1490
+ /* 版本条:版本号是这一屏的主信息,状态胶囊紧跟其后,发布时间靠右作为附属说明。
1491
+ 三者在同一行、字号与颜色分层,用户一眼就能分清「版本号」与「状态」 */
1492
+ .hubVersionBar {
1492
1493
  display: flex;
1493
1494
  align-items: center;
1494
1495
  flex-wrap: wrap;
1495
- gap: 4px 14px;
1496
- font-size: 12px;
1496
+ gap: 4px 10px;
1497
+ }
1498
+
1499
+ .hubVersionNumber {
1500
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
1501
+ font-size: 18px;
1502
+ font-weight: 600;
1503
+ line-height: 26px;
1504
+ color: var(--hub-text-primary);
1505
+ }
1506
+
1507
+ /* 状态胶囊:颜色本身就是区分,读不到文案也能分辨最新 / 可更新 / 暂缓 */
1508
+ .hubStateChip {
1509
+ padding: 0 8px;
1510
+ border: 1px solid transparent;
1511
+ border-radius: 999px;
1512
+ font-size: 11px;
1497
1513
  line-height: 18px;
1514
+ white-space: nowrap;
1515
+ }
1516
+
1517
+ .hubStateLatest {
1518
+ color: var(--hub-success);
1519
+ border-color: var(--hub-success-border);
1520
+ background: var(--hub-success-tint);
1521
+ }
1522
+
1523
+ .hubStateAvailable {
1524
+ color: var(--hub-warn);
1525
+ border-color: var(--hub-warn-border);
1526
+ background: var(--hub-warn-tint);
1498
1527
  }
1499
1528
 
1500
- .hubUpdateMetaItem {
1529
+ .hubStatePending {
1530
+ color: var(--hub-text-tertiary);
1531
+ border-color: var(--hub-border-2);
1532
+ }
1533
+
1534
+ .hubVersionDate {
1535
+ margin-left: auto;
1536
+ font-size: 12px;
1537
+ line-height: 18px;
1501
1538
  color: var(--hub-text-tertiary);
1502
1539
  }
1503
1540
 
@@ -148,7 +148,9 @@ export function registerCatalogRoutes(webServer: WebServerService, profile: stri
148
148
  // 兜底缓存也损坏:落到下面的 502
149
149
  }
150
150
  }
151
- sendJson(response, 502, { error: 'catalog fetch failed' })
151
+ // 带上 curl 退出码与 stderr 末行:只回 error 字段时,用户贴出来的响应里
152
+ // 没有任何可诊断信息
153
+ sendJson(response, 502, { error: 'catalog fetch failed', detail: r.detail })
152
154
  return
153
155
  }
154
156
  try {
@@ -287,13 +287,29 @@ export async function finalUrl(url: string, proxy: string, timeoutMs: number): P
287
287
  */
288
288
  export function fetchViaCurl(
289
289
  url: string, proxy: string, timeoutMs: number,
290
- ): Promise<{ ok: boolean; body: string; reason: ProbeFailReason | null }> {
290
+ ): Promise<{ ok: boolean; body: string; reason: ProbeFailReason | null; detail: string | null }> {
291
291
  // 同样用 -sS:正文之外还要 stderr 里的 TLS 失败线索(被 stderr 收集,不外泄给用户)。
292
- // --compressed:目录 JSON 约 3.6MB,源站支持 gzip,压缩后弱网下也能在超时内拉完。
293
- const args = ['-sS', '--compressed', '--max-time', String(Math.max(1, Math.round(timeoutMs / 1000))), url]
294
- return curlWithTlsFallback(args, proxyEnv(proxy), timeoutMs).then((r) => ({
295
- ok: r.code === 0,
296
- body: r.stdout,
297
- reason: r.code !== 0 && tlsRevocationBlocked(r.stderr) ? 'tlsRevocation' : null,
298
- }))
292
+ // 编码不用 --compressed 的自动协商,而是明确只接受 gzip:协商会让服务端按本机 curl 声明的
293
+ // 能力回 br / zstd,解不开时 curl 非零退出且正文为空,目录拉取直接失败;而 gzip 的解码器
294
+ // 每个 curl 都有,对 JSON 的压缩率也够。
295
+ const gzip = ['-H', 'Accept-Encoding: gzip', '--compressed']
296
+ const attempt = (extra: string[]): string[] => [
297
+ '-sS', ...gzip, ...extra, '--max-time', String(Math.max(1, Math.round(timeoutMs / 1000))), url,
298
+ ]
299
+ return curlWithTlsFallback(attempt([]), proxyEnv(proxy), timeoutMs).then(async (first) => {
300
+ // 首轮失败(连接被重置 / 编码解不开 / HTTP/2 流异常)时退到 HTTP/1.1 再试一次:
301
+ // Windows 自带 curl 与大响应 + HTTP/2 的组合偶发流级失败,换协议版本代价最小;
302
+ // 只在真失败时才多一次请求。
303
+ const r = first.code === 0
304
+ ? first
305
+ : await curlWithTlsFallback(attempt(['--http1.1']), proxyEnv(proxy), timeoutMs)
306
+ const failed = r.code !== 0 || r.stdout === ''
307
+ return {
308
+ ok: !failed,
309
+ body: r.stdout,
310
+ reason: failed && tlsRevocationBlocked(r.stderr) ? 'tlsRevocation' : null,
311
+ // 只留退出码与 stderr 末行:正文不进错误详情,且要够短才能在界面上完整显示
312
+ detail: failed ? `curl exit ${r.code ?? 'null'}: ${r.stderr.trim().split('\n').slice(-1)[0] ?? ''}`.slice(0, 300) : null,
313
+ }
314
+ })
299
315
  }