Files
MarkLite/src/renderer/hooks/useFileOperations.ts
T
thzxx ee5f35177e release: v0.6.0 — 内置解析器迁移 / Toast 全面接入 / Sqlark 深度集成
- 渲染管线迁移至 MetonaEditor 内置解析器,移除 unified/rehype 全家桶(9 个依赖)
- 修复相对路径图片修复的目录前缀碰撞与路径解析 bug
- ConfirmDialog/useConfirm/LoadingSpinner/useDocStats 移除,改用 MeToast.confirm/loading/promise
- 新增状态栏(字数/行数/阅读时间/光标位置)、Zen 模式、数据备份导出导入
- Sqlark: 版本化迁移(addMigration/migrateTo)、subscribe 表变更、备份 exportAll/importTable
- 数据库损坏自愈:异常退出残留残缺 SSTable 导致打开失败时自动重建
- 大纲导航改用 scrollToLine 官方 API
- 修复 rollup 平台包互删(postinstall 自动补齐)+ 集成测试(fake-indexeddb)
2026-08-09 16:50:06 +08:00

121 lines
4.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 { MeToast, showToast } from '../lib/toast'
/**
* AR-01: 从 App.tsx 提取的文件操作逻辑
* UX-02: 添加 loading 状态指示
* v0.1.9: handleSave 添加防重入锁,避免编辑器 onSave + 全局 Ctrl+S 双重触发
* v0.6.0: loading 改用 MeToast.loading 链式转换,保存结果用 MeToast.promise 提示
*/
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> => {
if (!window.electronAPI) return
const loading = MeToast.loading('打开文件...')
try {
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)
loading.success('文件已打开')
} else {
loading.dismiss()
}
} catch (error) {
logError('打开文件失败', error)
loading.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
// v0.6.0: promise 监听保存生命周期 — 自动 loading → success/error,返回原 Promise
const result = await MeToast.promise(
window.electronAPI.saveFile({
filePath: tab.filePath,
content: tab.content,
}),
{
loading: '保存中...',
success: '已保存',
error: '保存失败',
},
)
if (result.success) {
useTabStore.getState().setModified(tab.id, false)
} else {
// IPC 返回 success:false 不 rejectpromise 的 error 文案不会触发,需显式提示
showToast('保存失败', 'error')
}
} catch (error) {
logError('保存文件失败', 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 MeToast.promise(
window.electronAPI.saveFileAs({ content: tab.content }),
{
loading: '另存为...',
success: '已保存',
error: '另存为失败',
},
)
if (result.success) {
useTabStore.getState().setModified(tab.id, false)
} else if (!result.canceled) {
showToast('另存为失败', 'error')
}
} catch (error) {
logError('另存为失败', 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 }
}