feat: 広場のページネーション(#169) (#189)
#169 バグ修正 Merge remote-tracking branch 'origin/main' into feature/169 #169 Co-authored-by: miteruzo <miteruzo@naver.com> Reviewed-on: #189
This commit was merged in pull request #189.
This commit is contained in:
@@ -3,20 +3,35 @@ class PostsController < ApplicationController
|
|||||||
|
|
||||||
# GET /posts
|
# GET /posts
|
||||||
def index
|
def index
|
||||||
limit = params[:limit].presence&.to_i
|
page = (params[:page].presence || 1).to_i
|
||||||
|
limit = (params[:limit].presence || 20).to_i
|
||||||
cursor = params[:cursor].presence
|
cursor = params[:cursor].presence
|
||||||
|
|
||||||
created_at = ('COALESCE(posts.original_created_before - INTERVAL 1 SECOND,' +
|
page = 1 if page < 1
|
||||||
'posts.original_created_from,' +
|
limit = 1 if limit < 1
|
||||||
'posts.created_at)')
|
|
||||||
q = filtered_posts.order(Arel.sql("#{ created_at } DESC"))
|
|
||||||
q = q.where("#{ created_at } < ?", Time.iso8601(cursor)) if cursor
|
|
||||||
|
|
||||||
posts = limit ? q.limit(limit + 1) : q
|
offset = (page - 1) * limit
|
||||||
|
|
||||||
|
sort_sql =
|
||||||
|
'COALESCE(posts.original_created_before - INTERVAL 1 SECOND,' +
|
||||||
|
'posts.original_created_from,' +
|
||||||
|
'posts.created_at)'
|
||||||
|
q =
|
||||||
|
filtered_posts
|
||||||
|
.preload(:tags)
|
||||||
|
.with_attached_thumbnail
|
||||||
|
.select("posts.*, #{ sort_sql } AS sort_ts")
|
||||||
|
.order(Arel.sql("#{ sort_sql } DESC"))
|
||||||
|
posts = (
|
||||||
|
if cursor
|
||||||
|
q.where("#{ sort_sql } < ?", Time.iso8601(cursor)).limit(limit + 1)
|
||||||
|
else
|
||||||
|
q.limit(limit).offset(offset)
|
||||||
|
end).to_a
|
||||||
|
|
||||||
next_cursor = nil
|
next_cursor = nil
|
||||||
if limit && posts.size > limit
|
if cursor && posts.length > limit
|
||||||
next_cursor = posts.last.created_at.iso8601(6)
|
next_cursor = posts.last.read_attribute('sort_ts').iso8601(6)
|
||||||
posts = posts.first(limit)
|
posts = posts.first(limit)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -29,7 +44,7 @@ class PostsController < ApplicationController
|
|||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
}, next_cursor: }
|
}, count: filtered_posts.count(:id), next_cursor: }
|
||||||
end
|
end
|
||||||
|
|
||||||
def random
|
def random
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ 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'
|
||||||
import WikiBody from '@/components/WikiBody'
|
import WikiBody from '@/components/WikiBody'
|
||||||
|
import Pagination from '@/components/common/Pagination'
|
||||||
import TabGroup, { Tab } from '@/components/common/TabGroup'
|
import TabGroup, { Tab } from '@/components/common/TabGroup'
|
||||||
import MainArea from '@/components/layout/MainArea'
|
import MainArea from '@/components/layout/MainArea'
|
||||||
import { API_BASE_URL, SITE_TITLE } from '@/config'
|
import { API_BASE_URL, SITE_TITLE } from '@/config'
|
||||||
@@ -23,6 +24,7 @@ export default () => {
|
|||||||
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 [totalPages, setTotalPages] = useState (0)
|
||||||
const [wikiPage, setWikiPage] = useState<WikiPage | null> (null)
|
const [wikiPage, setWikiPage] = useState<WikiPage | null> (null)
|
||||||
|
|
||||||
const loadMore = async (withCursor: boolean) => {
|
const loadMore = async (withCursor: boolean) => {
|
||||||
@@ -31,15 +33,19 @@ export default () => {
|
|||||||
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',
|
||||||
limit: '20',
|
...(page && { page }),
|
||||||
|
...(limit && { limit }),
|
||||||
...(withCursor && { cursor }) } })
|
...(withCursor && { cursor }) } })
|
||||||
const data = toCamel (res.data as any, { deep: true }) as { posts: Post[]
|
const data = toCamel (res.data as any, { deep: true }) as {
|
||||||
nextCursor: string }
|
posts: Post[]
|
||||||
|
count: number
|
||||||
|
nextCursor: string }
|
||||||
setPosts (posts => (
|
setPosts (posts => (
|
||||||
[...((new Map ([...(withCursor ? posts : []), ...data.posts]
|
[...((new Map ([...(withCursor ? posts : []), ...data.posts]
|
||||||
.map (post => [post.id, post])))
|
.map (post => [post.id, post])))
|
||||||
.values ())]))
|
.values ())]))
|
||||||
setCursor (data.nextCursor)
|
setCursor (data.nextCursor)
|
||||||
|
setTotalPages (Math.ceil (data.count / limit))
|
||||||
|
|
||||||
setLoading (false)
|
setLoading (false)
|
||||||
}
|
}
|
||||||
@@ -49,6 +55,8 @@ export default () => {
|
|||||||
const tagsQuery = query.get ('tags') ?? ''
|
const tagsQuery = query.get ('tags') ?? ''
|
||||||
const anyFlg = query.get ('match') === 'any'
|
const anyFlg = query.get ('match') === 'any'
|
||||||
const tags = tagsQuery.split (' ').filter (e => e !== '')
|
const tags = tagsQuery.split (' ').filter (e => e !== '')
|
||||||
|
const page = Number (query.get ('page') ?? 1)
|
||||||
|
const limit = Number (query.get ('limit') ?? 20)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const observer = new IntersectionObserver (entries => {
|
const observer = new IntersectionObserver (entries => {
|
||||||
@@ -65,7 +73,8 @@ export default () => {
|
|||||||
}, [loaderRef, loading])
|
}, [loaderRef, loading])
|
||||||
|
|
||||||
useLayoutEffect (() => {
|
useLayoutEffect (() => {
|
||||||
const savedState = sessionStorage.getItem (`posts:${ tagsQuery }`)
|
// TODO: 無限ロード用
|
||||||
|
const savedState = /* sessionStorage.getItem (`posts:${ tagsQuery }`) */ null
|
||||||
if (savedState && navigationType === 'POP')
|
if (savedState && navigationType === 'POP')
|
||||||
{
|
{
|
||||||
const { posts, cursor, scroll } = JSON.parse (savedState)
|
const { posts, cursor, scroll } = JSON.parse (savedState)
|
||||||
@@ -116,18 +125,23 @@ export default () => {
|
|||||||
<MainArea>
|
<MainArea>
|
||||||
<TabGroup>
|
<TabGroup>
|
||||||
<Tab name="広場">
|
<Tab name="広場">
|
||||||
{posts.length
|
{posts.length > 0
|
||||||
? (
|
? (
|
||||||
<PostList posts={posts} onClick={() => {
|
<>
|
||||||
const statesToSave = {
|
<PostList posts={posts} onClick={() => {
|
||||||
posts, cursor,
|
// TODO: 無限ロード用なので復活時に戻す.
|
||||||
scroll: containerRef.current?.scrollTop ?? 0 }
|
// const statesToSave = {
|
||||||
sessionStorage.setItem (`posts:${ tagsQuery }`,
|
// posts, cursor,
|
||||||
JSON.stringify (statesToSave))
|
// scroll: containerRef.current?.scrollTop ?? 0 }
|
||||||
}}/>)
|
// sessionStorage.setItem (`posts:${ tagsQuery }`,
|
||||||
|
// JSON.stringify (statesToSave))
|
||||||
|
}}/>
|
||||||
|
<Pagination page={page} totalPages={totalPages}/>
|
||||||
|
</>)
|
||||||
: !(loading) && '広場には何もありませんよ.'}
|
: !(loading) && '広場には何もありませんよ.'}
|
||||||
{loading && 'Loading...'}
|
{loading && 'Loading...'}
|
||||||
<div ref={loaderRef} className="h-12"/>
|
{/* TODO: 無限ローディング復活までコメント・アウト */}
|
||||||
|
{/* <div ref={loaderRef} className="h-12"/> */}
|
||||||
</Tab>
|
</Tab>
|
||||||
{tags.length === 1 && (
|
{tags.length === 1 && (
|
||||||
<Tab name="Wiki">
|
<Tab name="Wiki">
|
||||||
|
|||||||
Reference in New Issue
Block a user