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