feat(history): 添加历史记录管理功能

实现历史记录管理页面,包含以下功能:
- 添加历史记录搜索和过滤功能
- 支持快速搜索和编辑后搜索
- 添加历史记录展开/收起功能
- 添加从选项页面恢复历史记录的功能
这个提交包含在:
Zain 2025-11-08 11:49:44 +08:00
父节点 f32329ce19
当前提交 d64d047a36
共有 5 个文件被更改,包括 455 次插入 和 43 次删除

查看文件

@ -0,0 +1,260 @@
import { useState } from 'react'
import type { SearchHistory, UserSettings } from '@/types'
import { formatDistanceToNow } from 'date-fns'
import { zhCN, enUS } from 'date-fns/locale'
import { useTranslation } from '@/i18n'
import { SearchAdapterFactory } from '@/services/adapters'
import { DEFAULT_SETTINGS } from '@/types'
interface HistoryManagerProps {
history: SearchHistory[]
onClear: () => void
}
export function HistoryManager({ history, onClear }: HistoryManagerProps) {
const { t, language } = useTranslation()
const locale = language === 'en-US' ? enUS : zhCN
const [searchFilter, setSearchFilter] = useState('')
// 获取搜索引擎显示名称
const getEngineName = (engine: string) => {
return t(`common.searchEngines.${engine}`, undefined, engine)
}
// 格式化时间戳
const formatTime = (timestamp: number) => {
try {
return formatDistanceToNow(new Date(timestamp), {
addSuffix: true,
locale
})
} catch {
return t('searchHistory.unknownTime')
}
}
// 获取搜索语法的标签
const getSyntaxBadges = (item: SearchHistory) => {
const badges = []
if (item.syntax.site) {
badges.push({
key: 'site',
label: `site: ${item.syntax.site}`,
className: 'badge-primary'
})
}
if (item.syntax.fileType) {
badges.push({
key: 'filetype',
label: `filetype: ${item.syntax.fileType}`,
className: 'badge-secondary'
})
}
if (item.syntax.exactMatch) {
badges.push({
key: 'exact',
label: `"${item.syntax.exactMatch}"`,
className: 'badge-primary'
})
}
return badges
}
// 快速搜索功能 - 直接执行搜索
const handleQuickSearch = async (item: SearchHistory) => {
try {
// 1. 构建搜索URL
const adapter = SearchAdapterFactory.getAdapter(item.engine)
const searchUrl = adapter.buildQuery({
keyword: item.keyword,
engine: item.engine,
site: item.syntax.site,
fileType: item.syntax.fileType,
exactMatch: item.syntax.exactMatch
})
// 2. 获取用户设置
const { user_settings } = await chrome.storage.local.get('user_settings')
const settings: UserSettings = user_settings || DEFAULT_SETTINGS
// 3. 打开搜索
if (settings.autoOpenInNewTab) {
await chrome.tabs.create({ url: searchUrl })
} else {
const tabs = await chrome.tabs.query({ active: true, currentWindow: true })
if (tabs[0]?.id) {
await chrome.tabs.update(tabs[0].id, { url: searchUrl })
} else {
// 回退:如果无法获取当前标签页,创建新标签页
await chrome.tabs.create({ url: searchUrl })
}
}
} catch (error) {
console.error('执行搜索失败:', error)
}
}
// 编辑搜索功能 - 保存到临时存储并提示打开popup
const handleEditSearch = async (item: SearchHistory, event: React.MouseEvent) => {
event.stopPropagation()
try {
// 保存到临时存储
await chrome.storage.local.set({
pending_restore_history: item
})
// 显示通知(简单的alert,后续可以优化为toast通知)
alert(t('historyManager.editPrepared'))
} catch (error) {
console.error('保存编辑状态失败:', error)
}
}
// 过滤历史记录
const filteredHistory = searchFilter
? history.filter(item =>
item.keyword.toLowerCase().includes(searchFilter.toLowerCase()) ||
item.engine.toLowerCase().includes(searchFilter.toLowerCase()) ||
item.syntax.site?.toLowerCase().includes(searchFilter.toLowerCase()) ||
item.syntax.fileType?.toLowerCase().includes(searchFilter.toLowerCase())
)
: history
return (
<div className="space-y-4">
{/* 顶部操作栏 */}
<div className="flex items-center justify-between gap-4">
<div className="flex-1">
<input
type="text"
placeholder={t('historyManager.searchPlaceholder')}
value={searchFilter}
onChange={(e) => setSearchFilter(e.target.value)}
className="input w-full"
/>
</div>
<button
onClick={onClear}
className="btn btn-danger"
disabled={history.length === 0}
>
{t('searchHistory.clearButton')}
</button>
</div>
{/* 统计信息 */}
<div className="text-sm text-gray-600 dark:text-gray-400">
{searchFilter ? (
<p>
{t('historyManager.filterResults', {
count: filteredHistory.length,
total: history.length
})}
</p>
) : (
<p>{t('historyManager.totalRecords', { count: history.length })}</p>
)}
</div>
{/* 历史记录列表 */}
{filteredHistory.length > 0 ? (
<div className="space-y-3 max-h-[600px] overflow-y-auto">
{filteredHistory.map((item) => (
<div
key={item.id}
className="p-4 bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 hover:border-primary-500 dark:hover:border-primary-500 transition-colors"
>
<div className="flex items-start justify-between gap-4">
<div
className="flex-1 cursor-pointer"
onClick={() => handleQuickSearch(item)}
title={t('historyManager.quickSearchTooltip')}
>
<div className="flex items-center gap-2 mb-2">
<p className="text-base font-medium text-gray-900 dark:text-gray-100">
{item.keyword || t('searchHistory.emptyKeyword')}
</p>
<span className="text-xs px-2 py-1 bg-gray-100 dark:bg-gray-700 rounded">
{getEngineName(item.engine)}
</span>
</div>
<p className="text-xs text-gray-500 dark:text-gray-400 mb-2">
{formatTime(item.timestamp)}
</p>
{/* 搜索语法标签 */}
{getSyntaxBadges(item).length > 0 && (
<div className="flex flex-wrap gap-1 mb-2">
{getSyntaxBadges(item).map((badge) => (
<span
key={badge.key}
className={`badge ${badge.className} text-xs`}
>
{badge.label}
</span>
))}
</div>
)}
{/* 生成的查询 */}
<div className="p-2 bg-gray-50 dark:bg-gray-900 rounded border border-gray-200 dark:border-gray-600">
<code className="text-xs text-gray-600 dark:text-gray-400 break-all">
{item.generatedQuery}
</code>
</div>
</div>
{/* 操作按钮 */}
<div className="flex flex-col gap-2">
<button
onClick={() => handleQuickSearch(item)}
className="btn btn-sm btn-primary whitespace-nowrap"
title={t('historyManager.quickSearchTooltip')}
>
🚀 {t('historyManager.quickSearch')}
</button>
<button
onClick={(e) => handleEditSearch(item, e)}
className="btn btn-sm btn-ghost whitespace-nowrap"
title={t('historyManager.editSearchTooltip')}
>
📝 {t('historyManager.editSearch')}
</button>
</div>
</div>
</div>
))}
</div>
) : (
<div className="text-center py-12 text-gray-500 dark:text-gray-400">
<svg className="w-16 h-16 mx-auto mb-4 opacity-50" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
<p className="text-base">
{searchFilter
? t('historyManager.noFilterResults')
: t('searchHistory.emptyState')
}
</p>
</div>
)}
{/* 使用提示 */}
<div className="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-4">
<p className="text-sm text-blue-700 dark:text-blue-300 mb-2">
💡 {t('historyManager.usageTips')}
</p>
<ul className="text-xs text-blue-600 dark:text-blue-400 space-y-1 list-disc list-inside">
<li>{t('historyManager.tip1')}</li>
<li>{t('historyManager.tip2')}</li>
</ul>
</div>
</div>
)
}

