From 63aab5cdd639202ca19d218b8c5e8e6f899c4e60 Mon Sep 17 00:00:00 2001 From: Wu Tianwei <30284043+WTW0313@users.noreply.github.com> Date: Thu, 10 Apr 2025 11:18:43 +0800 Subject: [PATCH] feat: add search params to url (#17684) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- web/app/(commonLayout)/plugins/page.tsx | 2 +- web/app/components/plugins/hooks.ts | 14 ++++++ .../plugins/marketplace/context.tsx | 43 ++++++++++++++++--- .../components/plugins/marketplace/index.tsx | 4 ++ .../marketplace/plugin-type-switch.tsx | 20 +++++++++ .../components/plugins/marketplace/utils.ts | 20 +++++++++ .../plugins/plugin-page/context.tsx | 15 ++----- .../components/plugins/plugin-page/index.tsx | 23 ++++++---- 8 files changed, 114 insertions(+), 27 deletions(-) diff --git a/web/app/(commonLayout)/plugins/page.tsx b/web/app/(commonLayout)/plugins/page.tsx index cc525992f..47f279107 100644 --- a/web/app/(commonLayout)/plugins/page.tsx +++ b/web/app/(commonLayout)/plugins/page.tsx @@ -8,7 +8,7 @@ const PluginList = async () => { return ( } - marketplace={} + marketplace={} /> ) } diff --git a/web/app/components/plugins/hooks.ts b/web/app/components/plugins/hooks.ts index f4b81d98c..0349c46f9 100644 --- a/web/app/components/plugins/hooks.ts +++ b/web/app/components/plugins/hooks.ts @@ -92,3 +92,17 @@ export const useSingleCategories = (translateFromOut?: TFunction) => { categoriesMap, } } + +export const PLUGIN_PAGE_TABS_MAP = { + plugins: 'plugins', + marketplace: 'discover', +} + +export const usePluginPageTabs = () => { + const { t } = useTranslation() + const tabs = [ + { value: PLUGIN_PAGE_TABS_MAP.plugins, text: t('common.menus.plugins') }, + { value: PLUGIN_PAGE_TABS_MAP.marketplace, text: t('common.menus.exploreMarketplace') }, + ] + return tabs +} diff --git a/web/app/components/plugins/marketplace/context.tsx b/web/app/components/plugins/marketplace/context.tsx index 53f57c025..91621afaf 100644 --- a/web/app/components/plugins/marketplace/context.tsx +++ b/web/app/components/plugins/marketplace/context.tsx @@ -35,9 +35,10 @@ import { import { getMarketplaceListCondition, getMarketplaceListFilterType, + updateSearchParams, } from './utils' import { useInstalledPluginList } from '@/service/use-plugins' -import { noop } from 'lodash-es' +import { debounce, noop } from 'lodash-es' export type MarketplaceContextValue = { intersected: boolean @@ -96,6 +97,7 @@ type MarketplaceContextProviderProps = { searchParams?: SearchParams shouldExclude?: boolean scrollContainerId?: string + showSearchParams?: boolean } export function useMarketplaceContext(selector: (value: MarketplaceContextValue) => any) { @@ -107,6 +109,7 @@ export const MarketplaceContextProvider = ({ searchParams, shouldExclude, scrollContainerId, + showSearchParams, }: MarketplaceContextProviderProps) => { const { data, isSuccess } = useInstalledPluginList(!shouldExclude) const exclude = useMemo(() => { @@ -159,7 +162,10 @@ export const MarketplaceContextProvider = ({ type: getMarketplaceListFilterType(activePluginTypeRef.current), page: pageRef.current, }) - history.pushState({}, '', `/${searchParams?.language ? `?language=${searchParams?.language}` : ''}`) + const url = new URL(window.location.href) + if (searchParams?.language) + url.searchParams.set('language', searchParams?.language) + history.replaceState({}, '', url) } else { if (shouldExclude && isSuccess) { @@ -182,7 +188,31 @@ export const MarketplaceContextProvider = ({ resetPlugins() }, [exclude, queryMarketplaceCollectionsAndPlugins, resetPlugins]) + const debouncedUpdateSearchParams = useMemo(() => debounce(() => { + updateSearchParams({ + query: searchPluginTextRef.current, + category: activePluginTypeRef.current, + tags: filterPluginTagsRef.current, + }) + }, 500), []) + + const handleUpdateSearchParams = useCallback((debounced?: boolean) => { + if (!showSearchParams) + return + if (debounced) { + debouncedUpdateSearchParams() + } + else { + updateSearchParams({ + query: searchPluginTextRef.current, + category: activePluginTypeRef.current, + tags: filterPluginTagsRef.current, + }) + } + }, [debouncedUpdateSearchParams, showSearchParams]) + const handleQueryPlugins = useCallback((debounced?: boolean) => { + handleUpdateSearchParams(debounced) if (debounced) { queryPluginsWithDebounced({ query: searchPluginTextRef.current, @@ -207,17 +237,18 @@ export const MarketplaceContextProvider = ({ page: pageRef.current, }) } - }, [exclude, queryPluginsWithDebounced, queryPlugins]) + }, [exclude, queryPluginsWithDebounced, queryPlugins, handleUpdateSearchParams]) const handleQuery = useCallback((debounced?: boolean) => { if (!searchPluginTextRef.current && !filterPluginTagsRef.current.length) { + handleUpdateSearchParams(debounced) cancelQueryPluginsWithDebounced() handleQueryMarketplaceCollectionsAndPlugins() return } handleQueryPlugins(debounced) - }, [handleQueryMarketplaceCollectionsAndPlugins, handleQueryPlugins, cancelQueryPluginsWithDebounced]) + }, [handleQueryMarketplaceCollectionsAndPlugins, handleQueryPlugins, cancelQueryPluginsWithDebounced, handleUpdateSearchParams]) const handleSearchPluginTextChange = useCallback((text: string) => { setSearchPluginText(text) @@ -242,11 +273,9 @@ export const MarketplaceContextProvider = ({ activePluginTypeRef.current = type setPage(1) pageRef.current = 1 - }, []) - useEffect(() => { handleQuery() - }, [activePluginType, handleQuery]) + }, [handleQuery]) const handleSortChange = useCallback((sort: PluginsSort) => { setSort(sort) diff --git a/web/app/components/plugins/marketplace/index.tsx b/web/app/components/plugins/marketplace/index.tsx index 5e6fbeec9..7a29556bd 100644 --- a/web/app/components/plugins/marketplace/index.tsx +++ b/web/app/components/plugins/marketplace/index.tsx @@ -17,6 +17,7 @@ type MarketplaceProps = { pluginTypeSwitchClassName?: string intersectionContainerId?: string scrollContainerId?: string + showSearchParams?: boolean } const Marketplace = async ({ locale, @@ -27,6 +28,7 @@ const Marketplace = async ({ pluginTypeSwitchClassName, intersectionContainerId, scrollContainerId, + showSearchParams = true, }: MarketplaceProps) => { let marketplaceCollections: any = [] let marketplaceCollectionPluginsMap = {} @@ -42,6 +44,7 @@ const Marketplace = async ({ searchParams={searchParams} shouldExclude={shouldExclude} scrollContainerId={scrollContainerId} + showSearchParams={showSearchParams} > @@ -53,6 +56,7 @@ const Marketplace = async ({ locale={locale} className={pluginTypeSwitchClassName} searchBoxAutoAnimate={searchBoxAutoAnimate} + showSearchParams={showSearchParams} /> { const { t } = useMixedTranslation(locale) const activePluginType = useMarketplaceContext(s => s.activePluginType) @@ -70,6 +73,23 @@ const PluginTypeSwitch = ({ }, ] + const handlePopState = useCallback(() => { + if (!showSearchParams) + return + const url = new URL(window.location.href) + const category = url.searchParams.get('category') || PLUGIN_TYPE_SEARCH_MAP.all + handleActivePluginTypeChange(category) + }, [showSearchParams, handleActivePluginTypeChange]) + + useEffect(() => { + window.addEventListener('popstate', () => { + handlePopState() + }) + return () => { + window.removeEventListener('popstate', handlePopState) + } + }, [handlePopState]) + return (
{ return 'plugin' } + +export const updateSearchParams = (pluginsSearchParams: PluginsSearchParams) => { + const { query, category, tags } = pluginsSearchParams + const url = new URL(window.location.href) + const categoryChanged = url.searchParams.get('category') !== category + if (query) + url.searchParams.set('q', query) + else + url.searchParams.delete('q') + if (category) + url.searchParams.set('category', category) + else + url.searchParams.delete('category') + if (tags && tags.length) + url.searchParams.set('tags', tags.join(',')) + else + url.searchParams.delete('tags') + history[`${categoryChanged ? 'pushState' : 'replaceState'}`]({}, '', url) +} diff --git a/web/app/components/plugins/plugin-page/context.tsx b/web/app/components/plugins/plugin-page/context.tsx index cf26cd4e0..ae1ad7d05 100644 --- a/web/app/components/plugins/plugin-page/context.tsx +++ b/web/app/components/plugins/plugin-page/context.tsx @@ -12,9 +12,9 @@ import { } from 'use-context-selector' import { useSelector as useAppContextSelector } from '@/context/app-context' import type { FilterState } from './filter-management' -import { useTranslation } from 'react-i18next' import { useTabSearchParams } from '@/hooks/use-tab-searchparams' import { noop } from 'lodash-es' +import { PLUGIN_PAGE_TABS_MAP, usePluginPageTabs } from '../hooks' export type PluginPageContextValue = { containerRef: React.RefObject @@ -53,7 +53,6 @@ export function usePluginPageContext(selector: (value: PluginPageContextValue) = export const PluginPageContextProvider = ({ children, }: PluginPageContextProviderProps) => { - const { t } = useTranslation() const containerRef = useRef(null) const [filters, setFilters] = useState({ categories: [], @@ -63,16 +62,10 @@ export const PluginPageContextProvider = ({ const [currentPluginID, setCurrentPluginID] = useState() const { enable_marketplace } = useAppContextSelector(s => s.systemFeatures) + const tabs = usePluginPageTabs() const options = useMemo(() => { - return [ - { value: 'plugins', text: t('common.menus.plugins') }, - ...( - enable_marketplace - ? [{ value: 'discover', text: t('common.menus.exploreMarketplace') }] - : [] - ), - ] - }, [t, enable_marketplace]) + return enable_marketplace ? tabs : tabs.filter(tab => tab.value !== PLUGIN_PAGE_TABS_MAP.marketplace) + }, [tabs, enable_marketplace]) const [activeTab, setActiveTab] = useTabSearchParams({ defaultTab: options[0].value, }) diff --git a/web/app/components/plugins/plugin-page/index.tsx b/web/app/components/plugins/plugin-page/index.tsx index 801eaf660..072b8ee22 100644 --- a/web/app/components/plugins/plugin-page/index.tsx +++ b/web/app/components/plugins/plugin-page/index.tsx @@ -40,6 +40,8 @@ import { SUPPORT_INSTALL_LOCAL_FILE_EXTENSIONS } from '@/config' import { LanguagesSupported } from '@/i18n/language' import I18n from '@/context/i18n' import { noop } from 'lodash-es' +import { PLUGIN_TYPE_SEARCH_MAP } from '../marketplace/plugin-type-switch' +import { PLUGIN_PAGE_TABS_MAP } from '../hooks' const PACKAGE_IDS_KEY = 'package-ids' const BUNDLE_INFO_KEY = 'bundle-info' @@ -136,40 +138,45 @@ const PluginPage = ({ const setActiveTab = usePluginPageContext(v => v.setActiveTab) const { enable_marketplace } = useAppContextSelector(s => s.systemFeatures) + const isPluginsTab = useMemo(() => activeTab === PLUGIN_PAGE_TABS_MAP.plugins, [activeTab]) + const isExploringMarketplace = useMemo(() => { + const values = Object.values(PLUGIN_TYPE_SEARCH_MAP) + return activeTab === PLUGIN_PAGE_TABS_MAP.marketplace || values.includes(activeTab) + }, [activeTab]) + const uploaderProps = useUploader({ onFileChange: setCurrentFile, containerRef, - enabled: activeTab === 'plugins', + enabled: isPluginsTab, }) const { dragging, fileUploader, fileChangeHandle, removeFile } = uploaderProps - return (
{ - activeTab === 'discover' && ( + isExploringMarketplace && ( <>
- {activeTab === 'plugins' && ( + {isPluginsTab && ( <> {plugins} {dragging && ( @@ -246,7 +253,7 @@ const PluginPage = ({ )} { - activeTab === 'discover' && enable_marketplace && marketplace + isExploringMarketplace && enable_marketplace && marketplace } {showPluginSettingModal && (