|
|
@@ -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 [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 [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 [tagsStr, setTagsStr] = useState ('') |
|
|
const [title, setTitle] = 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 [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> |
|
|
|
|
|
<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> |
|
|
<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"> |
|
|
|
|
|
<table className="table-auto w-full border-collapse"> |
|
|
|
|
|
<thead> |
|
|
|
|
|
<tr> |
|
|
|
|
|
</tr> |
|
|
|
|
|
</thead> |
|
|
|
|
|
</table> |
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
{results.length > 0 && ( |
|
|
|
|
|
<div className="mt-4"> |
|
|
|
|
|
<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>) |
|
|
</MainArea>) |
|
|
}) satisfies FC |
|
|
}) satisfies FC |