diff --git a/frontend/src/stores/documentStore.ts b/frontend/src/stores/documentStore.ts index aec0ba1..9b9163f 100644 --- a/frontend/src/stores/documentStore.ts +++ b/frontend/src/stores/documentStore.ts @@ -70,12 +70,11 @@ export const useDocumentStore = defineStore('document', () => { // 在新窗口中打开文档 const openDocumentInNewWindow = async (docId: number): Promise => { 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); diff --git a/frontend/src/stores/editorStore.ts b/frontend/src/stores/editorStore.ts index 7e1923c..54b56c5 100644 --- a/frontend/src/stores/editorStore.ts +++ b/frontend/src/stores/editorStore.ts @@ -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(); diff --git a/frontend/src/stores/tabStore.ts b/frontend/src/stores/tabStore.ts index 250b866..dcd1413 100644 --- a/frontend/src/stores/tabStore.ts +++ b/frontend/src/stores/tabStore.ts @@ -156,10 +156,10 @@ export const useTabStore = defineStore('tab', () => { */ const validateTabs = () => { const validDocIds = Object.keys(documentStore.documents).map(Number); - + // 找出无效的标签页(文档已被删除) const invalidTabIds = tabOrder.value.filter(docId => !validDocIds.includes(docId)); - + if (invalidTabIds.length > 0) { // 批量清理无效标签页 invalidTabIds.forEach(docId => { @@ -175,7 +175,7 @@ export const useTabStore = defineStore('tab', () => { const initializeTab = () => { // 先验证并清理无效的标签页(处理持久化的脏数据) validateTabs(); - + if (isTabsEnabled.value) { const currentDoc = documentStore.currentDocument; if (currentDoc) { @@ -254,7 +254,7 @@ export const useTabStore = defineStore('tab', () => { return { tabsMap, tabOrder, - + // 状态 tabs: readonly(tabs), draggedTabId, @@ -283,9 +283,5 @@ export const useTabStore = defineStore('tab', () => { getTab }; }, { - persist: { - key: 'voidraft-tabs', - storage: localStorage, - pick: ['tabsMap', 'tabOrder'], - }, + persist: false, }); diff --git a/frontend/src/views/editor/basic/cursorPositionExtension.ts b/frontend/src/views/editor/basic/cursorPositionExtension.ts new file mode 100644 index 0000000..9592253 --- /dev/null +++ b/frontend/src/views/editor/basic/cursorPositionExtension.ts @@ -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' + }) + }); +} +