素材管理(#99) (#303)
#99 #99 #99 #99 #99 #99 #99 #99 #99 #99 Co-authored-by: miteruzo <miteruzo@naver.com> Reviewed-on: #303
This commit was merged in pull request #303.
This commit is contained in:
@@ -7,24 +7,22 @@ import { useLocation, useNavigate } from 'react-router-dom'
|
||||
import PrefetchLink from '@/components/PrefetchLink'
|
||||
import SortHeader from '@/components/SortHeader'
|
||||
import TagLink from '@/components/TagLink'
|
||||
import TagSearchBox from '@/components/TagSearchBox'
|
||||
import DateTimeField from '@/components/common/DateTimeField'
|
||||
import Label from '@/components/common/Label'
|
||||
import PageTitle from '@/components/common/PageTitle'
|
||||
import Pagination from '@/components/common/Pagination'
|
||||
import TagInput from '@/components/common/TagInput'
|
||||
import MainArea from '@/components/layout/MainArea'
|
||||
import { SITE_TITLE } from '@/config'
|
||||
import { apiGet } from '@/lib/api'
|
||||
import { fetchPosts } from '@/lib/posts'
|
||||
import { postsKeys } from '@/lib/queryKeys'
|
||||
import { dateString, originalCreatedAtString } from '@/lib/utils'
|
||||
|
||||
import type { FC, ChangeEvent, FormEvent, KeyboardEvent } from 'react'
|
||||
import type { FC, FormEvent } from 'react'
|
||||
|
||||
import type { FetchPostsOrder,
|
||||
FetchPostsOrderField,
|
||||
FetchPostsParams,
|
||||
Tag } from '@/types'
|
||||
FetchPostsParams } from '@/types'
|
||||
|
||||
|
||||
const setIf = (qs: URLSearchParams, k: string, v: string | null) => {
|
||||
@@ -57,14 +55,11 @@ export default (() => {
|
||||
const qUpdatedTo = query.get ('updated_to') ?? ''
|
||||
const order = (query.get ('order') || 'original_created_at:desc') as FetchPostsOrder
|
||||
|
||||
const [activeIndex, setActiveIndex] = useState (-1)
|
||||
const [createdFrom, setCreatedFrom] = useState<string | null> (null)
|
||||
const [createdTo, setCreatedTo] = useState<string | null> (null)
|
||||
const [matchType, setMatchType] = useState<'all' | 'any'> ('all')
|
||||
const [originalCreatedFrom, setOriginalCreatedFrom] = useState<string | null> (null)
|
||||
const [originalCreatedTo, setOriginalCreatedTo] = useState<string | null> (null)
|
||||
const [suggestions, setSuggestions] = useState<Tag[]> ([])
|
||||
const [suggestionsVsbl, setSuggestionsVsbl] = useState (false)
|
||||
const [tagsStr, setTagsStr] = useState ('')
|
||||
const [title, setTitle] = useState ('')
|
||||
const [updatedFrom, setUpdatedFrom] = useState<string | null> (null)
|
||||
@@ -103,58 +98,6 @@ export default (() => {
|
||||
document.querySelector ('table')?.scrollIntoView ({ behavior: 'smooth' })
|
||||
}, [location.search])
|
||||
|
||||
// TODO: TagSearch からのコピペのため,共通化を考へる.
|
||||
const whenChanged = async (ev: ChangeEvent<HTMLInputElement>) => {
|
||||
setTagsStr (ev.target.value)
|
||||
|
||||
const q = ev.target.value.trim ().split (' ').at (-1)
|
||||
if (!(q))
|
||||
{
|
||||
setSuggestions ([])
|
||||
return
|
||||
}
|
||||
|
||||
const data = await apiGet<Tag[]> ('/tags/autocomplete', { params: { q } })
|
||||
setSuggestions (data.filter (t => t.postCount > 0))
|
||||
if (suggestions.length > 0)
|
||||
setSuggestionsVsbl (true)
|
||||
}
|
||||
|
||||
// TODO: TagSearch からのコピペのため,共通化を考へる.
|
||||
const handleKeyDown = (ev: KeyboardEvent<HTMLInputElement>) => {
|
||||
switch (ev.key)
|
||||
{
|
||||
case 'ArrowDown':
|
||||
ev.preventDefault ()
|
||||
setActiveIndex (i => Math.min (i + 1, suggestions.length - 1))
|
||||
setSuggestionsVsbl (true)
|
||||
break
|
||||
|
||||
case 'ArrowUp':
|
||||
ev.preventDefault ()
|
||||
setActiveIndex (i => Math.max (i - 1, -1))
|
||||
setSuggestionsVsbl (true)
|
||||
break
|
||||
|
||||
case 'Enter':
|
||||
if (activeIndex < 0)
|
||||
break
|
||||
ev.preventDefault ()
|
||||
const selected = suggestions[activeIndex]
|
||||
selected && handleTagSelect (selected)
|
||||
break
|
||||
|
||||
case 'Escape':
|
||||
ev.preventDefault ()
|
||||
setSuggestionsVsbl (false)
|
||||
break
|
||||
}
|
||||
if (ev.key === 'Enter' && (!(suggestionsVsbl) || activeIndex < 0))
|
||||
{
|
||||
setSuggestionsVsbl (false)
|
||||
}
|
||||
}
|
||||
|
||||
const search = async () => {
|
||||
const qs = new URLSearchParams ()
|
||||
setIf (qs, 'tags', tagsStr)
|
||||
@@ -172,15 +115,6 @@ export default (() => {
|
||||
navigate (`${ location.pathname }?${ qs.toString () }`)
|
||||
}
|
||||
|
||||
// TODO: TagSearch からのコピペのため,共通化を考へる.
|
||||
const handleTagSelect = (tag: Tag) => {
|
||||
const parts = tagsStr.split (' ')
|
||||
parts[parts.length - 1] = tag.name
|
||||
setTagsStr (parts.join (' ') + ' ')
|
||||
setSuggestions ([])
|
||||
setActiveIndex (-1)
|
||||
}
|
||||
|
||||
const handleSearch = (e: FormEvent) => {
|
||||
e.preventDefault ()
|
||||
search ()
|
||||
@@ -223,21 +157,11 @@ export default (() => {
|
||||
</div>
|
||||
|
||||
{/* タグ */}
|
||||
<div className="relative">
|
||||
<div>
|
||||
<Label>タグ</Label>
|
||||
<input
|
||||
type="text"
|
||||
<TagInput
|
||||
value={tagsStr}
|
||||
onChange={whenChanged}
|
||||
onFocus={() => setSuggestionsVsbl (true)}
|
||||
onBlur={() => setSuggestionsVsbl (false)}
|
||||
onKeyDown={handleKeyDown}
|
||||
className="w-full border p-2 rounded"/>
|
||||
<TagSearchBox
|
||||
suggestions={
|
||||
suggestionsVsbl && suggestions.length > 0 ? suggestions : [] as Tag[]}
|
||||
activeIndex={activeIndex}
|
||||
onSelect={handleTagSelect}/>
|
||||
setValue={setTagsStr}/>
|
||||
<fieldset className="w-full my-2">
|
||||
<label>検索区分:</label>
|
||||
<label className="mx-2">
|
||||
|
||||
Reference in New Issue
Block a user