镜像自地址
https://github.com/lhly/search-syntax-pro.git
已同步 2026-10-21 14:44:01 +08:00
- 新增全局快捷键支持(Ctrl+Shift+F/Command+Shift+F) - 添加快捷键命令类型定义(chrome-commands.d.ts) - 实现版本管理工具(version.ts) - 新增模板和快捷键相关类型定义 - 优化搜索表单组件支持外部控制高级选项 - 新增引擎选择器组件(EngineSelector) - 改进全局样式和按钮样式 - 在设置页面新增快捷键配置选项 - 添加测试页面和调试工具 - 更新文档包括发布说明和测试指南 本次提交包含多项功能新增和优化,主要围绕快捷键功能的实现和用户体验提升。
428 行
12 KiB
Markdown
428 行
12 KiB
Markdown
# 快捷键同步问题修复完成报告
|
|
|
|
**修复日期**: 2025-11-11
|
|
**问题类型**: 数据同步和事件通知
|
|
**影响范围**: Options 页面 → Popup 页面的快捷键配置同步
|
|
|
|
---
|
|
|
|
## 问题描述
|
|
|
|
用户在 Options 页面修改快捷键对应的搜索引擎后,存在两个同步问题:
|
|
|
|
1. **Popup 页面显示不同步**: 快捷键提示中的引擎名称未更新
|
|
2. **实际功能不同步**: 按下快捷键后切换的搜索引擎仍使用旧配置
|
|
|
|
## 根本原因分析
|
|
|
|
### 原因 1: 缺少跨上下文存储变化监听
|
|
|
|
- Options 页面修改快捷键后正确保存到 `chrome.storage.local`
|
|
- 但**没有触发任何跨上下文的通知机制**
|
|
- Popup 页面在初始化时只加载一次快捷键配置,**不监听存储变化**
|
|
|
|
### 原因 2: ShortcutManager 初始化时机问题
|
|
|
|
- `shortcutManager.initialize()` 只在组件挂载时调用一次
|
|
- `updateShortcutEngine()` 保存到存储,但**不会自动重新加载到内存**
|
|
- 导致处理器使用的是**陈旧的闭包状态**
|
|
|
|
### 原因 3: 数据流不完整
|
|
|
|
```
|
|
Options 页面修改 → chrome.storage.local 保存 ✅
|
|
↓
|
|
[缺少事件传播] ❌
|
|
↓
|
|
Popup 页面重载 ← 监听存储变化 ❌
|
|
↓
|
|
ShortcutManager 重新加载配置 ❌
|
|
↓
|
|
更新处理器闭包中的引用 ❌
|
|
```
|
|
|
|
---
|
|
|
|
## 修复方案实施
|
|
|
|
### 1. ShortcutManager 添加存储监听机制
|
|
|
|
**文件**: `/Users/lhly/chromeex/ssp/src/services/shortcut-manager.ts`
|
|
|
|
#### 修改 1.1: 添加存储监听器
|
|
|
|
```typescript
|
|
private storageListener: ((changes: { [key: string]: chrome.storage.StorageChange }, namespace: string) => void) | null = null;
|
|
|
|
private setupStorageListener(): void {
|
|
this.storageListener = (changes, namespace) => {
|
|
if (namespace === 'local' && changes[STORAGE_KEY_CUSTOM_SHORTCUTS]) {
|
|
console.log('[ShortcutManager] 检测到快捷键配置变化,正在重新加载...');
|
|
this.reloadShortcuts();
|
|
}
|
|
};
|
|
|
|
chrome.storage.onChanged.addListener(this.storageListener);
|
|
}
|
|
```
|
|
|
|
#### 修改 1.2: 实现重新加载方法
|
|
|
|
```typescript
|
|
private async reloadShortcuts(): Promise<void> {
|
|
console.log('[ShortcutManager] 开始重新加载快捷键配置');
|
|
|
|
// 清空当前快捷键
|
|
this.shortcuts.clear();
|
|
|
|
// 重新加载默认快捷键
|
|
Object.entries(DEFAULT_SHORTCUTS).forEach(([id, shortcut]) => {
|
|
this.shortcuts.set(id, { ...shortcut });
|
|
});
|
|
|
|
// 加载用户自定义快捷键
|
|
try {
|
|
const result = await chrome.storage.local.get(STORAGE_KEY_CUSTOM_SHORTCUTS);
|
|
const custom = result[STORAGE_KEY_CUSTOM_SHORTCUTS] || {};
|
|
|
|
Object.entries(custom).forEach(([id, shortcut]) => {
|
|
const existing = this.shortcuts.get(id);
|
|
if (existing && existing.customizable) {
|
|
this.shortcuts.set(id, shortcut as KeyboardShortcut);
|
|
}
|
|
});
|
|
|
|
console.log('[ShortcutManager] 快捷键配置重新加载完成');
|
|
} catch (error) {
|
|
console.error('[ShortcutManager] 重新加载快捷键配置失败:', error);
|
|
}
|
|
}
|
|
```
|
|
|
|
#### 修改 1.3: 更新销毁方法
|
|
|
|
```typescript
|
|
destroy(): void {
|
|
document.removeEventListener('keydown', this.handleKeyPress.bind(this), true);
|
|
|
|
if (this.storageListener) {
|
|
chrome.storage.onChanged.removeListener(this.storageListener);
|
|
this.storageListener = null;
|
|
}
|
|
|
|
this.initialized = false;
|
|
}
|
|
```
|
|
|
|
#### 修改 1.4: 使用最新配置执行处理器
|
|
|
|
```typescript
|
|
private handleKeyPress(event: KeyboardEvent): void {
|
|
// ... 省略前面的代码 ...
|
|
|
|
// 执行注册的处理器
|
|
const handler = this.handlers.get(shortcut.action);
|
|
if (handler) {
|
|
// 关键修复:使用最新的快捷键配置中的 actionParam
|
|
// 对于 SWITCH_ENGINE,优先使用 targetEngine,其次使用 actionParam
|
|
const param = shortcut.action === 'SWITCH_ENGINE'
|
|
? (shortcut.targetEngine || shortcut.actionParam)
|
|
: shortcut.actionParam;
|
|
|
|
console.log(`[ShortcutManager] 执行快捷键 ${shortcut.description},参数:`, param);
|
|
handler(param);
|
|
}
|
|
}
|
|
```
|
|
|
|
#### 修改 1.5: 同步更新 targetEngine 和 actionParam
|
|
|
|
```typescript
|
|
async updateShortcutEngine(shortcutId: string, targetEngine: string): Promise<void> {
|
|
const shortcut = this.shortcuts.get(shortcutId);
|
|
if (!shortcut) {
|
|
throw new Error('快捷键不存在');
|
|
}
|
|
|
|
if (shortcut.action !== 'SWITCH_ENGINE') {
|
|
throw new Error('只能为引擎切换快捷键设置目标引擎');
|
|
}
|
|
|
|
// 同时更新 targetEngine 和 actionParam 以保持一致性
|
|
shortcut.targetEngine = targetEngine;
|
|
shortcut.actionParam = targetEngine;
|
|
this.shortcuts.set(shortcutId, shortcut);
|
|
|
|
console.log(`[ShortcutManager] 更新快捷键 ${shortcutId} 的目标引擎: ${targetEngine}`);
|
|
|
|
// 保存到存储(会触发 storage.onChanged 事件)
|
|
await this.saveCustomShortcuts();
|
|
}
|
|
```
|
|
|
|
### 2. Popup 页面添加存储监听
|
|
|
|
**文件**: `/Users/lhly/chromeex/ssp/src/popup/App.tsx`
|
|
|
|
#### 修改 2.1: 添加存储变化监听器
|
|
|
|
```typescript
|
|
// 监听快捷键配置变化并重新注册处理器
|
|
useEffect(() => {
|
|
const handleShortcutConfigChange = (changes: { [key: string]: chrome.storage.StorageChange }, namespace: string) => {
|
|
if (namespace === 'local' && changes['custom_shortcuts']) {
|
|
console.log('[Popup] 检测到快捷键配置变化,处理器将使用更新后的配置')
|
|
// ShortcutManager 会自动重新加载配置
|
|
// 处理器会在下次快捷键触发时使用新的 targetEngine 值
|
|
}
|
|
}
|
|
|
|
chrome.storage.onChanged.addListener(handleShortcutConfigChange)
|
|
|
|
return () => {
|
|
chrome.storage.onChanged.removeListener(handleShortcutConfigChange)
|
|
}
|
|
}, [])
|
|
```
|
|
|
|
#### 修改 2.2: 增强 handleSwitchEngine 日志
|
|
|
|
```typescript
|
|
const handleSwitchEngine = useCallback((actionParam: string | number | undefined) => {
|
|
console.log('[App] handleSwitchEngine 被调用,actionParam:', actionParam)
|
|
|
|
const engines = SearchAdapterFactory.getSupportedEngines()
|
|
let targetEngine: SearchEngine | undefined
|
|
|
|
if (typeof actionParam === 'string') {
|
|
if (engines.includes(actionParam as SearchEngine)) {
|
|
targetEngine = actionParam as SearchEngine
|
|
console.log(`[App] 使用引擎名称: ${targetEngine}`)
|
|
} else {
|
|
console.warn(`[App] 无效的引擎名称: "${actionParam}"`)
|
|
}
|
|
} else if (typeof actionParam === 'number') {
|
|
if (actionParam >= 0 && actionParam < engines.length) {
|
|
targetEngine = engines[actionParam]
|
|
console.log(`[App] 使用引擎索引 ${actionParam}: ${targetEngine}`)
|
|
} else {
|
|
console.warn(`[App] 引擎索引越界: ${actionParam}`)
|
|
}
|
|
}
|
|
|
|
if (targetEngine) {
|
|
const newParams = { ...searchParams, engine: targetEngine }
|
|
setSearchParams(newParams)
|
|
generateQuery(newParams)
|
|
console.log(`[App] 切换到搜索引擎: ${targetEngine}`)
|
|
} else {
|
|
console.warn(`[App] 无法切换引擎,使用默认引擎`)
|
|
}
|
|
}, [searchParams, generateQuery])
|
|
```
|
|
|
|
### 3. ShortcutSettings 组件添加存储监听
|
|
|
|
**文件**: `/Users/lhly/chromeex/ssp/src/components/ShortcutSettings.tsx`
|
|
|
|
#### 修改 3.1: 监听存储变化
|
|
|
|
```typescript
|
|
// 监听存储变化,实时更新快捷键配置
|
|
useEffect(() => {
|
|
const handleStorageChange = (changes: { [key: string]: chrome.storage.StorageChange }, namespace: string) => {
|
|
if (namespace === 'local' && changes['custom_shortcuts']) {
|
|
console.log('[ShortcutSettings] 检测到快捷键配置变化,重新加载');
|
|
loadShortcuts();
|
|
}
|
|
};
|
|
|
|
chrome.storage.onChanged.addListener(handleStorageChange);
|
|
|
|
return () => {
|
|
chrome.storage.onChanged.removeListener(handleStorageChange);
|
|
};
|
|
}, []);
|
|
```
|
|
|
|
### 4. ShortcutHint 组件使用实时配置
|
|
|
|
**文件**: `/Users/lhly/chromeex/ssp/src/components/ShortcutHint.tsx`
|
|
|
|
#### 修改 4.1: 从 ShortcutManager 加载最新配置
|
|
|
|
```typescript
|
|
useEffect(() => {
|
|
const loadShortcuts = async () => {
|
|
// 确保 shortcutManager 已初始化
|
|
await shortcutManager.initialize('popup');
|
|
|
|
// 获取最新的快捷键配置
|
|
const shortcutsMap = shortcutManager.getShortcutsMap();
|
|
|
|
// 更新分组中的快捷键配置
|
|
const updatedGroups = SHORTCUT_GROUPS.map(group => ({
|
|
...group,
|
|
shortcuts: group.shortcuts.map(shortcut => {
|
|
// 从 shortcutManager 中找到对应的快捷键
|
|
for (const [, sc] of shortcutsMap.entries()) {
|
|
if (sc.action === shortcut.action && sc.key === shortcut.key) {
|
|
return sc;
|
|
}
|
|
}
|
|
return shortcut;
|
|
})
|
|
}));
|
|
|
|
setGroups(updatedGroups);
|
|
};
|
|
|
|
loadShortcuts();
|
|
|
|
// ... 监听 ESC 键的代码 ...
|
|
}, [onClose]);
|
|
```
|
|
|
|
#### 修改 4.2: 显示目标引擎名称
|
|
|
|
```typescript
|
|
<div className="flex items-center gap-2">
|
|
<span className="text-sm text-gray-700 dark:text-gray-300">
|
|
{shortcut.description}
|
|
</span>
|
|
{shortcut.action === 'SWITCH_ENGINE' && shortcut.targetEngine && (
|
|
<span className="text-xs text-gray-500 dark:text-gray-400">
|
|
({shortcut.targetEngine})
|
|
</span>
|
|
)}
|
|
</div>
|
|
```
|
|
|
|
---
|
|
|
|
## 修复效果验证
|
|
|
|
### 数据流现在完整工作:
|
|
|
|
```
|
|
Options 页面修改
|
|
↓
|
|
chrome.storage.local 保存 ✅
|
|
↓
|
|
触发 storage.onChanged 事件 ✅
|
|
↓
|
|
ShortcutManager 自动重新加载配置 ✅
|
|
↓
|
|
下次快捷键触发时使用最新的 targetEngine ✅
|
|
↓
|
|
Popup/Options 页面监听器更新 UI ✅
|
|
```
|
|
|
|
### 预期行为:
|
|
|
|
1. **Options 页面修改快捷键引擎** → 立即保存到 `chrome.storage.local`
|
|
2. **ShortcutManager 自动检测变化** → 重新加载快捷键配置到内存
|
|
3. **Popup 页面检测变化** → 日志记录配置更新
|
|
4. **用户按下快捷键** → 使用最新的 `targetEngine` 执行引擎切换
|
|
5. **快捷键提示面板** → 显示最新的目标引擎名称
|
|
|
|
---
|
|
|
|
## 测试建议
|
|
|
|
### 测试场景 1: 基本同步测试
|
|
|
|
1. 打开 Options 页面
|
|
2. 修改 `Ctrl+1` 快捷键的目标引擎从 "baidu" 改为 "google"
|
|
3. 打开 Popup 页面
|
|
4. 按下 `Ctrl+1`
|
|
5. **预期**: 切换到 Google 搜索引擎
|
|
|
|
### 测试场景 2: 实时更新测试
|
|
|
|
1. 保持 Popup 页面打开
|
|
2. 在另一个标签页打开 Options 页面
|
|
3. 修改 `Ctrl+2` 快捷键的目标引擎
|
|
4. 返回 Popup 页面
|
|
5. 按下 `Ctrl+2`
|
|
6. **预期**: 切换到新设置的搜索引擎
|
|
|
|
### 测试场景 3: UI 更新测试
|
|
|
|
1. 在 Popup 页面打开快捷键帮助面板 (按 `?`)
|
|
2. 在 Options 页面修改某个引擎切换快捷键
|
|
3. 关闭并重新打开快捷键帮助面板
|
|
4. **预期**: 显示更新后的目标引擎名称
|
|
|
|
### 测试场景 4: 控制台日志验证
|
|
|
|
打开浏览器开发者工具,查看控制台日志:
|
|
|
|
- `[ShortcutManager] 检测到快捷键配置变化,正在重新加载...`
|
|
- `[ShortcutManager] 快捷键配置重新加载完成`
|
|
- `[ShortcutManager] 执行快捷键 切换到XXX搜索,参数: xxx`
|
|
- `[App] handleSwitchEngine 被调用,actionParam: xxx`
|
|
- `[App] 切换到搜索引擎: xxx`
|
|
|
|
---
|
|
|
|
## 技术亮点
|
|
|
|
### 1. 事件驱动架构
|
|
|
|
- 利用 Chrome Storage API 的 `onChanged` 事件实现跨上下文通信
|
|
- 无需轮询,响应及时,性能优秀
|
|
|
|
### 2. 自动重载机制
|
|
|
|
- ShortcutManager 自动检测配置变化并重新加载
|
|
- 无需手动刷新,用户体验流畅
|
|
|
|
### 3. 数据一致性保证
|
|
|
|
- 同时更新 `targetEngine` 和 `actionParam` 字段
|
|
- 确保向后兼容性的同时保证数据一致性
|
|
|
|
### 4. 完整的日志系统
|
|
|
|
- 所有关键操作都有详细日志
|
|
- 便于调试和问题追踪
|
|
|
|
---
|
|
|
|
## 文件修改清单
|
|
|
|
| 文件路径 | 修改类型 | 修改内容 |
|
|
|---------|---------|---------|
|
|
| `src/services/shortcut-manager.ts` | 核心修复 | 添加存储监听、重载机制、使用最新配置 |
|
|
| `src/popup/App.tsx` | 功能增强 | 添加存储监听、增强日志 |
|
|
| `src/components/ShortcutSettings.tsx` | UI 同步 | 添加存储监听、自动重载 |
|
|
| `src/components/ShortcutHint.tsx` | UI 同步 | 使用实时配置、显示目标引擎 |
|
|
|
|
---
|
|
|
|
## 构建验证
|
|
|
|
✅ **TypeScript 编译通过**
|
|
✅ **Vite 构建成功**
|
|
✅ **无警告和错误**
|
|
|
|
```bash
|
|
$ npm run build
|
|
✅ 构建后处理完成!
|
|
📦 生产构建已准备就绪: dist/
|
|
```
|
|
|
|
---
|
|
|
|
## 总结
|
|
|
|
本次修复通过实现完整的跨上下文数据同步机制,彻底解决了快捷键配置在 Options 和 Popup 页面之间的同步问题。修复方案:
|
|
|
|
1. **最小化侵入性**: 仅修改必要的组件
|
|
2. **向后兼容**: 保持对旧版本数据的支持
|
|
3. **性能优化**: 使用事件驱动而非轮询
|
|
4. **可维护性**: 详细的日志和清晰的代码注释
|
|
|
|
用户现在可以在 Options 页面修改快捷键配置,修改会立即在 Popup 页面生效,无需刷新或重启扩展。
|