Browse Source

#206

feature/206
みてるぞ 4 days ago
parent
commit
1350ae3d99
3 changed files with 77 additions and 22 deletions
  1. +23
    -5
      backend/app/controllers/posts_controller.rb
  2. +10
    -6
      frontend/src/lib/prefetchers.ts
  3. +44
    -11
      frontend/src/pages/posts/PostSearchPage.tsx

+ 23
- 5
backend/app/controllers/posts_controller.rb View File

@@ -9,6 +9,19 @@ class PostsController < ApplicationController
created_between = params[:created_from].presence, params[:created_to].presence created_between = params[:created_from].presence, params[:created_to].presence
updated_between = params[:updated_from].presence, params[:updated_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 page = (params[:page].presence || 1).to_i
limit = (params[:limit].presence || 20).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] q = q.where('posts.updated_at <= ?', updated_between[1]) if updated_between[1]


sort_sql = sort_sql =
'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"))
if order[0] == 'original_created_at'
'COALESCE(posts.original_created_before - INTERVAL 1 MINUTE,' +
'posts.original_created_from,' +
'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 .limit(limit).offset(offset).to_a


render json: { posts: posts.map { |post| render json: { posts: posts.map { |post|


+ 10
- 6
frontend/src/lib/prefetchers.ts View File

@@ -8,6 +8,8 @@ import { fetchWikiPage,
fetchWikiPageByTitle, fetchWikiPageByTitle,
fetchWikiPages } from '@/lib/wiki' fetchWikiPages } from '@/lib/wiki'


import type { FetchPostsOrder } from '@/types'

type Prefetcher = (qc: QueryClient, url: URL) => Promise<void> type Prefetcher = (qc: QueryClient, url: URL) => Promise<void>


const mPost = match<{ id: string }> ('/posts/:id') 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 m: 'all' | 'any' = url.searchParams.get ('match') === 'any' ? 'any' : 'all'
const page = Number (url.searchParams.get ('page') || 1) const page = Number (url.searchParams.get ('page') || 1)
const limit = Number (url.searchParams.get ('limit') || 20) const limit = Number (url.searchParams.get ('limit') || 20)
const order = url.searchParams.get ('order') as FetchPostsOrder | null


const keys = { const keys = {
tags, match: m, page, limit, tags, match: m, page, limit,
...(qURL && { url: qURL }), ...(qURL && { url: qURL }),
...(title && { title }), ...(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 ({ await qc.prefetchQuery ({
queryKey: postsKeys.index (keys), queryKey: postsKeys.index (keys),


+ 44
- 11
frontend/src/pages/posts/PostSearchPage.tsx View File

@@ -18,7 +18,10 @@ import { postsKeys } from '@/lib/queryKeys'


import type { FC, ChangeEvent, FormEvent, KeyboardEvent } from 'react' 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) => { const setIf = (qs: URLSearchParams, k: string, v: string | null) => {
@@ -49,13 +52,12 @@ export default (() => {
const qCreatedTo = query.get ('created_to') const qCreatedTo = query.get ('created_to')
const qUpdatedFrom = query.get ('updated_from') const qUpdatedFrom = query.get ('updated_from')
const qUpdatedTo = query.get ('updated_to') 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 [activeIndex, setActiveIndex] = useState (-1)
const [createdFrom, setCreatedFrom] = useState (qCreatedFrom) const [createdFrom, setCreatedFrom] = useState (qCreatedFrom)
const [createdTo, setCreatedTo] = useState (qCreatedTo) const [createdTo, setCreatedTo] = useState (qCreatedTo)
const [matchType, setMatchType] = useState (qMatch ?? 'all') const [matchType, setMatchType] = useState (qMatch ?? 'all')
const [order, setOrder] = useState<FetchPostsOrder> ('original_created_at:desc')
const [originalCreatedFrom, setOriginalCreatedFrom] = useState (qOriginalCreatedFrom) const [originalCreatedFrom, setOriginalCreatedFrom] = useState (qOriginalCreatedFrom)
const [originalCreatedTo, setOriginalCreatedTo] = useState (qOriginalCreatedTo) const [originalCreatedTo, setOriginalCreatedTo] = useState (qOriginalCreatedTo)
const [suggestions, setSuggestions] = useState<Tag[]> ([]) const [suggestions, setSuggestions] = useState<Tag[]> ([])
@@ -76,7 +78,7 @@ export default (() => {
...(qCreatedTo && { createdTo: qCreatedTo }), ...(qCreatedTo && { createdTo: qCreatedTo }),
...(qUpdatedFrom && { updatedFrom: qUpdatedFrom }), ...(qUpdatedFrom && { updatedFrom: qUpdatedFrom }),
...(qUpdatedTo && { updatedTo: qUpdatedTo }), ...(qUpdatedTo && { updatedTo: qUpdatedTo }),
...(qOrder && { order: qOrder }) }
...(order && { order }) }
const { data, isLoading: loading } = useQuery ({ const { data, isLoading: loading } = useQuery ({
queryKey: postsKeys.index (keys), queryKey: postsKeys.index (keys),
queryFn: () => fetchPosts (keys) }) queryFn: () => fetchPosts (keys) })
@@ -94,11 +96,32 @@ export default (() => {
setCreatedTo (qCreatedTo) setCreatedTo (qCreatedTo)
setUpdatedFrom (qUpdatedFrom) setUpdatedFrom (qUpdatedFrom)
setUpdatedTo (qUpdatedTo) setUpdatedTo (qUpdatedTo)
setOrder (qOrder)


document.querySelector ('table')?.scrollIntoView ({ behavior: 'smooth' }) document.querySelector ('table')?.scrollIntoView ({ behavior: 'smooth' })
}, [location.search]) }, [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 からのコピペのため,共通化を考へる. // TODO: TagSearch からのコピペのため,共通化を考へる.
const whenChanged = async (ev: ChangeEvent<HTMLInputElement>) => { const whenChanged = async (ev: ChangeEvent<HTMLInputElement>) => {
setTagsStr (ev.target.value) setTagsStr (ev.target.value)
@@ -305,7 +328,7 @@ export default (() => {
<col className="w-72"/> <col className="w-72"/>
<col className="w-80"/> <col className="w-80"/>
<col className="w-[24rem]"/> <col className="w-[24rem]"/>
<col className="w-44"/>
<col className="w-60"/>
<col className="w-44"/> <col className="w-44"/>
<col className="w-44"/> <col className="w-44"/>
</colgroup> </colgroup>
@@ -313,12 +336,22 @@ export default (() => {
<thead className="border-b-2 border-black dark:border-white"> <thead className="border-b-2 border-black dark:border-white">
<tr> <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">タイトル</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">更新日時</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> </tr>
</thead> </thead>
<tbody> <tbody>


Loading…
Cancel
Save