This commit is contained in:
2026-03-01 15:05:40 +09:00
parent a088503272
commit 1350ae3d99
3 changed files with 77 additions and 22 deletions
+21 -3
View File
@@ -9,6 +9,19 @@ class PostsController < ApplicationController
created_between = params[:created_from].presence, params[:created_to].presence
updated_between = params[:updated_from].presence, params[:updated_to].presence
order = params[:order].to_s.split(':', 2).map(&:strip)
unless order[0].in?(['title', 'url', 'original_created_at', 'created_at', 'updated_at'])
order[0] = 'original_created_at'
end
unless order[1].in?(['asc', 'desc'])
order[1] =
if order[0].in?(['title', 'url'])
'asc'
else
'desc'
end
end
page = (params[:page].presence || 1).to_i
limit = (params[:limit].presence || 20).to_i
@@ -36,11 +49,16 @@ class PostsController < ApplicationController
q = q.where('posts.updated_at <= ?', updated_between[1]) if updated_between[1]
sort_sql =
if order[0] == 'original_created_at'
'COALESCE(posts.original_created_before - INTERVAL 1 MINUTE,' +
'posts.original_created_from,' +
'posts.created_at)'
posts = q.select("posts.*, #{ sort_sql } AS sort_ts")
.order(Arel.sql("#{ sort_sql } DESC"))
'posts.created_at) ' +
order[1]
else
"posts.#{ order[0] } #{ order[1] }"
end
posts = q.select('posts.*')
.order(Arel.sql("#{ sort_sql }"))
.limit(limit).offset(offset).to_a
render json: { posts: posts.map { |post|
+10 -6
View File
@@ -8,6 +8,8 @@ import { fetchWikiPage,
fetchWikiPageByTitle,
fetchWikiPages } from '@/lib/wiki'
import type { FetchPostsOrder } from '@/types'
type Prefetcher = (qc: QueryClient, url: URL) => Promise<void>
const mPost = match<{ id: string }> ('/posts/:id')
@@ -79,17 +81,19 @@ const prefetchPostsIndex: Prefetcher = async (qc, url) => {
const m: 'all' | 'any' = url.searchParams.get ('match') === 'any' ? 'any' : 'all'
const page = Number (url.searchParams.get ('page') || 1)
const limit = Number (url.searchParams.get ('limit') || 20)
const order = url.searchParams.get ('order') as FetchPostsOrder | null
const keys = {
tags, match: m, page, limit,
...(qURL && { url: qURL }),
...(title && { title }),
...(originalCreatedFrom && { original_created_from: originalCreatedFrom }),
...(originalCreatedTo && { original_created_to: originalCreatedTo }),
...(createdFrom && { created_from: createdFrom }),
...(createdTo && { created_to: createdTo }),
...(updatedFrom && { updated_from: updatedFrom }),
...(updatedTo && { updated_to: updatedTo }) }
...(originalCreatedFrom && { originalCreatedFrom }),
...(originalCreatedTo && { originalCreatedTo }),
...(createdFrom && { createdFrom }),
...(createdTo && { createdTo }),
...(updatedFrom && { updatedFrom }),
...(updatedTo && { updatedTo }),
...(order && { order }) }
await qc.prefetchQuery ({
queryKey: postsKeys.index (keys),
+44 -11
View File
@@ -18,7 +18,10 @@ import { postsKeys } from '@/lib/queryKeys'
import type { FC, ChangeEvent, FormEvent, KeyboardEvent } from 'react'
import type { FetchPostsOrder, FetchPostsParams, Tag } from '@/types'
import type { FetchPostsOrder,
FetchPostsOrderField,
FetchPostsParams,
Tag } from '@/types'
const setIf = (qs: URLSearchParams, k: string, v: string | null) => {
@@ -49,13 +52,12 @@ export default (() => {
const qCreatedTo = query.get ('created_to')
const qUpdatedFrom = query.get ('updated_from')
const qUpdatedTo = query.get ('updated_to')
const qOrder = (query.get ('order') || 'original_created_at:desc') as FetchPostsOrder
const order = (query.get ('order') || 'original_created_at:desc') as FetchPostsOrder
const [activeIndex, setActiveIndex] = useState (-1)
const [createdFrom, setCreatedFrom] = useState (qCreatedFrom)
const [createdTo, setCreatedTo] = useState (qCreatedTo)
const [matchType, setMatchType] = useState (qMatch ?? 'all')
const [order, setOrder] = useState<FetchPostsOrder> ('original_created_at:desc')
const [originalCreatedFrom, setOriginalCreatedFrom] = useState (qOriginalCreatedFrom)
const [originalCreatedTo, setOriginalCreatedTo] = useState (qOriginalCreatedTo)
const [suggestions, setSuggestions] = useState<Tag[]> ([])
@@ -76,7 +78,7 @@ export default (() => {
...(qCreatedTo && { createdTo: qCreatedTo }),
...(qUpdatedFrom && { updatedFrom: qUpdatedFrom }),
...(qUpdatedTo && { updatedTo: qUpdatedTo }),
...(qOrder && { order: qOrder }) }
...(order && { order }) }
const { data, isLoading: loading } = useQuery ({
queryKey: postsKeys.index (keys),
queryFn: () => fetchPosts (keys) })
@@ -94,11 +96,32 @@ export default (() => {
setCreatedTo (qCreatedTo)
setUpdatedFrom (qUpdatedFrom)
setUpdatedTo (qUpdatedTo)
setOrder (qOrder)
document.querySelector ('table')?.scrollIntoView ({ behavior: 'smooth' })
}, [location.search])
const SortHeader = ({ by, label }: { by: FetchPostsOrderField; label: string }) => {
const [fld, dir] = order.split (':')
const qs = new URLSearchParams (location.search)
const nextDir =
(by === fld)
? (dir === 'asc' ? 'desc' : 'asc')
: (['title', 'url'].includes (by) ? 'asc' : 'desc')
qs.set ('order', `${ by }:${ nextDir }`)
qs.set ('page', '1')
return (
<PrefetchLink
className="text-inherit visited:text-inherit hover:text-inherit"
to={`${ location.pathname }?${ qs.toString () }`}>
<span className="font-bold">
{label}
{by === fld && (dir === 'asc' ? ' ▲' : ' ▼')}
</span>
</PrefetchLink>)
}
// TODO: TagSearch からのコピペのため,共通化を考へる.
const whenChanged = async (ev: ChangeEvent<HTMLInputElement>) => {
setTagsStr (ev.target.value)
@@ -305,7 +328,7 @@ export default (() => {
<col className="w-72"/>
<col className="w-80"/>
<col className="w-[24rem]"/>
<col className="w-44"/>
<col className="w-60"/>
<col className="w-44"/>
<col className="w-44"/>
</colgroup>
@@ -313,12 +336,22 @@ export default (() => {
<thead className="border-b-2 border-black dark:border-white">
<tr>
<th className="p-2 text-left whitespace-nowrap">稿</th>
<th className="p-2 text-left whitespace-nowrap"></th>
<th className="p-2 text-left whitespace-nowrap">URL</th>
<th className="p-2 text-left whitespace-nowrap">
<SortHeader by="title" label="タイトル"/>
</th>
<th className="p-2 text-left whitespace-nowrap">
<SortHeader by="url" label="URL"/>
</th>
<th className="p-2 text-left whitespace-nowrap"></th>
<th className="p-2 text-left whitespace-nowrap">稿</th>
<th className="p-2 text-left whitespace-nowrap">稿</th>
<th className="p-2 text-left whitespace-nowrap"></th>
<th className="p-2 text-left whitespace-nowrap">
<SortHeader by="original_created_at" label="オリジナルの投稿日時"/>
</th>
<th className="p-2 text-left whitespace-nowrap">
<SortHeader by="created_at" label="投稿日時"/>
</th>
<th className="p-2 text-left whitespace-nowrap">
<SortHeader by="updated_at" label="更新日時"/>
</th>
</tr>
</thead>
<tbody>