import { useCallback, useEffect, useRef, useState } from 'react'; import { Search, User } from 'lucide-react'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; import { Switch } from '@/components/ui/switch'; import { api } from '../../api/client'; import { useMediaQuery } from '../../hooks/useTheme'; import { useForumLimits } from '../../hooks/useForumLimits'; import { getRecentSearches, type PostSearchSubmitInput, type RecentSearch, } from '../../hooks/usePostSearch'; import { cn } from '@/lib/utils'; export interface PostSearchDraft { keyword: string; author: string; titleOnly: boolean; scopeBoardId: number; } interface Props { open: boolean; onOpenChange: (open: boolean) => void; draft: PostSearchDraft; contextBoardId: number; contextBoardName?: string; onSubmit: (input: PostSearchSubmitInput) => boolean; onClear: () => void; } type UserSuggest = { id: number; username: string; nickname: string; avatar?: string }; export default function PostSearchPanel({ open, onOpenChange, draft, contextBoardId, contextBoardName = '', onSubmit, onClear, }: Props) { const isMobile = useMediaQuery('(max-width: 768px)'); const { limits } = useForumLimits(); const keywordRef = useRef(null); const [keyword, setKeyword] = useState(draft.keyword); const [author, setAuthor] = useState(draft.author); const [titleOnly, setTitleOnly] = useState(draft.titleOnly); const [scopeBoardId, setScopeBoardId] = useState(draft.scopeBoardId); const [recent, setRecent] = useState([]); const [suggestions, setSuggestions] = useState([]); const [suggestOpen, setSuggestOpen] = useState(false); const authorWrapRef = useRef(null); useEffect(() => { if (!open) return; setKeyword(draft.keyword); setAuthor(draft.author); setTitleOnly(draft.titleOnly); setScopeBoardId(draft.scopeBoardId); setRecent(getRecentSearches()); setSuggestions([]); setSuggestOpen(false); const t = window.setTimeout(() => keywordRef.current?.focus(), 80); return () => window.clearTimeout(t); }, [open, draft]); useEffect(() => { if (!open) return; const q = author.trim(); if (q.length < 1) { setSuggestions([]); setSuggestOpen(false); return; } let cancelled = false; const timer = window.setTimeout(() => { api.searchUsers(q, 8).then((r) => { if (cancelled) return; const users = Array.isArray(r.users) ? r.users : []; setSuggestions(users); setSuggestOpen(users.length > 0); }).catch(() => { if (!cancelled) { setSuggestions([]); setSuggestOpen(false); } }); }, 300); return () => { cancelled = true; window.clearTimeout(timer); }; }, [author, open]); useEffect(() => { if (!suggestOpen) return; const onDoc = (e: MouseEvent) => { if (authorWrapRef.current?.contains(e.target as Node)) return; setSuggestOpen(false); }; document.addEventListener('mousedown', onDoc); return () => document.removeEventListener('mousedown', onDoc); }, [suggestOpen]); const handleSubmit = useCallback(() => { const ok = onSubmit({ keyword, author, titleOnly: !!keyword.trim() && titleOnly, scopeBoardId: scopeBoardId > 0 ? scopeBoardId : 0, }); if (ok) onOpenChange(false); }, [keyword, author, titleOnly, scopeBoardId, onSubmit, onOpenChange]); const applyRecent = (item: RecentSearch) => { setKeyword(item.keyword); setAuthor(item.author); setTitleOnly(item.titleOnly); setScopeBoardId(item.scopeBoardId); }; const pickAuthor = (u: UserSuggest) => { setAuthor(u.nickname || u.username); setSuggestOpen(false); }; const showBoardScope = contextBoardId > 0; const kwHint = limits.search_keyword_min > 1 ? `关键词 ${limits.search_keyword_min}–${limits.search_keyword_max} 字` : `关键词最多 ${limits.search_keyword_max} 字`; return ( e.preventDefault()} > {isMobile &&
} 搜索帖子 {!isMobile && ( 按关键词、作者或板块范围查找帖子 )}
{showBoardScope && (
范围
)}
仅搜标题 需填写关键词时生效
{recent.length > 0 && (
最近搜索
    {recent.map((item) => { const label = [ item.keyword && `「${item.keyword}」`, item.author && `@${item.author}`, item.titleOnly && '仅标题', item.scopeBoardId > 0 && '本板块', ].filter(Boolean).join(' · ') || '搜索'; return (
  • ); })}
)}
); }