dsh-plugin 1.1.2 → 1.2.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.
Files changed (110) hide show
  1. package/client/client.js +4465 -1442
  2. package/client/client.js.map +1 -1
  3. package/lib/http/routes.js +438 -22
  4. package/lib/index.js +1 -1
  5. package/lib/services/install/install.js +4 -0
  6. package/lib/services/install/npm-check.js +55 -0
  7. package/lib/services/{npm-resolve.js → install/npm-resolve.js} +7 -5
  8. package/lib/services/{preflight.js → install/preflight.js} +15 -4
  9. package/lib/services/{task-queue.js → install/task-queue.js} +155 -66
  10. package/lib/services/loader.js +56 -1
  11. package/lib/services/log.js +138 -0
  12. package/lib/services/probe.js +157 -0
  13. package/lib/services/{profile.js → profile/profile.js} +29 -1
  14. package/lib/services/settings.js +60 -0
  15. package/lib/types/http/routes.d.ts +1 -1
  16. package/lib/types/index.d.ts +1 -1
  17. package/lib/types/services/{install-types.d.ts → install/install-types.d.ts} +3 -0
  18. package/lib/types/services/{install.d.ts → install/install.d.ts} +5 -5
  19. package/lib/types/services/install/npm-check.d.ts +12 -0
  20. package/lib/types/services/{npm-resolve.d.ts → install/npm-resolve.d.ts} +2 -1
  21. package/lib/types/services/{task-queue.d.ts → install/task-queue.d.ts} +5 -1
  22. package/lib/types/services/loader.d.ts +14 -8
  23. package/lib/types/services/log.d.ts +49 -0
  24. package/lib/types/services/probe.d.ts +30 -0
  25. package/lib/types/services/{pending-restart.d.ts → profile/pending-restart.d.ts} +1 -1
  26. package/lib/types/services/{profile.d.ts → profile/profile.d.ts} +2 -0
  27. package/lib/types/services/settings.d.ts +24 -0
  28. package/package.json +1 -1
  29. package/src/client/components/PluginHubSection.tsx +341 -120
  30. package/src/client/components/{CatalogList.tsx → catalog/CatalogList.tsx} +11 -7
  31. package/src/client/components/{PluginCard.tsx → catalog/PluginCard.tsx} +5 -4
  32. package/src/client/components/layout/CatalogControls.tsx +80 -0
  33. package/src/client/components/{CatalogHeader.tsx → layout/CatalogHeader.tsx} +4 -4
  34. package/src/client/components/{CategoryTabs.tsx → layout/CategoryTabs.tsx} +3 -3
  35. package/src/client/components/layout/SectionTabs.tsx +60 -0
  36. package/src/client/components/{AboutModal.tsx → modals/AboutModal.tsx} +7 -7
  37. package/src/client/components/{HubUpdateModal.tsx → modals/HubUpdateModal.tsx} +5 -5
  38. package/src/client/components/modals/InstalledDetailModal.tsx +193 -0
  39. package/src/client/components/modals/LogsModal.tsx +301 -0
  40. package/src/client/components/modals/LogsPathModal.tsx +161 -0
  41. package/src/client/components/{NotificationsModal.tsx → modals/NotificationsModal.tsx} +47 -20
  42. package/src/client/components/{ProgressView.tsx → modals/ProgressView.tsx} +2 -2
  43. package/src/client/components/{modals.tsx → modals/modals.tsx} +117 -40
  44. package/src/client/components/{Dropdown.tsx → ui/Dropdown.tsx} +14 -6
  45. package/src/client/components/ui/Toggle.tsx +28 -0
  46. package/src/client/components/ui/icons.tsx +343 -0
  47. package/src/client/components/views/CustomInstallView.tsx +250 -0
  48. package/src/client/components/views/DiagnosticsView.tsx +172 -0
  49. package/src/client/components/views/InstalledView.tsx +328 -0
  50. package/src/client/components/views/LogsView.tsx +150 -0
  51. package/src/client/components/views/MarketView.tsx +66 -0
  52. package/src/client/components/views/SettingsView.tsx +273 -0
  53. package/src/client/data/catalog.ts +38 -0
  54. package/src/client/data/host.ts +107 -0
  55. package/src/client/data/hub.ts +41 -0
  56. package/src/client/hooks/useCatalog.ts +71 -115
  57. package/src/client/hooks/useIncrementalList.ts +46 -0
  58. package/src/client/hooks/useLanguage.ts +40 -0
  59. package/src/client/hooks/useSettings.ts +90 -0
  60. package/src/client/hooks/useTaskQueue.ts +156 -39
  61. package/src/client/locales.ts +358 -38
  62. package/src/client/logic/constants.ts +86 -0
  63. package/src/client/{lib → logic}/failures.ts +95 -74
  64. package/src/client/logic/install-command.ts +56 -0
  65. package/src/client/logic/installed.ts +207 -0
  66. package/src/client/logic/normalize.ts +52 -0
  67. package/src/client/logic/urls.ts +125 -0
  68. package/src/client/styles/CustomInstallView.module.css +191 -0
  69. package/src/client/styles/DiagnosticsView.module.css +231 -0
  70. package/src/client/styles/Dropdown.module.css +42 -41
  71. package/src/client/styles/Header.module.css +100 -132
  72. package/src/client/styles/InstalledView.module.css +447 -0
  73. package/src/client/styles/List.module.css +6 -0
  74. package/src/client/styles/LogsView.module.css +503 -0
  75. package/src/client/styles/Modal.module.css +352 -70
  76. package/src/client/styles/SectionTabs.module.css +142 -0
  77. package/src/client/styles/SettingsView.module.css +232 -0
  78. package/src/client/styles/Toggle.module.css +60 -0
  79. package/src/client/styles/tokens.module.css +1 -0
  80. package/src/client/types.ts +23 -3
  81. package/src/server/http/routes.ts +414 -22
  82. package/src/server/index.ts +1 -1
  83. package/src/server/services/{install-types.ts → install/install-types.ts} +3 -1
  84. package/src/server/services/{install.ts → install/install.ts} +5 -5
  85. package/src/server/services/install/npm-check.ts +53 -0
  86. package/src/server/services/{npm-resolve.ts → install/npm-resolve.ts} +7 -5
  87. package/src/server/services/{preflight.ts → install/preflight.ts} +16 -4
  88. package/src/server/services/{task-queue.ts → install/task-queue.ts} +157 -63
  89. package/src/server/services/loader.ts +55 -1
  90. package/src/server/services/log.ts +167 -0
  91. package/src/server/services/probe.ts +157 -0
  92. package/src/server/services/{pending-restart.ts → profile/pending-restart.ts} +1 -1
  93. package/src/server/services/{profile.ts → profile/profile.ts} +31 -1
  94. package/src/server/services/settings.ts +79 -0
  95. package/lib/services/install.js +0 -4
  96. package/src/client/components/CatalogControls.tsx +0 -108
  97. package/src/client/components/icons.tsx +0 -129
  98. package/src/client/lib/catalog.ts +0 -293
  99. package/src/client/lib/env.ts +0 -20
  100. /package/lib/services/{install-types.js → install/install-types.js} +0 -0
  101. /package/lib/services/{installed-versions.js → profile/installed-versions.js} +0 -0
  102. /package/lib/services/{pending-restart.js → profile/pending-restart.js} +0 -0
  103. /package/lib/services/{progress.js → profile/progress.js} +0 -0
  104. /package/lib/types/services/{preflight.d.ts → install/preflight.d.ts} +0 -0
  105. /package/lib/types/services/{installed-versions.d.ts → profile/installed-versions.d.ts} +0 -0
  106. /package/lib/types/services/{progress.d.ts → profile/progress.d.ts} +0 -0
  107. /package/src/client/{lib → logic}/format.ts +0 -0
  108. /package/src/client/{lib → logic}/renderMarkdown.ts +0 -0
  109. /package/src/server/services/{installed-versions.ts → profile/installed-versions.ts} +0 -0
  110. /package/src/server/services/{progress.ts → profile/progress.ts} +0 -0
