import { useState, useEffect, useRef, useMemo } from 'react'; import { Settings, ExternalLink, ArrowLeft, Search, History, X, Globe, Sun, Moon, Monitor, } from 'lucide-react'; import { useRouter } from '@/providers/RouterProvider'; import { useThemeMode } from '@/providers/ThemeModeProvider'; import { FEATURES, FeatureConfig } from '@/config/features'; import { storageUtil } from '@/utils/chromeStorage'; import { openExtensionPage } from '@/utils/chromeTabs'; import { useTranslation } from 'react-i18next'; import { SUPPORTED_LANGUAGES, normalizeLanguage } from '@/i18n'; const topBarStyles = { SEARCH_MAX_WIDTH: 400, DROPDOWN_MAX_HEIGHT: 300, Z_INDEX: 1100, DROPDOWN_Z_INDEX: 1200, SEARCH_HISTORY_LIMIT: 10, SEARCH_HISTORY_DISPLAY: 5, }; export default function TopBar({ onOpenOptions }: { onOpenOptions: () => void }) { const { currentPage, goBack, navigateTo } = useRouter(); const { mode, setMode } = useThemeMode(); const { t, i18n } = useTranslation(['common', 'features']); const [searchQuery, setSearchQuery] = useState(''); const [showResults, setShowResults] = useState(false); const [searchHistory, setSearchHistory] = useState([]); const [selectedIndex, setSelectedIndex] = useState(-1); const inputRef = useRef(null); // 加载搜索历史 useEffect(() => { storageUtil .get('app/searchHistory', []) .then((history) => { setSearchHistory(history || []); }) .catch((error) => { console.error('加载搜索历史失败:', error); }); }, []); // 模糊搜索逻辑 const searchResults = useMemo(() => { if (!searchQuery.trim()) return []; const query = searchQuery.toLowerCase(); return FEATURES.filter((f) => { if (f.key === 'dashboard') return false; const label = t(f.labelKey).toLowerCase(); const desc = t(f.descriptionKey).toLowerCase(); return label.includes(query) || desc.includes(query); }); }, [searchQuery, t]); const displayedHistory = useMemo(() => { if (searchQuery.trim()) return []; return searchHistory.slice(0, topBarStyles.SEARCH_HISTORY_DISPLAY); }, [searchHistory, searchQuery]); const handleOpenInTab = async () => { await openExtensionPage('popup.html', { mode: 'tab' }); window.close(); }; const handleSearchChange = (e: React.ChangeEvent) => { setSearchQuery(e.target.value); setShowResults(true); setSelectedIndex(-1); }; const saveToHistory = async (query: string) => { if (!query.trim()) return; setSearchHistory((prev) => { const newHistory = [query, ...prev.filter((h) => h !== query)].slice( 0, topBarStyles.SEARCH_HISTORY_LIMIT, ); return newHistory; }); }; // 副作用:搜索历史变化后持久化到 storage useEffect(() => { storageUtil.set('app/searchHistory', searchHistory).catch((error) => { console.error('保存搜索历史失败:', error); }); }, [searchHistory]); const handleSelectFeature = (feature: FeatureConfig) => { navigateTo(feature.key); saveToHistory(t(feature.labelKey)); setSearchQuery(''); setShowResults(false); }; const toggleLanguage = async () => { const currentLng = normalizeLanguage(i18n.language); const currentIndex = SUPPORTED_LANGUAGES.indexOf(currentLng); const nextIndex = (currentIndex + 1) % SUPPORTED_LANGUAGES.length; const newLng = SUPPORTED_LANGUAGES[nextIndex]; await i18n.changeLanguage(newLng); await storageUtil.set('app/language', newLng); }; const cycleThemeMode = () => { const next = { light: 'dark', dark: 'system', system: 'light' } as const; setMode(next[mode]); }; const ThemeIcon = mode === 'light' ? Sun : mode === 'dark' ? Moon : Monitor; const handleKeyDown = (e: React.KeyboardEvent) => { const totalItems = searchQuery.trim() ? searchResults.length : displayedHistory.length; if (e.key === 'ArrowDown') { e.preventDefault(); setSelectedIndex((prev) => (prev < totalItems - 1 ? prev + 1 : prev)); } else if (e.key === 'ArrowUp') { e.preventDefault(); setSelectedIndex((prev) => (prev > 0 ? prev - 1 : prev)); } else if (e.key === 'Enter') { if (selectedIndex >= 0) { if (searchQuery.trim()) { handleSelectFeature(searchResults[selectedIndex]); } else { const selectedQuery = displayedHistory[selectedIndex]; setSearchQuery(selectedQuery); setSelectedIndex(-1); // 触发搜索:如果匹配到功能则跳转,否则保持搜索词展示结果 const matchedFeature = FEATURES.find( (f) => f.key !== 'dashboard' && t(f.labelKey) === selectedQuery, ); if (matchedFeature) { handleSelectFeature(matchedFeature); } } } else if (searchQuery.trim() && searchResults.length > 0) { handleSelectFeature(searchResults[0]); } } else if (e.key === 'Escape') { setShowResults(false); inputRef.current?.blur(); } }; const isDashboard = currentPage === 'dashboard'; return (
{!isDashboard && ( )}
{t('common:appName')}
setShowResults(true)} onKeyDown={handleKeyDown} aria-label={t('common:buttons.search')} className="w-full pl-9 pr-8 py-1.5 text-sm rounded-lg border border-transparent bg-gray-50 focus:bg-white focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 transition-all" /> {searchQuery && ( )}
{showResults && (searchQuery.trim() || displayedHistory.length > 0) && (
    {searchQuery.trim() ? ( searchResults.length > 0 ? ( searchResults.map((feature, index) => (
  • handleSelectFeature(feature)} className={`flex items-center gap-3 px-3 py-2 cursor-pointer transition-colors ${ selectedIndex === index ? 'bg-blue-50' : 'hover:bg-gray-50' }`} >
    {feature.icon && }

    {t(feature.labelKey)}

    {t(feature.descriptionKey)}

  • )) ) : (
  • {t('common:buttons.noResults')}

  • ) ) : ( <>
  • {t('common:buttons.recentSearch')}
  • {displayedHistory.map((item, index) => (
  • { setSearchQuery(item); setSelectedIndex(-1); }} className={`flex items-center gap-3 px-3 py-2 cursor-pointer transition-colors ${ selectedIndex === index ? 'bg-blue-50' : 'hover:bg-gray-50' }`} >
    {item}
  • ))} )}
)}
); }