Use SQLite instead of JSON storage

This commit is contained in:
2025-06-29 23:41:34 +08:00
parent 6f8775472d
commit 70d88dabba
25 changed files with 807 additions and 636 deletions

View File

@@ -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;

View File

@@ -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 设置配置项
*/

View File

@@ -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);

View File

@@ -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 更新扩展启用状态
*/

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;

View File

@@ -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
};
});

View File

@@ -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
};

View File

@@ -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;
}
});
}