タグ補完ウィンドウ(#103) (#269)
#103 Merge remote-tracking branch 'origin/main' into feature/103 #103 #103 タグ補完からニコタグ除外 Co-authored-by: miteruzo <miteruzo@naver.com> Reviewed-on: #269
This commit was merged in pull request #269.
This commit is contained in:
@@ -25,8 +25,8 @@ const getTokenAt = (value: string, pos: number) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const replaceToken = (value: string, start: number, end: number, text: string) => (
|
const replaceToken = (value: string, start: number, end: number, text: string) =>
|
||||||
`${ value.slice (0, start) }${ text }${ value.slice (end) }`)
|
`${ value.slice (0, start) }${ text }${ value.slice (end) }`
|
||||||
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -38,16 +38,17 @@ export default (({ tags, setTags }: Props) => {
|
|||||||
const ref = useRef<HTMLTextAreaElement> (null)
|
const ref = useRef<HTMLTextAreaElement> (null)
|
||||||
|
|
||||||
const [bounds, setBounds] = useState<{ start: number; end: number }> ({ start: 0, end: 0 })
|
const [bounds, setBounds] = useState<{ start: number; end: number }> ({ start: 0, end: 0 })
|
||||||
|
const [focused, setFocused] = useState (false)
|
||||||
const [suggestions, setSuggestions] = useState<Tag[]> ([])
|
const [suggestions, setSuggestions] = useState<Tag[]> ([])
|
||||||
const [suggestionsVsbl, setSuggestionsVsbl] = useState (false)
|
const [suggestionsVsbl, setSuggestionsVsbl] = useState (false)
|
||||||
|
|
||||||
const handleTagSelect = (tag: Tag) => {
|
const handleTagSelect = (tag: Tag) => {
|
||||||
setSuggestionsVsbl (false)
|
setSuggestionsVsbl (false)
|
||||||
const textarea = ref.current!
|
const textarea = ref.current!
|
||||||
const newValue = replaceToken (tags, bounds.start, bounds.end, tag.name)
|
const newValue = replaceToken (tags, bounds.start, bounds.end, tag.name + ' ')
|
||||||
setTags (newValue)
|
setTags (newValue)
|
||||||
requestAnimationFrame (async () => {
|
requestAnimationFrame (async () => {
|
||||||
const p = bounds.start + tag.name.length
|
const p = bounds.start + tag.name.length + 1
|
||||||
textarea.selectionStart = textarea.selectionEnd = p
|
textarea.selectionStart = textarea.selectionEnd = p
|
||||||
textarea.focus ()
|
textarea.focus ()
|
||||||
await recompute (p, newValue)
|
await recompute (p, newValue)
|
||||||
@@ -56,14 +57,21 @@ export default (({ tags, setTags }: Props) => {
|
|||||||
|
|
||||||
const recompute = async (pos: number, v: string = tags) => {
|
const recompute = async (pos: number, v: string = tags) => {
|
||||||
const { start, end, token } = getTokenAt (v, pos)
|
const { start, end, token } = getTokenAt (v, pos)
|
||||||
|
if (!(token.trim ()))
|
||||||
|
{
|
||||||
|
setSuggestionsVsbl (false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
setBounds ({ start, end })
|
setBounds ({ start, end })
|
||||||
const data = await apiGet<Tag[]> ('/tags/autocomplete', { params: { q: token } })
|
|
||||||
|
const data = await apiGet<Tag[]> ('/tags/autocomplete', { params: { q: token, nico: '0' } })
|
||||||
setSuggestions (data.filter (t => t.postCount > 0))
|
setSuggestions (data.filter (t => t.postCount > 0))
|
||||||
setSuggestionsVsbl (suggestions.length > 0)
|
setSuggestionsVsbl (suggestions.length > 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="relative w-full">
|
||||||
<Label>タグ</Label>
|
<Label>タグ</Label>
|
||||||
<TextArea
|
<TextArea
|
||||||
ref={ref}
|
ref={ref}
|
||||||
@@ -72,11 +80,20 @@ export default (({ tags, setTags }: Props) => {
|
|||||||
onSelect={async (ev: SyntheticEvent<HTMLTextAreaElement>) => {
|
onSelect={async (ev: SyntheticEvent<HTMLTextAreaElement>) => {
|
||||||
const pos = (ev.target as HTMLTextAreaElement).selectionStart
|
const pos = (ev.target as HTMLTextAreaElement).selectionStart
|
||||||
await recompute (pos)
|
await recompute (pos)
|
||||||
|
}}
|
||||||
|
onFocus={() => {
|
||||||
|
setFocused (true)
|
||||||
|
}}
|
||||||
|
onBlur={() => {
|
||||||
|
setFocused (false)
|
||||||
|
setSuggestionsVsbl (false)
|
||||||
}}/>
|
}}/>
|
||||||
<TagSearchBox suggestions={suggestionsVsbl && suggestions.length
|
{focused && (
|
||||||
? suggestions
|
<TagSearchBox
|
||||||
: [] as Tag[]}
|
suggestions={suggestionsVsbl && suggestions.length > 0
|
||||||
activeIndex={-1}
|
? suggestions
|
||||||
onSelect={handleTagSelect}/>
|
: [] as Tag[]}
|
||||||
|
activeIndex={-1}
|
||||||
|
onSelect={handleTagSelect}/>)}
|
||||||
</div>)
|
</div>)
|
||||||
}) satisfies FC<Props>
|
}) satisfies FC<Props>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { clsx, type ClassValue } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { twMerge } from 'tailwind-merge'
|
import { twMerge } from 'tailwind-merge'
|
||||||
|
|
||||||
export function cn (...inputs: ClassValue[]) {
|
import type { ClassValue } from 'clsx'
|
||||||
return twMerge(clsx(...inputs))
|
|
||||||
}
|
|
||||||
|
export const cn = (...inputs: ClassValue[]) => twMerge (clsx (...inputs))
|
||||||
|
|||||||
Reference in New Issue
Block a user