🚧 Refactor backup service

This commit is contained in:
2025-12-14 23:48:52 +08:00
parent cc4c2189dc
commit 67d35626cb
47 changed files with 2184 additions and 489 deletions

View File

@@ -1,6 +1,5 @@
import { Extension } from '@codemirror/state';
import { useKeybindingStore } from '@/stores/keybindingStore';
import { useExtensionStore } from '@/stores/extensionStore';
import { Manager } from './manager';
/**
@@ -8,24 +7,13 @@ import { Manager } from './manager';
*/
export const createDynamicKeymapExtension = async (): Promise<Extension> => {
const keybindingStore = useKeybindingStore();
const extensionStore = useExtensionStore();
// 确保快捷键配置已加载
if (keybindingStore.keyBindings.length === 0) {
await keybindingStore.loadKeyBindings();
}
// 确保扩展配置已加载
if (extensionStore.extensions.length === 0) {
await extensionStore.loadExtensions();
}
// 获取启用的扩展key列表
const enabledExtensionKeys = extensionStore.enabledExtensions
.map(ext => ext.key)
.filter((key): key is string => key !== undefined);
return Manager.createKeymapExtension(keybindingStore.keyBindings, enabledExtensionKeys);
return Manager.createKeymapExtension(keybindingStore.keyBindings);
};
/**
@@ -34,14 +22,7 @@ export const createDynamicKeymapExtension = async (): Promise<Extension> => {
*/
export const updateKeymapExtension = (view: any): void => {
const keybindingStore = useKeybindingStore();
const extensionStore = useExtensionStore();
// 获取启用的扩展key列表
const enabledExtensionKeys = extensionStore.enabledExtensions
.map(ext => ext.key)
.filter((key): key is string => key !== undefined);
Manager.updateKeymap(view, keybindingStore.keyBindings, enabledExtensionKeys);
Manager.updateKeymap(view, keybindingStore.keyBindings);
};
// 导出相关模块

View File

