kopia lustrzana https://github.com/Stopka/fedisearch
Node page now stores the search query in url
rodzic
abc52b915a
commit
861e2a1476
|
@ -18,6 +18,14 @@ export function preserveNull<Source, Target> (cast: (value:Source)=>Target) {
|
|||
}
|
||||
}
|
||||
|
||||
export function undefinedToDefault<Type> (defaultValue:Type): (value:Type|undefined)=>Type {
|
||||
return (value) => typeof value === 'undefined' ? defaultValue : value
|
||||
}
|
||||
|
||||
export function stringTrimmed (value: string|undefined): string {
|
||||
return (value ?? '').trim().replace(/^\++|\++$/g, '')
|
||||
}
|
||||
|
||||
export function stringToInt (value: string): number {
|
||||
return parseInt(value)
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ const handleFeedSearch = async (req: NextApiRequest, res: NextApiResponse<FeedRe
|
|||
console.info('Searching feeds', { query: req.query })
|
||||
|
||||
const feedRequest = feedRequestSchema.parse(req.query)
|
||||
const phrases = (feedRequest.search ?? '').trim().replace(/^\++|\++$/g, '').split(/[\s+]+/)
|
||||
const phrases = feedRequest.search.split(/[\s+]+/)
|
||||
const feeds = await prisma.feed.findMany({
|
||||
where: {
|
||||
AND: phrases.map(phrase => {
|
||||
|
|
|
@ -8,9 +8,7 @@ const handleFeedSearch = async (req: NextApiRequest, res: NextApiResponse<NodeRe
|
|||
console.info('Searching nodes', { query: req.query })
|
||||
|
||||
const nodeRequest = nodeRequestSchema.parse(req.query)
|
||||
const phrases = (nodeRequest.search ?? '').trim().split(/[\s+]+/)
|
||||
nodeRequest.sortBy = nodeRequest.sortBy ?? 'refreshedAt'
|
||||
nodeRequest.sortWay = nodeRequest.sortWay ?? 'desc'
|
||||
const phrases = nodeRequest.search.split(/[\s+]+/)
|
||||
const order = {}
|
||||
order[nodeRequest.sortBy] = nodeRequest.sortWay
|
||||
const nodes = await prisma.node.findMany({
|
||||
|
|
|
@ -6,27 +6,27 @@ import Layout, { siteTitle } from '../components/Layout'
|
|||
import { matomoConfig } from '../lib/matomoConfig'
|
||||
import getMatomo from '../lib/getMatomo'
|
||||
import { GetServerSideProps, InferGetServerSidePropsType } from 'next'
|
||||
import { nodeResponseSchema } from '../types/NodeResponse'
|
||||
import { NodeResponseItem, nodeResponseSchema } from '../types/NodeResponse'
|
||||
import SoftwareBadge from '../components/badges/SoftwareBadge'
|
||||
import SortToggle from '../components/SortToggle'
|
||||
import { StatsRequestSortBy } from '../types/StatsRequest'
|
||||
import { Sort } from '../types/Sort'
|
||||
import { faSearch, faAngleDoubleDown } from '@fortawesome/free-solid-svg-icons'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { useRouter } from 'next/router'
|
||||
import { NodeRequestQuery, nodeRequestQuerySchema, NodeRequestSortBy } from '../types/NodeRequest'
|
||||
|
||||
let source = axios.CancelToken.source()
|
||||
|
||||
const Nodes: React.FC<InferGetServerSidePropsType<typeof getServerSideProps>> = ({ matomoConfig }) => {
|
||||
const [query, setQuery] = useState('')
|
||||
const [submitted, setSubmitted] = useState(null)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [results, setResults] = useState([])
|
||||
const [page, setPage] = useState(0)
|
||||
const [hasMore, setHasMore] = useState(false)
|
||||
const [loaded, setLoaded] = useState(false)
|
||||
const [sort, setSort] = useState<Sort>({
|
||||
sortBy: 'refreshedAt', sortWay: 'desc'
|
||||
})
|
||||
const router = useRouter()
|
||||
const routerQuery = nodeRequestQuerySchema.parse(router.query)
|
||||
console.log('Router query', routerQuery)
|
||||
const [query, setQuery] = useState<NodeRequestQuery>(routerQuery)
|
||||
const [submitted, setSubmitted] = useState<Date|null>(null)
|
||||
const [loading, setLoading] = useState<boolean>(false)
|
||||
const [results, setResults] = useState<NodeResponseItem[]>([])
|
||||
const [page, setPage] = useState<number>(0)
|
||||
const [hasMore, setHasMore] = useState<boolean>(false)
|
||||
const [loaded, setLoaded] = useState<boolean>(false)
|
||||
|
||||
const search = async () => {
|
||||
setLoading(true)
|
||||
|
@ -34,7 +34,7 @@ const Nodes: React.FC<InferGetServerSidePropsType<typeof getServerSideProps>> =
|
|||
console.info('Retrieving results', { query, page })
|
||||
source = axios.CancelToken.source()
|
||||
const response = await axios.get('/api/node', {
|
||||
params: { search: query, page, sortBy: sort.sortBy, sortWay: sort.sortWay },
|
||||
params: { ...query, page },
|
||||
cancelToken: source.token
|
||||
})
|
||||
const responseData = await nodeResponseSchema.parseAsync(response.data)
|
||||
|
@ -54,6 +54,8 @@ const Nodes: React.FC<InferGetServerSidePropsType<typeof getServerSideProps>> =
|
|||
const loadNewQueryResults = () => {
|
||||
console.info('Cancelling searches')
|
||||
source.cancel('New query on the way')
|
||||
router.query = query
|
||||
router.push(router)
|
||||
setResults([])
|
||||
setHasMore(false)
|
||||
setLoaded(false)
|
||||
|
@ -86,9 +88,13 @@ const Nodes: React.FC<InferGetServerSidePropsType<typeof getServerSideProps>> =
|
|||
}
|
||||
|
||||
const handleQueryChange = (event) => {
|
||||
const value = event.target.value
|
||||
console.info('Query changed', { query: value })
|
||||
setQuery(value)
|
||||
const targetInput = event.target
|
||||
const value = targetInput.value
|
||||
const name = targetInput.name
|
||||
const newQuery:NodeRequestQuery = { ...query }
|
||||
newQuery[name] = value
|
||||
console.info('Query changed', { name, value })
|
||||
setQuery(newQuery)
|
||||
setPage(0)
|
||||
}
|
||||
|
||||
|
@ -104,8 +110,8 @@ const Nodes: React.FC<InferGetServerSidePropsType<typeof getServerSideProps>> =
|
|||
setPage(page + 1)
|
||||
}
|
||||
|
||||
const toggleSort = (sortBy: StatsRequestSortBy) => {
|
||||
const sortWay = sort.sortBy === sortBy && sort.sortWay === 'asc' ? 'desc' : 'asc'
|
||||
const toggleSort = (sortBy: NodeRequestSortBy) => {
|
||||
const sortWay = query.sortBy === sortBy && query.sortWay === 'asc' ? 'desc' : 'asc'
|
||||
getMatomo(matomoConfig).trackEvent({
|
||||
category: 'nodes',
|
||||
action: 'sort',
|
||||
|
@ -116,13 +122,13 @@ const Nodes: React.FC<InferGetServerSidePropsType<typeof getServerSideProps>> =
|
|||
}
|
||||
]
|
||||
})
|
||||
setSort({
|
||||
sortBy: sortBy,
|
||||
sortWay: sortWay
|
||||
})
|
||||
const newQuery:NodeRequestQuery = { ...query }
|
||||
newQuery.sortBy = sortBy
|
||||
newQuery.sortWay = sortWay
|
||||
setQuery(newQuery)
|
||||
}
|
||||
|
||||
useEffect(loadNewQueryResults, [query, submitted, sort])
|
||||
useEffect(loadNewQueryResults, [query, submitted])
|
||||
useEffect(loadNextPageResults, [page])
|
||||
|
||||
return (
|
||||
|
@ -134,13 +140,13 @@ const Nodes: React.FC<InferGetServerSidePropsType<typeof getServerSideProps>> =
|
|||
<form onSubmit={handleSearchSubmit}>
|
||||
<div className={'input-group mb-3'}>
|
||||
<input
|
||||
name={'query'}
|
||||
id={'query'}
|
||||
name={'search'}
|
||||
id={'search'}
|
||||
type={'search'}
|
||||
className={'form-control'}
|
||||
onChange={handleQueryChange}
|
||||
onBlur={handleQueryChange}
|
||||
value={query}
|
||||
value={query.search}
|
||||
placeholder={'Search servers on fediverse'}
|
||||
autoFocus={true}
|
||||
aria-label="Search servers on fediverse"
|
||||
|
@ -161,45 +167,45 @@ const Nodes: React.FC<InferGetServerSidePropsType<typeof getServerSideProps>> =
|
|||
<thead>
|
||||
<tr>
|
||||
<th rowSpan={2}>
|
||||
<SortToggle onToggle={toggleSort} field={'domain'} sort={sort}>
|
||||
<SortToggle onToggle={toggleSort} field={'domain'} sort={query}>
|
||||
Domain
|
||||
</SortToggle>
|
||||
</th>
|
||||
<th rowSpan={2}>
|
||||
<SortToggle onToggle={toggleSort} field={'softwareName'} sort={sort}>
|
||||
<SortToggle onToggle={toggleSort} field={'softwareName'} sort={query}>
|
||||
Software
|
||||
</SortToggle>
|
||||
</th>
|
||||
<th colSpan={3}>User count</th>
|
||||
<th rowSpan={2} className={'number-cell'}>
|
||||
<SortToggle onToggle={toggleSort} field={'statusesCount'} sort={sort}>
|
||||
<SortToggle onToggle={toggleSort} field={'statusesCount'} sort={query}>
|
||||
Statuses
|
||||
</SortToggle>
|
||||
</th>
|
||||
<th rowSpan={2}>
|
||||
<SortToggle onToggle={toggleSort} field={'openRegistrations'} sort={sort}>
|
||||
<SortToggle onToggle={toggleSort} field={'openRegistrations'} sort={query}>
|
||||
Registrations
|
||||
</SortToggle>
|
||||
</th>
|
||||
<th rowSpan={2}>
|
||||
<SortToggle onToggle={toggleSort} field={'refreshedAt'} sort={sort}>
|
||||
<SortToggle onToggle={toggleSort} field={'refreshedAt'} sort={query}>
|
||||
Last refreshed
|
||||
</SortToggle>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th className={'text-end'}>
|
||||
<SortToggle onToggle={toggleSort} field={'totalUserCount'} sort={sort}>
|
||||
<SortToggle onToggle={toggleSort} field={'totalUserCount'} sort={query}>
|
||||
Total
|
||||
</SortToggle>
|
||||
</th>
|
||||
<th className={'text-end'}>
|
||||
<SortToggle onToggle={toggleSort} field={'monthActiveUserCount'} sort={sort}>
|
||||
<SortToggle onToggle={toggleSort} field={'monthActiveUserCount'} sort={query}>
|
||||
Month active
|
||||
</SortToggle>
|
||||
</th>
|
||||
<th className={'text-end'}>
|
||||
<SortToggle onToggle={toggleSort} field={'halfYearActiveUserCount'} sort={sort}>
|
||||
<SortToggle onToggle={toggleSort} field={'halfYearActiveUserCount'} sort={query}>
|
||||
Half year active
|
||||
</SortToggle>
|
||||
</th>
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
import { z } from 'zod'
|
||||
import { preserveUndefined, stringToInt, transform } from '../lib/transform'
|
||||
import { preserveUndefined, stringToInt, stringTrimmed, transform } from '../lib/transform'
|
||||
|
||||
export const feedRequestQuerySchema = z.object({
|
||||
search: z.string().optional()
|
||||
search: transform(
|
||||
z.string().optional(),
|
||||
stringTrimmed,
|
||||
z.string()
|
||||
)
|
||||
/*
|
||||
softwareName: z.string().optional(),
|
||||
domain: z.string().optional(),
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { z } from 'zod'
|
||||
import { preserveUndefined, stringToInt, transform } from '../lib/transform'
|
||||
import { preserveUndefined, stringToInt, stringTrimmed, transform, undefinedToDefault } from '../lib/transform'
|
||||
|
||||
export const statsRequestSortBySchema = z.enum([
|
||||
export const nodeRequestSortBySchema = z.enum([
|
||||
'softwareName',
|
||||
'softwareVersion',
|
||||
'totalUserCount',
|
||||
|
@ -13,15 +13,30 @@ export const statsRequestSortBySchema = z.enum([
|
|||
'domain'
|
||||
])
|
||||
|
||||
export const statsRequestSortWaySchema = z.enum([
|
||||
export const nodeRequestSortWaySchema = z.enum([
|
||||
'asc',
|
||||
'desc'
|
||||
])
|
||||
|
||||
export const nodeRequestSchema = z.object({
|
||||
sortBy: z.optional(statsRequestSortBySchema),
|
||||
sortWay: z.optional(statsRequestSortWaySchema),
|
||||
search: z.string().optional(),
|
||||
export const nodeRequestQuerySchema = z.object({
|
||||
sortBy: transform(
|
||||
z.optional(nodeRequestSortBySchema),
|
||||
undefinedToDefault<NodeRequestSortBy>('refreshedAt'),
|
||||
nodeRequestSortBySchema
|
||||
),
|
||||
sortWay: transform(
|
||||
z.optional(nodeRequestSortWaySchema),
|
||||
undefinedToDefault<NodeRequestSortWay>('desc'),
|
||||
nodeRequestSortWaySchema
|
||||
),
|
||||
search: transform(
|
||||
z.string().optional(),
|
||||
stringTrimmed,
|
||||
z.string()
|
||||
)
|
||||
})
|
||||
|
||||
export const nodeRequestSchema = nodeRequestQuerySchema.extend({
|
||||
page: transform(
|
||||
z.string().optional(),
|
||||
preserveUndefined(stringToInt),
|
||||
|
@ -29,4 +44,7 @@ export const nodeRequestSchema = z.object({
|
|||
)
|
||||
})
|
||||
|
||||
export type NodeRequestQuery = z.infer<typeof nodeRequestQuerySchema>
|
||||
export type NodeRequest = z.infer<typeof nodeRequestSchema>
|
||||
export type NodeRequestSortWay = z.infer<typeof nodeRequestSortWaySchema>
|
||||
export type NodeRequestSortBy = z.infer<typeof nodeRequestSortBySchema>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export type Sort = {
|
||||
sortBy: string,
|
||||
sortWay: 'asc' | 'desc'
|
||||
sortBy?: string,
|
||||
sortWay?: 'asc' | 'desc'
|
||||
}
|
||||
|
|
Ładowanie…
Reference in New Issue