- chore: 升级 @metona-team/metona-editor 0.1.3 → 0.1.10 - feat: 三主题系统(亮色/暗色/暖色),循环切换 - feat: 应用颜色自动同步编辑器主题(MeEditor.themes.exportCSSVars) - feat: 启用编辑器新功能(lineNumbers/autoBrackets/readOnly/autofocus/exportTool) - refactor: 主题从 boolean darkMode 重构为 ThemeMode 枚举 - refactor: 插件注册改用字符串数组(与 demo 一致) - refactor: 主题同步从手动 CSS 覆写改为 editor.setTheme() 实例 API - refactor: Ctrl+S 双通道(编辑器 onSave + 全局兜底)+ 防重入锁 - style: 移除 variables.css 中硬编码 .dark 色值,改为 JS 动态驱动 - test: 更新 editorStore 测试覆盖新的 themeMode/cycleTheme
95 lines
3.4 KiB
TypeScript
95 lines
3.4 KiB
TypeScript
import { useCallback, useRef } from 'react'
|
|
import { useTabStore } from '../stores/tabStore'
|
|
import { useEditorStore } from '../stores/editorStore'
|
|
import { recentFilesRepository } from '../db/recentFilesRepository'
|
|
import { logError } from '../lib/errorHandler'
|
|
import { showToast } from '../lib/toast'
|
|
|
|
/**
|
|
* AR-01: 从 App.tsx 提取的文件操作逻辑
|
|
* UX-02: 添加 loading 状态指示
|
|
* v0.1.9: handleSave 添加防重入锁,避免编辑器 onSave + 全局 Ctrl+S 双重触发
|
|
*/
|
|
export function useFileOperations() {
|
|
const createTab = useTabStore(s => s.createTab)
|
|
const getActiveTab = useTabStore(s => s.getActiveTab)
|
|
const setLoading = useEditorStore(s => s.setLoading)
|
|
|
|
// 防重入:onSave 回调 + 全局 Ctrl+S handler 可能在 300ms 内双重触发
|
|
const savingGate = useRef(false)
|
|
|
|
const handleOpenFile = useCallback(async (): Promise<void> => {
|
|
try {
|
|
if (!window.electronAPI) return
|
|
setLoading('file-open', true)
|
|
const result = await window.electronAPI.openFile()
|
|
if (result && 'filePath' in result) {
|
|
createTab(result.filePath, result.content)
|
|
if (result.filePath) recentFilesRepository.add(result.filePath)
|
|
setTimeout(() => useTabStore.getState().saveToDB(), 100)
|
|
}
|
|
} catch (error) {
|
|
logError('打开文件失败', error)
|
|
showToast('打开文件失败', 'error')
|
|
} finally {
|
|
setLoading('file-open', false)
|
|
}
|
|
}, [createTab, setLoading])
|
|
|
|
const handleSave = useCallback(async (): Promise<void> => {
|
|
// 防重入:300ms 内忽略重复调用(编辑器 onSave + 全局 Ctrl+S 双重触发)
|
|
if (savingGate.current) return
|
|
savingGate.current = true
|
|
try {
|
|
const tab = getActiveTab()
|
|
if (!tab || !window.electronAPI) return
|
|
const result = await window.electronAPI.saveFile({
|
|
filePath: tab.filePath,
|
|
content: tab.content
|
|
})
|
|
if (result.success) {
|
|
useTabStore.getState().setModified(tab.id, false)
|
|
showToast('已保存', 'success')
|
|
}
|
|
} catch (error) {
|
|
logError('保存文件失败', error)
|
|
showToast('保存失败', 'error')
|
|
} finally {
|
|
// 300ms 后释放锁,允许下次保存
|
|
setTimeout(() => { savingGate.current = false }, 300)
|
|
}
|
|
}, [getActiveTab])
|
|
|
|
const handleSaveAs = useCallback(async (): Promise<void> => {
|
|
try {
|
|
const tab = getActiveTab()
|
|
if (!tab || !window.electronAPI) return
|
|
const result = await window.electronAPI.saveFileAs({ content: tab.content })
|
|
if (result.success) {
|
|
useTabStore.getState().setModified(tab.id, false)
|
|
showToast('已保存', 'success')
|
|
}
|
|
} catch (error) {
|
|
logError('另存为失败', error)
|
|
showToast('另存为失败', 'error')
|
|
}
|
|
}, [getActiveTab])
|
|
|
|
const handleOpenRecent = useCallback(async (filePath: string): Promise<void> => {
|
|
if (!window.electronAPI) return
|
|
setLoading('file-open', true)
|
|
try {
|
|
const result = await window.electronAPI.readFile(filePath)
|
|
if (result.success && result.content !== undefined) {
|
|
createTab(filePath, result.content)
|
|
recentFilesRepository.add(filePath)
|
|
setTimeout(() => useTabStore.getState().saveToDB(), 100)
|
|
}
|
|
} finally {
|
|
setLoading('file-open', false)
|
|
}
|
|
}, [createTab, setLoading])
|
|
|
|
return { handleOpenFile, handleSave, handleSaveAs, handleOpenRecent }
|
|
}
|