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.
- package/client/client.js +4465 -1442
- package/client/client.js.map +1 -1
- package/lib/http/routes.js +438 -22
- package/lib/index.js +1 -1
- package/lib/services/install/install.js +4 -0
- package/lib/services/install/npm-check.js +55 -0
- package/lib/services/{npm-resolve.js → install/npm-resolve.js} +7 -5
- package/lib/services/{preflight.js → install/preflight.js} +15 -4
- package/lib/services/{task-queue.js → install/task-queue.js} +155 -66
- package/lib/services/loader.js +56 -1
- package/lib/services/log.js +138 -0
- package/lib/services/probe.js +157 -0
- package/lib/services/{profile.js → profile/profile.js} +29 -1
- package/lib/services/settings.js +60 -0
- package/lib/types/http/routes.d.ts +1 -1
- package/lib/types/index.d.ts +1 -1
- package/lib/types/services/{install-types.d.ts → install/install-types.d.ts} +3 -0
- package/lib/types/services/{install.d.ts → install/install.d.ts} +5 -5
- package/lib/types/services/install/npm-check.d.ts +12 -0
- package/lib/types/services/{npm-resolve.d.ts → install/npm-resolve.d.ts} +2 -1
- package/lib/types/services/{task-queue.d.ts → install/task-queue.d.ts} +5 -1
- package/lib/types/services/loader.d.ts +14 -8
- package/lib/types/services/log.d.ts +49 -0
- package/lib/types/services/probe.d.ts +30 -0
- package/lib/types/services/{pending-restart.d.ts → profile/pending-restart.d.ts} +1 -1
- package/lib/types/services/{profile.d.ts → profile/profile.d.ts} +2 -0
- package/lib/types/services/settings.d.ts +24 -0
- package/package.json +1 -1
- package/src/client/components/PluginHubSection.tsx +341 -120
- package/src/client/components/{CatalogList.tsx → catalog/CatalogList.tsx} +11 -7
- package/src/client/components/{PluginCard.tsx → catalog/PluginCard.tsx} +5 -4
- package/src/client/components/layout/CatalogControls.tsx +80 -0
- package/src/client/components/{CatalogHeader.tsx → layout/CatalogHeader.tsx} +4 -4
- package/src/client/components/{CategoryTabs.tsx → layout/CategoryTabs.tsx} +3 -3
- package/src/client/components/layout/SectionTabs.tsx +60 -0
- package/src/client/components/{AboutModal.tsx → modals/AboutModal.tsx} +7 -7
- package/src/client/components/{HubUpdateModal.tsx → modals/HubUpdateModal.tsx} +5 -5
- package/src/client/components/modals/InstalledDetailModal.tsx +193 -0
- package/src/client/components/modals/LogsModal.tsx +301 -0
- package/src/client/components/modals/LogsPathModal.tsx +161 -0
- package/src/client/components/{NotificationsModal.tsx → modals/NotificationsModal.tsx} +47 -20
- package/src/client/components/{ProgressView.tsx → modals/ProgressView.tsx} +2 -2
- package/src/client/components/{modals.tsx → modals/modals.tsx} +117 -40
- package/src/client/components/{Dropdown.tsx → ui/Dropdown.tsx} +14 -6
- package/src/client/components/ui/Toggle.tsx +28 -0
- package/src/client/components/ui/icons.tsx +343 -0
- package/src/client/components/views/CustomInstallView.tsx +250 -0
- package/src/client/components/views/DiagnosticsView.tsx +172 -0
- package/src/client/components/views/InstalledView.tsx +328 -0
- package/src/client/components/views/LogsView.tsx +150 -0
- package/src/client/components/views/MarketView.tsx +66 -0
- package/src/client/components/views/SettingsView.tsx +273 -0
- package/src/client/data/catalog.ts +38 -0
- package/src/client/data/host.ts +107 -0
- package/src/client/data/hub.ts +41 -0
- package/src/client/hooks/useCatalog.ts +71 -115
- package/src/client/hooks/useIncrementalList.ts +46 -0
- package/src/client/hooks/useLanguage.ts +40 -0
- package/src/client/hooks/useSettings.ts +90 -0
- package/src/client/hooks/useTaskQueue.ts +156 -39
- package/src/client/locales.ts +358 -38
- package/src/client/logic/constants.ts +86 -0
- package/src/client/{lib → logic}/failures.ts +95 -74
- package/src/client/logic/install-command.ts +56 -0
- package/src/client/logic/installed.ts +207 -0
- package/src/client/logic/normalize.ts +52 -0
- package/src/client/logic/urls.ts +125 -0
- package/src/client/styles/CustomInstallView.module.css +191 -0
- package/src/client/styles/DiagnosticsView.module.css +231 -0
- package/src/client/styles/Dropdown.module.css +42 -41
- package/src/client/styles/Header.module.css +100 -132
- package/src/client/styles/InstalledView.module.css +447 -0
- package/src/client/styles/List.module.css +6 -0
- package/src/client/styles/LogsView.module.css +503 -0
- package/src/client/styles/Modal.module.css +352 -70
- package/src/client/styles/SectionTabs.module.css +142 -0
- package/src/client/styles/SettingsView.module.css +232 -0
- package/src/client/styles/Toggle.module.css +60 -0
- package/src/client/styles/tokens.module.css +1 -0
- package/src/client/types.ts +23 -3
- package/src/server/http/routes.ts +414 -22
- package/src/server/index.ts +1 -1
- package/src/server/services/{install-types.ts → install/install-types.ts} +3 -1
- package/src/server/services/{install.ts → install/install.ts} +5 -5
- package/src/server/services/install/npm-check.ts +53 -0
- package/src/server/services/{npm-resolve.ts → install/npm-resolve.ts} +7 -5
- package/src/server/services/{preflight.ts → install/preflight.ts} +16 -4
- package/src/server/services/{task-queue.ts → install/task-queue.ts} +157 -63
- package/src/server/services/loader.ts +55 -1
- package/src/server/services/log.ts +167 -0
- package/src/server/services/probe.ts +157 -0
- package/src/server/services/{pending-restart.ts → profile/pending-restart.ts} +1 -1
- package/src/server/services/{profile.ts → profile/profile.ts} +31 -1
- package/src/server/services/settings.ts +79 -0
- package/lib/services/install.js +0 -4
- package/src/client/components/CatalogControls.tsx +0 -108
- package/src/client/components/icons.tsx +0 -129
- package/src/client/lib/catalog.ts +0 -293
- package/src/client/lib/env.ts +0 -20
- /package/lib/services/{install-types.js → install/install-types.js} +0 -0
- /package/lib/services/{installed-versions.js → profile/installed-versions.js} +0 -0
- /package/lib/services/{pending-restart.js → profile/pending-restart.js} +0 -0
- /package/lib/services/{progress.js → profile/progress.js} +0 -0
- /package/lib/types/services/{preflight.d.ts → install/preflight.d.ts} +0 -0
- /package/lib/types/services/{installed-versions.d.ts → profile/installed-versions.d.ts} +0 -0
- /package/lib/types/services/{progress.d.ts → profile/progress.d.ts} +0 -0
- /package/src/client/{lib → logic}/format.ts +0 -0
- /package/src/client/{lib → logic}/renderMarkdown.ts +0 -0
- /package/src/server/services/{installed-versions.ts → profile/installed-versions.ts} +0 -0
- /package/src/server/services/{progress.ts → profile/progress.ts} +0 -0
|
@@ -9,12 +9,13 @@
|
|
|
9
9
|
* not mistaken for a no-op update.
|
|
10
10
|
*/
|
|
11
11
|
import { createElement as h, useEffect, useRef } from 'react'
|
|
12
|
-
import styles from '
|
|
13
|
-
import type { HubPlugin, LocaleId, Translate } from '
|
|
14
|
-
import { SITE_URL } from '
|
|
12
|
+
import styles from '../../styles/List.module.css'
|
|
13
|
+
import type { HubPlugin, LocaleId, Translate } from '../../types.ts'
|
|
14
|
+
import { SITE_URL } from '../../logic/constants.ts'
|
|
15
|
+
import { useIncrementalList } from '../../hooks/useIncrementalList.ts'
|
|
15
16
|
import { PluginCard } from './PluginCard.tsx'
|
|
16
17
|
|
|
17
|
-
export function CatalogList({ plugins, failed, visible, total, t, langPath, reload, category,
|
|
18
|
+
export function CatalogList({ plugins, failed, visible, total, t, langPath, reload, category, copied, installedName, installedVersion, hasUpdate, langKey, onInstall, onUninstall }: {
|
|
18
19
|
plugins: HubPlugin[] | null
|
|
19
20
|
failed: boolean
|
|
20
21
|
visible: HubPlugin[]
|
|
@@ -23,7 +24,6 @@ export function CatalogList({ plugins, failed, visible, total, t, langPath, relo
|
|
|
23
24
|
langPath: string
|
|
24
25
|
reload: () => void
|
|
25
26
|
category: string
|
|
26
|
-
installedFilter: 'all' | 'installed' | 'notInstalled'
|
|
27
27
|
copied: string | null
|
|
28
28
|
installedName: (p: HubPlugin) => string | null
|
|
29
29
|
installedVersion: (p: HubPlugin) => string | null
|
|
@@ -34,10 +34,12 @@ export function CatalogList({ plugins, failed, visible, total, t, langPath, relo
|
|
|
34
34
|
}) {
|
|
35
35
|
/** 列表滚动容器:分类/搜索切换后列表内容替换但 scrollTop 保留,会让用户误以为列表没更新,需重置回顶部 */
|
|
36
36
|
const listRef = useRef<HTMLDivElement | null>(null)
|
|
37
|
+
/** 增量渲染:全量目录太大(4400+),只渲染首批,滚动接近底部时自动追加下一批 */
|
|
38
|
+
const { shown, hasMore, sentinelRef } = useIncrementalList(visible)
|
|
37
39
|
|
|
38
40
|
useEffect(() => {
|
|
39
41
|
listRef.current?.scrollTo({ top: 0 })
|
|
40
|
-
}, [category
|
|
42
|
+
}, [category])
|
|
41
43
|
|
|
42
44
|
return h('div', { className: styles.body },
|
|
43
45
|
h('div', { ref: listRef, className: styles.list },
|
|
@@ -51,7 +53,7 @@ export function CatalogList({ plugins, failed, visible, total, t, langPath, relo
|
|
|
51
53
|
h('div', { className: styles.stateTitle }, t('noResult')),
|
|
52
54
|
h('div', { className: styles.stateDesc }, t('noResultDesc')),
|
|
53
55
|
),
|
|
54
|
-
plugins !== null && !failed &&
|
|
56
|
+
plugins !== null && !failed && shown.map((p) => h(PluginCard, {
|
|
55
57
|
// 唯一 key:数据重构后同一 slug 可能对应多个作者仓库,仅用 slug 会撞 key,
|
|
56
58
|
// 导致切换分类时 React 复用旧 DOM、列表不刷新。
|
|
57
59
|
key: p.ownerSlug ? `${p.ownerSlug}/${p.slug}` : p.slug,
|
|
@@ -66,6 +68,8 @@ export function CatalogList({ plugins, failed, visible, total, t, langPath, relo
|
|
|
66
68
|
onInstall,
|
|
67
69
|
onUninstall,
|
|
68
70
|
})),
|
|
71
|
+
// 滚动加载哨兵:透明 1px 元素,进入视口即触发下一批渲染(加载是纯追加,无需按钮)
|
|
72
|
+
hasMore && h('div', { ref: sentinelRef, className: styles.moreSentinel, 'aria-hidden': 'true' }),
|
|
69
73
|
),
|
|
70
74
|
plugins !== null && !failed && h('div', { className: styles.footer },
|
|
71
75
|
h('a', {
|
|
@@ -8,10 +8,11 @@
|
|
|
8
8
|
* uninstall actions.
|
|
9
9
|
*/
|
|
10
10
|
import { createElement as h } from 'react'
|
|
11
|
-
import styles from '
|
|
12
|
-
import type { HubPlugin, LocaleId, Translate } from '
|
|
13
|
-
import { CATEGORY_LABELS, categoryLabel
|
|
14
|
-
import {
|
|
11
|
+
import styles from '../../styles/List.module.css'
|
|
12
|
+
import type { HubPlugin, LocaleId, Translate } from '../../types.ts'
|
|
13
|
+
import { CATEGORY_LABELS, categoryLabel } from '../../logic/constants.ts'
|
|
14
|
+
import { pluginDetailUrl } from '../../logic/urls.ts'
|
|
15
|
+
import { fmtStars, relTime } from '../../logic/format.ts'
|
|
15
16
|
|
|
16
17
|
export function PluginCard({ plugin: p, copied, installedName, installedVersion, hasUpdate, t, langKey, langPath, onInstall, onUninstall }: {
|
|
17
18
|
plugin: HubPlugin
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
|
|
3
|
+
* Website: https://dsh-plugin.org
|
|
4
|
+
* GitHub: https://github.com/dshplugin/dsh-plugin-hub
|
|
5
|
+
*
|
|
6
|
+
* Toolbar above the market list:
|
|
7
|
+
* row 1 — full-width search input;
|
|
8
|
+
* row 2 — result count on the left, sort segmented control on the right
|
|
9
|
+
* (mirrors the Installed view: clicking the same sort toggles
|
|
10
|
+
* ascending/descending, a new sort uses its default order; the active
|
|
11
|
+
* button shows an up/down arrow for the current direction).
|
|
12
|
+
*/
|
|
13
|
+
import { createElement as h, Fragment } from 'react'
|
|
14
|
+
import type { FormEvent, ReactNode } from 'react'
|
|
15
|
+
import styles from '../../styles/Header.module.css'
|
|
16
|
+
import type { Translate } from '../../types.ts'
|
|
17
|
+
import { SORTS } from '../../logic/constants.ts'
|
|
18
|
+
import type { SortKey } from '../../logic/constants.ts'
|
|
19
|
+
import { SortArrowIcon } from '../ui/icons.tsx'
|
|
20
|
+
|
|
21
|
+
/** 单选按钮组中的一个按钮(segmented control,与已安装视图排序按钮同款) */
|
|
22
|
+
function SegBtn({ active, onClick, label, icon }: {
|
|
23
|
+
active: boolean
|
|
24
|
+
onClick: () => void
|
|
25
|
+
label: string
|
|
26
|
+
icon?: ReactNode
|
|
27
|
+
}) {
|
|
28
|
+
return h('button', {
|
|
29
|
+
type: 'button',
|
|
30
|
+
className: active ? `${styles.segBtn} ${styles.segBtnActive}` : styles.segBtn,
|
|
31
|
+
'aria-pressed': active,
|
|
32
|
+
onClick,
|
|
33
|
+
}, label, icon)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function CatalogControls({ query, setQuery, sort, sortDir, toggleSort, t, resultText }: {
|
|
37
|
+
query: string
|
|
38
|
+
setQuery: (value: string) => void
|
|
39
|
+
sort: SortKey
|
|
40
|
+
/** 当前排序方向:决定激活排序按钮上的箭头朝上(正序)/朝下(倒序) */
|
|
41
|
+
sortDir: 'asc' | 'desc'
|
|
42
|
+
toggleSort: (value: SortKey) => void
|
|
43
|
+
t: Translate
|
|
44
|
+
resultText: string | null
|
|
45
|
+
}) {
|
|
46
|
+
return h(Fragment, null,
|
|
47
|
+
// 第一行:搜索框占满整行(搜插件名称、描述、标签)
|
|
48
|
+
h('div', { className: styles.searchRow },
|
|
49
|
+
h('input', {
|
|
50
|
+
className: styles.search,
|
|
51
|
+
type: 'search',
|
|
52
|
+
placeholder: t('search'),
|
|
53
|
+
value: query,
|
|
54
|
+
spellCheck: false,
|
|
55
|
+
onInput: (e: FormEvent<HTMLInputElement>) =>
|
|
56
|
+
setQuery((e.target as HTMLInputElement).value),
|
|
57
|
+
}),
|
|
58
|
+
),
|
|
59
|
+
// 第二行:左侧筛选结果数「结果 N 条插件」(数字等宽预留 5 位不跳动),
|
|
60
|
+
// 右侧排序按钮组(与已安装视图同款 segmented control,激活按钮带正/倒序箭头)
|
|
61
|
+
h('div', { className: styles.controls },
|
|
62
|
+
resultText ? h('span', { className: styles.resultSeg },
|
|
63
|
+
...resultText.split(/(\d+)/).map((part, i) =>
|
|
64
|
+
/^\d+$/.test(part)
|
|
65
|
+
? h('span', { key: i, className: styles.resultCount }, part)
|
|
66
|
+
: part)) : null,
|
|
67
|
+
h('div', { className: styles.sortGroup },
|
|
68
|
+
h('span', { className: styles.segLabel }, t('sortByLabel')),
|
|
69
|
+
h('div', { className: styles.segGroup, role: 'radiogroup', 'aria-label': t('sortAria') },
|
|
70
|
+
SORTS.map((key) => SegBtn({
|
|
71
|
+
active: sort === key,
|
|
72
|
+
onClick: () => toggleSort(key),
|
|
73
|
+
label: t(key),
|
|
74
|
+
icon: sort === key ? h(SortArrowIcon, { up: sortDir === 'asc' }) : null,
|
|
75
|
+
})),
|
|
76
|
+
),
|
|
77
|
+
),
|
|
78
|
+
),
|
|
79
|
+
)
|
|
80
|
+
}
|
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
* by the purple ad banner that promotes the catalog stats.
|
|
8
8
|
*/
|
|
9
9
|
import { createElement as h, Fragment } from 'react'
|
|
10
|
-
import styles from '
|
|
11
|
-
import type { Translate } from '
|
|
12
|
-
import { GITHUB_URL, PLUGIN_VERSION, SITE_URL } from '
|
|
13
|
-
import { GitHubIcon, LogoIcon } from '
|
|
10
|
+
import styles from '../../styles/Header.module.css'
|
|
11
|
+
import type { Translate } from '../../types.ts'
|
|
12
|
+
import { GITHUB_URL, PLUGIN_VERSION, SITE_URL } from '../../logic/constants.ts'
|
|
13
|
+
import { GitHubIcon, LogoIcon } from '../ui/icons.tsx'
|
|
14
14
|
|
|
15
15
|
export function CatalogHeader({ t, langPath, statsTotal, statsVerified, onToggleLang, hubUpdate, onVersionClick, onAboutClick }: {
|
|
16
16
|
t: Translate
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
* carry no counts so their widths stay uniform.
|
|
9
9
|
*/
|
|
10
10
|
import { createElement as h } from 'react'
|
|
11
|
-
import styles from '
|
|
12
|
-
import type { LocaleId } from '
|
|
13
|
-
import { CATEGORY_ORDER, CATEGORY_SHORT_LABELS, categoryLabel } from '
|
|
11
|
+
import styles from '../../styles/Header.module.css'
|
|
12
|
+
import type { LocaleId } from '../../types.ts'
|
|
13
|
+
import { CATEGORY_ORDER, CATEGORY_SHORT_LABELS, categoryLabel } from '../../logic/constants.ts'
|
|
14
14
|
|
|
15
15
|
export function CategoryTabs({ category, setCategory, allLabel, totalCount, langKey }: {
|
|
16
16
|
category: string
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
|
|
3
|
+
* Website: https://dsh-plugin.org
|
|
4
|
+
* GitHub: https://github.com/dshplugin/dsh-plugin-hub
|
|
5
|
+
*
|
|
6
|
+
* Primary navigation: a compact segmented control switching between the
|
|
7
|
+
* four top-level sections — Market (browse), Installed (manage), Custom
|
|
8
|
+
* (manual command installs) and Settings (network sources & preferences).
|
|
9
|
+
* Kept visually distinct from the category chips so the hierarchy reads:
|
|
10
|
+
* section → category → plugin.
|
|
11
|
+
*/
|
|
12
|
+
import { createElement as h } from 'react'
|
|
13
|
+
import styles from '../../styles/SectionTabs.module.css'
|
|
14
|
+
import { BellIcon, InstalledIcon, MarketIcon, SettingsIcon, TerminalIcon } from '../ui/icons.tsx'
|
|
15
|
+
|
|
16
|
+
export type SectionView = 'market' | 'installed' | 'custom' | 'settings'
|
|
17
|
+
|
|
18
|
+
const ORDER: Array<{ id: SectionView; labelKey: string; hintKey: string; Icon: () => ReturnType<typeof h> }> = [
|
|
19
|
+
{ id: 'market', labelKey: 'viewMarket', hintKey: 'viewMarketHint', Icon: MarketIcon },
|
|
20
|
+
{ id: 'installed', labelKey: 'viewInstalled', hintKey: 'viewInstalledHint', Icon: InstalledIcon },
|
|
21
|
+
{ id: 'custom', labelKey: 'viewCustom', hintKey: 'viewCustomHint', Icon: TerminalIcon },
|
|
22
|
+
{ id: 'settings', labelKey: 'viewSettings', hintKey: 'viewSettingsHint', Icon: SettingsIcon },
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
export function SectionTabs({ view, setView, installedCount, t, noticeCount, onOpenNotifications }: {
|
|
26
|
+
view: SectionView
|
|
27
|
+
setView: (value: SectionView) => void
|
|
28
|
+
installedCount: number
|
|
29
|
+
t: (key: string) => string
|
|
30
|
+
/** 通知中心红圈计数(记录 + 进行中任务 + 待重启);0 时不显示徽标 */
|
|
31
|
+
noticeCount: number
|
|
32
|
+
/** 点击打开通知中心 */
|
|
33
|
+
onOpenNotifications: () => void
|
|
34
|
+
}) {
|
|
35
|
+
return h('div', { className: styles.root, role: 'tablist' },
|
|
36
|
+
ORDER.map(({ id, labelKey, hintKey, Icon }) => h('button', {
|
|
37
|
+
key: id,
|
|
38
|
+
role: 'tab',
|
|
39
|
+
'aria-selected': view === id,
|
|
40
|
+
title: t(hintKey),
|
|
41
|
+
className: view === id ? styles.tabActive : styles.tab,
|
|
42
|
+
onClick: () => setView(id),
|
|
43
|
+
},
|
|
44
|
+
h('span', { className: styles.tabIcon }, h(Icon)),
|
|
45
|
+
t(labelKey),
|
|
46
|
+
id === 'installed' && installedCount > 0
|
|
47
|
+
? h('span', { className: view === id ? styles.tabCountActive : styles.tabCount }, installedCount)
|
|
48
|
+
: null)),
|
|
49
|
+
// 通知入口:设置在最后一个 tab 后边,靠右对齐 —— 铃铛 + 红底白字计数(内联跟在铃铛后,不悬浮)
|
|
50
|
+
h('button', {
|
|
51
|
+
type: 'button',
|
|
52
|
+
className: styles.noticeBtn,
|
|
53
|
+
onClick: onOpenNotifications,
|
|
54
|
+
title: t('notificationsHint'),
|
|
55
|
+
'aria-label': t('notificationsHint'),
|
|
56
|
+
},
|
|
57
|
+
h('span', { className: styles.noticeIcon }, h(BellIcon)),
|
|
58
|
+
noticeCount > 0 ? h('span', { className: styles.noticeCount }, noticeCount > 99 ? '99+' : String(noticeCount)) : null),
|
|
59
|
+
)
|
|
60
|
+
}
|
|
@@ -3,18 +3,18 @@
|
|
|
3
3
|
* Website: https://dsh-plugin.org
|
|
4
4
|
* GitHub: https://github.com/dshplugin/dsh-plugin-hub
|
|
5
5
|
*
|
|
6
|
-
* 头部「关注我们」弹窗:介绍 DSH-Plugin Hub
|
|
6
|
+
* 头部「关注我们」弹窗:介绍 DSH-Plugin Hub 的功能定位 + 用户反馈群二维码。
|
|
7
7
|
* 内容以 Markdown 形式由 dsh-update Worker 的 /about 接口下发(key = hub:about),
|
|
8
|
-
*
|
|
8
|
+
* 客户端按接口返回渲染,不内置固定文案。
|
|
9
9
|
* 图片(反馈群二维码)经 renderMarkdown 的  语法嵌入,居中、最大高度受控。
|
|
10
10
|
*/
|
|
11
11
|
import { createElement as h } from 'react'
|
|
12
12
|
import type { MouseEvent } from 'react'
|
|
13
|
-
import styles from '
|
|
14
|
-
import type { HubAboutInfo } from '
|
|
15
|
-
import type { LocaleId, Translate } from '
|
|
16
|
-
import { renderMarkdown } from '
|
|
17
|
-
import { CloseIcon } from '
|
|
13
|
+
import styles from '../../styles/Modal.module.css'
|
|
14
|
+
import type { HubAboutInfo } from '../../types.ts'
|
|
15
|
+
import type { LocaleId, Translate } from '../../types.ts'
|
|
16
|
+
import { renderMarkdown } from '../../logic/renderMarkdown.ts'
|
|
17
|
+
import { CloseIcon } from '../ui/icons.tsx'
|
|
18
18
|
|
|
19
19
|
export function AboutModal({ info, lang, t, onClose }: {
|
|
20
20
|
info: HubAboutInfo | null
|
|
@@ -12,11 +12,11 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { createElement as h } from 'react'
|
|
14
14
|
import type { MouseEvent } from 'react'
|
|
15
|
-
import styles from '
|
|
16
|
-
import type { HubUpdateInfo } from '
|
|
17
|
-
import type { LocaleId, Translate } from '
|
|
18
|
-
import { renderMarkdown } from '
|
|
19
|
-
import { CloseIcon } from '
|
|
15
|
+
import styles from '../../styles/Modal.module.css'
|
|
16
|
+
import type { HubUpdateInfo } from '../../types.ts'
|
|
17
|
+
import type { LocaleId, Translate } from '../../types.ts'
|
|
18
|
+
import { renderMarkdown } from '../../logic/renderMarkdown.ts'
|
|
19
|
+
import { CloseIcon } from '../ui/icons.tsx'
|
|
20
20
|
|
|
21
21
|
export function HubUpdateModal({ info, lang, t, hasUpdate, onProceed, onClose }: {
|
|
22
22
|
info: HubUpdateInfo
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
|
|
3
|
+
* Website: https://dsh-plugin.org
|
|
4
|
+
* GitHub: https://github.com/dshplugin/dsh-plugin-hub
|
|
5
|
+
*
|
|
6
|
+
* Installed-plugin detail dialog: opens from the Installed view (row click
|
|
7
|
+
* or the detail button) and shows everything we know about an installed
|
|
8
|
+
* entry — the catalog metadata (category, catalog entry, stars/forks,
|
|
9
|
+
* latest version) merged with the host runtime facts (install path, installed
|
|
10
|
+
* version, install time, last update). Update / uninstall actions are
|
|
11
|
+
* offered from here too, mirroring the row actions.
|
|
12
|
+
*/
|
|
13
|
+
import { createElement as h } from 'react'
|
|
14
|
+
import type { MouseEvent } from 'react'
|
|
15
|
+
import styles from '../../styles/Modal.module.css'
|
|
16
|
+
import type { LocaleId, Translate } from '../../types.ts'
|
|
17
|
+
import type { InstalledItem } from '../../logic/installed.ts'
|
|
18
|
+
import { CATEGORY_LABELS, categoryLabel } from '../../logic/constants.ts'
|
|
19
|
+
import { pluginDetailUrl } from '../../logic/urls.ts'
|
|
20
|
+
import { fmtStars } from '../../logic/format.ts'
|
|
21
|
+
import { CloseIcon, CopyIcon, FolderIcon, LinkIcon } from '../ui/icons.tsx'
|
|
22
|
+
|
|
23
|
+
/** ISO 安装时间 → 本地可读;非法值返回 null(行内不显示)。 */
|
|
24
|
+
function fmtDate(iso: string | null, lang: LocaleId): string | null {
|
|
25
|
+
if (!iso) return null
|
|
26
|
+
const d = new Date(iso)
|
|
27
|
+
return Number.isNaN(d.getTime()) ? null : d.toLocaleString(lang === 'en' ? 'en-US' : 'zh-CN')
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function InstalledDetailModal({ item, t, lang, langPath, onClose, onUpdate, onUninstall, onCopyPath, onReveal }: {
|
|
31
|
+
item: InstalledItem
|
|
32
|
+
t: Translate
|
|
33
|
+
lang: LocaleId
|
|
34
|
+
langPath: string
|
|
35
|
+
onClose: () => void
|
|
36
|
+
/** 更新:仅目录插件且有更新时展示(点击后关闭详情、走安装确认弹窗的更新语义) */
|
|
37
|
+
onUpdate: (item: InstalledItem) => void
|
|
38
|
+
onUninstall: (item: InstalledItem) => void
|
|
39
|
+
/** 复制安装路径:成功提示由调用方 toast 反馈 */
|
|
40
|
+
onCopyPath: (path: string) => void
|
|
41
|
+
/** 在系统文件管理器里定位安装目录(服务端 spawn open):失败提示由调用方 toast 反馈 */
|
|
42
|
+
onReveal: (item: InstalledItem) => void
|
|
43
|
+
}) {
|
|
44
|
+
const p = item.plugin
|
|
45
|
+
const name = p?.displayName ?? item.name
|
|
46
|
+
const installedAt = fmtDate(item.installedAt, lang)
|
|
47
|
+
// 最后更新:目录插件取仓库最近提交时间(live 数据);自定义安装无目录信号
|
|
48
|
+
const lastUpdated = fmtDate(p?.dates?.repoUpdatedAt ?? null, lang)
|
|
49
|
+
const hasUpdate = item.hasUpdate && item.plugin !== null
|
|
50
|
+
|
|
51
|
+
return h('div', {
|
|
52
|
+
className: styles.overlay,
|
|
53
|
+
onClick: (e: MouseEvent<HTMLDivElement>) => {
|
|
54
|
+
if (e.target === e.currentTarget) onClose()
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
h('div', { className: `${styles.modal} ${styles.detailModal}`, role: 'dialog', 'aria-modal': 'true' },
|
|
58
|
+
h('div', { className: styles.modalHead },
|
|
59
|
+
h('div', { className: styles.modalTitle }, name),
|
|
60
|
+
h('button', {
|
|
61
|
+
className: styles.modalClose,
|
|
62
|
+
'aria-label': t('confirmCancel'),
|
|
63
|
+
onClick: onClose,
|
|
64
|
+
}, h(CloseIcon)),
|
|
65
|
+
),
|
|
66
|
+
h('div', { className: styles.modalBody },
|
|
67
|
+
// 描述:目录收录的插件简介
|
|
68
|
+
p?.description ? h('div', { className: styles.modalDesc }, p.description) : null,
|
|
69
|
+
// 有可用更新:醒目提示条(版本号存在时给出升级路径)
|
|
70
|
+
hasUpdate
|
|
71
|
+
? h('div', { className: styles.detailUpdateHint },
|
|
72
|
+
item.installedVersion && item.catalogVersion
|
|
73
|
+
? t('versionUpHint', { from: item.installedVersion, to: item.catalogVersion })
|
|
74
|
+
: t('updateAvailableHint'))
|
|
75
|
+
: null,
|
|
76
|
+
// 详情信息网格:分类 / 状态 / 版本 / 收录入口 / 包名 / 统计 / 安装路径 / 安装时间 / 最后更新
|
|
77
|
+
h('div', { className: styles.detailGrid },
|
|
78
|
+
p?.category
|
|
79
|
+
? h('div', { className: styles.detailRow },
|
|
80
|
+
h('span', { className: styles.detailLabel }, t('detailCategory')),
|
|
81
|
+
h('span', { className: styles.detailValue },
|
|
82
|
+
categoryLabel(CATEGORY_LABELS, p.category, lang)))
|
|
83
|
+
: null,
|
|
84
|
+
h('div', { className: styles.detailRow },
|
|
85
|
+
h('span', { className: styles.detailLabel }, t('statusLabel')),
|
|
86
|
+
h('span', { className: styles.detailValue },
|
|
87
|
+
h('span', {
|
|
88
|
+
className: item.loaded ? styles.detailStatusRunning : styles.detailStatusPending,
|
|
89
|
+
'aria-hidden': 'true',
|
|
90
|
+
}),
|
|
91
|
+
h('span', { className: styles.detailStatusText },
|
|
92
|
+
item.loaded ? t('statusRunning') : t('statusPending')))),
|
|
93
|
+
item.installedVersion
|
|
94
|
+
? h('div', { className: styles.detailRow },
|
|
95
|
+
h('span', { className: styles.detailLabel }, t('installedVersionLabel')),
|
|
96
|
+
h('span', { className: styles.detailValue },
|
|
97
|
+
h('span', { className: styles.detailMono }, item.installedVersion),
|
|
98
|
+
// 目录有更新版本且与已装版本不同:给出「→ 目录最新」
|
|
99
|
+
item.catalogVersion && item.catalogVersion !== item.installedVersion
|
|
100
|
+
? h('span', { className: styles.detailArrow },
|
|
101
|
+
'\u2192', h('span', { className: styles.detailMono }, item.catalogVersion),
|
|
102
|
+
h('span', { className: styles.detailDim }, t('catalogLatest')))
|
|
103
|
+
: null))
|
|
104
|
+
: null,
|
|
105
|
+
// 收录入口:仓库名 → 官网详情页(目录插件跳收录页;自定义安装回退到 GitHub 仓库)
|
|
106
|
+
item.repo
|
|
107
|
+
? h('div', { className: styles.detailRow },
|
|
108
|
+
h('span', { className: styles.detailLabel }, t('detailCatalog')),
|
|
109
|
+
h('a', {
|
|
110
|
+
className: styles.detailLink,
|
|
111
|
+
href: p ? pluginDetailUrl(p, langPath) : `https://github.com/${item.repo}`,
|
|
112
|
+
target: '_blank',
|
|
113
|
+
rel: 'noopener noreferrer',
|
|
114
|
+
title: item.repo,
|
|
115
|
+
}, h(LinkIcon), item.repo))
|
|
116
|
+
: null,
|
|
117
|
+
item.plugin?.source?.npmPackage
|
|
118
|
+
? h('div', { className: styles.detailRow },
|
|
119
|
+
h('span', { className: styles.detailLabel }, t('packageName')),
|
|
120
|
+
h('span', { className: styles.detailValue },
|
|
121
|
+
h('span', { className: styles.detailMono }, item.plugin.source.npmPackage)))
|
|
122
|
+
: null,
|
|
123
|
+
p?.stats && (p.stats.stargazers_count ?? 0) > 0
|
|
124
|
+
? h('div', { className: styles.detailRow },
|
|
125
|
+
h('span', { className: styles.detailLabel }, t('detailStats')),
|
|
126
|
+
h('span', { className: styles.detailValue },
|
|
127
|
+
h('span', { className: styles.detailStars }, '\u2605 ', fmtStars(p.stats.stargazers_count)),
|
|
128
|
+
h('span', { className: styles.detailDim }, t('fork'), ' ', fmtStars(p.stats.forks_count))))
|
|
129
|
+
: null,
|
|
130
|
+
item.installPath
|
|
131
|
+
? h('div', { className: styles.detailRow },
|
|
132
|
+
h('span', { className: styles.detailLabel }, t('installPath')),
|
|
133
|
+
h('div', { className: styles.detailPath },
|
|
134
|
+
// 整条点击复制路径(等宽文本省略号)
|
|
135
|
+
h('span', {
|
|
136
|
+
className: styles.detailPathText,
|
|
137
|
+
title: t('copyPath'),
|
|
138
|
+
onClick: () => onCopyPath(item.installPath!),
|
|
139
|
+
}, item.installPath),
|
|
140
|
+
h('span', { className: styles.detailPathActions },
|
|
141
|
+
h('button', {
|
|
142
|
+
className: styles.detailPathBtn,
|
|
143
|
+
type: 'button',
|
|
144
|
+
title: t('copyPath'),
|
|
145
|
+
onClick: (e: MouseEvent<HTMLButtonElement>) => {
|
|
146
|
+
e.stopPropagation()
|
|
147
|
+
onCopyPath(item.installPath!)
|
|
148
|
+
},
|
|
149
|
+
}, h(CopyIcon)),
|
|
150
|
+
h('button', {
|
|
151
|
+
className: styles.detailPathBtn,
|
|
152
|
+
type: 'button',
|
|
153
|
+
title: t('openFolder'),
|
|
154
|
+
onClick: (e: MouseEvent<HTMLButtonElement>) => {
|
|
155
|
+
e.stopPropagation()
|
|
156
|
+
onReveal(item)
|
|
157
|
+
},
|
|
158
|
+
}, h(FolderIcon)),
|
|
159
|
+
)))
|
|
160
|
+
: null,
|
|
161
|
+
installedAt
|
|
162
|
+
? h('div', { className: styles.detailRow },
|
|
163
|
+
h('span', { className: styles.detailLabel }, t('installedAtLabel')),
|
|
164
|
+
h('span', { className: styles.detailValue }, installedAt))
|
|
165
|
+
: null,
|
|
166
|
+
// 最后更新时间:与安装时间成对展示(目录插件才有)
|
|
167
|
+
lastUpdated
|
|
168
|
+
? h('div', { className: styles.detailRow },
|
|
169
|
+
h('span', { className: styles.detailLabel }, t('lastUpdatedLabel')),
|
|
170
|
+
h('span', { className: styles.detailValue }, lastUpdated))
|
|
171
|
+
: null,
|
|
172
|
+
),
|
|
173
|
+
),
|
|
174
|
+
// 底部操作:完成 / 更新 / 卸载(复制路径与打开文件夹已在路径行内)
|
|
175
|
+
h('div', { className: styles.modalActions },
|
|
176
|
+
h('button', {
|
|
177
|
+
className: styles.restartLater,
|
|
178
|
+
onClick: onClose,
|
|
179
|
+
}, t('done')),
|
|
180
|
+
hasUpdate
|
|
181
|
+
? h('button', {
|
|
182
|
+
className: styles.modalInstall,
|
|
183
|
+
onClick: () => onUpdate(item),
|
|
184
|
+
}, t('update'))
|
|
185
|
+
: null,
|
|
186
|
+
h('button', {
|
|
187
|
+
className: styles.uninstallConfirm,
|
|
188
|
+
onClick: () => onUninstall(item),
|
|
189
|
+
}, t('uninstall')),
|
|
190
|
+
),
|
|
191
|
+
),
|
|
192
|
+
)
|
|
193
|
+
}
|