This commit is contained in:
+17
-11
@@ -4,22 +4,28 @@ import type { Post, PostTagChange } from '@/types'
|
||||
|
||||
|
||||
export const fetchPosts = async (
|
||||
{ tags, match, page, limit, cursor }: {
|
||||
tags: string
|
||||
match: 'any' | 'all'
|
||||
{ url, title, tags, match, created_from, created_to, updated_from,
|
||||
updated_to, original_created_from, original_created_to, page, limit }: {
|
||||
url?: string
|
||||
title?: string
|
||||
tags?: string
|
||||
match?: 'all' | 'any'
|
||||
created_from?: string
|
||||
created_to?: string
|
||||
updated_from?: string
|
||||
updated_to?: string
|
||||
original_created_from?: string
|
||||
original_created_to?: string
|
||||
page?: number
|
||||
limit?: number
|
||||
cursor?: string }
|
||||
limit?: number },
|
||||
): Promise<{
|
||||
posts: Post[]
|
||||
count: number
|
||||
nextCursor: string }> =>
|
||||
count: number }> =>
|
||||
await apiGet ('/posts', { params: {
|
||||
tags,
|
||||
match,
|
||||
url, title, tags, match, created_from, created_to, updated_from, updated_to,
|
||||
original_created_from, original_created_to,
|
||||
...(page && { page }),
|
||||
...(limit && { limit }),
|
||||
...(cursor && { cursor }) } })
|
||||
...(limit && { limit }) } })
|
||||
|
||||
|
||||
export const fetchPost = async (id: string): Promise<Post> => await apiGet (`/posts/${ id }`)
|
||||
|
||||
@@ -1,7 +1,17 @@
|
||||
export const postsKeys = {
|
||||
root: ['posts'] as const,
|
||||
index: (p: { tags: string; match: 'any' | 'all'; page: number; limit: number }) =>
|
||||
['posts', 'index', p] as const,
|
||||
index: (p: { url?: string
|
||||
title?: string
|
||||
tags?: string
|
||||
match?: 'all' | 'any'
|
||||
created_from?: string
|
||||
created_to?: string
|
||||
updated_from?: string
|
||||
updated_to?: string
|
||||
original_created_from?: string
|
||||
original_created_to?: string
|
||||
page?: number
|
||||
limit?: number }) => ['posts', 'index', p] as const,
|
||||
show: (id: string) => ['posts', id] as const,
|
||||
related: (id: string) => ['related', id] as const,
|
||||
changes: (p: { id?: string; page: number; limit: number }) =>
|
||||
|
||||
@@ -39,7 +39,7 @@ export default (() => {
|
||||
queryKey: postsKeys.index ({ tags: tagsKey, match, page, limit }),
|
||||
queryFn: () => fetchPosts ({ tags: tagsKey, match, page, limit }) })
|
||||
const posts = data?.posts ?? []
|
||||
const cursor = data?.nextCursor ?? ''
|
||||
const cursor = ''
|
||||
const totalPages = data ? Math.ceil (data.count / limit) : 0
|
||||
|
||||
useLayoutEffect (() => {
|
||||
|
||||
@@ -2,12 +2,15 @@ import { useState } from 'react'
|
||||
import { Helmet } from 'react-helmet-async'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
|
||||
import PrefetchLink from '@/components/PrefetchLink'
|
||||
import TagLink from '@/components/TagLink'
|
||||
import DateTimeField from '@/components/common/DateTimeField'
|
||||
import Label from '@/components/common/Label'
|
||||
import SectionTitle from '@/components/common/SectionTitle'
|
||||
import PageTitle from '@/components/common/PageTitle'
|
||||
import Pagination from '@/components/common/Pagination'
|
||||
import MainArea from '@/components/layout/MainArea'
|
||||
import { SITE_TITLE } from '@/config'
|
||||
import { apiGet } from '@/lib/api'
|
||||
import { fetchPosts } from '@/lib/posts'
|
||||
|
||||
import type { FC, FormEvent } from 'react'
|
||||
|
||||
@@ -15,17 +18,18 @@ import type { Post } from '@/types'
|
||||
|
||||
|
||||
export default (() => {
|
||||
const [createdFrom, setCreatedFrom] = useState<string | null> (null)
|
||||
const [createdTo, setCreatedTo] = useState<string | null> (null)
|
||||
const [createdFrom, setCreatedFrom] = useState<string | undefined> ()
|
||||
const [createdTo, setCreatedTo] = useState<string | undefined> ()
|
||||
const [matchType, setMatchType] = useState<'all' | 'any'> ('all')
|
||||
const [originalCreatedFrom, setOriginalCreatedFrom] = useState<string | null> (null)
|
||||
const [originalCreatedTo, setOriginalCreatedTo] = useState<string | null> (null)
|
||||
const [originalCreatedFrom, setOriginalCreatedFrom] = useState<string | undefined> ()
|
||||
const [originalCreatedTo, setOriginalCreatedTo] = useState<string | undefined> ()
|
||||
const [tagsStr, setTagsStr] = useState ('')
|
||||
const [title, setTitle] = useState ('')
|
||||
const [updatedFrom, setUpdatedFrom] = useState<string | null> (null)
|
||||
const [updatedTo, setUpdatedTo] = useState<string | null> (null)
|
||||
const [updatedFrom, setUpdatedFrom] = useState<string | undefined> ()
|
||||
const [updatedTo, setUpdatedTo] = useState<string | undefined> ()
|
||||
const [url, setURL] = useState ('')
|
||||
const [results, setResults] = useState<Post[]> ([])
|
||||
const [totalPages, setTotalPages] = useState (0)
|
||||
|
||||
const location = useLocation ()
|
||||
const query = new URLSearchParams (location.search)
|
||||
@@ -34,13 +38,15 @@ export default (() => {
|
||||
|
||||
const search = async () => {
|
||||
const tags = tagsStr.split (' ').filter (e => e !== '')
|
||||
setResults (await apiGet ('/posts', { params: {
|
||||
const data = await fetchPosts ({
|
||||
url, title, tags: tags.join (' '), match: matchType,
|
||||
created_from: createdFrom, created_to: createdTo,
|
||||
updated_from: updatedFrom, updated_to: updatedTo,
|
||||
original_created_from: originalCreatedFrom,
|
||||
original_created_to: originalCreatedTo,
|
||||
page, limit } }))
|
||||
page, limit })
|
||||
setResults (data.posts)
|
||||
setTotalPages (data ? Math.ceil (data.count / limit) : 0)
|
||||
}
|
||||
|
||||
const handleSearch = (e: FormEvent) => {
|
||||
@@ -55,18 +61,9 @@ export default (() => {
|
||||
</Helmet>
|
||||
|
||||
<div className="max-w-xl">
|
||||
<SectionTitle>広場検索</SectionTitle>
|
||||
<form onSubmit={handleSearch} className="space-y-2">
|
||||
{/* URL */}
|
||||
<div>
|
||||
<Label>URL</Label>
|
||||
<input
|
||||
type="url"
|
||||
value={url}
|
||||
onChange={e => setURL (e.target.value)}
|
||||
className="w-full border p-2 rounded"/>
|
||||
</div>
|
||||
<PageTitle>広場検索</PageTitle>
|
||||
|
||||
<form onSubmit={handleSearch} className="space-y-2">
|
||||
{/* タイトル */}
|
||||
<div>
|
||||
<Label>タイトル</Label>
|
||||
@@ -77,6 +74,16 @@ export default (() => {
|
||||
className="w-full border p-2 rounded"/>
|
||||
</div>
|
||||
|
||||
{/* URL */}
|
||||
<div>
|
||||
<Label>URL</Label>
|
||||
<input
|
||||
type="url"
|
||||
value={url}
|
||||
onChange={e => setURL (e.target.value)}
|
||||
className="w-full border p-2 rounded"/>
|
||||
</div>
|
||||
|
||||
{/* タグ */}
|
||||
<div>
|
||||
<Label>タグ</Label>
|
||||
@@ -111,11 +118,11 @@ export default (() => {
|
||||
<Label>投稿日時</Label>
|
||||
<DateTimeField
|
||||
value={createdFrom ?? undefined}
|
||||
onChange={setCreatedFrom}/>
|
||||
onChange={isoUTC => setCreatedFrom (isoUTC ?? undefined)}/>
|
||||
<span className="mx-1">〜</span>
|
||||
<DateTimeField
|
||||
value={createdTo ?? undefined}
|
||||
onChange={setCreatedTo}/>
|
||||
onChange={isoUTC => setCreatedTo (isoUTC ?? undefined)}/>
|
||||
</div>
|
||||
|
||||
{/* 更新日時 */}
|
||||
@@ -123,11 +130,11 @@ export default (() => {
|
||||
<Label>更新日時</Label>
|
||||
<DateTimeField
|
||||
value={updatedFrom ?? undefined}
|
||||
onChange={setUpdatedFrom}/>
|
||||
onChange={isoUTC => setUpdatedFrom (isoUTC ?? undefined)}/>
|
||||
<span className="mx-1">〜</span>
|
||||
<DateTimeField
|
||||
value={updatedTo ?? undefined}
|
||||
onChange={setUpdatedTo}/>
|
||||
onChange={isoUTC => setUpdatedTo (isoUTC ?? undefined)}/>
|
||||
</div>
|
||||
|
||||
{/* オリジナルの投稿日時 */}
|
||||
@@ -135,11 +142,11 @@ export default (() => {
|
||||
<Label>オリジナルの投稿日時</Label>
|
||||
<DateTimeField
|
||||
value={originalCreatedFrom ?? undefined}
|
||||
onChange={setOriginalCreatedFrom}/>
|
||||
onChange={isoUTC => setOriginalCreatedFrom (isoUTC ?? undefined)}/>
|
||||
<span className="mx-1">〜</span>
|
||||
<DateTimeField
|
||||
value={originalCreatedTo ?? undefined}
|
||||
onChange={setOriginalCreatedTo}/>
|
||||
onChange={isoUTC => setOriginalCreatedTo (isoUTC ?? undefined)}/>
|
||||
</div>
|
||||
|
||||
{/* 検索 */}
|
||||
@@ -153,13 +160,72 @@ export default (() => {
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{results.length > 0 && (
|
||||
<div className="mt-4">
|
||||
<table className="table-auto w-full border-collapse">
|
||||
<thead>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full min-w-[1200px] table-fixed border-collapse">
|
||||
<colgroup>
|
||||
<col className="w-14"/>
|
||||
<col className="w-72"/>
|
||||
<col className="w-80"/>
|
||||
<col className="w-[24rem]"/>
|
||||
<col className="w-44"/>
|
||||
<col className="w-44"/>
|
||||
<col className="w-44"/>
|
||||
</colgroup>
|
||||
|
||||
<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">タグ</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>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{results.map (row => (
|
||||
<tr key={row.id} className={'even:bg-gray-100 dark:even:bg-gray-700'}>
|
||||
<td className="p-2">
|
||||
<PrefetchLink to={`/posts/${ row.id }`} title={row.title}>
|
||||
<img src={row.thumbnail || row.thumbnailBase || undefined}
|
||||
alt={row.title || row.url}
|
||||
title={row.title || row.url || undefined}
|
||||
className="w-8"/>
|
||||
</PrefetchLink>
|
||||
</td>
|
||||
<td className="p-2 truncate">
|
||||
<PrefetchLink to={`/posts/${ row.id }`} title={row.title}>
|
||||
{row.title}
|
||||
</PrefetchLink>
|
||||
</td>
|
||||
<td className="p-2 truncate">
|
||||
<a href={row.url}
|
||||
title={row.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow">
|
||||
{row.url}
|
||||
</a>
|
||||
</td>
|
||||
<td className="p-2">
|
||||
{row.tags.map (t => (
|
||||
<span key={t.id} className="mr-2">
|
||||
<TagLink tag={t} withWiki={false} withCount={false}/>
|
||||
</span>))}
|
||||
</td>
|
||||
<td className="p-2">
|
||||
{row.originalCreatedFrom} 〜 {row.originalCreatedBefore}
|
||||
</td>
|
||||
<td className="p-2">{row.createdAt}</td>
|
||||
<td className="p-2">{row.updatedAt}</td>
|
||||
</tr>))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<Pagination page={page} totalPages={totalPages}/>
|
||||
</div>)}
|
||||
</MainArea>)
|
||||
}) satisfies FC
|
||||
|
||||
@@ -25,9 +25,10 @@ export type Post = {
|
||||
tags: Tag[]
|
||||
viewed: boolean
|
||||
related: Post[]
|
||||
createdAt: string
|
||||
originalCreatedFrom: string | null
|
||||
originalCreatedBefore: string | null }
|
||||
originalCreatedBefore: string | null
|
||||
createdAt: string
|
||||
updatedAt: string }
|
||||
|
||||
export type PostTagChange = {
|
||||
post: Post
|
||||
|
||||
Reference in New Issue
Block a user