♻️ Refactor and clean up the code
This commit is contained in:
@@ -132,11 +132,19 @@ export class ConfigMetadata {
|
|||||||
*/
|
*/
|
||||||
"lastUpdated": string;
|
"lastUpdated": string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置版本号
|
||||||
|
*/
|
||||||
|
"version": string;
|
||||||
|
|
||||||
/** Creates a new ConfigMetadata instance. */
|
/** Creates a new ConfigMetadata instance. */
|
||||||
constructor($$source: Partial<ConfigMetadata> = {}) {
|
constructor($$source: Partial<ConfigMetadata> = {}) {
|
||||||
if (!("lastUpdated" in $$source)) {
|
if (!("lastUpdated" in $$source)) {
|
||||||
this["lastUpdated"] = "";
|
this["lastUpdated"] = "";
|
||||||
}
|
}
|
||||||
|
if (!("version" in $$source)) {
|
||||||
|
this["version"] = "";
|
||||||
|
}
|
||||||
|
|
||||||
Object.assign(this, $$source);
|
Object.assign(this, $$source);
|
||||||
}
|
}
|
||||||
@@ -978,7 +986,7 @@ export class UpdatesConfig {
|
|||||||
/**
|
/**
|
||||||
* 当前版本号
|
* 当前版本号
|
||||||
*/
|
*/
|
||||||
"Version": string;
|
"version": string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否自动更新
|
* 是否自动更新
|
||||||
@@ -992,8 +1000,8 @@ export class UpdatesConfig {
|
|||||||
|
|
||||||
/** Creates a new UpdatesConfig instance. */
|
/** Creates a new UpdatesConfig instance. */
|
||||||
constructor($$source: Partial<UpdatesConfig> = {}) {
|
constructor($$source: Partial<UpdatesConfig> = {}) {
|
||||||
if (!("Version" in $$source)) {
|
if (!("version" in $$source)) {
|
||||||
this["Version"] = "";
|
this["version"] = "";
|
||||||
}
|
}
|
||||||
if (!("autoUpdate" in $$source)) {
|
if (!("autoUpdate" in $$source)) {
|
||||||
this["autoUpdate"] = false;
|
this["autoUpdate"] = false;
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
// This file is automatically generated. DO NOT EDIT
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ConfigService 提供基于 Viper 的配置管理功能
|
* ConfigService 应用配置服务
|
||||||
* @module
|
* @module
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -42,6 +42,14 @@ export function ResetConfig(): Promise<void> & { cancel(): void } {
|
|||||||
return $resultPromise;
|
return $resultPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ServiceShutdown 关闭服务
|
||||||
|
*/
|
||||||
|
export function ServiceShutdown(): Promise<void> & { cancel(): void } {
|
||||||
|
let $resultPromise = $Call.ByID(3963562361) as any;
|
||||||
|
return $resultPromise;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set 设置配置项
|
* Set 设置配置项
|
||||||
*/
|
*/
|
||||||
|
@@ -39,10 +39,10 @@ export function GetKeyBindingConfig(): Promise<models$0.KeyBindingConfig | null>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shutdown 关闭服务
|
* ServiceShutdown 关闭服务
|
||||||
*/
|
*/
|
||||||
export function Shutdown(): Promise<void> & { cancel(): void } {
|
export function ServiceShutdown(): Promise<void> & { cancel(): void } {
|
||||||
let $resultPromise = $Call.ByID(3046465148) as any;
|
let $resultPromise = $Call.ByID(1610182855) as any;
|
||||||
return $resultPromise;
|
return $resultPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -3,7 +3,6 @@ import {useI18n} from 'vue-i18n';
|
|||||||
import {onMounted, onUnmounted, ref, watch} from 'vue';
|
import {onMounted, onUnmounted, ref, watch} from 'vue';
|
||||||
import {useConfigStore} from '@/stores/configStore';
|
import {useConfigStore} from '@/stores/configStore';
|
||||||
import {useEditorStore} from '@/stores/editorStore';
|
import {useEditorStore} from '@/stores/editorStore';
|
||||||
import {useErrorHandler} from '@/utils/errorHandler';
|
|
||||||
import * as runtime from '@wailsio/runtime';
|
import * as runtime from '@wailsio/runtime';
|
||||||
import {useRouter} from 'vue-router';
|
import {useRouter} from 'vue-router';
|
||||||
import BlockLanguageSelector from './BlockLanguageSelector.vue';
|
import BlockLanguageSelector from './BlockLanguageSelector.vue';
|
||||||
@@ -12,25 +11,20 @@ import {getLanguage} from '@/views/editor/extensions/codeblock/lang-parser/langu
|
|||||||
|
|
||||||
const editorStore = useEditorStore();
|
const editorStore = useEditorStore();
|
||||||
const configStore = useConfigStore();
|
const configStore = useConfigStore();
|
||||||
const {safeCall} = useErrorHandler();
|
|
||||||
const {t} = useI18n();
|
const {t} = useI18n();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
|
||||||
// 设置窗口置顶
|
// 设置窗口置顶
|
||||||
const setWindowAlwaysOnTop = async (isTop: boolean) => {
|
const setWindowAlwaysOnTop = async (isTop: boolean) => {
|
||||||
await safeCall(async () => {
|
await runtime.Window.SetAlwaysOnTop(isTop);
|
||||||
await runtime.Window.SetAlwaysOnTop(isTop);
|
|
||||||
}, 'window.setTopFailed');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 切换窗口置顶
|
// 切换窗口置顶
|
||||||
const toggleAlwaysOnTop = async () => {
|
const toggleAlwaysOnTop = async () => {
|
||||||
await safeCall(async () => {
|
await configStore.toggleAlwaysOnTop();
|
||||||
await configStore.toggleAlwaysOnTop();
|
// 使用Window.SetAlwaysOnTop方法设置窗口置顶状态
|
||||||
// 使用Window.SetAlwaysOnTop方法设置窗口置顶状态
|
await runtime.Window.SetAlwaysOnTop(configStore.config.general.alwaysOnTop);
|
||||||
await runtime.Window.SetAlwaysOnTop(configStore.config.general.alwaysOnTop);
|
|
||||||
}, 'config.alwaysOnTopFailed');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 跳转到设置页面
|
// 跳转到设置页面
|
||||||
|
@@ -11,70 +11,19 @@ export default {
|
|||||||
characters: 'Ch',
|
characters: 'Ch',
|
||||||
selected: 'Sel'
|
selected: 'Sel'
|
||||||
},
|
},
|
||||||
fontSize: 'Font Size',
|
|
||||||
fontSizeTooltip: 'Font Size (Ctrl+wheel to adjust)',
|
fontSizeTooltip: 'Font Size (Ctrl+wheel to adjust)',
|
||||||
tabLabel: 'Tab',
|
|
||||||
tabType: {
|
|
||||||
spaces: 'Spaces',
|
|
||||||
tab: 'Tab'
|
|
||||||
},
|
|
||||||
encoding: 'UTF-8',
|
|
||||||
settings: 'Settings',
|
settings: 'Settings',
|
||||||
alwaysOnTop: 'Always on Top',
|
alwaysOnTop: 'Always on Top',
|
||||||
blockLanguage: 'Block Language',
|
blockLanguage: 'Block Language',
|
||||||
searchLanguage: 'Search language...',
|
searchLanguage: 'Search language...',
|
||||||
noLanguageFound: 'No language found',
|
noLanguageFound: 'No language found',
|
||||||
autoDetected: 'Auto-detected',
|
|
||||||
formatHint: 'Current block supports formatting, use Ctrl+Shift+F shortcut for formatting',
|
formatHint: 'Current block supports formatting, use Ctrl+Shift+F shortcut for formatting',
|
||||||
},
|
},
|
||||||
config: {
|
|
||||||
loadSuccess: 'Configuration loaded successfully',
|
|
||||||
loadFailed: 'Failed to load configuration',
|
|
||||||
saveSuccess: 'Configuration saved',
|
|
||||||
saveFailed: 'Failed to save configuration',
|
|
||||||
resetSuccess: 'Configuration reset to defaults',
|
|
||||||
resetFailed: 'Failed to reset configuration',
|
|
||||||
fontSizeFixed: 'Font 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',
|
|
||||||
alwaysOnTopFailed: 'Failed to set window always on top',
|
|
||||||
alwaysOnTopSuccess: 'Window always on top status updated',
|
|
||||||
languageChanged: 'Language setting updated',
|
|
||||||
languageChangeFailed: 'Failed to update language setting',
|
|
||||||
themeChanged: 'Theme setting updated',
|
|
||||||
themeChangeFailed: 'Failed to update theme setting',
|
|
||||||
systemThemeChanged: 'System theme setting updated',
|
|
||||||
systemThemeChangeFailed: 'Failed to update system theme setting',
|
|
||||||
startupSuccess: 'Startup setting updated',
|
|
||||||
startupFailed: 'Failed to update startup setting'
|
|
||||||
},
|
|
||||||
languages: {
|
|
||||||
'zh-CN': '简体中文',
|
|
||||||
'en-US': 'English'
|
|
||||||
},
|
|
||||||
systemTheme: {
|
systemTheme: {
|
||||||
dark: 'Dark',
|
dark: 'Dark',
|
||||||
light: 'Light',
|
light: 'Light',
|
||||||
auto: 'Follow System'
|
auto: 'Follow System'
|
||||||
},
|
},
|
||||||
document: {
|
|
||||||
loadSuccess: 'Document loaded successfully',
|
|
||||||
loadFailed: 'Failed to load document',
|
|
||||||
saveSuccess: 'Document saved successfully',
|
|
||||||
saveFailed: 'Failed to save document',
|
|
||||||
manualSaveSuccess: 'Manually saved successfully',
|
|
||||||
settings: {
|
|
||||||
loadFailed: 'Failed to load save settings',
|
|
||||||
saveSuccess: 'Save settings updated',
|
|
||||||
saveFailed: 'Failed to update save settings'
|
|
||||||
},
|
|
||||||
},
|
|
||||||
migration: {
|
|
||||||
started: 'Starting data migration',
|
|
||||||
migrating: 'Migrating',
|
|
||||||
completed: 'Migration Completed',
|
|
||||||
failed: 'Migration Failed'
|
|
||||||
},
|
|
||||||
keybindings: {
|
keybindings: {
|
||||||
headers: {
|
headers: {
|
||||||
shortcut: 'Shortcut',
|
shortcut: 'Shortcut',
|
||||||
@@ -88,10 +37,7 @@ export default {
|
|||||||
searchToggleWord: 'Toggle whole word matching',
|
searchToggleWord: 'Toggle whole word matching',
|
||||||
searchToggleRegex: 'Toggle regular expression matching',
|
searchToggleRegex: 'Toggle regular expression matching',
|
||||||
searchShowReplace: 'Show replace functionality',
|
searchShowReplace: 'Show replace functionality',
|
||||||
searchFindNext: 'Find next match',
|
|
||||||
searchFindPrev: 'Find previous match',
|
|
||||||
searchReplaceAll: 'Replace all matches',
|
searchReplaceAll: 'Replace all matches',
|
||||||
searchSelectAll: 'Select all content',
|
|
||||||
blockSelectAll: 'Select all in block',
|
blockSelectAll: 'Select all in block',
|
||||||
blockAddAfterCurrent: 'Add new block after current',
|
blockAddAfterCurrent: 'Add new block after current',
|
||||||
blockAddAfterLast: 'Add new block at end',
|
blockAddAfterLast: 'Add new block at end',
|
||||||
@@ -125,7 +71,6 @@ export default {
|
|||||||
selectSyntaxRight: 'Select syntax right',
|
selectSyntaxRight: 'Select syntax right',
|
||||||
copyLineUp: 'Copy line up',
|
copyLineUp: 'Copy line up',
|
||||||
copyLineDown: 'Copy line down',
|
copyLineDown: 'Copy line down',
|
||||||
simplifySelection: 'Simplify selection',
|
|
||||||
insertBlankLine: 'Insert blank line',
|
insertBlankLine: 'Insert blank line',
|
||||||
selectLine: 'Select line',
|
selectLine: 'Select line',
|
||||||
selectParentSyntax: 'Select parent syntax',
|
selectParentSyntax: 'Select parent syntax',
|
||||||
@@ -151,10 +96,7 @@ export default {
|
|||||||
appearance: 'Appearance',
|
appearance: 'Appearance',
|
||||||
keyBindings: 'Key Bindings',
|
keyBindings: 'Key Bindings',
|
||||||
updates: 'Updates',
|
updates: 'Updates',
|
||||||
comingSoon: 'Coming Soon...',
|
|
||||||
save: 'Save',
|
|
||||||
reset: 'Reset',
|
reset: 'Reset',
|
||||||
cancel: 'Cancel',
|
|
||||||
dangerZone: 'Danger Zone',
|
dangerZone: 'Danger Zone',
|
||||||
resetAllSettings: 'Reset All Settings',
|
resetAllSettings: 'Reset All Settings',
|
||||||
confirmReset: 'Click again to confirm reset',
|
confirmReset: 'Click again to confirm reset',
|
||||||
@@ -171,7 +113,6 @@ export default {
|
|||||||
clickToSelectPath: 'Click to select path',
|
clickToSelectPath: 'Click to select path',
|
||||||
resetDefault: 'Reset Default',
|
resetDefault: 'Reset Default',
|
||||||
resetToDefaultPath: 'Reset to default path',
|
resetToDefaultPath: 'Reset to default path',
|
||||||
restartRequiredForDataPath: 'Restart required',
|
|
||||||
fontSize: 'Font Size',
|
fontSize: 'Font Size',
|
||||||
fontSizeDescription: 'Editor font size',
|
fontSizeDescription: 'Editor font size',
|
||||||
fontSettings: 'Font Settings',
|
fontSettings: 'Font Settings',
|
||||||
@@ -189,15 +130,7 @@ export default {
|
|||||||
enableTabIndent: 'Enable Tab Indent',
|
enableTabIndent: 'Enable Tab Indent',
|
||||||
language: 'Interface Language',
|
language: 'Interface Language',
|
||||||
systemTheme: 'System Theme',
|
systemTheme: 'System Theme',
|
||||||
theme: 'Editor Theme',
|
|
||||||
themeDescription: 'Choose editor theme',
|
|
||||||
restartRequired: '(Restart required)',
|
|
||||||
saveOptions: 'Save Options',
|
saveOptions: 'Save Options',
|
||||||
autoSaveDelay: 'Auto Save Delay (ms)',
|
autoSaveDelay: 'Auto Save Delay (ms)'
|
||||||
selectDirectoryFailed: 'Failed to select directory',
|
|
||||||
validation: {
|
|
||||||
customPathRequired: 'A valid directory must be selected when enabling custom path',
|
|
||||||
customPathAutoDisabled: 'Custom data path has been automatically disabled due to no valid directory selected'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
@@ -11,70 +11,19 @@ export default {
|
|||||||
characters: 'Ch',
|
characters: 'Ch',
|
||||||
selected: 'Sel'
|
selected: 'Sel'
|
||||||
},
|
},
|
||||||
fontSize: '字体大小',
|
|
||||||
fontSizeTooltip: '字体大小 (Ctrl+滚轮调整)',
|
fontSizeTooltip: '字体大小 (Ctrl+滚轮调整)',
|
||||||
tabLabel: 'Tab',
|
|
||||||
tabType: {
|
|
||||||
spaces: '空格',
|
|
||||||
tab: '制表符'
|
|
||||||
},
|
|
||||||
encoding: 'UTF-8',
|
|
||||||
settings: '设置',
|
settings: '设置',
|
||||||
alwaysOnTop: '窗口置顶',
|
alwaysOnTop: '窗口置顶',
|
||||||
blockLanguage: '块语言',
|
blockLanguage: '块语言',
|
||||||
searchLanguage: '搜索语言...',
|
searchLanguage: '搜索语言...',
|
||||||
noLanguageFound: '未找到匹配的语言',
|
noLanguageFound: '未找到匹配的语言',
|
||||||
autoDetected: '自动检测',
|
|
||||||
formatHint: '当前区块支持格式化,使用 Ctrl+Shift+F 进行格式化',
|
formatHint: '当前区块支持格式化,使用 Ctrl+Shift+F 进行格式化',
|
||||||
},
|
},
|
||||||
config: {
|
|
||||||
loadSuccess: '配置加载成功',
|
|
||||||
loadFailed: '配置加载失败',
|
|
||||||
saveSuccess: '配置已保存',
|
|
||||||
saveFailed: '配置保存失败',
|
|
||||||
resetSuccess: '配置已重置为默认值',
|
|
||||||
resetFailed: '重置配置失败',
|
|
||||||
fontSizeFixed: '字体大小值({value})已被修正为{fixed}',
|
|
||||||
tabSizeFixed: 'Tab大小值({value})已被修正为{fixed}',
|
|
||||||
tabTypeFixed: 'Tab类型({value})不合法,已修正为空格',
|
|
||||||
alwaysOnTopFailed: '无法设置窗口置顶状态',
|
|
||||||
alwaysOnTopSuccess: '窗口置顶状态已更新',
|
|
||||||
languageChanged: '语言设置已更新',
|
|
||||||
languageChangeFailed: '语言设置更新失败',
|
|
||||||
themeChanged: '主题设置已更新',
|
|
||||||
themeChangeFailed: '主题设置更新失败',
|
|
||||||
systemThemeChanged: '系统主题设置已更新',
|
|
||||||
systemThemeChangeFailed: '系统主题设置更新失败',
|
|
||||||
startupSuccess: '开机启动设置已更新',
|
|
||||||
startupFailed: '开机启动设置失败'
|
|
||||||
},
|
|
||||||
languages: {
|
|
||||||
'zh-CN': '简体中文',
|
|
||||||
'en-US': 'English'
|
|
||||||
},
|
|
||||||
systemTheme: {
|
systemTheme: {
|
||||||
dark: '深色',
|
dark: '深色',
|
||||||
light: '浅色',
|
light: '浅色',
|
||||||
auto: '跟随系统'
|
auto: '跟随系统'
|
||||||
},
|
},
|
||||||
document: {
|
|
||||||
loadSuccess: '文档加载成功',
|
|
||||||
loadFailed: '文档加载失败',
|
|
||||||
saveSuccess: '文档保存成功',
|
|
||||||
saveFailed: '文档保存失败',
|
|
||||||
manualSaveSuccess: '手动保存成功',
|
|
||||||
settings: {
|
|
||||||
loadFailed: '加载保存设置失败',
|
|
||||||
saveSuccess: '保存设置已更新',
|
|
||||||
saveFailed: '保存设置更新失败'
|
|
||||||
},
|
|
||||||
},
|
|
||||||
migration: {
|
|
||||||
started: '开始迁移数据',
|
|
||||||
migrating: '迁移中',
|
|
||||||
completed: '迁移已完成',
|
|
||||||
failed: '迁移失败'
|
|
||||||
},
|
|
||||||
keybindings: {
|
keybindings: {
|
||||||
headers: {
|
headers: {
|
||||||
shortcut: '快捷键',
|
shortcut: '快捷键',
|
||||||
@@ -88,10 +37,7 @@ export default {
|
|||||||
searchToggleWord: '切换整词匹配',
|
searchToggleWord: '切换整词匹配',
|
||||||
searchToggleRegex: '切换正则表达式匹配',
|
searchToggleRegex: '切换正则表达式匹配',
|
||||||
searchShowReplace: '显示替换功能',
|
searchShowReplace: '显示替换功能',
|
||||||
searchFindNext: '查找下一个匹配项',
|
|
||||||
searchFindPrev: '查找上一个匹配项',
|
|
||||||
searchReplaceAll: '替换全部匹配项',
|
searchReplaceAll: '替换全部匹配项',
|
||||||
searchSelectAll: '选择全部内容',
|
|
||||||
blockSelectAll: '块内选择全部',
|
blockSelectAll: '块内选择全部',
|
||||||
blockAddAfterCurrent: '在当前块后添加新块',
|
blockAddAfterCurrent: '在当前块后添加新块',
|
||||||
blockAddAfterLast: '在最后添加新块',
|
blockAddAfterLast: '在最后添加新块',
|
||||||
@@ -125,7 +71,6 @@ export default {
|
|||||||
selectSyntaxRight: '按语法选择右侧',
|
selectSyntaxRight: '按语法选择右侧',
|
||||||
copyLineUp: '向上复制行',
|
copyLineUp: '向上复制行',
|
||||||
copyLineDown: '向下复制行',
|
copyLineDown: '向下复制行',
|
||||||
simplifySelection: '简化选择',
|
|
||||||
insertBlankLine: '插入空行',
|
insertBlankLine: '插入空行',
|
||||||
selectLine: '选择行',
|
selectLine: '选择行',
|
||||||
selectParentSyntax: '选择父级语法',
|
selectParentSyntax: '选择父级语法',
|
||||||
@@ -151,10 +96,7 @@ export default {
|
|||||||
appearance: '外观',
|
appearance: '外观',
|
||||||
keyBindings: '快捷键',
|
keyBindings: '快捷键',
|
||||||
updates: '更新',
|
updates: '更新',
|
||||||
comingSoon: '即将推出...',
|
|
||||||
save: '保存',
|
|
||||||
reset: '重置',
|
reset: '重置',
|
||||||
cancel: '取消',
|
|
||||||
dangerZone: '危险操作',
|
dangerZone: '危险操作',
|
||||||
resetAllSettings: '重置所有设置',
|
resetAllSettings: '重置所有设置',
|
||||||
confirmReset: '再次点击确认重置',
|
confirmReset: '再次点击确认重置',
|
||||||
@@ -171,7 +113,6 @@ export default {
|
|||||||
clickToSelectPath: '点击选择路径',
|
clickToSelectPath: '点击选择路径',
|
||||||
resetDefault: '恢复默认',
|
resetDefault: '恢复默认',
|
||||||
resetToDefaultPath: '恢复为默认路径',
|
resetToDefaultPath: '恢复为默认路径',
|
||||||
restartRequiredForDataPath: '需要重启应用程序',
|
|
||||||
fontSize: '字体大小',
|
fontSize: '字体大小',
|
||||||
fontSizeDescription: '编辑器字体大小',
|
fontSizeDescription: '编辑器字体大小',
|
||||||
fontSettings: '字体设置',
|
fontSettings: '字体设置',
|
||||||
@@ -189,15 +130,7 @@ export default {
|
|||||||
enableTabIndent: '启用 Tab 缩进',
|
enableTabIndent: '启用 Tab 缩进',
|
||||||
language: '界面语言',
|
language: '界面语言',
|
||||||
systemTheme: '系统主题',
|
systemTheme: '系统主题',
|
||||||
theme: '编辑器主题',
|
|
||||||
themeDescription: '选择编辑器主题',
|
|
||||||
restartRequired: '(需要重启)',
|
|
||||||
saveOptions: '保存选项',
|
saveOptions: '保存选项',
|
||||||
autoSaveDelay: '自动保存延迟(毫秒)',
|
autoSaveDelay: '自动保存延迟(毫秒)'
|
||||||
selectDirectoryFailed: '选择目录失败',
|
|
||||||
validation: {
|
|
||||||
customPathRequired: '启用自定义路径时必须选择一个有效的目录',
|
|
||||||
customPathAutoDisabled: '由于未选择有效目录,自定义数据路径已自动关闭'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
@@ -1,6 +1,6 @@
|
|||||||
import {defineStore} from 'pinia';
|
import {defineStore} from 'pinia';
|
||||||
import {computed, reactive} from 'vue';
|
import {computed, reactive} from 'vue';
|
||||||
import {ConfigService, StartupService} from '../../bindings/voidraft/internal/services';
|
import {ConfigService, StartupService} from '@/../bindings/voidraft/internal/services';
|
||||||
import {
|
import {
|
||||||
AppConfig,
|
AppConfig,
|
||||||
AppearanceConfig,
|
AppearanceConfig,
|
||||||
@@ -11,7 +11,6 @@ import {
|
|||||||
TabType,
|
TabType,
|
||||||
} from '@/../bindings/voidraft/internal/models/models';
|
} from '@/../bindings/voidraft/internal/models/models';
|
||||||
import {useI18n} from 'vue-i18n';
|
import {useI18n} from 'vue-i18n';
|
||||||
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';
|
import * as runtime from '@wailsio/runtime';
|
||||||
@@ -144,11 +143,12 @@ const DEFAULT_CONFIG: AppConfig = {
|
|||||||
systemTheme: SystemThemeType.SystemThemeAuto
|
systemTheme: SystemThemeType.SystemThemeAuto
|
||||||
},
|
},
|
||||||
updates: {
|
updates: {
|
||||||
Version: "1.0.0",
|
version: "1.0.0",
|
||||||
autoUpdate: true,
|
autoUpdate: true,
|
||||||
betaChannel: false
|
betaChannel: false
|
||||||
},
|
},
|
||||||
metadata: {
|
metadata: {
|
||||||
|
version: '1.0.0',
|
||||||
lastUpdated: new Date().toString()
|
lastUpdated: new Date().toString()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -156,7 +156,6 @@ const DEFAULT_CONFIG: AppConfig = {
|
|||||||
|
|
||||||
export const useConfigStore = defineStore('config', () => {
|
export const useConfigStore = defineStore('config', () => {
|
||||||
const {locale} = useI18n();
|
const {locale} = useI18n();
|
||||||
const {safeCall} = useErrorHandler();
|
|
||||||
|
|
||||||
// 响应式状态
|
// 响应式状态
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
@@ -250,26 +249,26 @@ export const useConfigStore = defineStore('config', () => {
|
|||||||
const clamp = (value: number) => ConfigUtils.clamp(value, limit.min, limit.max);
|
const clamp = (value: number) => ConfigUtils.clamp(value, limit.min, limit.max);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
increase: () => safeCall(() => updateEditingConfig(key, clamp(state.config.editing[key] + 1)), 'config.saveFailed', 'config.saveSuccess'),
|
increase: async () => await updateEditingConfig(key, clamp(state.config.editing[key] + 1)),
|
||||||
decrease: () => safeCall(() => updateEditingConfig(key, clamp(state.config.editing[key] - 1)), 'config.saveFailed', 'config.saveSuccess'),
|
decrease: async () => await updateEditingConfig(key, clamp(state.config.editing[key] - 1)),
|
||||||
set: (value: number) => safeCall(() => updateEditingConfig(key, clamp(value)), 'config.saveFailed', 'config.saveSuccess'),
|
set: async (value: number) => await updateEditingConfig(key, clamp(value)),
|
||||||
reset: () => safeCall(() => updateEditingConfig(key, limit.default), 'config.saveFailed', 'config.saveSuccess')
|
reset: async () => await updateEditingConfig(key, limit.default)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// 通用布尔值切换器
|
// 通用布尔值切换器
|
||||||
const createGeneralToggler = <T extends keyof GeneralConfig>(key: T) =>
|
const createGeneralToggler = <T extends keyof GeneralConfig>(key: T) =>
|
||||||
() => safeCall(() => updateGeneralConfig(key, !state.config.general[key] as GeneralConfig[T]), 'config.saveFailed', 'config.saveSuccess');
|
async () => await updateGeneralConfig(key, !state.config.general[key] as GeneralConfig[T]);
|
||||||
|
|
||||||
const createEditingToggler = <T extends keyof EditingConfig>(key: T) =>
|
const createEditingToggler = <T extends keyof EditingConfig>(key: T) =>
|
||||||
() => safeCall(() => updateEditingConfig(key, !state.config.editing[key] as EditingConfig[T]), 'config.saveFailed', 'config.saveSuccess');
|
async () => await updateEditingConfig(key, !state.config.editing[key] as EditingConfig[T]);
|
||||||
|
|
||||||
// 枚举值切换器
|
// 枚举值切换器
|
||||||
const createEnumToggler = <T extends TabType>(key: 'tabType', values: readonly T[]) =>
|
const createEnumToggler = <T extends TabType>(key: 'tabType', values: readonly T[]) =>
|
||||||
() => {
|
async () => {
|
||||||
const currentIndex = values.indexOf(state.config.editing[key] as T);
|
const currentIndex = values.indexOf(state.config.editing[key] as T);
|
||||||
const nextIndex = (currentIndex + 1) % values.length;
|
const nextIndex = (currentIndex + 1) % values.length;
|
||||||
return safeCall(() => updateEditingConfig(key, values[nextIndex]), 'config.saveFailed', 'config.saveSuccess');
|
return await updateEditingConfig(key, values[nextIndex]);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 重置配置
|
// 重置配置
|
||||||
@@ -278,16 +277,12 @@ export const useConfigStore = defineStore('config', () => {
|
|||||||
|
|
||||||
state.isLoading = true;
|
state.isLoading = true;
|
||||||
try {
|
try {
|
||||||
// 调用后端重置配置
|
|
||||||
await safeCall(() => ConfigService.ResetConfig(), 'config.resetFailed', 'config.resetSuccess');
|
|
||||||
|
|
||||||
// 立即重新加载后端配置以确保前端状态同步
|
await ConfigService.ResetConfig()
|
||||||
await safeCall(async () => {
|
const appConfig = await ConfigService.GetConfig();
|
||||||
const appConfig = await ConfigService.GetConfig();
|
if (appConfig) {
|
||||||
if (appConfig) {
|
state.config = JSON.parse(JSON.stringify(appConfig)) as AppConfig;
|
||||||
state.config = JSON.parse(JSON.stringify(appConfig)) as AppConfig;
|
}
|
||||||
}
|
|
||||||
}, 'config.loadFailed', 'config.loadSuccess');
|
|
||||||
} finally {
|
} finally {
|
||||||
state.isLoading = false;
|
state.isLoading = false;
|
||||||
}
|
}
|
||||||
@@ -295,20 +290,16 @@ export const useConfigStore = defineStore('config', () => {
|
|||||||
|
|
||||||
// 语言设置方法
|
// 语言设置方法
|
||||||
const setLanguage = async (language: LanguageType): Promise<void> => {
|
const setLanguage = async (language: LanguageType): Promise<void> => {
|
||||||
await safeCall(async () => {
|
await updateAppearanceConfig('language', language);
|
||||||
await updateAppearanceConfig('language', language);
|
|
||||||
|
|
||||||
// 同步更新前端语言
|
// 同步更新前端语言
|
||||||
const frontendLocale = ConfigUtils.backendLanguageToFrontend(language);
|
const frontendLocale = ConfigUtils.backendLanguageToFrontend(language);
|
||||||
locale.value = frontendLocale as any;
|
locale.value = frontendLocale as any;
|
||||||
}, 'config.languageChangeFailed', 'config.languageChanged');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 系统主题设置方法
|
// 系统主题设置方法
|
||||||
const setSystemTheme = async (systemTheme: SystemThemeType): Promise<void> => {
|
const setSystemTheme = async (systemTheme: SystemThemeType): Promise<void> => {
|
||||||
await safeCall(async () => {
|
await updateAppearanceConfig('systemTheme', systemTheme);
|
||||||
await updateAppearanceConfig('systemTheme', systemTheme);
|
|
||||||
}, 'config.systemThemeChangeFailed', 'config.systemThemeChanged');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 初始化语言设置
|
// 初始化语言设置
|
||||||
@@ -339,21 +330,19 @@ export const useConfigStore = defineStore('config', () => {
|
|||||||
const togglers = {
|
const togglers = {
|
||||||
tabIndent: createEditingToggler('enableTabIndent'),
|
tabIndent: createEditingToggler('enableTabIndent'),
|
||||||
alwaysOnTop: async () => {
|
alwaysOnTop: async () => {
|
||||||
await safeCall(async () => {
|
await updateGeneralConfig('alwaysOnTop', !state.config.general.alwaysOnTop);
|
||||||
await updateGeneralConfig('alwaysOnTop', !state.config.general.alwaysOnTop);
|
// 立即应用窗口置顶状态
|
||||||
// 立即应用窗口置顶状态
|
await runtime.Window.SetAlwaysOnTop(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)
|
||||||
};
|
};
|
||||||
|
|
||||||
// 字符串配置设置器
|
// 字符串配置设置器
|
||||||
const setters = {
|
const setters = {
|
||||||
fontFamily: (value: string) => safeCall(() => updateEditingConfig('fontFamily', value), 'config.saveFailed', 'config.saveSuccess'),
|
fontFamily: async (value: string) => await updateEditingConfig('fontFamily', value),
|
||||||
fontWeight: (value: string) => safeCall(() => updateEditingConfig('fontWeight', value), 'config.saveFailed', 'config.saveSuccess'),
|
fontWeight: async (value: string) => await updateEditingConfig('fontWeight', value),
|
||||||
dataPath: (value: string) => safeCall(() => updateGeneralConfig('dataPath', value), 'config.saveFailed', 'config.saveSuccess'),
|
dataPath: async (value: string) => await updateGeneralConfig('dataPath', value),
|
||||||
autoSaveDelay: (value: number) => safeCall(() => updateEditingConfig('autoSaveDelay', value), 'config.saveFailed', 'config.saveSuccess')
|
autoSaveDelay: async (value: number) => await updateEditingConfig('autoSaveDelay', value)
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -366,7 +355,7 @@ export const useConfigStore = defineStore('config', () => {
|
|||||||
...limits,
|
...limits,
|
||||||
|
|
||||||
// 核心方法
|
// 核心方法
|
||||||
initConfig: () => safeCall(() => initConfig(), 'config.loadFailed', 'config.loadSuccess'),
|
initConfig,
|
||||||
resetConfig,
|
resetConfig,
|
||||||
|
|
||||||
// 语言相关方法
|
// 语言相关方法
|
||||||
@@ -385,7 +374,7 @@ export const useConfigStore = defineStore('config', () => {
|
|||||||
|
|
||||||
// Tab操作
|
// Tab操作
|
||||||
toggleTabIndent: togglers.tabIndent,
|
toggleTabIndent: togglers.tabIndent,
|
||||||
setEnableTabIndent: (value: boolean) => safeCall(() => updateEditingConfig('enableTabIndent', value), 'config.saveFailed', 'config.saveSuccess'),
|
setEnableTabIndent: (value: boolean) => updateEditingConfig('enableTabIndent', value),
|
||||||
...adjusters.tabSize,
|
...adjusters.tabSize,
|
||||||
increaseTabSize: adjusters.tabSize.increase,
|
increaseTabSize: adjusters.tabSize.increase,
|
||||||
decreaseTabSize: adjusters.tabSize.decrease,
|
decreaseTabSize: adjusters.tabSize.decrease,
|
||||||
@@ -397,7 +386,7 @@ export const useConfigStore = defineStore('config', () => {
|
|||||||
|
|
||||||
// 窗口操作
|
// 窗口操作
|
||||||
toggleAlwaysOnTop: togglers.alwaysOnTop,
|
toggleAlwaysOnTop: togglers.alwaysOnTop,
|
||||||
setAlwaysOnTop: (value: boolean) => safeCall(() => updateGeneralConfig('alwaysOnTop', value), 'config.saveFailed', 'config.saveSuccess'),
|
setAlwaysOnTop: (value: boolean) => updateGeneralConfig('alwaysOnTop', value),
|
||||||
|
|
||||||
// 字体操作
|
// 字体操作
|
||||||
setFontFamily: setters.fontFamily,
|
setFontFamily: setters.fontFamily,
|
||||||
@@ -410,20 +399,18 @@ export const useConfigStore = defineStore('config', () => {
|
|||||||
setAutoSaveDelay: setters.autoSaveDelay,
|
setAutoSaveDelay: setters.autoSaveDelay,
|
||||||
|
|
||||||
// 热键配置相关方法
|
// 热键配置相关方法
|
||||||
setEnableGlobalHotkey: (value: boolean) => safeCall(() => updateGeneralConfig('enableGlobalHotkey', value), 'config.saveFailed', 'config.saveSuccess'),
|
setEnableGlobalHotkey: (value: boolean) => updateGeneralConfig('enableGlobalHotkey', value),
|
||||||
setGlobalHotkey: (hotkey: any) => safeCall(() => updateGeneralConfig('globalHotkey', hotkey), 'config.saveFailed', 'config.saveSuccess'),
|
setGlobalHotkey: (hotkey: any) => updateGeneralConfig('globalHotkey', hotkey),
|
||||||
|
|
||||||
// 系统托盘配置相关方法
|
// 系统托盘配置相关方法
|
||||||
setEnableSystemTray: (value: boolean) => safeCall(() => updateGeneralConfig('enableSystemTray', value), 'config.saveFailed', 'config.saveSuccess'),
|
setEnableSystemTray: (value: boolean) => updateGeneralConfig('enableSystemTray', value),
|
||||||
|
|
||||||
// 开机启动配置相关方法
|
// 开机启动配置相关方法
|
||||||
setStartAtLogin: async (value: boolean) => {
|
setStartAtLogin: async (value: boolean) => {
|
||||||
await safeCall(async () => {
|
// 先更新配置文件
|
||||||
// 先更新配置文件
|
await updateGeneralConfig('startAtLogin', value);
|
||||||
await updateGeneralConfig('startAtLogin', value);
|
// 再调用系统设置API
|
||||||
// 再调用系统设置API
|
await StartupService.SetEnabled(value);
|
||||||
await StartupService.SetEnabled(value);
|
|
||||||
}, 'config.startupFailed', 'config.startupSuccess');
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
@@ -2,11 +2,8 @@ import {defineStore} from 'pinia';
|
|||||||
import {computed, ref} from 'vue';
|
import {computed, ref} from 'vue';
|
||||||
import {DocumentService} from '@/../bindings/voidraft/internal/services';
|
import {DocumentService} from '@/../bindings/voidraft/internal/services';
|
||||||
import {Document} from '@/../bindings/voidraft/internal/models/models';
|
import {Document} from '@/../bindings/voidraft/internal/models/models';
|
||||||
import {useErrorHandler} from '@/utils/errorHandler';
|
|
||||||
|
|
||||||
export const useDocumentStore = defineStore('document', () => {
|
export const useDocumentStore = defineStore('document', () => {
|
||||||
const {safeCall} = useErrorHandler();
|
|
||||||
|
|
||||||
// 状态
|
// 状态
|
||||||
const activeDocument = ref<Document | null>(null);
|
const activeDocument = ref<Document | null>(null);
|
||||||
const isLoading = ref(false);
|
const isLoading = ref(false);
|
||||||
@@ -23,15 +20,13 @@ export const useDocumentStore = defineStore('document', () => {
|
|||||||
// 状态管理包装器
|
// 状态管理包装器
|
||||||
const withStateGuard = async <T>(
|
const withStateGuard = async <T>(
|
||||||
operation: () => Promise<T>,
|
operation: () => Promise<T>,
|
||||||
stateRef: typeof isLoading | typeof isSaving,
|
stateRef: typeof isLoading | typeof isSaving
|
||||||
errorMessageKey: string,
|
|
||||||
successMessageKey?: string
|
|
||||||
): Promise<T | null> => {
|
): Promise<T | null> => {
|
||||||
if (stateRef.value) return null;
|
if (stateRef.value) return null;
|
||||||
|
|
||||||
stateRef.value = true;
|
stateRef.value = true;
|
||||||
try {
|
try {
|
||||||
return await safeCall(operation, errorMessageKey, successMessageKey);
|
return await operation();
|
||||||
} finally {
|
} finally {
|
||||||
stateRef.value = false;
|
stateRef.value = false;
|
||||||
}
|
}
|
||||||
@@ -44,9 +39,7 @@ export const useDocumentStore = defineStore('document', () => {
|
|||||||
activeDocument.value = doc;
|
activeDocument.value = doc;
|
||||||
return doc;
|
return doc;
|
||||||
},
|
},
|
||||||
isLoading,
|
isLoading
|
||||||
'document.loadFailed',
|
|
||||||
'document.loadSuccess'
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// 保存文档
|
// 保存文档
|
||||||
@@ -64,9 +57,7 @@ export const useDocumentStore = defineStore('document', () => {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
isSaving,
|
isSaving
|
||||||
'document.saveFailed',
|
|
||||||
'document.saveSuccess'
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return result ?? false;
|
return result ?? false;
|
||||||
@@ -88,9 +79,7 @@ export const useDocumentStore = defineStore('document', () => {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
isSaving,
|
isSaving
|
||||||
'document.saveFailed',
|
|
||||||
'document.manualSaveSuccess'
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return result ?? false;
|
return result ?? false;
|
||||||
|
@@ -4,34 +4,29 @@ import {EditorView} from '@codemirror/view';
|
|||||||
import {EditorState, Extension} from '@codemirror/state';
|
import {EditorState, Extension} from '@codemirror/state';
|
||||||
import {useConfigStore} from './configStore';
|
import {useConfigStore} from './configStore';
|
||||||
import {useDocumentStore} from './documentStore';
|
import {useDocumentStore} from './documentStore';
|
||||||
import {useLogStore} from './logStore';
|
import {useThemeStore} from './themeStore';
|
||||||
|
import {useI18n} from 'vue-i18n';
|
||||||
|
import {SystemThemeType} from '@/../bindings/voidraft/internal/models/models';
|
||||||
|
import {DocumentService} from '@/../bindings/voidraft/internal/services';
|
||||||
|
import {ensureSyntaxTree} from "@codemirror/language"
|
||||||
import {createBasicSetup} from '@/views/editor/extensions/basicSetup';
|
import {createBasicSetup} from '@/views/editor/extensions/basicSetup';
|
||||||
import {
|
import {createThemeExtension, updateEditorTheme} from '@/views/editor/extensions/themeExtension';
|
||||||
createStatsUpdateExtension,
|
import {getTabExtensions, updateTabConfig} from '@/views/editor/extensions/tabExtension';
|
||||||
getTabExtensions,
|
import {createFontExtensionFromBackend, updateFontConfig} from '@/views/editor/extensions/fontExtension';
|
||||||
updateTabConfig,
|
import {createStatsUpdateExtension} from '@/views/editor/extensions/statsExtension';
|
||||||
createAutoSavePlugin,
|
import {createAutoSavePlugin, createSaveShortcutPlugin} from '@/views/editor/extensions/autoSaveExtension';
|
||||||
createSaveShortcutPlugin,
|
import {createDynamicKeymapExtension} from '@/views/editor/extensions/keymap';
|
||||||
createFontExtensionFromBackend,
|
|
||||||
updateFontConfig,
|
|
||||||
createDynamicKeymapExtension,
|
|
||||||
} from '@/views/editor/extensions';
|
|
||||||
import { createThemeExtension, updateEditorTheme } from '@/views/editor/extensions/themeExtension';
|
|
||||||
import { useThemeStore } from './themeStore';
|
|
||||||
import { useI18n } from 'vue-i18n';
|
|
||||||
import { SystemThemeType } from '@/../bindings/voidraft/internal/models/models';
|
|
||||||
import { DocumentService } from '@/../bindings/voidraft/internal/services';
|
|
||||||
import {ensureSyntaxTree } from "@codemirror/language"
|
|
||||||
export interface DocumentStats {
|
export interface DocumentStats {
|
||||||
lines: number;
|
lines: number;
|
||||||
characters: number;
|
characters: number;
|
||||||
selectedCharacters: number;
|
selectedCharacters: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useEditorStore = defineStore('editor', () => {
|
export const useEditorStore = defineStore('editor', () => {
|
||||||
// 引用配置store
|
// 引用配置store
|
||||||
const configStore = useConfigStore();
|
const configStore = useConfigStore();
|
||||||
const documentStore = useDocumentStore();
|
const documentStore = useDocumentStore();
|
||||||
const logStore = useLogStore();
|
|
||||||
const themeStore = useThemeStore();
|
const themeStore = useThemeStore();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
@@ -100,22 +95,6 @@ export const useEditorStore = defineStore('editor', () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 手动保存文档
|
|
||||||
const handleManualSave = async () => {
|
|
||||||
if (!editorView.value) return;
|
|
||||||
|
|
||||||
const view = editorView.value as EditorView;
|
|
||||||
const content = view.state.doc.toString();
|
|
||||||
|
|
||||||
// 先更新内容
|
|
||||||
await DocumentService.UpdateActiveDocumentContent(content);
|
|
||||||
// 然后调用强制保存方法
|
|
||||||
const success = await documentStore.forceSaveDocument();
|
|
||||||
if (success) {
|
|
||||||
logStore.info(t('document.manualSaveSuccess'));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 创建编辑器
|
// 创建编辑器
|
||||||
const createEditor = async (initialDoc: string = '') => {
|
const createEditor = async (initialDoc: string = '') => {
|
||||||
if (isEditorInitialized.value || !editorContainer.value) return;
|
if (isEditorInitialized.value || !editorContainer.value) return;
|
||||||
@@ -231,6 +210,18 @@ export const useEditorStore = defineStore('editor', () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 手动保存文档
|
||||||
|
const handleManualSave = async () => {
|
||||||
|
if (!editorView.value) return;
|
||||||
|
|
||||||
|
const view = editorView.value as EditorView;
|
||||||
|
const content = view.state.doc.toString();
|
||||||
|
|
||||||
|
// 先更新内容
|
||||||
|
await DocumentService.UpdateActiveDocumentContent(content);
|
||||||
|
// 然后调用强制保存方法
|
||||||
|
await documentStore.forceSaveDocument();
|
||||||
|
};
|
||||||
|
|
||||||
// 销毁编辑器
|
// 销毁编辑器
|
||||||
const destroyEditor = () => {
|
const destroyEditor = () => {
|
||||||
@@ -241,51 +232,50 @@ export const useEditorStore = defineStore('editor', () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 监听Tab设置变化
|
||||||
|
watch([
|
||||||
|
() => configStore.config.editing.tabSize,
|
||||||
|
() => configStore.config.editing.enableTabIndent,
|
||||||
|
() => configStore.config.editing.tabType,
|
||||||
|
], () => {
|
||||||
|
reconfigureTabSettings();
|
||||||
|
});
|
||||||
|
|
||||||
// 监听Tab设置变化
|
// 监听字体大小变化
|
||||||
watch([
|
watch([
|
||||||
() => configStore.config.editing.tabSize,
|
() => configStore.config.editing.fontFamily,
|
||||||
() => configStore.config.editing.enableTabIndent,
|
() => configStore.config.editing.fontSize,
|
||||||
() => configStore.config.editing.tabType,
|
() => configStore.config.editing.lineHeight,
|
||||||
], () => {
|
() => configStore.config.editing.fontWeight,
|
||||||
reconfigureTabSettings();
|
], () => {
|
||||||
});
|
reconfigureFontSettings();
|
||||||
|
applyFontSize();
|
||||||
|
});
|
||||||
|
|
||||||
// 监听字体大小变化
|
// 监听主题变化
|
||||||
watch([
|
watch(() => themeStore.currentTheme, (newTheme) => {
|
||||||
() => configStore.config.editing.fontFamily,
|
if (editorView.value && newTheme) {
|
||||||
() => configStore.config.editing.fontSize,
|
updateEditorTheme(editorView.value as EditorView, newTheme);
|
||||||
() => configStore.config.editing.lineHeight,
|
}
|
||||||
() => configStore.config.editing.fontWeight,
|
});
|
||||||
], () => {
|
|
||||||
reconfigureFontSettings();
|
|
||||||
applyFontSize();
|
|
||||||
});
|
|
||||||
|
|
||||||
// 监听主题变化
|
return {
|
||||||
watch(() => themeStore.currentTheme, (newTheme) => {
|
// 状态
|
||||||
if (editorView.value && newTheme) {
|
documentStats,
|
||||||
updateEditorTheme(editorView.value as EditorView, newTheme);
|
editorView,
|
||||||
}
|
isEditorInitialized,
|
||||||
});
|
editorContainer,
|
||||||
|
|
||||||
return {
|
// 方法
|
||||||
// 状态
|
setEditorView,
|
||||||
documentStats,
|
setEditorContainer,
|
||||||
editorView,
|
updateDocumentStats,
|
||||||
isEditorInitialized,
|
applyFontSize,
|
||||||
editorContainer,
|
createEditor,
|
||||||
|
reconfigureTabSettings,
|
||||||
// 方法
|
reconfigureFontSettings,
|
||||||
setEditorView,
|
handleManualSave,
|
||||||
setEditorContainer,
|
destroyEditor,
|
||||||
updateDocumentStats,
|
scrollEditorToBottom,
|
||||||
applyFontSize,
|
};
|
||||||
createEditor,
|
|
||||||
reconfigureTabSettings,
|
|
||||||
reconfigureFontSettings,
|
|
||||||
handleManualSave,
|
|
||||||
destroyEditor,
|
|
||||||
scrollEditorToBottom,
|
|
||||||
};
|
|
||||||
});
|
});
|
@@ -1,166 +0,0 @@
|
|||||||
import { defineStore } from 'pinia';
|
|
||||||
import { ref } from 'vue';
|
|
||||||
|
|
||||||
// 日志级别定义
|
|
||||||
export enum LogLevel {
|
|
||||||
INFO = 'info',
|
|
||||||
WARNING = 'warning',
|
|
||||||
ERROR = 'error'
|
|
||||||
}
|
|
||||||
|
|
||||||
// 日志项结构
|
|
||||||
export interface LogItem {
|
|
||||||
id: number;
|
|
||||||
level: LogLevel;
|
|
||||||
message: string;
|
|
||||||
timestamp: Date;
|
|
||||||
shown: boolean; // 是否已显示过
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useLogStore = defineStore('log', () => {
|
|
||||||
// 日志列表
|
|
||||||
const logs = ref<LogItem[]>([]);
|
|
||||||
|
|
||||||
// 显示队列 - 存储待显示的日志
|
|
||||||
const displayQueue = ref<LogItem[]>([]);
|
|
||||||
|
|
||||||
// 当前显示的日志
|
|
||||||
const currentLog = ref<LogItem | null>(null);
|
|
||||||
|
|
||||||
// 最近一条日志,用于在工具栏显示
|
|
||||||
const latestLog = ref<LogItem | null>(null);
|
|
||||||
|
|
||||||
// 是否显示日志
|
|
||||||
const showLog = ref(false);
|
|
||||||
|
|
||||||
// 自动隐藏计时器
|
|
||||||
let hideTimer: number | null = null;
|
|
||||||
|
|
||||||
// 根据日志级别获取显示时间
|
|
||||||
function getDisplayTimeByLevel(level: LogLevel): number {
|
|
||||||
switch(level) {
|
|
||||||
case LogLevel.ERROR:
|
|
||||||
return 8000; // 错误显示更长时间
|
|
||||||
case LogLevel.WARNING:
|
|
||||||
return 6000; // 警告显示中等时间
|
|
||||||
case LogLevel.INFO:
|
|
||||||
default:
|
|
||||||
return 4000; // 信息显示较短时间
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 显示下一条日志
|
|
||||||
function showNextLog() {
|
|
||||||
if (hideTimer) {
|
|
||||||
window.clearTimeout(hideTimer);
|
|
||||||
hideTimer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果队列为空,则不显示
|
|
||||||
if (displayQueue.value.length === 0) {
|
|
||||||
showLog.value = false;
|
|
||||||
currentLog.value = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 取出队列中第一条日志显示
|
|
||||||
const nextLog = displayQueue.value.shift()!;
|
|
||||||
currentLog.value = nextLog;
|
|
||||||
showLog.value = true;
|
|
||||||
|
|
||||||
// 设置自动隐藏和切换到下一条
|
|
||||||
const displayTime = getDisplayTimeByLevel(nextLog.level);
|
|
||||||
hideTimer = window.setTimeout(() => {
|
|
||||||
showNextLog();
|
|
||||||
}, displayTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加日志
|
|
||||||
function addLog(level: LogLevel, message: string, autoHideDelay = 0) {
|
|
||||||
const id = Date.now();
|
|
||||||
const logItem: LogItem = {
|
|
||||||
id,
|
|
||||||
level,
|
|
||||||
message,
|
|
||||||
timestamp: new Date(),
|
|
||||||
shown: false
|
|
||||||
};
|
|
||||||
|
|
||||||
// 添加到日志列表
|
|
||||||
logs.value.push(logItem);
|
|
||||||
|
|
||||||
// 保持日志列表在合理大小
|
|
||||||
if (logs.value.length > 100) {
|
|
||||||
logs.value = logs.value.slice(-100);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置最新日志
|
|
||||||
latestLog.value = logItem;
|
|
||||||
|
|
||||||
// 添加到显示队列
|
|
||||||
displayQueue.value.push(logItem);
|
|
||||||
|
|
||||||
// 如果当前没有显示日志,则开始显示
|
|
||||||
if (!currentLog.value && !hideTimer) {
|
|
||||||
showNextLog();
|
|
||||||
}
|
|
||||||
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加不同级别的日志的便捷方法
|
|
||||||
function info(message: string) {
|
|
||||||
return addLog(LogLevel.INFO, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
function warning(message: string) {
|
|
||||||
return addLog(LogLevel.WARNING, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
function error(message: string) {
|
|
||||||
return addLog(LogLevel.ERROR, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 清除日志
|
|
||||||
function clearLogs() {
|
|
||||||
logs.value = [];
|
|
||||||
displayQueue.value = [];
|
|
||||||
latestLog.value = null;
|
|
||||||
currentLog.value = null;
|
|
||||||
showLog.value = false;
|
|
||||||
|
|
||||||
if (hideTimer) {
|
|
||||||
window.clearTimeout(hideTimer);
|
|
||||||
hideTimer = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 手动隐藏当前日志
|
|
||||||
function hideCurrentLog() {
|
|
||||||
if (hideTimer) {
|
|
||||||
window.clearTimeout(hideTimer);
|
|
||||||
hideTimer = null;
|
|
||||||
}
|
|
||||||
showLog.value = false;
|
|
||||||
currentLog.value = null;
|
|
||||||
displayQueue.value = []; // 清空队列
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
// 状态
|
|
||||||
logs,
|
|
||||||
latestLog,
|
|
||||||
currentLog,
|
|
||||||
showLog,
|
|
||||||
displayQueue,
|
|
||||||
|
|
||||||
// 方法
|
|
||||||
addLog,
|
|
||||||
info,
|
|
||||||
warning,
|
|
||||||
error,
|
|
||||||
clearLogs,
|
|
||||||
hideCurrentLog,
|
|
||||||
showNextLog
|
|
||||||
};
|
|
||||||
});
|
|
@@ -1,97 +0,0 @@
|
|||||||
import { useLogStore } from '@/stores/logStore';
|
|
||||||
import { useI18n } from 'vue-i18n';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建组合式函数,用于在组件中使用错误处理
|
|
||||||
*/
|
|
||||||
export function useErrorHandler() {
|
|
||||||
const logStore = useLogStore();
|
|
||||||
const { t } = useI18n();
|
|
||||||
|
|
||||||
const handleError = (error: unknown, messageKey: string) => {
|
|
||||||
logStore.error(t(messageKey));
|
|
||||||
};
|
|
||||||
|
|
||||||
const safeCall = async <T>(
|
|
||||||
operation: () => Promise<T>,
|
|
||||||
errorMessageKey: string,
|
|
||||||
successMessageKey?: string
|
|
||||||
): Promise<T | null> => {
|
|
||||||
try {
|
|
||||||
const result = await operation();
|
|
||||||
if (successMessageKey) {
|
|
||||||
logStore.info(t(successMessageKey));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
} catch (error) {
|
|
||||||
logStore.error(t(errorMessageKey));
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 静默处理错误,不显示用户提示
|
|
||||||
*/
|
|
||||||
const silentCall = async <T>(
|
|
||||||
operation: () => Promise<T>
|
|
||||||
): Promise<T | null> => {
|
|
||||||
try {
|
|
||||||
return await operation();
|
|
||||||
} catch (error) {
|
|
||||||
// 静默忽略错误
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建带错误处理的数值调整器
|
|
||||||
*/
|
|
||||||
const createSafeAdjuster = (
|
|
||||||
adjustFn: () => Promise<void>,
|
|
||||||
errorMessageKey: string
|
|
||||||
) => () => safeCall(adjustFn, errorMessageKey);
|
|
||||||
|
|
||||||
return {
|
|
||||||
handleError,
|
|
||||||
safeCall,
|
|
||||||
silentCall,
|
|
||||||
createSafeAdjuster
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 错误处理工具函数集合(不依赖Vue上下文)
|
|
||||||
*/
|
|
||||||
export const ErrorUtils = {
|
|
||||||
/**
|
|
||||||
* 静默处理错误
|
|
||||||
*/
|
|
||||||
silent: async <T>(operation: () => Promise<T>): Promise<T | null> => {
|
|
||||||
try {
|
|
||||||
return await operation();
|
|
||||||
} catch (error) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 带重试的错误处理
|
|
||||||
*/
|
|
||||||
withRetry: async <T>(
|
|
||||||
operation: () => Promise<T>,
|
|
||||||
maxRetries: number = 3,
|
|
||||||
delay: number = 1000
|
|
||||||
): Promise<T | null> => {
|
|
||||||
for (let i = 0; i < maxRetries; i++) {
|
|
||||||
try {
|
|
||||||
return await operation();
|
|
||||||
} catch (error) {
|
|
||||||
if (i === maxRetries - 1) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
await new Promise(resolve => setTimeout(resolve, delay));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
@@ -1,7 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useConfigStore } from '@/stores/configStore';
|
import { useConfigStore } from '@/stores/configStore';
|
||||||
import { useThemeStore } from '@/stores/themeStore';
|
import { useThemeStore } from '@/stores/themeStore';
|
||||||
import { useErrorHandler } from '@/utils/errorHandler';
|
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import SettingSection from '../components/SettingSection.vue';
|
import SettingSection from '../components/SettingSection.vue';
|
||||||
import SettingItem from '../components/SettingItem.vue';
|
import SettingItem from '../components/SettingItem.vue';
|
||||||
@@ -10,7 +9,6 @@ import { SystemThemeType, LanguageType } from '@/../bindings/voidraft/internal/m
|
|||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const configStore = useConfigStore();
|
const configStore = useConfigStore();
|
||||||
const themeStore = useThemeStore();
|
const themeStore = useThemeStore();
|
||||||
const { safeCall } = useErrorHandler();
|
|
||||||
|
|
||||||
// 语言选项
|
// 语言选项
|
||||||
const languageOptions = [
|
const languageOptions = [
|
||||||
@@ -30,10 +28,7 @@ const updateLanguage = async (event: Event) => {
|
|||||||
const select = event.target as HTMLSelectElement;
|
const select = event.target as HTMLSelectElement;
|
||||||
const selectedLanguage = select.value as LanguageType;
|
const selectedLanguage = select.value as LanguageType;
|
||||||
|
|
||||||
await safeCall(
|
await configStore.setLanguage(selectedLanguage);
|
||||||
() => configStore.setLanguage(selectedLanguage),
|
|
||||||
'config.languageChangeFailed'
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 更新系统主题设置
|
// 更新系统主题设置
|
||||||
@@ -41,10 +36,7 @@ const updateSystemTheme = async (event: Event) => {
|
|||||||
const select = event.target as HTMLSelectElement;
|
const select = event.target as HTMLSelectElement;
|
||||||
const selectedSystemTheme = select.value as SystemThemeType;
|
const selectedSystemTheme = select.value as SystemThemeType;
|
||||||
|
|
||||||
await safeCall(
|
await themeStore.setTheme(selectedSystemTheme);
|
||||||
() => themeStore.setTheme(selectedSystemTheme),
|
|
||||||
'config.systemThemeChangeFailed'
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useConfigStore } from '@/stores/configStore';
|
import { useConfigStore } from '@/stores/configStore';
|
||||||
import { FONT_OPTIONS } from '@/stores/configStore';
|
import { FONT_OPTIONS } from '@/stores/configStore';
|
||||||
import { useErrorHandler } from '@/utils/errorHandler';
|
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import {computed, onMounted } from 'vue';
|
import {computed, onMounted } from 'vue';
|
||||||
import SettingSection from '../components/SettingSection.vue';
|
import SettingSection from '../components/SettingSection.vue';
|
||||||
@@ -11,7 +10,6 @@ import { TabType } from '@/../bindings/voidraft/internal/models/';
|
|||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const configStore = useConfigStore();
|
const configStore = useConfigStore();
|
||||||
const { safeCall } = useErrorHandler();
|
|
||||||
|
|
||||||
// 确保配置已加载
|
// 确保配置已加载
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
@@ -29,10 +27,7 @@ const handleFontFamilyChange = async (event: Event) => {
|
|||||||
const target = event.target as HTMLSelectElement;
|
const target = event.target as HTMLSelectElement;
|
||||||
const fontFamily = target.value;
|
const fontFamily = target.value;
|
||||||
if (fontFamily) {
|
if (fontFamily) {
|
||||||
await safeCall(
|
await configStore.setFontFamily(fontFamily);
|
||||||
() => configStore.setFontFamily(fontFamily),
|
|
||||||
'config.fontFamilyUpdateFailed'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -52,42 +47,27 @@ const fontWeightOptions = [
|
|||||||
// 字体粗细选择
|
// 字体粗细选择
|
||||||
const handleFontWeightChange = async (event: Event) => {
|
const handleFontWeightChange = async (event: Event) => {
|
||||||
const target = event.target as HTMLSelectElement;
|
const target = event.target as HTMLSelectElement;
|
||||||
await safeCall(
|
await configStore.setFontWeight(target.value);
|
||||||
() => configStore.setFontWeight(target.value),
|
|
||||||
'config.fontWeightUpdateFailed'
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 行高控制
|
// 行高控制
|
||||||
const increaseLineHeight = async () => {
|
const increaseLineHeight = async () => {
|
||||||
const newLineHeight = Math.min(3.0, configStore.config.editing.lineHeight + 0.1);
|
const newLineHeight = Math.min(3.0, configStore.config.editing.lineHeight + 0.1);
|
||||||
await safeCall(
|
await configStore.setLineHeight(Math.round(newLineHeight * 10) / 10);
|
||||||
() => configStore.setLineHeight(Math.round(newLineHeight * 10) / 10),
|
|
||||||
'config.lineHeightIncreaseFailed'
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const decreaseLineHeight = async () => {
|
const decreaseLineHeight = async () => {
|
||||||
const newLineHeight = Math.max(1.0, configStore.config.editing.lineHeight - 0.1);
|
const newLineHeight = Math.max(1.0, configStore.config.editing.lineHeight - 0.1);
|
||||||
await safeCall(
|
await configStore.setLineHeight(Math.round(newLineHeight * 10) / 10);
|
||||||
() => configStore.setLineHeight(Math.round(newLineHeight * 10) / 10),
|
|
||||||
'config.lineHeightDecreaseFailed'
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 字体大小控制
|
// 字体大小控制
|
||||||
const increaseFontSize = async () => {
|
const increaseFontSize = async () => {
|
||||||
await safeCall(
|
await configStore.increaseFontSize();
|
||||||
() => configStore.increaseFontSize(),
|
|
||||||
'config.fontSizeIncreaseFailed'
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const decreaseFontSize = async () => {
|
const decreaseFontSize = async () => {
|
||||||
await safeCall(
|
await configStore.decreaseFontSize();
|
||||||
() => configStore.decreaseFontSize(),
|
|
||||||
'config.fontSizeDecreaseFailed'
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Tab类型切换
|
// Tab类型切换
|
||||||
@@ -99,35 +79,23 @@ const tabTypeText = computed(() => {
|
|||||||
|
|
||||||
// Tab大小增减
|
// Tab大小增减
|
||||||
const increaseTabSize = async () => {
|
const increaseTabSize = async () => {
|
||||||
await safeCall(
|
await configStore.increaseTabSize();
|
||||||
() => configStore.increaseTabSize(),
|
|
||||||
'config.tabSizeIncreaseFailed'
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const decreaseTabSize = async () => {
|
const decreaseTabSize = async () => {
|
||||||
await safeCall(
|
await configStore.decreaseTabSize();
|
||||||
() => configStore.decreaseTabSize(),
|
|
||||||
'config.tabSizeDecreaseFailed'
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Tab相关操作
|
// Tab相关操作
|
||||||
const handleToggleTabType = async () => {
|
const handleToggleTabType = async () => {
|
||||||
await safeCall(
|
await configStore.toggleTabType();
|
||||||
() => configStore.toggleTabType(),
|
|
||||||
'config.tabTypeToggleFailed'
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 创建双向绑定的计算属性
|
// 创建双向绑定的计算属性
|
||||||
const enableTabIndent = computed({
|
const enableTabIndent = computed({
|
||||||
get: () => configStore.config.editing.enableTabIndent,
|
get: () => configStore.config.editing.enableTabIndent,
|
||||||
set: async (value: boolean) => {
|
set: async (value: boolean) => {
|
||||||
await safeCall(
|
await configStore.setEnableTabIndent(value);
|
||||||
() => configStore.setEnableTabIndent(value),
|
|
||||||
'config.tabIndentToggleFailed'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -136,10 +104,7 @@ const handleAutoSaveDelayChange = async (event: Event) => {
|
|||||||
const target = event.target as HTMLInputElement;
|
const target = event.target as HTMLInputElement;
|
||||||
const value = parseInt(target.value, 10);
|
const value = parseInt(target.value, 10);
|
||||||
if (!isNaN(value) && value >= 1000 && value <= 30000) {
|
if (!isNaN(value) && value >= 1000 && value <= 30000) {
|
||||||
await safeCall(
|
await configStore.setAutoSaveDelay(value);
|
||||||
() => configStore.setAutoSaveDelay(value),
|
|
||||||
'config.autoSaveDelayUpdateFailed'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -5,13 +5,11 @@ import {computed, onUnmounted, ref} 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 {DialogService, MigrationService, MigrationProgress, MigrationStatus} from '@/../bindings/voidraft/internal/services';
|
import {DialogService, MigrationService, MigrationProgress, MigrationStatus} from '@/../bindings/voidraft/internal/services';
|
||||||
import * as runtime from '@wailsio/runtime';
|
import * as runtime from '@wailsio/runtime';
|
||||||
|
|
||||||
const {t} = useI18n();
|
const {t} = useI18n();
|
||||||
const configStore = useConfigStore();
|
const configStore = useConfigStore();
|
||||||
const {safeCall} = useErrorHandler();
|
|
||||||
|
|
||||||
// 迁移进度状态
|
// 迁移进度状态
|
||||||
const migrationProgress = ref<MigrationProgress>(new MigrationProgress({
|
const migrationProgress = ref<MigrationProgress>(new MigrationProgress({
|
||||||
@@ -143,12 +141,10 @@ const enableGlobalHotkey = computed({
|
|||||||
const alwaysOnTop = computed({
|
const alwaysOnTop = computed({
|
||||||
get: () => configStore.config.general.alwaysOnTop,
|
get: () => configStore.config.general.alwaysOnTop,
|
||||||
set: async (value: boolean) => {
|
set: async (value: boolean) => {
|
||||||
await safeCall(async () => {
|
// 先更新配置
|
||||||
// 先更新配置
|
await configStore.setAlwaysOnTop(value);
|
||||||
await configStore.setAlwaysOnTop(value);
|
// 然后立即应用窗口置顶状态
|
||||||
// 然后立即应用窗口置顶状态
|
await runtime.Window.SetAlwaysOnTop(value);
|
||||||
await runtime.Window.SetAlwaysOnTop(value);
|
|
||||||
}, 'config.alwaysOnTopFailed', 'config.alwaysOnTopSuccess');
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
20
go.mod
20
go.mod
@@ -5,8 +5,10 @@ go 1.23.0
|
|||||||
toolchain go1.24.2
|
toolchain go1.24.2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/fsnotify/fsnotify v1.9.0
|
github.com/knadh/koanf/parsers/json v1.0.0
|
||||||
github.com/spf13/viper v1.20.1
|
github.com/knadh/koanf/providers/file v1.2.0
|
||||||
|
github.com/knadh/koanf/providers/structs v1.0.0
|
||||||
|
github.com/knadh/koanf/v2 v2.2.1
|
||||||
github.com/wailsapp/wails/v3 v3.0.0-alpha.9
|
github.com/wailsapp/wails/v3 v3.0.0-alpha.9
|
||||||
golang.org/x/sys v0.33.0
|
golang.org/x/sys v0.33.0
|
||||||
)
|
)
|
||||||
@@ -21,6 +23,8 @@ require (
|
|||||||
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
|
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
|
||||||
github.com/ebitengine/purego v0.8.4 // indirect
|
github.com/ebitengine/purego v0.8.4 // indirect
|
||||||
github.com/emirpasic/gods v1.18.1 // indirect
|
github.com/emirpasic/gods v1.18.1 // indirect
|
||||||
|
github.com/fatih/structs v1.1.0 // indirect
|
||||||
|
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||||
github.com/go-git/go-billy/v5 v5.6.2 // indirect
|
github.com/go-git/go-billy/v5 v5.6.2 // indirect
|
||||||
github.com/go-git/go-git/v5 v5.16.2 // indirect
|
github.com/go-git/go-git/v5 v5.16.2 // indirect
|
||||||
@@ -32,34 +36,28 @@ require (
|
|||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||||
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 // indirect
|
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 // indirect
|
||||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||||
|
github.com/knadh/koanf/maps v0.1.2 // indirect
|
||||||
github.com/leaanthony/go-ansi-parser v1.6.1 // indirect
|
github.com/leaanthony/go-ansi-parser v1.6.1 // indirect
|
||||||
github.com/leaanthony/u v1.1.1 // indirect
|
github.com/leaanthony/u v1.1.1 // indirect
|
||||||
github.com/lmittmann/tint v1.1.2 // indirect
|
github.com/lmittmann/tint v1.1.2 // indirect
|
||||||
github.com/matryer/is v1.4.1 // indirect
|
github.com/matryer/is v1.4.1 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||||
|
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||||
github.com/pjbgf/sha1cd v0.3.2 // indirect
|
github.com/pjbgf/sha1cd v0.3.2 // indirect
|
||||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
github.com/sagikazarmark/locafero v0.9.0 // indirect
|
|
||||||
github.com/samber/lo v1.51.0 // indirect
|
github.com/samber/lo v1.51.0 // indirect
|
||||||
github.com/sergi/go-diff v1.4.0 // indirect
|
github.com/sergi/go-diff v1.4.0 // indirect
|
||||||
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/spf13/afero v1.14.0 // indirect
|
|
||||||
github.com/spf13/cast v1.9.2 // indirect
|
|
||||||
github.com/spf13/pflag v1.0.6 // 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
|
||||||
github.com/wailsapp/mimetype v1.4.1 // indirect
|
github.com/wailsapp/mimetype v1.4.1 // indirect
|
||||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
|
||||||
golang.org/x/crypto v0.39.0 // indirect
|
golang.org/x/crypto v0.39.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect
|
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect
|
||||||
golang.org/x/net v0.41.0 // indirect
|
golang.org/x/net v0.41.0 // indirect
|
||||||
golang.org/x/text v0.26.0 // indirect
|
golang.org/x/text v0.26.0 // indirect
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
|
||||||
)
|
)
|
||||||
|
36
go.sum
36
go.sum
@@ -26,8 +26,8 @@ github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o
|
|||||||
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
|
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
|
||||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
||||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||||
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
||||||
@@ -58,6 +58,16 @@ github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 h1:njuLRcjAuMKr7
|
|||||||
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
|
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
|
||||||
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
|
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
|
||||||
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||||
|
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
|
||||||
|
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
|
||||||
|
github.com/knadh/koanf/parsers/json v1.0.0 h1:1pVR1JhMwbqSg5ICzU+surJmeBbdT4bQm7jjgnA+f8o=
|
||||||
|
github.com/knadh/koanf/parsers/json v1.0.0/go.mod h1:zb5WtibRdpxSoSJfXysqGbVxvbszdlroWDHGdDkkEYU=
|
||||||
|
github.com/knadh/koanf/providers/file v1.2.0 h1:hrUJ6Y9YOA49aNu/RSYzOTFlqzXSCpmYIDXI7OJU6+U=
|
||||||
|
github.com/knadh/koanf/providers/file v1.2.0/go.mod h1:bp1PM5f83Q+TOUu10J/0ApLBd9uIzg+n9UgthfY+nRA=
|
||||||
|
github.com/knadh/koanf/providers/structs v1.0.0 h1:DznjB7NQykhqCar2LvNug3MuxEQsZ5KvfgMbio+23u4=
|
||||||
|
github.com/knadh/koanf/providers/structs v1.0.0/go.mod h1:kjo5TFtgpaZORlpoJqcbeLowM2cINodv8kX+oFAeQ1w=
|
||||||
|
github.com/knadh/koanf/v2 v2.2.1 h1:jaleChtw85y3UdBnI0wCqcg1sj1gPoz6D3caGNHtrNE=
|
||||||
|
github.com/knadh/koanf/v2 v2.2.1/go.mod h1:PSFru3ufQgTsI7IF+95rf9s8XA1+aHxKuO/W+dPoHEY=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
@@ -78,10 +88,12 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP
|
|||||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||||
|
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||||
|
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||||
|
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||||
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
||||||
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
|
||||||
github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
|
github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
|
||||||
github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
|
github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
|
||||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||||
@@ -95,8 +107,6 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
|||||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||||
github.com/sagikazarmark/locafero v0.9.0 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFTqyMTC9k=
|
|
||||||
github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk=
|
|
||||||
github.com/samber/lo v1.51.0 h1:kysRYLbHy/MB7kQZf5DSN50JHmMsNEdeY24VzJFu7wI=
|
github.com/samber/lo v1.51.0 h1:kysRYLbHy/MB7kQZf5DSN50JHmMsNEdeY24VzJFu7wI=
|
||||||
github.com/samber/lo v1.51.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
|
github.com/samber/lo v1.51.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
|
||||||
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
|
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
|
||||||
@@ -104,23 +114,11 @@ github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepq
|
|||||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||||
github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
|
github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
|
||||||
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
|
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
|
||||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
|
||||||
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/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo=
|
|
||||||
github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE=
|
|
||||||
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/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
|
||||||
github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
|
|
||||||
github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
|
||||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
|
||||||
github.com/wailsapp/go-webview2 v1.0.21 h1:k3dtoZU4KCoN/AEIbWiPln3P2661GtA2oEgA2Pb+maA=
|
github.com/wailsapp/go-webview2 v1.0.21 h1:k3dtoZU4KCoN/AEIbWiPln3P2661GtA2oEgA2Pb+maA=
|
||||||
github.com/wailsapp/go-webview2 v1.0.21/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc=
|
github.com/wailsapp/go-webview2 v1.0.21/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc=
|
||||||
github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
|
github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
|
||||||
@@ -129,8 +127,6 @@ github.com/wailsapp/wails/v3 v3.0.0-alpha.9 h1:b8CfRrhPno8Fra0xFp4Ifyj+ogmXBc35r
|
|||||||
github.com/wailsapp/wails/v3 v3.0.0-alpha.9/go.mod h1:dSv6s722nSWaUyUiapAM1DHc5HKggNGY1a79shO85/g=
|
github.com/wailsapp/wails/v3 v3.0.0-alpha.9/go.mod h1:dSv6s722nSWaUyUiapAM1DHc5HKggNGY1a79shO85/g=
|
||||||
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
||||||
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
||||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
|
||||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
|
||||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||||
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||||
|
@@ -84,7 +84,7 @@ type AppearanceConfig struct {
|
|||||||
|
|
||||||
// UpdatesConfig 更新设置配置
|
// UpdatesConfig 更新设置配置
|
||||||
type UpdatesConfig struct {
|
type UpdatesConfig struct {
|
||||||
Version string `json:"Version"` // 当前版本号
|
Version string `json:"version"` // 当前版本号
|
||||||
AutoUpdate bool `json:"autoUpdate"` // 是否自动更新
|
AutoUpdate bool `json:"autoUpdate"` // 是否自动更新
|
||||||
BetaChannel bool `json:"betaChannel"` // 是否启用测试版
|
BetaChannel bool `json:"betaChannel"` // 是否启用测试版
|
||||||
}
|
}
|
||||||
@@ -101,6 +101,7 @@ type AppConfig struct {
|
|||||||
// ConfigMetadata 配置元数据
|
// ConfigMetadata 配置元数据
|
||||||
type ConfigMetadata struct {
|
type ConfigMetadata struct {
|
||||||
LastUpdated string `json:"lastUpdated"` // 最后更新时间
|
LastUpdated string `json:"lastUpdated"` // 最后更新时间
|
||||||
|
Version string `json:"version"` // 配置版本号
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDefaultAppConfig 创建默认应用配置
|
// NewDefaultAppConfig 创建默认应用配置
|
||||||
@@ -148,6 +149,7 @@ func NewDefaultAppConfig() *AppConfig {
|
|||||||
},
|
},
|
||||||
Metadata: ConfigMetadata{
|
Metadata: ConfigMetadata{
|
||||||
LastUpdated: time.Now().Format(time.RFC3339),
|
LastUpdated: time.Now().Format(time.RFC3339),
|
||||||
|
Version: "1.0.0",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -9,7 +9,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
"voidraft/internal/models"
|
"voidraft/internal/models"
|
||||||
|
|
||||||
"github.com/spf13/viper"
|
"github.com/knadh/koanf/v2"
|
||||||
"github.com/wailsapp/wails/v3/pkg/services/log"
|
"github.com/wailsapp/wails/v3/pkg/services/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ type ConfigListener struct {
|
|||||||
ChangeType ConfigChangeType // 监听的配置变更类型
|
ChangeType ConfigChangeType // 监听的配置变更类型
|
||||||
Callback ConfigChangeCallback // 回调函数(现在包含新旧配置)
|
Callback ConfigChangeCallback // 回调函数(现在包含新旧配置)
|
||||||
DebounceDelay time.Duration // 防抖延迟时间
|
DebounceDelay time.Duration // 防抖延迟时间
|
||||||
GetConfigFunc func(*viper.Viper) *models.AppConfig // 获取相关配置的函数
|
GetConfigFunc func(*koanf.Koanf) *models.AppConfig // 获取相关配置的函数
|
||||||
|
|
||||||
// 内部状态
|
// 内部状态
|
||||||
mu sync.RWMutex // 监听器状态锁
|
mu sync.RWMutex // 监听器状态锁
|
||||||
@@ -47,18 +47,18 @@ type ConfigListener struct {
|
|||||||
type ConfigNotificationService struct {
|
type ConfigNotificationService struct {
|
||||||
listeners sync.Map // 使用sync.Map替代普通map+锁
|
listeners sync.Map // 使用sync.Map替代普通map+锁
|
||||||
logger *log.LoggerService // 日志服务
|
logger *log.LoggerService // 日志服务
|
||||||
viper *viper.Viper // Viper实例
|
koanf *koanf.Koanf // koanf实例
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewConfigNotificationService 创建配置通知服务
|
// NewConfigNotificationService 创建配置通知服务
|
||||||
func NewConfigNotificationService(viper *viper.Viper, logger *log.LoggerService) *ConfigNotificationService {
|
func NewConfigNotificationService(k *koanf.Koanf, logger *log.LoggerService) *ConfigNotificationService {
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
return &ConfigNotificationService{
|
return &ConfigNotificationService{
|
||||||
logger: logger,
|
logger: logger,
|
||||||
viper: viper,
|
koanf: k,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
cancel: cancel,
|
cancel: cancel,
|
||||||
}
|
}
|
||||||
@@ -90,7 +90,7 @@ func (cns *ConfigNotificationService) initializeListenerState(listener *ConfigLi
|
|||||||
return fmt.Errorf("GetConfigFunc is required")
|
return fmt.Errorf("GetConfigFunc is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
if config := listener.GetConfigFunc(cns.viper); config != nil {
|
if config := listener.GetConfigFunc(cns.koanf); config != nil {
|
||||||
listener.mu.Lock()
|
listener.mu.Lock()
|
||||||
listener.lastConfig = deepCopyConfig(config)
|
listener.lastConfig = deepCopyConfig(config)
|
||||||
listener.lastConfigHash = computeConfigHash(config)
|
listener.lastConfigHash = computeConfigHash(config)
|
||||||
@@ -125,7 +125,7 @@ func (cns *ConfigNotificationService) checkAndNotify(listener *ConfigListener) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
currentConfig := listener.GetConfigFunc(cns.viper)
|
currentConfig := listener.GetConfigFunc(cns.koanf)
|
||||||
|
|
||||||
listener.mu.RLock()
|
listener.mu.RLock()
|
||||||
lastHash := listener.lastConfigHash
|
lastHash := listener.lastConfigHash
|
||||||
@@ -268,9 +268,9 @@ func CreateHotkeyListener(callback func(enable bool, hotkey *models.HotkeyCombo)
|
|||||||
return callback(defaultConfig.General.EnableGlobalHotkey, &defaultConfig.General.GlobalHotkey)
|
return callback(defaultConfig.General.EnableGlobalHotkey, &defaultConfig.General.GlobalHotkey)
|
||||||
},
|
},
|
||||||
DebounceDelay: 200 * time.Millisecond,
|
DebounceDelay: 200 * time.Millisecond,
|
||||||
GetConfigFunc: func(v *viper.Viper) *models.AppConfig {
|
GetConfigFunc: func(k *koanf.Koanf) *models.AppConfig {
|
||||||
var config models.AppConfig
|
var config models.AppConfig
|
||||||
if err := v.Unmarshal(&config); err != nil {
|
if err := k.Unmarshal("", &config); err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return &config
|
return &config
|
||||||
@@ -303,9 +303,9 @@ func CreateDataPathListener(callback func(oldPath, newPath string) error) *Confi
|
|||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
DebounceDelay: 100 * time.Millisecond,
|
DebounceDelay: 100 * time.Millisecond,
|
||||||
GetConfigFunc: func(v *viper.Viper) *models.AppConfig {
|
GetConfigFunc: func(k *koanf.Koanf) *models.AppConfig {
|
||||||
var config models.AppConfig
|
var config models.AppConfig
|
||||||
if err := v.Unmarshal(&config); err != nil {
|
if err := k.Unmarshal("", &config); err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return &config
|
return &config
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
package services
|
package services
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
@@ -9,17 +8,20 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
"voidraft/internal/models"
|
"voidraft/internal/models"
|
||||||
|
|
||||||
"github.com/fsnotify/fsnotify"
|
jsonparser "github.com/knadh/koanf/parsers/json"
|
||||||
"github.com/spf13/viper"
|
"github.com/knadh/koanf/providers/file"
|
||||||
|
"github.com/knadh/koanf/providers/structs"
|
||||||
|
"github.com/knadh/koanf/v2"
|
||||||
"github.com/wailsapp/wails/v3/pkg/services/log"
|
"github.com/wailsapp/wails/v3/pkg/services/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ConfigService 提供基于 Viper 的配置管理功能
|
// ConfigService 应用配置服务
|
||||||
type ConfigService struct {
|
type ConfigService struct {
|
||||||
viper *viper.Viper // Viper 实例
|
koanf *koanf.Koanf // koanf 实例
|
||||||
logger *log.LoggerService // 日志服务
|
logger *log.LoggerService // 日志服务
|
||||||
pathManager *PathManager // 路径管理器
|
pathManager *PathManager // 路径管理器
|
||||||
mu sync.RWMutex // 读写锁
|
mu sync.RWMutex // 读写锁
|
||||||
|
fileProvider *file.File // 文件提供器,用于监听
|
||||||
|
|
||||||
// 配置通知服务
|
// 配置通知服务
|
||||||
notificationService *ConfigNotificationService
|
notificationService *ConfigNotificationService
|
||||||
@@ -60,34 +62,22 @@ func NewConfigService(logger *log.LoggerService, pathManager *PathManager) *Conf
|
|||||||
pathManager = NewPathManager()
|
pathManager = NewPathManager()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建 Viper 实例
|
// 使用"."作为键路径分隔符
|
||||||
v := viper.New()
|
k := koanf.New(".")
|
||||||
|
|
||||||
// 配置 Viper
|
|
||||||
v.SetConfigName(pathManager.GetConfigName())
|
|
||||||
v.SetConfigType("json")
|
|
||||||
v.AddConfigPath(pathManager.GetConfigDir())
|
|
||||||
|
|
||||||
// 设置环境变量前缀
|
|
||||||
v.SetEnvPrefix("VOIDRAFT")
|
|
||||||
v.AutomaticEnv()
|
|
||||||
|
|
||||||
// 设置默认值
|
|
||||||
setDefaults(v)
|
|
||||||
|
|
||||||
// 构造配置服务实例
|
// 构造配置服务实例
|
||||||
service := &ConfigService{
|
service := &ConfigService{
|
||||||
viper: v,
|
koanf: k,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
pathManager: pathManager,
|
pathManager: pathManager,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化配置通知服务
|
// 初始化配置通知服务
|
||||||
service.notificationService = NewConfigNotificationService(v, logger)
|
service.notificationService = NewConfigNotificationService(k, logger)
|
||||||
|
|
||||||
// 初始化配置
|
// 初始化配置
|
||||||
if err := service.initConfig(); err != nil {
|
if err := service.initConfig(); err != nil {
|
||||||
service.logger.Error("Failed to initialize config", "error", err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 启动配置文件监听
|
// 启动配置文件监听
|
||||||
@@ -96,37 +86,15 @@ func NewConfigService(logger *log.LoggerService, pathManager *PathManager) *Conf
|
|||||||
return service
|
return service
|
||||||
}
|
}
|
||||||
|
|
||||||
// setDefaults 设置默认配置值
|
// setDefaults 设置默认配置
|
||||||
func setDefaults(v *viper.Viper) {
|
func (cs *ConfigService) setDefaults() error {
|
||||||
defaultConfig := models.NewDefaultAppConfig()
|
defaultConfig := models.NewDefaultAppConfig()
|
||||||
|
|
||||||
// 通用设置默认值
|
if err := cs.koanf.Load(structs.Provider(defaultConfig, "json"), nil); err != nil {
|
||||||
v.SetDefault("general.alwaysOnTop", defaultConfig.General.AlwaysOnTop)
|
return &ConfigError{Operation: "load_defaults", Err: err}
|
||||||
v.SetDefault("general.dataPath", defaultConfig.General.DataPath)
|
}
|
||||||
v.SetDefault("general.enableSystemTray", defaultConfig.General.EnableSystemTray)
|
|
||||||
v.SetDefault("general.enableGlobalHotkey", defaultConfig.General.EnableGlobalHotkey)
|
|
||||||
v.SetDefault("general.globalHotkey.ctrl", defaultConfig.General.GlobalHotkey.Ctrl)
|
|
||||||
v.SetDefault("general.globalHotkey.shift", defaultConfig.General.GlobalHotkey.Shift)
|
|
||||||
v.SetDefault("general.globalHotkey.alt", defaultConfig.General.GlobalHotkey.Alt)
|
|
||||||
v.SetDefault("general.globalHotkey.win", defaultConfig.General.GlobalHotkey.Win)
|
|
||||||
v.SetDefault("general.globalHotkey.key", defaultConfig.General.GlobalHotkey.Key)
|
|
||||||
|
|
||||||
// 编辑设置默认值
|
return nil
|
||||||
v.SetDefault("editing.fontSize", defaultConfig.Editing.FontSize)
|
|
||||||
v.SetDefault("editing.fontFamily", defaultConfig.Editing.FontFamily)
|
|
||||||
v.SetDefault("editing.fontWeight", defaultConfig.Editing.FontWeight)
|
|
||||||
v.SetDefault("editing.lineHeight", defaultConfig.Editing.LineHeight)
|
|
||||||
v.SetDefault("editing.enableTabIndent", defaultConfig.Editing.EnableTabIndent)
|
|
||||||
v.SetDefault("editing.tabSize", defaultConfig.Editing.TabSize)
|
|
||||||
v.SetDefault("editing.tabType", defaultConfig.Editing.TabType)
|
|
||||||
v.SetDefault("editing.autoSaveDelay", defaultConfig.Editing.AutoSaveDelay)
|
|
||||||
|
|
||||||
// 外观设置默认值
|
|
||||||
v.SetDefault("appearance.language", defaultConfig.Appearance.Language)
|
|
||||||
v.SetDefault("appearance.systemTheme", defaultConfig.Appearance.SystemTheme)
|
|
||||||
|
|
||||||
// 元数据默认值
|
|
||||||
v.SetDefault("metadata.lastUpdated", defaultConfig.Metadata.LastUpdated)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// initConfig 初始化配置
|
// initConfig 初始化配置
|
||||||
@@ -134,15 +102,16 @@ func (cs *ConfigService) initConfig() error {
|
|||||||
cs.mu.Lock()
|
cs.mu.Lock()
|
||||||
defer cs.mu.Unlock()
|
defer cs.mu.Unlock()
|
||||||
|
|
||||||
// 尝试读取配置文件
|
// 检查配置文件是否存在
|
||||||
if err := cs.viper.ReadInConfig(); err != nil {
|
configPath := cs.pathManager.GetSettingsPath()
|
||||||
var configFileNotFoundError viper.ConfigFileNotFoundError
|
if _, err := os.Stat(configPath); os.IsNotExist(err) {
|
||||||
if errors.As(err, &configFileNotFoundError) {
|
return cs.createDefaultConfig()
|
||||||
// 配置文件不存在,创建默认配置文件
|
}
|
||||||
return cs.createDefaultConfig()
|
|
||||||
}
|
// 配置文件存在,直接加载
|
||||||
// 配置文件存在但读取失败
|
cs.fileProvider = file.Provider(configPath)
|
||||||
return &ConfigError{Operation: "read_config", Err: err}
|
if err := cs.koanf.Load(cs.fileProvider, jsonparser.Parser()); err != nil {
|
||||||
|
return &ConfigError{Operation: "load_config_file", Err: err}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -155,38 +124,50 @@ func (cs *ConfigService) createDefaultConfig() error {
|
|||||||
return &ConfigError{Operation: "create_config_dir", Err: err}
|
return &ConfigError{Operation: "create_config_dir", Err: err}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取默认配置
|
if err := cs.setDefaults(); err != nil {
|
||||||
defaultConfig := models.NewDefaultAppConfig()
|
return err
|
||||||
|
|
||||||
// 使用 JSON marshal 方式设置完整的默认配置
|
|
||||||
configBytes, err := json.MarshalIndent(defaultConfig, "", " ")
|
|
||||||
if err != nil {
|
|
||||||
return &ConfigError{Operation: "marshal_default_config", Err: err}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 写入配置文件
|
if err := cs.writeConfigToFile(); err != nil {
|
||||||
if err := os.WriteFile(cs.pathManager.GetSettingsPath(), configBytes, 0644); err != nil {
|
return err
|
||||||
return &ConfigError{Operation: "write_default_config", Err: err}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重新读取配置文件到viper
|
// 创建文件提供器
|
||||||
if err := cs.viper.ReadInConfig(); err != nil {
|
cs.fileProvider = file.Provider(cs.pathManager.GetSettingsPath())
|
||||||
return &ConfigError{Operation: "read_created_config", Err: err}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if err := cs.koanf.Load(cs.fileProvider, jsonparser.Parser()); err != nil {
|
||||||
|
return &ConfigError{Operation: "load_config_file", Err: err}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// startWatching 启动配置文件监听
|
// startWatching 启动配置文件监听
|
||||||
func (cs *ConfigService) startWatching() {
|
func (cs *ConfigService) startWatching() {
|
||||||
// 设置配置变化回调
|
if cs.fileProvider == nil {
|
||||||
cs.viper.OnConfigChange(func(e fsnotify.Event) {
|
return
|
||||||
|
}
|
||||||
|
err := cs.fileProvider.Watch(func(event interface{}, err error) {
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cs.koanf.Load(cs.fileProvider, jsonparser.Parser())
|
||||||
|
|
||||||
// 使用配置通知服务检查所有已注册的配置变更
|
// 使用配置通知服务检查所有已注册的配置变更
|
||||||
cs.notificationService.CheckConfigChanges()
|
if cs.notificationService != nil {
|
||||||
|
cs.notificationService.CheckConfigChanges()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 启动配置文件监听
|
if err != nil {
|
||||||
cs.viper.WatchConfig()
|
cs.logger.Error("Failed to setup config file watcher", "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// stopWatching 停止配置文件监听
|
||||||
|
func (cs *ConfigService) stopWatching() {
|
||||||
|
if cs.fileProvider != nil {
|
||||||
|
cs.fileProvider.Unwatch()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetConfig 获取完整应用配置
|
// GetConfig 获取完整应用配置
|
||||||
@@ -195,7 +176,7 @@ func (cs *ConfigService) GetConfig() (*models.AppConfig, error) {
|
|||||||
defer cs.mu.RUnlock()
|
defer cs.mu.RUnlock()
|
||||||
|
|
||||||
var config models.AppConfig
|
var config models.AppConfig
|
||||||
if err := cs.viper.Unmarshal(&config); err != nil {
|
if err := cs.koanf.Unmarshal("", &config); err != nil {
|
||||||
return nil, &ConfigError{Operation: "unmarshal_config", Err: err}
|
return nil, &ConfigError{Operation: "unmarshal_config", Err: err}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,70 +188,80 @@ func (cs *ConfigService) Set(key string, value interface{}) error {
|
|||||||
cs.mu.Lock()
|
cs.mu.Lock()
|
||||||
defer cs.mu.Unlock()
|
defer cs.mu.Unlock()
|
||||||
|
|
||||||
// 设置值到viper
|
// 设置值到koanf
|
||||||
cs.viper.Set(key, value)
|
cs.koanf.Set(key, value)
|
||||||
|
|
||||||
// 直接从viper获取配置并构建AppConfig结构
|
|
||||||
var config models.AppConfig
|
|
||||||
if err := cs.viper.Unmarshal(&config); err != nil {
|
|
||||||
return &ConfigError{Operation: "unmarshal_config_for_set", Err: err}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新时间戳
|
// 更新时间戳
|
||||||
config.Metadata.LastUpdated = time.Now().Format(time.RFC3339)
|
cs.koanf.Set("metadata.lastUpdated", time.Now().Format(time.RFC3339))
|
||||||
|
|
||||||
// 直接写入JSON文件
|
// 将配置写回文件
|
||||||
if err := cs.writeConfigToFile(&config); err != nil {
|
return cs.writeConfigToFile()
|
||||||
return &ConfigError{Operation: "set_config", Err: err}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get 获取配置项
|
// Get 获取配置项
|
||||||
func (cs *ConfigService) Get(key string) interface{} {
|
func (cs *ConfigService) Get(key string) interface{} {
|
||||||
cs.mu.RLock()
|
cs.mu.RLock()
|
||||||
defer cs.mu.RUnlock()
|
defer cs.mu.RUnlock()
|
||||||
return cs.viper.Get(key)
|
return cs.koanf.Get(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResetConfig 强制重置所有配置为默认值
|
// ResetConfig 强制重置所有配置为默认值
|
||||||
func (cs *ConfigService) ResetConfig() {
|
func (cs *ConfigService) ResetConfig() error {
|
||||||
cs.mu.Lock()
|
cs.mu.Lock()
|
||||||
defer cs.mu.Unlock()
|
defer cs.mu.Unlock()
|
||||||
|
|
||||||
defaultConfig := models.NewDefaultAppConfig()
|
// 停止文件监听
|
||||||
|
if cs.fileProvider != nil {
|
||||||
// 直接写入JSON文件
|
cs.fileProvider.Unwatch()
|
||||||
if err := cs.writeConfigToFile(defaultConfig); err != nil {
|
cs.fileProvider = nil
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重新读取配置文件到viper
|
// 设置默认配置
|
||||||
if err := cs.viper.ReadInConfig(); err != nil {
|
if err := cs.setDefaults(); err != nil {
|
||||||
return
|
return &ConfigError{Operation: "reset_set_defaults", Err: err}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 写入配置文件
|
||||||
|
if err := cs.writeConfigToFile(); err != nil {
|
||||||
|
return &ConfigError{Operation: "reset_write_config", Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重新创建koanf实例
|
||||||
|
cs.koanf = koanf.New(".")
|
||||||
|
|
||||||
|
// 重新加载默认配置到koanf
|
||||||
|
if err := cs.setDefaults(); err != nil {
|
||||||
|
return &ConfigError{Operation: "reset_reload_defaults", Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重新创建文件提供器
|
||||||
|
cs.fileProvider = file.Provider(cs.pathManager.GetSettingsPath())
|
||||||
|
|
||||||
|
// 重新加载配置文件
|
||||||
|
if err := cs.koanf.Load(cs.fileProvider, jsonparser.Parser()); err != nil {
|
||||||
|
return &ConfigError{Operation: "reset_reload_config", Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重新启动文件监听
|
||||||
|
cs.startWatching()
|
||||||
|
|
||||||
// 手动触发配置变更检查,确保通知系统能感知到变更
|
// 手动触发配置变更检查,确保通知系统能感知到变更
|
||||||
cs.notificationService.CheckConfigChanges()
|
if cs.notificationService != nil {
|
||||||
|
cs.notificationService.CheckConfigChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeConfigToFile 直接写入配置到JSON文件
|
// writeConfigToFile 将配置写回JSON文件
|
||||||
func (cs *ConfigService) writeConfigToFile(config *models.AppConfig) error {
|
func (cs *ConfigService) writeConfigToFile() error {
|
||||||
// 序列化为JSON
|
configBytes, err := cs.koanf.Marshal(jsonparser.Parser())
|
||||||
configBytes, err := json.MarshalIndent(config, "", " ")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to marshal config: %v", err)
|
return &ConfigError{Operation: "marshal_config", Err: err}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 写入文件
|
|
||||||
if err := os.WriteFile(cs.pathManager.GetSettingsPath(), configBytes, 0644); err != nil {
|
if err := os.WriteFile(cs.pathManager.GetSettingsPath(), configBytes, 0644); err != nil {
|
||||||
return fmt.Errorf("failed to write config file: %v", err)
|
return &ConfigError{Operation: "write_config_file", Err: err}
|
||||||
}
|
|
||||||
|
|
||||||
// 重新读取到viper中
|
|
||||||
if err := cs.viper.ReadInConfig(); err != nil {
|
|
||||||
return fmt.Errorf("failed to reload config: %v", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -295,3 +286,12 @@ func (cs *ConfigService) SetDataPathChangeCallback(callback func(oldPath, newPat
|
|||||||
dataPathListener := CreateDataPathListener(callback)
|
dataPathListener := CreateDataPathListener(callback)
|
||||||
return cs.notificationService.RegisterListener(dataPathListener)
|
return cs.notificationService.RegisterListener(dataPathListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ServiceShutdown 关闭服务
|
||||||
|
func (cs *ConfigService) ServiceShutdown() error {
|
||||||
|
cs.stopWatching()
|
||||||
|
if cs.notificationService != nil {
|
||||||
|
cs.notificationService.Cleanup()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@@ -2,23 +2,25 @@ package services
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"voidraft/internal/models"
|
"voidraft/internal/models"
|
||||||
|
|
||||||
"github.com/fsnotify/fsnotify"
|
jsonparser "github.com/knadh/koanf/parsers/json"
|
||||||
"github.com/spf13/viper"
|
"github.com/knadh/koanf/providers/file"
|
||||||
|
"github.com/knadh/koanf/providers/structs"
|
||||||
|
"github.com/knadh/koanf/v2"
|
||||||
"github.com/wailsapp/wails/v3/pkg/services/log"
|
"github.com/wailsapp/wails/v3/pkg/services/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// KeyBindingService 快捷键管理服务
|
// KeyBindingService 快捷键管理服务
|
||||||
type KeyBindingService struct {
|
type KeyBindingService struct {
|
||||||
viper *viper.Viper
|
koanf *koanf.Koanf
|
||||||
logger *log.LoggerService
|
logger *log.LoggerService
|
||||||
pathManager *PathManager
|
pathManager *PathManager
|
||||||
|
fileProvider *file.File
|
||||||
|
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
@@ -60,16 +62,10 @@ func NewKeyBindingService(logger *log.LoggerService, pathManager *PathManager) *
|
|||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
// 创建并配置 Viper
|
k := koanf.New(".")
|
||||||
v := viper.New()
|
|
||||||
v.SetConfigName(pathManager.GetKeybindsName())
|
|
||||||
v.SetConfigType("json")
|
|
||||||
v.AddConfigPath(pathManager.GetConfigDir())
|
|
||||||
v.SetEnvPrefix("VOIDRAFT_KEYBINDING")
|
|
||||||
v.AutomaticEnv()
|
|
||||||
|
|
||||||
service := &KeyBindingService{
|
service := &KeyBindingService{
|
||||||
viper: v,
|
koanf: k,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
pathManager: pathManager,
|
pathManager: pathManager,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
@@ -85,21 +81,21 @@ func NewKeyBindingService(logger *log.LoggerService, pathManager *PathManager) *
|
|||||||
// initialize 初始化配置
|
// initialize 初始化配置
|
||||||
func (kbs *KeyBindingService) initialize() {
|
func (kbs *KeyBindingService) initialize() {
|
||||||
kbs.initOnce.Do(func() {
|
kbs.initOnce.Do(func() {
|
||||||
kbs.setDefaults()
|
|
||||||
|
|
||||||
if err := kbs.initConfig(); err != nil {
|
if err := kbs.initConfig(); err != nil {
|
||||||
kbs.logger.Error("failed to initialize keybinding config", "error", err)
|
kbs.logger.Error("failed to initialize keybinding config", "error", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
kbs.startWatching()
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// setDefaults 设置默认值
|
// setDefaults 设置默认值
|
||||||
func (kbs *KeyBindingService) setDefaults() {
|
func (kbs *KeyBindingService) setDefaults() error {
|
||||||
defaultConfig := models.NewDefaultKeyBindingConfig()
|
defaultConfig := models.NewDefaultKeyBindingConfig()
|
||||||
kbs.viper.SetDefault("keyBindings", defaultConfig.KeyBindings)
|
|
||||||
kbs.viper.SetDefault("metadata.lastUpdated", defaultConfig.Metadata.LastUpdated)
|
if err := kbs.koanf.Load(structs.Provider(defaultConfig, "json"), nil); err != nil {
|
||||||
|
return &KeyBindingError{"load_defaults", "", err}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// initConfig 初始化配置
|
// initConfig 初始化配置
|
||||||
@@ -107,13 +103,18 @@ func (kbs *KeyBindingService) initConfig() error {
|
|||||||
kbs.mu.Lock()
|
kbs.mu.Lock()
|
||||||
defer kbs.mu.Unlock()
|
defer kbs.mu.Unlock()
|
||||||
|
|
||||||
if err := kbs.viper.ReadInConfig(); err != nil {
|
// 检查配置文件是否存在
|
||||||
var configFileNotFoundError viper.ConfigFileNotFoundError
|
configPath := kbs.pathManager.GetKeybindsPath()
|
||||||
if errors.As(err, &configFileNotFoundError) {
|
if _, err := os.Stat(configPath); os.IsNotExist(err) {
|
||||||
return kbs.createDefaultConfig()
|
return kbs.createDefaultConfig()
|
||||||
}
|
|
||||||
return &KeyBindingError{"read_config", "", err}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 配置文件存在,直接加载
|
||||||
|
kbs.fileProvider = file.Provider(configPath)
|
||||||
|
if err := kbs.koanf.Load(kbs.fileProvider, jsonparser.Parser()); err != nil {
|
||||||
|
return &KeyBindingError{"load_config_file", "", err}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,8 +124,11 @@ func (kbs *KeyBindingService) createDefaultConfig() error {
|
|||||||
return &KeyBindingError{"create_config_dir", "", err}
|
return &KeyBindingError{"create_config_dir", "", err}
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultConfig := models.NewDefaultKeyBindingConfig()
|
if err := kbs.setDefaults(); err != nil {
|
||||||
configBytes, err := json.MarshalIndent(defaultConfig, "", " ")
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
configBytes, err := kbs.koanf.Marshal(jsonparser.Parser())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &KeyBindingError{"marshal_config", "", err}
|
return &KeyBindingError{"marshal_config", "", err}
|
||||||
}
|
}
|
||||||
@@ -133,15 +137,12 @@ func (kbs *KeyBindingService) createDefaultConfig() error {
|
|||||||
return &KeyBindingError{"write_config", "", err}
|
return &KeyBindingError{"write_config", "", err}
|
||||||
}
|
}
|
||||||
|
|
||||||
return kbs.viper.ReadInConfig()
|
// 创建文件提供器
|
||||||
}
|
kbs.fileProvider = file.Provider(kbs.pathManager.GetKeybindsPath())
|
||||||
|
if err = kbs.koanf.Load(kbs.fileProvider, jsonparser.Parser()); err != nil {
|
||||||
// startWatching 启动配置文件监听
|
return err
|
||||||
func (kbs *KeyBindingService) startWatching() {
|
}
|
||||||
kbs.viper.OnConfigChange(func(e fsnotify.Event) {
|
return nil
|
||||||
|
|
||||||
})
|
|
||||||
kbs.viper.WatchConfig()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetKeyBindingConfig 获取完整快捷键配置
|
// GetKeyBindingConfig 获取完整快捷键配置
|
||||||
@@ -150,7 +151,7 @@ func (kbs *KeyBindingService) GetKeyBindingConfig() (*models.KeyBindingConfig, e
|
|||||||
defer kbs.mu.RUnlock()
|
defer kbs.mu.RUnlock()
|
||||||
|
|
||||||
var config models.KeyBindingConfig
|
var config models.KeyBindingConfig
|
||||||
if err := kbs.viper.Unmarshal(&config); err != nil {
|
if err := kbs.koanf.Unmarshal("", &config); err != nil {
|
||||||
return nil, &KeyBindingError{"unmarshal_config", "", err}
|
return nil, &KeyBindingError{"unmarshal_config", "", err}
|
||||||
}
|
}
|
||||||
return &config, nil
|
return &config, nil
|
||||||
@@ -165,8 +166,8 @@ func (kbs *KeyBindingService) GetAllKeyBindings() ([]models.KeyBinding, error) {
|
|||||||
return config.KeyBindings, nil
|
return config.KeyBindings, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shutdown 关闭服务
|
// ServiceShutdown 关闭服务
|
||||||
func (kbs *KeyBindingService) Shutdown() error {
|
func (kbs *KeyBindingService) ServiceShutdown() error {
|
||||||
kbs.cancel()
|
kbs.cancel()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@@ -31,11 +31,6 @@ func NewPathManager() *PathManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetConfigDir 获取配置目录路径
|
|
||||||
func (pm *PathManager) GetConfigDir() string {
|
|
||||||
return pm.configDir
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSettingsPath 获取设置文件路径
|
// GetSettingsPath 获取设置文件路径
|
||||||
func (pm *PathManager) GetSettingsPath() string {
|
func (pm *PathManager) GetSettingsPath() string {
|
||||||
return pm.settingsPath
|
return pm.settingsPath
|
||||||
@@ -50,13 +45,3 @@ func (pm *PathManager) GetKeybindsPath() string {
|
|||||||
func (pm *PathManager) EnsureConfigDir() error {
|
func (pm *PathManager) EnsureConfigDir() error {
|
||||||
return os.MkdirAll(pm.configDir, 0755)
|
return os.MkdirAll(pm.configDir, 0755)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetConfigName 获取配置文件
|
|
||||||
func (pm *PathManager) GetConfigName() string {
|
|
||||||
return "settings"
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetKeybindsName 获取快捷键配置文件名
|
|
||||||
func (pm *PathManager) GetKeybindsName() string {
|
|
||||||
return "keybindings"
|
|
||||||
}
|
|
||||||
|
Reference in New Issue
Block a user