镜像自地址
https://github.com/lhly/search-syntax-pro.git
已同步 2026-10-21 22:45:41 +08:00
feat(history): 添加历史记录管理功能
实现历史记录管理页面,包含以下功能: - 添加历史记录搜索和过滤功能 - 支持快速搜索和编辑后搜索 - 添加历史记录展开/收起功能 - 添加从选项页面恢复历史记录的功能
这个提交包含在:
父节点
f32329ce19
当前提交
d64d047a36
260
src/components/HistoryManager.tsx
普通文件
260
src/components/HistoryManager.tsx
普通文件
@ -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 { formatDistanceToNow } from 'date-fns'
|
||||
import { zhCN, enUS } from 'date-fns/locale'
|
||||
@ -12,6 +13,7 @@ interface SearchHistoryProps {
|
||||
export function SearchHistory({ history, onRestore, onClear }: SearchHistoryProps) {
|
||||
const { t, language } = useTranslation()
|
||||
const locale = language === 'en-US' ? enUS : zhCN
|
||||
const [showAll, setShowAll] = useState(false)
|
||||
|
||||
// 获取搜索引擎显示名称
|
||||
const getEngineName = (engine: string) => {
|
||||
@ -61,6 +63,10 @@ export function SearchHistory({ history, onRestore, onClear }: SearchHistoryProp
|
||||
return badges
|
||||
}
|
||||
|
||||
// 计算要显示的记录数
|
||||
const displayCount = showAll ? history.length : 10
|
||||
const hasMore = history.length > 10
|
||||
|
||||
return (
|
||||
<div className="card p-4">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
@ -77,7 +83,7 @@ export function SearchHistory({ history, onRestore, onClear }: SearchHistoryProp
|
||||
</div>
|
||||
|
||||
<div className="space-y-2 max-h-60 overflow-y-auto">
|
||||
{history.slice(0, 10).map((item) => (
|
||||
{history.slice(0, displayCount).map((item) => (
|
||||
<div
|
||||
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"
|
||||
@ -125,15 +131,35 @@ export function SearchHistory({ history, onRestore, onClear }: SearchHistoryProp
|
||||
<p className="text-sm">{t('searchHistory.emptyState')}</p>
|
||||
</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>
|
||||
|
||||
{/* 展开/收起按钮 - 当历史记录超过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>
|
||||
)
|
||||
}
|
||||
|
||||
@ -49,11 +49,19 @@ const translations: Record<Language, Record<string, string>> = {
|
||||
'searchHistory.emptyState': '暂无搜索历史',
|
||||
'searchHistory.summary': '显示最近10条记录,共{count}条',
|
||||
'searchHistory.unknownTime': '未知时间',
|
||||
'searchHistory.showingAll': '已展开全部 {count} 条记录',
|
||||
'searchHistory.showingRecent': '显示最近 {showing} 条,共 {total} 条',
|
||||
'searchHistory.expandAll': '展开全部 {count} 条记录 ↓',
|
||||
'searchHistory.collapse': '收起历史记录 ↑',
|
||||
'searchHistory.viewAllHint': '或前往设置页面查看完整历史记录',
|
||||
'options.header.title': '搜索语法大师 - 设置',
|
||||
'options.header.subtitle': '设置和配置',
|
||||
'options.tabs.settings': '设置',
|
||||
'options.tabs.history': '历史记录',
|
||||
'options.sections.basic': '基本设置',
|
||||
'options.sections.features': '功能设置',
|
||||
'options.sections.data': '数据管理',
|
||||
'options.sections.historyManagement': '历史记录管理',
|
||||
'options.fields.defaultEngine.label': '默认搜索引擎',
|
||||
'options.fields.language.label': '界面语言',
|
||||
'options.fields.theme.label': '主题设置',
|
||||
@ -81,6 +89,7 @@ const translations: Record<Language, Record<string, string>> = {
|
||||
'options.confirm.reset': '确定要重置所有设置吗?\n\n这将恢复默认配置,但会保留您的搜索历史记录。\n操作将立即生效并自动保存。',
|
||||
'options.confirm.clearAll': '确定要清除所有数据吗?这将删除搜索历史、设置等所有信息,且无法恢复。',
|
||||
'options.confirm.clearAllSecond': '请再次确认:此操作不可撤销!',
|
||||
'options.confirm.clearHistory': '确定要清除所有历史记录吗?此操作无法恢复。',
|
||||
'options.alert.importSuccess': '数据导入成功!页面将重新加载。',
|
||||
'options.alert.importFailure': '导入失败: {error}',
|
||||
'options.alert.importInvalid': '无效的备份文件格式',
|
||||
@ -89,7 +98,19 @@ const translations: Record<Language, Record<string, string>> = {
|
||||
'options.footer.title': '关于',
|
||||
'options.footer.version': '版本: {version}',
|
||||
'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': {
|
||||
'common.languages.zh-CN': 'Simplified Chinese',
|
||||
@ -139,11 +160,19 @@ const translations: Record<Language, Record<string, string>> = {
|
||||
'searchHistory.emptyState': 'No search history yet',
|
||||
'searchHistory.summary': 'Showing the latest 10 records, {count} total',
|
||||
'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.subtitle': 'Settings & configuration',
|
||||
'options.tabs.settings': 'Settings',
|
||||
'options.tabs.history': 'History',
|
||||
'options.sections.basic': 'Basic settings',
|
||||
'options.sections.features': 'Feature settings',
|
||||
'options.sections.data': 'Data management',
|
||||
'options.sections.historyManagement': 'History Management',
|
||||
'options.fields.defaultEngine.label': 'Default search engine',
|
||||
'options.fields.language.label': 'Interface language',
|
||||
'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.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.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.importFailure': 'Import failed: {error}',
|
||||
'options.alert.importInvalid': 'Invalid backup file format',
|
||||
@ -179,7 +209,19 @@ const translations: Record<Language, Record<string, string>> = {
|
||||
'options.footer.title': 'About',
|
||||
'options.footer.version': 'Version: {version}',
|
||||
'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 { useStorage } from '@/hooks/useStorage'
|
||||
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 { TranslationProvider, useTranslation, translate } from '@/i18n'
|
||||
|
||||
@ -12,17 +13,34 @@ function App() {
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [saving, setSaving] = useState(false)
|
||||
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: storedHistory } = useStorage<SearchHistory[]>('search_history')
|
||||
|
||||
useEffect(() => {
|
||||
setSettings(storedSettings || DEFAULT_SETTINGS)
|
||||
setLoading(false)
|
||||
}, [storedSettings])
|
||||
|
||||
useEffect(() => {
|
||||
if (storedHistory) {
|
||||
setHistory(storedHistory)
|
||||
}
|
||||
}, [storedHistory])
|
||||
|
||||
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 () => {
|
||||
if (!settings) return
|
||||
@ -161,11 +179,15 @@ function App() {
|
||||
updateSettings={updateSettings}
|
||||
message={message}
|
||||
saving={saving}
|
||||
history={history}
|
||||
activeTab={activeTab}
|
||||
onTabChange={setActiveTab}
|
||||
onReset={handleResetSettings}
|
||||
onSave={handleSaveSettings}
|
||||
onExport={handleExportData}
|
||||
onImport={handleImportData}
|
||||
onClearAll={handleClearAllData}
|
||||
onClearHistory={handleClearHistory}
|
||||
/>
|
||||
</TranslationProvider>
|
||||
</ThemeProvider>
|
||||
@ -179,11 +201,15 @@ interface OptionsContentProps {
|
||||
updateSettings: (updater: (prev: UserSettings) => UserSettings) => void
|
||||
message: { type: 'success' | 'error'; key: string } | null
|
||||
saving: boolean
|
||||
history: SearchHistory[]
|
||||
activeTab: 'settings' | 'history'
|
||||
onTabChange: (tab: 'settings' | 'history') => void
|
||||
onReset: () => void
|
||||
onSave: () => void
|
||||
onExport: () => void
|
||||
onImport: () => void
|
||||
onClearAll: () => void
|
||||
onClearHistory: () => void
|
||||
}
|
||||
|
||||
function OptionsContent({
|
||||
@ -192,11 +218,15 @@ function OptionsContent({
|
||||
updateSettings,
|
||||
message,
|
||||
saving,
|
||||
history,
|
||||
activeTab,
|
||||
onTabChange,
|
||||
onReset,
|
||||
onSave,
|
||||
onExport,
|
||||
onImport,
|
||||
onClearAll
|
||||
onClearAll,
|
||||
onClearHistory
|
||||
}: OptionsContentProps) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
@ -220,13 +250,39 @@ function OptionsContent({
|
||||
<div className="space-y-6">
|
||||
{/* 头部 */}
|
||||
<header className="bg-white dark:bg-gray-800 rounded-lg shadow-sm p-6">
|
||||
<div className="flex items-center space-x-4">
|
||||
<Logo size={40} color="#3B82F6" />
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-gray-100">
|
||||
{t('options.header.title')}
|
||||
</h1>
|
||||
<p className="text-gray-600 dark:text-gray-400">{t('options.header.subtitle')}</p>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-4">
|
||||
<Logo size={40} color="#3B82F6" />
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-gray-100">
|
||||
{t('options.header.title')}
|
||||
</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>
|
||||
</header>
|
||||
@ -234,14 +290,26 @@ function OptionsContent({
|
||||
{/* 消息提示 */}
|
||||
{message && (
|
||||
<div className={`p-4 rounded-lg ${
|
||||
message.type === 'success'
|
||||
? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200'
|
||||
message.type === 'success'
|
||||
? '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'
|
||||
}`}>
|
||||
{t(message.key)}
|
||||
</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">
|
||||
<h2 className="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
|
||||
@ -460,26 +528,30 @@ function OptionsContent({
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 关于信息 */}
|
||||
<footer 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.footer.title')}
|
||||
</h2>
|
||||
<div className="space-y-2 text-sm text-gray-600 dark:text-gray-400">
|
||||
<p>{t('options.footer.version', { version: '1.0.0' })}</p>
|
||||
<p>{t('options.footer.author', { author: '冷火凉烟' })}</p>
|
||||
<p>
|
||||
<a
|
||||
href="https://github.com/lhly/search-syntax-pro"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-primary-600 hover:text-primary-700"
|
||||
>
|
||||
{t('options.footer.homepage')}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
{/* 关于信息 - 仅在设置页面显示 */}
|
||||
{activeTab === 'settings' && (
|
||||
<footer 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.footer.title')}
|
||||
</h2>
|
||||
<div className="space-y-2 text-sm text-gray-600 dark:text-gray-400">
|
||||
<p>{t('options.footer.version', { version: '1.0.0' })}</p>
|
||||
<p>{t('options.footer.author', { author: '冷火凉烟' })}</p>
|
||||
<p>
|
||||
<a
|
||||
href="https://github.com/lhly/search-syntax-pro"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-primary-600 hover:text-primary-700"
|
||||
>
|
||||
{t('options.footer.homepage')}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -45,6 +45,18 @@ function App() {
|
||||
}
|
||||
}, [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) => {
|
||||
try {
|
||||
|
||||
正在加载...
在新工单中引用
屏蔽一个用户