♻️ Refactor cursor position cache

This commit is contained in:
2025-12-17 00:12:59 +08:00
parent 8fce8bdca4
commit 8a10b8fe0f
4 changed files with 94 additions and 36 deletions

View File

@@ -70,12 +70,11 @@ export const useDocumentStore = defineStore('document', () => {
// 在新窗口中打开文档
const openDocumentInNewWindow = async (docId: number): Promise<boolean> => {
try {
await OpenDocumentWindow(docId);
const tabStore = useTabStore();
if (tabStore.isTabsEnabled && tabStore.hasTab(docId)) {
tabStore.closeTab(docId);
}
await OpenDocumentWindow(docId);
return true;
} catch (error) {
console.error('Failed to open document in new window:', error);

View File

@@ -13,6 +13,7 @@ import {createFontExtensionFromBackend, updateFontConfig} from '@/views/editor/b
import {createStatsUpdateExtension} from '@/views/editor/basic/statsExtension';
import {createContentChangePlugin} from '@/views/editor/basic/contentChangeExtension';
import {createWheelZoomExtension} from '@/views/editor/basic/wheelZoomExtension';
import {createCursorPositionExtension, scrollToCursor} from '@/views/editor/basic/cursorPositionExtension';
import {createDynamicKeymapExtension, updateKeymapExtension} from '@/views/editor/keymap';
import {
createDynamicExtensions,
@@ -21,7 +22,7 @@ import {
setExtensionManagerView
} from '@/views/editor/manager';
import {useExtensionStore} from './extensionStore';
import createCodeBlockExtension, {blockState} from "@/views/editor/extensions/codeblock";
import createCodeBlockExtension from "@/views/editor/extensions/codeblock";
import {LruCache} from '@/common/utils/lruCache';
import {AsyncManager} from '@/common/utils/asyncManager';
import {generateContentHash} from "@/common/utils/hashUtils";
@@ -35,7 +36,7 @@ export interface DocumentStats {
selectedCharacters: number;
}
// 修复:只保存光标位置,恢复时自动滚动到光标处(更简单可靠)
// 修复:只保存光标位置,恢复时自动滚动到光标处
export interface EditorViewState {
cursorPos: number;
}
@@ -180,6 +181,9 @@ export const useEditorStore = defineStore('editor', () => {
enableAutoDetection: true
});
// 光标位置持久化扩展
const cursorPositionExtension = createCursorPositionExtension(documentId);
// 再次检查操作有效性
if (!operationManager.isOperationValid(operationId, documentId)) {
throw new Error('Operation cancelled');
@@ -212,13 +216,23 @@ export const useEditorStore = defineStore('editor', () => {
statsExtension,
contentChangeExtension,
codeBlockExtension,
cursorPositionExtension,
...dynamicExtensions,
];
// 创建编辑器状态
// 获取保存的光标位置
const savedState = documentStore.documentStates[documentId];
const docLength = content.length;
const initialCursorPos = savedState?.cursorPos !== undefined
? Math.min(savedState.cursorPos, docLength)
: docLength;
// 创建编辑器状态,设置初始光标位置
const state = EditorState.create({
doc: content,
extensions
extensions,
selection: { anchor: initialCursorPos, head: initialCursorPos }
});
return new EditorView({
@@ -316,6 +330,9 @@ export const useEditorStore = defineStore('editor', () => {
//使用 nextTick + requestAnimationFrame 确保 DOM 完全渲染
nextTick(() => {
requestAnimationFrame(() => {
// 滚动到当前光标位置
scrollToCursor(instance.view);
// 聚焦编辑器
instance.view.focus();
@@ -487,15 +504,6 @@ export const useEditorStore = defineStore('editor', () => {
await saveEditorContent(documentId);
}
// 保存光标位置
if (instance.view && instance.view.state) {
const currentState: EditorViewState = {
cursorPos: instance.view.state.selection.main.head
};
// 保存到 documentStore 用于持久化
documentStore.documentStates[documentId] = currentState;
}
// 清除自动保存定时器
instance.autoSaveTimer.clear();
@@ -578,22 +586,10 @@ export const useEditorStore = defineStore('editor', () => {
operationManager.cancelAllOperations();
editorCache.clear((_documentId, instance) => {
// 修复:清空前只保存光标位置
if (instance.view) {
const currentState: EditorViewState = {
cursorPos: instance.view.state.selection.main.head
};
// 同时保存到实例和 documentStore
instance.editorState = currentState;
documentStore.documentStates[instance.documentId] = currentState;
}
// 清除自动保存定时器
instance.autoSaveTimer.clear();
// 从扩展管理器移除
removeExtensionManagerView(instance.documentId);
// 移除DOM元素
if (instance.view.dom.parentElement) {
instance.view.dom.remove();

View File

@@ -283,9 +283,5 @@ export const useTabStore = defineStore('tab', () => {
getTab
};
}, {
persist: {
key: 'voidraft-tabs',
storage: localStorage,
pick: ['tabsMap', 'tabOrder'],
},
persist: false,
});

View File

@@ -0,0 +1,67 @@
import {EditorView, ViewPlugin, ViewUpdate} from '@codemirror/view';
import {useDocumentStore} from '@/stores/documentStore';
import {createDebounce} from '@/common/utils/debounce';
/**
* 光标位置持久化扩展
* 实时监听光标位置变化并持久化到 documentStore
*/
export function createCursorPositionExtension(documentId: number) {
return ViewPlugin.fromClass(
class CursorPositionPlugin {
private readonly documentStore = useDocumentStore();
private readonly debouncedSave;
constructor(private view: EditorView) {
const { debouncedFn, flush } = createDebounce(
() => this.saveCursorPosition(),
{ delay: 400 }
);
this.debouncedSave = { fn: debouncedFn, flush };
// 初始化时保存一次光标位置
this.saveCursorPosition();
}
update(update: ViewUpdate) {
// 只在选择变化时触发
if (!update.selectionSet) {
return;
}
// 防抖保存光标位置
this.debouncedSave.fn();
}
destroy() {
// 销毁时立即执行待保存的操作
this.debouncedSave.flush();
// 再保存一次确保最新状态
this.saveCursorPosition();
}
private saveCursorPosition() {
const cursorPos = this.view.state.selection.main.head;
// 持久化到 documentStore
this.documentStore.documentStates[documentId] = {
cursorPos
};
}
}
);
}
/**
* 滚动到当前光标位置(视口中心)
* @param view 编辑器视图
*/
export function scrollToCursor(view: EditorView) {
const cursorPos = view.state.selection.main.head;
view.dispatch({
effects: EditorView.scrollIntoView(cursorPos, {
y: 'center',
x: 'center'
})
});
}