🎨 Optimize code

This commit is contained in:
2026-01-02 00:03:50 +08:00
parent 76f6c30b9d
commit 009274e4ad
14 changed files with 590 additions and 487 deletions

View File

@@ -5,7 +5,7 @@
// 编辑器实例管理 // 编辑器实例管理
export const EDITOR_CONFIG = { export const EDITOR_CONFIG = {
/** 最多缓存的编辑器实例数量 */ /** 最多缓存的编辑器实例数量 */
MAX_INSTANCES: 5, MAX_INSTANCES: 10,
/** 语法树缓存过期时间(毫秒) */ /** 语法树缓存过期时间(毫秒) */
SYNTAX_TREE_CACHE_TIMEOUT: 30000, SYNTAX_TREE_CACHE_TIMEOUT: 30000,
/** 加载状态延迟时间(毫秒) */ /** 加载状态延迟时间(毫秒) */

View File

@@ -37,10 +37,12 @@ import TabContextMenu from './TabContextMenu.vue';
import { useTabStore } from '@/stores/tabStore'; import { useTabStore } from '@/stores/tabStore';
import { useDocumentStore } from '@/stores/documentStore'; import { useDocumentStore } from '@/stores/documentStore';
import { useEditorStore } from '@/stores/editorStore'; import { useEditorStore } from '@/stores/editorStore';
import { useEditorStateStore } from '@/stores/editorStateStore';
const tabStore = useTabStore(); const tabStore = useTabStore();
const documentStore = useDocumentStore(); const documentStore = useDocumentStore();
const editorStore = useEditorStore(); const editorStore = useEditorStore();
const editorStateStore = useEditorStateStore();
// DOM 引用 // DOM 引用
const tabBarRef = ref<HTMLElement>(); const tabBarRef = ref<HTMLElement>();
@@ -55,15 +57,34 @@ const contextMenuTargetId = ref<number | null>(null);
// 标签页操作 // 标签页操作
const switchToTab = async (documentId: number) => { const switchToTab = async (documentId: number) => {
await tabStore.switchToTabAndDocument(documentId);
const doc = documentStore.currentDocument; // 保存旧文档的光标位置
if (doc && doc.id !== undefined && editorStore.hasContainer) { const oldDocId = documentStore.currentDocumentId;
await editorStore.loadEditor(doc.id, doc.content || ''); if (oldDocId) {
const cursorPos = editorStore.getCurrentCursorPosition();
editorStateStore.saveCursorPosition(oldDocId, cursorPos);
} }
if (doc && tabStore.isTabsEnabled) { // 如果旧文档有未保存修改,保存它
tabStore.addOrActivateTab(doc); if (oldDocId && editorStore.hasUnsavedChanges(oldDocId)) {
try {
const content = editorStore.getCurrentContent();
await documentStore.saveDocument(oldDocId, content);
editorStore.syncAfterSave(oldDocId);
} catch (error) {
console.error('save document error:', error);
}
}
// 切换文档
await tabStore.switchToTabAndDocument(documentId);
// 切换到新编辑器
await editorStore.switchToEditor(documentId);
// 更新标签页
if (documentStore.currentDocument && tabStore.isTabsEnabled) {
tabStore.addOrActivateTab(documentStore.currentDocument);
} }
}; };

View File

@@ -51,13 +51,13 @@ let editorScope: ReturnType<typeof effectScope> | null = null;
// 更新当前块语言信息 // 更新当前块语言信息
const updateCurrentBlockLanguage = () => { const updateCurrentBlockLanguage = () => {
if (!editorStore.editorView) { if (!editorStore.currentEditor) {
currentBlockLanguage.value = { name: 'text', auto: false }; currentBlockLanguage.value = { name: 'text', auto: false };
return; return;
} }
try { try {
const state = editorStore.editorView.state; const state = editorStore.currentEditor.state;
const activeBlock = getActiveNoteBlock(state as any); const activeBlock = getActiveNoteBlock(state as any);
if (activeBlock) { if (activeBlock) {
const newLanguage = { const newLanguage = {
@@ -128,7 +128,7 @@ const setupEventListeners = (view: any) => {
// 监听编辑器状态变化 // 监听编辑器状态变化
watch( watch(
() => editorStore.editorView, () => editorStore.currentEditor,
(newView) => { (newView) => {
if (newView) { if (newView) {
setupEventListeners(newView); setupEventListeners(newView);
@@ -175,13 +175,13 @@ const closeLanguageMenu = () => {
// 选择语言 - 优化性能 // 选择语言 - 优化性能
const selectLanguage = (languageId: SupportedLanguage) => { const selectLanguage = (languageId: SupportedLanguage) => {
if (!editorStore.editorView) { if (!editorStore.currentEditor) {
closeLanguageMenu(); closeLanguageMenu();
return; return;
} }
try { try {
const view = editorStore.editorView; const view = editorStore.currentEditor;
const state = view.state; const state = view.state;
const dispatch = view.dispatch; const dispatch = view.dispatch;

View File

@@ -3,12 +3,13 @@ import {computed, nextTick, reactive, ref, watch} from 'vue';
import {useDocumentStore} from '@/stores/documentStore'; import {useDocumentStore} from '@/stores/documentStore';
import {useTabStore} from '@/stores/tabStore'; import {useTabStore} from '@/stores/tabStore';
import {useEditorStore} from '@/stores/editorStore'; import {useEditorStore} from '@/stores/editorStore';
import {useEditorStateStore} from '@/stores/editorStateStore';
import {useWindowStore} from '@/stores/windowStore'; import {useWindowStore} from '@/stores/windowStore';
import {useI18n} from 'vue-i18n'; import {useI18n} from 'vue-i18n';
import {useConfirm} from '@/composables'; import {useConfirm} from '@/composables';
import {validateDocumentTitle} from '@/common/utils/validation'; import {validateDocumentTitle} from '@/common/utils/validation';
import {formatDateTime, truncateString} from '@/common/utils/formatter'; import {formatDateTime, truncateString} from '@/common/utils/formatter';
import type {Document} from '@/../bindings/voidraft/internal/models/ent/models'; import {Document} from '@/../bindings/voidraft/internal/models/ent/models';
// 类型定义 // 类型定义
interface DocumentItem extends Document { interface DocumentItem extends Document {
@@ -18,6 +19,7 @@ interface DocumentItem extends Document {
const documentStore = useDocumentStore(); const documentStore = useDocumentStore();
const tabStore = useTabStore(); const tabStore = useTabStore();
const editorStore = useEditorStore(); const editorStore = useEditorStore();
const editorStateStore = useEditorStateStore();
const windowStore = useWindowStore(); const windowStore = useWindowStore();
const {t} = useI18n(); const {t} = useI18n();
@@ -29,6 +31,7 @@ const editInputRef = ref<HTMLInputElement>();
const state = reactive({ const state = reactive({
isLoaded: false, isLoaded: false,
searchQuery: '', searchQuery: '',
documentList: [] as Document[], // 缓存文档列表
editing: { editing: {
id: null as number | null, id: null as number | null,
title: '' title: ''
@@ -46,7 +49,7 @@ const currentDocName = computed(() => {
}); });
const filteredItems = computed<DocumentItem[]>(() => { const filteredItems = computed<DocumentItem[]>(() => {
const docs = documentStore.documentList; const docs = state.documentList;
const query = state.searchQuery.trim(); const query = state.searchQuery.trim();
if (!query) return docs; if (!query) return docs;
@@ -69,7 +72,7 @@ const filteredItems = computed<DocumentItem[]>(() => {
// 核心操作 // 核心操作
const openMenu = async () => { const openMenu = async () => {
await documentStore.getDocumentMetaList(); state.documentList = await documentStore.getDocumentList();
documentStore.openDocumentSelector(); documentStore.openDocumentSelector();
state.isLoaded = true; state.isLoaded = true;
await nextTick(); await nextTick();
@@ -90,7 +93,7 @@ const closeMenu = () => {
resetDeleteConfirm(); resetDeleteConfirm();
}; };
const selectDoc = async (doc: Document) => { const selectDoc = async (doc: DocumentItem) => {
if (doc.id === undefined) return; if (doc.id === undefined) return;
// 如果选择的就是当前文档,直接关闭菜单 // 如果选择的就是当前文档,直接关闭菜单
@@ -106,16 +109,32 @@ const selectDoc = async (doc: Document) => {
} }
// 保存旧文档的光标位置
const oldDocId = documentStore.currentDocumentId;
if (oldDocId) {
const cursorPos = editorStore.getCurrentCursorPosition();
editorStateStore.saveCursorPosition(oldDocId, cursorPos);
}
// 如果旧文档有未保存修改,保存它
if (oldDocId && editorStore.hasUnsavedChanges(oldDocId)) {
const content = editorStore.getCurrentContent();
await documentStore.saveDocument(oldDocId, content);
editorStore.syncAfterSave(oldDocId);
}
// 打开新文档
const success = await documentStore.openDocument(doc.id); const success = await documentStore.openDocument(doc.id);
if (!success) return; if (!success) return;
const fullDoc = documentStore.currentDocument; // 切换到新编辑器
if (fullDoc && editorStore.hasContainer) { await editorStore.switchToEditor(doc.id);
await editorStore.loadEditor(fullDoc.id!, fullDoc.content || '');
}
if (fullDoc && tabStore.isTabsEnabled) { // 更新标签页
tabStore.addOrActivateTab(fullDoc); if (documentStore.currentDocument && tabStore.isTabsEnabled) {
tabStore.addOrActivateTab(documentStore.currentDocument);
} }
closeMenu(); closeMenu();
@@ -128,7 +147,9 @@ const createDoc = async (title: string) => {
try { try {
const newDoc = await documentStore.createNewDocument(trimmedTitle); const newDoc = await documentStore.createNewDocument(trimmedTitle);
if (newDoc) await selectDoc(newDoc); if (newDoc && newDoc.id) {
await selectDoc(newDoc);
}
} catch (error) { } catch (error) {
console.error('Failed to create document:', error); console.error('Failed to create document:', error);
} }
@@ -151,7 +172,7 @@ const handleSearchEnter = () => {
}; };
// 编辑操作 // 编辑操作
const renameDoc = (doc: Document, event: Event) => { const renameDoc = (doc: DocumentItem, event: Event) => {
event.stopPropagation(); event.stopPropagation();
state.editing.id = doc.id ?? null; state.editing.id = doc.id ?? null;
state.editing.title = doc.title || ''; state.editing.title = doc.title || '';
@@ -174,8 +195,8 @@ const saveEdit = async () => {
if (error) return; if (error) return;
try { try {
await documentStore.updateDocumentMetadata(state.editing.id, trimmedTitle); await documentStore.updateDocumentTitle(state.editing.id, trimmedTitle);
await documentStore.getDocumentMetaList(); state.documentList = await documentStore.getDocumentList();
// 如果tabs功能开启且该文档有标签页更新标签页标题 // 如果tabs功能开启且该文档有标签页更新标签页标题
if (tabStore.isTabsEnabled && tabStore.hasTab(state.editing.id)) { if (tabStore.isTabsEnabled && tabStore.hasTab(state.editing.id)) {
@@ -195,7 +216,7 @@ const cancelEdit = () => {
}; };
// 其他操作 // 其他操作
const openInNewWindow = async (doc: Document, event: Event) => { const openInNewWindow = async (doc: DocumentItem, event: Event) => {
event.stopPropagation(); event.stopPropagation();
if (doc.id === undefined) return; if (doc.id === undefined) return;
try { try {
@@ -209,7 +230,7 @@ const openInNewWindow = async (doc: Document, event: Event) => {
} }
}; };
const handleDelete = async (doc: Document, event: Event) => { const handleDelete = async (doc: DocumentItem, event: Event) => {
event.stopPropagation(); event.stopPropagation();
if (doc.id === undefined) return; if (doc.id === undefined) return;
@@ -224,10 +245,10 @@ const handleDelete = async (doc: Document, event: Event) => {
const deleteSuccess = await documentStore.deleteDocument(doc.id); const deleteSuccess = await documentStore.deleteDocument(doc.id);
if (deleteSuccess) { if (deleteSuccess) {
await documentStore.getDocumentMetaList(); state.documentList = await documentStore.getDocumentList();
// 如果删除的是当前文档,切换到第一个文档 // 如果删除的是当前文档,切换到第一个文档
if (documentStore.currentDocument?.id === doc.id && documentStore.documentList.length > 0) { if (documentStore.currentDocument?.id === doc.id && state.documentList.length > 0) {
const firstDoc = documentStore.documentList[0]; const firstDoc = state.documentList[0];
if (firstDoc) await selectDoc(firstDoc); if (firstDoc) await selectDoc(firstDoc);
} }
} }
@@ -366,7 +387,7 @@ watch(() => documentStore.showDocumentSelector, (isOpen) => {
</svg> </svg>
</button> </button>
<button <button
v-if="documentStore.documentList.length > 1 && item.id !== 1" v-if="state.documentList.length > 1 && item.id !== 1"
class="action-btn delete-btn" class="action-btn delete-btn"
:class="{ 'delete-confirm': isDeleting(item.id!) }" :class="{ 'delete-confirm': isDeleting(item.id!) }"
@click="handleDelete(item, $event)" @click="handleDelete(item, $event)"

View File

@@ -3,6 +3,7 @@ import {useI18n} from 'vue-i18n';
import {computed, onMounted, onUnmounted, ref, watch, shallowRef, readonly, toRefs, effectScope, onScopeDispose} from 'vue'; import {computed, onMounted, onUnmounted, ref, watch, shallowRef, readonly, toRefs, effectScope, onScopeDispose} from 'vue';
import {useConfigStore} from '@/stores/configStore'; import {useConfigStore} from '@/stores/configStore';
import {useEditorStore} from '@/stores/editorStore'; import {useEditorStore} from '@/stores/editorStore';
import {useEditorStateStore} from '@/stores/editorStateStore';
import {useUpdateStore} from '@/stores/updateStore'; import {useUpdateStore} from '@/stores/updateStore';
import {useWindowStore} from '@/stores/windowStore'; import {useWindowStore} from '@/stores/windowStore';
import {useSystemStore} from '@/stores/systemStore'; import {useSystemStore} from '@/stores/systemStore';
@@ -15,6 +16,7 @@ import {formatBlockContent} from '@/views/editor/extensions/codeblock/formatCode
import {createDebounce} from '@/common/utils/debounce'; import {createDebounce} from '@/common/utils/debounce';
const editorStore = useEditorStore(); const editorStore = useEditorStore();
const editorStateStore = useEditorStateStore();
const configStore = useConfigStore(); const configStore = useConfigStore();
const updateStore = useUpdateStore(); const updateStore = useUpdateStore();
const windowStore = useWindowStore(); const windowStore = useWindowStore();
@@ -25,7 +27,6 @@ const router = useRouter();
const canFormatCurrentBlock = ref(false); const canFormatCurrentBlock = ref(false);
const isLoaded = shallowRef(false); const isLoaded = shallowRef(false);
const { documentStats } = toRefs(editorStore);
const { config } = toRefs(configStore); const { config } = toRefs(configStore);
// 窗口置顶状态 // 窗口置顶状态
@@ -57,14 +58,14 @@ const goToSettings = () => {
// 执行格式化 // 执行格式化
const formatCurrentBlock = () => { const formatCurrentBlock = () => {
if (!canFormatCurrentBlock.value || !editorStore.editorView) return; if (!canFormatCurrentBlock.value || !editorStore.currentEditor) return;
formatBlockContent(editorStore.editorView); formatBlockContent(editorStore.currentEditor);
}; };
// 统一更新按钮状态 // 统一更新按钮状态
const updateButtonStates = () => { const updateButtonStates = () => {
const view: any = editorStore.editorView; const view: any = editorStore.currentEditor;
if (!view) { if (!view) {
canFormatCurrentBlock.value = false; canFormatCurrentBlock.value = false;
return; return;
@@ -125,7 +126,7 @@ const setupEditorListeners = (view: any) => {
// 监听编辑器视图变化 // 监听编辑器视图变化
watch( watch(
() => editorStore.editorView, () => editorStore.currentEditor,
(newView) => { (newView) => {
// 在 scope 中管理副作用 // 在 scope 中管理副作用
editorScope.run(() => { editorScope.run(() => {
@@ -191,11 +192,13 @@ const updateButtonTitle = computed(() => {
}); });
// 统计数据的计算属性 // 统计数据的计算属性
const statsData = computed(() => ({ const statsData = computed(() => {
lines: documentStats.value.lines, const docId = editorStore.currentEditorId;
characters: documentStats.value.characters, if (!docId) {
selectedCharacters: documentStats.value.selectedCharacters return { lines: 0, characters: 0, selectedCharacters: 0 };
})); }
return editorStateStore.getDocumentStats(docId);
});
</script> </script>
<template> <template>

View File

@@ -1,43 +1,24 @@
import {defineStore} from 'pinia'; import {defineStore} from 'pinia';
import {computed, ref} from 'vue'; import {ref} from 'vue';
import {DocumentService} from '@/../bindings/voidraft/internal/services'; import {DocumentService} from '@/../bindings/voidraft/internal/services';
import {OpenDocumentWindow} from '@/../bindings/voidraft/internal/services/windowservice'; import {OpenDocumentWindow} from '@/../bindings/voidraft/internal/services/windowservice';
import {Document} from '@/../bindings/voidraft/internal/models/ent/models'; import {Document} from '@/../bindings/voidraft/internal/models/ent/models';
import type {EditorViewState} from '@/stores/editorStore'; import type {TimerManager} from '@/common/utils/timerUtils';
import {createTimerManager} from '@/common/utils/timerUtils';
export const useDocumentStore = defineStore('document', () => { export const useDocumentStore = defineStore('document', () => {
// === 核心状态 ===
const documents = ref<Record<number, Document>>({});
const currentDocumentId = ref<number | null>(null); const currentDocumentId = ref<number | null>(null);
const currentDocument = ref<Document | null>(null); const currentDocument = ref<Document | null>(null);
// === 编辑器状态持久化 === // 自动保存定时器
const documentStates = ref<Record<number, EditorViewState>>({}); const autoSaveTimers = ref<Map<number, TimerManager>>(new Map());
// === UI状态 === // === UI状态 ===
const showDocumentSelector = ref(false); const showDocumentSelector = ref(false);
const selectorError = ref<{ docId: number; message: string } | null>(null); const selectorError = ref<{ docId: number; message: string } | null>(null);
const isLoading = ref(false); const isLoading = ref(false);
// === 计算属性 ===
const documentList = computed(() =>
Object.values(documents.value).sort((a, b) => {
const timeA = a.updated_at ? new Date(a.updated_at).getTime() : 0;
const timeB = b.updated_at ? new Date(b.updated_at).getTime() : 0;
return timeB - timeA;
})
);
const setDocuments = (docs: Document[]) => {
documents.value = {};
docs.forEach(doc => {
if (doc.id !== undefined) {
documents.value[doc.id] = doc;
}
});
};
// === 错误处理 === // === 错误处理 ===
const setError = (docId: number, message: string) => { const setError = (docId: number, message: string) => {
selectorError.value = {docId, message}; selectorError.value = {docId, message};
@@ -65,6 +46,39 @@ export const useDocumentStore = defineStore('document', () => {
}; };
// 获取文档列表
const getDocumentList = async (): Promise<Document[]> => {
try {
isLoading.value = true;
const docs = await DocumentService.ListAllDocumentsMeta();
return docs?.filter((doc): doc is Document => doc !== null) || [];
} catch (_error) {
return [];
} finally {
isLoading.value = false;
}
};
// 获取单个文档
const getDocument = async (docId: number): Promise<Document | null> => {
try {
return await DocumentService.GetDocumentByID(docId);
} catch (error) {
console.error('Failed to get document:', error);
return null;
}
};
// 保存文档内容
const saveDocument = async (docId: number, content: string): Promise<Document | null> => {
try {
await DocumentService.UpdateDocumentContent(docId, content);
return await DocumentService.GetDocumentByID(docId);
} catch (error) {
console.error('Failed to save document:', error);
throw error;
}
};
// 在新窗口中打开文档 // 在新窗口中打开文档
const openDocumentInNewWindow = async (docId: number): Promise<boolean> => { const openDocumentInNewWindow = async (docId: number): Promise<boolean> => {
@@ -81,36 +95,16 @@ export const useDocumentStore = defineStore('document', () => {
const createNewDocument = async (title: string): Promise<Document | null> => { const createNewDocument = async (title: string): Promise<Document | null> => {
try { try {
const doc = await DocumentService.CreateDocument(title); const doc = await DocumentService.CreateDocument(title);
if (doc && doc.id !== undefined) { return doc || null;
documents.value[doc.id] = doc;
return doc;
}
return null;
} catch (error) { } catch (error) {
console.error('Failed to create document:', error); console.error('Failed to create document:', error);
return null; return null;
} }
}; };
// 获取文档列表 // 打开文档
const getDocumentMetaList = async () => {
try {
isLoading.value = true;
const docs = await DocumentService.ListAllDocumentsMeta();
if (docs) {
setDocuments(docs.filter((doc): doc is Document => doc !== null));
}
} catch (error) {
console.error('Failed to update documents:', error);
} finally {
isLoading.value = false;
}
};
// 打开文档 - 只负责文档数据管理
const openDocument = async (docId: number): Promise<boolean> => { const openDocument = async (docId: number): Promise<boolean> => {
try { try {
// 获取完整文档数据
const doc = await DocumentService.GetDocumentByID(docId); const doc = await DocumentService.GetDocumentByID(docId);
if (!doc) { if (!doc) {
throw new Error(`Document ${docId} not found`); throw new Error(`Document ${docId} not found`);
@@ -118,7 +112,6 @@ export const useDocumentStore = defineStore('document', () => {
currentDocumentId.value = docId; currentDocumentId.value = docId;
currentDocument.value = doc; currentDocument.value = doc;
return true; return true;
} catch (error) { } catch (error) {
console.error('Failed to open document:', error); console.error('Failed to open document:', error);
@@ -126,18 +119,12 @@ export const useDocumentStore = defineStore('document', () => {
} }
}; };
// 更新文档元数据 - 只负责文档数据管理 // 更新文档标题
const updateDocumentMetadata = async (docId: number, title: string): Promise<boolean> => { const updateDocumentTitle = async (docId: number, title: string): Promise<boolean> => {
try { try {
await DocumentService.UpdateDocumentTitle(docId, title); await DocumentService.UpdateDocumentTitle(docId, title);
// 更新本地状态 // 更新当前文档状态
const doc = documents.value[docId];
if (doc) {
doc.title = title;
doc.updated_at = new Date().toISOString();
}
if (currentDocument.value?.id === docId) { if (currentDocument.value?.id === docId) {
currentDocument.value.title = title; currentDocument.value.title = title;
currentDocument.value.updated_at = new Date().toISOString(); currentDocument.value.updated_at = new Date().toISOString();
@@ -145,24 +132,28 @@ export const useDocumentStore = defineStore('document', () => {
return true; return true;
} catch (error) { } catch (error) {
console.error('Failed to update document metadata:', error); console.error('Failed to update document title:', error);
return false; return false;
} }
}; };
// 删除文档 - 只负责文档数据管理 // 删除文档
const deleteDocument = async (docId: number): Promise<boolean> => { const deleteDocument = async (docId: number): Promise<boolean> => {
try { try {
await DocumentService.DeleteDocument(docId); await DocumentService.DeleteDocument(docId);
// 更新本地状态 // 清理定时器
delete documents.value[docId]; const timer = autoSaveTimers.value.get(docId);
if (timer) {
timer.clear();
autoSaveTimers.value.delete(docId);
}
// 如果删除的是当前文档,切换到第一个可用文档 // 如果删除的是当前文档,切换到第一个可用文档
if (currentDocumentId.value === docId) { if (currentDocumentId.value === docId) {
const availableDocs = Object.values(documents.value); const docs = await getDocumentList();
if (availableDocs.length > 0 && availableDocs[0].id !== undefined) { if (docs.length > 0 && docs[0].id !== undefined) {
await openDocument(availableDocs[0].id); await openDocument(docs[0].id);
} else { } else {
currentDocumentId.value = null; currentDocumentId.value = null;
currentDocument.value = null; currentDocument.value = null;
@@ -176,22 +167,45 @@ export const useDocumentStore = defineStore('document', () => {
} }
}; };
// === 初始化 === // 调度自动保存
const scheduleAutoSave = (docId: number, saveCallback: () => Promise<void>, delay: number = 2000) => {
let timer = autoSaveTimers.value.get(docId);
if (!timer) {
timer = createTimerManager();
autoSaveTimers.value.set(docId, timer);
}
timer.set(async () => {
try {
await saveCallback();
} catch (error) {
console.error(`auto save for document ${docId} failed:`, error);
}
}, delay);
};
// 取消自动保存
const cancelAutoSave = (docId: number) => {
const timer = autoSaveTimers.value.get(docId);
if (timer) {
timer.clear();
}
};
// 初始化文档
const initDocument = async (urlDocumentId?: number): Promise<void> => { const initDocument = async (urlDocumentId?: number): Promise<void> => {
try { try {
await getDocumentMetaList(); const docs = await getDocumentList();
// 优先使用URL参数中的文档ID // 优先使用URL参数中的文档ID
if (urlDocumentId && documents.value[urlDocumentId]) { if (urlDocumentId) {
await openDocument(urlDocumentId); await openDocument(urlDocumentId);
} else if (currentDocumentId.value && documents.value[currentDocumentId.value]) { } else if (currentDocumentId.value) {
// 如果URL中没有指定文档ID使用持久化的文档ID // 使用持久化的文档ID
await openDocument(currentDocumentId.value); await openDocument(currentDocumentId.value);
} else { } else if (docs.length > 0 && docs[0].id !== undefined) {
// 否则打开第一个文档 // 打开第一个文档
if (documentList.value[0].id) { await openDocument(docs[0].id);
await openDocument(documentList.value[0].id);
}
} }
} catch (error) { } catch (error) {
console.error('Failed to initialize document store:', error); console.error('Failed to initialize document store:', error);
@@ -200,32 +214,38 @@ export const useDocumentStore = defineStore('document', () => {
return { return {
// 状态 // 状态
documents,
documentList,
currentDocumentId, currentDocumentId,
currentDocument, currentDocument,
documentStates,
showDocumentSelector, showDocumentSelector,
selectorError, selectorError,
isLoading, isLoading,
// 方法 getDocumentList,
getDocumentMetaList, getDocument,
saveDocument,
createNewDocument,
updateDocumentTitle,
deleteDocument,
openDocument, openDocument,
openDocumentInNewWindow, openDocumentInNewWindow,
createNewDocument,
updateDocumentMetadata, // 自动保存
deleteDocument, scheduleAutoSave,
cancelAutoSave,
// UI 控制
openDocumentSelector, openDocumentSelector,
closeDocumentSelector, closeDocumentSelector,
setError, setError,
clearError, clearError,
// 初始化
initDocument, initDocument,
}; };
}, { }, {
persist: { persist: {
key: 'voidraft-document', key: 'voidraft-document',
storage: localStorage, storage: localStorage,
pick: ['currentDocumentId', 'documents', 'documentStates'] pick: ['currentDocumentId']
} }
}); });

View File

@@ -0,0 +1,70 @@
import {defineStore} from 'pinia';
import {ref} from 'vue';
export interface DocumentStats {
lines: number;
characters: number;
selectedCharacters: number;
}
export const useEditorStateStore = defineStore('editorState', () => {
// 光标位置存储 Record<docId, cursorPosition>
const cursorPositions = ref<Record<number, number>>({});
// 文档统计数据存储 Record<docId, DocumentStats>
const documentStats = ref<Record<number, DocumentStats>>({});
// 保存光标位置
const saveCursorPosition = (docId: number, position: number) => {
cursorPositions.value[docId] = position;
};
// 获取光标位置
const getCursorPosition = (docId: number): number | undefined => {
return cursorPositions.value[docId];
};
// 保存文档统计数据
const saveDocumentStats = (docId: number, stats: DocumentStats) => {
documentStats.value[docId] = stats;
};
// 获取文档统计数据
const getDocumentStats = (docId: number): DocumentStats => {
return documentStats.value[docId] || {
lines: 0,
characters: 0,
selectedCharacters: 0
};
};
// 清除文档状态
const clearDocumentState = (docId: number) => {
delete cursorPositions.value[docId];
delete documentStats.value[docId];
};
// 清除所有状态
const clearAllStates = () => {
cursorPositions.value = {};
documentStats.value = {};
};
return {
cursorPositions,
documentStats,
saveCursorPosition,
getCursorPosition,
saveDocumentStats,
getDocumentStats,
clearDocumentState,
clearAllStates
};
}, {
persist: {
key: 'voidraft-editor-state',
storage: localStorage,
pick: ['cursorPositions']
}
});

View File

@@ -1,11 +1,10 @@
import {defineStore} from 'pinia'; import {defineStore} from 'pinia';
import {computed, nextTick, ref} from 'vue'; import {computed, readonly, ref} from 'vue';
import {EditorView} from '@codemirror/view'; import {EditorView} from '@codemirror/view';
import {EditorState, Extension} from '@codemirror/state'; import {EditorState, Extension} from '@codemirror/state';
import {Document} from '@/../bindings/voidraft/internal/models/ent/models';
import {useConfigStore} from './configStore'; import {useConfigStore} from './configStore';
import {useDocumentStore} from './documentStore'; import {useDocumentStore} from './documentStore';
import {DocumentService, ExtensionService} from '@/../bindings/voidraft/internal/services';
import {ensureSyntaxTree} from "@codemirror/language";
import {createBasicSetup} from '@/views/editor/basic/basicSetup'; import {createBasicSetup} from '@/views/editor/basic/basicSetup';
import {createThemeExtension, updateEditorTheme} from '@/views/editor/basic/themeExtension'; import {createThemeExtension, updateEditorTheme} from '@/views/editor/basic/themeExtension';
import {getTabExtensions, updateTabConfig} from '@/views/editor/basic/tabExtension'; import {getTabExtensions, updateTabConfig} from '@/views/editor/basic/tabExtension';
@@ -17,121 +16,65 @@ import {createCursorPositionExtension, scrollToCursor} from '@/views/editor/basi
import {createDynamicKeymapExtension, updateKeymapExtension} from '@/views/editor/keymap'; import {createDynamicKeymapExtension, updateKeymapExtension} from '@/views/editor/keymap';
import { import {
createDynamicExtensions, createDynamicExtensions,
getExtensionManager,
removeExtensionManagerView, removeExtensionManagerView,
setExtensionManagerView setExtensionManagerView
} from '@/views/editor/manager'; } from '@/views/editor/manager';
import {useExtensionStore} from './extensionStore';
import createCodeBlockExtension from "@/views/editor/extensions/codeblock"; import createCodeBlockExtension from "@/views/editor/extensions/codeblock";
import {LruCache} from '@/common/utils/lruCache'; import {LruCache} from '@/common/utils/lruCache';
import {generateContentHash} from "@/common/utils/hashUtils";
import {createTimerManager, type TimerManager} from '@/common/utils/timerUtils';
import {EDITOR_CONFIG} from '@/common/constant/editor'; import {EDITOR_CONFIG} from '@/common/constant/editor';
import {createDebounce} from '@/common/utils/debounce'; import {useEditorStateStore, type DocumentStats} from './editorStateStore';
import {useKeybindingStore} from "@/stores/keybindingStore";
export interface DocumentStats {
lines: number;
characters: number;
selectedCharacters: number;
}
export interface EditorViewState {
cursorPos: number;
}
// 编辑器实例
interface EditorInstance { interface EditorInstance {
view: EditorView; view: EditorView;
documentId: number; documentId: number;
content: string; contentTimestamp: string; // 文档时间戳
contentLength: number; // 内容长度
isDirty: boolean; isDirty: boolean;
lastModified: Date; lastModified: number;
autoSaveTimer: TimerManager;
syntaxTreeCache: {
lastDocLength: number;
lastContentHash: string;
lastParsed: Date;
} | null;
editorState?: EditorViewState;
} }
export const useEditorStore = defineStore('editor', () => { export const useEditorStore = defineStore('editor', () => {
// === 依赖store ===
const configStore = useConfigStore(); const configStore = useConfigStore();
const documentStore = useDocumentStore(); const documentStore = useDocumentStore();
const extensionStore = useExtensionStore(); const editorStateStore = useEditorStateStore();
// === 核心状态 ===
const editorCache = new LruCache<number, EditorInstance>(EDITOR_CONFIG.MAX_INSTANCES); const editorCache = new LruCache<number, EditorInstance>(EDITOR_CONFIG.MAX_INSTANCES);
const containerElement = ref<HTMLElement | null>(null); const containerElement = ref<HTMLElement | null>(null);
const currentEditorId = ref<number | null>(null);
const currentEditor = ref<EditorView | null>(null);
const documentStats = ref<DocumentStats>({
lines: 0,
characters: 0,
selectedCharacters: 0
});
// 编辑器加载状态
const isLoading = ref(false); const isLoading = ref(false);
// 自动保存设置 - 从配置动态获取
const getAutoSaveDelay = () => configStore.config.editing.autoSaveDelay;
// 创建防抖的语法树缓存清理函数 // 验证缓存是否有效
const debouncedClearSyntaxCache = createDebounce((instance) => { const isCacheValid = (cached: EditorInstance, doc: Document): boolean => {
if (instance) { return cached.contentTimestamp === doc.updated_at
instance.syntaxTreeCache = null; && cached.contentLength === (doc.content || '').length;
}
}, {delay: 500}); // 500ms 内的多次输入只清理一次
// 缓存化的语法树确保方法
const ensureSyntaxTreeCached = (view: EditorView, documentId: number): void => {
const instance = editorCache.get(documentId);
if (!instance) return;
const docLength = view.state.doc.length;
const content = view.state.doc.toString();
const contentHash = generateContentHash(content);
const now = new Date();
// 检查是否需要重新构建语法树
const cache = instance.syntaxTreeCache;
const shouldRebuild = !cache ||
cache.lastDocLength !== docLength ||
cache.lastContentHash !== contentHash ||
(now.getTime() - cache.lastParsed.getTime()) > EDITOR_CONFIG.SYNTAX_TREE_CACHE_TIMEOUT;
if (shouldRebuild) {
ensureSyntaxTree(view.state, docLength, 5000);
// 更新缓存
instance.syntaxTreeCache = {
lastDocLength: docLength,
lastContentHash: contentHash,
lastParsed: now
}; };
} // 检查内容是否真的不同
const hasContentChanged = (cached: EditorInstance, doc: Document): boolean => {
const currentContent = cached.view.state.doc.toString();
return currentContent !== (doc.content || '');
}; };
// 创建编辑器实例 // 创建编辑器实例
const createEditorInstance = async ( const createEditorInstance = async (
content: string, docId: number,
documentId: number doc: Document
): Promise<EditorView> => { ): Promise<EditorInstance> => {
if (!containerElement.value) { if (!containerElement.value) {
throw new Error('Editor container not set'); throw new Error('Editor container not set');
} }
// 获取基本扩展 const content = doc.content || '';
// 基本扩展
const basicExtensions = createBasicSetup(); const basicExtensions = createBasicSetup();
// 获取主题扩展 // 主题扩展
const themeExtension = createThemeExtension(); const themeExtension = createThemeExtension();
// Tab相关扩展 // Tab 扩展
const tabExtensions = getTabExtensions( const tabExtensions = getTabExtensions(
configStore.config.editing.tabSize, configStore.config.editing.tabSize,
configStore.config.editing.enableTabIndent, configStore.config.editing.enableTabIndent,
@@ -146,6 +89,7 @@ export const useEditorStore = defineStore('editor', () => {
fontWeight: configStore.config.editing.fontWeight fontWeight: configStore.config.editing.fontWeight
}); });
// 滚轮缩放扩展
const wheelZoomExtension = createWheelZoomExtension({ const wheelZoomExtension = createWheelZoomExtension({
increaseFontSize: () => configStore.increaseFontSizeLocal(), increaseFontSize: () => configStore.increaseFontSizeLocal(),
decreaseFontSize: () => configStore.decreaseFontSizeLocal(), decreaseFontSize: () => configStore.decreaseFontSizeLocal(),
@@ -154,10 +98,16 @@ export const useEditorStore = defineStore('editor', () => {
}); });
// 统计扩展 // 统计扩展
const statsExtension = createStatsUpdateExtension(updateDocumentStats); const statsExtension = createStatsUpdateExtension((stats: DocumentStats) => {
if (currentEditorId.value) {
editorStateStore.saveDocumentStats(currentEditorId.value, stats);
}
});
// 内容变化扩展 // 内容变化扩展
const contentChangeExtension = createContentChangePlugin(); const contentChangeExtension = createContentChangePlugin(() => {
handleContentChange(docId);
});
// 代码块扩展 // 代码块扩展
const codeBlockExtension = createCodeBlockExtension({ const codeBlockExtension = createCodeBlockExtension({
@@ -166,12 +116,12 @@ export const useEditorStore = defineStore('editor', () => {
}); });
// 光标位置持久化扩展 // 光标位置持久化扩展
const cursorPositionExtension = createCursorPositionExtension(documentId); const cursorPositionExtension = createCursorPositionExtension(docId);
// 快捷键扩展 // 快捷键扩展
const keymapExtension = await createDynamicKeymapExtension(); const keymapExtension = await createDynamicKeymapExtension();
// 动态扩展传递文档ID以便扩展管理器可以预初始化 // 动态扩展
const dynamicExtensions = await createDynamicExtensions(); const dynamicExtensions = await createDynamicExtensions();
// 组合所有扩展 // 组合所有扩展
@@ -190,213 +140,191 @@ export const useEditorStore = defineStore('editor', () => {
]; ];
// 获取保存的光标位置 // 获取保存的光标位置
const savedState = documentStore.documentStates[documentId]; const savedCursorPos = editorStateStore.getCursorPosition(docId);
const docLength = content.length; const docLength = content.length;
const initialCursorPos = savedState?.cursorPos !== undefined const initialCursorPos = savedCursorPos !== undefined
? Math.min(savedState.cursorPos, docLength) ? Math.min(savedCursorPos, docLength)
: docLength; : docLength;
// 创建编辑器状态
// 创建编辑器状态,设置初始光标位置
const state = EditorState.create({ const state = EditorState.create({
doc: content, doc: content,
extensions, extensions,
selection: {anchor: initialCursorPos, head: initialCursorPos} selection: {anchor: initialCursorPos, head: initialCursorPos}
}); });
return new EditorView({ const view = new EditorView({state});
state
});
};
// 添加编辑器到缓存 return {
const addEditorToCache = (documentId: number, view: EditorView, content: string) => {
const instance: EditorInstance = {
view, view,
documentId, documentId: docId,
content, contentTimestamp: doc.updated_at || '',
contentLength: content.length,
isDirty: false, isDirty: false,
lastModified: new Date(), lastModified: Date.now()
autoSaveTimer: createTimerManager(), };
syntaxTreeCache: null,
editorState: documentStore.documentStates[documentId]
}; };
// 使用LRU缓存的onEvict回调处理被驱逐的实例 // 更新编辑器内容
editorCache.set(documentId, instance, (_evictedKey, evictedInstance) => { const updateEditorContent = (instance: EditorInstance, doc: Document) => {
// 清除自动保存定时器 const currentContent = instance.view.state.doc.toString();
evictedInstance.autoSaveTimer.clear(); const newContent = doc.content || '';
// 移除DOM元素
if (evictedInstance.view.dom.parentElement) { // 如果内容相同,只更新元数据
evictedInstance.view.dom.remove(); if (currentContent === newContent) {
instance.contentTimestamp = doc.updated_at || '';
instance.contentLength = newContent.length;
return;
}
// 保存当前光标位置
const currentCursorPos = instance.view.state.selection.main.head;
// 更新内容
instance.view.dispatch({
changes: {
from: 0,
to: instance.view.state.doc.length,
insert: newContent
} }
evictedInstance.view.destroy();
}); });
// 初始化语法树缓存 // 智能恢复光标位置
ensureSyntaxTreeCached(view, documentId); const newContentLength = newContent.length;
}; const safeCursorPos = Math.min(currentCursorPos, newContentLength);
// 获取或创建编辑器 if (safeCursorPos > 0 && safeCursorPos < newContentLength) {
const getOrCreateEditor = async ( instance.view.dispatch({
documentId: number, selection: {anchor: safeCursorPos, head: safeCursorPos}
content: string });
): Promise<EditorView> => {
// 检查缓存
const cached = editorCache.get(documentId);
if (cached) {
return cached.view;
} }
// 创建新的编辑器实例 // 同步元数据
const view = await createEditorInstance(content, documentId); instance.contentTimestamp = doc.updated_at || '';
instance.contentLength = newContent.length;
addEditorToCache(documentId, view, content); instance.isDirty = false;
return view;
}; };
// 显示编辑器 // 显示编辑器
const showEditor = (documentId: number) => { const showEditor = (instance: EditorInstance) => {
const instance = editorCache.get(documentId); if (!containerElement.value) return;
if (!instance || !containerElement.value) return;
try { try {
// 移除当前编辑器DOM // 移除当前编辑器 DOM
if (currentEditor.value && currentEditor.value.dom && currentEditor.value.dom.parentElement) { const currentEditor = editorCache.get(currentEditorId.value || 0);
currentEditor.value.dom.remove(); if (currentEditor && currentEditor.view.dom && currentEditor.view.dom.parentElement) {
currentEditor.view.dom.remove();
} }
// 目标编辑器DOM添加到容器 // 添加目标编辑器 DOM
containerElement.value.appendChild(instance.view.dom); containerElement.value.appendChild(instance.view.dom);
currentEditor.value = instance.view; currentEditorId.value = instance.documentId;
// 设置扩展管理器视图 // 设置扩展管理器视图
setExtensionManagerView(instance.view, documentId); setExtensionManagerView(instance.view, instance.documentId);
//使用 nextTick + requestAnimationFrame 确保 DOM 完全渲染 // 使用 requestAnimationFrame 确保 DOM 渲染
nextTick(() => {
requestAnimationFrame(() => { requestAnimationFrame(() => {
// 滚动到当前光标位置
scrollToCursor(instance.view); scrollToCursor(instance.view);
// 聚焦编辑器
instance.view.focus(); instance.view.focus();
// 使用缓存的语法树确保方法
ensureSyntaxTreeCached(instance.view, documentId);
});
}); });
} catch (error) { } catch (error) {
console.error('Error showing editor:', error); console.error('Error showing editor:', error);
} }
}; };
// 保存编辑器内容
const saveEditorContent = async (documentId: number): Promise<boolean> => {
const instance = editorCache.get(documentId);
if (!instance || !instance.isDirty) return true;
try {
const content = instance.view.state.doc.toString();
const lastModified = instance.lastModified;
await DocumentService.UpdateDocumentContent(documentId, content);
// 检查在保存期间内容是否又被修改了
if (instance.lastModified === lastModified) {
instance.content = content;
instance.isDirty = false;
instance.lastModified = new Date();
}
return true;
} catch (error) {
console.error('Failed to save editor content:', error);
return false;
}
};
// 内容变化处理 // 内容变化处理
const onContentChange = () => { const handleContentChange = (docId: number) => {
const documentId = documentStore.currentDocumentId; const instance = editorCache.get(docId);
if (!documentId) return;
const instance = editorCache.get(documentId);
if (!instance) return; if (!instance) return;
// 立即设置脏标记和修改时间(切换文档时需要判断) // 标记为脏数据
instance.isDirty = true; instance.isDirty = true;
instance.lastModified = new Date(); instance.lastModified = Date.now();
// 优使用防抖清理语法树缓 // 调度自动保
debouncedClearSyntaxCache.debouncedFn(instance); const autoSaveDelay = configStore.config.editing.autoSaveDelay;
documentStore.scheduleAutoSave(
docId,
async () => {
const content = instance.view.state.doc.toString();
const savedDoc = await documentStore.saveDocument(docId, content);
// 设置自动保存定时器(已经是防抖效果:每次重置定时器) // 同步版本信息
instance.autoSaveTimer.set(() => { if (savedDoc) {
saveEditorContent(documentId); instance.contentTimestamp = savedDoc.updated_at || '';
}, getAutoSaveDelay()); instance.contentLength = (savedDoc.content || '').length;
instance.isDirty = false;
}
},
autoSaveDelay
);
}; };
// 检查容器是否已设置 // 切换到指定编辑器
const hasContainer = computed(() => containerElement.value !== null); const switchToEditor = async (docId: number) => {
// 设置编辑器容器
const setEditorContainer = (container: HTMLElement | null) => {
containerElement.value = container;
// watch 会自动监听并加载编辑器,无需手动调用
};
// 加载编辑器
const loadEditor = async (documentId: number, content: string) => {
isLoading.value = true; isLoading.value = true;
try { try {
// 验证参数 // 直接从后端获取文档
if (!documentId) { const doc = await documentStore.getDocument(docId);
throw new Error('Invalid parameters for loadEditor'); if (!doc) {
throw new Error(`Failed to load document ${docId}`);
} }
// 保存当前编辑器内容 const cached = editorCache.get(docId);
if (currentEditor.value) {
const currentDocId = documentStore.currentDocumentId; if (cached) {
if (currentDocId && currentDocId !== documentId) { // 场景1缓存有效
await saveEditorContent(currentDocId); if (isCacheValid(cached, doc)) {
} showEditor(cached);
return;
} }
// 获取或创建编辑器 // 场景2有未保存修改
const view = await getOrCreateEditor(documentId, content); if (cached.isDirty) {
// 检查内容是否真的不同
if (!hasContentChanged(cached, doc)) {
// 内容实际相同,只是元数据变了,同步元数据
cached.contentTimestamp = doc.updated_at || '';
cached.contentLength = (doc.content || '').length;
cached.isDirty = false;
}
// 内容不同,保留用户编辑
showEditor(cached);
return;
}
// 更新内容 // 场景3缓存失效且无脏数据更新内容
const instance = editorCache.get(documentId); updateEditorContent(cached, doc);
if (instance && instance.content !== content) { showEditor(cached);
// 确保编辑器视图有效 } else {
if (view && view.state && view.dispatch) { // 场景4创建新编辑器
const contentLength = content.length; const editor = await createEditorInstance(docId, doc);
view.dispatch({
changes: { // 添加到缓存
from: 0, editorCache.set(docId, editor, (_evictedKey, evictedInstance) => {
to: view.state.doc.length, // 保存光标位置
insert: content const cursorPos = evictedInstance.view.state.selection.main.head;
}, editorStateStore.saveCursorPosition(evictedInstance.documentId, cursorPos);
selection: {anchor: contentLength, head: contentLength}
// 从扩展管理器移除
removeExtensionManagerView(evictedInstance.documentId);
// 移除 DOM
if (evictedInstance.view.dom.parentElement) {
evictedInstance.view.dom.remove();
}
// 销毁编辑器
evictedInstance.view.destroy();
}); });
instance.content = content;
instance.isDirty = false;
// 清理语法树缓存,因为内容已更新
instance.syntaxTreeCache = null;
// 修复:内容变了,清空光标位置,避免越界
instance.editorState = undefined;
delete documentStore.documentStates[documentId];
}
}
// 显示编辑器
showEditor(documentId);
showEditor(editor);
}
} catch (error) { } catch (error) {
console.error('Failed to load editor:', error); console.error('Failed to switch editor:', error);
} finally { } finally {
setTimeout(() => { setTimeout(() => {
isLoading.value = false; isLoading.value = false;
@@ -404,49 +332,100 @@ export const useEditorStore = defineStore('editor', () => {
} }
}; };
// 移除编辑器 // 获取当前内容
const removeEditor = async (documentId: number) => { const getCurrentContent = (): string => {
const instance = editorCache.get(documentId); if (!currentEditorId.value) return '';
if (instance) { const instance = editorCache.get(currentEditorId.value);
try { return instance ? instance.view.state.doc.toString() : '';
if (instance.isDirty) { };
await saveEditorContent(documentId);
// 获取当前光标位置
const getCurrentCursorPosition = (): number => {
if (!currentEditorId.value) return 0;
const instance = editorCache.get(currentEditorId.value);
return instance ? instance.view.state.selection.main.head : 0;
};
// 检查是否有未保存修改
const hasUnsavedChanges = (docId: number): boolean => {
const instance = editorCache.get(docId);
return instance?.isDirty || false;
};
// 同步保存后的版本信息
const syncAfterSave = async (docId: number) => {
const instance = editorCache.get(docId);
if (!instance) return;
const doc = await documentStore.getDocument(docId);
if (doc) {
instance.contentTimestamp = doc.updated_at || '';
instance.contentLength = (doc.content || '').length;
instance.isDirty = false;
} }
};
// 清除自动保存定时 // 销毁编辑
instance.autoSaveTimer.clear(); const destroyEditor = async (docId: number) => {
const instance = editorCache.get(docId);
if (!instance) return;
// 从扩展管理器中移除视图 try {
removeExtensionManagerView(documentId); // 保存光标位置
const cursorPos = instance.view.state.selection.main.head;
editorStateStore.saveCursorPosition(docId, cursorPos);
// 移除DOM元素 // 从扩展管理器移除
if (instance.view && instance.view.dom && instance.view.dom.parentElement) { removeExtensionManagerView(docId);
// 移除 DOM
if (instance.view.dom && instance.view.dom.parentElement) {
instance.view.dom.remove(); instance.view.dom.remove();
} }
// 销毁编辑器 // 销毁编辑器
if (instance.view && instance.view.destroy) {
instance.view.destroy(); instance.view.destroy();
}
// 清理引用 // 从缓存删除
if (currentEditor.value === instance.view) { editorCache.delete(docId);
currentEditor.value = null;
}
// 从缓存中删除 // 清空当前编辑器引用
editorCache.delete(documentId); if (currentEditorId.value === docId) {
currentEditorId.value = null;
}
} catch (error) { } catch (error) {
console.error('Error removing editor:', error); console.error('Error destroying editor:', error);
}
} }
}; };
// 更新文档统计 // 清空所有编辑器
const updateDocumentStats = (stats: DocumentStats) => { const destroyAllEditors = () => {
documentStats.value = stats; editorCache.clear((_documentId, instance) => {
// 保存光标位置
const cursorPos = instance.view.state.selection.main.head;
editorStateStore.saveCursorPosition(instance.documentId, cursorPos);
// 从扩展管理器移除
removeExtensionManagerView(instance.documentId);
// 移除 DOM
if (instance.view.dom.parentElement) {
instance.view.dom.remove();
}
// 销毁编辑器
instance.view.destroy();
});
currentEditorId.value = null;
}; };
// 设置编辑器容器
const setEditorContainer = (container: HTMLElement | null) => {
containerElement.value = container;
};
// 应用字体设置 // 应用字体设置
const applyFontSettings = () => { const applyFontSettings = () => {
editorCache.values().forEach(instance => { editorCache.values().forEach(instance => {
@@ -466,8 +445,7 @@ export const useEditorStore = defineStore('editor', () => {
}); });
}; };
// 应用 Tab 设置
// 应用Tab设置
const applyTabSettings = () => { const applyTabSettings = () => {
editorCache.values().forEach(instance => { editorCache.values().forEach(instance => {
updateTabConfig( updateTabConfig(
@@ -481,7 +459,6 @@ export const useEditorStore = defineStore('editor', () => {
// 应用快捷键设置 // 应用快捷键设置
const applyKeymapSettings = async () => { const applyKeymapSettings = async () => {
// 确保所有编辑器实例的快捷键都更新
await Promise.all( await Promise.all(
editorCache.values().map(instance => editorCache.values().map(instance =>
updateKeymapExtension(instance.view) updateKeymapExtension(instance.view)
@@ -489,74 +466,36 @@ export const useEditorStore = defineStore('editor', () => {
); );
}; };
// 清空所有编辑器 const hasContainer = computed(() => containerElement.value !== null);
const clearAllEditors = () => { const currentEditor = computed(() => {
editorCache.clear((_documentId, instance) => { if (!currentEditorId.value) return null;
// 清除自动保存定时器 const instance = editorCache.get(currentEditorId.value);
instance.autoSaveTimer.clear(); return instance ? instance.view : null;
// 从扩展管理器移除
removeExtensionManagerView(instance.documentId);
// 移除DOM元素
if (instance.view.dom.parentElement) {
instance.view.dom.remove();
}
// 销毁编辑器
instance.view.destroy();
}); });
currentEditor.value = null;
};
// 更新扩展
const updateExtension = async (id: number, enabled: boolean, config?: any) => {
// 更新启用状态
await ExtensionService.UpdateExtensionEnabled(id, enabled);
// 如果需要更新配置
if (config !== undefined) {
await ExtensionService.UpdateExtensionConfig(id, config);
}
// 重新加载扩展配置
await extensionStore.loadExtensions();
// 获取更新后的扩展名称
const extension = extensionStore.extensions.find(ext => ext.id === id);
if (!extension) return;
// 更新前端编辑器扩展 - 应用于所有实例
const manager = getExtensionManager();
if (manager) {
// 直接更新前端扩展至所有视图
manager.updateExtension(extension.name, enabled, config);
}
await useKeybindingStore().loadKeyBindings();
await applyKeymapSettings();
};
return { return {
// 状态 // 状态
currentEditorId: readonly(currentEditorId),
currentEditor, currentEditor,
documentStats, isLoading: readonly(isLoading),
isLoading,
hasContainer, hasContainer,
// 方法 // 编辑器管理
setEditorContainer, setEditorContainer,
loadEditor, switchToEditor,
removeEditor, destroyEditor,
clearAllEditors, destroyAllEditors,
onContentChange,
// 查询方法
getCurrentContent,
getCurrentCursorPosition,
hasUnsavedChanges,
syncAfterSave,
// 配置应用
applyFontSettings, applyFontSettings,
applyThemeSettings, applyThemeSettings,
applyTabSettings, applyTabSettings,
applyKeymapSettings, applyKeymapSettings,
// 扩展管理方法
updateExtension,
editorView: currentEditor,
}; };
}); });

View File

@@ -151,8 +151,9 @@ export const useTabStore = defineStore('tab', () => {
/** /**
* 验证并清理无效的标签页 * 验证并清理无效的标签页
*/ */
const validateTabs = () => { const validateTabs = async () => {
const validDocIds = Object.keys(documentStore.documents).map(Number); const docs = await documentStore.getDocumentList();
const validDocIds = docs.map(doc => doc.id).filter((id): id is number => id !== undefined);
// 找出无效的标签页(文档已被删除) // 找出无效的标签页(文档已被删除)
const invalidTabIds = tabOrder.value.filter(docId => !validDocIds.includes(docId)); const invalidTabIds = tabOrder.value.filter(docId => !validDocIds.includes(docId));
@@ -169,9 +170,9 @@ export const useTabStore = defineStore('tab', () => {
/** /**
* 初始化标签页(当前文档) * 初始化标签页(当前文档)
*/ */
const initTab = () => { const initTab = async () => {
// 先验证并清理无效的标签页 // 先验证并清理无效的标签页
validateTabs(); await validateTabs();
if (isTabsEnabled.value) { if (isTabsEnabled.value) {
const currentDoc = documentStore.currentDocument; const currentDoc = documentStore.currentDocument;

View File

@@ -32,9 +32,9 @@ onMounted(async () => {
editorStore.setEditorContainer(editorElement.value); editorStore.setEditorContainer(editorElement.value);
const currentDoc = documentStore.currentDocument; const currentDocId = documentStore.currentDocumentId;
if (currentDoc && currentDoc.id !== undefined) { if (currentDocId) {
await editorStore.loadEditor(currentDoc.id, currentDoc.content || ''); await editorStore.switchToEditor(currentDocId);
} }
await tabStore.initTab(); await tabStore.initTab();

View File

@@ -1,13 +1,13 @@
import {EditorView, ViewPlugin, ViewUpdate} from '@codemirror/view'; import {EditorView, ViewPlugin, ViewUpdate} from '@codemirror/view';
import type {Text} from '@codemirror/state'; import type {Text} from '@codemirror/state';
import {useEditorStore} from '@/stores/editorStore';
/** /**
* 内容变化监听扩展
* 通过回调函数解耦,不直接依赖 Store
*/ */
export function createContentChangePlugin() { export function createContentChangePlugin(onContentChange: () => void) {
return ViewPlugin.fromClass( return ViewPlugin.fromClass(
class ContentChangePlugin { class ContentChangePlugin {
private readonly editorStore = useEditorStore();
private lastDoc: Text; private lastDoc: Text;
private rafId: number | null = null; private rafId: number | null = null;
private pendingNotification = false; private pendingNotification = false;
@@ -40,7 +40,7 @@ export function createContentChangePlugin() {
this.rafId = requestAnimationFrame(() => { this.rafId = requestAnimationFrame(() => {
this.pendingNotification = false; this.pendingNotification = false;
this.rafId = null; this.rafId = null;
this.editorStore.onContentChange(); onContentChange(); // 调用注入的回调
}); });
} }
} }

View File

@@ -1,15 +1,15 @@
import {EditorView, ViewPlugin, ViewUpdate} from '@codemirror/view'; import {EditorView, ViewPlugin, ViewUpdate} from '@codemirror/view';
import {useDocumentStore} from '@/stores/documentStore'; import {useEditorStateStore} from '@/stores/editorStateStore';
import {createDebounce} from '@/common/utils/debounce'; import {createDebounce} from '@/common/utils/debounce';
/** /**
* 光标位置持久化扩展 * 光标位置持久化扩展
* 实时监听光标位置变化并持久化到 documentStore * 实时监听光标位置变化并持久化到 editorStateStore
*/ */
export function createCursorPositionExtension(documentId: number) { export function createCursorPositionExtension(documentId: number) {
return ViewPlugin.fromClass( return ViewPlugin.fromClass(
class CursorPositionPlugin { class CursorPositionPlugin {
private readonly documentStore = useDocumentStore(); private readonly editorStateStore = useEditorStateStore();
private readonly debouncedSave; private readonly debouncedSave;
constructor(private view: EditorView) { constructor(private view: EditorView) {
@@ -42,11 +42,7 @@ export function createCursorPositionExtension(documentId: number) {
private saveCursorPosition() { private saveCursorPosition() {
const cursorPos = this.view.state.selection.main.head; const cursorPos = this.view.state.selection.main.head;
if (!this.documentStore.documentStates[documentId]) { this.editorStateStore.saveCursorPosition(documentId, cursorPos);
this.documentStore.documentStates[documentId] = {cursorPos};
} else {
this.documentStore.documentStates[documentId].cursorPos = cursorPos;
}
} }
} }
); );

View File

@@ -1,6 +1,6 @@
import {Extension} from '@codemirror/state'; import {Extension} from '@codemirror/state';
import {EditorView} from '@codemirror/view'; import {EditorView} from '@codemirror/view';
import {DocumentStats} from '@/stores/editorStore'; import {DocumentStats} from '@/stores/editorStateStore';
import {getActiveNoteBlock} from '@/views/editor/extensions/codeblock/state'; import {getActiveNoteBlock} from '@/views/editor/extensions/codeblock/state';
// 更新编辑器文档统计信息 // 更新编辑器文档统计信息

View File

@@ -3,6 +3,7 @@ import {computed, onMounted, ref} from 'vue';
import {useI18n} from 'vue-i18n'; import {useI18n} from 'vue-i18n';
import {useEditorStore} from '@/stores/editorStore'; import {useEditorStore} from '@/stores/editorStore';
import {useExtensionStore} from '@/stores/extensionStore'; import {useExtensionStore} from '@/stores/extensionStore';
import {useKeybindingStore} from '@/stores/keybindingStore';
import {ExtensionService} from '@/../bindings/voidraft/internal/services'; import {ExtensionService} from '@/../bindings/voidraft/internal/services';
import { import {
getExtensionDefaultConfig, getExtensionDefaultConfig,
@@ -10,6 +11,7 @@ import {
getExtensionDisplayName, getExtensionsMap, getExtensionDisplayName, getExtensionsMap,
hasExtensionConfig hasExtensionConfig
} from '@/views/editor/manager/extensions'; } from '@/views/editor/manager/extensions';
import {getExtensionManager} from '@/views/editor/manager';
import SettingSection from '../components/SettingSection.vue'; import SettingSection from '../components/SettingSection.vue';
import SettingItem from '../components/SettingItem.vue'; import SettingItem from '../components/SettingItem.vue';
import ToggleSwitch from '../components/ToggleSwitch.vue'; import ToggleSwitch from '../components/ToggleSwitch.vue';
@@ -17,6 +19,7 @@ import ToggleSwitch from '../components/ToggleSwitch.vue';
const {t} = useI18n(); const {t} = useI18n();
const editorStore = useEditorStore(); const editorStore = useEditorStore();
const extensionStore = useExtensionStore(); const extensionStore = useExtensionStore();
const keybindingStore = useKeybindingStore();
// 页面初始化时加载扩展数据 // 页面初始化时加载扩展数据
onMounted(async () => { onMounted(async () => {
@@ -55,7 +58,25 @@ const toggleExpanded = (extensionId: number) => {
// 更新扩展状态 // 更新扩展状态
const updateExtension = async (extensionId: number, enabled: boolean) => { const updateExtension = async (extensionId: number, enabled: boolean) => {
try { try {
await editorStore.updateExtension(extensionId, enabled); // 更新后端
await ExtensionService.UpdateExtensionEnabled(extensionId, enabled);
// 重新加载各个 Store 的状态
await extensionStore.loadExtensions();
await keybindingStore.loadKeyBindings();
// 获取更新后的扩展
const extension = extensionStore.extensions.find(ext => ext.id === extensionId);
if (!extension) return;
// 应用到编辑器
const manager = getExtensionManager();
if (manager) {
manager.updateExtension(extension.name, enabled, extension.config);
}
// 更新快捷键
await editorStore.applyKeymapSettings();
} catch (error) { } catch (error) {
console.error('Failed to update extension:', error); console.error('Failed to update extension:', error);
} }
@@ -75,9 +96,18 @@ const updateExtensionConfig = async (extensionId: number, configKey: string, val
} else { } else {
updatedConfig[configKey] = value; updatedConfig[configKey] = value;
} }
// 使用editorStore的updateExtension方法更新确保应用到所有编辑器实例
await editorStore.updateExtension(extensionId, extension.enabled ?? false, updatedConfig);
// 更新后端配置
await ExtensionService.UpdateExtensionConfig(extensionId, updatedConfig);
// 重新加载状态
await extensionStore.loadExtensions();
// 应用到编辑器
const manager = getExtensionManager();
if (manager) {
manager.updateExtension(extension.name, extension.enabled ?? false, updatedConfig);
}
} catch (error) { } catch (error) {
console.error('Failed to update extension config:', error); console.error('Failed to update extension config:', error);
} }
@@ -89,14 +119,16 @@ const resetExtension = async (extensionId: number) => {
// 重置到默认配置 // 重置到默认配置
await ExtensionService.ResetExtensionConfig(extensionId); await ExtensionService.ResetExtensionConfig(extensionId);
// 重新加载扩展状态以获取最新配置 // 重新加载扩展状态
await extensionStore.loadExtensions(); await extensionStore.loadExtensions();
// 获取重置后的状态,立即应用到所有编辑器视图 // 获取重置后的状态,应用到编辑器
const extension = extensionStore.extensions.find(ext => ext.id === extensionId); const extension = extensionStore.extensions.find(ext => ext.id === extensionId);
if (extension) { if (extension) {
// 通过editorStore更新确保所有视图都能同步 const manager = getExtensionManager();
await editorStore.updateExtension(extensionId, extension.enabled ?? false, extension.config); if (manager) {
manager.updateExtension(extension.name, extension.enabled ?? false, extension.config);
}
} }
} catch (error) { } catch (error) {
console.error('Failed to reset extension:', error); console.error('Failed to reset extension:', error);