Reviewed-on: #397 Co-authored-by: miteruzo <miteruzo@naver.com> Co-committed-by: miteruzo <miteruzo@naver.com>
このコミットはPull リクエスト #397 でマージされました.
このコミットが含まれているのは:
@@ -14,8 +14,20 @@ import Pagination from '@/components/common/Pagination'
|
||||
import TagInput from '@/components/common/TagInput'
|
||||
import MainArea from '@/components/layout/MainArea'
|
||||
import { SITE_TITLE } from '@/config'
|
||||
import { clientAnimationTransition,
|
||||
clientScrollBehaviour } from '@/lib/clientAnimation'
|
||||
import { fetchPosts } from '@/lib/posts'
|
||||
import { postsKeys } from '@/lib/queryKeys'
|
||||
import {
|
||||
DEFAULT_POST_LIST_LIMIT,
|
||||
DEFAULT_POST_LIST_ORDER,
|
||||
getClientListLimit,
|
||||
getClientListOrder,
|
||||
LIST_LIMIT_OPTIONS,
|
||||
setClientListSettings,
|
||||
} from '@/lib/settings'
|
||||
import { useClientBehaviourSettings } from '@/lib/useClientBehaviourSettings'
|
||||
import { useKeyboardShortcuts } from '@/lib/useKeyboardShortcuts'
|
||||
import { dateString, inputClass, originalCreatedAtString } from '@/lib/utils'
|
||||
|
||||
import type { FC, FormEvent } from 'react'
|
||||
@@ -31,8 +43,40 @@ const setIf = (qs: URLSearchParams, k: string, v: string | null) => {
|
||||
qs.set (k, t)
|
||||
}
|
||||
|
||||
const parseLimit = (value: string | null): 20 | 50 | 100 | null => {
|
||||
const n = Number (value)
|
||||
return n === 20 || n === 50 || n === 100 ? n : null
|
||||
}
|
||||
|
||||
const parseOrder = (value: string | null): FetchPostsOrder | null => {
|
||||
switch (value)
|
||||
{
|
||||
case 'title:asc':
|
||||
case 'title:desc':
|
||||
case 'url:asc':
|
||||
case 'url:desc':
|
||||
case 'original_created_at:asc':
|
||||
case 'original_created_at:desc':
|
||||
case 'created_at:asc':
|
||||
case 'created_at:desc':
|
||||
case 'updated_at:asc':
|
||||
case 'updated_at:desc':
|
||||
return value
|
||||
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const PostSearchPage: FC = () => {
|
||||
const behaviourSettings = useClientBehaviourSettings ()
|
||||
const animationMode = behaviourSettings.animation ?? 'normal'
|
||||
const thumbnailTransition = clientAnimationTransition (
|
||||
animationMode,
|
||||
{ normal: { type: 'spring', stiffness: 500, damping: 40, mass: .5 },
|
||||
reduced: { duration: .08, ease: 'linear' as const } },
|
||||
)
|
||||
const location = useLocation ()
|
||||
|
||||
const navigate = useNavigate ()
|
||||
@@ -41,7 +85,11 @@ const PostSearchPage: FC = () => {
|
||||
[location.search])
|
||||
|
||||
const page = Number (query.get ('page') ?? 1)
|
||||
const limit = Number (query.get ('limit') ?? 20)
|
||||
const limit = (
|
||||
parseLimit (query.get ('limit'))
|
||||
?? getClientListLimit ('postSearch')
|
||||
?? DEFAULT_POST_LIST_LIMIT
|
||||
)
|
||||
|
||||
const qURL = query.get ('url') ?? ''
|
||||
const qTitle = query.get ('title') ?? ''
|
||||
@@ -53,7 +101,11 @@ const PostSearchPage: FC = () => {
|
||||
const qCreatedTo = query.get ('created_to') ?? ''
|
||||
const qUpdatedFrom = query.get ('updated_from') ?? ''
|
||||
const qUpdatedTo = query.get ('updated_to') ?? ''
|
||||
const order = (query.get ('order') || 'original_created_at:desc') as FetchPostsOrder
|
||||
const order = (
|
||||
parseOrder (query.get ('order'))
|
||||
?? getClientListOrder<FetchPostsOrder> ('postSearch')
|
||||
?? DEFAULT_POST_LIST_ORDER
|
||||
)
|
||||
|
||||
const [createdFrom, setCreatedFrom] = useState<string | null> (null)
|
||||
const [createdTo, setCreatedTo] = useState<string | null> (null)
|
||||
@@ -83,6 +135,10 @@ const PostSearchPage: FC = () => {
|
||||
const results = data?.posts ?? []
|
||||
const totalPages = data ? Math.ceil (data.count / limit) : 0
|
||||
|
||||
useEffect (() => {
|
||||
setClientListSettings ('postSearch', { limit, order })
|
||||
}, [limit, order])
|
||||
|
||||
useEffect (() => {
|
||||
setURL (qURL ?? '')
|
||||
setTitle (qTitle ?? '')
|
||||
@@ -95,9 +151,11 @@ const PostSearchPage: FC = () => {
|
||||
setUpdatedFrom (qUpdatedFrom)
|
||||
setUpdatedTo (qUpdatedTo)
|
||||
|
||||
document.querySelector ('table')?.scrollIntoView ({ behavior: 'smooth' })
|
||||
}, [location.search, qCreatedFrom, qCreatedTo, qMatch, qOriginalCreatedFrom,
|
||||
qOriginalCreatedTo, qTags, qTitle, qUpdatedFrom, qUpdatedTo, qURL])
|
||||
document.querySelector ('table')?.scrollIntoView ({
|
||||
behavior: clientScrollBehaviour (animationMode) })
|
||||
}, [animationMode, location.search, qCreatedFrom, qCreatedTo, qMatch,
|
||||
qOriginalCreatedFrom, qOriginalCreatedTo, qTags, qTitle, qUpdatedFrom,
|
||||
qUpdatedTo, qURL])
|
||||
|
||||
const search = async () => {
|
||||
const qs = new URLSearchParams ()
|
||||
@@ -113,6 +171,22 @@ const PostSearchPage: FC = () => {
|
||||
qs.set ('match', matchType)
|
||||
qs.set ('page', '1')
|
||||
qs.set ('order', order)
|
||||
qs.set ('limit', String (limit))
|
||||
navigate (`${ location.pathname }?${ qs.toString () }`)
|
||||
}
|
||||
|
||||
const updateListQuery = (next: { limit?: 20 | 50 | 100
|
||||
order?: FetchPostsOrder }) => {
|
||||
const qs = new URLSearchParams (location.search)
|
||||
qs.set ('limit', String (next.limit ?? limit))
|
||||
qs.set ('order', next.order ?? order)
|
||||
qs.set ('page', '1')
|
||||
navigate (`${ location.pathname }?${ qs.toString () }`)
|
||||
}
|
||||
|
||||
const updatePage = (nextPage: number) => {
|
||||
const qs = new URLSearchParams (location.search)
|
||||
qs.set ('page', String (Math.max (1, nextPage)))
|
||||
navigate (`${ location.pathname }?${ qs.toString () }`)
|
||||
}
|
||||
|
||||
@@ -121,6 +195,23 @@ const PostSearchPage: FC = () => {
|
||||
search ()
|
||||
}
|
||||
|
||||
useKeyboardShortcuts (useMemo (() => ({
|
||||
...(page > 1
|
||||
? {
|
||||
'pagination.previous': () => {
|
||||
updatePage (page - 1)
|
||||
},
|
||||
}
|
||||
: { }),
|
||||
...(totalPages > 0 && page < totalPages
|
||||
? {
|
||||
'pagination.next': () => {
|
||||
updatePage (Math.min (page + 1, totalPages))
|
||||
},
|
||||
}
|
||||
: { }),
|
||||
}), [page, totalPages, location.search]))
|
||||
|
||||
const defaultDirection = { title: 'asc',
|
||||
url: 'asc',
|
||||
original_created_at: 'desc',
|
||||
@@ -134,7 +225,24 @@ const PostSearchPage: FC = () => {
|
||||
</Helmet>
|
||||
|
||||
<div className="max-w-xl">
|
||||
<PageTitle>広場検索</PageTitle>
|
||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
||||
<PageTitle>広場検索</PageTitle>
|
||||
<label className="flex items-center gap-2 text-xs text-muted-foreground">
|
||||
<span>件数</span>
|
||||
<select
|
||||
className="rounded border border-border bg-background px-2 py-1 text-sm
|
||||
text-foreground"
|
||||
value={limit}
|
||||
onChange={ev => updateListQuery ({
|
||||
limit: Number (ev.target.value) as 20 | 50 | 100,
|
||||
})}>
|
||||
{LIST_LIMIT_OPTIONS.map (value => (
|
||||
<option key={value} value={value}>
|
||||
{value}
|
||||
</option>))}
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSearch} className="space-y-2">
|
||||
{/* タイトル */}
|
||||
@@ -142,6 +250,7 @@ const PostSearchPage: FC = () => {
|
||||
{({ invalid }) => (
|
||||
<input
|
||||
type="text"
|
||||
data-shortcut-focus-search="true"
|
||||
value={title}
|
||||
onChange={e => setTitle (e.target.value)}
|
||||
className={inputClass (invalid)}/>)}
|
||||
@@ -161,6 +270,7 @@ const PostSearchPage: FC = () => {
|
||||
<FormField label="タグ">
|
||||
{() => (
|
||||
<TagInput
|
||||
includeNico={true}
|
||||
value={tagsStr}
|
||||
setValue={setTagsStr}/>)}
|
||||
</FormField>
|
||||
@@ -296,15 +406,16 @@ const PostSearchPage: FC = () => {
|
||||
</thead>
|
||||
<tbody>
|
||||
{results.map (row => (
|
||||
<tr key={row.id} className="even:bg-gray-100 dark:even:bg-gray-700">
|
||||
<tr
|
||||
key={row.id}
|
||||
className="even:bg-gray-100 dark:even:bg-gray-700">
|
||||
<td className="p-2">
|
||||
<PrefetchLink to={`/posts/${ row.id }`} title={row.title || undefined}>
|
||||
<motion.div
|
||||
layoutId={`page-${ row.id }`}
|
||||
transition={{ type: 'spring',
|
||||
stiffness: 500,
|
||||
damping: 40,
|
||||
mass: .5 }}>
|
||||
layoutId={animationMode === 'off'
|
||||
? undefined
|
||||
: `page-${ row.id }`}
|
||||
transition={thumbnailTransition}>
|
||||
<img src={row.thumbnail || row.thumbnailBase || undefined}
|
||||
alt={row.title || row.url}
|
||||
title={row.title || row.url || undefined}
|
||||
|
||||
新しい課題から参照
ユーザをブロックする