From 246069d1284c691fee61e9e8d8dcec8bc3cdbcef Mon Sep 17 00:00:00 2001 From: LTbinglingfeng Date: Mon, 15 Jun 2026 01:21:10 +0800 Subject: [PATCH] feat(pluginStore): enhance plugin store entry with source information and update related functionality --- src/features/plugins/PluginStorePage.tsx | 76 ++++++++++++++---------- src/i18n/locales/en.json | 1 + src/i18n/locales/ru.json | 1 + src/i18n/locales/zh-CN.json | 1 + src/i18n/locales/zh-TW.json | 1 + src/services/api/plugins.ts | 32 +++++++++- src/types/plugin.ts | 14 +++++ 7 files changed, 93 insertions(+), 33 deletions(-) diff --git a/src/features/plugins/PluginStorePage.tsx b/src/features/plugins/PluginStorePage.tsx index 6697e2a..bdb5238 100644 --- a/src/features/plugins/PluginStorePage.tsx +++ b/src/features/plugins/PluginStorePage.tsx @@ -40,6 +40,9 @@ const getErrorDetailMessage = (error: unknown): string => { const DESCRIPTION_COLLAPSED_LINES = 2; const getStoreEntryTitle = (entry: PluginStoreEntry) => entry.name || entry.id; +const getStoreEntryKey = (entry: PluginStoreEntry) => entry.storeId || entry.id; +const getDescriptionDOMID = (entryKey: string) => + `plugin-store-desc-${encodeURIComponent(entryKey)}`; function StoreCardLogo({ src }: { src: string }) { const [failed, setFailed] = useState(false); @@ -66,10 +69,10 @@ export function PluginStorePage() { const [error, setError] = useState(null); const [filter, setFilter] = useState(''); const [statusFilter, setStatusFilter] = useState('all'); - const [installingID, setInstallingID] = useState(''); - const [restartRequiredIDs, setRestartRequiredIDs] = useState([]); - const [expandedDescriptionIDs, setExpandedDescriptionIDs] = useState([]); - const [overflowingDescriptionIDs, setOverflowingDescriptionIDs] = useState([]); + const [installingKey, setInstallingKey] = useState(''); + const [restartRequiredKeys, setRestartRequiredKeys] = useState([]); + const [expandedDescriptionKeys, setExpandedDescriptionKeys] = useState([]); + const [overflowingDescriptionKeys, setOverflowingDescriptionKeys] = useState([]); const descriptionRefs = useRef>({}); const connected = connectionStatus === 'connected'; @@ -145,6 +148,8 @@ export function PluginStorePage() { plugin.description, plugin.author, plugin.repository, + plugin.sourceName, + plugin.sourceUrl, plugin.license, ...plugin.tags, ] @@ -166,20 +171,20 @@ export function PluginStorePage() { { key: 'updates', label: t('plugin_store.filter_updates'), count: stats.updates }, ]; - const restartNames = restartRequiredIDs.map((id) => { - const entry = data?.plugins.find((plugin) => plugin.id === id); - return entry ? getStoreEntryTitle(entry) : id; + const restartNames = restartRequiredKeys.map((key) => { + const entry = data?.plugins.find((plugin) => getStoreEntryKey(plugin) === key); + return entry ? getStoreEntryTitle(entry) : key; }); const hasActiveFilters = Boolean(filter.trim()) || statusFilter !== 'all'; - const expandedDescriptionIDSet = useMemo( - () => new Set(expandedDescriptionIDs), - [expandedDescriptionIDs] + const expandedDescriptionKeySet = useMemo( + () => new Set(expandedDescriptionKeys), + [expandedDescriptionKeys] ); - const overflowingDescriptionIDSet = useMemo( - () => new Set(overflowingDescriptionIDs), - [overflowingDescriptionIDs] + const overflowingDescriptionKeySet = useMemo( + () => new Set(overflowingDescriptionKeys), + [overflowingDescriptionKeys] ); const registerDescriptionRef = useCallback((id: string, node: HTMLParagraphElement | null) => { @@ -203,7 +208,7 @@ export function PluginStorePage() { }) .map(([id]) => id); - setOverflowingDescriptionIDs((current) => { + setOverflowingDescriptionKeys((current) => { if (current.length === nextIDs.length && current.every((id) => nextIDs.includes(id))) { return current; } @@ -230,12 +235,13 @@ export function PluginStorePage() { }, [measureDescriptionOverflow]); const toggleDescription = useCallback((id: string) => { - setExpandedDescriptionIDs((current) => + setExpandedDescriptionKeys((current) => current.includes(id) ? current.filter((currentID) => currentID !== id) : [...current, id] ); }, []); const handleInstall = (entry: PluginStoreEntry) => { + const entryKey = getStoreEntryKey(entry); const isUpdate = entry.installed && entry.updateAvailable; const title = getStoreEntryTitle(entry); const target = entry.version ? `${title} v${entry.version}` : title; @@ -251,16 +257,16 @@ export function PluginStorePage() { confirmText: isUpdate ? t('plugin_store.update') : t('plugin_store.install'), variant: 'primary', onConfirm: async () => { - setInstallingID(entry.id); + setInstallingKey(entryKey); try { - const result = await pluginStoreApi.install(entry.id); + const result = await pluginStoreApi.install(entry.id, entry.sourceId || undefined); showNotification( isUpdate ? t('plugin_store.update_success') : t('plugin_store.install_success'), 'success' ); if (result.restartRequired) { - setRestartRequiredIDs((current) => - current.includes(entry.id) ? current : [...current, entry.id] + setRestartRequiredKeys((current) => + current.includes(entryKey) ? current : [...current, entryKey] ); showNotification(t('plugin_store.restart_required_notice'), 'warning'); } @@ -270,13 +276,14 @@ export function PluginStorePage() { showNotification(`${t(failedKey)}: ${getErrorMessage(err, t(failedKey))}`, 'error'); throw err; } finally { - setInstallingID(''); + setInstallingKey(''); } }, }); }; const renderCard = (entry: PluginStoreEntry) => { + const entryKey = getStoreEntryKey(entry); const logo = resolvePluginAssetURL(entry.logo, apiBase); const repositoryURL = buildRepositoryURL(entry.repository); const homepageURL = /^https?:\/\//i.test(entry.homepage) ? entry.homepage : ''; @@ -289,13 +296,18 @@ export function PluginStorePage() { : entry.version ? `v${entry.version}` : ''; - const metaItems = [versionText, entry.author, entry.license].filter(Boolean); - const isDescriptionExpanded = expandedDescriptionIDSet.has(entry.id); - const isDescriptionOverflowing = overflowingDescriptionIDSet.has(entry.id); - const descriptionID = `plugin-store-desc-${entry.id}`; + const sourceText = entry.sourceName + ? t('plugin_store.source_name', { source: entry.sourceName }) + : ''; + const metaItems = [versionText, sourceText, entry.author, entry.license].filter(Boolean); + const isInstalling = installingKey === entryKey; + const hasPendingInstall = Boolean(installingKey); + const isDescriptionExpanded = expandedDescriptionKeySet.has(entryKey); + const isDescriptionOverflowing = overflowingDescriptionKeySet.has(entryKey); + const descriptionID = getDescriptionDOMID(entryKey); return ( -
+