This commit is contained in:
2026-02-25 00:56:37 +09:00
parent 2a412b589f
commit 3e5eb4687b
5 changed files with 135 additions and 52 deletions
+18 -12
View File
@@ -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 }`)
+12 -2
View File
@@ -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 }) =>
+1 -1
View File
@@ -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 (() => {
+101 -35
View File
@@ -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
+3 -2
View File
@@ -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