dsh-plugin 1.6.12 → 2.0.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/README.en.md +1 -1
- package/README.md +1 -1
- package/client/client.js +615 -741
- package/client/client.js.map +1 -1
- package/lib/http/catalog.js +2 -3
- package/lib/http/diagnostics.js +6 -7
- package/lib/http/install.js +47 -213
- package/lib/http/logs.js +1 -2
- package/lib/http/npm-release-age.js +42 -0
- package/lib/http/routes.js +8 -7
- package/lib/http/settings.js +2 -2
- package/lib/http/shared.js +2 -2
- package/lib/http/task.js +1 -2
- package/lib/index.js +5 -6
- package/lib/services/install/install-types.js +4 -4
- package/lib/services/install/install.js +1 -1
- package/lib/services/install/npm-check.js +3 -2
- package/lib/services/install/npm-resolve.js +9 -96
- package/lib/services/install/package-entry.js +6 -11
- package/lib/services/install/registry-check.js +2 -2
- package/lib/services/install/release-target.js +8 -9
- package/lib/services/install/task-queue.js +40 -48
- package/lib/services/npm-release-age.js +119 -0
- package/lib/services/probe.js +4 -4
- package/lib/services/profile/installed-versions.js +15 -21
- package/lib/services/profile/profile.js +16 -44
- package/lib/services/profile/progress.js +8 -11
- package/lib/services/settings.js +2 -5
- package/lib/types/http/catalog.d.ts +1 -1
- package/lib/types/http/install.d.ts +2 -2
- package/lib/types/http/npm-release-age.d.ts +2 -0
- package/lib/types/http/routes.d.ts +10 -12
- package/lib/types/http/shared.d.ts +5 -6
- package/lib/types/index.d.ts +7 -8
- package/lib/types/services/install/install-types.d.ts +12 -13
- package/lib/types/services/install/install.d.ts +1 -1
- package/lib/types/services/install/npm-resolve.d.ts +4 -16
- package/lib/types/services/install/package-entry.d.ts +6 -11
- package/lib/types/services/install/release-target.d.ts +7 -8
- package/lib/types/services/install/task-queue.d.ts +25 -30
- package/lib/types/services/npm-release-age.d.ts +15 -0
- package/lib/types/services/probe.d.ts +4 -4
- package/lib/types/services/profile/installed-versions.d.ts +5 -7
- package/lib/types/services/profile/profile.d.ts +14 -18
- package/lib/types/services/profile/progress.d.ts +7 -10
- package/lib/types/services/settings.d.ts +0 -4
- package/package.json +1 -1
- package/src/client/components/PluginHubSection.tsx +17 -22
- package/src/client/components/catalog/CatalogList.tsx +2 -4
- package/src/client/components/catalog/PluginCard.tsx +2 -3
- package/src/client/components/layout/CatalogControls.tsx +5 -6
- package/src/client/components/layout/CatalogHeader.tsx +1 -2
- package/src/client/components/layout/CategoryTabs.tsx +2 -3
- package/src/client/components/layout/SectionTabs.tsx +3 -5
- package/src/client/components/modals/ConfirmDialog.tsx +4 -6
- package/src/client/components/modals/InstalledDetailModal.tsx +3 -6
- package/src/client/components/modals/LogsModal.tsx +7 -12
- package/src/client/components/modals/NotificationsModal.tsx +35 -16
- package/src/client/components/modals/ProgressView.tsx +3 -4
- package/src/client/components/modals/modals.tsx +35 -15
- package/src/client/components/ui/Dropdown.tsx +4 -6
- package/src/client/components/ui/Toggle.tsx +2 -3
- package/src/client/components/ui/icons.tsx +1 -2
- package/src/client/components/views/CustomInstallView.tsx +20 -178
- package/src/client/components/views/DiagnosticsView.tsx +3 -6
- package/src/client/components/views/InstalledView.tsx +6 -11
- package/src/client/components/views/MarketView.tsx +2 -4
- package/src/client/components/views/SettingsView.tsx +9 -32
- package/src/client/css-modules.d.ts +1 -1
- package/src/client/data/catalog.ts +2 -3
- package/src/client/data/host.ts +26 -3
- package/src/client/data/hub.ts +2 -3
- package/src/client/hooks/useCatalog.ts +79 -24
- package/src/client/hooks/useIncrementalList.ts +3 -5
- package/src/client/hooks/useLanguage.ts +4 -6
- package/src/client/hooks/useSettings.ts +4 -11
- package/src/client/hooks/useTaskQueue.ts +36 -42
- package/src/client/index.tsx +2 -3
- package/src/client/locales.ts +75 -114
- package/src/client/logic/constants.ts +1 -2
- package/src/client/logic/ensure-plugin-css.ts +8 -12
- package/src/client/logic/failures.ts +16 -18
- package/src/client/logic/format.ts +1 -1
- package/src/client/logic/install-command.ts +14 -35
- package/src/client/logic/installed.ts +15 -10
- package/src/client/logic/normalize.ts +3 -3
- package/src/client/logic/release-age.ts +19 -0
- package/src/client/logic/urls.ts +20 -20
- package/src/client/styles/CustomInstallView.module.css +3 -30
- package/src/client/styles/DiagnosticsView.module.css +3 -5
- package/src/client/styles/Dropdown.module.css +2 -4
- package/src/client/styles/Header.module.css +6 -7
- package/src/client/styles/InstalledView.module.css +4 -7
- package/src/client/styles/List.module.css +4 -5
- package/src/client/styles/LogsView.module.css +2 -3
- package/src/client/styles/Modal.module.css +4 -5
- package/src/client/styles/SectionTabs.module.css +2 -4
- package/src/client/styles/SettingsView.module.css +3 -5
- package/src/client/styles/Toggle.module.css +2 -4
- package/src/client/styles/tokens.module.css +14 -18
- package/src/client/types.ts +11 -11
- package/src/server/http/catalog.ts +2 -3
- package/src/server/http/diagnostics.ts +6 -7
- package/src/server/http/install.ts +47 -208
- package/src/server/http/logs.ts +1 -2
- package/src/server/http/npm-release-age.ts +42 -0
- package/src/server/http/routes.ts +12 -12
- package/src/server/http/settings.ts +2 -2
- package/src/server/http/shared.ts +5 -6
- package/src/server/http/task.ts +1 -2
- package/src/server/index.ts +7 -8
- package/src/server/services/install/install-types.ts +12 -13
- package/src/server/services/install/install.ts +0 -1
- package/src/server/services/install/npm-check.ts +3 -2
- package/src/server/services/install/npm-resolve.ts +9 -99
- package/src/server/services/install/package-entry.ts +6 -11
- package/src/server/services/install/registry-check.ts +2 -2
- package/src/server/services/install/release-target.ts +8 -9
- package/src/server/services/install/task-queue.ts +47 -54
- package/src/server/services/npm-release-age.ts +107 -0
- package/src/server/services/probe.ts +4 -4
- package/src/server/services/profile/installed-versions.ts +16 -22
- package/src/server/services/profile/profile.ts +16 -42
- package/src/server/services/profile/progress.ts +8 -11
- package/src/server/services/settings.ts +2 -9
- package/lib/services/install/preflight.js +0 -178
- package/lib/types/services/install/preflight.d.ts +0 -23
- package/src/server/services/install/preflight.ts +0 -184
|
@@ -3,15 +3,11 @@
|
|
|
3
3
|
* Website: https://dsh-plugin.org
|
|
4
4
|
* GitHub: https://github.com/dshplugin/dsh-plugin-hub
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
* remove task — successes and failures alike.
|
|
6
|
+
* 通知中心弹窗:每个已结算的安装/卸载任务(成功与失败)的持久化清单。
|
|
8
7
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* success, red cross for failure) with white glyph and message text;
|
|
13
|
-
* failures keep their copy / fix / file-an-issue actions. Opened from the
|
|
14
|
-
* header entry button (after the Settings tab).
|
|
8
|
+
* 记录在结算时写入 localStorage(见 logic/failures.ts),即使弹窗被关闭或用户离开,
|
|
9
|
+
* 结果也不会丢失。每条带圆形状态徽标(成功绿勾、失败红叉,白色图形)与消息文本;
|
|
10
|
+
* 失败项保留复制/修复/提 Issue 动作。由头部入口按钮(「设置」tab 之后)打开。
|
|
15
11
|
*/
|
|
16
12
|
import { createElement as h, useState } from 'react'
|
|
17
13
|
import type { MouseEvent } from 'react'
|
|
@@ -336,7 +332,7 @@ export function NotificationsModal({ records, tasks, pendingRestarts, t, env, on
|
|
|
336
332
|
}
|
|
337
333
|
if (kind === 'registryVersionStale') {
|
|
338
334
|
// 要装的版本在「实际执行安装的那个源」里查不到(ERR_PNPM_NO_MATCHING_VERSION / ETARGET):
|
|
339
|
-
// 常见于镜像滞后 ——
|
|
335
|
+
// 常见于镜像滞后 —— 实际安装沿用本机 ~/.npmrc 的源;
|
|
340
336
|
// 也可能只是版本号写错。与插件作者无关 —— 给对齐源 / 显式走官方源 / 离线包指引,不引导提 Issue
|
|
341
337
|
return h('div', { className: styles.failPrepareHint }, t('failRegistryVersionStaleHint'))
|
|
342
338
|
}
|
|
@@ -367,7 +363,7 @@ export function NotificationsModal({ records, tasks, pendingRestarts, t, env, on
|
|
|
367
363
|
return h('div', { className: styles.failPrepareHint }, t('failOriginRejectedHint', { profile: env?.profile ?? '<profile>' }))
|
|
368
364
|
}
|
|
369
365
|
if (kind === 'network') {
|
|
370
|
-
//
|
|
366
|
+
// 安装前网络探测 / 安装日志里的连接失败([network] / git fetch 超时 / DNS / TLS):
|
|
371
367
|
// 是本机网络不通或代理有问题,不是插件问题 —— 提示检查网络 + 直达「系统诊断」,
|
|
372
368
|
// 不引导提 Issue
|
|
373
369
|
return h('div', null, [
|
|
@@ -384,7 +380,7 @@ export function NotificationsModal({ records, tasks, pendingRestarts, t, env, on
|
|
|
384
380
|
])
|
|
385
381
|
}
|
|
386
382
|
if (kind === 'pluginPrepare') {
|
|
387
|
-
// [packaging]
|
|
383
|
+
// [packaging](装后校验拦截:分发缺产物)与
|
|
388
384
|
// prepare 构建脚本实际执行失败:唯一确有证据的插件打包分发问题 —— 引导到插件仓库提 Issue
|
|
389
385
|
return h('div', null, [
|
|
390
386
|
h('div', { className: styles.failPrepareHint }, /\[packaging\]/i.test(r.message) ? t('failPackagingHint') : t('failPrepareHint')),
|
|
@@ -395,7 +391,7 @@ export function NotificationsModal({ records, tasks, pendingRestarts, t, env, on
|
|
|
395
391
|
rel: 'noopener noreferrer',
|
|
396
392
|
title: t('failIssueHint'),
|
|
397
393
|
}, t('failIssueBig')) : null,
|
|
398
|
-
// 次要入口:这条分支的判据([packaging]
|
|
394
|
+
// 次要入口:这条分支的判据([packaging] 装后校验、prepare 摘要)是插件中心
|
|
399
395
|
// 自己产出的,不是 pnpm 原文 —— 判错时用户会被送去作者仓库、我们永远收不到反馈
|
|
400
396
|
r.repo && shownRepo ? h('div', null, [
|
|
401
397
|
h('div', { className: styles.failPrepareHint }, t('failMisjudgeHint')),
|
|
@@ -433,11 +429,34 @@ export function NotificationsModal({ records, tasks, pendingRestarts, t, env, on
|
|
|
433
429
|
h('div', { className: styles.failPrepareHint }, t('failHostCliSpawnHint')),
|
|
434
430
|
])
|
|
435
431
|
}
|
|
436
|
-
|
|
432
|
+
if (kind === 'pnpmIgnoredBuild') {
|
|
433
|
+
// 插件的依赖需要在用户机器上编译原生模块,pnpm 默认拦住;我们已自动放行
|
|
434
|
+
// allowBuilds 并重试过一次,仍失败说明任何用户都装不上 —— 属作者侧(发预构建产物 / 走 release 包)
|
|
435
|
+
return h('div', null, [
|
|
436
|
+
h('div', { className: styles.failPrepareHint }, t('failIgnoredBuild')),
|
|
437
|
+
r.repo && shownRepo ? h('a', {
|
|
438
|
+
className: styles.failBigIssue,
|
|
439
|
+
href: pluginIssueUrl(shownRepo, r.message, env, r.command, r.attempts, action),
|
|
440
|
+
target: '_blank',
|
|
441
|
+
rel: 'noopener noreferrer',
|
|
442
|
+
title: t('failIssueHint'),
|
|
443
|
+
}, t('failIssueBig')) : null,
|
|
444
|
+
// 这条判定里也有我们的动作(自动放行过 allowBuilds),判错时得留一条回到插件中心的路
|
|
445
|
+
r.repo && shownRepo ? h('div', null, [
|
|
446
|
+
h('div', { className: styles.failPrepareHint }, t('failMisjudgeHint')),
|
|
447
|
+
h('a', {
|
|
448
|
+
className: styles.failMisjudgeLink,
|
|
449
|
+
href: hubIssueUrl(shownRepo, r.message, env, r.command, r.attempts, 'misjudge', action),
|
|
450
|
+
target: '_blank',
|
|
451
|
+
rel: 'noopener noreferrer',
|
|
452
|
+
title: t('failHubIssueHint'),
|
|
453
|
+
}, t('failMisjudgeLink')),
|
|
454
|
+
]) : null,
|
|
455
|
+
])
|
|
456
|
+
}
|
|
457
|
+
// 未知失败:不归到插件作者,改为引导到插件中心仓库排查
|
|
437
458
|
return h('div', null, [
|
|
438
|
-
|
|
439
|
-
? h('div', { className: styles.failPrepareHint }, t('failIgnoredBuild'))
|
|
440
|
-
: h('div', { className: styles.failPrepareHint }, t('failUnclassifiedHint', { action: actionWord })),
|
|
459
|
+
h('div', { className: styles.failPrepareHint }, t('failUnclassifiedHint', { action: actionWord })),
|
|
441
460
|
h('a', {
|
|
442
461
|
className: styles.failBigIssue,
|
|
443
462
|
href: hubIssueUrl(shownRepo || r.repo, r.message, env, r.command, r.attempts, 'triage', action),
|
|
@@ -3,10 +3,9 @@
|
|
|
3
3
|
* Website: https://dsh-plugin.org
|
|
4
4
|
* GitHub: https://github.com/dshplugin/dsh-plugin-hub
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* empty for most of the run; failures surface their full output in ErrorModal.
|
|
6
|
+
* 安装与卸载弹窗共用的内联进度块:真实百分比进度条(服务端估算、客户端轮询)。
|
|
7
|
+
* 不做实时终端窗口 —— pnpm 在 git clone/构建期间几乎无输出,日志面板大部分时间是空的;
|
|
8
|
+
* 失败时完整输出在 ErrorModal 里展示。
|
|
10
9
|
*/
|
|
11
10
|
import { createElement as h } from 'react'
|
|
12
11
|
import styles from '../../styles/Modal.module.css'
|
|
@@ -3,11 +3,9 @@
|
|
|
3
3
|
* Website: https://dsh-plugin.org
|
|
4
4
|
* GitHub: https://github.com/dshplugin/dsh-plugin-hub
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* the common case (the page refreshes on close so the change applies), or a
|
|
10
|
-
* single "restart now" for the rare changes a refresh cannot pick up.
|
|
6
|
+
* 插件中心的弹窗层:安装确认弹窗、卸载弹窗与全局 Toast。两个弹窗在变更执行期间
|
|
7
|
+
* 自我锁定,随后切到只有一个动作的结果视图 —— 常见情况给「完成」(关闭时刷新页面即生效),
|
|
8
|
+
* 刷新解决不了的少数情况才给单个「立即重启」。
|
|
11
9
|
*/
|
|
12
10
|
import { createElement as h } from 'react'
|
|
13
11
|
import type { MouseEvent } from 'react'
|
|
@@ -343,7 +341,7 @@ export function ErrorModal({ message, repo, action, command, attempts, t, env, o
|
|
|
343
341
|
repo: string | null
|
|
344
342
|
action: FailureAction
|
|
345
343
|
command?: string
|
|
346
|
-
/**
|
|
344
|
+
/** 实际执行/重试过的安装命令:issue 预填一并贴给作者 */
|
|
347
345
|
attempts?: string[]
|
|
348
346
|
t: Translate
|
|
349
347
|
env: EnvInfo | null
|
|
@@ -352,7 +350,7 @@ export function ErrorModal({ message, repo, action, command, attempts, t, env, o
|
|
|
352
350
|
/** 网络不通时的「去系统诊断」直达按钮:跳到设置 → 系统诊断跑连通性检测(由宿主提供) */
|
|
353
351
|
onRunDiagnostics?: () => void
|
|
354
352
|
}) {
|
|
355
|
-
//
|
|
353
|
+
// 失败归类:装不上 = 插件分发/依赖的问题,一律引导提 Issue
|
|
356
354
|
const failureKind = classifyFailure(message)
|
|
357
355
|
// 动作词:标题、徽标与提示里的 {action} 统一取这一个词
|
|
358
356
|
const actionWord = t(action === 'uninstall' ? 'actionUninstall' : action === 'update' ? 'actionUpdate' : 'actionInstall')
|
|
@@ -469,7 +467,7 @@ export function ErrorModal({ message, repo, action, command, attempts, t, env, o
|
|
|
469
467
|
])
|
|
470
468
|
: failureKind === 'registryVersionStale'
|
|
471
469
|
// 要装的版本在「实际执行安装的那个源」里查不到(ERR_PNPM_NO_MATCHING_VERSION / ETARGET):
|
|
472
|
-
// 常见于镜像滞后 ——
|
|
470
|
+
// 常见于镜像滞后 —— 实际安装沿用本机 ~/.npmrc 的源,
|
|
473
471
|
// 官方源刚发布而镜像还没同步;也可能只是版本号写错。与插件作者无关 ——
|
|
474
472
|
// 给「对齐源 / 那一次显式走官方源 / 用离线包」的指引,不引导提 Issue
|
|
475
473
|
? h('div', { className: styles.failPrepareHint }, t('failRegistryVersionStaleHint'))
|
|
@@ -492,7 +490,7 @@ export function ErrorModal({ message, repo, action, command, attempts, t, env, o
|
|
|
492
490
|
// 安装流程,与插件无关 —— 引导从本机地址重新打开市场,不引导提 Issue
|
|
493
491
|
? h('div', { className: styles.failPrepareHint }, t('failOriginRejectedHint', { profile: env?.profile ?? '<profile>' }))
|
|
494
492
|
: failureKind === 'network'
|
|
495
|
-
//
|
|
493
|
+
// 安装前网络探测 / 安装日志里的连接失败([network] / git fetch 超时 / DNS / TLS):
|
|
496
494
|
// 是本机网络不通或代理有问题,不是插件问题 —— 提示检查网络 + 直达「系统诊断」,
|
|
497
495
|
// 不引导提 Issue
|
|
498
496
|
? h('div', null, [
|
|
@@ -509,7 +507,7 @@ export function ErrorModal({ message, repo, action, command, attempts, t, env, o
|
|
|
509
507
|
])
|
|
510
508
|
: failureKind === 'pluginPrepare'
|
|
511
509
|
? h('div', null, [
|
|
512
|
-
// [packaging]
|
|
510
|
+
// [packaging](装后校验拦截:分发缺产物)与
|
|
513
511
|
// prepare 构建脚本实际执行失败:唯一确有证据的插件打包分发问题 —— 引导到插件仓库提 Issue
|
|
514
512
|
h('div', { className: styles.failPrepareHint }, /\[packaging\]/i.test(message) ? t('failPackagingHint') : t('failPrepareHint')),
|
|
515
513
|
repo ? h('a', {
|
|
@@ -519,7 +517,7 @@ export function ErrorModal({ message, repo, action, command, attempts, t, env, o
|
|
|
519
517
|
rel: 'noopener noreferrer',
|
|
520
518
|
title: t('failIssueHint'),
|
|
521
519
|
}, t('failIssueBig')) : null,
|
|
522
|
-
// 次要入口:这条分支的判据([packaging]
|
|
520
|
+
// 次要入口:这条分支的判据([packaging] 装后校验、prepare 摘要)是插件中心
|
|
523
521
|
// 自己产出的,不是 pnpm 原文 —— 判错时用户会被送去作者仓库、我们永远收不到反馈,
|
|
524
522
|
// 故另给一条「可能是市场误判」的反馈通道(正文自我说明它来自哪条分支)
|
|
525
523
|
repo ? h('div', null, [
|
|
@@ -553,11 +551,33 @@ export function ErrorModal({ message, repo, action, command, attempts, t, env, o
|
|
|
553
551
|
// 宿主自身入口起不来:环境问题,给提示、不引导提 Issue
|
|
554
552
|
h('div', { className: styles.failPrepareHint }, t('failHostCliSpawnHint')),
|
|
555
553
|
])
|
|
554
|
+
: failureKind === 'pnpmIgnoredBuild'
|
|
555
|
+
// 该类型的插件依赖需要在用户机器上编译原生模块,pnpm 默认拦住;我们已自动放行
|
|
556
|
+
// allowBuilds 并重试过一次,仍失败说明任何用户都装不上 —— 属作者侧(发预构建产物 / 走 release 包)。
|
|
557
|
+
? h('div', null, [
|
|
558
|
+
h('div', { className: styles.failPrepareHint }, t('failIgnoredBuild')),
|
|
559
|
+
repo ? h('a', {
|
|
560
|
+
className: styles.failBigIssue,
|
|
561
|
+
href: pluginIssueUrl(repo, message, env, command, attempts, action),
|
|
562
|
+
target: '_blank',
|
|
563
|
+
rel: 'noopener noreferrer',
|
|
564
|
+
title: t('failIssueHint'),
|
|
565
|
+
}, t('failIssueBig')) : null,
|
|
566
|
+
// 这条判定里也有我们的动作(我们自动放行过 allowBuilds),判错时得留一条回到插件中心的路
|
|
567
|
+
repo ? h('div', null, [
|
|
568
|
+
h('div', { className: styles.failPrepareHint }, t('failMisjudgeHint')),
|
|
569
|
+
h('a', {
|
|
570
|
+
className: styles.failMisjudgeLink,
|
|
571
|
+
href: hubIssueUrl(repo, message, env, command, attempts, 'misjudge', action),
|
|
572
|
+
target: '_blank',
|
|
573
|
+
rel: 'noopener noreferrer',
|
|
574
|
+
title: t('failHubIssueHint'),
|
|
575
|
+
}, t('failMisjudgeLink')),
|
|
576
|
+
]) : null,
|
|
577
|
+
])
|
|
556
578
|
: h('div', null, [
|
|
557
|
-
//
|
|
558
|
-
|
|
559
|
-
? h('div', { className: styles.failPrepareHint }, t('failIgnoredBuild'))
|
|
560
|
-
: h('div', { className: styles.failPrepareHint }, t('failUnclassifiedHint', { action: actionWord })),
|
|
579
|
+
// 未分类(hub):不归到插件作者,改为引导到插件中心仓库分诊
|
|
580
|
+
h('div', { className: styles.failPrepareHint }, t('failUnclassifiedHint', { action: actionWord })),
|
|
561
581
|
h('a', {
|
|
562
582
|
className: styles.failBigIssue,
|
|
563
583
|
href: hubIssueUrl(repo, message, env, command, attempts, 'triage', action),
|
|
@@ -3,13 +3,11 @@
|
|
|
3
3
|
* Website: https://dsh-plugin.org
|
|
4
4
|
* GitHub: https://github.com/dshplugin/dsh-plugin-hub
|
|
5
5
|
*
|
|
6
|
-
*
|
|
6
|
+
* 按钮式下拉框(替代原生 <select>)。
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* outside or pressing Esc closes the panel; options get a hover fill and
|
|
12
|
-
* the active one a brand highlight, all driven by the --hub-* tokens.
|
|
8
|
+
* 触发器是紧凑的工具栏按钮 —— 细边框 + 淡灰填充 + 当前值 + 箭头 —— 与其它工具栏按钮
|
|
9
|
+
* 并排自然,只占一格横向空间。点击外部或按 Esc 关闭面板;选项有 hover 填充、
|
|
10
|
+
* 激活项为品牌高亮,全部由 --hub-* 变量驱动。
|
|
13
11
|
*/
|
|
14
12
|
import { createElement as h, useEffect, useRef, useState } from 'react'
|
|
15
13
|
import type { JSX } from 'react'
|
|
@@ -3,9 +3,8 @@
|
|
|
3
3
|
* Website: https://dsh-plugin.org
|
|
4
4
|
* GitHub: https://github.com/dshplugin/dsh-plugin-hub
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* by the --hub-* tokens (on = brand fill, off = neutral track).
|
|
6
|
+
* 设置行用的紧凑开关。基于原生 button,可键盘聚焦并通过 aria-checked 播报状态;
|
|
7
|
+
* 样式由 --hub-* 变量驱动(开 = 品牌填充,关 = 中性轨道)。
|
|
9
8
|
*/
|
|
10
9
|
import { createElement as h } from 'react'
|
|
11
10
|
import styles from '../../styles/Toggle.module.css'
|
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
* Website: https://dsh-plugin.org
|
|
4
4
|
* GitHub: https://github.com/dshplugin/dsh-plugin-hub
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
* they adapt to themes and disabled states automatically.
|
|
6
|
+
* 客户端各处使用的内联 SVG 图标。图标继承 currentColor,自动适配主题与禁用态。
|
|
8
7
|
*/
|
|
9
8
|
import { createElement as h } from 'react'
|
|
10
9
|
import headerStyles from '../../styles/Header.module.css'
|
|
@@ -3,18 +3,12 @@
|
|
|
3
3
|
* Website: https://dsh-plugin.org
|
|
4
4
|
* GitHub: https://github.com/dshplugin/dsh-plugin-hub
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* npm/pnpm install command, including the official global `npm install -g`),
|
|
10
|
-
* GitHub source and the official `dsh plugin ... add <target>` command —
|
|
11
|
-
* each with a placeholder example above the input and its own format check.
|
|
12
|
-
* The cards stay separate so a user never has to guess which format goes
|
|
13
|
-
* where. Finished installs surface in the Installed view, marked as custom.
|
|
6
|
+
* 自定义安装视图:独立一级板块,用于手动安装目录之外的 npm 包。安装卡片接受裸包名或完整的
|
|
7
|
+
* npm/pnpm 安装命令(含官方全局 `npm install -g`),输入框上方带示例并自带格式校验。
|
|
8
|
+
* 装完的插件出现在「已安装」视图,标记为自定义安装。
|
|
14
9
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* via onInstallCustom.
|
|
10
|
+
* 纯展示组件:只持有本地输入/错误状态;真正的安装(custom 源,受安全开关控制)经
|
|
11
|
+
* onInstallCustom 上抛。
|
|
18
12
|
*/
|
|
19
13
|
import { createElement as h, useState } from 'react'
|
|
20
14
|
import type { FormEvent, MouseEvent } from 'react'
|
|
@@ -23,27 +17,14 @@ import modal from '../../styles/Modal.module.css'
|
|
|
23
17
|
import type { InstallChannel, Translate } from '../../types.ts'
|
|
24
18
|
import { CloseIcon, HelpIcon } from '../ui/icons.tsx'
|
|
25
19
|
|
|
26
|
-
/* ——
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* git 框只认 GitHub 地址,命令框只认官方 `dsh plugin --profile <name> <add|update> <target>`
|
|
30
|
-
* (--profile 必填,无 -p 简写;update = 更新已安装目标到最新)。
|
|
20
|
+
/* —— 自定义安装输入校验 ——
|
|
21
|
+
* 提交前校验「输入是否符合格式」,识别不了的输入不发请求,就地提示推荐格式。
|
|
22
|
+
* npm 框认裸包名或完整 npm/pnpm 安装命令(含全局 `npm install -g`)。
|
|
31
23
|
* 这里只做格式校验,真正归一化/安装由服务端完成。 */
|
|
32
24
|
|
|
33
|
-
/** GitHub 地址(带前缀形态):github:、https://github.com/、git+https://github.com/、git@github.com: */
|
|
34
|
-
const GITHUB_URL_RE = /^(?:github:|https?:\/\/github\.com\/|git\+https?:\/\/github\.com\/|git@github\.com:)([A-Za-z0-9._-]+)\/([A-Za-z0-9._-]+?)(?:\.git)?$/
|
|
35
|
-
/** 裸 owner/repo(npm 包名只有 @scope/pkg 才带斜杠,无 @ 前缀的 a/b 一定是 GitHub 仓库) */
|
|
36
|
-
const GITHUB_BARE_RE = /^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/
|
|
37
25
|
/** npm 包名(含 @scope/pkg) */
|
|
38
26
|
const NPM_PACKAGE_RE = /^(?:@[a-z0-9._-]+\/)?[a-z0-9._-]+$/
|
|
39
27
|
|
|
40
|
-
/** DSH 命令行前缀(官方唯一形式 `dsh plugin --profile <name> <add|update> <target>`)—— 提取其中的动作与目标。
|
|
41
|
-
* 官方 CLI(apps/cli/src/args.ts,commander):--profile 为必填长选项(无 -p 简写,省略即报错),
|
|
42
|
-
* 支持 `--profile=<name>` 等号形式;命令大小写不敏感。update = 已安装目标覆盖更新到最新;
|
|
43
|
-
* remove 不走这里(卸载由「已安装」列表的卸载按钮执行,命令框不认卸载动词)。
|
|
44
|
-
* 与 server 端 installTargetOf 口径一致。 */
|
|
45
|
-
const DSH_PLUGIN_CMD_RE = /^dsh\s+plugin\s+--profile(?:\s+|=)\S+\s+(add|update)\s+(.+)$/i
|
|
46
|
-
|
|
47
28
|
/** npm 全局安装命令(官方 README 格式,如 `npm install -g @deepseek-ai/dsh @deepseek-harness-tui/dsh-tui`)——
|
|
48
29
|
* 提取 -g 之后的包列表;任一包名非法返回 null(交给格式错误提示)。与 server 端 globalNpmPackagesOf 口径一致。 */
|
|
49
30
|
const NPM_GLOBAL_CMD_RE = /^npm\s+(?:install|i)\s+(?:-g|--global)\s+(.+)$/i
|
|
@@ -52,14 +33,6 @@ const NPM_GLOBAL_CMD_RE = /^npm\s+(?:install|i)\s+(?:-g|--global)\s+(.+)$/i
|
|
|
52
33
|
* 提取命令后的包列表;包列表在 splitPackages 里逐个校验。 */
|
|
53
34
|
const NPM_INSTALL_CMD_RE = /^(?:npm\s+(?:install|i)|pnpm\s+(?:add|i|install))\s+(.+)$/i
|
|
54
35
|
|
|
55
|
-
/** 解析 DSH 命令:返回 { action, target };非官方命令返回 null(交给格式错误提示)。 */
|
|
56
|
-
function parseDshCommand(raw: string): { action: 'add' | 'update'; target: string } | null {
|
|
57
|
-
const input = raw.trim()
|
|
58
|
-
const match = DSH_PLUGIN_CMD_RE.exec(input)
|
|
59
|
-
if (match === null) return null
|
|
60
|
-
return { action: match[1].toLowerCase() as 'add' | 'update', target: match[2].trim() }
|
|
61
|
-
}
|
|
62
|
-
|
|
63
36
|
/** 空格分隔的包列表:逐个按 npm 包名语法校验 + 拒绝以 - 开头的 token(npm 会把 --xxx 当参数而非包名),
|
|
64
37
|
* 任一非法返回 null(交给格式错误提示)。与 server 端 globalNpmPackagesOf 同口径。 */
|
|
65
38
|
function splitPackages(raw: string): string[] | null {
|
|
@@ -96,46 +69,28 @@ function parseNpmInput(raw: string): NpmInputParse | null {
|
|
|
96
69
|
return null
|
|
97
70
|
}
|
|
98
71
|
|
|
99
|
-
|
|
100
|
-
const input = raw.trim()
|
|
101
|
-
return GITHUB_URL_RE.test(input) || GITHUB_BARE_RE.test(input)
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/** 卡片标题旁的「帮助」弹窗:三块卡片各自的标题与格式清单语言包 key。 */
|
|
72
|
+
/** 卡片标题旁的「帮助」弹窗:该卡片的标题与格式清单语言包 key。 */
|
|
105
73
|
const HELP_CARDS = {
|
|
106
74
|
npm: { titleKey: 'npmInstallLabel', contentKey: 'npmInstallHelp' },
|
|
107
|
-
git: { titleKey: 'gitInstallLabel', contentKey: 'gitInstallHelp' },
|
|
108
|
-
cmd: { titleKey: 'dshCmdLabel', contentKey: 'dshCmdHelp' },
|
|
109
75
|
} as const
|
|
110
76
|
|
|
111
77
|
type HelpTarget = keyof typeof HELP_CARDS
|
|
112
78
|
|
|
113
|
-
export function CustomInstallView({ t, onInstallCustom, enableNpm,
|
|
79
|
+
export function CustomInstallView({ t, onInstallCustom, enableNpm, onOpenSettings }: {
|
|
114
80
|
t: Translate
|
|
115
|
-
/** 命令行安装:输入 npm 包名 / npm、pnpm
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
* 命令(npm install -g),附带解析出的包列表(仍属 NPM 包入口)。 */
|
|
81
|
+
/** 命令行安装:输入 npm 包名 / npm、pnpm 安装命令即装(custom 源,受安全设置开关控制)。
|
|
82
|
+
* installChannel 必填 = 入口渠道(NPM 包卡片):服务端日志/报错提示据此溯源「从哪个入口发起」;
|
|
83
|
+
* opts.globalNpm 表示官方 npm 全局安装命令(npm install -g),附带解析出的包列表。 */
|
|
119
84
|
onInstallCustom: (raw: string, opts: { installChannel: InstallChannel; globalNpm?: string[] }) => void
|
|
120
|
-
/**
|
|
85
|
+
/** 安全信任开关:关掉后输入卡片禁用(不提交),并引导去设置打开 */
|
|
121
86
|
enableNpm: boolean
|
|
122
|
-
enableGit: boolean
|
|
123
|
-
enableDsh: boolean
|
|
124
87
|
/** 引导去「设置 → 安全信任」打开被关闭的通道开关 */
|
|
125
88
|
onOpenSettings: () => void
|
|
126
|
-
/** 当前 dsh profile 名(宿主 /env 快照):一键插入的更新命令用它拼 --profile */
|
|
127
|
-
profile: string
|
|
128
89
|
}) {
|
|
129
90
|
/** 自定义安装:NPM 包输入与格式错误('' = 无错误) */
|
|
130
91
|
const [npmQuery, setNpmQuery] = useState('')
|
|
131
92
|
const [npmError, setNpmError] = useState('')
|
|
132
|
-
/**
|
|
133
|
-
const [gitQuery, setGitQuery] = useState('')
|
|
134
|
-
const [gitError, setGitError] = useState('')
|
|
135
|
-
/** 自定义安装:DSH 命令输入与格式错误('' = 无错误) */
|
|
136
|
-
const [cmdQuery, setCmdQuery] = useState('')
|
|
137
|
-
const [cmdError, setCmdError] = useState('')
|
|
138
|
-
/** 卡片标题旁「帮助」弹窗:null = 未打开;'npm' | 'git' | 'cmd' = 打开对应卡片的格式清单 */
|
|
93
|
+
/** 卡片标题旁「帮助」弹窗:null = 未打开;'npm' = 打开该卡片的格式清单 */
|
|
139
94
|
const [helpFor, setHelpFor] = useState<HelpTarget | null>(null)
|
|
140
95
|
|
|
141
96
|
/** 通道关闭提示行:开关关掉后替代示例与输入行,引导去设置打开 */
|
|
@@ -148,9 +103,8 @@ export function CustomInstallView({ t, onInstallCustom, enableNpm, enableGit, en
|
|
|
148
103
|
}, t('goToSettings')),
|
|
149
104
|
)
|
|
150
105
|
|
|
151
|
-
//
|
|
152
|
-
// NPM 框认裸包名或完整 npm/pnpm 命令(含全局 npm install -g,走 globalNpm
|
|
153
|
-
// 命令框只认官方 dsh plugin 命令,提交时剥成裸目标发给服务端。
|
|
106
|
+
// 只做格式校验 —— 识别不了的输入不发请求,就地提示推荐写法。
|
|
107
|
+
// NPM 框认裸包名或完整 npm/pnpm 命令(含全局 npm install -g,走 globalNpm 通道)。
|
|
154
108
|
const submitNpm = () => {
|
|
155
109
|
const raw = npmQuery.trim()
|
|
156
110
|
if (!raw) return
|
|
@@ -164,36 +118,12 @@ export function CustomInstallView({ t, onInstallCustom, enableNpm, enableGit, en
|
|
|
164
118
|
setNpmQuery('')
|
|
165
119
|
setNpmError('')
|
|
166
120
|
}
|
|
167
|
-
const submitGit = () => {
|
|
168
|
-
const target = gitQuery.trim()
|
|
169
|
-
if (!target) return
|
|
170
|
-
if (!isGitHubInput(target)) { setGitError(t('gitInstallInvalid')); return }
|
|
171
|
-
onInstallCustom(target, { installChannel: 'git' })
|
|
172
|
-
setGitQuery('')
|
|
173
|
-
setGitError('')
|
|
174
|
-
}
|
|
175
|
-
const submitCmd = () => {
|
|
176
|
-
const raw = cmdQuery.trim()
|
|
177
|
-
if (!raw) return
|
|
178
|
-
const parsed = parseDshCommand(raw)
|
|
179
|
-
if (parsed === null) { setCmdError(t('dshCmdInvalid', { profile })); return }
|
|
180
|
-
// add / update 统一走安装流程:目标已安装时弹窗自动转「更新」覆盖重装(服务端 mode=update 放行),
|
|
181
|
-
// 未安装时即为普通安装 —— 粘贴 `dsh plugin --profile web update dsh-plugin` 这类命令即可更新到最新
|
|
182
|
-
onInstallCustom(parsed.target, { installChannel: 'dsh' })
|
|
183
|
-
setCmdQuery('')
|
|
184
|
-
setCmdError('')
|
|
185
|
-
}
|
|
186
|
-
/** 一键插入 DSH Plugin Hub 自身的更新命令:按当前 profile 拼官方命令填进输入框,用户确认后提交 */
|
|
187
|
-
const insertHubUpdate = () => {
|
|
188
|
-
setCmdQuery(`dsh plugin --profile ${profile} update dsh-plugin`)
|
|
189
|
-
setCmdError('')
|
|
190
|
-
}
|
|
191
121
|
|
|
192
122
|
return h('div', { className: styles.root },
|
|
193
123
|
// 页面说明:独立一级导航,交代「装完去哪看」
|
|
194
124
|
h('p', { className: styles.desc }, t('customViewDesc')),
|
|
195
|
-
//
|
|
196
|
-
//
|
|
125
|
+
// NPM 包卡片:label + 文本框上方示例 + 输入行 + 错误区;
|
|
126
|
+
// 安全信任开关关掉时,卡片禁用并显示「前往设置」提示行
|
|
197
127
|
h('div', { className: styles.installCards },
|
|
198
128
|
// NPM 包:输入包名或原生 npm/pnpm 安装命令即装(含官方全局 npm install -g)
|
|
199
129
|
h('div', { className: enableNpm ? styles.installCard : `${styles.installCard} ${styles.installCardDisabled}` },
|
|
@@ -239,94 +169,6 @@ export function CustomInstallView({ t, onInstallCustom, enableNpm, enableGit, en
|
|
|
239
169
|
npmError ? h('div', { className: styles.installError }, npmError) : null,
|
|
240
170
|
],
|
|
241
171
|
),
|
|
242
|
-
// GitHub 源码:输入仓库地址即装
|
|
243
|
-
h('div', { className: enableGit ? styles.installCard : `${styles.installCard} ${styles.installCardDisabled}` },
|
|
244
|
-
h('div', { className: styles.installCardHead },
|
|
245
|
-
h('span', { className: styles.installLabel }, t('gitInstallLabel')),
|
|
246
|
-
h('button', {
|
|
247
|
-
type: 'button',
|
|
248
|
-
className: styles.installHelpBtn,
|
|
249
|
-
title: t('installHelp'),
|
|
250
|
-
'aria-label': t('installHelp'),
|
|
251
|
-
onClick: () => setHelpFor('git'),
|
|
252
|
-
}, h(HelpIcon)),
|
|
253
|
-
),
|
|
254
|
-
!enableGit
|
|
255
|
-
? channelOffRow
|
|
256
|
-
: [
|
|
257
|
-
h('div', { className: styles.installExample }, t('gitInstallExample')),
|
|
258
|
-
h('div', { className: styles.installRow },
|
|
259
|
-
h('input', {
|
|
260
|
-
className: gitError ? `${styles.installInput} ${styles.installInputError}` : styles.installInput,
|
|
261
|
-
type: 'text',
|
|
262
|
-
placeholder: t('gitInstallPlaceholder'),
|
|
263
|
-
value: gitQuery,
|
|
264
|
-
spellCheck: false,
|
|
265
|
-
onInput: (e: FormEvent<HTMLInputElement>) => {
|
|
266
|
-
setGitQuery((e.target as HTMLInputElement).value)
|
|
267
|
-
setGitError('')
|
|
268
|
-
},
|
|
269
|
-
onKeyDown: (e: KeyboardEvent) => { if (e.key === 'Enter') submitGit() },
|
|
270
|
-
}),
|
|
271
|
-
h('button', {
|
|
272
|
-
className: styles.installBtn,
|
|
273
|
-
type: 'button',
|
|
274
|
-
disabled: gitQuery.trim() === '',
|
|
275
|
-
onClick: submitGit,
|
|
276
|
-
}, t('installCliBtn')),
|
|
277
|
-
),
|
|
278
|
-
gitError ? h('div', { className: styles.installError }, gitError) : null,
|
|
279
|
-
],
|
|
280
|
-
),
|
|
281
|
-
// 命令卡片:粘贴官方 dsh plugin 命令即装/即更新 —— 只认完整命令
|
|
282
|
-
// (--profile 必填,可含 = 等号,动词 add/update,剥成裸目标走安装流程)
|
|
283
|
-
h('div', { className: enableDsh ? styles.installCard : `${styles.installCard} ${styles.installCardDisabled}` },
|
|
284
|
-
h('div', { className: styles.installCardHead },
|
|
285
|
-
h('span', { className: styles.installLabel }, t('dshCmdLabel')),
|
|
286
|
-
h('button', {
|
|
287
|
-
type: 'button',
|
|
288
|
-
className: styles.installHelpBtn,
|
|
289
|
-
title: t('installHelp'),
|
|
290
|
-
'aria-label': t('installHelp'),
|
|
291
|
-
onClick: () => setHelpFor('cmd'),
|
|
292
|
-
}, h(HelpIcon)),
|
|
293
|
-
// 一键插入 Hub 自身的更新命令:按当前 profile 拼 `dsh plugin --profile <p> update dsh-plugin`
|
|
294
|
-
// 填入输入框(命令卡片被安全开关禁用时不显示,插入后也提交不了)
|
|
295
|
-
enableDsh
|
|
296
|
-
? h('button', {
|
|
297
|
-
type: 'button',
|
|
298
|
-
className: styles.installInsertBtn,
|
|
299
|
-
onClick: insertHubUpdate,
|
|
300
|
-
}, t('dshCmdInsertHubUpdate'))
|
|
301
|
-
: null,
|
|
302
|
-
),
|
|
303
|
-
!enableDsh
|
|
304
|
-
? channelOffRow
|
|
305
|
-
: [
|
|
306
|
-
h('div', { className: styles.installExample }, t('dshCmdExample', { profile })),
|
|
307
|
-
h('div', { className: styles.installRow },
|
|
308
|
-
h('input', {
|
|
309
|
-
className: cmdError ? `${styles.installInput} ${styles.installInputError}` : styles.installInput,
|
|
310
|
-
type: 'text',
|
|
311
|
-
placeholder: t('dshCmdPlaceholder'),
|
|
312
|
-
value: cmdQuery,
|
|
313
|
-
spellCheck: false,
|
|
314
|
-
onInput: (e: FormEvent<HTMLInputElement>) => {
|
|
315
|
-
setCmdQuery((e.target as HTMLInputElement).value)
|
|
316
|
-
setCmdError('')
|
|
317
|
-
},
|
|
318
|
-
onKeyDown: (e: KeyboardEvent) => { if (e.key === 'Enter') submitCmd() },
|
|
319
|
-
}),
|
|
320
|
-
h('button', {
|
|
321
|
-
className: styles.installBtn,
|
|
322
|
-
type: 'button',
|
|
323
|
-
disabled: cmdQuery.trim() === '',
|
|
324
|
-
onClick: submitCmd,
|
|
325
|
-
}, t('installCliBtn')),
|
|
326
|
-
),
|
|
327
|
-
cmdError ? h('div', { className: styles.installError }, cmdError) : null,
|
|
328
|
-
],
|
|
329
|
-
),
|
|
330
172
|
),
|
|
331
173
|
// 帮助弹窗:点卡片标题旁的问号打开 —— 宽窗(720px)把该通道支持的命令格式整行展示,防止折行
|
|
332
174
|
helpFor !== null
|
|
@@ -346,7 +188,7 @@ export function CustomInstallView({ t, onInstallCustom, enableNpm, enableGit, en
|
|
|
346
188
|
h('div', { className: modal.modalBody },
|
|
347
189
|
// 格式清单:一行一条;`命令\t说明` 的说明跟在命令后,纯说明行以 \t 开头
|
|
348
190
|
h('div', { className: styles.helpBody },
|
|
349
|
-
t(HELP_CARDS[helpFor].contentKey
|
|
191
|
+
t(HELP_CARDS[helpFor].contentKey).split('\n').map((line, i) => {
|
|
350
192
|
const [cmd, note] = line.split('\t')
|
|
351
193
|
return h('div', { key: i, className: styles.helpLine },
|
|
352
194
|
cmd !== '' ? h('span', { className: styles.helpCmd }, cmd) : null,
|
|
@@ -3,12 +3,9 @@
|
|
|
3
3
|
* Website: https://dsh-plugin.org
|
|
4
4
|
* GitHub: https://github.com/dshplugin/dsh-plugin-hub
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* automatically when the page opens; every row is a button that re-probes
|
|
10
|
-
* just that channel, so a click always yields visible feedback
|
|
11
|
-
* (row pulses → badge flips → latency refreshes).
|
|
6
|
+
* 系统诊断列表:每个连通性通道(npm registry / GitHub API / 目录站点)一行,各带实时
|
|
7
|
+
* 状态徽标 —— 200 OK 绿、不可达红、探测中显示「检测中…」。页面打开时自动跑一轮;
|
|
8
|
+
* 每行都是按钮,点击只重测该通道,因此点击总有可见反馈(行脉冲 → 徽标翻转 → 延迟刷新)。
|
|
12
9
|
*/
|
|
13
10
|
import { createElement as h, useCallback, useEffect, useRef, useState } from 'react'
|
|
14
11
|
import styles from '../../styles/DiagnosticsView.module.css'
|
|
@@ -3,18 +3,13 @@
|
|
|
3
3
|
* Website: https://dsh-plugin.org
|
|
4
4
|
* GitHub: https://github.com/dshplugin/dsh-plugin-hub
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* search box plus two segmented radio-button groups: source filter
|
|
12
|
-
* (all / catalog / custom) and sort (name / recently installed / stars / forks).
|
|
13
|
-
* Each row carries restart (when pending), update and uninstall actions
|
|
14
|
-
* and opens a detail modal with the full install metadata.
|
|
6
|
+
* 已安装视图:管理安装进当前 profile 的插件。沿用主流扩展管理器模式(VSCode 已安装 tab)——
|
|
7
|
+
* 可搜索、可排序的已安装清单,混排目录插件(完整元数据)与命令行自定义安装(目录之外,
|
|
8
|
+
* 只有运行时信息,打「自定义」徽标)。工具栏含搜索框与两组分段单选:来源筛选
|
|
9
|
+
* (全部/目录/自定义)与排序(名称/最近安装/Star/Fork)。每行带重启(待重启时)、更新与
|
|
10
|
+
* 卸载动作,并打开含完整安装元数据的详情弹窗。
|
|
15
11
|
*
|
|
16
|
-
*
|
|
17
|
-
* model arrives from the catalog hook and actions bubble up via callbacks.
|
|
12
|
+
* 纯展示组件:只持有本地搜索/排序状态;InstalledItem 模型来自目录 hook,动作经回调上抛。
|
|
18
13
|
*/
|
|
19
14
|
import { createElement as h, useMemo, useState } from 'react'
|
|
20
15
|
import type { FormEvent, MouseEvent, ReactNode } from 'react'
|
|
@@ -3,10 +3,8 @@
|
|
|
3
3
|
* Website: https://dsh-plugin.org
|
|
4
4
|
* GitHub: https://github.com/dshplugin/dsh-plugin-hub
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* it wires catalog state (query/sort/filter/category) to the small
|
|
9
|
-
* presentational components so PluginHubSection stays free of view internals.
|
|
6
|
+
* 市场视图:目录的浏览体验 —— 分类 Tab、搜索/排序/安装状态控件与插件列表。自身不持有状态,
|
|
7
|
+
* 只把目录状态(query/sort/filter/category)接到小型展示组件上,让 PluginHubSection 不掺视图细节。
|
|
10
8
|
*/
|
|
11
9
|
import { createElement as h, Fragment } from 'react'
|
|
12
10
|
import type { HubPlugin, LocaleId } from '../../types.ts'
|