188 行
5.4 KiB
TypeScript
188 行
5.4 KiB
TypeScript
import { useQuery } from '@tanstack/react-query'
|
|
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
|
|
import { Helmet } from 'react-helmet-async'
|
|
import { useLocation, useNavigate } from 'react-router-dom'
|
|
|
|
import PostList from '@/components/PostList'
|
|
import PrefetchLink from '@/components/PrefetchLink'
|
|
import TagSidebar from '@/components/TagSidebar'
|
|
import WikiBody from '@/components/WikiBody'
|
|
import Pagination from '@/components/common/Pagination'
|
|
import TabGroup, { Tab } from '@/components/common/TabGroup'
|
|
import MainArea from '@/components/layout/MainArea'
|
|
import { SITE_TITLE } from '@/config'
|
|
import { fetchPosts } from '@/lib/posts'
|
|
import { postsKeys } from '@/lib/queryKeys'
|
|
import {
|
|
DEFAULT_POST_LIST_LIMIT,
|
|
getClientListLimit,
|
|
LIST_LIMIT_OPTIONS,
|
|
setClientListSettings,
|
|
} from '@/lib/settings'
|
|
import { useKeyboardShortcuts } from '@/lib/useKeyboardShortcuts'
|
|
import { fetchWikiPageByTitle } from '@/lib/wiki'
|
|
|
|
import type { FC } from 'react'
|
|
|
|
import type { WikiPage } from '@/types'
|
|
|
|
const parseLimit = (value: string | null): 20 | 50 | 100 | null => {
|
|
const n = Number (value)
|
|
return n === 20 || n === 50 || n === 100 ? n : null
|
|
}
|
|
|
|
|
|
const PostListPage: FC = () => {
|
|
const containerRef = useRef<HTMLDivElement | null> (null)
|
|
|
|
const [wikiPage, setWikiPage] = useState<WikiPage | null> (null)
|
|
|
|
const location = useLocation ()
|
|
const navigate = useNavigate ()
|
|
const query = new URLSearchParams (location.search)
|
|
const tagsQuery = query.get ('tags') ?? ''
|
|
const anyFlg = query.get ('match') === 'any'
|
|
const match = anyFlg ? 'any' : 'all'
|
|
const tags = useMemo (() => tagsQuery.split (' ').filter (e => e !== ''), [tagsQuery])
|
|
const tagsKey = tags.join (' ')
|
|
const page = Number (query.get ('page') ?? 1)
|
|
const limit = (
|
|
parseLimit (query.get ('limit'))
|
|
?? getClientListLimit ('postList')
|
|
?? DEFAULT_POST_LIST_LIMIT
|
|
)
|
|
const order = 'original_created_at:desc' as const
|
|
|
|
const keys = {
|
|
tags: tagsKey, match, page, limit,
|
|
url: '', title: '', originalCreatedFrom: '', originalCreatedTo: '',
|
|
createdFrom: '', createdTo: '', updatedFrom: '', updatedTo: '',
|
|
order } as const
|
|
const { data, isLoading: loading } = useQuery ({
|
|
queryKey: postsKeys.index (keys),
|
|
queryFn: () => fetchPosts (keys) })
|
|
const posts = data?.posts ?? []
|
|
const cursor = ''
|
|
const totalPages = data ? Math.ceil (data.count / limit) : 0
|
|
|
|
useEffect (() => {
|
|
setClientListSettings ('postList', { limit })
|
|
}, [limit])
|
|
|
|
const updatePage = (nextPage: number) => {
|
|
const params = new URLSearchParams (location.search)
|
|
params.set ('page', String (Math.max (1, nextPage)))
|
|
navigate (`${ location.pathname }?${ params.toString () }`)
|
|
}
|
|
|
|
const updateListQuery = (next: { limit?: 20 | 50 | 100 }) => {
|
|
const params = new URLSearchParams (location.search)
|
|
params.set ('limit', String (next.limit ?? limit))
|
|
params.set ('page', '1')
|
|
navigate (`${ location.pathname }?${ params.toString () }`)
|
|
}
|
|
|
|
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]))
|
|
|
|
useLayoutEffect (() => {
|
|
scroll (0, 0)
|
|
|
|
setWikiPage (null)
|
|
|
|
if (tags.length !== 1)
|
|
return
|
|
|
|
void (async () => {
|
|
try
|
|
{
|
|
const tagName = tags[0]
|
|
setWikiPage (await fetchWikiPageByTitle (tagName, { }))
|
|
}
|
|
catch
|
|
{
|
|
;
|
|
}
|
|
}) ()
|
|
}, [location.search, tags])
|
|
|
|
return (
|
|
<div
|
|
data-sidebar-container
|
|
className="md:flex md:flex-1 overflow-y-auto md:overflow-y-hidden"
|
|
ref={containerRef}>
|
|
<Helmet>
|
|
<title>
|
|
{tags.length
|
|
? `${ tags.join (anyFlg ? ' or ' : ' and ') } | ${ SITE_TITLE }`
|
|
: `${ SITE_TITLE }\u3000〜 ぼざろクリーチャーシリーズ綜合リンク集サイト`}
|
|
</title>
|
|
</Helmet>
|
|
|
|
<TagSidebar posts={posts.slice (0, 20)} onClick={() => {
|
|
const statesToSave = {
|
|
posts, cursor,
|
|
scroll: containerRef.current?.scrollTop ?? 0 }
|
|
sessionStorage.setItem (`posts:${ tagsQuery }`,
|
|
JSON.stringify (statesToSave))
|
|
}}/>
|
|
|
|
<MainArea>
|
|
<TabGroup
|
|
rightAddon={
|
|
<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>
|
|
}>
|
|
<Tab name="広場">
|
|
{posts.length > 0
|
|
? (
|
|
<>
|
|
<PostList posts={posts}/>
|
|
<Pagination page={page} totalPages={totalPages}/>
|
|
</>)
|
|
: !(loading) && '広場には何もありませんよ.'}
|
|
{loading && 'Loading...'}
|
|
</Tab>
|
|
{tags.length === 1 && (
|
|
<Tab name="Wiki">
|
|
<WikiBody title={tags[0]} body={wikiPage?.body}/>
|
|
<div className="my-2">
|
|
<PrefetchLink to={`/wiki/${ encodeURIComponent (tags[0]) }`}>
|
|
Wiki を見る
|
|
</PrefetchLink>
|
|
</div>
|
|
</Tab>)}
|
|
</TabGroup>
|
|
</MainArea>
|
|
</div>)
|
|
}
|
|
|
|
export default PostListPage
|