查看文件

@ -1,3 +1,4 @@
import { useState } from 'react'
import type { SearchHistory } from '@/types' import type { SearchHistory } from '@/types'
import { formatDistanceToNow } from 'date-fns' import { formatDistanceToNow } from 'date-fns'
import { zhCN, enUS } from 'date-fns/locale' import { zhCN, enUS } from 'date-fns/locale'
@ -12,6 +13,7 @@ interface SearchHistoryProps {
export function SearchHistory({ history, onRestore, onClear }: SearchHistoryProps) { export function SearchHistory({ history, onRestore, onClear }: SearchHistoryProps) {
const { t, language } = useTranslation() const { t, language } = useTranslation()
const locale = language === 'en-US' ? enUS : zhCN const locale = language === 'en-US' ? enUS : zhCN
const [showAll, setShowAll] = useState(false)
// 获取搜索引擎显示名称 // 获取搜索引擎显示名称
const getEngineName = (engine: string) => { const getEngineName = (engine: string) => {
@ -61,6 +63,10 @@ export function SearchHistory({ history, onRestore, onClear }: SearchHistoryProp
return badges return badges
} }
// 计算要显示的记录数
const displayCount = showAll ? history.length : 10
const hasMore = history.length > 10
return ( return (
<div className="card p-4"> <div className="card p-4">
<div className="flex items-center justify-between mb-3"> <div className="flex items-center justify-between mb-3">
@ -77,7 +83,7 @@ export function SearchHistory({ history, onRestore, onClear }: SearchHistoryProp
</div> </div>
<div className="space-y-2 max-h-60 overflow-y-auto"> <div className="space-y-2 max-h-60 overflow-y-auto">
{history.slice(0, 10).map((item) => ( {history.slice(0, displayCount).map((item) => (
<div <div
key={item.id} key={item.id}
className="p-3 bg-gray-50 dark:bg-gray-900 rounded-lg border border-gray-200 dark:border-gray-700 hover:bg-gray-100 dark:hover:bg-gray-800 cursor-pointer transition-colors" className="p-3 bg-gray-50 dark:bg-gray-900 rounded-lg border border-gray-200 dark:border-gray-700 hover:bg-gray-100 dark:hover:bg-gray-800 cursor-pointer transition-colors"
@ -125,15 +131,35 @@ export function SearchHistory({ history, onRestore, onClear }: SearchHistoryProp
<p className="text-sm">{t('searchHistory.emptyState')}</p> <p className="text-sm">{t('searchHistory.emptyState')}</p>
</div> </div>
)} )}
{history.length > 10 && (
<div className="text-center py-2">
<p className="text-xs text-gray-500 dark:text-gray-400">
{t('searchHistory.summary', { count: history.length })}
</p>
</div>
)}
</div> </div>
{/* 展开/收起按钮 - 当历史记录超过10条时显示 */}
{hasMore && (
<div className="mt-3 pt-3 border-t border-gray-200 dark:border-gray-700">
<div className="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-3">
<p className="text-xs text-blue-700 dark:text-blue-300 text-center mb-2">
{showAll
? t('searchHistory.showingAll', { count: history.length })
: t('searchHistory.showingRecent', { showing: 10, total: history.length })
}
</p>
<button
onClick={() => setShowAll(!showAll)}
className="w-full btn btn-sm btn-primary"
>
{showAll
? t('searchHistory.collapse')
: t('searchHistory.expandAll', { count: history.length })
}
</button>
{!showAll && (
<p className="text-xs text-gray-500 dark:text-gray-400 text-center mt-2">
{t('searchHistory.viewAllHint')}
</p>
)}
</div>
</div>
)}
</div> </div>
) )
} }

查看文件

@ -49,11 +49,19 @@ const translations: Record<Language, Record<string, string>> = {
'searchHistory.emptyState': '暂无搜索历史', 'searchHistory.emptyState': '暂无搜索历史',
'searchHistory.summary': '显示最近10条记录,共{count}条', 'searchHistory.summary': '显示最近10条记录,共{count}条',
'searchHistory.unknownTime': '未知时间', 'searchHistory.unknownTime': '未知时间',
'searchHistory.showingAll': '已展开全部 {count} 条记录',
'searchHistory.showingRecent': '显示最近 {showing} 条,共 {total} 条',
'searchHistory.expandAll': '展开全部 {count} 条记录 ↓',
'searchHistory.collapse': '收起历史记录 ↑',
'searchHistory.viewAllHint': '或前往设置页面查看完整历史记录',
'options.header.title': '搜索语法大师 - 设置', 'options.header.title': '搜索语法大师 - 设置',
'options.header.subtitle': '设置和配置', 'options.header.subtitle': '设置和配置',
'options.tabs.settings': '设置',
'options.tabs.history': '历史记录',
'options.sections.basic': '基本设置', 'options.sections.basic': '基本设置',
'options.sections.features': '功能设置', 'options.sections.features': '功能设置',
'options.sections.data': '数据管理', 'options.sections.data': '数据管理',
'options.sections.historyManagement': '历史记录管理',
'options.fields.defaultEngine.label': '默认搜索引擎', 'options.fields.defaultEngine.label': '默认搜索引擎',
'options.fields.language.label': '界面语言', 'options.fields.language.label': '界面语言',
'options.fields.theme.label': '主题设置', 'options.fields.theme.label': '主题设置',
@ -81,6 +89,7 @@ const translations: Record<Language, Record<string, string>> = {
'options.confirm.reset': '确定要重置所有设置吗?\n\n这将恢复默认配置,但会保留您的搜索历史记录。\n操作将立即生效并自动保存。', 'options.confirm.reset': '确定要重置所有设置吗?\n\n这将恢复默认配置,但会保留您的搜索历史记录。\n操作将立即生效并自动保存。',
'options.confirm.clearAll': '确定要清除所有数据吗?这将删除搜索历史、设置等所有信息,且无法恢复。', 'options.confirm.clearAll': '确定要清除所有数据吗?这将删除搜索历史、设置等所有信息,且无法恢复。',
'options.confirm.clearAllSecond': '请再次确认:此操作不可撤销!', 'options.confirm.clearAllSecond': '请再次确认:此操作不可撤销!',
'options.confirm.clearHistory': '确定要清除所有历史记录吗?此操作无法恢复。',
'options.alert.importSuccess': '数据导入成功!页面将重新加载。', 'options.alert.importSuccess': '数据导入成功!页面将重新加载。',
'options.alert.importFailure': '导入失败: {error}', 'options.alert.importFailure': '导入失败: {error}',
'options.alert.importInvalid': '无效的备份文件格式', 'options.alert.importInvalid': '无效的备份文件格式',
@ -89,7 +98,19 @@ const translations: Record<Language, Record<string, string>> = {
'options.footer.title': '关于', 'options.footer.title': '关于',
'options.footer.version': '版本: {version}', 'options.footer.version': '版本: {version}',
'options.footer.author': '作者: {author}', 'options.footer.author': '作者: {author}',
'options.footer.homepage': '项目主页' 'options.footer.homepage': '项目主页',
'historyManager.searchPlaceholder': '搜索历史记录...',
'historyManager.totalRecords': '共 {count} 条历史记录',
'historyManager.filterResults': '找到 {count} 条记录(共 {total} 条)',
'historyManager.quickSearch': '立即搜索',
'historyManager.editSearch': '编辑后搜索',
'historyManager.quickSearchTooltip': '直接执行此搜索',
'historyManager.editSearchTooltip': 'Shift+点击可编辑参数后搜索',
'historyManager.editPrepared': '搜索参数已准备好,请点击浏览器工具栏的扩展图标查看和编辑',
'historyManager.noFilterResults': '未找到匹配的历史记录',
'historyManager.usageTips': '使用提示',
'historyManager.tip1': '点击历史记录可直接执行搜索',
'historyManager.tip2': '点击"编辑后搜索"按钮可在弹窗中修改参数后搜索'
}, },
'en-US': { 'en-US': {
'common.languages.zh-CN': 'Simplified Chinese', 'common.languages.zh-CN': 'Simplified Chinese',
@ -139,11 +160,19 @@ const translations: Record<Language, Record<string, string>> = {
'searchHistory.emptyState': 'No search history yet', 'searchHistory.emptyState': 'No search history yet',
'searchHistory.summary': 'Showing the latest 10 records, {count} total', 'searchHistory.summary': 'Showing the latest 10 records, {count} total',
'searchHistory.unknownTime': 'Unknown time', 'searchHistory.unknownTime': 'Unknown time',
'searchHistory.showingAll': 'Showing all {count} records',
'searchHistory.showingRecent': 'Showing latest {showing} of {total} records',
'searchHistory.expandAll': 'Show all {count} records ↓',
'searchHistory.collapse': 'Collapse history ↑',
'searchHistory.viewAllHint': 'Or visit settings page to view complete history',
'options.header.title': 'SearchSyntax Pro - Settings', 'options.header.title': 'SearchSyntax Pro - Settings',
'options.header.subtitle': 'Settings & configuration', 'options.header.subtitle': 'Settings & configuration',
'options.tabs.settings': 'Settings',
'options.tabs.history': 'History',
'options.sections.basic': 'Basic settings', 'options.sections.basic': 'Basic settings',
'options.sections.features': 'Feature settings', 'options.sections.features': 'Feature settings',
'options.sections.data': 'Data management', 'options.sections.data': 'Data management',
'options.sections.historyManagement': 'History Management',
'options.fields.defaultEngine.label': 'Default search engine', 'options.fields.defaultEngine.label': 'Default search engine',
'options.fields.language.label': 'Interface language', 'options.fields.language.label': 'Interface language',
'options.fields.theme.label': 'Theme', 'options.fields.theme.label': 'Theme',
@ -171,6 +200,7 @@ const translations: Record<Language, Record<string, string>> = {
'options.confirm.reset': 'Reset all settings?\n\nThis will restore default configuration but keep your search history.\nChanges will take effect immediately and be saved automatically.', 'options.confirm.reset': 'Reset all settings?\n\nThis will restore default configuration but keep your search history.\nChanges will take effect immediately and be saved automatically.',
'options.confirm.clearAll': 'Are you sure you want to clear all data? This will delete history, settings, and cannot be undone.', 'options.confirm.clearAll': 'Are you sure you want to clear all data? This will delete history, settings, and cannot be undone.',
'options.confirm.clearAllSecond': 'Please confirm again: this action cannot be undone!', 'options.confirm.clearAllSecond': 'Please confirm again: this action cannot be undone!',
'options.confirm.clearHistory': 'Are you sure you want to clear all history? This action cannot be undone.',
'options.alert.importSuccess': 'Data imported successfully! The page will reload.', 'options.alert.importSuccess': 'Data imported successfully! The page will reload.',
'options.alert.importFailure': 'Import failed: {error}', 'options.alert.importFailure': 'Import failed: {error}',
'options.alert.importInvalid': 'Invalid backup file format', 'options.alert.importInvalid': 'Invalid backup file format',
@ -179,7 +209,19 @@ const translations: Record<Language, Record<string, string>> = {
'options.footer.title': 'About', 'options.footer.title': 'About',
'options.footer.version': 'Version: {version}', 'options.footer.version': 'Version: {version}',
'options.footer.author': 'Author: {author}', 'options.footer.author': 'Author: {author}',
'options.footer.homepage': 'Project home' 'options.footer.homepage': 'Project home',
'historyManager.searchPlaceholder': 'Search history...',
'historyManager.totalRecords': 'Total {count} records',
'historyManager.filterResults': 'Found {count} records (of {total} total)',
'historyManager.quickSearch': 'Quick Search',
'historyManager.editSearch': 'Edit & Search',
'historyManager.quickSearchTooltip': 'Execute this search directly',
'historyManager.editSearchTooltip': 'Shift+click to edit parameters before searching',
'historyManager.editPrepared': 'Search parameters prepared. Please click the extension icon in the browser toolbar to view and edit.',
'historyManager.noFilterResults': 'No matching history found',
'historyManager.usageTips': 'Usage Tips',
'historyManager.tip1': 'Click on a history item to execute the search directly',
'historyManager.tip2': 'Click "Edit & Search" button to modify parameters in popup before searching'
} }
} }

查看文件

@ -2,7 +2,8 @@ import { useState, useEffect } from 'react'
import { ThemeProvider } from '@/hooks/useTheme' import { ThemeProvider } from '@/hooks/useTheme'
import { useStorage } from '@/hooks/useStorage' import { useStorage } from '@/hooks/useStorage'
import { Logo } from '@/components/Logo' import { Logo } from '@/components/Logo'
import type { UserSettings, Language } from '@/types' import { HistoryManager } from '@/components/HistoryManager'
import type { UserSettings, Language, SearchHistory } from '@/types'
import { DEFAULT_SETTINGS } from '@/types' import { DEFAULT_SETTINGS } from '@/types'
import { TranslationProvider, useTranslation, translate } from '@/i18n' import { TranslationProvider, useTranslation, translate } from '@/i18n'
@ -12,17 +13,34 @@ function App() {
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const [saving, setSaving] = useState(false) const [saving, setSaving] = useState(false)
const [message, setMessage] = useState<{ type: 'success' | 'error'; key: string } | null>(null) const [message, setMessage] = useState<{ type: 'success' | 'error'; key: string } | null>(null)
const [history, setHistory] = useState<SearchHistory[]>([])
const [activeTab, setActiveTab] = useState<'settings' | 'history'>('settings')
// 从存储中加载设置 // 从存储中加载设置和历史记录
const { data: storedSettings, save: saveSettings } = useStorage<UserSettings>('user_settings', DEFAULT_SETTINGS) const { data: storedSettings, save: saveSettings } = useStorage<UserSettings>('user_settings', DEFAULT_SETTINGS)
const { data: storedHistory } = useStorage<SearchHistory[]>('search_history')
useEffect(() => { useEffect(() => {
setSettings(storedSettings || DEFAULT_SETTINGS) setSettings(storedSettings || DEFAULT_SETTINGS)
setLoading(false) setLoading(false)
}, [storedSettings]) }, [storedSettings])
useEffect(() => {
if (storedHistory) {
setHistory(storedHistory)
}
}, [storedHistory])
const language = settings?.language ?? 'zh-CN' const language = settings?.language ?? 'zh-CN'
// 清除历史记录
const handleClearHistory = () => {
if (confirm(translate(language, 'options.confirm.clearHistory'))) {
setHistory([])
chrome.storage.local.remove('search_history')
}
}
// 保存设置 // 保存设置
const handleSaveSettings = async () => { const handleSaveSettings = async () => {
if (!settings) return if (!settings) return
@ -161,11 +179,15 @@ function App() {
updateSettings={updateSettings} updateSettings={updateSettings}
message={message} message={message}
saving={saving} saving={saving}
history={history}
activeTab={activeTab}
onTabChange={setActiveTab}
onReset={handleResetSettings} onReset={handleResetSettings}
onSave={handleSaveSettings} onSave={handleSaveSettings}
onExport={handleExportData} onExport={handleExportData}
onImport={handleImportData} onImport={handleImportData}
onClearAll={handleClearAllData} onClearAll={handleClearAllData}
onClearHistory={handleClearHistory}
/> />
</TranslationProvider> </TranslationProvider>
</ThemeProvider> </ThemeProvider>
@ -179,11 +201,15 @@ interface OptionsContentProps {
updateSettings: (updater: (prev: UserSettings) => UserSettings) => void updateSettings: (updater: (prev: UserSettings) => UserSettings) => void
message: { type: 'success' | 'error'; key: string } | null message: { type: 'success' | 'error'; key: string } | null
saving: boolean saving: boolean
history: SearchHistory[]
activeTab: 'settings' | 'history'
onTabChange: (tab: 'settings' | 'history') => void
onReset: () => void onReset: () => void
onSave: () => void onSave: () => void
onExport: () => void onExport: () => void
onImport: () => void onImport: () => void
onClearAll: () => void onClearAll: () => void
onClearHistory: () => void
} }
function OptionsContent({ function OptionsContent({
@ -192,11 +218,15 @@ function OptionsContent({
updateSettings, updateSettings,
message, message,
saving, saving,
history,
activeTab,
onTabChange,
onReset, onReset,
onSave, onSave,
onExport, onExport,
onImport, onImport,
onClearAll onClearAll,
onClearHistory
}: OptionsContentProps) { }: OptionsContentProps) {
const { t } = useTranslation() const { t } = useTranslation()
@ -220,13 +250,39 @@ function OptionsContent({
<div className="space-y-6"> <div className="space-y-6">
{/* 头部 */} {/* 头部 */}
<header className="bg-white dark:bg-gray-800 rounded-lg shadow-sm p-6"> <header className="bg-white dark:bg-gray-800 rounded-lg shadow-sm p-6">
<div className="flex items-center space-x-4"> <div className="flex items-center justify-between">
<Logo size={40} color="#3B82F6" /> <div className="flex items-center space-x-4">
<div> <Logo size={40} color="#3B82F6" />
<h1 className="text-2xl font-bold text-gray-900 dark:text-gray-100"> <div>
{t('options.header.title')} <h1 className="text-2xl font-bold text-gray-900 dark:text-gray-100">
</h1> {t('options.header.title')}
<p className="text-gray-600 dark:text-gray-400">{t('options.header.subtitle')}</p> </h1>
<p className="text-gray-600 dark:text-gray-400">{t('options.header.subtitle')}</p>
</div>
</div>
{/* Tab 切换 */}
<div className="flex space-x-2">
<button
onClick={() => onTabChange('settings')}
className={`px-4 py-2 rounded-lg font-medium transition-colors ${
activeTab === 'settings'
? 'bg-primary-600 text-white'
: 'bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600'
}`}
>
⚙️ {t('options.tabs.settings')}
</button>
<button
onClick={() => onTabChange('history')}
className={`px-4 py-2 rounded-lg font-medium transition-colors ${
activeTab === 'history'
? 'bg-primary-600 text-white'
: 'bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600'
}`}
>
📜 {t('options.tabs.history')} {history.length > 0 && `(${history.length})`}
</button>
</div> </div>
</div> </div>
</header> </header>
@ -234,14 +290,26 @@ function OptionsContent({
{/* 消息提示 */} {/* 消息提示 */}
{message && ( {message && (
<div className={`p-4 rounded-lg ${ <div className={`p-4 rounded-lg ${
message.type === 'success' message.type === 'success'
? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200' ? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200'
: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200' : 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200'
}`}> }`}>
{t(message.key)} {t(message.key)}
</div> </div>
)} )}
{/* Tab 内容 */}
{activeTab === 'history' ? (
<section className="bg-white dark:bg-gray-800 rounded-lg shadow-sm p-6">
<h2 className="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
{t('options.sections.historyManagement')}
</h2>
<HistoryManager history={history} onClear={onClearHistory} />
</section>
) : (
<>
{/* 原有的设置内容 */}
{/* 基本设置 */} {/* 基本设置 */}
<section className="bg-white dark:bg-gray-800 rounded-lg shadow-sm p-6"> <section className="bg-white dark:bg-gray-800 rounded-lg shadow-sm p-6">
<h2 className="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4"> <h2 className="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
@ -460,26 +528,30 @@ function OptionsContent({
</div> </div>
</section> </section>
{/* 关于信息 */} {/* 关于信息 - 仅在设置页面显示 */}
<footer className="bg-white dark:bg-gray-800 rounded-lg shadow-sm p-6"> {activeTab === 'settings' && (
<h2 className="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4"> <footer className="bg-white dark:bg-gray-800 rounded-lg shadow-sm p-6">
{t('options.footer.title')} <h2 className="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
</h2> {t('options.footer.title')}
<div className="space-y-2 text-sm text-gray-600 dark:text-gray-400"> </h2>
<p>{t('options.footer.version', { version: '1.0.0' })}</p> <div className="space-y-2 text-sm text-gray-600 dark:text-gray-400">
<p>{t('options.footer.author', { author: '冷火凉烟' })}</p> <p>{t('options.footer.version', { version: '1.0.0' })}</p>
<p> <p>{t('options.footer.author', { author: '冷火凉烟' })}</p>
<a <p>
href="https://github.com/lhly/search-syntax-pro" <a
target="_blank" href="https://github.com/lhly/search-syntax-pro"
rel="noopener noreferrer" target="_blank"
className="text-primary-600 hover:text-primary-700" rel="noopener noreferrer"
> className="text-primary-600 hover:text-primary-700"
{t('options.footer.homepage')} >
</a> {t('options.footer.homepage')}
</p> </a>
</div> </p>
</footer> </div>
</footer>
)}
</>
)}
</div> </div>
) )
} }

查看文件

@ -45,6 +45,18 @@ function App() {
} }
}, [storedHistory]) }, [storedHistory])
// 监听从 Options 页面传递的待恢复历史记录
useEffect(() => {
chrome.storage.local.get('pending_restore_history', (data) => {
if (data.pending_restore_history) {
const historyItem = data.pending_restore_history as SearchHistoryType
restoreFromHistory(historyItem)
// 清除临时状态
chrome.storage.local.remove('pending_restore_history')
}
})
}, [])
// 生成搜索查询 // 生成搜索查询
const generateQuery = (params: SearchParams) => { const generateQuery = (params: SearchParams) => {
try { try {