✨ Use SQLite instead of JSON storage
This commit is contained in:
@@ -159,27 +159,32 @@ export class ConfigMetadata {
|
||||
}
|
||||
|
||||
/**
|
||||
* Document 表示一个文档
|
||||
* Document 表示一个文档(使用自增主键)
|
||||
*/
|
||||
export class Document {
|
||||
/**
|
||||
* 元数据
|
||||
*/
|
||||
"meta": DocumentMeta;
|
||||
|
||||
/**
|
||||
* 文档内容
|
||||
*/
|
||||
"id": number;
|
||||
"title": string;
|
||||
"content": string;
|
||||
"createdAt": time$0.Time;
|
||||
"updatedAt": time$0.Time;
|
||||
|
||||
/** Creates a new Document instance. */
|
||||
constructor($$source: Partial<Document> = {}) {
|
||||
if (!("meta" in $$source)) {
|
||||
this["meta"] = (new DocumentMeta());
|
||||
if (!("id" in $$source)) {
|
||||
this["id"] = 0;
|
||||
}
|
||||
if (!("title" in $$source)) {
|
||||
this["title"] = "";
|
||||
}
|
||||
if (!("content" in $$source)) {
|
||||
this["content"] = "";
|
||||
}
|
||||
if (!("createdAt" in $$source)) {
|
||||
this["createdAt"] = null;
|
||||
}
|
||||
if (!("updatedAt" in $$source)) {
|
||||
this["updatedAt"] = null;
|
||||
}
|
||||
|
||||
Object.assign(this, $$source);
|
||||
}
|
||||
@@ -188,66 +193,11 @@ export class Document {
|
||||
* Creates a new Document instance from a string or object.
|
||||
*/
|
||||
static createFrom($$source: any = {}): Document {
|
||||
const $$createField0_0 = $$createType5;
|
||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||
if ("meta" in $$parsedSource) {
|
||||
$$parsedSource["meta"] = $$createField0_0($$parsedSource["meta"]);
|
||||
}
|
||||
return new Document($$parsedSource as Partial<Document>);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DocumentMeta 文档元数据
|
||||
*/
|
||||
export class DocumentMeta {
|
||||
/**
|
||||
* 文档唯一标识
|
||||
*/
|
||||
"id": string;
|
||||
|
||||
/**
|
||||
* 文档标题
|
||||
*/
|
||||
"title": string;
|
||||
|
||||
/**
|
||||
* 最后更新时间
|
||||
*/
|
||||
"lastUpdated": time$0.Time;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
"createdAt": time$0.Time;
|
||||
|
||||
/** Creates a new DocumentMeta instance. */
|
||||
constructor($$source: Partial<DocumentMeta> = {}) {
|
||||
if (!("id" in $$source)) {
|
||||
this["id"] = "";
|
||||
}
|
||||
if (!("title" in $$source)) {
|
||||
this["title"] = "";
|
||||
}
|
||||
if (!("lastUpdated" in $$source)) {
|
||||
this["lastUpdated"] = null;
|
||||
}
|
||||
if (!("createdAt" in $$source)) {
|
||||
this["createdAt"] = null;
|
||||
}
|
||||
|
||||
Object.assign(this, $$source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new DocumentMeta instance from a string or object.
|
||||
*/
|
||||
static createFrom($$source: any = {}): DocumentMeta {
|
||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||
return new DocumentMeta($$parsedSource as Partial<DocumentMeta>);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* EditingConfig 编辑设置配置
|
||||
*/
|
||||
@@ -380,7 +330,7 @@ export class Extension {
|
||||
* Creates a new Extension instance from a string or object.
|
||||
*/
|
||||
static createFrom($$source: any = {}): Extension {
|
||||
const $$createField3_0 = $$createType6;
|
||||
const $$createField3_0 = $$createType5;
|
||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||
if ("config" in $$parsedSource) {
|
||||
$$parsedSource["config"] = $$createField3_0($$parsedSource["config"]);
|
||||
@@ -508,7 +458,7 @@ export class GeneralConfig {
|
||||
* Creates a new GeneralConfig instance from a string or object.
|
||||
*/
|
||||
static createFrom($$source: any = {}): GeneralConfig {
|
||||
const $$createField5_0 = $$createType8;
|
||||
const $$createField5_0 = $$createType7;
|
||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||
if ("globalHotkey" in $$parsedSource) {
|
||||
$$parsedSource["globalHotkey"] = $$createField5_0($$parsedSource["globalHotkey"]);
|
||||
@@ -956,8 +906,8 @@ export class KeyBindingConfig {
|
||||
* Creates a new KeyBindingConfig instance from a string or object.
|
||||
*/
|
||||
static createFrom($$source: any = {}): KeyBindingConfig {
|
||||
const $$createField0_0 = $$createType10;
|
||||
const $$createField1_0 = $$createType11;
|
||||
const $$createField0_0 = $$createType9;
|
||||
const $$createField1_0 = $$createType10;
|
||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||
if ("keyBindings" in $$parsedSource) {
|
||||
$$parsedSource["keyBindings"] = $$createField0_0($$parsedSource["keyBindings"]);
|
||||
@@ -1110,15 +1060,14 @@ const $$createType1 = EditingConfig.createFrom;
|
||||
const $$createType2 = AppearanceConfig.createFrom;
|
||||
const $$createType3 = UpdatesConfig.createFrom;
|
||||
const $$createType4 = ConfigMetadata.createFrom;
|
||||
const $$createType5 = DocumentMeta.createFrom;
|
||||
var $$createType6 = (function $$initCreateType6(...args): any {
|
||||
if ($$createType6 === $$initCreateType6) {
|
||||
$$createType6 = $$createType7;
|
||||
var $$createType5 = (function $$initCreateType5(...args): any {
|
||||
if ($$createType5 === $$initCreateType5) {
|
||||
$$createType5 = $$createType6;
|
||||
}
|
||||
return $$createType6(...args);
|
||||
return $$createType5(...args);
|
||||
});
|
||||
const $$createType7 = $Create.Map($Create.Any, $Create.Any);
|
||||
const $$createType8 = HotkeyCombo.createFrom;
|
||||
const $$createType9 = KeyBinding.createFrom;
|
||||
const $$createType10 = $Create.Array($$createType9);
|
||||
const $$createType11 = KeyBindingMetadata.createFrom;
|
||||
const $$createType6 = $Create.Map($Create.Any, $Create.Any);
|
||||
const $$createType7 = HotkeyCombo.createFrom;
|
||||
const $$createType8 = KeyBinding.createFrom;
|
||||
const $$createType9 = $Create.Array($$createType8);
|
||||
const $$createType10 = KeyBindingMetadata.createFrom;
|
||||
|
@@ -42,14 +42,6 @@ export function ResetConfig(): Promise<void> & { cancel(): void } {
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* ServiceShutdown 关闭服务
|
||||
*/
|
||||
export function ServiceShutdown(): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(3963562361) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set 设置配置项
|
||||
*/
|
||||
|
@@ -2,7 +2,7 @@
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
/**
|
||||
* DocumentService 提供文档管理功能
|
||||
* DocumentService provides document management functionality
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -15,18 +15,10 @@ import {Call as $Call, Create as $Create} from "@wailsio/runtime";
|
||||
import * as models$0 from "../models/models.js";
|
||||
|
||||
/**
|
||||
* ForceSave 强制保存
|
||||
* CreateDocument creates a new document and returns the created document with ID
|
||||
*/
|
||||
export function ForceSave(): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(2767091023) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* GetActiveDocument 获取活动文档
|
||||
*/
|
||||
export function GetActiveDocument(): Promise<models$0.Document | null> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(1785823398) as any;
|
||||
export function CreateDocument(title: string): Promise<models$0.Document | null> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(3360680842, title) as any;
|
||||
let $typingPromise = $resultPromise.then(($result: any) => {
|
||||
return $$createType1($result);
|
||||
}) as any;
|
||||
@@ -35,45 +27,70 @@ export function GetActiveDocument(): Promise<models$0.Document | null> & { cance
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize 初始化服务
|
||||
* DeleteDocument deletes a document (not allowed if it's the only document)
|
||||
*/
|
||||
export function Initialize(): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(3418008221) as any;
|
||||
export function DeleteDocument(id: number): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(412287269, id) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* OnDataPathChanged 处理数据路径变更
|
||||
* GetDocumentByID gets a document by ID
|
||||
*/
|
||||
export function OnDataPathChanged(oldPath: string, newPath: string): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(269349439, oldPath, newPath) as any;
|
||||
export function GetDocumentByID(id: number): Promise<models$0.Document | null> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(3468193232, id) as any;
|
||||
let $typingPromise = $resultPromise.then(($result: any) => {
|
||||
return $$createType1($result);
|
||||
}) as any;
|
||||
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
|
||||
return $typingPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* GetFirstDocumentID gets the first document's ID for frontend initialization
|
||||
*/
|
||||
export function GetFirstDocumentID(): Promise<number> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(2970773833) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* ReloadDocument 重新加载文档
|
||||
* ListAllDocumentsMeta lists all document metadata
|
||||
*/
|
||||
export function ReloadDocument(): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(3093415283) as any;
|
||||
export function ListAllDocumentsMeta(): Promise<(models$0.Document | null)[]> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(3073950297) as any;
|
||||
let $typingPromise = $resultPromise.then(($result: any) => {
|
||||
return $$createType2($result);
|
||||
}) as any;
|
||||
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
|
||||
return $typingPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* OnDataPathChanged handles data path changes
|
||||
*/
|
||||
export function OnDataPathChanged(): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(269349439) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* ServiceShutdown 关闭服务
|
||||
* UpdateDocumentContent updates the content of a document
|
||||
*/
|
||||
export function ServiceShutdown(): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(638578044) as any;
|
||||
export function UpdateDocumentContent(id: number, content: string): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(3251897116, id, content) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* UpdateActiveDocumentContent 更新文档内容
|
||||
* UpdateDocumentTitle updates the title of a document
|
||||
*/
|
||||
export function UpdateActiveDocumentContent(content: string): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(1486276638, content) as any;
|
||||
export function UpdateDocumentTitle(id: number, title: string): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(2045530459, id, title) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
// Private type creation functions
|
||||
const $$createType0 = models$0.Document.createFrom;
|
||||
const $$createType1 = $Create.Nullable($$createType0);
|
||||
const $$createType2 = $Create.Array($$createType1);
|
||||
|
@@ -42,14 +42,6 @@ export function ResetExtensionToDefault(id: models$0.ExtensionID): Promise<void>
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* ServiceShutdown 关闭服务
|
||||
*/
|
||||
export function ServiceShutdown(): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(4127635746) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* UpdateExtensionEnabled 更新扩展启用状态
|
||||
*/
|
||||
|
@@ -54,7 +54,7 @@ export function RegisterHotkey(hotkey: models$0.HotkeyCombo | null): Promise<voi
|
||||
}
|
||||
|
||||
/**
|
||||
* ServiceShutdown 关闭服务
|
||||
* OnShutdown 关闭服务
|
||||
*/
|
||||
export function ServiceShutdown(): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(157291181) as any;
|
||||
|
@@ -38,14 +38,6 @@ export function GetKeyBindingConfig(): Promise<models$0.KeyBindingConfig | null>
|
||||
return $typingPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* ServiceShutdown 关闭服务
|
||||
*/
|
||||
export function ServiceShutdown(): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(1610182855) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
// Private type creation functions
|
||||
const $$createType0 = models$0.KeyBinding.createFrom;
|
||||
const $$createType1 = $Create.Array($$createType0);
|
||||
|
@@ -42,13 +42,5 @@ export function MigrateDirectory(srcPath: string, dstPath: string): Promise<void
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* ServiceShutdown 服务关闭
|
||||
*/
|
||||
export function ServiceShutdown(): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(3472042605) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
// Private type creation functions
|
||||
const $$createType0 = $models.MigrationProgress.createFrom;
|
||||
|
@@ -4,103 +4,113 @@ import {DocumentService} from '@/../bindings/voidraft/internal/services';
|
||||
import {Document} from '@/../bindings/voidraft/internal/models/models';
|
||||
|
||||
export const useDocumentStore = defineStore('document', () => {
|
||||
// 状态
|
||||
const activeDocument = ref<Document | null>(null);
|
||||
const isLoading = ref(false);
|
||||
const isSaving = ref(false);
|
||||
const lastSaved = ref<Date | null>(null);
|
||||
|
||||
// 计算属性
|
||||
const documentContent = computed(() => activeDocument.value?.content ?? '');
|
||||
const documentTitle = computed(() => activeDocument.value?.meta?.title ?? '');
|
||||
const hasActiveDocument = computed(() => !!activeDocument.value);
|
||||
const isSaveInProgress = computed(() => isSaving.value);
|
||||
const lastSavedTime = computed(() => lastSaved.value);
|
||||
|
||||
// 加载文档
|
||||
const loadDocument = async (): Promise<Document | null> => {
|
||||
if (isLoading.value) return null;
|
||||
|
||||
isLoading.value = true;
|
||||
try {
|
||||
const doc = await DocumentService.GetActiveDocument();
|
||||
activeDocument.value = doc;
|
||||
return doc;
|
||||
} catch (error) {
|
||||
return null;
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 保存文档
|
||||
const saveDocument = async (content: string): Promise<boolean> => {
|
||||
if (isSaving.value) return false;
|
||||
|
||||
isSaving.value = true;
|
||||
try {
|
||||
await DocumentService.UpdateActiveDocumentContent(content);
|
||||
lastSaved.value = new Date();
|
||||
|
||||
// 更新本地副本
|
||||
if (activeDocument.value) {
|
||||
activeDocument.value.content = content;
|
||||
activeDocument.value.meta.lastUpdated = lastSaved.value;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
} finally {
|
||||
isSaving.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 强制保存文档到磁盘
|
||||
const forceSaveDocument = async (): Promise<boolean> => {
|
||||
if (isSaving.value) return false;
|
||||
|
||||
isSaving.value = true;
|
||||
try {
|
||||
await DocumentService.ForceSave();
|
||||
lastSaved.value = new Date();
|
||||
|
||||
// 更新时间戳
|
||||
if (activeDocument.value) {
|
||||
activeDocument.value.meta.lastUpdated = lastSaved.value;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
} finally {
|
||||
isSaving.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化
|
||||
const initialize = async (): Promise<void> => {
|
||||
await loadDocument();
|
||||
};
|
||||
|
||||
return {
|
||||
// 状态
|
||||
activeDocument,
|
||||
isLoading,
|
||||
isSaving,
|
||||
lastSaved,
|
||||
|
||||
const currentDocument = ref<Document | null>(null);
|
||||
const isLoading = ref(false);
|
||||
const isSaving = ref(false);
|
||||
const lastSaved = ref<Date | null>(null);
|
||||
|
||||
// 计算属性
|
||||
documentContent,
|
||||
documentTitle,
|
||||
hasActiveDocument,
|
||||
isSaveInProgress,
|
||||
lastSavedTime,
|
||||
|
||||
// 方法
|
||||
loadDocument,
|
||||
saveDocument,
|
||||
forceSaveDocument,
|
||||
initialize
|
||||
};
|
||||
const documentContent = computed(() => currentDocument.value?.content ?? '');
|
||||
const documentTitle = computed(() => currentDocument.value?.title ?? '');
|
||||
const hasDocument = computed(() => !!currentDocument.value);
|
||||
const isSaveInProgress = computed(() => isSaving.value);
|
||||
const lastSavedTime = computed(() => lastSaved.value);
|
||||
|
||||
// 加载文档
|
||||
const loadDocument = async (documentId = 1): Promise<Document | null> => {
|
||||
if (isLoading.value) return currentDocument.value;
|
||||
|
||||
isLoading.value = true;
|
||||
try {
|
||||
const doc = await DocumentService.GetDocumentByID(documentId);
|
||||
if (doc) {
|
||||
currentDocument.value = doc;
|
||||
return doc;
|
||||
}
|
||||
return null;
|
||||
} catch (error) {
|
||||
return null;
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 保存文档内容
|
||||
const saveDocumentContent = async (content: string): Promise<boolean> => {
|
||||
// 如果内容没有变化,直接返回成功
|
||||
if (currentDocument.value?.content === content) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 如果正在保存中,直接返回
|
||||
if (isSaving.value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
isSaving.value = true;
|
||||
try {
|
||||
const documentId = currentDocument.value?.id || 1;
|
||||
await DocumentService.UpdateDocumentContent(documentId, content);
|
||||
|
||||
const now = new Date();
|
||||
lastSaved.value = now;
|
||||
|
||||
// 更新本地副本
|
||||
if (currentDocument.value) {
|
||||
currentDocument.value.content = content;
|
||||
currentDocument.value.updatedAt = now;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
} finally {
|
||||
isSaving.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 保存文档标题
|
||||
const saveDocumentTitle = async (title: string): Promise<boolean> => {
|
||||
if (!currentDocument.value || currentDocument.value.title === title) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
await DocumentService.UpdateDocumentTitle(currentDocument.value.id, title);
|
||||
const now = new Date();
|
||||
lastSaved.value = now;
|
||||
currentDocument.value.title = title;
|
||||
currentDocument.value.updatedAt = now;
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化
|
||||
const initialize = async (): Promise<void> => {
|
||||
await loadDocument();
|
||||
};
|
||||
|
||||
return {
|
||||
// 状态
|
||||
currentDocument,
|
||||
isLoading,
|
||||
isSaving,
|
||||
lastSaved,
|
||||
|
||||
// 计算属性
|
||||
documentContent,
|
||||
documentTitle,
|
||||
hasDocument,
|
||||
isSaveInProgress,
|
||||
lastSavedTime,
|
||||
|
||||
// 方法
|
||||
loadDocument,
|
||||
saveDocumentContent,
|
||||
saveDocumentTitle,
|
||||
initialize
|
||||
};
|
||||
});
|
@@ -5,16 +5,15 @@ import {EditorState, Extension} from '@codemirror/state';
|
||||
import {useConfigStore} from './configStore';
|
||||
import {useDocumentStore} from './documentStore';
|
||||
import {useThemeStore} from './themeStore';
|
||||
import {useKeybindingStore} from './keybindingStore';
|
||||
import {SystemThemeType} from '@/../bindings/voidraft/internal/models/models';
|
||||
import {DocumentService, ExtensionService} from '@/../bindings/voidraft/internal/services';
|
||||
import {ExtensionService} from '@/../bindings/voidraft/internal/services';
|
||||
import {ensureSyntaxTree} from "@codemirror/language"
|
||||
import {createBasicSetup} from '@/views/editor/basic/basicSetup';
|
||||
import {createThemeExtension, updateEditorTheme} from '@/views/editor/basic/themeExtension';
|
||||
import {getTabExtensions, updateTabConfig} from '@/views/editor/basic/tabExtension';
|
||||
import {createFontExtensionFromBackend, updateFontConfig} from '@/views/editor/basic/fontExtension';
|
||||
import {createStatsUpdateExtension} from '@/views/editor/basic/statsExtension';
|
||||
import {createAutoSavePlugin, createSaveShortcutPlugin} from '@/views/editor/basic/autoSaveExtension';
|
||||
import {createAutoSavePlugin} from '@/views/editor/basic/autoSaveExtension';
|
||||
import {createDynamicKeymapExtension, updateKeymapExtension} from '@/views/editor/keymap';
|
||||
import {createDynamicExtensions, getExtensionManager, setExtensionManagerView} from '@/views/editor/manager';
|
||||
import {useExtensionStore} from './extensionStore';
|
||||
@@ -128,21 +127,10 @@ export const useEditorStore = defineStore('editor', () => {
|
||||
updateDocumentStats
|
||||
);
|
||||
|
||||
// 创建保存快捷键插件
|
||||
const saveShortcutExtension = createSaveShortcutPlugin(() => {
|
||||
if (editorView.value) {
|
||||
handleManualSave();
|
||||
}
|
||||
});
|
||||
|
||||
// 创建自动保存插件
|
||||
const autoSaveExtension = createAutoSavePlugin({
|
||||
debounceDelay: 300, // 300毫秒的输入防抖
|
||||
onSave: (success) => {
|
||||
if (success) {
|
||||
documentStore.lastSaved = new Date();
|
||||
}
|
||||
}
|
||||
debounceDelay: configStore.config.editing.autoSaveDelay
|
||||
});
|
||||
// 代码块功能
|
||||
const codeBlockExtension = createCodeBlockExtension({
|
||||
@@ -164,7 +152,6 @@ export const useEditorStore = defineStore('editor', () => {
|
||||
...tabExtensions,
|
||||
fontExtension,
|
||||
statsExtension,
|
||||
saveShortcutExtension,
|
||||
autoSaveExtension,
|
||||
codeBlockExtension,
|
||||
...dynamicExtensions
|
||||
@@ -220,19 +207,6 @@ export const useEditorStore = defineStore('editor', () => {
|
||||
});
|
||||
};
|
||||
|
||||
// 手动保存文档
|
||||
const handleManualSave = async () => {
|
||||
if (!editorView.value) return;
|
||||
|
||||
const view = editorView.value as EditorView;
|
||||
const content = view.state.doc.toString();
|
||||
|
||||
// 先更新内容
|
||||
await DocumentService.UpdateActiveDocumentContent(content);
|
||||
// 然后调用强制保存方法
|
||||
await documentStore.forceSaveDocument();
|
||||
};
|
||||
|
||||
// 销毁编辑器
|
||||
const destroyEditor = () => {
|
||||
if (editorView.value) {
|
||||
@@ -283,7 +257,7 @@ export const useEditorStore = defineStore('editor', () => {
|
||||
// 如果需要更新配置
|
||||
await ExtensionService.UpdateExtensionState(id, enabled, config)
|
||||
}
|
||||
|
||||
|
||||
// 更新前端编辑器扩展
|
||||
const manager = getExtensionManager()
|
||||
if (manager) {
|
||||
@@ -292,7 +266,7 @@ export const useEditorStore = defineStore('editor', () => {
|
||||
|
||||
// 重新加载扩展配置
|
||||
await extensionStore.loadExtensions()
|
||||
|
||||
|
||||
// 更新快捷键映射
|
||||
if (editorView.value) {
|
||||
updateKeymapExtension(editorView.value)
|
||||
@@ -321,7 +295,6 @@ export const useEditorStore = defineStore('editor', () => {
|
||||
createEditor,
|
||||
reconfigureTabSettings,
|
||||
reconfigureFontSettings,
|
||||
handleManualSave,
|
||||
destroyEditor,
|
||||
updateExtension
|
||||
};
|
||||
|
@@ -1,98 +1,109 @@
|
||||
import { EditorView, ViewPlugin, ViewUpdate } from '@codemirror/view';
|
||||
import { DocumentService } from '@/../bindings/voidraft/internal/services';
|
||||
import { useDebounceFn } from '@vueuse/core';
|
||||
import { useDocumentStore } from '@/stores/documentStore';
|
||||
|
||||
// 定义自动保存配置选项
|
||||
// 自动保存配置选项
|
||||
export interface AutoSaveOptions {
|
||||
// 保存回调
|
||||
onSave?: (success: boolean) => void;
|
||||
// 内容变更延迟传递(毫秒)- 输入时不会立即发送,有一个小延迟,避免频繁调用后端
|
||||
// 防抖延迟(毫秒)
|
||||
debounceDelay?: number;
|
||||
// 保存状态回调
|
||||
onSaveStart?: () => void;
|
||||
onSaveSuccess?: () => void;
|
||||
onSaveError?: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 简单防抖函数
|
||||
*/
|
||||
function debounce<T extends (...args: any[]) => any>(
|
||||
func: T,
|
||||
delay: number
|
||||
): T & { cancel: () => void } {
|
||||
let timeoutId: number | null = null;
|
||||
|
||||
const debounced = ((...args: Parameters<T>) => {
|
||||
if (timeoutId !== null) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
|
||||
timeoutId = window.setTimeout(() => {
|
||||
timeoutId = null;
|
||||
func(...args);
|
||||
}, delay);
|
||||
}) as T & { cancel: () => void };
|
||||
|
||||
debounced.cancel = () => {
|
||||
if (timeoutId !== null) {
|
||||
clearTimeout(timeoutId);
|
||||
timeoutId = null;
|
||||
}
|
||||
};
|
||||
|
||||
return debounced;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建自动保存插件
|
||||
*
|
||||
* @param options 配置选项
|
||||
* @returns EditorView.Plugin
|
||||
*/
|
||||
export function createAutoSavePlugin(options: AutoSaveOptions = {}) {
|
||||
const {
|
||||
onSave = () => {},
|
||||
debounceDelay = 2000
|
||||
const {
|
||||
debounceDelay = 2000,
|
||||
onSaveStart = () => {},
|
||||
onSaveSuccess = () => {},
|
||||
onSaveError = () => {}
|
||||
} = options;
|
||||
|
||||
return ViewPlugin.fromClass(
|
||||
class {
|
||||
private isActive: boolean = true;
|
||||
private isSaving: boolean = false;
|
||||
private readonly contentUpdateFn: (view: EditorView) => void;
|
||||
class AutoSavePlugin {
|
||||
private documentStore = useDocumentStore();
|
||||
private debouncedSave: ((content: string) => void) & { cancel: () => void };
|
||||
private isDestroyed = false;
|
||||
private lastContent = '';
|
||||
|
||||
constructor(private view: EditorView) {
|
||||
// 创建内容更新函数,简单传递内容给后端
|
||||
this.contentUpdateFn = this.createDebouncedUpdateFn(debounceDelay);
|
||||
this.lastContent = view.state.doc.toString();
|
||||
this.debouncedSave = debounce(
|
||||
(content: string) => this.performSave(content),
|
||||
debounceDelay
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建防抖的内容更新函数
|
||||
*/
|
||||
private createDebouncedUpdateFn(delay: number): (view: EditorView) => void {
|
||||
// 使用VueUse的防抖函数创建一个新函数
|
||||
return useDebounceFn(async (view: EditorView) => {
|
||||
// 如果插件已不活跃或正在保存中,不发送
|
||||
if (!this.isActive || this.isSaving) return;
|
||||
private async performSave(content: string): Promise<void> {
|
||||
if (this.isDestroyed) return;
|
||||
|
||||
try {
|
||||
onSaveStart();
|
||||
const success = await this.documentStore.saveDocumentContent(content);
|
||||
|
||||
this.isSaving = true;
|
||||
const content = view.state.doc.toString();
|
||||
|
||||
try {
|
||||
// 简单将内容传递给后端,让后端处理保存策略
|
||||
await DocumentService.UpdateActiveDocumentContent(content);
|
||||
onSave(true);
|
||||
} catch (err) {
|
||||
// 静默处理错误,不在控制台打印
|
||||
onSave(false);
|
||||
} finally {
|
||||
this.isSaving = false;
|
||||
if (success) {
|
||||
this.lastContent = content;
|
||||
onSaveSuccess();
|
||||
} else {
|
||||
onSaveError();
|
||||
}
|
||||
}, delay);
|
||||
} catch (error) {
|
||||
onSaveError();
|
||||
}
|
||||
}
|
||||
|
||||
update(update: ViewUpdate) {
|
||||
// 如果内容没有变化,直接返回
|
||||
if (!update.docChanged) return;
|
||||
if (!update.docChanged || this.isDestroyed) return;
|
||||
|
||||
// 调用防抖函数
|
||||
this.contentUpdateFn(this.view);
|
||||
const newContent = this.view.state.doc.toString();
|
||||
if (newContent === this.lastContent) return;
|
||||
|
||||
this.debouncedSave(newContent);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
// 标记插件不再活跃
|
||||
this.isActive = false;
|
||||
this.isDestroyed = true;
|
||||
this.debouncedSave.cancel();
|
||||
|
||||
// 静默发送最终内容,忽略错误
|
||||
const content = this.view.state.doc.toString();
|
||||
DocumentService.UpdateActiveDocumentContent(content).then();
|
||||
// 如果内容有变化,立即保存
|
||||
const currentContent = this.view.state.doc.toString();
|
||||
if (currentContent !== this.lastContent) {
|
||||
this.documentStore.saveDocumentContent(currentContent).catch(() => {});
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建处理保存快捷键的插件
|
||||
* @param onSave 保存回调
|
||||
* @returns EditorView.Plugin
|
||||
*/
|
||||
export function createSaveShortcutPlugin(onSave: () => void) {
|
||||
return EditorView.domEventHandlers({
|
||||
keydown: (event) => {
|
||||
// Ctrl+S / Cmd+S
|
||||
if ((event.ctrlKey || event.metaKey) && event.key === 's') {
|
||||
event.preventDefault();
|
||||
onSave();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
Reference in New Issue
Block a user