@@ -223,16 +223,28 @@
223
223
  text-overflow: ellipsis;
224
224
  }
225
225
 
226
+ /* 工具栏第二行:左侧筛选结果数 + 右侧排序按钮组(排序组靠 auto 顶到行尾);
227
+ 分隔线收在行底,与插件列表之间一条横线 */
226
228
  .controls {
227
229
  display: flex;
228
230
  align-items: center;
229
- gap: 4px;
231
+ gap: 8px;
230
232
  flex-shrink: 0;
231
- /* 左右分区:左侧操作控件靠左,通知入口经 margin-left:auto 独立顶到最右 */
233
+ padding: 0 2px 8px;
234
+ border-bottom: 1px solid var(--hub-border-1);
235
+ }
236
+
237
+ /* 结果数里的数字:等宽数字 + 固定 5 位宽度;数字在预留位中始终居中,两侧位置都固定不跳动 */
238
+ .resultCount {
239
+ font-variant-numeric: tabular-nums;
240
+ min-width: 5ch;
241
+ text-align: center;
232
242
  }
233
243
 
234
- /* 列表头部筛选结果数:位于排序下拉之前,弱化灰字提示「筛选出 N 条插件」,字号与相邻按钮一致 */
235
- .filterResults {
244
+ /* —— result count —— */
245
+
246
+ /* 筛选结果数:弱化灰字信息「结果 N 条插件」,位于工具栏行最左侧 */
247
+ .resultSeg {
236
248
  flex-shrink: 0;
237
249
  display: inline-flex;
238
250
  align-items: baseline;
@@ -240,13 +252,76 @@
240
252
  line-height: 18px;
241
253
  color: var(--hub-text-tertiary);
242
254
  white-space: nowrap;
255
+ user-select: none;
243
256
  }
244
257
 
245
- /* 结果数里的数字:等宽数字 + 固定 5 位宽度;数字在预留位中始终居中,两侧位置都固定不跳动 */
246
- .resultCount {
247
- font-variant-numeric: tabular-nums;
248
- min-width: 5ch;
249
- text-align: center;
258
+ /* —— 排序按钮组(与已安装视图同款 segmented control)——
259
+ 组前小标签 + 按钮组,靠 margin-left:auto 顶到结果数右侧行尾 */
260
+
261
+ /* 排序组容器:整体右对齐,独立一行内容不换行 */
262
+ .sortGroup {
263
+ margin-left: auto;
264
+ flex-shrink: 0;
265
+ display: inline-flex;
266
+ align-items: center;
267
+ gap: 8px;
268
+ }
269
+
270
+ /* 组前小标签:灰字,标注这组按钮的作用 */
271
+ .segLabel {
272
+ flex-shrink: 0;
273
+ font-size: 11px;
274
+ line-height: 16px;
275
+ color: var(--hub-text-tertiary);
276
+ }
277
+
278
+ .segGroup {
279
+ display: inline-flex;
280
+ align-items: center;
281
+ flex-shrink: 0;
282
+ border: 1px solid var(--hub-border-2);
283
+ border-radius: 6px;
284
+ overflow: hidden;
285
+ }
286
+
287
+ .segBtn {
288
+ font-family: inherit;
289
+ font-size: 12px;
290
+ line-height: 24px;
291
+ height: 24px;
292
+ padding: 0 10px;
293
+ display: inline-flex;
294
+ align-items: center;
295
+ justify-content: center;
296
+ gap: 4px;
297
+ color: var(--hub-text-secondary);
298
+ background: transparent;
299
+ border: none;
300
+ cursor: pointer;
301
+ user-select: none;
302
+ white-space: nowrap;
303
+ transition: color 120ms ease, background 120ms ease;
304
+ }
305
+
306
+ /* 激活排序按钮上的方向箭头:随文字色着色、不可被压缩 */
307
+ .segBtn svg {
308
+ flex-shrink: 0;
309
+ display: block;
310
+ }
311
+
312
+ .segBtn + .segBtn {
313
+ border-left: 1px solid var(--hub-border-2);
314
+ }
315
+
316
+ .segBtn:hover {
317
+ color: var(--hub-text-primary);
318
+ background: var(--hub-bg-hover);
319
+ }
320
+
321
+ .segBtnActive,
322
+ .segBtnActive:hover {
323
+ color: #fff;
324
+ background: var(--hub-brand);
250
325
  }
251
326
 
252
327
  /* —— ad banner —— */
@@ -307,19 +382,23 @@
307
382
 
308
383
  .searchRow {
309
384
  display: flex;
385
+ align-items: center;
386
+ gap: 6px;
310
387
  padding: 0 2px;
311
388
  }
312
389
 
313
- /* 搜索框:用户指定只占整行一半宽,右侧留白给排序/筛选/通知控件组 */
390
+ /* 搜索框:整行占满(排序已移出到结果数下方的按钮组行,搜索框独占整行) */
314
391
  .search {
315
- width: 50%;
392
+ flex: 1 1 auto;
393
+ min-width: 0;
394
+ height: 28px;
395
+ padding: 0 9px;
316
396
  font-size: 12px;
317
- line-height: 18px;
397
+ line-height: 26px;
318
398
  color: inherit;
319
399
  background: transparent;
320
400
  border: 1px solid var(--hub-border-2);
321
401
  border-radius: 6px;
322
- padding: 4px 9px;
323
402
  outline: none;
324
403
  transition: border-color 120ms ease;
325
404
  }
@@ -332,79 +411,6 @@
332
411
  border-color: var(--hub-brand);
333
412
  }
334
413
 
335
- /* —— install-state filter —— */
336
-
337
- /* 已安装 / 未安装:两个独立按钮,外观尺寸与「安装失败记录」按钮一致(高 24px);
338
- 各自点击筛选对应安装状态,再点一次恢复全部。公共规则统一无边框,
339
- 避免激活态回落到浏览器默认 2px 边框导致点击时按钮变粗跳动。 */
340
- .installedBtn,
341
- .installedBtnActive,
342
- .installedBtnDisabled {
343
- flex-shrink: 0;
344
- display: inline-flex;
345
- align-items: center;
346
- gap: 5px;
347
- height: 24px;
348
- padding: 0 8px;
349
- border: none;
350
- border-radius: 6px;
351
- font-size: 12px;
352
- line-height: 22px;
353
- cursor: pointer;
354
- user-select: none;
355
- transition: color 120ms ease, background 120ms ease;
356
- }
357
-
358
- .installedBtn {
359
- color: var(--hub-text-primary);
360
- background: transparent;
361
- }
362
-
363
- /* 悬浮:明显加重底色,保证「看得见、点得准」。 */
364
- .installedBtn:hover {
365
- color: var(--hub-text-primary);
366
- background: var(--hub-bg-hover);
367
- }
368
-
369
- /* 置灰:当前分类下无对应安装状态的插件时不可点;尺寸保持 24px 不塌陷,
370
- 文字弱化,配合 disabled 属性天然屏蔽 hover/点击。 */
371
- .installedBtnDisabled,
372
- .installedBtnDisabled:hover {
373
- color: var(--hub-text-disabled);
374
- background: transparent;
375
- cursor: not-allowed;
376
- }
377
-
378
- .installedBtnDisabled .segCount {
379
- color: var(--hub-text-disabled);
380
- background: rgba(128, 128, 128, 0.06);
381
- }
382
-
383
- .installedBtnActive {
384
- color: var(--hub-text-on-fill);
385
- background: var(--hub-btn-fill);
386
- }
387
-
388
- .segCount,
389
- .segCountActive {
390
- font-size: 10px;
391
- line-height: 14px;
392
- border-radius: 999px;
393
- padding: 0 4px;
394
- min-width: 14px;
395
- text-align: center;
396
- }
397
-
398
- .segCount {
399
- color: var(--hub-text-tertiary);
400
- background: var(--hub-bg-btn);
401
- }
402
-
403
- .installedBtnActive .segCountActive {
404
- color: var(--hub-text-on-fill);
405
- background: var(--hub-bg-on-fill);
406
- }
407
-
408
414
  /* —— category chips —— */
409
415
 
410
416
  .tabs {
@@ -464,53 +470,15 @@
464
470
  background: var(--hub-bg-on-fill);
465
471
  }
466
472
 
467
- /* —— notifications entry —— */
468
-
469
- /* 通知中心入口:中性次级按钮(浅灰底 + 细边框),与左侧操作按钮区分、常态可辨;
470
- box-sizing: border-box 让 1px 边框包含在 24px 高度内,与「已安装/未安装」无边框按钮外尺寸一致;
471
- 有通知时右上角悬浮的红圈白字徽标成为提醒焦点 */
472
- .failBtn {
473
- position: relative;
474
- flex-shrink: 0;
475
- /* 与左侧操作按钮分开:自动吃掉剩余空间,把按钮顶到控件行最右 */
476
- margin-left: auto;
477
- display: inline-flex;
473
+ /* 一级导航行:tab 占满左侧 */
474
+ .tabsRow {
475
+ display: flex;
478
476
  align-items: center;
479
- box-sizing: border-box;
480
- height: 24px;
481
- padding: 0 10px;
482
- border: 1px solid var(--hub-border-2);
483
- border-radius: 6px;
484
- font-size: 12px;
485
- line-height: 22px;
486
- color: var(--hub-text-primary);
487
- background: var(--hub-bg-hover);
488
- cursor: pointer;
489
- user-select: none;
490
- transition: color 120ms ease, background 120ms ease;
491
- }
492
-
493
- .failBtn:hover {
494
- color: var(--hub-text-primary);
495
- background: rgba(128, 128, 128, 0.28);
477
+ gap: 10px;
478
+ flex-shrink: 0;
496
479
  }
497
480
 
498
- /* 计数徽标:红色圆形 + 白色数字,整体浮在按钮右上角上沿之外(16px 徽章 + top:-16px
499
- 完全悬于按钮灰底之上,不遮盖灰底),只在通知数 >0 时出现 */
500
- .failBadge {
501
- position: absolute;
502
- top: -16px;
503
- right: -7px;
504
- z-index: 1;
505
- min-width: 16px;
506
- height: 16px;
507
- padding: 0 5px;
508
- border-radius: 999px;
509
- font-size: 10px;
510
- font-weight: 600;
511
- line-height: 16px;
512
- text-align: center;
513
- color: #ffffff;
514
- background: var(--hub-danger);
515
- box-shadow: 0 1px 4px rgba(209, 36, 47, 0.4);
481
+ .tabsRow > :first-child {
482
+ flex: 1 1 auto;
483
+ min-width: 0;
516
484
  }
@@ -0,0 +1,447 @@
1
+ /* DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * Website: https://dsh-plugin.org
3
+ * GitHub: https://github.com/dshplugin/dsh-plugin-hub
4
+ *
5
+ * Installed view: searchable, sortable list of installed plugins mixing
6
+ * catalog plugins (full metadata) and custom installs (runtime info only,
7
+ * amber custom badge). The toolbar holds a search box plus two segmented
8
+ * radio-button groups (source filter + sort). Each row has three tidy
9
+ * lines: title line (running-status dot + name + version/update badges +
10
+ * fixed action buttons on the right), meta line (repo · category), and a
11
+ * one-line description. Colors resolve through the --hub-* tokens. */
12
+
13
+ .root {
14
+ display: flex;
15
+ flex-direction: column;
16
+ gap: 8px;
17
+ min-width: 0;
18
+ }
19
+
20
+ /* —— 工具栏:「已安装插件」列表上方 ——
21
+ 第一行搜索框(撑满整行),第二行来源筛选按钮组 + 排序按钮组;
22
+ hairline 与下方列表区隔 */
23
+
24
+ .toolbar {
25
+ display: flex;
26
+ flex-direction: column;
27
+ gap: 8px;
28
+ flex-shrink: 0;
29
+ padding: 0 2px 10px;
30
+ border-bottom: 1px solid var(--hub-border-1);
31
+ }
32
+
33
+ /* 搜索框容器:撑满整行宽度,与按钮组等视觉高度 */
34
+ .searchWrap {
35
+ width: 100%;
36
+ height: 28px;
37
+ min-width: 0;
38
+ }
39
+
40
+ /* 搜索输入:28px 与下拉触发器等高 */
41
+ .searchInput {
42
+ width: 100%;
43
+ height: 28px;
44
+ padding: 0 9px;
45
+ font-size: 12px;
46
+ line-height: 26px;
47
+ color: var(--hub-text-primary);
48
+ background: transparent;
49
+ border: 1px solid var(--hub-border-2);
50
+ border-radius: 6px;
51
+ outline: none;
52
+ transition: border-color 120ms ease;
53
+ }
54
+
55
+ .searchInput::placeholder {
56
+ color: var(--hub-text-tertiary);
57
+ }
58
+
59
+ .searchInput:focus {
60
+ border-color: var(--hub-brand);
61
+ }
62
+
63
+ /* —— 单选按钮组(segmented control):来源筛选 / 排序 ——
64
+ 一组一个描边容器,按钮间竖线分隔,选中项品牌色实底 */
65
+
66
+ .segRow {
67
+ display: flex;
68
+ align-items: center;
69
+ gap: 8px;
70
+ min-width: 0;
71
+ }
72
+
73
+ /* 组前小标签:灰字,标注这组按钮的作用 */
74
+ .segLabel {
75
+ flex-shrink: 0;
76
+ font-size: 11px;
77
+ line-height: 16px;
78
+ color: var(--hub-text-tertiary);
79
+ }
80
+
81
+ .segGroup {
82
+ display: inline-flex;
83
+ align-items: center;
84
+ flex-shrink: 0;
85
+ border: 1px solid var(--hub-border-2);
86
+ border-radius: 6px;
87
+ overflow: hidden;
88
+ }
89
+
90
+ .segBtn {
91
+ font-family: inherit;
92
+ font-size: 12px;
93
+ line-height: 24px;
94
+ height: 24px;
95
+ padding: 0 10px;
96
+ display: inline-flex;
97
+ align-items: center;
98
+ justify-content: center;
99
+ gap: 4px;
100
+ color: var(--hub-text-secondary);
101
+ background: transparent;
102
+ border: none;
103
+ cursor: pointer;
104
+ user-select: none;
105
+ white-space: nowrap;
106
+ transition: color 120ms ease, background 120ms ease;
107
+ }
108
+
109
+ /* 激活排序按钮上的方向箭头:随文字色着色、不可被压缩 */
110
+ .segBtn svg {
111
+ flex-shrink: 0;
112
+ display: block;
113
+ }
114
+
115
+ .segBtn + .segBtn {
116
+ border-left: 1px solid var(--hub-border-2);
117
+ }
118
+
119
+ .segBtn:hover {
120
+ color: var(--hub-text-primary);
121
+ background: var(--hub-bg-hover);
122
+ }
123
+
124
+ .segBtnActive,
125
+ .segBtnActive:hover {
126
+ color: #fff;
127
+ background: var(--hub-brand);
128
+ }
129
+
130
+ /* —— 空态 / 搜索无结果 —— */
131
+
132
+ .empty {
133
+ display: flex;
134
+ flex-direction: column;
135
+ align-items: center;
136
+ gap: 4px;
137
+ padding: 36px 12px;
138
+ font-size: 12px;
139
+ line-height: 18px;
140
+ color: var(--hub-text-tertiary);
141
+ text-align: center;
142
+ border: 1px dashed var(--hub-border-2);
143
+ border-radius: 8px;
144
+ }
145
+
146
+ .emptyTitle {
147
+ font-size: 13px;
148
+ font-weight: 600;
149
+ color: var(--hub-text-secondary);
150
+ }
151
+
152
+ .emptyDesc {
153
+ color: var(--hub-text-tertiary);
154
+ }
155
+
156
+ /* —— 行列表 —— */
157
+
158
+ .list {
159
+ display: flex;
160
+ flex-direction: column;
161
+ list-style: none;
162
+ margin: 0;
163
+ padding: 0;
164
+ border: 1px solid var(--hub-border-1);
165
+ border-radius: 8px;
166
+ overflow: hidden;
167
+ }
168
+
169
+ .row {
170
+ display: flex;
171
+ flex-direction: column;
172
+ gap: 4px;
173
+ padding: 8px 10px;
174
+ background: var(--hub-bg-1);
175
+ cursor: pointer;
176
+ user-select: none;
177
+ transition: background 120ms ease;
178
+ }
179
+
180
+ .row + .row {
181
+ border-top: 1px solid var(--hub-border-1);
182
+ }
183
+
184
+ .row:hover {
185
+ background: var(--hub-bg-2);
186
+ }
187
+
188
+ /* 第一行:状态点 + 名称/版本/更新徽标 + 右侧固定操作按钮组 */
189
+ .rowTitleLine {
190
+ display: flex;
191
+ align-items: center;
192
+ gap: 8px;
193
+ min-width: 0;
194
+ }
195
+
196
+ /* 第三行(仅目录插件):简介单独一行,超长省略号收尾;
197
+ 左缩进对齐名称(8 状态点 + 8 间隔 = 16px) */
198
+ .rowDesc {
199
+ padding-left: 16px;
200
+ font-size: 11px;
201
+ line-height: 16px;
202
+ color: var(--hub-text-tertiary);
203
+ white-space: nowrap;
204
+ overflow: hidden;
205
+ text-overflow: ellipsis;
206
+ min-width: 0;
207
+ }
208
+
209
+ /* 运行状态点:绿 = 已加载进 loader(运行中);琥珀 = 已安装未挂载(装完未重启) */
210
+ .statusDot {
211
+ flex-shrink: 0;
212
+ width: 8px;
213
+ height: 8px;
214
+ border-radius: 50%;
215
+ background: var(--hub-success);
216
+ }
217
+
218
+ .statusDot.statusPending {
219
+ background: var(--hub-warning);
220
+ }
221
+
222
+ /* 非 dsh 插件(如官方示例项目):宿主不加载,灰色点表示「未启用」而非「待重启」 */
223
+ .statusDot.statusInactive {
224
+ background: var(--hub-text-tertiary);
225
+ }
226
+
227
+ /* 主信息:名称 + 已装版本 + 有更新徽标 */
228
+ .rowMain {
229
+ display: flex;
230
+ align-items: baseline;
231
+ gap: 6px;
232
+ min-width: 0;
233
+ flex: 1 1 auto;
234
+ }
235
+
236
+ .rowTitle {
237
+ font-size: 12px;
238
+ line-height: 18px;
239
+ font-weight: 600;
240
+ color: var(--hub-text-primary);
241
+ min-width: 0;
242
+ white-space: nowrap;
243
+ overflow: hidden;
244
+ text-overflow: ellipsis;
245
+ }
246
+
247
+ /* 已装版本:等宽弱化小字 */
248
+ .versionBadge {
249
+ flex-shrink: 0;
250
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
251
+ font-size: 11px;
252
+ line-height: 16px;
253
+ color: var(--hub-text-tertiary);
254
+ background: var(--hub-bg-hover);
255
+ border-radius: 4px;
256
+ padding: 0 5px;
257
+ }
258
+
259
+ /* 有更新徽标:品牌色实底小圆标 */
260
+ .updateBadge {
261
+ flex-shrink: 0;
262
+ font-size: 11px;
263
+ line-height: 16px;
264
+ font-weight: 500;
265
+ color: var(--hub-brand);
266
+ background: var(--hub-brand-tint-strong);
267
+ border-radius: 4px;
268
+ padding: 0 5px;
269
+ }
270
+
271
+ /* 来源标签列(行尾、操作按钮前):固定宽度等宽,所有条目标签在同一列右对齐(整齐不乱)。
272
+ 「插件中心」= 目录插件(品牌蓝描边),「手动安装」= 命令行安装(琥珀描边)。
273
+ 两词等宽(各 4 字),宽度固定后标签列严格对齐 */
274
+ .rowSourceTag {
275
+ flex-shrink: 0;
276
+ width: 60px;
277
+ box-sizing: border-box;
278
+ text-align: center;
279
+ font-size: 11px;
280
+ line-height: 16px;
281
+ font-weight: 500;
282
+ border-radius: 4px;
283
+ padding: 0 4px;
284
+ white-space: nowrap;
285
+ overflow: hidden;
286
+ text-overflow: ellipsis;
287
+ }
288
+
289
+ .rowSourceTagHub {
290
+ color: var(--hub-brand);
291
+ background: var(--hub-brand-tint);
292
+ border: 1px solid var(--hub-brand-border-soft);
293
+ }
294
+
295
+ .rowSourceTagManual {
296
+ color: var(--hub-warning);
297
+ background: var(--hub-warning-tint);
298
+ border: 1px solid var(--hub-warning-border);
299
+ }
300
+
301
+ /* 示例项目徽标(非 dsh 插件,如官方示例仓库):品牌蓝描边 + 浅蓝底,
302
+ 与「有更新」实底区分,表达「这是示例项目,宿主不加载」 */
303
+ .exampleBadge {
304
+ flex-shrink: 0;
305
+ font-size: 11px;
306
+ line-height: 16px;
307
+ font-weight: 500;
308
+ color: var(--hub-brand);
309
+ background: var(--hub-brand-tint);
310
+ border: 1px solid var(--hub-brand-border-soft);
311
+ border-radius: 4px;
312
+ padding: 0 5px;
313
+ }
314
+
315
+ /* 示例项目徽标行:独立一行,左缩进对齐名称(8 状态点 + 8 间隔 = 16px),
316
+ 标签可完整显示不换行,不受名称行宽度挤压 */
317
+ .exampleRow {
318
+ display: flex;
319
+ align-items: center;
320
+ padding-left: 16px;
321
+ min-width: 0;
322
+ }
323
+
324
+ /* 第二行元信息:仓库 · 分类 —— 独立成行,左缩进与名称对齐,不再挤压名称 */
325
+ .rowMeta {
326
+ display: flex;
327
+ align-items: center;
328
+ gap: 8px;
329
+ padding-left: 16px;
330
+ min-width: 0;
331
+ overflow: hidden;
332
+ }
333
+
334
+ .rowRepo {
335
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
336
+ font-size: 11px;
337
+ line-height: 16px;
338
+ color: var(--hub-text-tertiary);
339
+ min-width: 0;
340
+ white-space: nowrap;
341
+ overflow: hidden;
342
+ text-overflow: ellipsis;
343
+ }
344
+
345
+ .rowCategory {
346
+ flex-shrink: 0;
347
+ font-size: 11px;
348
+ line-height: 16px;
349
+ color: var(--hub-text-tertiary);
350
+ background: var(--hub-bg-hover);
351
+ border-radius: 4px;
352
+ padding: 0 6px;
353
+ }
354
+
355
+ /* —— 行操作:详情 / 更新 / 卸载 —— */
356
+
357
+ .rowActions {
358
+ display: flex;
359
+ align-items: center;
360
+ gap: 4px;
361
+ flex-shrink: 0;
362
+ }
363
+
364
+ /* 详情:中性细边框按钮,与卸载同款描边样式(视觉成组) */
365
+ .rowDetail {
366
+ font-family: inherit;
367
+ font-size: 12px;
368
+ line-height: 18px;
369
+ color: var(--hub-text-secondary);
370
+ background: transparent;
371
+ border: 1px solid var(--hub-border-2);
372
+ border-radius: 6px;
373
+ padding: 2px 10px;
374
+ cursor: pointer;
375
+ user-select: none;
376
+ transition: color 120ms ease, background 120ms ease, border-color 120ms ease;
377
+ }
378
+
379
+ .rowDetail:hover {
380
+ color: var(--hub-text-primary);
381
+ border-color: var(--hub-border-ghost);
382
+ background: var(--hub-bg-hover);
383
+ }
384
+
385
+ /* 重启:琥珀警告色描边小按钮,仅在「待重启」时出现 —— 装完未挂载,点了宿主重启后即生效。
386
+ 重启属于「提醒/待办」语义,用警告色而非品牌蓝,与状态点的琥珀点呼应 */
387
+ .rowRestart {
388
+ font-family: inherit;
389
+ font-size: 12px;
390
+ line-height: 18px;
391
+ font-weight: 500;
392
+ color: var(--hub-warning);
393
+ background: var(--hub-warning-tint);
394
+ border: 1px solid var(--hub-warning-border);
395
+ border-radius: 6px;
396
+ padding: 2px 10px;
397
+ cursor: pointer;
398
+ user-select: none;
399
+ transition: color 120ms ease, background 120ms ease, border-color 120ms ease;
400
+ }
401
+
402
+ .rowRestart:hover {
403
+ color: #fff;
404
+ background: var(--hub-warning);
405
+ border-color: var(--hub-warning);
406
+ }
407
+
408
+ /* 更新:品牌色实底小按钮,仅在「有更新」时出现 */
409
+ .rowUpdate {
410
+ font-family: inherit;
411
+ font-size: 12px;
412
+ line-height: 18px;
413
+ font-weight: 500;
414
+ color: #fff;
415
+ background: var(--hub-brand);
416
+ border: none;
417
+ border-radius: 6px;
418
+ padding: 3px 10px;
419
+ cursor: pointer;
420
+ user-select: none;
421
+ transition: background 120ms ease;
422
+ }
423
+
424
+ .rowUpdate:hover {
425
+ background: var(--hub-brand-hover);
426
+ }
427
+
428
+ /* 卸载:危险色描边小按钮 */
429
+ .rowUninstall {
430
+ font-family: inherit;
431
+ font-size: 12px;
432
+ line-height: 18px;
433
+ color: var(--hub-danger);
434
+ background: transparent;
435
+ border: 1px solid var(--hub-danger-border-soft);
436
+ border-radius: 6px;
437
+ padding: 2px 10px;
438
+ cursor: pointer;
439
+ user-select: none;
440
+ transition: color 120ms ease, background 120ms ease, border-color 120ms ease;
441
+ }
442
+
443
+ .rowUninstall:hover {
444
+ color: #fff;
445
+ background: var(--hub-danger);
446
+ border-color: var(--hub-danger);
447
+ }