✨ Added configuration change notification service
This commit is contained in:
@@ -0,0 +1,4 @@
|
|||||||
|
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
|
||||||
|
export * from "./models.js";
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore: Unused imports
|
||||||
|
import {Create as $Create} from "@wailsio/runtime";
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore: Unused imports
|
||||||
|
import * as slog$0 from "../../../../../../log/slog/models.js";
|
||||||
|
|
||||||
|
export class App {
|
||||||
|
/**
|
||||||
|
* The main application menu
|
||||||
|
*/
|
||||||
|
"ApplicationMenu": Menu | null;
|
||||||
|
"Logger": slog$0.Logger | null;
|
||||||
|
|
||||||
|
/** Creates a new App instance. */
|
||||||
|
constructor($$source: Partial<App> = {}) {
|
||||||
|
if (!("ApplicationMenu" in $$source)) {
|
||||||
|
this["ApplicationMenu"] = null;
|
||||||
|
}
|
||||||
|
if (!("Logger" in $$source)) {
|
||||||
|
this["Logger"] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(this, $$source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new App instance from a string or object.
|
||||||
|
*/
|
||||||
|
static createFrom($$source: any = {}): App {
|
||||||
|
const $$createField0_0 = $$createType1;
|
||||||
|
const $$createField1_0 = $$createType3;
|
||||||
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
|
if ("ApplicationMenu" in $$parsedSource) {
|
||||||
|
$$parsedSource["ApplicationMenu"] = $$createField0_0($$parsedSource["ApplicationMenu"]);
|
||||||
|
}
|
||||||
|
if ("Logger" in $$parsedSource) {
|
||||||
|
$$parsedSource["Logger"] = $$createField1_0($$parsedSource["Logger"]);
|
||||||
|
}
|
||||||
|
return new App($$parsedSource as Partial<App>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Menu {
|
||||||
|
|
||||||
|
/** Creates a new Menu instance. */
|
||||||
|
constructor($$source: Partial<Menu> = {}) {
|
||||||
|
|
||||||
|
Object.assign(this, $$source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new Menu instance from a string or object.
|
||||||
|
*/
|
||||||
|
static createFrom($$source: any = {}): Menu {
|
||||||
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
|
return new Menu($$parsedSource as Partial<Menu>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private type creation functions
|
||||||
|
const $$createType0 = Menu.createFrom;
|
||||||
|
const $$createType1 = $Create.Nullable($$createType0);
|
||||||
|
const $$createType2 = slog$0.Logger.createFrom;
|
||||||
|
const $$createType3 = $Create.Nullable($$createType2);
|
||||||
4
frontend/bindings/log/slog/index.ts
Normal file
4
frontend/bindings/log/slog/index.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
|
||||||
|
export * from "./models.js";
|
||||||
31
frontend/bindings/log/slog/models.ts
Normal file
31
frontend/bindings/log/slog/models.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore: Unused imports
|
||||||
|
import {Create as $Create} from "@wailsio/runtime";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Logger records structured information about each call to its
|
||||||
|
* Log, Debug, Info, Warn, and Error methods.
|
||||||
|
* For each call, it creates a [Record] and passes it to a [Handler].
|
||||||
|
*
|
||||||
|
* To create a new Logger, call [New] or a Logger method
|
||||||
|
* that begins "With".
|
||||||
|
*/
|
||||||
|
export class Logger {
|
||||||
|
|
||||||
|
/** Creates a new Logger instance. */
|
||||||
|
constructor($$source: Partial<Logger> = {}) {
|
||||||
|
|
||||||
|
Object.assign(this, $$source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new Logger instance from a string or object.
|
||||||
|
*/
|
||||||
|
static createFrom($$source: any = {}): Logger {
|
||||||
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
|
return new Logger($$parsedSource as Partial<Logger>);
|
||||||
|
}
|
||||||
|
}
|
||||||
577
frontend/bindings/voidraft/internal/models/models.ts
Normal file
577
frontend/bindings/voidraft/internal/models/models.ts
Normal file
@@ -0,0 +1,577 @@
|
|||||||
|
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore: Unused imports
|
||||||
|
import {Create as $Create} from "@wailsio/runtime";
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore: Unused imports
|
||||||
|
import * as time$0 from "../../../time/models.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AppConfig 应用配置 - 按照前端设置页面分类组织
|
||||||
|
*/
|
||||||
|
export class AppConfig {
|
||||||
|
/**
|
||||||
|
* 通用设置
|
||||||
|
*/
|
||||||
|
"general": GeneralConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编辑设置
|
||||||
|
*/
|
||||||
|
"editing": EditingConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 外观设置
|
||||||
|
*/
|
||||||
|
"appearance": AppearanceConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 快捷键设置
|
||||||
|
*/
|
||||||
|
"keyBindings": KeyBindingsConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新设置
|
||||||
|
*/
|
||||||
|
"updates": UpdatesConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置元数据
|
||||||
|
*/
|
||||||
|
"metadata": ConfigMetadata;
|
||||||
|
|
||||||
|
/** Creates a new AppConfig instance. */
|
||||||
|
constructor($$source: Partial<AppConfig> = {}) {
|
||||||
|
if (!("general" in $$source)) {
|
||||||
|
this["general"] = (new GeneralConfig());
|
||||||
|
}
|
||||||
|
if (!("editing" in $$source)) {
|
||||||
|
this["editing"] = (new EditingConfig());
|
||||||
|
}
|
||||||
|
if (!("appearance" in $$source)) {
|
||||||
|
this["appearance"] = (new AppearanceConfig());
|
||||||
|
}
|
||||||
|
if (!("keyBindings" in $$source)) {
|
||||||
|
this["keyBindings"] = (new KeyBindingsConfig());
|
||||||
|
}
|
||||||
|
if (!("updates" in $$source)) {
|
||||||
|
this["updates"] = (new UpdatesConfig());
|
||||||
|
}
|
||||||
|
if (!("metadata" in $$source)) {
|
||||||
|
this["metadata"] = (new ConfigMetadata());
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(this, $$source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new AppConfig instance from a string or object.
|
||||||
|
*/
|
||||||
|
static createFrom($$source: any = {}): AppConfig {
|
||||||
|
const $$createField0_0 = $$createType0;
|
||||||
|
const $$createField1_0 = $$createType1;
|
||||||
|
const $$createField2_0 = $$createType2;
|
||||||
|
const $$createField3_0 = $$createType3;
|
||||||
|
const $$createField4_0 = $$createType4;
|
||||||
|
const $$createField5_0 = $$createType5;
|
||||||
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
|
if ("general" in $$parsedSource) {
|
||||||
|
$$parsedSource["general"] = $$createField0_0($$parsedSource["general"]);
|
||||||
|
}
|
||||||
|
if ("editing" in $$parsedSource) {
|
||||||
|
$$parsedSource["editing"] = $$createField1_0($$parsedSource["editing"]);
|
||||||
|
}
|
||||||
|
if ("appearance" in $$parsedSource) {
|
||||||
|
$$parsedSource["appearance"] = $$createField2_0($$parsedSource["appearance"]);
|
||||||
|
}
|
||||||
|
if ("keyBindings" in $$parsedSource) {
|
||||||
|
$$parsedSource["keyBindings"] = $$createField3_0($$parsedSource["keyBindings"]);
|
||||||
|
}
|
||||||
|
if ("updates" in $$parsedSource) {
|
||||||
|
$$parsedSource["updates"] = $$createField4_0($$parsedSource["updates"]);
|
||||||
|
}
|
||||||
|
if ("metadata" in $$parsedSource) {
|
||||||
|
$$parsedSource["metadata"] = $$createField5_0($$parsedSource["metadata"]);
|
||||||
|
}
|
||||||
|
return new AppConfig($$parsedSource as Partial<AppConfig>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AppearanceConfig 外观设置配置
|
||||||
|
*/
|
||||||
|
export class AppearanceConfig {
|
||||||
|
/**
|
||||||
|
* 界面语言
|
||||||
|
*/
|
||||||
|
"language": LanguageType;
|
||||||
|
|
||||||
|
/** Creates a new AppearanceConfig instance. */
|
||||||
|
constructor($$source: Partial<AppearanceConfig> = {}) {
|
||||||
|
if (!("language" in $$source)) {
|
||||||
|
this["language"] = ("" as LanguageType);
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(this, $$source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new AppearanceConfig instance from a string or object.
|
||||||
|
*/
|
||||||
|
static createFrom($$source: any = {}): AppearanceConfig {
|
||||||
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
|
return new AppearanceConfig($$parsedSource as Partial<AppearanceConfig>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ConfigMetadata 配置元数据
|
||||||
|
*/
|
||||||
|
export class ConfigMetadata {
|
||||||
|
/**
|
||||||
|
* 配置版本
|
||||||
|
*/
|
||||||
|
"version": string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最后更新时间
|
||||||
|
*/
|
||||||
|
"lastUpdated": time$0.Time;
|
||||||
|
|
||||||
|
/** Creates a new ConfigMetadata instance. */
|
||||||
|
constructor($$source: Partial<ConfigMetadata> = {}) {
|
||||||
|
if (!("version" in $$source)) {
|
||||||
|
this["version"] = "";
|
||||||
|
}
|
||||||
|
if (!("lastUpdated" in $$source)) {
|
||||||
|
this["lastUpdated"] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(this, $$source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new ConfigMetadata instance from a string or object.
|
||||||
|
*/
|
||||||
|
static createFrom($$source: any = {}): ConfigMetadata {
|
||||||
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
|
return new ConfigMetadata($$parsedSource as Partial<ConfigMetadata>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Document 表示一个文档
|
||||||
|
*/
|
||||||
|
export class Document {
|
||||||
|
/**
|
||||||
|
* 元数据
|
||||||
|
*/
|
||||||
|
"meta": DocumentMeta;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文档内容
|
||||||
|
*/
|
||||||
|
"content": string;
|
||||||
|
|
||||||
|
/** Creates a new Document instance. */
|
||||||
|
constructor($$source: Partial<Document> = {}) {
|
||||||
|
if (!("meta" in $$source)) {
|
||||||
|
this["meta"] = (new DocumentMeta());
|
||||||
|
}
|
||||||
|
if (!("content" in $$source)) {
|
||||||
|
this["content"] = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(this, $$source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new Document instance from a string or object.
|
||||||
|
*/
|
||||||
|
static createFrom($$source: any = {}): Document {
|
||||||
|
const $$createField0_0 = $$createType6;
|
||||||
|
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 编辑设置配置
|
||||||
|
*/
|
||||||
|
export class EditingConfig {
|
||||||
|
/**
|
||||||
|
* 字体设置
|
||||||
|
* 字体大小
|
||||||
|
*/
|
||||||
|
"fontSize": number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字体族
|
||||||
|
*/
|
||||||
|
"fontFamily": string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字体粗细
|
||||||
|
*/
|
||||||
|
"fontWeight": string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 行高
|
||||||
|
*/
|
||||||
|
"lineHeight": number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tab设置
|
||||||
|
* 是否启用Tab缩进
|
||||||
|
*/
|
||||||
|
"enableTabIndent": boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tab大小
|
||||||
|
*/
|
||||||
|
"tabSize": number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tab类型(空格或Tab)
|
||||||
|
*/
|
||||||
|
"tabType": TabType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存选项
|
||||||
|
* 自动保存延迟(毫秒)
|
||||||
|
*/
|
||||||
|
"autoSaveDelay": number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 变更字符阈值
|
||||||
|
*/
|
||||||
|
"changeThreshold": number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最小保存间隔(毫秒)
|
||||||
|
*/
|
||||||
|
"minSaveInterval": number;
|
||||||
|
|
||||||
|
/** Creates a new EditingConfig instance. */
|
||||||
|
constructor($$source: Partial<EditingConfig> = {}) {
|
||||||
|
if (!("fontSize" in $$source)) {
|
||||||
|
this["fontSize"] = 0;
|
||||||
|
}
|
||||||
|
if (!("fontFamily" in $$source)) {
|
||||||
|
this["fontFamily"] = "";
|
||||||
|
}
|
||||||
|
if (!("fontWeight" in $$source)) {
|
||||||
|
this["fontWeight"] = "";
|
||||||
|
}
|
||||||
|
if (!("lineHeight" in $$source)) {
|
||||||
|
this["lineHeight"] = 0;
|
||||||
|
}
|
||||||
|
if (!("enableTabIndent" in $$source)) {
|
||||||
|
this["enableTabIndent"] = false;
|
||||||
|
}
|
||||||
|
if (!("tabSize" in $$source)) {
|
||||||
|
this["tabSize"] = 0;
|
||||||
|
}
|
||||||
|
if (!("tabType" in $$source)) {
|
||||||
|
this["tabType"] = ("" as TabType);
|
||||||
|
}
|
||||||
|
if (!("autoSaveDelay" in $$source)) {
|
||||||
|
this["autoSaveDelay"] = 0;
|
||||||
|
}
|
||||||
|
if (!("changeThreshold" in $$source)) {
|
||||||
|
this["changeThreshold"] = 0;
|
||||||
|
}
|
||||||
|
if (!("minSaveInterval" in $$source)) {
|
||||||
|
this["minSaveInterval"] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(this, $$source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new EditingConfig instance from a string or object.
|
||||||
|
*/
|
||||||
|
static createFrom($$source: any = {}): EditingConfig {
|
||||||
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
|
return new EditingConfig($$parsedSource as Partial<EditingConfig>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GeneralConfig 通用设置配置
|
||||||
|
*/
|
||||||
|
export class GeneralConfig {
|
||||||
|
/**
|
||||||
|
* 窗口是否置顶
|
||||||
|
*/
|
||||||
|
"alwaysOnTop": boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认数据存储路径
|
||||||
|
*/
|
||||||
|
"defaultDataPath": string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据存储路径配置
|
||||||
|
* 是否使用自定义数据路径
|
||||||
|
*/
|
||||||
|
"useCustomDataPath": boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义数据存储路径
|
||||||
|
*/
|
||||||
|
"customDataPath": string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全局热键设置
|
||||||
|
* 是否启用全局热键
|
||||||
|
*/
|
||||||
|
"enableGlobalHotkey": boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全局热键组合
|
||||||
|
*/
|
||||||
|
"globalHotkey": HotkeyCombo;
|
||||||
|
|
||||||
|
/** Creates a new GeneralConfig instance. */
|
||||||
|
constructor($$source: Partial<GeneralConfig> = {}) {
|
||||||
|
if (!("alwaysOnTop" in $$source)) {
|
||||||
|
this["alwaysOnTop"] = false;
|
||||||
|
}
|
||||||
|
if (!("defaultDataPath" in $$source)) {
|
||||||
|
this["defaultDataPath"] = "";
|
||||||
|
}
|
||||||
|
if (!("useCustomDataPath" in $$source)) {
|
||||||
|
this["useCustomDataPath"] = false;
|
||||||
|
}
|
||||||
|
if (!("customDataPath" in $$source)) {
|
||||||
|
this["customDataPath"] = "";
|
||||||
|
}
|
||||||
|
if (!("enableGlobalHotkey" in $$source)) {
|
||||||
|
this["enableGlobalHotkey"] = false;
|
||||||
|
}
|
||||||
|
if (!("globalHotkey" in $$source)) {
|
||||||
|
this["globalHotkey"] = (new HotkeyCombo());
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(this, $$source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new GeneralConfig instance from a string or object.
|
||||||
|
*/
|
||||||
|
static createFrom($$source: any = {}): GeneralConfig {
|
||||||
|
const $$createField5_0 = $$createType7;
|
||||||
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
|
if ("globalHotkey" in $$parsedSource) {
|
||||||
|
$$parsedSource["globalHotkey"] = $$createField5_0($$parsedSource["globalHotkey"]);
|
||||||
|
}
|
||||||
|
return new GeneralConfig($$parsedSource as Partial<GeneralConfig>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HotkeyCombo 热键组合定义
|
||||||
|
*/
|
||||||
|
export class HotkeyCombo {
|
||||||
|
/**
|
||||||
|
* Ctrl键
|
||||||
|
*/
|
||||||
|
"ctrl": boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shift键
|
||||||
|
*/
|
||||||
|
"shift": boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alt键
|
||||||
|
*/
|
||||||
|
"alt": boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Win键
|
||||||
|
*/
|
||||||
|
"win": boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主键(如 'X', 'F1' 等)
|
||||||
|
*/
|
||||||
|
"key": string;
|
||||||
|
|
||||||
|
/** Creates a new HotkeyCombo instance. */
|
||||||
|
constructor($$source: Partial<HotkeyCombo> = {}) {
|
||||||
|
if (!("ctrl" in $$source)) {
|
||||||
|
this["ctrl"] = false;
|
||||||
|
}
|
||||||
|
if (!("shift" in $$source)) {
|
||||||
|
this["shift"] = false;
|
||||||
|
}
|
||||||
|
if (!("alt" in $$source)) {
|
||||||
|
this["alt"] = false;
|
||||||
|
}
|
||||||
|
if (!("win" in $$source)) {
|
||||||
|
this["win"] = false;
|
||||||
|
}
|
||||||
|
if (!("key" in $$source)) {
|
||||||
|
this["key"] = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(this, $$source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new HotkeyCombo instance from a string or object.
|
||||||
|
*/
|
||||||
|
static createFrom($$source: any = {}): HotkeyCombo {
|
||||||
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
|
return new HotkeyCombo($$parsedSource as Partial<HotkeyCombo>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* KeyBindingsConfig 快捷键设置配置
|
||||||
|
*/
|
||||||
|
export class KeyBindingsConfig {
|
||||||
|
|
||||||
|
/** Creates a new KeyBindingsConfig instance. */
|
||||||
|
constructor($$source: Partial<KeyBindingsConfig> = {}) {
|
||||||
|
|
||||||
|
Object.assign(this, $$source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new KeyBindingsConfig instance from a string or object.
|
||||||
|
*/
|
||||||
|
static createFrom($$source: any = {}): KeyBindingsConfig {
|
||||||
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
|
return new KeyBindingsConfig($$parsedSource as Partial<KeyBindingsConfig>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LanguageType 语言类型定义
|
||||||
|
*/
|
||||||
|
export enum LanguageType {
|
||||||
|
/**
|
||||||
|
* The Go zero value for the underlying type of the enum.
|
||||||
|
*/
|
||||||
|
$zero = "",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LangZhCN 中文简体
|
||||||
|
*/
|
||||||
|
LangZhCN = "zh-CN",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LangEnUS 英文-美国
|
||||||
|
*/
|
||||||
|
LangEnUS = "en-US",
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TabType 定义了制表符类型
|
||||||
|
*/
|
||||||
|
export enum TabType {
|
||||||
|
/**
|
||||||
|
* The Go zero value for the underlying type of the enum.
|
||||||
|
*/
|
||||||
|
$zero = "",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TabTypeSpaces 使用空格作为制表符
|
||||||
|
*/
|
||||||
|
TabTypeSpaces = "spaces",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TabTypeTab 使用Tab作为制表符
|
||||||
|
*/
|
||||||
|
TabTypeTab = "tab",
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UpdatesConfig 更新设置配置
|
||||||
|
*/
|
||||||
|
export class UpdatesConfig {
|
||||||
|
|
||||||
|
/** Creates a new UpdatesConfig instance. */
|
||||||
|
constructor($$source: Partial<UpdatesConfig> = {}) {
|
||||||
|
|
||||||
|
Object.assign(this, $$source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new UpdatesConfig instance from a string or object.
|
||||||
|
*/
|
||||||
|
static createFrom($$source: any = {}): UpdatesConfig {
|
||||||
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
|
return new UpdatesConfig($$parsedSource as Partial<UpdatesConfig>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private type creation functions
|
||||||
|
const $$createType0 = GeneralConfig.createFrom;
|
||||||
|
const $$createType1 = EditingConfig.createFrom;
|
||||||
|
const $$createType2 = AppearanceConfig.createFrom;
|
||||||
|
const $$createType3 = KeyBindingsConfig.createFrom;
|
||||||
|
const $$createType4 = UpdatesConfig.createFrom;
|
||||||
|
const $$createType5 = ConfigMetadata.createFrom;
|
||||||
|
const $$createType6 = DocumentMeta.createFrom;
|
||||||
|
const $$createType7 = HotkeyCombo.createFrom;
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ConfigService 提供基于 Viper 的配置管理功能
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore: Unused imports
|
||||||
|
import {Call as $Call, Create as $Create} from "@wailsio/runtime";
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore: Unused imports
|
||||||
|
import * as models$0 from "../models/models.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get 获取配置项
|
||||||
|
*/
|
||||||
|
export function Get(key: string): Promise<any> & { cancel(): void } {
|
||||||
|
let $resultPromise = $Call.ByID(807201772, key) as any;
|
||||||
|
return $resultPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GetConfig 获取完整应用配置
|
||||||
|
*/
|
||||||
|
export function GetConfig(): Promise<models$0.AppConfig | null> & { cancel(): void } {
|
||||||
|
let $resultPromise = $Call.ByID(1013336538) as any;
|
||||||
|
let $typingPromise = $resultPromise.then(($result: any) => {
|
||||||
|
return $$createType1($result);
|
||||||
|
}) as any;
|
||||||
|
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
|
||||||
|
return $typingPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ResetConfig 重置为默认配置
|
||||||
|
*/
|
||||||
|
export function ResetConfig(): Promise<void> & { cancel(): void } {
|
||||||
|
let $resultPromise = $Call.ByID(3593047389) as any;
|
||||||
|
return $resultPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set 设置配置项
|
||||||
|
*/
|
||||||
|
export function Set(key: string, value: any): Promise<void> & { cancel(): void } {
|
||||||
|
let $resultPromise = $Call.ByID(2921955968, key, value) as any;
|
||||||
|
return $resultPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SetHotkeyChangeCallback 设置热键配置变更回调
|
||||||
|
*/
|
||||||
|
export function SetHotkeyChangeCallback(callback: any): Promise<void> & { cancel(): void } {
|
||||||
|
let $resultPromise = $Call.ByID(283872321, callback) as any;
|
||||||
|
return $resultPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private type creation functions
|
||||||
|
const $$createType0 = models$0.AppConfig.createFrom;
|
||||||
|
const $$createType1 = $Create.Nullable($$createType0);
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HotkeyService 全局热键服务
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore: Unused imports
|
||||||
|
import {Call as $Call, Create as $Create} from "@wailsio/runtime";
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore: Unused imports
|
||||||
|
import * as application$0 from "../../../github.com/wailsapp/wails/v3/pkg/application/models.js";
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore: Unused imports
|
||||||
|
import * as models$0 from "../models/models.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GetCurrentHotkey 获取当前注册的热键
|
||||||
|
*/
|
||||||
|
export function GetCurrentHotkey(): Promise<models$0.HotkeyCombo | null> & { cancel(): void } {
|
||||||
|
let $resultPromise = $Call.ByID(2572811187) as any;
|
||||||
|
let $typingPromise = $resultPromise.then(($result: any) => {
|
||||||
|
return $$createType1($result);
|
||||||
|
}) as any;
|
||||||
|
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
|
||||||
|
return $typingPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize 初始化热键服务
|
||||||
|
*/
|
||||||
|
export function Initialize(app: application$0.App | null): Promise<void> & { cancel(): void } {
|
||||||
|
let $resultPromise = $Call.ByID(3671360458, app) as any;
|
||||||
|
return $resultPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IsRegistered 检查是否已注册热键
|
||||||
|
*/
|
||||||
|
export function IsRegistered(): Promise<boolean> & { cancel(): void } {
|
||||||
|
let $resultPromise = $Call.ByID(106954156) as any;
|
||||||
|
return $resultPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RegisterHotkey 注册全局热键
|
||||||
|
*/
|
||||||
|
export function RegisterHotkey(hotkey: models$0.HotkeyCombo | null): Promise<void> & { cancel(): void } {
|
||||||
|
let $resultPromise = $Call.ByID(1103945691, hotkey) as any;
|
||||||
|
return $resultPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ServiceShutdown 关闭热键服务
|
||||||
|
*/
|
||||||
|
export function ServiceShutdown(): Promise<void> & { cancel(): void } {
|
||||||
|
let $resultPromise = $Call.ByID(157291181) as any;
|
||||||
|
return $resultPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ToggleWindow 切换窗口显示/隐藏 - 通过事件通知前端处理
|
||||||
|
*/
|
||||||
|
export function ToggleWindow(): Promise<void> & { cancel(): void } {
|
||||||
|
let $resultPromise = $Call.ByID(1318185132) as any;
|
||||||
|
return $resultPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UnregisterHotkey 取消注册全局热键
|
||||||
|
*/
|
||||||
|
export function UnregisterHotkey(): Promise<void> & { cancel(): void } {
|
||||||
|
let $resultPromise = $Call.ByID(3544283732) as any;
|
||||||
|
return $resultPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UpdateHotkey 更新热键配置
|
||||||
|
*/
|
||||||
|
export function UpdateHotkey(enable: boolean, hotkey: models$0.HotkeyCombo | null): Promise<void> & { cancel(): void } {
|
||||||
|
let $resultPromise = $Call.ByID(823285555, enable, hotkey) as any;
|
||||||
|
return $resultPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private type creation functions
|
||||||
|
const $$createType0 = models$0.HotkeyCombo.createFrom;
|
||||||
|
const $$createType1 = $Create.Nullable($$createType0);
|
||||||
15
frontend/bindings/voidraft/internal/services/index.ts
Normal file
15
frontend/bindings/voidraft/internal/services/index.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
|
||||||
|
import * as ConfigService from "./configservice.js";
|
||||||
|
import * as DocumentService from "./documentservice.js";
|
||||||
|
import * as HotkeyService from "./hotkeyservice.js";
|
||||||
|
import * as SystemService from "./systemservice.js";
|
||||||
|
export {
|
||||||
|
ConfigService,
|
||||||
|
DocumentService,
|
||||||
|
HotkeyService,
|
||||||
|
SystemService
|
||||||
|
};
|
||||||
|
|
||||||
|
export * from "./models.js";
|
||||||
131
frontend/package-lock.json
generated
131
frontend/package-lock.json
generated
@@ -30,7 +30,7 @@
|
|||||||
"@codemirror/lang-wast": "^6.0.2",
|
"@codemirror/lang-wast": "^6.0.2",
|
||||||
"@codemirror/lang-xml": "^6.1.0",
|
"@codemirror/lang-xml": "^6.1.0",
|
||||||
"@codemirror/lang-yaml": "^6.1.2",
|
"@codemirror/lang-yaml": "^6.1.2",
|
||||||
"@codemirror/language": "^6.11.0",
|
"@codemirror/language": "^6.11.1",
|
||||||
"@codemirror/language-data": "^6.5.1",
|
"@codemirror/language-data": "^6.5.1",
|
||||||
"@codemirror/legacy-modes": "^6.5.1",
|
"@codemirror/legacy-modes": "^6.5.1",
|
||||||
"@codemirror/lint": "^6.8.5",
|
"@codemirror/lint": "^6.8.5",
|
||||||
@@ -58,7 +58,7 @@
|
|||||||
"eslint-plugin-vue": "^10.1.0",
|
"eslint-plugin-vue": "^10.1.0",
|
||||||
"globals": "^16.2.0",
|
"globals": "^16.2.0",
|
||||||
"typescript": "^5.8.3",
|
"typescript": "^5.8.3",
|
||||||
"typescript-eslint": "^8.33.0",
|
"typescript-eslint": "^8.33.1",
|
||||||
"unplugin-vue-components": "^28.7.0",
|
"unplugin-vue-components": "^28.7.0",
|
||||||
"vite": "^6.3.5",
|
"vite": "^6.3.5",
|
||||||
"vue-eslint-parser": "^10.1.3",
|
"vue-eslint-parser": "^10.1.3",
|
||||||
@@ -400,9 +400,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@codemirror/language": {
|
"node_modules/@codemirror/language": {
|
||||||
"version": "6.11.0",
|
"version": "6.11.1",
|
||||||
"resolved": "https://registry.npmmirror.com/@codemirror/language/-/language-6.11.0.tgz",
|
"resolved": "https://registry.npmmirror.com/@codemirror/language/-/language-6.11.1.tgz",
|
||||||
"integrity": "sha512-A7+f++LodNNc1wGgoRDTt78cOwWm9KVezApgjOMp1W4hM0898nsqBXwF+sbePE7ZRcjN7Sa1Z5m2oN27XkmEjQ==",
|
"integrity": "sha512-5kS1U7emOGV84vxC+ruBty5sUgcD0te6dyupyRVG2zaSjhTDM73LhVKUtVwiqSe6QwmEoA4SCiU8AKPFyumAWQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/state": "^6.0.0",
|
"@codemirror/state": "^6.0.0",
|
||||||
@@ -2065,17 +2065,17 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||||
"version": "8.33.0",
|
"version": "8.33.1",
|
||||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.33.0.tgz",
|
"resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.33.1.tgz",
|
||||||
"integrity": "sha512-CACyQuqSHt7ma3Ns601xykeBK/rDeZa3w6IS6UtMQbixO5DWy+8TilKkviGDH6jtWCo8FGRKEK5cLLkPvEammQ==",
|
"integrity": "sha512-TDCXj+YxLgtvxvFlAvpoRv9MAncDLBV2oT9Bd7YBGC/b/sEURoOYuIwLI99rjWOfY3QtDzO+mk0n4AmdFExW8A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/regexpp": "^4.10.0",
|
"@eslint-community/regexpp": "^4.10.0",
|
||||||
"@typescript-eslint/scope-manager": "8.33.0",
|
"@typescript-eslint/scope-manager": "8.33.1",
|
||||||
"@typescript-eslint/type-utils": "8.33.0",
|
"@typescript-eslint/type-utils": "8.33.1",
|
||||||
"@typescript-eslint/utils": "8.33.0",
|
"@typescript-eslint/utils": "8.33.1",
|
||||||
"@typescript-eslint/visitor-keys": "8.33.0",
|
"@typescript-eslint/visitor-keys": "8.33.1",
|
||||||
"graphemer": "^1.4.0",
|
"graphemer": "^1.4.0",
|
||||||
"ignore": "^7.0.0",
|
"ignore": "^7.0.0",
|
||||||
"natural-compare": "^1.4.0",
|
"natural-compare": "^1.4.0",
|
||||||
@@ -2089,7 +2089,7 @@
|
|||||||
"url": "https://opencollective.com/typescript-eslint"
|
"url": "https://opencollective.com/typescript-eslint"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@typescript-eslint/parser": "^8.33.0",
|
"@typescript-eslint/parser": "^8.33.1",
|
||||||
"eslint": "^8.57.0 || ^9.0.0",
|
"eslint": "^8.57.0 || ^9.0.0",
|
||||||
"typescript": ">=4.8.4 <5.9.0"
|
"typescript": ">=4.8.4 <5.9.0"
|
||||||
}
|
}
|
||||||
@@ -2105,16 +2105,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/parser": {
|
"node_modules/@typescript-eslint/parser": {
|
||||||
"version": "8.33.0",
|
"version": "8.33.1",
|
||||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-8.33.0.tgz",
|
"resolved": "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-8.33.1.tgz",
|
||||||
"integrity": "sha512-JaehZvf6m0yqYp34+RVnihBAChkqeH+tqqhS0GuX1qgPpwLvmTPheKEs6OeCK6hVJgXZHJ2vbjnC9j119auStQ==",
|
"integrity": "sha512-qwxv6dq682yVvgKKp2qWwLgRbscDAYktPptK4JPojCwwi3R9cwrvIxS4lvBpzmcqzR4bdn54Z0IG1uHFskW4dA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.33.0",
|
"@typescript-eslint/scope-manager": "8.33.1",
|
||||||
"@typescript-eslint/types": "8.33.0",
|
"@typescript-eslint/types": "8.33.1",
|
||||||
"@typescript-eslint/typescript-estree": "8.33.0",
|
"@typescript-eslint/typescript-estree": "8.33.1",
|
||||||
"@typescript-eslint/visitor-keys": "8.33.0",
|
"@typescript-eslint/visitor-keys": "8.33.1",
|
||||||
"debug": "^4.3.4"
|
"debug": "^4.3.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -2130,14 +2130,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/project-service": {
|
"node_modules/@typescript-eslint/project-service": {
|
||||||
"version": "8.33.0",
|
"version": "8.33.1",
|
||||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/project-service/-/project-service-8.33.0.tgz",
|
"resolved": "https://registry.npmmirror.com/@typescript-eslint/project-service/-/project-service-8.33.1.tgz",
|
||||||
"integrity": "sha512-d1hz0u9l6N+u/gcrk6s6gYdl7/+pp8yHheRTqP6X5hVDKALEaTn8WfGiit7G511yueBEL3OpOEpD+3/MBdoN+A==",
|
"integrity": "sha512-DZR0efeNklDIHHGRpMpR5gJITQpu6tLr9lDJnKdONTC7vvzOlLAG/wcfxcdxEWrbiZApcoBCzXqU/Z458Za5Iw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/tsconfig-utils": "^8.33.0",
|
"@typescript-eslint/tsconfig-utils": "^8.33.1",
|
||||||
"@typescript-eslint/types": "^8.33.0",
|
"@typescript-eslint/types": "^8.33.1",
|
||||||
"debug": "^4.3.4"
|
"debug": "^4.3.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -2146,17 +2146,20 @@
|
|||||||
"funding": {
|
"funding": {
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
"url": "https://opencollective.com/typescript-eslint"
|
"url": "https://opencollective.com/typescript-eslint"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": ">=4.8.4 <5.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/scope-manager": {
|
"node_modules/@typescript-eslint/scope-manager": {
|
||||||
"version": "8.33.0",
|
"version": "8.33.1",
|
||||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-8.33.0.tgz",
|
"resolved": "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-8.33.1.tgz",
|
||||||
"integrity": "sha512-LMi/oqrzpqxyO72ltP+dBSP6V0xiUb4saY7WLtxSfiNEBI8m321LLVFU9/QDJxjDQG9/tjSqKz/E3380TEqSTw==",
|
"integrity": "sha512-dM4UBtgmzHR9bS0Rv09JST0RcHYearoEoo3pG5B6GoTR9XcyeqX87FEhPo+5kTvVfKCvfHaHrcgeJQc6mrDKrA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.33.0",
|
"@typescript-eslint/types": "8.33.1",
|
||||||
"@typescript-eslint/visitor-keys": "8.33.0"
|
"@typescript-eslint/visitor-keys": "8.33.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
@@ -2167,9 +2170,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/tsconfig-utils": {
|
"node_modules/@typescript-eslint/tsconfig-utils": {
|
||||||
"version": "8.33.0",
|
"version": "8.33.1",
|
||||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.33.0.tgz",
|
"resolved": "https://registry.npmmirror.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.33.1.tgz",
|
||||||
"integrity": "sha512-sTkETlbqhEoiFmGr1gsdq5HyVbSOF0145SYDJ/EQmXHtKViCaGvnyLqWFFHtEXoS0J1yU8Wyou2UGmgW88fEug==",
|
"integrity": "sha512-STAQsGYbHCF0/e+ShUQ4EatXQ7ceh3fBCXkNU7/MZVKulrlq1usH7t2FhxvCpuCi5O5oi1vmVaAjrGeL71OK1g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -2184,14 +2187,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/type-utils": {
|
"node_modules/@typescript-eslint/type-utils": {
|
||||||
"version": "8.33.0",
|
"version": "8.33.1",
|
||||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-8.33.0.tgz",
|
"resolved": "https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-8.33.1.tgz",
|
||||||
"integrity": "sha512-lScnHNCBqL1QayuSrWeqAL5GmqNdVUQAAMTaCwdYEdWfIrSrOGzyLGRCHXcCixa5NK6i5l0AfSO2oBSjCjf4XQ==",
|
"integrity": "sha512-1cG37d9xOkhlykom55WVwG2QRNC7YXlxMaMzqw2uPeJixBFfKWZgaP/hjAObqMN/u3fr5BrTwTnc31/L9jQ2ww==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/typescript-estree": "8.33.0",
|
"@typescript-eslint/typescript-estree": "8.33.1",
|
||||||
"@typescript-eslint/utils": "8.33.0",
|
"@typescript-eslint/utils": "8.33.1",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"ts-api-utils": "^2.1.0"
|
"ts-api-utils": "^2.1.0"
|
||||||
},
|
},
|
||||||
@@ -2208,9 +2211,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/types": {
|
"node_modules/@typescript-eslint/types": {
|
||||||
"version": "8.33.0",
|
"version": "8.33.1",
|
||||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/types/-/types-8.33.0.tgz",
|
"resolved": "https://registry.npmmirror.com/@typescript-eslint/types/-/types-8.33.1.tgz",
|
||||||
"integrity": "sha512-DKuXOKpM5IDT1FA2g9x9x1Ug81YuKrzf4mYX8FAVSNu5Wo/LELHWQyM1pQaDkI42bX15PWl0vNPt1uGiIFUOpg==",
|
"integrity": "sha512-xid1WfizGhy/TKMTwhtVOgalHwPtV8T32MS9MaH50Cwvz6x6YqRIPdD2WvW0XaqOzTV9p5xdLY0h/ZusU5Lokg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -2222,16 +2225,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/typescript-estree": {
|
"node_modules/@typescript-eslint/typescript-estree": {
|
||||||
"version": "8.33.0",
|
"version": "8.33.1",
|
||||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.33.0.tgz",
|
"resolved": "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.33.1.tgz",
|
||||||
"integrity": "sha512-vegY4FQoB6jL97Tu/lWRsAiUUp8qJTqzAmENH2k59SJhw0Th1oszb9Idq/FyyONLuNqT1OADJPXfyUNOR8SzAQ==",
|
"integrity": "sha512-+s9LYcT8LWjdYWu7IWs7FvUxpQ/DGkdjZeE/GGulHvv8rvYwQvVaUZ6DE+j5x/prADUgSbbCWZ2nPI3usuVeOA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/project-service": "8.33.0",
|
"@typescript-eslint/project-service": "8.33.1",
|
||||||
"@typescript-eslint/tsconfig-utils": "8.33.0",
|
"@typescript-eslint/tsconfig-utils": "8.33.1",
|
||||||
"@typescript-eslint/types": "8.33.0",
|
"@typescript-eslint/types": "8.33.1",
|
||||||
"@typescript-eslint/visitor-keys": "8.33.0",
|
"@typescript-eslint/visitor-keys": "8.33.1",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"fast-glob": "^3.3.2",
|
"fast-glob": "^3.3.2",
|
||||||
"is-glob": "^4.0.3",
|
"is-glob": "^4.0.3",
|
||||||
@@ -2277,16 +2280,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/utils": {
|
"node_modules/@typescript-eslint/utils": {
|
||||||
"version": "8.33.0",
|
"version": "8.33.1",
|
||||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-8.33.0.tgz",
|
"resolved": "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-8.33.1.tgz",
|
||||||
"integrity": "sha512-lPFuQaLA9aSNa7D5u2EpRiqdAUhzShwGg/nhpBlc4GR6kcTABttCuyjFs8BcEZ8VWrjCBof/bePhP3Q3fS+Yrw==",
|
"integrity": "sha512-52HaBiEQUaRYqAXpfzWSR2U3gxk92Kw006+xZpElaPMg3C4PgM+A5LqwoQI1f9E5aZ/qlxAZxzm42WX+vn92SQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.7.0",
|
"@eslint-community/eslint-utils": "^4.7.0",
|
||||||
"@typescript-eslint/scope-manager": "8.33.0",
|
"@typescript-eslint/scope-manager": "8.33.1",
|
||||||
"@typescript-eslint/types": "8.33.0",
|
"@typescript-eslint/types": "8.33.1",
|
||||||
"@typescript-eslint/typescript-estree": "8.33.0"
|
"@typescript-eslint/typescript-estree": "8.33.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
@@ -2301,13 +2304,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/visitor-keys": {
|
"node_modules/@typescript-eslint/visitor-keys": {
|
||||||
"version": "8.33.0",
|
"version": "8.33.1",
|
||||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.33.0.tgz",
|
"resolved": "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.33.1.tgz",
|
||||||
"integrity": "sha512-7RW7CMYoskiz5OOGAWjJFxgb7c5UNjTG292gYhWeOAcFmYCtVCSqjqSBj5zMhxbXo2JOW95YYrUWJfU0zrpaGQ==",
|
"integrity": "sha512-3i8NrFcZeeDHJ+7ZUuDkGT+UHq+XoFGsymNK2jZCOHcfEzRQ0BdpRtdpSx/Iyf3MHLWIcLS0COuOPibKQboIiQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.33.0",
|
"@typescript-eslint/types": "8.33.1",
|
||||||
"eslint-visitor-keys": "^4.2.0"
|
"eslint-visitor-keys": "^4.2.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -4544,15 +4547,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/typescript-eslint": {
|
"node_modules/typescript-eslint": {
|
||||||
"version": "8.33.0",
|
"version": "8.33.1",
|
||||||
"resolved": "https://registry.npmmirror.com/typescript-eslint/-/typescript-eslint-8.33.0.tgz",
|
"resolved": "https://registry.npmmirror.com/typescript-eslint/-/typescript-eslint-8.33.1.tgz",
|
||||||
"integrity": "sha512-5YmNhF24ylCsvdNW2oJwMzTbaeO4bg90KeGtMjUw0AGtHksgEPLRTUil+coHwCfiu4QjVJFnjp94DmU6zV7DhQ==",
|
"integrity": "sha512-AgRnV4sKkWOiZ0Kjbnf5ytTJXMUZQ0qhSVdQtDNYLPLnjsATEYhaO94GlRQwi4t4gO8FfjM6NnikHeKjUm8D7A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/eslint-plugin": "8.33.0",
|
"@typescript-eslint/eslint-plugin": "8.33.1",
|
||||||
"@typescript-eslint/parser": "8.33.0",
|
"@typescript-eslint/parser": "8.33.1",
|
||||||
"@typescript-eslint/utils": "8.33.0"
|
"@typescript-eslint/utils": "8.33.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
"@codemirror/lang-wast": "^6.0.2",
|
"@codemirror/lang-wast": "^6.0.2",
|
||||||
"@codemirror/lang-xml": "^6.1.0",
|
"@codemirror/lang-xml": "^6.1.0",
|
||||||
"@codemirror/lang-yaml": "^6.1.2",
|
"@codemirror/lang-yaml": "^6.1.2",
|
||||||
"@codemirror/language": "^6.11.0",
|
"@codemirror/language": "^6.11.1",
|
||||||
"@codemirror/language-data": "^6.5.1",
|
"@codemirror/language-data": "^6.5.1",
|
||||||
"@codemirror/legacy-modes": "^6.5.1",
|
"@codemirror/legacy-modes": "^6.5.1",
|
||||||
"@codemirror/lint": "^6.8.5",
|
"@codemirror/lint": "^6.8.5",
|
||||||
@@ -62,7 +62,7 @@
|
|||||||
"eslint-plugin-vue": "^10.1.0",
|
"eslint-plugin-vue": "^10.1.0",
|
||||||
"globals": "^16.2.0",
|
"globals": "^16.2.0",
|
||||||
"typescript": "^5.8.3",
|
"typescript": "^5.8.3",
|
||||||
"typescript-eslint": "^8.33.0",
|
"typescript-eslint": "^8.33.1",
|
||||||
"unplugin-vue-components": "^28.7.0",
|
"unplugin-vue-components": "^28.7.0",
|
||||||
"vite": "^6.3.5",
|
"vite": "^6.3.5",
|
||||||
"vue-eslint-parser": "^10.1.3",
|
"vue-eslint-parser": "^10.1.3",
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export default {
|
|||||||
tabSizeFixed: 'Tab size ({value}) has been corrected to {fixed}',
|
tabSizeFixed: 'Tab size ({value}) has been corrected to {fixed}',
|
||||||
tabTypeFixed: 'Tab type ({value}) is invalid, corrected to spaces',
|
tabTypeFixed: 'Tab type ({value}) is invalid, corrected to spaces',
|
||||||
alwaysOnTopFailed: 'Failed to set window always on top',
|
alwaysOnTopFailed: 'Failed to set window always on top',
|
||||||
|
alwaysOnTopSuccess: 'Window always on top status updated',
|
||||||
languageChanged: 'Language setting updated',
|
languageChanged: 'Language setting updated',
|
||||||
languageChangeFailed: 'Failed to update language setting'
|
languageChangeFailed: 'Failed to update language setting'
|
||||||
},
|
},
|
||||||
@@ -67,9 +68,15 @@ export default {
|
|||||||
window: 'Window/Application',
|
window: 'Window/Application',
|
||||||
showInSystemTray: 'Show in System Tray',
|
showInSystemTray: 'Show in System Tray',
|
||||||
alwaysOnTop: 'Always on Top',
|
alwaysOnTop: 'Always on Top',
|
||||||
|
dataStorage: 'Data Storage',
|
||||||
|
useCustomDataPath: 'Use custom data storage path',
|
||||||
|
selectDirectory: 'Select Directory',
|
||||||
|
selectDataDirectory: 'Select Data Storage Directory',
|
||||||
|
defaultDataPath: 'Default data storage path',
|
||||||
|
enterCustomPath: 'Enter custom data storage path',
|
||||||
|
pathHint: 'Example: C:\\MyData or /home/user/data or relative path ./data',
|
||||||
bufferFiles: 'Buffer Files Path',
|
bufferFiles: 'Buffer Files Path',
|
||||||
useCustomLocation: 'Use custom location for buffer files',
|
useCustomLocation: 'Use custom location for buffer files',
|
||||||
selectDirectory: 'Select Directory',
|
|
||||||
fontSize: 'Font Size',
|
fontSize: 'Font Size',
|
||||||
fontSizeDescription: 'Editor font size',
|
fontSizeDescription: 'Editor font size',
|
||||||
fontSettings: 'Font Settings',
|
fontSettings: 'Font Settings',
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export default {
|
|||||||
tabSizeFixed: 'Tab大小值({value})已被修正为{fixed}',
|
tabSizeFixed: 'Tab大小值({value})已被修正为{fixed}',
|
||||||
tabTypeFixed: 'Tab类型({value})不合法,已修正为空格',
|
tabTypeFixed: 'Tab类型({value})不合法,已修正为空格',
|
||||||
alwaysOnTopFailed: '无法设置窗口置顶状态',
|
alwaysOnTopFailed: '无法设置窗口置顶状态',
|
||||||
|
alwaysOnTopSuccess: '窗口置顶状态已更新',
|
||||||
languageChanged: '语言设置已更新',
|
languageChanged: '语言设置已更新',
|
||||||
languageChangeFailed: '语言设置更新失败'
|
languageChangeFailed: '语言设置更新失败'
|
||||||
},
|
},
|
||||||
@@ -67,9 +68,15 @@ export default {
|
|||||||
window: '窗口/应用程序',
|
window: '窗口/应用程序',
|
||||||
showInSystemTray: '在系统托盘中显示',
|
showInSystemTray: '在系统托盘中显示',
|
||||||
alwaysOnTop: '窗口始终置顶',
|
alwaysOnTop: '窗口始终置顶',
|
||||||
|
dataStorage: '数据存储',
|
||||||
|
useCustomDataPath: '使用自定义数据存储路径',
|
||||||
|
selectDirectory: '选择目录',
|
||||||
|
selectDataDirectory: '选择数据存储目录',
|
||||||
|
defaultDataPath: '默认数据存储路径',
|
||||||
|
enterCustomPath: '请输入自定义数据存储路径',
|
||||||
|
pathHint: '例如: C:\\MyData 或 /home/user/data 或相对路径 ./data',
|
||||||
bufferFiles: '缓冲文件路径',
|
bufferFiles: '缓冲文件路径',
|
||||||
useCustomLocation: '使用自定义位置存储缓冲文件',
|
useCustomLocation: '使用自定义位置存储缓冲文件',
|
||||||
selectDirectory: '选择目录',
|
|
||||||
fontSize: '字体大小',
|
fontSize: '字体大小',
|
||||||
fontSizeDescription: '编辑器字体大小',
|
fontSizeDescription: '编辑器字体大小',
|
||||||
fontSettings: '字体设置',
|
fontSettings: '字体设置',
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {useI18n} from 'vue-i18n';
|
|||||||
import {useErrorHandler} from '@/utils/errorHandler';
|
import {useErrorHandler} from '@/utils/errorHandler';
|
||||||
import {ConfigUtils} from '@/utils/configUtils';
|
import {ConfigUtils} from '@/utils/configUtils';
|
||||||
import {WindowController} from '@/utils/windowController';
|
import {WindowController} from '@/utils/windowController';
|
||||||
|
import * as runtime from '@wailsio/runtime';
|
||||||
// 国际化相关导入
|
// 国际化相关导入
|
||||||
export type SupportedLocaleType = 'zh-CN' | 'en-US';
|
export type SupportedLocaleType = 'zh-CN' | 'en-US';
|
||||||
|
|
||||||
@@ -46,7 +47,9 @@ type NumberConfigKey = 'fontSize' | 'tabSize' | 'lineHeight';
|
|||||||
// 配置键映射
|
// 配置键映射
|
||||||
const GENERAL_CONFIG_KEY_MAP: GeneralConfigKeyMap = {
|
const GENERAL_CONFIG_KEY_MAP: GeneralConfigKeyMap = {
|
||||||
alwaysOnTop: 'general.always_on_top',
|
alwaysOnTop: 'general.always_on_top',
|
||||||
dataPath: 'general.data_path',
|
defaultDataPath: 'general.default_data_path',
|
||||||
|
useCustomDataPath: 'general.use_custom_data_path',
|
||||||
|
customDataPath: 'general.custom_data_path',
|
||||||
enableGlobalHotkey: 'general.enable_global_hotkey',
|
enableGlobalHotkey: 'general.enable_global_hotkey',
|
||||||
globalHotkey: 'general.global_hotkey'
|
globalHotkey: 'general.global_hotkey'
|
||||||
} as const;
|
} as const;
|
||||||
@@ -114,7 +117,9 @@ const getBrowserLanguage = (): SupportedLocaleType => {
|
|||||||
const DEFAULT_CONFIG: AppConfig = {
|
const DEFAULT_CONFIG: AppConfig = {
|
||||||
general: {
|
general: {
|
||||||
alwaysOnTop: false,
|
alwaysOnTop: false,
|
||||||
dataPath: './data',
|
defaultDataPath: './data',
|
||||||
|
useCustomDataPath: false,
|
||||||
|
customDataPath: '',
|
||||||
enableGlobalHotkey: false,
|
enableGlobalHotkey: false,
|
||||||
globalHotkey: {
|
globalHotkey: {
|
||||||
ctrl: false,
|
ctrl: false,
|
||||||
@@ -318,7 +323,13 @@ export const useConfigStore = defineStore('config', () => {
|
|||||||
// 创建切换器实例
|
// 创建切换器实例
|
||||||
const togglers = {
|
const togglers = {
|
||||||
tabIndent: createEditingToggler('enableTabIndent'),
|
tabIndent: createEditingToggler('enableTabIndent'),
|
||||||
alwaysOnTop: createGeneralToggler('alwaysOnTop'),
|
alwaysOnTop: async () => {
|
||||||
|
await safeCall(async () => {
|
||||||
|
await updateGeneralConfig('alwaysOnTop', !state.config.general.alwaysOnTop);
|
||||||
|
// 立即应用窗口置顶状态
|
||||||
|
await runtime.Window.SetAlwaysOnTop(state.config.general.alwaysOnTop);
|
||||||
|
}, 'config.alwaysOnTopFailed', 'config.alwaysOnTopSuccess');
|
||||||
|
},
|
||||||
tabType: createEnumToggler('tabType', CONFIG_LIMITS.tabType.values)
|
tabType: createEnumToggler('tabType', CONFIG_LIMITS.tabType.values)
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -326,7 +337,7 @@ export const useConfigStore = defineStore('config', () => {
|
|||||||
const setters = {
|
const setters = {
|
||||||
fontFamily: (value: string) => safeCall(() => updateEditingConfig('fontFamily', value), 'config.saveFailed', 'config.saveSuccess'),
|
fontFamily: (value: string) => safeCall(() => updateEditingConfig('fontFamily', value), 'config.saveFailed', 'config.saveSuccess'),
|
||||||
fontWeight: (value: string) => safeCall(() => updateEditingConfig('fontWeight', value), 'config.saveFailed', 'config.saveSuccess'),
|
fontWeight: (value: string) => safeCall(() => updateEditingConfig('fontWeight', value), 'config.saveFailed', 'config.saveSuccess'),
|
||||||
dataPath: (value: string) => safeCall(() => updateGeneralConfig('dataPath', value), 'config.saveFailed', 'config.saveSuccess'),
|
defaultDataPath: (value: string) => safeCall(() => updateGeneralConfig('defaultDataPath', value), 'config.saveFailed', 'config.saveSuccess'),
|
||||||
autoSaveDelay: (value: number) => safeCall(() => updateEditingConfig('autoSaveDelay', value), 'config.saveFailed', 'config.saveSuccess'),
|
autoSaveDelay: (value: number) => safeCall(() => updateEditingConfig('autoSaveDelay', value), 'config.saveFailed', 'config.saveSuccess'),
|
||||||
changeThreshold: (value: number) => safeCall(() => updateEditingConfig('changeThreshold', value), 'config.saveFailed', 'config.saveSuccess'),
|
changeThreshold: (value: number) => safeCall(() => updateEditingConfig('changeThreshold', value), 'config.saveFailed', 'config.saveSuccess'),
|
||||||
minSaveInterval: (value: number) => safeCall(() => updateEditingConfig('minSaveInterval', value), 'config.saveFailed', 'config.saveSuccess')
|
minSaveInterval: (value: number) => safeCall(() => updateEditingConfig('minSaveInterval', value), 'config.saveFailed', 'config.saveSuccess')
|
||||||
@@ -370,13 +381,16 @@ export const useConfigStore = defineStore('config', () => {
|
|||||||
|
|
||||||
// 窗口操作
|
// 窗口操作
|
||||||
toggleAlwaysOnTop: togglers.alwaysOnTop,
|
toggleAlwaysOnTop: togglers.alwaysOnTop,
|
||||||
|
setAlwaysOnTop: (value: boolean) => safeCall(() => updateGeneralConfig('alwaysOnTop', value), 'config.saveFailed', 'config.saveSuccess'),
|
||||||
|
|
||||||
// 字体操作
|
// 字体操作
|
||||||
setFontFamily: setters.fontFamily,
|
setFontFamily: setters.fontFamily,
|
||||||
setFontWeight: setters.fontWeight,
|
setFontWeight: setters.fontWeight,
|
||||||
|
|
||||||
// 路径操作
|
// 路径操作
|
||||||
setDataPath: setters.dataPath,
|
setDefaultDataPath: setters.defaultDataPath,
|
||||||
|
setUseCustomDataPath: (value: boolean) => safeCall(() => updateGeneralConfig('useCustomDataPath', value), 'config.saveFailed', 'config.saveSuccess'),
|
||||||
|
setCustomDataPath: (value: string) => safeCall(() => updateGeneralConfig('customDataPath', value), 'config.saveFailed', 'config.saveSuccess'),
|
||||||
|
|
||||||
// 保存配置相关方法
|
// 保存配置相关方法
|
||||||
setAutoSaveDelay: setters.autoSaveDelay,
|
setAutoSaveDelay: setters.autoSaveDelay,
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useConfigStore } from '@/stores/configStore';
|
import { useConfigStore } from '@/stores/configStore';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { computed } from 'vue';
|
import { computed, ref, watch } from 'vue';
|
||||||
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';
|
||||||
import { useErrorHandler } from '@/utils/errorHandler';
|
import { useErrorHandler } from '@/utils/errorHandler';
|
||||||
|
import * as runtime from '@wailsio/runtime';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const configStore = useConfigStore();
|
const configStore = useConfigStore();
|
||||||
@@ -25,6 +26,19 @@ const enableGlobalHotkey = computed({
|
|||||||
set: (value: boolean) => configStore.setEnableGlobalHotkey(value)
|
set: (value: boolean) => configStore.setEnableGlobalHotkey(value)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 计算属性 - 窗口始终置顶
|
||||||
|
const alwaysOnTop = computed({
|
||||||
|
get: () => configStore.config.general.alwaysOnTop,
|
||||||
|
set: async (value: boolean) => {
|
||||||
|
await safeCall(async () => {
|
||||||
|
// 先更新配置
|
||||||
|
await configStore.setAlwaysOnTop(value);
|
||||||
|
// 然后立即应用窗口置顶状态
|
||||||
|
await runtime.Window.SetAlwaysOnTop(value);
|
||||||
|
}, 'config.alwaysOnTopFailed', 'config.alwaysOnTopSuccess');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// 修饰键配置 - 只读计算属性
|
// 修饰键配置 - 只读计算属性
|
||||||
const modifierKeys = computed(() => ({
|
const modifierKeys = computed(() => ({
|
||||||
ctrl: configStore.config.general.globalHotkey.ctrl,
|
ctrl: configStore.config.general.globalHotkey.ctrl,
|
||||||
@@ -71,6 +85,34 @@ const hotkeyPreview = computed(() => {
|
|||||||
|
|
||||||
return parts.join(' + ');
|
return parts.join(' + ');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 数据存储路径配置
|
||||||
|
const useCustomDataPath = computed({
|
||||||
|
get: () => configStore.config.general.useCustomDataPath,
|
||||||
|
set: (value: boolean) => configStore.setUseCustomDataPath(value)
|
||||||
|
});
|
||||||
|
|
||||||
|
// 自定义路径输入
|
||||||
|
const customPathInput = ref('');
|
||||||
|
|
||||||
|
// 监听自定义路径的变化,同步到配置
|
||||||
|
const updateCustomPath = () => {
|
||||||
|
configStore.setCustomDataPath(customPathInput.value.trim());
|
||||||
|
};
|
||||||
|
|
||||||
|
// 当启用自定义路径时,初始化输入框的值
|
||||||
|
const initCustomPath = () => {
|
||||||
|
if (useCustomDataPath.value) {
|
||||||
|
customPathInput.value = configStore.config.general.customDataPath || '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 监听useCustomDataPath的变化
|
||||||
|
watch(() => useCustomDataPath.value, (newVal) => {
|
||||||
|
if (newVal) {
|
||||||
|
initCustomPath();
|
||||||
|
}
|
||||||
|
}, { immediate: true });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -112,21 +154,29 @@ const hotkeyPreview = computed(() => {
|
|||||||
</SettingSection>
|
</SettingSection>
|
||||||
|
|
||||||
<SettingSection :title="t('settings.window')">
|
<SettingSection :title="t('settings.window')">
|
||||||
<SettingItem :title="t('settings.showInSystemTray')">
|
|
||||||
<ToggleSwitch v-model="configStore.config.general.alwaysOnTop" /> <!-- 需要后端实现 -->
|
|
||||||
</SettingItem>
|
|
||||||
<SettingItem :title="t('settings.alwaysOnTop')">
|
<SettingItem :title="t('settings.alwaysOnTop')">
|
||||||
<ToggleSwitch v-model="configStore.config.general.alwaysOnTop" @update:modelValue="configStore.toggleAlwaysOnTop" />
|
<ToggleSwitch v-model="alwaysOnTop" />
|
||||||
</SettingItem>
|
</SettingItem>
|
||||||
</SettingSection>
|
</SettingSection>
|
||||||
|
|
||||||
<SettingSection :title="t('settings.bufferFiles')">
|
<SettingSection :title="t('settings.dataStorage')">
|
||||||
<SettingItem :title="t('settings.useCustomLocation')">
|
<SettingItem :title="t('settings.useCustomDataPath')">
|
||||||
<ToggleSwitch v-model="configStore.config.general.alwaysOnTop" /> <!-- 需要后端实现 -->
|
<ToggleSwitch v-model="useCustomDataPath" />
|
||||||
</SettingItem>
|
</SettingItem>
|
||||||
<div class="directory-selector">
|
<div class="path-input-section" v-if="useCustomDataPath">
|
||||||
<div class="path-display">{{ configStore.config.general.alwaysOnTop ? 'C:/Custom/Path' : 'Default Location' }}</div>
|
<input
|
||||||
<button class="select-button">{{ t('settings.selectDirectory') }}</button>
|
type="text"
|
||||||
|
v-model="customPathInput"
|
||||||
|
@blur="updateCustomPath"
|
||||||
|
@keyup.enter="updateCustomPath"
|
||||||
|
:placeholder="t('settings.enterCustomPath')"
|
||||||
|
class="path-input"
|
||||||
|
/>
|
||||||
|
<div class="path-hint">{{ t('settings.pathHint') }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="default-path-info" v-else>
|
||||||
|
<div class="path-display default">{{ configStore.config.general.defaultDataPath }}</div>
|
||||||
|
<span class="path-label">{{ t('settings.defaultDataPath') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</SettingSection>
|
</SettingSection>
|
||||||
|
|
||||||
@@ -255,46 +305,70 @@ const hotkeyPreview = computed(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.directory-selector {
|
.path-input-section {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
display: flex;
|
padding-right: 20px;
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
|
|
||||||
.path-display {
|
.path-input {
|
||||||
flex: 1;
|
width: 100%;
|
||||||
padding: 8px 12px;
|
max-width: 100%;
|
||||||
background-color: #3a3a3a;
|
box-sizing: border-box;
|
||||||
border: 1px solid #555555;
|
|
||||||
border-radius: 4px;
|
|
||||||
color: #b0b0b0;
|
|
||||||
font-size: 13px;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select-button {
|
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
background-color: #3a3a3a;
|
background-color: #3a3a3a;
|
||||||
border: 1px solid #555555;
|
border: 1px solid #555555;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
color: #e0e0e0;
|
color: #e0e0e0;
|
||||||
cursor: pointer;
|
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #4a9eff;
|
||||||
|
background-color: #404040;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: #888888;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-hint {
|
||||||
|
margin-top: 5px;
|
||||||
|
color: #888888;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.default-path-info {
|
||||||
|
margin-top: 10px;
|
||||||
|
padding-left: 20px;
|
||||||
|
padding-right: 20px;
|
||||||
|
|
||||||
|
.path-display {
|
||||||
|
padding: 8px 12px;
|
||||||
|
background-color: #2a2a2a;
|
||||||
|
border: 1px solid #444444;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #888888;
|
||||||
|
font-size: 13px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
&:hover {
|
&.default {
|
||||||
background-color: #444444;
|
font-style: italic;
|
||||||
border-color: #666666;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
transform: translateY(1px);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.path-label {
|
||||||
|
display: block;
|
||||||
|
margin-top: 4px;
|
||||||
|
color: #666666;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.danger-zone {
|
.danger-zone {
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -46,7 +46,7 @@ require (
|
|||||||
github.com/skeema/knownhosts v1.3.1 // indirect
|
github.com/skeema/knownhosts v1.3.1 // indirect
|
||||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||||
github.com/spf13/afero v1.14.0 // indirect
|
github.com/spf13/afero v1.14.0 // indirect
|
||||||
github.com/spf13/cast v1.8.0 // indirect
|
github.com/spf13/cast v1.9.2 // indirect
|
||||||
github.com/spf13/pflag v1.0.6 // indirect
|
github.com/spf13/pflag v1.0.6 // indirect
|
||||||
github.com/subosito/gotenv v1.6.0 // indirect
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
github.com/wailsapp/go-webview2 v1.0.21 // indirect
|
github.com/wailsapp/go-webview2 v1.0.21 // indirect
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -107,8 +107,8 @@ github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9yS
|
|||||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||||
github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA=
|
github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA=
|
||||||
github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo=
|
github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo=
|
||||||
github.com/spf13/cast v1.8.0 h1:gEN9K4b8Xws4EX0+a0reLmhq8moKn7ntRlQYgjPeCDk=
|
github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE=
|
||||||
github.com/spf13/cast v1.8.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||||
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
|
github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
|
||||||
|
|||||||
@@ -28,8 +28,12 @@ const (
|
|||||||
|
|
||||||
// GeneralConfig 通用设置配置
|
// GeneralConfig 通用设置配置
|
||||||
type GeneralConfig struct {
|
type GeneralConfig struct {
|
||||||
AlwaysOnTop bool `json:"alwaysOnTop" yaml:"always_on_top" mapstructure:"always_on_top"` // 窗口是否置顶
|
AlwaysOnTop bool `json:"alwaysOnTop" yaml:"always_on_top" mapstructure:"always_on_top"` // 窗口是否置顶
|
||||||
DataPath string `json:"dataPath" yaml:"data_path" mapstructure:"data_path"` // 数据存储路径
|
DefaultDataPath string `json:"defaultDataPath" yaml:"default_data_path" mapstructure:"default_data_path"` // 默认数据存储路径
|
||||||
|
|
||||||
|
// 数据存储路径配置
|
||||||
|
UseCustomDataPath bool `json:"useCustomDataPath" yaml:"use_custom_data_path" mapstructure:"use_custom_data_path"` // 是否使用自定义数据路径
|
||||||
|
CustomDataPath string `json:"customDataPath" yaml:"custom_data_path" mapstructure:"custom_data_path"` // 自定义数据存储路径
|
||||||
|
|
||||||
// 全局热键设置
|
// 全局热键设置
|
||||||
EnableGlobalHotkey bool `json:"enableGlobalHotkey" yaml:"enable_global_hotkey" mapstructure:"enable_global_hotkey"` // 是否启用全局热键
|
EnableGlobalHotkey bool `json:"enableGlobalHotkey" yaml:"enable_global_hotkey" mapstructure:"enable_global_hotkey"` // 是否启用全局热键
|
||||||
@@ -109,7 +113,9 @@ func NewDefaultAppConfig() *AppConfig {
|
|||||||
return &AppConfig{
|
return &AppConfig{
|
||||||
General: GeneralConfig{
|
General: GeneralConfig{
|
||||||
AlwaysOnTop: false,
|
AlwaysOnTop: false,
|
||||||
DataPath: dataDir,
|
DefaultDataPath: dataDir,
|
||||||
|
UseCustomDataPath: false,
|
||||||
|
CustomDataPath: "",
|
||||||
EnableGlobalHotkey: false,
|
EnableGlobalHotkey: false,
|
||||||
GlobalHotkey: HotkeyCombo{
|
GlobalHotkey: HotkeyCombo{
|
||||||
Ctrl: false,
|
Ctrl: false,
|
||||||
|
|||||||
456
internal/services/config_notification_service.go
Normal file
456
internal/services/config_notification_service.go
Normal file
@@ -0,0 +1,456 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
"voidraft/internal/models"
|
||||||
|
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"github.com/wailsapp/wails/v3/pkg/services/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConfigChangeType 配置变更类型
|
||||||
|
type ConfigChangeType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ConfigChangeTypeHotkey 热键配置变更
|
||||||
|
ConfigChangeTypeHotkey ConfigChangeType = "hotkey"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConfigChangeCallback 配置变更回调函数类型
|
||||||
|
type ConfigChangeCallback func(changeType ConfigChangeType, oldConfig, newConfig interface{}) error
|
||||||
|
|
||||||
|
// ConfigListener 配置监听器
|
||||||
|
type ConfigListener struct {
|
||||||
|
Name string // 监听器名称
|
||||||
|
ChangeType ConfigChangeType // 监听的配置变更类型
|
||||||
|
Callback ConfigChangeCallback // 回调函数(现在包含新旧配置)
|
||||||
|
DebounceDelay time.Duration // 防抖延迟时间
|
||||||
|
GetConfigFunc func(*viper.Viper) interface{} // 获取相关配置的函数
|
||||||
|
|
||||||
|
// 内部状态
|
||||||
|
mu sync.RWMutex // 监听器状态锁
|
||||||
|
timer *time.Timer // 防抖定时器
|
||||||
|
lastConfigHash string // 上次配置的哈希值,用于变更检测
|
||||||
|
lastConfig interface{} // 上次的配置副本
|
||||||
|
stopChan chan struct{} // 停止通道,用于停止异步goroutine
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfigNotificationService 配置通知服务
|
||||||
|
type ConfigNotificationService struct {
|
||||||
|
listeners map[ConfigChangeType]*ConfigListener // 监听器映射
|
||||||
|
mu sync.RWMutex // 读写锁
|
||||||
|
logger *log.LoggerService // 日志服务
|
||||||
|
viper *viper.Viper // Viper实例
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConfigNotificationService 创建配置通知服务
|
||||||
|
func NewConfigNotificationService(viper *viper.Viper, logger *log.LoggerService) *ConfigNotificationService {
|
||||||
|
return &ConfigNotificationService{
|
||||||
|
listeners: make(map[ConfigChangeType]*ConfigListener),
|
||||||
|
logger: logger,
|
||||||
|
viper: viper,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterListener 注册配置监听器
|
||||||
|
func (cns *ConfigNotificationService) RegisterListener(listener *ConfigListener) error {
|
||||||
|
cns.mu.Lock()
|
||||||
|
defer cns.mu.Unlock()
|
||||||
|
|
||||||
|
// 检查是否已存在同类型监听器
|
||||||
|
if existingListener, exists := cns.listeners[listener.ChangeType]; exists {
|
||||||
|
cns.logger.Warning("ConfigNotification: Listener already exists, will be replaced",
|
||||||
|
"existing_name", existingListener.Name,
|
||||||
|
"new_name", listener.Name,
|
||||||
|
"type", string(listener.ChangeType))
|
||||||
|
|
||||||
|
// 清理旧监听器
|
||||||
|
cns.cleanupListener(existingListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化新监听器
|
||||||
|
listener.stopChan = make(chan struct{})
|
||||||
|
|
||||||
|
// 初始化监听器的配置状态
|
||||||
|
if err := cns.initializeListenerState(listener); err != nil {
|
||||||
|
cns.logger.Error("ConfigNotification: Failed to initialize listener state",
|
||||||
|
"listener", listener.Name,
|
||||||
|
"error", err)
|
||||||
|
return fmt.Errorf("failed to initialize listener state: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cns.listeners[listener.ChangeType] = listener
|
||||||
|
cns.logger.Info("ConfigNotification: Registered listener",
|
||||||
|
"name", listener.Name,
|
||||||
|
"type", string(listener.ChangeType))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// cleanupListener 清理监听器资源
|
||||||
|
func (cns *ConfigNotificationService) cleanupListener(listener *ConfigListener) {
|
||||||
|
listener.mu.Lock()
|
||||||
|
defer listener.mu.Unlock()
|
||||||
|
|
||||||
|
// 停止防抖定时器
|
||||||
|
if listener.timer != nil {
|
||||||
|
listener.timer.Stop()
|
||||||
|
listener.timer = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭停止通道,通知goroutine退出
|
||||||
|
if listener.stopChan != nil {
|
||||||
|
close(listener.stopChan)
|
||||||
|
listener.stopChan = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// initializeListenerState 初始化监听器状态
|
||||||
|
func (cns *ConfigNotificationService) initializeListenerState(listener *ConfigListener) error {
|
||||||
|
if listener.GetConfigFunc == nil {
|
||||||
|
return fmt.Errorf("GetConfigFunc is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取初始配置
|
||||||
|
config := listener.GetConfigFunc(cns.viper)
|
||||||
|
if config != nil {
|
||||||
|
listener.mu.Lock()
|
||||||
|
listener.lastConfig = cns.deepCopy(config)
|
||||||
|
listener.lastConfigHash = cns.computeConfigHash(config)
|
||||||
|
listener.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnregisterListener 注销配置监听器
|
||||||
|
func (cns *ConfigNotificationService) UnregisterListener(changeType ConfigChangeType) {
|
||||||
|
cns.mu.Lock()
|
||||||
|
defer cns.mu.Unlock()
|
||||||
|
|
||||||
|
if listener, exists := cns.listeners[changeType]; exists {
|
||||||
|
cns.cleanupListener(listener)
|
||||||
|
delete(cns.listeners, changeType)
|
||||||
|
cns.logger.Info("ConfigNotification: Unregistered listener", "type", string(changeType))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckConfigChanges 检查配置变更并通知相关监听器
|
||||||
|
func (cns *ConfigNotificationService) CheckConfigChanges() {
|
||||||
|
cns.mu.RLock()
|
||||||
|
listeners := make([]*ConfigListener, 0, len(cns.listeners))
|
||||||
|
for _, listener := range cns.listeners {
|
||||||
|
listeners = append(listeners, listener)
|
||||||
|
}
|
||||||
|
cns.mu.RUnlock()
|
||||||
|
|
||||||
|
// 检查每个监听器的配置变更
|
||||||
|
for _, listener := range listeners {
|
||||||
|
if hasChanges, oldConfig, newConfig := cns.checkListenerConfigChanges(listener); hasChanges {
|
||||||
|
cns.logger.Debug("ConfigNotification: Actual config change detected",
|
||||||
|
"type", string(listener.ChangeType),
|
||||||
|
"listener", listener.Name)
|
||||||
|
|
||||||
|
// 触发防抖通知,传递新旧配置
|
||||||
|
cns.debounceNotifyWithChanges(listener, oldConfig, newConfig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkListenerConfigChanges 检查单个监听器的配置变更
|
||||||
|
func (cns *ConfigNotificationService) checkListenerConfigChanges(listener *ConfigListener) (bool, interface{}, interface{}) {
|
||||||
|
if listener.GetConfigFunc == nil {
|
||||||
|
return false, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前配置
|
||||||
|
currentConfig := listener.GetConfigFunc(cns.viper)
|
||||||
|
|
||||||
|
// 读取监听器状态
|
||||||
|
listener.mu.RLock()
|
||||||
|
lastHash := listener.lastConfigHash
|
||||||
|
lastConfig := listener.lastConfig
|
||||||
|
listener.mu.RUnlock()
|
||||||
|
|
||||||
|
if currentConfig == nil {
|
||||||
|
// 配置不存在或获取失败
|
||||||
|
if lastConfig != nil {
|
||||||
|
// 配置被删除,更新状态
|
||||||
|
listener.mu.Lock()
|
||||||
|
listener.lastConfig = nil
|
||||||
|
listener.lastConfigHash = ""
|
||||||
|
listener.mu.Unlock()
|
||||||
|
return true, lastConfig, nil
|
||||||
|
}
|
||||||
|
return false, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算当前配置的哈希
|
||||||
|
currentHash := cns.computeConfigHash(currentConfig)
|
||||||
|
|
||||||
|
// 检查是否有变更
|
||||||
|
if currentHash != lastHash {
|
||||||
|
// 更新监听器状态
|
||||||
|
listener.mu.Lock()
|
||||||
|
listener.lastConfig = cns.deepCopy(currentConfig)
|
||||||
|
listener.lastConfigHash = currentHash
|
||||||
|
listener.mu.Unlock()
|
||||||
|
|
||||||
|
return true, lastConfig, currentConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// computeConfigHash 计算配置的哈希值 - 安全稳定版本
|
||||||
|
func (cns *ConfigNotificationService) computeConfigHash(config interface{}) string {
|
||||||
|
if config == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用JSON序列化确保稳定性和准确性
|
||||||
|
jsonBytes, err := json.Marshal(config)
|
||||||
|
if err != nil {
|
||||||
|
// 如果JSON序列化失败,回退到字符串表示
|
||||||
|
cns.logger.Warning("ConfigNotification: JSON marshal failed, using string representation",
|
||||||
|
"error", err)
|
||||||
|
configStr := fmt.Sprintf("%+v", config)
|
||||||
|
jsonBytes = []byte(configStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
hash := sha256.Sum256(jsonBytes)
|
||||||
|
return fmt.Sprintf("%x", hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
// deepCopy 深拷贝配置对象 - 完整实现
|
||||||
|
func (cns *ConfigNotificationService) deepCopy(src interface{}) interface{} {
|
||||||
|
if src == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 首先尝试JSON序列化方式深拷贝(适用于大多数配置对象)
|
||||||
|
jsonBytes, err := json.Marshal(src)
|
||||||
|
if err != nil {
|
||||||
|
cns.logger.Warning("ConfigNotification: JSON marshal for deep copy failed, using reflection",
|
||||||
|
"error", err)
|
||||||
|
return cns.reflectDeepCopy(src)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建同类型的新对象
|
||||||
|
srcType := reflect.TypeOf(src)
|
||||||
|
var dst interface{}
|
||||||
|
|
||||||
|
if srcType.Kind() == reflect.Ptr {
|
||||||
|
// 对于指针类型,创建指向新对象的指针
|
||||||
|
elemType := srcType.Elem()
|
||||||
|
newObj := reflect.New(elemType)
|
||||||
|
dst = newObj.Interface()
|
||||||
|
} else {
|
||||||
|
// 对于值类型,创建零值
|
||||||
|
newObj := reflect.New(srcType)
|
||||||
|
dst = newObj.Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 反序列化到新对象
|
||||||
|
err = json.Unmarshal(jsonBytes, dst)
|
||||||
|
if err != nil {
|
||||||
|
cns.logger.Warning("ConfigNotification: JSON unmarshal for deep copy failed, using reflection",
|
||||||
|
"error", err)
|
||||||
|
return cns.reflectDeepCopy(src)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果原对象不是指针类型,返回值而不是指针
|
||||||
|
if srcType.Kind() != reflect.Ptr {
|
||||||
|
return reflect.ValueOf(dst).Elem().Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
// reflectDeepCopy 使用反射进行深拷贝的备用方法
|
||||||
|
func (cns *ConfigNotificationService) reflectDeepCopy(src interface{}) interface{} {
|
||||||
|
srcValue := reflect.ValueOf(src)
|
||||||
|
return cns.reflectDeepCopyValue(srcValue).Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
// reflectDeepCopyValue 递归深拷贝reflect.Value
|
||||||
|
func (cns *ConfigNotificationService) reflectDeepCopyValue(src reflect.Value) reflect.Value {
|
||||||
|
if !src.IsValid() {
|
||||||
|
return reflect.Value{}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch src.Kind() {
|
||||||
|
case reflect.Ptr:
|
||||||
|
if src.IsNil() {
|
||||||
|
return reflect.New(src.Type()).Elem()
|
||||||
|
}
|
||||||
|
dst := reflect.New(src.Type().Elem())
|
||||||
|
dst.Elem().Set(cns.reflectDeepCopyValue(src.Elem()))
|
||||||
|
return dst
|
||||||
|
|
||||||
|
case reflect.Struct:
|
||||||
|
dst := reflect.New(src.Type()).Elem()
|
||||||
|
for i := 0; i < src.NumField(); i++ {
|
||||||
|
if dst.Field(i).CanSet() {
|
||||||
|
dst.Field(i).Set(cns.reflectDeepCopyValue(src.Field(i)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dst
|
||||||
|
|
||||||
|
case reflect.Slice:
|
||||||
|
if src.IsNil() {
|
||||||
|
return reflect.Zero(src.Type())
|
||||||
|
}
|
||||||
|
dst := reflect.MakeSlice(src.Type(), src.Len(), src.Cap())
|
||||||
|
for i := 0; i < src.Len(); i++ {
|
||||||
|
dst.Index(i).Set(cns.reflectDeepCopyValue(src.Index(i)))
|
||||||
|
}
|
||||||
|
return dst
|
||||||
|
|
||||||
|
case reflect.Map:
|
||||||
|
if src.IsNil() {
|
||||||
|
return reflect.Zero(src.Type())
|
||||||
|
}
|
||||||
|
dst := reflect.MakeMap(src.Type())
|
||||||
|
for _, key := range src.MapKeys() {
|
||||||
|
dst.SetMapIndex(key, cns.reflectDeepCopyValue(src.MapIndex(key)))
|
||||||
|
}
|
||||||
|
return dst
|
||||||
|
|
||||||
|
default:
|
||||||
|
return src
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// debounceNotifyWithChanges 防抖通知(带变更信息)- 修复内存泄漏
|
||||||
|
func (cns *ConfigNotificationService) debounceNotifyWithChanges(listener *ConfigListener, oldConfig, newConfig interface{}) {
|
||||||
|
listener.mu.Lock()
|
||||||
|
defer listener.mu.Unlock()
|
||||||
|
|
||||||
|
// 取消之前的定时器
|
||||||
|
if listener.timer != nil {
|
||||||
|
listener.timer.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建配置副本,避免在闭包中持有原始引用
|
||||||
|
oldConfigCopy := cns.deepCopy(oldConfig)
|
||||||
|
newConfigCopy := cns.deepCopy(newConfig)
|
||||||
|
|
||||||
|
// 获取监听器信息的副本
|
||||||
|
listenerName := listener.Name
|
||||||
|
changeType := listener.ChangeType
|
||||||
|
stopChan := listener.stopChan
|
||||||
|
|
||||||
|
// 设置新的防抖定时器
|
||||||
|
listener.timer = time.AfterFunc(listener.DebounceDelay, func() {
|
||||||
|
cns.logger.Debug("ConfigNotification: Executing callback after debounce",
|
||||||
|
"listener", listenerName,
|
||||||
|
"type", string(changeType))
|
||||||
|
|
||||||
|
// 启动独立的goroutine处理回调,带有超时和停止信号检查
|
||||||
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
cns.logger.Error("ConfigNotification: Callback panic recovered",
|
||||||
|
"listener", listenerName,
|
||||||
|
"type", string(changeType),
|
||||||
|
"panic", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// 检查是否收到停止信号
|
||||||
|
select {
|
||||||
|
case <-stopChan:
|
||||||
|
cns.logger.Debug("ConfigNotification: Callback cancelled due to stop signal",
|
||||||
|
"listener", listenerName)
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行回调,设置超时
|
||||||
|
callbackDone := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
callbackDone <- listener.Callback(changeType, oldConfigCopy, newConfigCopy)
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-stopChan:
|
||||||
|
cns.logger.Debug("ConfigNotification: Callback interrupted by stop signal",
|
||||||
|
"listener", listenerName)
|
||||||
|
return
|
||||||
|
case err := <-callbackDone:
|
||||||
|
if err != nil {
|
||||||
|
cns.logger.Error("ConfigNotification: Callback execution failed",
|
||||||
|
"listener", listenerName,
|
||||||
|
"type", string(changeType),
|
||||||
|
"error", err)
|
||||||
|
} else {
|
||||||
|
cns.logger.Debug("ConfigNotification: Callback executed successfully",
|
||||||
|
"listener", listenerName,
|
||||||
|
"type", string(changeType))
|
||||||
|
}
|
||||||
|
case <-time.After(30 * time.Second): // 30秒超时
|
||||||
|
cns.logger.Error("ConfigNotification: Callback execution timeout",
|
||||||
|
"listener", listenerName,
|
||||||
|
"type", string(changeType),
|
||||||
|
"timeout", "30s")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
})
|
||||||
|
|
||||||
|
cns.logger.Debug("ConfigNotification: Debounce timer scheduled",
|
||||||
|
"listener", listenerName,
|
||||||
|
"delay", listener.DebounceDelay)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup 清理所有监听器
|
||||||
|
func (cns *ConfigNotificationService) Cleanup() {
|
||||||
|
cns.mu.Lock()
|
||||||
|
defer cns.mu.Unlock()
|
||||||
|
|
||||||
|
for changeType, listener := range cns.listeners {
|
||||||
|
cns.cleanupListener(listener)
|
||||||
|
delete(cns.listeners, changeType)
|
||||||
|
}
|
||||||
|
|
||||||
|
cns.logger.Info("ConfigNotification: All listeners cleaned up")
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateHotkeyListener 创建热键配置监听器
|
||||||
|
func CreateHotkeyListener(callback func(enable bool, hotkey *models.HotkeyCombo) error) *ConfigListener {
|
||||||
|
return &ConfigListener{
|
||||||
|
Name: "HotkeyListener",
|
||||||
|
ChangeType: ConfigChangeTypeHotkey,
|
||||||
|
Callback: func(changeType ConfigChangeType, oldConfig, newConfig interface{}) error {
|
||||||
|
// 处理新配置
|
||||||
|
if newAppConfig, ok := newConfig.(*models.AppConfig); ok {
|
||||||
|
return callback(newAppConfig.General.EnableGlobalHotkey, &newAppConfig.General.GlobalHotkey)
|
||||||
|
}
|
||||||
|
// 如果新配置为空,说明配置被删除,使用默认值
|
||||||
|
if newConfig == nil {
|
||||||
|
defaultConfig := models.NewDefaultAppConfig()
|
||||||
|
return callback(defaultConfig.General.EnableGlobalHotkey, &defaultConfig.General.GlobalHotkey)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
DebounceDelay: 200 * time.Millisecond,
|
||||||
|
GetConfigFunc: func(v *viper.Viper) interface{} {
|
||||||
|
var config models.AppConfig
|
||||||
|
if err := v.Unmarshal(&config); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &config
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServiceShutdown 关闭服务
|
||||||
|
func (cns *ConfigNotificationService) ServiceShutdown() error {
|
||||||
|
cns.Cleanup()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
"voidraft/internal/models"
|
"voidraft/internal/models"
|
||||||
@@ -21,12 +20,8 @@ type ConfigService struct {
|
|||||||
logger *log.LoggerService // 日志服务
|
logger *log.LoggerService // 日志服务
|
||||||
mu sync.RWMutex // 读写锁
|
mu sync.RWMutex // 读写锁
|
||||||
|
|
||||||
// 热键配置变更回调
|
// 配置通知服务
|
||||||
hotkeyChangeCallback func(enable bool, hotkey *models.HotkeyCombo) error
|
notificationService *ConfigNotificationService
|
||||||
|
|
||||||
// 热键变更防抖
|
|
||||||
hotkeyNotificationTimer *time.Timer
|
|
||||||
hotkeyNotificationMu sync.Mutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConfigError 配置错误
|
// ConfigError 配置错误
|
||||||
@@ -52,184 +47,6 @@ func (e *ConfigError) Is(target error) bool {
|
|||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConfigLimits 配置限制定义
|
|
||||||
type ConfigLimits struct {
|
|
||||||
FontSizeMin int
|
|
||||||
FontSizeMax int
|
|
||||||
TabSizeMin int
|
|
||||||
TabSizeMax int
|
|
||||||
}
|
|
||||||
|
|
||||||
// getConfigLimits 获取配置限制
|
|
||||||
func getConfigLimits() ConfigLimits {
|
|
||||||
return ConfigLimits{
|
|
||||||
FontSizeMin: 12,
|
|
||||||
FontSizeMax: 28,
|
|
||||||
TabSizeMin: 2,
|
|
||||||
TabSizeMax: 8,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateAndFixValue 验证并修正配置值
|
|
||||||
func (cs *ConfigService) validateAndFixValue(key string, value interface{}) (interface{}, bool) {
|
|
||||||
limits := getConfigLimits()
|
|
||||||
fixed := false
|
|
||||||
|
|
||||||
switch key {
|
|
||||||
case "editing.font_size":
|
|
||||||
if intVal, ok := value.(int); ok {
|
|
||||||
if intVal < limits.FontSizeMin {
|
|
||||||
cs.logger.Warning("Config: Font size too small, fixing", "original", intVal, "fixed", limits.FontSizeMin)
|
|
||||||
return limits.FontSizeMin, true
|
|
||||||
}
|
|
||||||
if intVal > limits.FontSizeMax {
|
|
||||||
cs.logger.Warning("Config: Font size too large, fixing", "original", intVal, "fixed", limits.FontSizeMax)
|
|
||||||
return limits.FontSizeMax, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case "editing.tab_size":
|
|
||||||
if intVal, ok := value.(int); ok {
|
|
||||||
if intVal < limits.TabSizeMin {
|
|
||||||
cs.logger.Warning("Config: Tab size too small, fixing", "original", intVal, "fixed", limits.TabSizeMin)
|
|
||||||
return limits.TabSizeMin, true
|
|
||||||
}
|
|
||||||
if intVal > limits.TabSizeMax {
|
|
||||||
cs.logger.Warning("Config: Tab size too large, fixing", "original", intVal, "fixed", limits.TabSizeMax)
|
|
||||||
return limits.TabSizeMax, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case "editing.tab_type":
|
|
||||||
if strVal, ok := value.(string); ok {
|
|
||||||
validTypes := []string{string(models.TabTypeSpaces), string(models.TabTypeTab)}
|
|
||||||
isValid := false
|
|
||||||
for _, validType := range validTypes {
|
|
||||||
if strVal == validType {
|
|
||||||
isValid = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !isValid {
|
|
||||||
cs.logger.Warning("Config: Invalid tab type, fixing", "original", strVal, "fixed", string(models.TabTypeSpaces))
|
|
||||||
return string(models.TabTypeSpaces), true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case "appearance.language":
|
|
||||||
if strVal, ok := value.(string); ok {
|
|
||||||
validLanguages := []string{string(models.LangZhCN), string(models.LangEnUS)}
|
|
||||||
isValid := false
|
|
||||||
for _, validLang := range validLanguages {
|
|
||||||
if strVal == validLang {
|
|
||||||
isValid = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !isValid {
|
|
||||||
cs.logger.Warning("Config: Invalid language, fixing", "original", strVal, "fixed", string(models.LangZhCN))
|
|
||||||
return string(models.LangZhCN), true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case "editing.auto_save_delay":
|
|
||||||
if intVal, ok := value.(int); ok {
|
|
||||||
if intVal < 1000 {
|
|
||||||
cs.logger.Warning("Config: Auto save delay too small, fixing", "original", intVal, "fixed", 1000)
|
|
||||||
return 1000, true
|
|
||||||
}
|
|
||||||
if intVal > 30000 {
|
|
||||||
cs.logger.Warning("Config: Auto save delay too large, fixing", "original", intVal, "fixed", 30000)
|
|
||||||
return 30000, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case "editing.change_threshold":
|
|
||||||
if intVal, ok := value.(int); ok {
|
|
||||||
if intVal < 10 {
|
|
||||||
cs.logger.Warning("Config: Change threshold too small, fixing", "original", intVal, "fixed", 10)
|
|
||||||
return 10, true
|
|
||||||
}
|
|
||||||
if intVal > 10000 {
|
|
||||||
cs.logger.Warning("Config: Change threshold too large, fixing", "original", intVal, "fixed", 10000)
|
|
||||||
return 10000, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case "editing.min_save_interval":
|
|
||||||
if intVal, ok := value.(int); ok {
|
|
||||||
if intVal < 100 {
|
|
||||||
cs.logger.Warning("Config: Min save interval too small, fixing", "original", intVal, "fixed", 100)
|
|
||||||
return 100, true
|
|
||||||
}
|
|
||||||
if intVal > 10000 {
|
|
||||||
cs.logger.Warning("Config: Min save interval too large, fixing", "original", intVal, "fixed", 10000)
|
|
||||||
return 10000, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return value, fixed
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateAllConfig 验证并修正所有配置
|
|
||||||
func (cs *ConfigService) validateAllConfig() error {
|
|
||||||
hasChanges := false
|
|
||||||
|
|
||||||
// 获取当前配置
|
|
||||||
var config models.AppConfig
|
|
||||||
if err := cs.viper.Unmarshal(&config); err != nil {
|
|
||||||
return &ConfigError{Operation: "unmarshal_for_validation", Err: err}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证编辑器配置
|
|
||||||
if fixedValue, fixed := cs.validateAndFixValue("editing.font_size", config.Editing.FontSize); fixed {
|
|
||||||
cs.viper.Set("editing.font_size", fixedValue)
|
|
||||||
hasChanges = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if fixedValue, fixed := cs.validateAndFixValue("editing.tab_size", config.Editing.TabSize); fixed {
|
|
||||||
cs.viper.Set("editing.tab_size", fixedValue)
|
|
||||||
hasChanges = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if fixedValue, fixed := cs.validateAndFixValue("editing.tab_type", string(config.Editing.TabType)); fixed {
|
|
||||||
cs.viper.Set("editing.tab_type", fixedValue)
|
|
||||||
hasChanges = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if fixedValue, fixed := cs.validateAndFixValue("appearance.language", string(config.Appearance.Language)); fixed {
|
|
||||||
cs.viper.Set("appearance.language", fixedValue)
|
|
||||||
hasChanges = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证保存选项配置
|
|
||||||
if fixedValue, fixed := cs.validateAndFixValue("editing.auto_save_delay", config.Editing.AutoSaveDelay); fixed {
|
|
||||||
cs.viper.Set("editing.auto_save_delay", fixedValue)
|
|
||||||
hasChanges = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if fixedValue, fixed := cs.validateAndFixValue("editing.change_threshold", config.Editing.ChangeThreshold); fixed {
|
|
||||||
cs.viper.Set("editing.change_threshold", fixedValue)
|
|
||||||
hasChanges = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if fixedValue, fixed := cs.validateAndFixValue("editing.min_save_interval", config.Editing.MinSaveInterval); fixed {
|
|
||||||
cs.viper.Set("editing.min_save_interval", fixedValue)
|
|
||||||
hasChanges = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果有修正,保存配置
|
|
||||||
if hasChanges {
|
|
||||||
if err := cs.viper.WriteConfig(); err != nil {
|
|
||||||
return &ConfigError{Operation: "save_validated_config", Err: err}
|
|
||||||
}
|
|
||||||
cs.logger.Info("Config: Configuration validated and fixed")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewConfigService 创建新的配置服务实例
|
// NewConfigService 创建新的配置服务实例
|
||||||
func NewConfigService(logger *log.LoggerService) *ConfigService {
|
func NewConfigService(logger *log.LoggerService) *ConfigService {
|
||||||
// 设置日志服务
|
// 设置日志服务
|
||||||
@@ -268,16 +85,14 @@ func NewConfigService(logger *log.LoggerService) *ConfigService {
|
|||||||
logger: logger,
|
logger: logger,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 初始化配置通知服务
|
||||||
|
service.notificationService = NewConfigNotificationService(v, logger)
|
||||||
|
|
||||||
// 初始化配置
|
// 初始化配置
|
||||||
if err := service.initConfig(); err != nil {
|
if err := service.initConfig(); err != nil {
|
||||||
service.logger.Error("Config: Failed to initialize config", "error", err)
|
service.logger.Error("Config: Failed to initialize config", "error", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证并修正配置
|
|
||||||
if err := service.validateAllConfig(); err != nil {
|
|
||||||
service.logger.Error("Config: Failed to validate config", "error", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 启动配置文件监听
|
// 启动配置文件监听
|
||||||
service.startWatching()
|
service.startWatching()
|
||||||
|
|
||||||
@@ -290,7 +105,9 @@ func setDefaults(v *viper.Viper) {
|
|||||||
|
|
||||||
// 通用设置默认值
|
// 通用设置默认值
|
||||||
v.SetDefault("general.always_on_top", defaultConfig.General.AlwaysOnTop)
|
v.SetDefault("general.always_on_top", defaultConfig.General.AlwaysOnTop)
|
||||||
v.SetDefault("general.data_path", defaultConfig.General.DataPath)
|
v.SetDefault("general.default_data_path", defaultConfig.General.DefaultDataPath)
|
||||||
|
v.SetDefault("general.use_custom_data_path", defaultConfig.General.UseCustomDataPath)
|
||||||
|
v.SetDefault("general.custom_data_path", defaultConfig.General.CustomDataPath)
|
||||||
v.SetDefault("general.enable_global_hotkey", defaultConfig.General.EnableGlobalHotkey)
|
v.SetDefault("general.enable_global_hotkey", defaultConfig.General.EnableGlobalHotkey)
|
||||||
v.SetDefault("general.global_hotkey.ctrl", defaultConfig.General.GlobalHotkey.Ctrl)
|
v.SetDefault("general.global_hotkey.ctrl", defaultConfig.General.GlobalHotkey.Ctrl)
|
||||||
v.SetDefault("general.global_hotkey.shift", defaultConfig.General.GlobalHotkey.Shift)
|
v.SetDefault("general.global_hotkey.shift", defaultConfig.General.GlobalHotkey.Shift)
|
||||||
@@ -371,11 +188,9 @@ func (cs *ConfigService) startWatching() {
|
|||||||
// 设置配置变化回调
|
// 设置配置变化回调
|
||||||
cs.viper.OnConfigChange(func(e fsnotify.Event) {
|
cs.viper.OnConfigChange(func(e fsnotify.Event) {
|
||||||
cs.logger.Info("Config: Config file changed", "file", e.Name, "operation", e.Op.String())
|
cs.logger.Info("Config: Config file changed", "file", e.Name, "operation", e.Op.String())
|
||||||
// 注释掉自动更新时间戳,避免无限循环
|
|
||||||
// err := cs.Set("metadata.last_updated", time.Now())
|
// 使用配置通知服务检查所有已注册的配置变更
|
||||||
// if err != nil {
|
cs.notificationService.CheckConfigChanges()
|
||||||
// cs.logger.Error("Config: Failed to update last_updated field", "error", err)
|
|
||||||
// }
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// 启动配置文件监听
|
// 启动配置文件监听
|
||||||
@@ -400,33 +215,14 @@ func (cs *ConfigService) GetConfig() (*models.AppConfig, error) {
|
|||||||
func (cs *ConfigService) Set(key string, value interface{}) error {
|
func (cs *ConfigService) Set(key string, value interface{}) error {
|
||||||
cs.mu.Lock()
|
cs.mu.Lock()
|
||||||
defer cs.mu.Unlock()
|
defer cs.mu.Unlock()
|
||||||
|
|
||||||
// 验证并修正配置值
|
|
||||||
validatedValue, wasFixed := cs.validateAndFixValue(key, value)
|
|
||||||
|
|
||||||
// 设置验证后的值
|
// 设置验证后的值
|
||||||
cs.viper.Set(key, validatedValue)
|
cs.viper.Set(key, value)
|
||||||
|
|
||||||
// 使用 WriteConfig 写入配置文件(会触发文件监听)
|
// 使用 WriteConfig 写入配置文件(会触发文件监听)
|
||||||
if err := cs.viper.WriteConfig(); err != nil {
|
if err := cs.viper.WriteConfig(); err != nil {
|
||||||
return &ConfigError{Operation: "set_config", Err: err}
|
return &ConfigError{Operation: "set_config", Err: err}
|
||||||
}
|
}
|
||||||
|
|
||||||
if wasFixed {
|
|
||||||
cs.logger.Debug("Config: Successfully set config with validation", "key", key, "original", value, "fixed", validatedValue)
|
|
||||||
} else {
|
|
||||||
cs.logger.Debug("Config: Successfully set config", "key", key, "value", value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否是热键相关配置
|
|
||||||
if cs.isHotkeyRelatedKey(key) {
|
|
||||||
cs.logger.Info("Config: Detected hotkey configuration change", "key", key, "value", value)
|
|
||||||
// 释放锁后通知,避免死锁
|
|
||||||
go func() {
|
|
||||||
cs.notifyHotkeyChange()
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -455,75 +251,11 @@ func (cs *ConfigService) ResetConfig() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetHotkeyChangeCallback 设置热键配置变更回调
|
// SetHotkeyChangeCallback 设置热键配置变更回调
|
||||||
func (cs *ConfigService) SetHotkeyChangeCallback(callback func(enable bool, hotkey *models.HotkeyCombo) error) {
|
func (cs *ConfigService) SetHotkeyChangeCallback(callback func(enable bool, hotkey *models.HotkeyCombo) error) error {
|
||||||
cs.mu.Lock()
|
cs.mu.Lock()
|
||||||
defer cs.mu.Unlock()
|
defer cs.mu.Unlock()
|
||||||
cs.hotkeyChangeCallback = callback
|
|
||||||
}
|
// 创建热键监听器并注册
|
||||||
|
hotkeyListener := CreateHotkeyListener(callback)
|
||||||
// notifyHotkeyChange 通知热键配置变更
|
return cs.notificationService.RegisterListener(hotkeyListener)
|
||||||
func (cs *ConfigService) notifyHotkeyChange() {
|
|
||||||
if cs.hotkeyChangeCallback == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
cs.hotkeyNotificationMu.Lock()
|
|
||||||
defer cs.hotkeyNotificationMu.Unlock()
|
|
||||||
|
|
||||||
// 取消之前的定时器
|
|
||||||
if cs.hotkeyNotificationTimer != nil {
|
|
||||||
cs.hotkeyNotificationTimer.Stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置新的防抖定时器(200ms延迟)
|
|
||||||
cs.hotkeyNotificationTimer = time.AfterFunc(200*time.Millisecond, func() {
|
|
||||||
cs.logger.Debug("Config: Executing hotkey change notification after debounce")
|
|
||||||
|
|
||||||
// 获取当前热键配置
|
|
||||||
config, err := cs.GetConfig()
|
|
||||||
if err != nil {
|
|
||||||
cs.logger.Error("Config: Failed to get config for hotkey notification", "error", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
cs.logger.Debug("Config: Notifying hotkey service of configuration change",
|
|
||||||
"enable", config.General.EnableGlobalHotkey,
|
|
||||||
"ctrl", config.General.GlobalHotkey.Ctrl,
|
|
||||||
"shift", config.General.GlobalHotkey.Shift,
|
|
||||||
"alt", config.General.GlobalHotkey.Alt,
|
|
||||||
"win", config.General.GlobalHotkey.Win,
|
|
||||||
"key", config.General.GlobalHotkey.Key)
|
|
||||||
|
|
||||||
// 异步通知热键服务
|
|
||||||
go func() {
|
|
||||||
err := cs.hotkeyChangeCallback(config.General.EnableGlobalHotkey, &config.General.GlobalHotkey)
|
|
||||||
if err != nil {
|
|
||||||
cs.logger.Error("Config: Failed to notify hotkey change", "error", err)
|
|
||||||
} else {
|
|
||||||
cs.logger.Debug("Config: Successfully notified hotkey change")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
})
|
|
||||||
|
|
||||||
cs.logger.Debug("Config: Hotkey change notification scheduled with debounce")
|
|
||||||
}
|
|
||||||
|
|
||||||
// isHotkeyRelatedKey 检查是否是热键相关的配置键
|
|
||||||
func (cs *ConfigService) isHotkeyRelatedKey(key string) bool {
|
|
||||||
hotkeyKeys := []string{
|
|
||||||
"general.enable_global_hotkey",
|
|
||||||
"general.global_hotkey",
|
|
||||||
"general.global_hotkey.ctrl",
|
|
||||||
"general.global_hotkey.shift",
|
|
||||||
"general.global_hotkey.alt",
|
|
||||||
"general.global_hotkey.win",
|
|
||||||
"general.global_hotkey.key",
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, hotkeyKey := range hotkeyKeys {
|
|
||||||
if strings.HasPrefix(key, hotkeyKey) || key == hotkeyKey {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -304,8 +304,16 @@ func (ds *DocumentService) ensureDocumentsDir() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取实际的数据路径(如果启用自定义路径则使用自定义路径,否则使用默认路径)
|
||||||
|
var dataPath string
|
||||||
|
if config.General.UseCustomDataPath && config.General.CustomDataPath != "" {
|
||||||
|
dataPath = config.General.CustomDataPath
|
||||||
|
} else {
|
||||||
|
dataPath = config.General.DefaultDataPath
|
||||||
|
}
|
||||||
|
|
||||||
// 创建文档目录
|
// 创建文档目录
|
||||||
docsDir := filepath.Join(config.General.DataPath, "docs")
|
docsDir := filepath.Join(dataPath, "docs")
|
||||||
err = os.MkdirAll(docsDir, 0755)
|
err = os.MkdirAll(docsDir, 0755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -320,7 +328,16 @@ func (ds *DocumentService) getDocumentsDir() (string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return filepath.Join(config.General.DataPath, "docs"), nil
|
|
||||||
|
// 获取实际的数据路径(如果启用自定义路径则使用自定义路径,否则使用默认路径)
|
||||||
|
var dataPath string
|
||||||
|
if config.General.UseCustomDataPath && config.General.CustomDataPath != "" {
|
||||||
|
dataPath = config.General.CustomDataPath
|
||||||
|
} else {
|
||||||
|
dataPath = config.General.DefaultDataPath
|
||||||
|
}
|
||||||
|
|
||||||
|
return filepath.Join(dataPath, "docs"), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getDefaultDocumentPath 获取默认文档路径
|
// getDefaultDocumentPath 获取默认文档路径
|
||||||
|
|||||||
@@ -33,13 +33,17 @@ func NewServiceManager() *ServiceManager {
|
|||||||
// 初始化热键服务
|
// 初始化热键服务
|
||||||
hotkeyService := NewHotkeyService(configService, logger)
|
hotkeyService := NewHotkeyService(configService, logger)
|
||||||
|
|
||||||
// 设置热键配置变更回调
|
// 使用新的配置通知系统设置热键配置变更监听
|
||||||
configService.SetHotkeyChangeCallback(func(enable bool, hotkey *models.HotkeyCombo) error {
|
err := configService.SetHotkeyChangeCallback(func(enable bool, hotkey *models.HotkeyCombo) error {
|
||||||
return hotkeyService.UpdateHotkey(enable, hotkey)
|
return hotkeyService.UpdateHotkey(enable, hotkey)
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Failed to set hotkey change callback", "error", err)
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
// 初始化文档服务
|
// 初始化文档服务
|
||||||
err := documentService.Initialize()
|
err = documentService.Initialize()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Failed to initialize document service", "error", err)
|
logger.Error("Failed to initialize document service", "error", err)
|
||||||
panic(err)
|
panic(err)
|
||||||
|
|||||||
Reference in New Issue
Block a user