This commit is contained in:
2025-07-26 20:40:03 +09:00
parent 64ff3160da
commit 05ef70b335
3 changed files with 47 additions and 19 deletions
+6 -3
View File
@@ -1,16 +1,19 @@
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import type { MouseEvent } from 'react'
import type { Post } from '@/types' import type { Post } from '@/types'
type Props = { posts: Post[] } type Props = { posts: Post[]
onClick?: (event: MouseEvent<HTMLElement>) => void }
export default ({ posts }: Props) => ( export default ({ posts, onClick }: Props) => (
<div className="flex flex-wrap gap-6 p-4"> <div className="flex flex-wrap gap-6 p-4">
{posts.map ((post, i) => ( {posts.map ((post, i) => (
<Link to={`/posts/${ post.id }`} <Link to={`/posts/${ post.id }`}
key={i} key={i}
className="w-40 h-40 overflow-hidden rounded-lg shadow-md hover:shadow-lg"> className="w-40 h-40 overflow-hidden rounded-lg shadow-md hover:shadow-lg"
onClick={onClick}>
<img src={post.thumbnail || post.thumbnailBase || undefined} <img src={post.thumbnail || post.thumbnailBase || undefined}
alt={post.title || post.url} alt={post.title || post.url}
title={post.title || post.url || undefined} title={post.title || post.url || undefined}
+3 -6
View File
@@ -1,4 +1,3 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client' import { createRoot } from 'react-dom/client'
import { HelmetProvider } from 'react-helmet-async' import { HelmetProvider } from 'react-helmet-async'
@@ -8,8 +7,6 @@ import App from '@/App'
const helmetContext = { } const helmetContext = { }
createRoot (document.getElementById ('root')!).render ( createRoot (document.getElementById ('root')!).render (
<StrictMode> <HelmetProvider context={helmetContext}>
<HelmetProvider context={helmetContext}> <App />
<App /> </HelmetProvider>)
</HelmetProvider>
</StrictMode>)
+38 -10
View File
@@ -1,8 +1,8 @@
import axios from 'axios' import axios from 'axios'
import toCamel from 'camelcase-keys' import toCamel from 'camelcase-keys'
import { useEffect, useRef, useState } from 'react' import { useEffect, useLayoutEffect, useRef, useState } from 'react'
import { Helmet } from 'react-helmet-async' import { Helmet } from 'react-helmet-async'
import { Link, useLocation } from 'react-router-dom' import { Link, useLocation, useNavigationType } from 'react-router-dom'
import PostList from '@/components/PostList' import PostList from '@/components/PostList'
import TagSidebar from '@/components/TagSidebar' import TagSidebar from '@/components/TagSidebar'
@@ -15,23 +15,29 @@ import type { Post, WikiPage } from '@/types'
export default () => { export default () => {
const navigationType = useNavigationType ()
const containerRef = useRef<HTMLDivElement | null> (null)
const loaderRef = useRef<HTMLDivElement | null> (null)
const [cursor, setCursor] = useState ('') const [cursor, setCursor] = useState ('')
const [loading, setLoading] = useState (false) const [loading, setLoading] = useState (false)
const [posts, setPosts] = useState<Post[]> ([]) const [posts, setPosts] = useState<Post[]> ([])
const [wikiPage, setWikiPage] = useState<WikiPage | null> (null) const [wikiPage, setWikiPage] = useState<WikiPage | null> (null)
const loaderRef = useRef<HTMLDivElement | null> (null)
const loadMore = async (withCursor: boolean) => { const loadMore = async (withCursor: boolean) => {
setLoading (true) setLoading (true)
const res = await axios.get (`${ API_BASE_URL }/posts`, { const res = await axios.get (`${ API_BASE_URL }/posts`, {
params: { tags: tags.join (' '), params: { tags: tags.join (' '),
match: anyFlg ? 'any' : 'all', match: anyFlg ? 'any' : 'all',
...(withCursor ? { cursor } : { }) } }) limit: '20',
...(withCursor && { cursor }) } })
const data = toCamel (res.data as any, { deep: true }) as { posts: Post[] const data = toCamel (res.data as any, { deep: true }) as { posts: Post[]
nextCursor: string } nextCursor: string }
setPosts (posts => [...(withCursor ? posts : []), ...data.posts]) setPosts (posts => [...(withCursor ? posts : []), ...data.posts])
setCursor (data.nextCursor) setCursor (data.nextCursor)
setLoading (false) setLoading (false)
} }
@@ -55,9 +61,25 @@ export default () => {
} }
}, [loaderRef, loading]) }, [loaderRef, loading])
useEffect (() => { useLayoutEffect (() => {
setPosts ([]) const savedState = sessionStorage.getItem ('posts')
loadMore (false) 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)
}
sessionStorage.removeItem ('posts')
setWikiPage (null) setWikiPage (null)
if (tags.length === 1) if (tags.length === 1)
@@ -78,7 +100,7 @@ export default () => {
}, [location.search]) }, [location.search])
return ( return (
<div className="md:flex md:flex-1"> <div className="md:flex md:flex-1" ref={containerRef}>
<Helmet> <Helmet>
<title> <title>
{tags.length {tags.length
@@ -93,7 +115,13 @@ export default () => {
<TabGroup> <TabGroup>
<Tab name="広場"> <Tab name="広場">
{posts.length {posts.length
? <PostList posts={posts} /> ? (
<PostList posts={posts} onClick={() => {
const statesToSave = {
posts, cursor,
scroll: containerRef.current?.scrollTop ?? 0 }
sessionStorage.setItem ('posts', JSON.stringify (statesToSave))
}} />)
: !(loading) && '広場には何もありませんよ.'} : !(loading) && '広場には何もありませんよ.'}
{loading && 'Loading...'} {loading && 'Loading...'}
<div ref={loaderRef} className="h-12"></div> <div ref={loaderRef} className="h-12"></div>