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 Pagination from '@/components/common/Pagination' 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 (null) const loaderRef = useRef (null) const [cursor, setCursor] = useState ('') const [loading, setLoading] = useState (false) const [posts, setPosts] = useState ([]) const [totalPages, setTotalPages] = useState (0) const [wikiPage, setWikiPage] = useState (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', ...(page && { page }), ...(limit && { limit }), ...(withCursor && { cursor }) } }) const data = toCamel (res.data as any, { deep: true }) as { posts: Post[] count: number nextCursor: string } setPosts (posts => ( [...((new Map ([...(withCursor ? posts : []), ...data.posts] .map (post => [post.id, post]))) .values ())])) setCursor (data.nextCursor) setTotalPages (Math.ceil (data.count / limit)) 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 !== '') const page = Number (query.get ('page') ?? 1) const limit = Number (query.get ('limit') ?? 20) 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 (() => { // TODO: 無限ロード用 const savedState = /* sessionStorage.getItem (`posts:${ tagsQuery }`) */ null 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 (
{tags.length ? `${ tags.join (anyFlg ? ' or ' : ' and ') } | ${ SITE_TITLE }` : `${ SITE_TITLE } 〜 ぼざろクリーチャーシリーズ綜合リンク集サイト`} {posts.length > 0 ? ( <> { // TODO: 無限ロード用なので復活時に戻す. // const statesToSave = { // posts, cursor, // scroll: containerRef.current?.scrollTop ?? 0 } // sessionStorage.setItem (`posts:${ tagsQuery }`, // JSON.stringify (statesToSave)) }}/> ) : !(loading) && '広場には何もありませんよ.'} {loading && 'Loading...'} {/* TODO: 無限ローディング復活までコメント・アウト */} {/*
*/} {tags.length === 1 && (
Wiki を見る
)}
) }