v0.3.6: 修复文档大纲导航Bug + 新增自动保存功能
Bug修复:
- 文档大纲点击标题未定位到编辑器内容 (P0)
- 根因: raw markdown字符偏移 ≠ ProseMirror文档位置
- 改为通过 view.state.doc.descendants() 遍历节点树,
按 heading类型名 + level + textContent 三重匹配定位
新增功能:
- 自动保存 (2秒防抖), 仅对文件标签页生效
- 状态栏显示 自动/保存中... 指示器
版本同步: package.json / AboutDialog / README 均更新至 v0.3.6
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
import { useEffect, useRef, useState, useCallback } from 'react'
|
||||
import { useTabStore } from '../stores/tabStore'
|
||||
import { logError } from '../lib/errorHandler'
|
||||
|
||||
/** Debounce delay for auto-save (ms) */
|
||||
const AUTO_SAVE_DELAY = 2000
|
||||
|
||||
/**
|
||||
* Auto-save hook: subscribes to Zustand tab store, debounces content changes
|
||||
* and saves automatically after a pause in editing for file-backed tabs.
|
||||
*
|
||||
* Uses `useTabStore.getState()` and `.subscribe()` so it doesn't need to
|
||||
* re-render on every keystroke — the effect is triggered once and runs
|
||||
* reactively via the store subscription.
|
||||
*/
|
||||
export function useAutoSave(): { isAutoSaving: boolean; autoSaveEnabled: boolean; toggleAutoSave: () => void } {
|
||||
const [isAutoSaving, setIsAutoSaving] = useState(false)
|
||||
const [autoSaveEnabled, setAutoSaveEnabled] = useState(true)
|
||||
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||
const isSavingRef = useRef(false)
|
||||
const enabledRef = useRef(true)
|
||||
|
||||
const toggleAutoSave = useCallback(() => {
|
||||
setAutoSaveEnabled(prev => {
|
||||
const next = !prev
|
||||
enabledRef.current = next
|
||||
if (!next && timerRef.current) {
|
||||
clearTimeout(timerRef.current)
|
||||
timerRef.current = null
|
||||
}
|
||||
return next
|
||||
})
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
// Subscribe to Zustand store — fires on every state change
|
||||
const unsub = useTabStore.subscribe((state) => {
|
||||
if (!enabledRef.current) return
|
||||
|
||||
const tab = state.getActiveTab()
|
||||
if (!tab || !tab.filePath || !tab.isModified) return
|
||||
|
||||
// Debounce: clear previous timer, start new one
|
||||
if (timerRef.current) {
|
||||
clearTimeout(timerRef.current)
|
||||
}
|
||||
|
||||
timerRef.current = setTimeout(async () => {
|
||||
if (isSavingRef.current) return
|
||||
|
||||
// Re-read latest state inside the timeout callback
|
||||
const currentState = useTabStore.getState()
|
||||
const currentTab = currentState.getActiveTab()
|
||||
if (!currentTab || !currentTab.filePath || !currentTab.isModified) return
|
||||
|
||||
isSavingRef.current = true
|
||||
setIsAutoSaving(true)
|
||||
|
||||
try {
|
||||
if (!window.electronAPI) return
|
||||
const result = await window.electronAPI.saveFile({
|
||||
filePath: currentTab.filePath,
|
||||
content: currentTab.content
|
||||
})
|
||||
if (result.success) {
|
||||
currentState.setModified(currentTab.id, false)
|
||||
}
|
||||
} catch (error) {
|
||||
logError('自动保存失败', error)
|
||||
} finally {
|
||||
setIsAutoSaving(false)
|
||||
isSavingRef.current = false
|
||||
}
|
||||
}, AUTO_SAVE_DELAY)
|
||||
})
|
||||
|
||||
return () => {
|
||||
unsub()
|
||||
if (timerRef.current) {
|
||||
clearTimeout(timerRef.current)
|
||||
timerRef.current = null
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
return { isAutoSaving, autoSaveEnabled, toggleAutoSave }
|
||||
}
|
||||
Reference in New Issue
Block a user