feat: marketplace list url support search & tags

This commit is contained in:
StyleZhang 2024-11-19 15:08:39 +08:00
parent c5c06c18f1
commit 43d7a538dc
7 changed files with 96 additions and 89 deletions

View File

@ -0,0 +1,25 @@
export const tagKeys = [
'search',
'image',
'videos',
'weather',
'finance',
'design',
'travel',
'social',
'news',
'medical',
'productivity',
'education',
'business',
'entertainment',
'utilities',
'other',
]
export const categoryKeys = [
'model',
'tool',
'extension',
'bundle',
]

View File

@ -1,5 +1,9 @@
import { useTranslation } from 'react-i18next'
import type { TFunction } from 'i18next'
import {
categoryKeys,
tagKeys,
} from './constants'
type Tag = {
name: string
@ -10,72 +14,12 @@ export const useTags = (translateFromOut?: TFunction) => {
const { t: translation } = useTranslation()
const t = translateFromOut || translation
const tags = [
{
name: 'search',
label: t('pluginTags.tags.search'),
},
{
name: 'image',
label: t('pluginTags.tags.image'),
},
{
name: 'videos',
label: t('pluginTags.tags.videos'),
},
{
name: 'weather',
label: t('pluginTags.tags.weather'),
},
{
name: 'finance',
label: t('pluginTags.tags.finance'),
},
{
name: 'design',
label: t('pluginTags.tags.design'),
},
{
name: 'travel',
label: t('pluginTags.tags.travel'),
},
{
name: 'social',
label: t('pluginTags.tags.social'),
},
{
name: 'news',
label: t('pluginTags.tags.news'),
},
{
name: 'medical',
label: t('pluginTags.tags.medical'),
},
{
name: 'productivity',
label: t('pluginTags.tags.productivity'),
},
{
name: 'education',
label: t('pluginTags.tags.education'),
},
{
name: 'business',
label: t('pluginTags.tags.business'),
},
{
name: 'entertainment',
label: t('pluginTags.tags.entertainment'),
},
{
name: 'utilities',
label: t('pluginTags.tags.utilities'),
},
{
name: 'other',
label: t('pluginTags.tags.other'),
},
]
const tags = tagKeys.map((tag) => {
return {
name: tag,
label: t(`pluginTags.tags.${tag}`),
}
})
const tagsMap = tags.reduce((acc, tag) => {
acc[tag.name] = tag
@ -97,24 +41,12 @@ export const useCategories = (translateFromOut?: TFunction) => {
const { t: translation } = useTranslation()
const t = translateFromOut || translation
const categories = [
{
name: 'model',
label: t('plugin.category.models'),
},
{
name: 'tool',
label: t('plugin.category.tools'),
},
{
name: 'extension',
label: t('plugin.category.extensions'),
},
{
name: 'bundle',
label: t('plugin.category.bundles'),
},
]
const categories = categoryKeys.map((category) => {
return {
name: category,
label: t(`plugin.category.${category}s`),
}
})
const categoriesMap = categories.reduce((acc, category) => {
acc[category.name] = category

View File

@ -5,6 +5,7 @@ import type {
} from 'react'
import {
useCallback,
useEffect,
useRef,
useState,
} from 'react'
@ -14,9 +15,14 @@ import {
} from 'use-context-selector'
import { PLUGIN_TYPE_SEARCH_MAP } from './plugin-type-switch'
import type { Plugin } from '../types'
import {
getValidCategoryKeys,
getValidTagKeys,
} from '../utils'
import type {
MarketplaceCollection,
PluginsSort,
SearchParams,
} from './types'
import { DEFAULT_SORT } from './constants'
import {
@ -66,6 +72,7 @@ export const MarketplaceContext = createContext<MarketplaceContextValue>({
type MarketplaceContextProviderProps = {
children: ReactNode
searchParams?: SearchParams
}
export function useMarketplaceContext(selector: (value: MarketplaceContextValue) => any) {
@ -74,13 +81,19 @@ export function useMarketplaceContext(selector: (value: MarketplaceContextValue)
export const MarketplaceContextProvider = ({
children,
searchParams,
}: MarketplaceContextProviderProps) => {
const queryFromSearchParams = searchParams?.q || ''
const tagsFromSearchParams = searchParams?.tags ? getValidTagKeys(searchParams.tags.split(',')) : []
const hasValidTags = !!tagsFromSearchParams.length
const hasValidCategory = getValidCategoryKeys(searchParams?.category)
const categoryFromSearchParams = hasValidCategory || PLUGIN_TYPE_SEARCH_MAP.all
const [intersected, setIntersected] = useState(true)
const [searchPluginText, setSearchPluginText] = useState('')
const [searchPluginText, setSearchPluginText] = useState(queryFromSearchParams)
const searchPluginTextRef = useRef(searchPluginText)
const [filterPluginTags, setFilterPluginTags] = useState<string[]>([])
const [filterPluginTags, setFilterPluginTags] = useState<string[]>(tagsFromSearchParams)
const filterPluginTagsRef = useRef(filterPluginTags)
const [activePluginType, setActivePluginType] = useState(PLUGIN_TYPE_SEARCH_MAP.all)
const [activePluginType, setActivePluginType] = useState(categoryFromSearchParams)
const activePluginTypeRef = useRef(activePluginType)
const [sort, setSort] = useState(DEFAULT_SORT)
const sortRef = useRef(sort)
@ -100,6 +113,20 @@ export const MarketplaceContextProvider = ({
isLoading: isPluginsLoading,
} = useMarketplacePlugins()
useEffect(() => {
if (queryFromSearchParams || hasValidTags || hasValidCategory) {
queryPlugins({
query: queryFromSearchParams,
category: hasValidCategory,
tags: hasValidTags ? tagsFromSearchParams : [],
sortBy: sortRef.current.sortBy,
sortOrder: sortRef.current.sortOrder,
})
history.pushState({}, '', `/${searchParams?.language ? `?language=${searchParams?.language}` : ''}`)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [queryPlugins])
const handleSearchPluginTextChange = useCallback((text: string) => {
setSearchPluginText(text)
searchPluginTextRef.current = text

View File

@ -4,22 +4,25 @@ import IntersectionLine from './intersection-line'
import SearchBoxWrapper from './search-box/search-box-wrapper'
import PluginTypeSwitch from './plugin-type-switch'
import ListWrapper from './list/list-wrapper'
import type { SearchParams } from './types'
import { getMarketplaceCollectionsAndPlugins } from './utils'
import { TanstackQueryIniter } from '@/context/query-client'
type MarketplaceProps = {
locale?: string
showInstallButton?: boolean
searchParams?: SearchParams
}
const Marketplace = async ({
locale,
showInstallButton = true,
searchParams,
}: MarketplaceProps) => {
const { marketplaceCollections, marketplaceCollectionPluginsMap } = await getMarketplaceCollectionsAndPlugins()
return (
<TanstackQueryIniter>
<MarketplaceContextProvider>
<MarketplaceContextProvider searchParams={searchParams}>
<Description locale={locale} />
<IntersectionLine />
<SearchBoxWrapper locale={locale} />

View File

@ -36,3 +36,10 @@ export type PluginsSort = {
export type CollectionsAndPluginsSearchParams = {
category?: string
}
export type SearchParams = {
language?: string
q?: string
tags?: string
category?: string
}

View File

@ -0,0 +1,13 @@
import {
categoryKeys,
tagKeys,
} from './constants'
export const getValidTagKeys = (tags: string[]) => {
return tags.filter(tag => tagKeys.includes(tag))
}
export const getValidCategoryKeys = (category?: string) => {
const currentCategory = categoryKeys.find(key => key === category)
return currentCategory ? `${currentCategory}s` : ''
}

View File

@ -57,7 +57,7 @@ const Marketplace = ({
</span>
{t('common.operation.in')}
<a
href={`${MARKETPLACE_URL_PREFIX}?language=${locale}`}
href={`${MARKETPLACE_URL_PREFIX}?language=${locale}&q=${searchPluginText}&tags=${filterPluginTags.join(',')}`}
className='flex items-center ml-1 system-sm-medium text-text-accent'
target='_blank'
>