145 lines
4.0 KiB
TypeScript
145 lines
4.0 KiB
TypeScript
import axios from 'axios'
|
|
import toCamel from 'camelcase-keys'
|
|
import { useEffect, useLayoutEffect, useRef, useState } from 'react'
|
|
import { Helmet } from 'react-helmet-async'
|
|
import { Link, useLocation, useNavigationType } from 'react-router-dom'
|
|
|
|
import PostList from '@/components/PostList'
|
|
import TagSidebar from '@/components/TagSidebar'
|
|
import WikiBody from '@/components/WikiBody'
|
|
import TabGroup, { Tab } from '@/components/common/TabGroup'
|
|
import MainArea from '@/components/layout/MainArea'
|
|
import { API_BASE_URL, SITE_TITLE } from '@/config'
|
|
|
|
import type { Post, WikiPage } from '@/types'
|
|
|
|
|
|
export default () => {
|
|
const navigationType = useNavigationType ()
|
|
|
|
const containerRef = useRef<HTMLDivElement | null> (null)
|
|
const loaderRef = useRef<HTMLDivElement | null> (null)
|
|
|
|
const [cursor, setCursor] = useState ('')
|
|
const [loading, setLoading] = useState (false)
|
|
const [posts, setPosts] = useState<Post[]> ([])
|
|
const [wikiPage, setWikiPage] = useState<WikiPage | null> (null)
|
|
|
|
const loadMore = async (withCursor: boolean) => {
|
|
setLoading (true)
|
|
|
|
const res = await axios.get (`${ API_BASE_URL }/posts`, {
|
|
params: { tags: tags.join (' '),
|
|
match: anyFlg ? 'any' : 'all',
|
|
limit: '20',
|
|
...(withCursor && { cursor }) } })
|
|
const data = toCamel (res.data as any, { deep: true }) as { posts: Post[]
|
|
nextCursor: string }
|
|
setPosts (posts => (
|
|
[...((new Map ([...(withCursor ? posts : []), ...data.posts]
|
|
.map (post => [post.id, post])))
|
|
.values ())]))
|
|
setCursor (data.nextCursor)
|
|
|
|
setLoading (false)
|
|
}
|
|
|
|
const location = useLocation ()
|
|
const query = new URLSearchParams (location.search)
|
|
const tagsQuery = query.get ('tags') ?? ''
|
|
const anyFlg = query.get ('match') === 'any'
|
|
const tags = tagsQuery.split (' ').filter (e => e !== '')
|
|
|
|
useEffect(() => {
|
|
const observer = new IntersectionObserver (entries => {
|
|
if (entries[0].isIntersecting && !(loading) && cursor)
|
|
loadMore (true)
|
|
}, { threshold: 1 })
|
|
|
|
const target = loaderRef.current
|
|
target && observer.observe (target)
|
|
|
|
return () => {
|
|
target && observer.unobserve (target)
|
|
}
|
|
}, [loaderRef, loading])
|
|
|
|
useLayoutEffect (() => {
|
|
const savedState = sessionStorage.getItem (`posts:${ tagsQuery }`)
|
|
if (savedState && navigationType === 'POP')
|
|
{
|
|
const { posts, cursor, scroll } = JSON.parse (savedState)
|
|
setPosts (posts)
|
|
setCursor (cursor)
|
|
|
|
if (containerRef.current)
|
|
containerRef.current.scrollTop = scroll
|
|
|
|
loadMore (true)
|
|
}
|
|
else
|
|
{
|
|
setPosts ([])
|
|
loadMore (false)
|
|
}
|
|
|
|
setWikiPage (null)
|
|
if (tags.length === 1)
|
|
{
|
|
void (async () => {
|
|
try
|
|
{
|
|
const tagName = tags[0]
|
|
const res = await axios.get (`${ API_BASE_URL }/wiki/title/${ tagName }`)
|
|
setWikiPage (toCamel (res.data as any, { deep: true }) as WikiPage)
|
|
}
|
|
catch
|
|
{
|
|
;
|
|
}
|
|
}) ()
|
|
}
|
|
}, [location.search])
|
|
|
|
return (
|
|
<div className="md:flex md:flex-1" ref={containerRef}>
|
|
<Helmet>
|
|
<title>
|
|
{tags.length
|
|
? `${ tags.join (anyFlg ? ' or ' : ' and ') } | ${ SITE_TITLE }`
|
|
: `${ SITE_TITLE } 〜 ぼざろクリーチャーシリーズ綜合リンク集サイト`}
|
|
</title>
|
|
</Helmet>
|
|
|
|
<TagSidebar posts={posts.slice (0, 20)}/>
|
|
|
|
<MainArea>
|
|
<TabGroup>
|
|
<Tab name="広場">
|
|
{posts.length
|
|
? (
|
|
<PostList posts={posts} onClick={() => {
|
|
const statesToSave = {
|
|
posts, cursor,
|
|
scroll: containerRef.current?.scrollTop ?? 0 }
|
|
sessionStorage.setItem (`posts:${ tagsQuery }`,
|
|
JSON.stringify (statesToSave))
|
|
}}/>)
|
|
: !(loading) && '広場には何もありませんよ.'}
|
|
{loading && 'Loading...'}
|
|
<div ref={loaderRef} className="h-12"/>
|
|
</Tab>
|
|
{tags.length === 1 && (
|
|
<Tab name="Wiki">
|
|
<WikiBody title={tags[0]} body={wikiPage?.body}/>
|
|
<div className="my-2">
|
|
<Link to={`/wiki/${ encodeURIComponent (tags[0]) }`}>
|
|
Wiki を見る
|
|
</Link>
|
|
</div>
|
|
</Tab>)}
|
|
</TabGroup>
|
|
</MainArea>
|
|
</div>)
|
|
}
|