@@ -14,10 +14,9 @@ export class Manager {
/**
* 将后端快捷键配置转换为CodeMirror快捷键绑定
* @param keyBindings 后端快捷键配置列表
* @param enabledExtensions 启用的扩展key列表如果不提供则使用所有启用的快捷键
* @returns 转换结果
*/
static convertToKeyBindings(keyBindings: KeyBindingConfig[], enabledExtensions?: string[]): KeymapResult {
static convertToKeyBindings(keyBindings: KeyBindingConfig[]): KeymapResult {
const result: KeyBinding[] = [];
for (const binding of keyBindings) {
@@ -26,11 +25,6 @@ export class Manager {
continue;
}
// 如果提供了扩展列表,则只处理启用扩展的快捷键
if (enabledExtensions && binding.extension && !enabledExtensions.includes(binding.extension)) {
continue;
}
// 检查命令是否已注册(使用 key 字段作为命令标识符)
if (!binding.key || !isCommandRegistered(binding.key)) {
continue;
@@ -59,13 +53,10 @@ export class Manager {
/**
* 创建CodeMirror快捷键扩展
* @param keyBindings 后端快捷键配置列表
* @param enabledExtensions 启用的扩展key列表
* @returns CodeMirror扩展
*/
static createKeymapExtension(keyBindings: KeyBindingConfig[], enabledExtensions?: string[]): Extension {
const {keyBindings: cmKeyBindings} =
this.convertToKeyBindings(keyBindings, enabledExtensions);
static createKeymapExtension(keyBindings: KeyBindingConfig[]): Extension {
const {keyBindings: cmKeyBindings} = this.convertToKeyBindings(keyBindings);
return this.compartment.of(keymap.of(cmKeyBindings));
}
@@ -73,12 +64,9 @@ export class Manager {
* 动态更新快捷键扩展
* @param view 编辑器视图
* @param keyBindings 后端快捷键配置列表
* @param enabledExtensions 启用的扩展key列表
*/
static updateKeymap(view: any, keyBindings: KeyBindingConfig[], enabledExtensions: string[]): void {
const {keyBindings: cmKeyBindings} =
this.convertToKeyBindings(keyBindings, enabledExtensions);
static updateKeymap(view: any, keyBindings: KeyBindingConfig[]): void {
const {keyBindings: cmKeyBindings} = this.convertToKeyBindings(keyBindings);
view.dispatch({
effects: this.compartment.reconfigure(keymap.of(cmKeyBindings))
});

View File

@@ -2,7 +2,7 @@
import {useConfigStore} from '@/stores/configStore';
import {useBackupStore} from '@/stores/backupStore';
import {useI18n} from 'vue-i18n';
import {computed} from 'vue';
import {computed, ref, watch, onUnmounted} from 'vue';
import SettingSection from '../components/SettingSection.vue';
import SettingItem from '../components/SettingItem.vue';
import ToggleSwitch from '../components/ToggleSwitch.vue';
@@ -13,6 +13,37 @@ const {t} = useI18n();
const configStore = useConfigStore();
const backupStore = useBackupStore();
// 消息显示状态
const message = ref<string | null>(null);
const isError = ref(false);
let messageTimer: ReturnType<typeof setTimeout> | null = null;
const clearMessage = () => {
if (messageTimer) {
clearTimeout(messageTimer);
messageTimer = null;
}
message.value = null;
};
// 监听同步完成,显示消息并自动消失
watch(() => backupStore.isSyncing, (syncing, wasSyncing) => {
if (wasSyncing && !syncing) {
clearMessage();
if (backupStore.error) {
message.value = backupStore.error;
isError.value = true;
messageTimer = setTimeout(clearMessage, 5000);
} else {
message.value = 'Sync successful';
isError.value = false;
messageTimer = setTimeout(clearMessage, 3000);
}
}
});
onUnmounted(clearMessage);
const authMethodOptions = computed(() => [
{value: AuthMethod.Token, label: t('settings.backup.authMethods.token')},
{value: AuthMethod.SSHKey, label: t('settings.backup.authMethods.sshKey')},
@@ -172,18 +203,18 @@ const selectSshKeyFile = async () => {
<!-- 备份操作 -->
<SettingSection :title="t('settings.backup.backupOperations')">
<SettingItem
:title="t('settings.backup.pushToRemote')"
:description="backupStore.message || undefined"
:descriptionType="backupStore.message ? (backupStore.isError ? 'error' : 'success') : 'default'"
:title="t('settings.backup.syncToRemote')"
:description="message || undefined"
:descriptionType="message ? (isError ? 'error' : 'success') : 'default'"
>
<button
class="push-button"
@click="backupStore.pushToRemote"
:disabled="!configStore.config.backup.enabled || !configStore.config.backup.repo_url || backupStore.isPushing"
:class="{ 'backing-up': backupStore.isPushing }"
class="sync-button"
@click="backupStore.sync"
:disabled="!configStore.config.backup.enabled || !configStore.config.backup.repo_url || backupStore.isSyncing"
:class="{ 'syncing': backupStore.isSyncing }"
>
<span v-if="backupStore.isPushing" class="loading-spinner"></span>
{{ backupStore.isPushing ? t('settings.backup.pushing') : t('settings.backup.actions.push') }}
<span v-if="backupStore.isSyncing" class="loading-spinner"></span>
{{ backupStore.isSyncing ? t('settings.backup.syncing') : t('settings.backup.actions.sync') }}
</button>
</SettingItem>
</SettingSection>
@@ -257,7 +288,7 @@ const selectSshKeyFile = async () => {
}
// 按钮样式
.push-button {
.sync-button {
padding: 8px 16px;
background-color: var(--settings-input-bg);
border: 1px solid var(--settings-input-border);
@@ -294,7 +325,7 @@ const selectSshKeyFile = async () => {
animation: spin 1s linear infinite;
}
&.backing-up {
&.syncing {
background-color: #2196f3;
border-color: #2196f3;
color: white;

View File

@@ -3,33 +3,27 @@ import { useI18n } from 'vue-i18n';
import { onMounted, computed } from 'vue';
import SettingSection from '../components/SettingSection.vue';
import { useKeybindingStore } from '@/stores/keybindingStore';
import { useExtensionStore } from '@/stores/extensionStore';
import { useSystemStore } from '@/stores/systemStore';
import { getCommandDescription } from '@/views/editor/keymap/commands';
import { KeyBindingKey } from '@/../bindings/voidraft/internal/models/models';
const { t } = useI18n();
const keybindingStore = useKeybindingStore();
const extensionStore = useExtensionStore();
const systemStore = useSystemStore();
// 加载数据
onMounted(async () => {
await keybindingStore.loadKeyBindings();
await extensionStore.loadExtensions();
});
// 从store中获取快捷键数据并转换为显示格式
const keyBindings = computed(() => {
// 只显示启用扩展的快捷键
const enabledExtensionIds = new Set(extensionStore.enabledExtensionIds);
return keybindingStore.keyBindings
.filter(kb => kb.enabled && (!kb.extension || enabledExtensionIds.has(kb.extension)))
.filter(kb => kb.enabled)
.map(kb => ({
id: kb.key,
keys: parseKeyBinding(kb.command || '', kb.key),
category: kb.extension || '',
key: kb.key,
command: parseKeyBinding(kb.command || '', kb.key),
extension: kb.extension || '',
description: kb.key ? (getCommandDescription(kb.key) || kb.key) : ''
}));
});
@@ -204,25 +198,25 @@ const parseKeyBinding = (keyStr: string, keyBindingKey?: string): string[] => {
<div class="key-bindings-container">
<div class="key-bindings-header">
<div class="keybinding-col">{{ t('keybindings.headers.shortcut') }}</div>
<div class="category-col">{{ t('keybindings.headers.category') }}</div>
<div class="extension-col">{{ t('keybindings.headers.extension') }}</div>
<div class="description-col">{{ t('keybindings.headers.description') }}</div>
</div>
<div
v-for="binding in keyBindings"
:key="binding.id"
:key="binding.key"
class="key-binding-row"
>
<div class="keybinding-col">
<span
v-for="(key, index) in binding.keys"
v-for="(key, index) in binding.command"
:key="index"
class="key-badge"
>
{{ key }}
</span>
</div>
<div class="category-col">{{ binding.category }}</div>
<div class="extension-col">{{ binding.extension }}</div>
<div class="description-col">{{ binding.description }}</div>
</div>
</div>
@@ -276,7 +270,7 @@ const parseKeyBinding = (keyStr: string, keyBindingKey?: string): string[] => {
}
}
.category-col {
.extension-col {
width: 80px;
padding: 0 10px 0 0;
font-size: 13px;