Added the backup feature

This commit is contained in:
2025-07-17 00:12:00 +08:00
parent b4b0ad9bba
commit 9fff7bcfca
39 changed files with 1876 additions and 1018 deletions

View File

@@ -1,9 +0,0 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
import * as Service from "./service.js";
export {
Service
};
export * from "./models.js";

View File

@@ -1,51 +0,0 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import {Create as $Create} from "@wailsio/runtime";
export class Config {
/**
* DBSource is the database URI to use.
* The string ":memory:" can be used to create an in-memory database.
* The sqlite driver can be configured through query parameters.
* For more details see https://pkg.go.dev/modernc.org/sqlite#Driver.Open
*/
"DBSource": string;
/** Creates a new Config instance. */
constructor($$source: Partial<Config> = {}) {
if (!("DBSource" in $$source)) {
this["DBSource"] = "";
}
Object.assign(this, $$source);
}
/**
* Creates a new Config instance from a string or object.
*/
static createFrom($$source: any = {}): Config {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new Config($$parsedSource as Partial<Config>);
}
}
/**
* Row holds a single row in the result of a query.
* It is a key-value map where keys are column names.
*/
export type Row = { [_: string]: any };
/**
* Rows holds the result of a query
* as an array of key-value maps where keys are column names.
*/
export type Rows = Row[];
/**
* Stmt wraps a prepared sql statement pointer.
* It provides the same methods as the [sql.Stmt] type.
*/
export type Stmt = string;

View File

@@ -1,223 +0,0 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import {Call as $Call, Create as $Create} from "@wailsio/runtime";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as application$0 from "../../application/models.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as $models from "./models.js";
export {
ExecContext as Execute,
QueryContext as Query
};
import { Stmt } from "./stmt.js";
/**
* Prepare creates a prepared statement for later queries or executions.
* Multiple queries or executions may be run concurrently from the returned statement.
*
* The caller must call the statement's Close method when it is no longer needed.
* Statements are closed automatically
* when the connection they are associated with is closed.
*
* Prepare supports early cancellation.
*/
export function Prepare(query: string): Promise<Stmt | null> & { cancel(): void } {
const promise = PrepareContext(query);
const wrapper: any = (promise.then(function (id) {
return id == null ? null : new Stmt(
ClosePrepared.bind(null, id),
ExecPrepared.bind(null, id),
QueryPrepared.bind(null, id));
}));
wrapper.cancel = promise.cancel;
return wrapper;
}
/**
* Close closes the current database connection if one is open, otherwise has no effect.
* Additionally, Close closes all open prepared statements associated to the connection.
*
* Even when a non-nil error is returned,
* the database service is left in a consistent state,
* ready for a call to [Service.Open].
*/
export function Close(): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(1888105376) as any;
return $resultPromise;
}
/**
* ClosePrepared closes a prepared statement
* obtained with [Service.Prepare] or [Service.PrepareContext].
* ClosePrepared is idempotent:
* it has no effect on prepared statements that are already closed.
*/
function ClosePrepared(stmt: $models.Stmt | null): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(2526200629, stmt) as any;
return $resultPromise;
}
/**
* Configure changes the database service configuration.
* The connection state at call time is preserved.
* Consumers will need to call [Service.Open] manually after Configure
* in order to reconnect with the new configuration.
*
* See [NewWithConfig] for details on configuration.
*/
export function Configure(config: $models.Config | null): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(1939578712, config) as any;
return $resultPromise;
}
/**
* ExecContext executes a query without returning any rows.
* It supports early cancellation.
*/
function ExecContext(query: string, ...args: any[]): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(674944556, query, args) as any;
return $resultPromise;
}
/**
* ExecPrepared executes a prepared statement
* obtained with [Service.Prepare] or [Service.PrepareContext]
* without returning any rows.
* It supports early cancellation.
*/
function ExecPrepared(stmt: $models.Stmt | null, ...args: any[]): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(2086877656, stmt, args) as any;
return $resultPromise;
}
/**
* Execute executes a query without returning any rows.
*/
export function Execute(query: string, ...args: any[]): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(3811930203, query, args) as any;
return $resultPromise;
}
/**
* Open validates the current configuration,
* closes the current connection if one is present,
* then opens and validates a new connection.
*
* Even when a non-nil error is returned,
* the database service is left in a consistent state,
* ready for a new call to Open.
*/
export function Open(): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(2012175612) as any;
return $resultPromise;
}
/**
* Prepare creates a prepared statement for later queries or executions.
* Multiple queries or executions may be run concurrently from the returned statement.
*
* The caller should call the statement's Close method when it is no longer needed.
* Statements are closed automatically
* when the connection they are associated with is closed.
*/
export function Prepare(query: string): Promise<$models.Stmt | null> & { cancel(): void } {
let $resultPromise = $Call.ByID(1801965143, query) as any;
return $resultPromise;
}
/**
* PrepareContext creates a prepared statement for later queries or executions.
* Multiple queries or executions may be run concurrently from the returned statement.
*
* The caller must call the statement's Close method when it is no longer needed.
* Statements are closed automatically
* when the connection they are associated with is closed.
*
* PrepareContext supports early cancellation.
*/
function PrepareContext(query: string): Promise<$models.Stmt | null> & { cancel(): void } {
let $resultPromise = $Call.ByID(570941694, query) as any;
return $resultPromise;
}
/**
* Query executes a query and returns a slice of key-value records,
* one per row, with column names as keys.
*/
export function Query(query: string, ...args: any[]): Promise<$models.Rows> & { cancel(): void } {
let $resultPromise = $Call.ByID(860757720, query, args) as any;
let $typingPromise = $resultPromise.then(($result: any) => {
return $$createType1($result);
}) as any;
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
return $typingPromise;
}
/**
* QueryContext executes a query and returns a slice of key-value records,
* one per row, with column names as keys.
* It supports early cancellation, returning the slice of results fetched so far.
*/
function QueryContext(query: string, ...args: any[]): Promise<$models.Rows> & { cancel(): void } {
let $resultPromise = $Call.ByID(4115542347, query, args) as any;
let $typingPromise = $resultPromise.then(($result: any) => {
return $$createType1($result);
}) as any;
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
return $typingPromise;
}
/**
* QueryPrepared executes a prepared statement
* obtained with [Service.Prepare] or [Service.PrepareContext]
* and returns a slice of key-value records, one per row, with column names as keys.
* It supports early cancellation, returning the slice of results fetched so far.
*/
function QueryPrepared(stmt: $models.Stmt | null, ...args: any[]): Promise<$models.Rows> & { cancel(): void } {
let $resultPromise = $Call.ByID(3885083725, stmt, args) as any;
let $typingPromise = $resultPromise.then(($result: any) => {
return $$createType1($result);
}) as any;
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
return $typingPromise;
}
/**
* ServiceName returns the name of the plugin.
* You should use the go module format e.g. github.com/myuser/myplugin
*/
export function ServiceName(): Promise<string> & { cancel(): void } {
let $resultPromise = $Call.ByID(1637123084) as any;
return $resultPromise;
}
/**
* ServiceShutdown closes the database connection.
* It returns a non-nil error in case of failures.
*/
export function ServiceShutdown(): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(3650435925) as any;
return $resultPromise;
}
/**
* ServiceStartup opens the database connection.
* It returns a non-nil error in case of failures.
*/
export function ServiceStartup(options: application$0.ServiceOptions): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(1113159936, options) as any;
return $resultPromise;
}
// Private type creation functions
const $$createType0 = $Create.Map($Create.Any, $Create.Any);
const $$createType1 = $Create.Array($$createType0);

View File

@@ -1,79 +0,0 @@
//@ts-check
//@ts-ignore: Unused imports
import * as $models from "./models.js";
const execSymbol = Symbol("exec"),
querySymbol = Symbol("query"),
closeSymbol = Symbol("close");
/**
* Stmt represents a prepared statement for later queries or executions.
* Multiple queries or executions may be run concurrently on the same statement.
*
* The caller must call the statement's Close method when it is no longer needed.
* Statements are closed automatically
* when the connection they are associated with is closed.
*/
export class Stmt {
/**
* Constructs a new prepared statement instance.
* @param {(...args: any[]) => Promise<void>} close
* @param {(...args: any[]) => Promise<void> & { cancel(): void }} exec
* @param {(...args: any[]) => Promise<$models.Rows> & { cancel(): void }} query
*/
constructor(close, exec, query) {
/**
* @member
* @private
* @type {typeof close}
*/
this[closeSymbol] = close;
/**
* @member
* @private
* @type {typeof exec}
*/
this[execSymbol] = exec;
/**
* @member
* @private
* @type {typeof query}
*/
this[querySymbol] = query;
}
/**
* Closes the prepared statement.
* It has no effect when the statement is already closed.
* @returns {Promise<void>}
*/
Close() {
return this[closeSymbol]();
}
/**
* Executes the prepared statement without returning any rows.
* It supports early cancellation.
*
* @param {any[]} args
* @returns {Promise<void> & { cancel(): void }}
*/
Exec(...args) {
return this[execSymbol](...args);
}
/**
* Executes the prepared statement
* and returns a slice of key-value records, one per row, with column names as keys.
* It supports early cancellation, returning the array of results fetched so far.
*
* @param {any[]} args
* @returns {Promise<$models.Rows> & { cancel(): void }}
*/
Query(...args) {
return this[querySymbol](...args);
}
}

View File

@@ -34,9 +34,9 @@ export class AppConfig {
"updates": UpdatesConfig;
/**
* Git同步设置
* Git备份设置
*/
"sync": GitSyncConfig;
"backup": GitBackupConfig;
/**
* 配置元数据
@@ -57,8 +57,8 @@ export class AppConfig {
if (!("updates" in $$source)) {
this["updates"] = (new UpdatesConfig());
}
if (!("sync" in $$source)) {
this["sync"] = (new GitSyncConfig());
if (!("backup" in $$source)) {
this["backup"] = (new GitBackupConfig());
}
if (!("metadata" in $$source)) {
this["metadata"] = (new ConfigMetadata());
@@ -90,8 +90,8 @@ export class AppConfig {
if ("updates" in $$parsedSource) {
$$parsedSource["updates"] = $$createField3_0($$parsedSource["updates"]);
}
if ("sync" in $$parsedSource) {
$$parsedSource["sync"] = $$createField4_0($$parsedSource["sync"]);
if ("backup" in $$parsedSource) {
$$parsedSource["backup"] = $$createField4_0($$parsedSource["backup"]);
}
if ("metadata" in $$parsedSource) {
$$parsedSource["metadata"] = $$createField5_0($$parsedSource["metadata"]);
@@ -148,6 +148,8 @@ export class AppearanceConfig {
}
/**
* Git备份相关类型定义
*
* AuthMethod 定义Git认证方式
*/
export enum AuthMethod {
@@ -157,18 +159,10 @@ export enum AuthMethod {
$zero = "",
/**
* 个人访问令牌
* 认证方式
*/
Token = "token",
/**
* SSH密钥
*/
SSHKey = "ssh_key",
/**
* 用户名密码
*/
UserPass = "user_pass",
};
@@ -577,12 +571,11 @@ export class GeneralConfig {
}
/**
* GitSyncConfig 保存Git同步的配置信息
* GitBackupConfig Git备份配置
*/
export class GitSyncConfig {
export class GitBackupConfig {
"enabled": boolean;
"repo_url": string;
"branch": string;
"auth_method": AuthMethod;
"username"?: string;
"password"?: string;
@@ -591,73 +584,38 @@ export class GitSyncConfig {
"ssh_key_passphrase"?: string;
/**
* 同步间隔(分钟
* 分钟
*/
"sync_interval": number;
"last_sync_time": time$0.Time;
"backup_interval": number;
"auto_backup": boolean;
/**
* 是否启用自动同步
*/
"auto_sync": boolean;
"local_repo_path": string;
/**
* 合并冲突策略
*/
"sync_strategy": SyncStrategy;
/**
* 要同步的文件列表,默认为数据库文件
*/
"files_to_sync": string[];
/** Creates a new GitSyncConfig instance. */
constructor($$source: Partial<GitSyncConfig> = {}) {
/** Creates a new GitBackupConfig instance. */
constructor($$source: Partial<GitBackupConfig> = {}) {
if (!("enabled" in $$source)) {
this["enabled"] = false;
}
if (!("repo_url" in $$source)) {
this["repo_url"] = "";
}
if (!("branch" in $$source)) {
this["branch"] = "";
}
if (!("auth_method" in $$source)) {
this["auth_method"] = ("" as AuthMethod);
}
if (!("sync_interval" in $$source)) {
this["sync_interval"] = 0;
if (!("backup_interval" in $$source)) {
this["backup_interval"] = 0;
}
if (!("last_sync_time" in $$source)) {
this["last_sync_time"] = null;
}
if (!("auto_sync" in $$source)) {
this["auto_sync"] = false;
}
if (!("local_repo_path" in $$source)) {
this["local_repo_path"] = "";
}
if (!("sync_strategy" in $$source)) {
this["sync_strategy"] = ("" as SyncStrategy);
}
if (!("files_to_sync" in $$source)) {
this["files_to_sync"] = [];
if (!("auto_backup" in $$source)) {
this["auto_backup"] = false;
}
Object.assign(this, $$source);
}
/**
* Creates a new GitSyncConfig instance from a string or object.
* Creates a new GitBackupConfig instance from a string or object.
*/
static createFrom($$source: any = {}): GitSyncConfig {
const $$createField14_0 = $$createType11;
static createFrom($$source: any = {}): GitBackupConfig {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
if ("files_to_sync" in $$parsedSource) {
$$parsedSource["files_to_sync"] = $$createField14_0($$parsedSource["files_to_sync"]);
}
return new GitSyncConfig($$parsedSource as Partial<GitSyncConfig>);
return new GitBackupConfig($$parsedSource as Partial<GitBackupConfig>);
}
}
@@ -1168,26 +1126,6 @@ export enum LanguageType {
LangEnUS = "en-US",
};
/**
* SyncStrategy 定义同步策略
*/
export enum SyncStrategy {
/**
* The Go zero value for the underlying type of the enum.
*/
$zero = "",
/**
* LocalFirst 本地优先:如有冲突,保留本地修改
*/
LocalFirst = "local_first",
/**
* RemoteFirst 远程优先:如有冲突,采用远程版本
*/
RemoteFirst = "remote_first",
};
/**
* SystemThemeType 系统主题类型定义
*/
@@ -1539,8 +1477,8 @@ export class UpdatesConfig {
* Creates a new UpdatesConfig instance from a string or object.
*/
static createFrom($$source: any = {}): UpdatesConfig {
const $$createField6_0 = $$createType12;
const $$createField7_0 = $$createType13;
const $$createField6_0 = $$createType11;
const $$createField7_0 = $$createType12;
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
if ("github" in $$parsedSource) {
$$parsedSource["github"] = $$createField6_0($$parsedSource["github"]);
@@ -1557,7 +1495,7 @@ const $$createType0 = GeneralConfig.createFrom;
const $$createType1 = EditingConfig.createFrom;
const $$createType2 = AppearanceConfig.createFrom;
const $$createType3 = UpdatesConfig.createFrom;
const $$createType4 = GitSyncConfig.createFrom;
const $$createType4 = GitBackupConfig.createFrom;
const $$createType5 = ConfigMetadata.createFrom;
const $$createType6 = CustomThemeConfig.createFrom;
const $$createType7 = ThemeColorConfig.createFrom;
@@ -1569,6 +1507,5 @@ var $$createType8 = (function $$initCreateType8(...args): any {
});
const $$createType9 = $Create.Map($Create.Any, $Create.Any);
const $$createType10 = HotkeyCombo.createFrom;
const $$createType11 = $Create.Array($Create.Any);
const $$createType12 = GithubConfig.createFrom;
const $$createType13 = GiteaConfig.createFrom;
const $$createType11 = GithubConfig.createFrom;
const $$createType12 = GiteaConfig.createFrom;

View File

@@ -0,0 +1,71 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
/**
* BackupService 提供基于Git的备份功能
* @module
*/
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import {Call as $Call, Create as $Create} from "@wailsio/runtime";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as models$0 from "../models/models.js";
/**
* HandleConfigChange 处理备份配置变更
*/
export function HandleConfigChange(config: models$0.GitBackupConfig | null): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(395287784, config) as any;
return $resultPromise;
}
/**
* Initialize 初始化备份服务
*/
export function Initialize(): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(1052437974) as any;
return $resultPromise;
}
/**
* PushToRemote 推送本地更改到远程仓库
*/
export function PushToRemote(): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(262644139) as any;
return $resultPromise;
}
/**
* Reinitialize 重新初始化备份服务,用于响应配置变更
*/
export function Reinitialize(): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(301562543) as any;
return $resultPromise;
}
/**
* ServiceShutdown 服务关闭时的清理工作
*/
export function ServiceShutdown(): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(422131801) as any;
return $resultPromise;
}
/**
* StartAutoBackup 启动自动备份定时器
*/
export function StartAutoBackup(): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(3035755449) as any;
return $resultPromise;
}
/**
* StopAutoBackup 停止自动备份
*/
export function StopAutoBackup(): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(2641894021) as any;
return $resultPromise;
}

View File

@@ -58,6 +58,14 @@ export function Set(key: string, value: any): Promise<void> & { cancel(): void }
return $resultPromise;
}
/**
* SetBackupConfigChangeCallback 设置备份配置变更回调
*/
export function SetBackupConfigChangeCallback(callback: any): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(3264871659, callback) as any;
return $resultPromise;
}
/**
* SetDataPathChangeCallback 设置数据路径配置变更回调
*/

View File

@@ -22,6 +22,14 @@ export function SelectDirectory(): Promise<string> & { cancel(): void } {
return $resultPromise;
}
/**
* SelectFile 打开文件选择对话框
*/
export function SelectFile(): Promise<string> & { cancel(): void } {
let $resultPromise = $Call.ByID(37302920) as any;
return $resultPromise;
}
/**
* SetWindow 设置绑定的窗口
*/

View File

@@ -1,6 +1,7 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
import * as BackupService from "./backupservice.js";
import * as ConfigService from "./configservice.js";
import * as DatabaseService from "./databaseservice.js";
import * as DialogService from "./dialogservice.js";
@@ -16,6 +17,7 @@ import * as TranslationService from "./translationservice.js";
import * as TrayService from "./trayservice.js";
import * as WindowService from "./windowservice.js";
export {
BackupService,
ConfigService,
DatabaseService,
DialogService,

View File

@@ -37,7 +37,7 @@
"@codemirror/lint": "^6.8.5",
"@codemirror/search": "^6.5.11",
"@codemirror/state": "^6.5.2",
"@codemirror/view": "^6.38.0",
"@codemirror/view": "^6.38.1",
"@lezer/highlight": "^1.2.1",
"@lezer/lr": "^1.4.2",
"codemirror": "^6.0.2",
@@ -53,25 +53,24 @@
"remarkable": "^2.0.1",
"sass": "^1.89.2",
"vue": "^3.5.17",
"vue-i18n": "^11.1.9",
"vue-i18n": "^11.1.10",
"vue-pick-colors": "^1.8.0",
"vue-router": "^4.5.1"
},
"devDependencies": {
"@eslint/js": "^9.30.1",
"@eslint/js": "^9.31.0",
"@lezer/generator": "^1.8.0",
"@types/lodash": "^4.17.20",
"@types/node": "^24.0.12",
"@types/node": "^24.0.14",
"@types/remarkable": "^2.0.8",
"@vitejs/plugin-vue": "^6.0.0",
"@wailsio/runtime": "latest",
"eslint": "^9.30.1",
"eslint": "^9.31.0",
"eslint-plugin-vue": "^10.3.0",
"globals": "^16.3.0",
"typescript": "^5.8.3",
"typescript-eslint": "^8.36.0",
"typescript-eslint": "^8.37.0",
"unplugin-vue-components": "^28.8.0",
"vite": "^7.0.3",
"vite": "^7.0.4",
"vue-eslint-parser": "^10.2.0",
"vue-tsc": "^3.0.1"
}
@@ -507,9 +506,9 @@
}
},
"node_modules/@codemirror/view": {
"version": "6.38.0",
"resolved": "https://registry.npmmirror.com/@codemirror/view/-/view-6.38.0.tgz",
"integrity": "sha512-yvSchUwHOdupXkd7xJ0ob36jdsSR/I+/C+VbY0ffBiL5NiSTEBDfB1ZGWbbIlDd5xgdUkody+lukAdOxYrOBeg==",
"version": "6.38.1",
"resolved": "https://registry.npmmirror.com/@codemirror/view/-/view-6.38.1.tgz",
"integrity": "sha512-RmTOkE7hRU3OVREqFVITWHz6ocgBjv08GoePscAakgVQfciA3SGCEk7mb9IzwW61cKKmlTpHXG6DUE5Ubx+MGQ==",
"license": "MIT",
"dependencies": {
"@codemirror/state": "^6.5.0",
@@ -1061,9 +1060,9 @@
}
},
"node_modules/@eslint/js": {
"version": "9.30.1",
"resolved": "https://registry.npmmirror.com/@eslint/js/-/js-9.30.1.tgz",
"integrity": "sha512-zXhuECFlyep42KZUhWjfvsmXGX39W8K8LFb8AWXM9gSV9dQB+MrJGLKvW6Zw0Ggnbpw0VHTtrhFXYe3Gym18jg==",
"version": "9.31.0",
"resolved": "https://registry.npmmirror.com/@eslint/js/-/js-9.31.0.tgz",
"integrity": "sha512-LOm5OVt7D4qiKCqoiPbA7LWmI+tbw1VbTUowBcUMgQSuM6poJufkFkYDcQpo5KfgD39TnNySV26QjOh7VFpSyw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -1164,13 +1163,13 @@
}
},
"node_modules/@intlify/core-base": {
"version": "11.1.9",
"resolved": "https://registry.npmmirror.com/@intlify/core-base/-/core-base-11.1.9.tgz",
"integrity": "sha512-Lrdi4wp3XnGhWmB/mMD/XtfGUw1Jt+PGpZI/M63X1ZqhTDjNHRVCs/i8vv8U1cwaj1A9fb0bkCQHLSL0SK+pIQ==",
"version": "11.1.10",
"resolved": "https://registry.npmmirror.com/@intlify/core-base/-/core-base-11.1.10.tgz",
"integrity": "sha512-JhRb40hD93Vk0BgMgDc/xMIFtdXPHoytzeK6VafBNOj6bb6oUZrGamXkBKecMsmGvDQQaPRGG2zpa25VCw8pyw==",
"license": "MIT",
"dependencies": {
"@intlify/message-compiler": "11.1.9",
"@intlify/shared": "11.1.9"
"@intlify/message-compiler": "11.1.10",
"@intlify/shared": "11.1.10"
},
"engines": {
"node": ">= 16"
@@ -1180,12 +1179,12 @@
}
},
"node_modules/@intlify/message-compiler": {
"version": "11.1.9",
"resolved": "https://registry.npmmirror.com/@intlify/message-compiler/-/message-compiler-11.1.9.tgz",
"integrity": "sha512-84SNs3Ikjg0rD1bOuchzb3iK1vR2/8nxrkyccIl5DjFTeMzE/Fxv6X+A7RN5ZXjEWelc1p5D4kHA6HEOhlKL5Q==",
"version": "11.1.10",
"resolved": "https://registry.npmmirror.com/@intlify/message-compiler/-/message-compiler-11.1.10.tgz",
"integrity": "sha512-TABl3c8tSLWbcD+jkQTyBhrnW251dzqW39MPgEUCsd69Ua3ceoimsbIzvkcPzzZvt1QDxNkenMht+5//V3JvLQ==",
"license": "MIT",
"dependencies": {
"@intlify/shared": "11.1.9",
"@intlify/shared": "11.1.10",
"source-map-js": "^1.0.2"
},
"engines": {
@@ -1196,9 +1195,9 @@
}
},
"node_modules/@intlify/shared": {
"version": "11.1.9",
"resolved": "https://registry.npmmirror.com/@intlify/shared/-/shared-11.1.9.tgz",
"integrity": "sha512-H/83xgU1l8ox+qG305p6ucmoy93qyjIPnvxGWRA7YdOoHe1tIiW9IlEu4lTdsOR7cfP1ecrwyflQSqXdXBacXA==",
"version": "11.1.10",
"resolved": "https://registry.npmmirror.com/@intlify/shared/-/shared-11.1.10.tgz",
"integrity": "sha512-6ZW/f3Zzjxfa1Wh0tYQI5pLKUtU+SY7l70pEG+0yd0zjcsYcK0EBt6Fz30Dy0tZhEqemziQQy2aNU3GJzyrMUA==",
"license": "MIT",
"engines": {
"node": ">= 16"
@@ -2134,17 +2133,10 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/lodash": {
"version": "4.17.20",
"resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.20.tgz",
"integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/node": {
"version": "24.0.12",
"resolved": "https://registry.npmmirror.com/@types/node/-/node-24.0.12.tgz",
"integrity": "sha512-LtOrbvDf5ndC9Xi+4QZjVL0woFymF/xSTKZKPgrrl7H7XoeDvnD+E2IclKVDyaK9UM756W/3BXqSU+JEHopA9g==",
"version": "24.0.14",
"resolved": "https://registry.npmmirror.com/@types/node/-/node-24.0.14.tgz",
"integrity": "sha512-4zXMWD91vBLGRtHK3YbIoFMia+1nqEz72coM42C5ETjnNCa/heoj7NT1G67iAfOqMmcfhuCZ4uNpyz8EjlAejw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2159,17 +2151,17 @@
"license": "MIT"
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.36.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.36.0.tgz",
"integrity": "sha512-lZNihHUVB6ZZiPBNgOQGSxUASI7UJWhT8nHyUGCnaQ28XFCw98IfrMCG3rUl1uwUWoAvodJQby2KTs79UTcrAg==",
"version": "8.37.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.37.0.tgz",
"integrity": "sha512-jsuVWeIkb6ggzB+wPCsR4e6loj+rM72ohW6IBn2C+5NCvfUVY8s33iFPySSVXqtm5Hu29Ne/9bnA0JmyLmgenA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "8.36.0",
"@typescript-eslint/type-utils": "8.36.0",
"@typescript-eslint/utils": "8.36.0",
"@typescript-eslint/visitor-keys": "8.36.0",
"@typescript-eslint/scope-manager": "8.37.0",
"@typescript-eslint/type-utils": "8.37.0",
"@typescript-eslint/utils": "8.37.0",
"@typescript-eslint/visitor-keys": "8.37.0",
"graphemer": "^1.4.0",
"ignore": "^7.0.0",
"natural-compare": "^1.4.0",
@@ -2183,7 +2175,7 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"@typescript-eslint/parser": "^8.36.0",
"@typescript-eslint/parser": "^8.37.0",
"eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <5.9.0"
}
@@ -2199,16 +2191,16 @@
}
},
"node_modules/@typescript-eslint/parser": {
"version": "8.36.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-8.36.0.tgz",
"integrity": "sha512-FuYgkHwZLuPbZjQHzJXrtXreJdFMKl16BFYyRrLxDhWr6Qr7Kbcu2s1Yhu8tsiMXw1S0W1pjfFfYEt+R604s+Q==",
"version": "8.37.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-8.37.0.tgz",
"integrity": "sha512-kVIaQE9vrN9RLCQMQ3iyRlVJpTiDUY6woHGb30JDkfJErqrQEmtdWH3gV0PBAfGZgQXoqzXOO0T3K6ioApbbAA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/scope-manager": "8.36.0",
"@typescript-eslint/types": "8.36.0",
"@typescript-eslint/typescript-estree": "8.36.0",
"@typescript-eslint/visitor-keys": "8.36.0",
"@typescript-eslint/scope-manager": "8.37.0",
"@typescript-eslint/types": "8.37.0",
"@typescript-eslint/typescript-estree": "8.37.0",
"@typescript-eslint/visitor-keys": "8.37.0",
"debug": "^4.3.4"
},
"engines": {
@@ -2224,14 +2216,14 @@
}
},
"node_modules/@typescript-eslint/project-service": {
"version": "8.36.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/project-service/-/project-service-8.36.0.tgz",
"integrity": "sha512-JAhQFIABkWccQYeLMrHadu/fhpzmSQ1F1KXkpzqiVxA/iYI6UnRt2trqXHt1sYEcw1mxLnB9rKMsOxXPxowN/g==",
"version": "8.37.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/project-service/-/project-service-8.37.0.tgz",
"integrity": "sha512-BIUXYsbkl5A1aJDdYJCBAo8rCEbAvdquQ8AnLb6z5Lp1u3x5PNgSSx9A/zqYc++Xnr/0DVpls8iQ2cJs/izTXA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/tsconfig-utils": "^8.36.0",
"@typescript-eslint/types": "^8.36.0",
"@typescript-eslint/tsconfig-utils": "^8.37.0",
"@typescript-eslint/types": "^8.37.0",
"debug": "^4.3.4"
},
"engines": {
@@ -2246,14 +2238,14 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "8.36.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-8.36.0.tgz",
"integrity": "sha512-wCnapIKnDkN62fYtTGv2+RY8FlnBYA3tNm0fm91kc2BjPhV2vIjwwozJ7LToaLAyb1ca8BxrS7vT+Pvvf7RvqA==",
"version": "8.37.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-8.37.0.tgz",
"integrity": "sha512-0vGq0yiU1gbjKob2q691ybTg9JX6ShiVXAAfm2jGf3q0hdP6/BruaFjL/ManAR/lj05AvYCH+5bbVo0VtzmjOA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.36.0",
"@typescript-eslint/visitor-keys": "8.36.0"
"@typescript-eslint/types": "8.37.0",
"@typescript-eslint/visitor-keys": "8.37.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2264,9 +2256,9 @@
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
"version": "8.36.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.36.0.tgz",
"integrity": "sha512-Nhh3TIEgN18mNbdXpd5Q8mSCBnrZQeY9V7Ca3dqYvNDStNIGRmJA6dmrIPMJ0kow3C7gcQbpsG2rPzy1Ks/AnA==",
"version": "8.37.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.37.0.tgz",
"integrity": "sha512-1/YHvAVTimMM9mmlPvTec9NP4bobA1RkDbMydxG8omqwJJLEW/Iy2C4adsAESIXU3WGLXFHSZUU+C9EoFWl4Zg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2281,14 +2273,15 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "8.36.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-8.36.0.tgz",
"integrity": "sha512-5aaGYG8cVDd6cxfk/ynpYzxBRZJk7w/ymto6uiyUFtdCozQIsQWh7M28/6r57Fwkbweng8qAzoMCPwSJfWlmsg==",
"version": "8.37.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-8.37.0.tgz",
"integrity": "sha512-SPkXWIkVZxhgwSwVq9rqj/4VFo7MnWwVaRNznfQDc/xPYHjXnPfLWn+4L6FF1cAz6e7dsqBeMawgl7QjUMj4Ow==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/typescript-estree": "8.36.0",
"@typescript-eslint/utils": "8.36.0",
"@typescript-eslint/types": "8.37.0",
"@typescript-eslint/typescript-estree": "8.37.0",
"@typescript-eslint/utils": "8.37.0",
"debug": "^4.3.4",
"ts-api-utils": "^2.1.0"
},
@@ -2305,9 +2298,9 @@
}
},
"node_modules/@typescript-eslint/types": {
"version": "8.36.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/types/-/types-8.36.0.tgz",
"integrity": "sha512-xGms6l5cTJKQPZOKM75Dl9yBfNdGeLRsIyufewnxT4vZTrjC0ImQT4fj8QmtJK84F58uSh5HVBSANwcfiXxABQ==",
"version": "8.37.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/types/-/types-8.37.0.tgz",
"integrity": "sha512-ax0nv7PUF9NOVPs+lmQ7yIE7IQmAf8LGcXbMvHX5Gm+YJUYNAl340XkGnrimxZ0elXyoQJuN5sbg6C4evKA4SQ==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2319,16 +2312,16 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "8.36.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.36.0.tgz",
"integrity": "sha512-JaS8bDVrfVJX4av0jLpe4ye0BpAaUW7+tnS4Y4ETa3q7NoZgzYbN9zDQTJ8kPb5fQ4n0hliAt9tA4Pfs2zA2Hg==",
"version": "8.37.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.37.0.tgz",
"integrity": "sha512-zuWDMDuzMRbQOM+bHyU4/slw27bAUEcKSKKs3hcv2aNnc/tvE/h7w60dwVw8vnal2Pub6RT1T7BI8tFZ1fE+yg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/project-service": "8.36.0",
"@typescript-eslint/tsconfig-utils": "8.36.0",
"@typescript-eslint/types": "8.36.0",
"@typescript-eslint/visitor-keys": "8.36.0",
"@typescript-eslint/project-service": "8.37.0",
"@typescript-eslint/tsconfig-utils": "8.37.0",
"@typescript-eslint/types": "8.37.0",
"@typescript-eslint/visitor-keys": "8.37.0",
"debug": "^4.3.4",
"fast-glob": "^3.3.2",
"is-glob": "^4.0.3",
@@ -2374,16 +2367,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
"version": "8.36.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-8.36.0.tgz",
"integrity": "sha512-VOqmHu42aEMT+P2qYjylw6zP/3E/HvptRwdn/PZxyV27KhZg2IOszXod4NcXisWzPAGSS4trE/g4moNj6XmH2g==",
"version": "8.37.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-8.37.0.tgz",
"integrity": "sha512-TSFvkIW6gGjN2p6zbXo20FzCABbyUAuq6tBvNRGsKdsSQ6a7rnV6ADfZ7f4iI3lIiXc4F4WWvtUfDw9CJ9pO5A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.7.0",
"@typescript-eslint/scope-manager": "8.36.0",
"@typescript-eslint/types": "8.36.0",
"@typescript-eslint/typescript-estree": "8.36.0"
"@typescript-eslint/scope-manager": "8.37.0",
"@typescript-eslint/types": "8.37.0",
"@typescript-eslint/typescript-estree": "8.37.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2398,13 +2391,13 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "8.36.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.36.0.tgz",
"integrity": "sha512-vZrhV2lRPWDuGoxcmrzRZyxAggPL+qp3WzUrlZD+slFueDiYHxeBa34dUXPuC0RmGKzl4lS5kFJYvKCq9cnNDA==",
"version": "8.37.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.37.0.tgz",
"integrity": "sha512-YzfhzcTnZVPiLfP/oeKtDp2evwvHLMe0LOy7oe+hb9KKIumLNohYS9Hgp1ifwpu42YWxhZE8yieggz6JpqO/1w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.36.0",
"@typescript-eslint/types": "8.37.0",
"eslint-visitor-keys": "^4.2.1"
},
"engines": {
@@ -3199,9 +3192,9 @@
}
},
"node_modules/eslint": {
"version": "9.30.1",
"resolved": "https://registry.npmmirror.com/eslint/-/eslint-9.30.1.tgz",
"integrity": "sha512-zmxXPNMOXmwm9E0yQLi5uqXHs7uq2UIiqEKo3Gq+3fwo1XrJ+hijAZImyF7hclW3E6oHz43Yk3RP8at6OTKflQ==",
"version": "9.31.0",
"resolved": "https://registry.npmmirror.com/eslint/-/eslint-9.31.0.tgz",
"integrity": "sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3209,9 +3202,9 @@
"@eslint-community/regexpp": "^4.12.1",
"@eslint/config-array": "^0.21.0",
"@eslint/config-helpers": "^0.3.0",
"@eslint/core": "^0.14.0",
"@eslint/core": "^0.15.0",
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "9.30.1",
"@eslint/js": "9.31.0",
"@eslint/plugin-kit": "^0.3.1",
"@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
@@ -3317,6 +3310,19 @@
"url": "https://opencollective.com/eslint"
}
},
"node_modules/eslint/node_modules/@eslint/core": {
"version": "0.15.1",
"resolved": "https://registry.npmmirror.com/@eslint/core/-/core-0.15.1.tgz",
"integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@types/json-schema": "^7.0.15"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/espree": {
"version": "10.4.0",
"resolved": "https://registry.npmmirror.com/espree/-/espree-10.4.0.tgz",
@@ -4827,15 +4833,16 @@
}
},
"node_modules/typescript-eslint": {
"version": "8.36.0",
"resolved": "https://registry.npmmirror.com/typescript-eslint/-/typescript-eslint-8.36.0.tgz",
"integrity": "sha512-fTCqxthY+h9QbEgSIBfL9iV6CvKDFuoxg6bHPNpJ9HIUzS+jy2lCEyCmGyZRWEBSaykqcDPf1SJ+BfCI8DRopA==",
"version": "8.37.0",
"resolved": "https://registry.npmmirror.com/typescript-eslint/-/typescript-eslint-8.37.0.tgz",
"integrity": "sha512-TnbEjzkE9EmcO0Q2zM+GE8NQLItNAJpMmED1BdgoBMYNdqMhzlbqfdSwiRlAzEK2pA9UzVW0gzaaIzXWg2BjfA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/eslint-plugin": "8.36.0",
"@typescript-eslint/parser": "8.36.0",
"@typescript-eslint/utils": "8.36.0"
"@typescript-eslint/eslint-plugin": "8.37.0",
"@typescript-eslint/parser": "8.37.0",
"@typescript-eslint/typescript-estree": "8.37.0",
"@typescript-eslint/utils": "8.37.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -5135,9 +5142,9 @@
"license": "MIT"
},
"node_modules/vite": {
"version": "7.0.3",
"resolved": "https://registry.npmmirror.com/vite/-/vite-7.0.3.tgz",
"integrity": "sha512-y2L5oJZF7bj4c0jgGYgBNSdIu+5HF+m68rn2cQXFbGoShdhV1phX9rbnxy9YXj82aS8MMsCLAAFkRxZeWdldrQ==",
"version": "7.0.4",
"resolved": "https://registry.npmmirror.com/vite/-/vite-7.0.4.tgz",
"integrity": "sha512-SkaSguuS7nnmV7mfJ8l81JGBFV7Gvzp8IzgE8A8t23+AxuNX61Q5H1Tpz5efduSN7NHC8nQXD3sKQKZAu5mNEA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -5290,13 +5297,13 @@
}
},
"node_modules/vue-i18n": {
"version": "11.1.9",
"resolved": "https://registry.npmmirror.com/vue-i18n/-/vue-i18n-11.1.9.tgz",
"integrity": "sha512-N9ZTsXdRmX38AwS9F6Rh93RtPkvZTkSy/zNv63FTIwZCUbLwwrpqlKz9YQuzFLdlvRdZTnWAUE5jMxr8exdl7g==",
"version": "11.1.10",
"resolved": "https://registry.npmmirror.com/vue-i18n/-/vue-i18n-11.1.10.tgz",
"integrity": "sha512-C+IwnSg8QDSOAox0gdFYP5tsKLx5jNWxiawNoiNB/Tw4CReXmM1VJMXbduhbrEzAFLhreqzfDocuSVjGbxQrag==",
"license": "MIT",
"dependencies": {
"@intlify/core-base": "11.1.9",
"@intlify/shared": "11.1.9",
"@intlify/core-base": "11.1.10",
"@intlify/shared": "11.1.10",
"@vue/devtools-api": "^6.5.0"
},
"engines": {

View File

@@ -41,7 +41,7 @@
"@codemirror/lint": "^6.8.5",
"@codemirror/search": "^6.5.11",
"@codemirror/state": "^6.5.2",
"@codemirror/view": "^6.38.0",
"@codemirror/view": "^6.38.1",
"@lezer/highlight": "^1.2.1",
"@lezer/lr": "^1.4.2",
"codemirror": "^6.0.2",
@@ -57,25 +57,24 @@
"remarkable": "^2.0.1",
"sass": "^1.89.2",
"vue": "^3.5.17",
"vue-i18n": "^11.1.9",
"vue-i18n": "^11.1.10",
"vue-pick-colors": "^1.8.0",
"vue-router": "^4.5.1"
},
"devDependencies": {
"@eslint/js": "^9.30.1",
"@eslint/js": "^9.31.0",
"@lezer/generator": "^1.8.0",
"@types/lodash": "^4.17.20",
"@types/node": "^24.0.12",
"@types/node": "^24.0.14",
"@types/remarkable": "^2.0.8",
"@vitejs/plugin-vue": "^6.0.0",
"@wailsio/runtime": "latest",
"eslint": "^9.30.1",
"eslint": "^9.31.0",
"eslint-plugin-vue": "^10.3.0",
"globals": "^16.3.0",
"typescript": "^5.8.3",
"typescript-eslint": "^8.36.0",
"typescript-eslint": "^8.37.0",
"unplugin-vue-components": "^28.8.0",
"vite": "^7.0.3",
"vite": "^7.0.4",
"vue-eslint-parser": "^10.2.0",
"vue-tsc": "^3.0.1"
}

View File

@@ -1,10 +1,11 @@
<script setup lang="ts">
import { onMounted } from 'vue';
import { useConfigStore } from '@/stores/configStore';
import { useSystemStore } from '@/stores/systemStore';
import { useKeybindingStore } from '@/stores/keybindingStore';
import { useThemeStore } from '@/stores/themeStore';
import { useUpdateStore } from '@/stores/updateStore';
import {onMounted} from 'vue';
import {useConfigStore} from '@/stores/configStore';
import {useSystemStore} from '@/stores/systemStore';
import {useKeybindingStore} from '@/stores/keybindingStore';
import {useThemeStore} from '@/stores/themeStore';
import {useUpdateStore} from '@/stores/updateStore';
import {useBackupStore} from '@/stores/backupStore';
import WindowTitleBar from '@/components/titlebar/WindowTitleBar.vue';
const configStore = useConfigStore();
@@ -12,6 +13,7 @@ const systemStore = useSystemStore();
const keybindingStore = useKeybindingStore();
const themeStore = useThemeStore();
const updateStore = useUpdateStore();
const backupStore = useBackupStore();
// 应用启动时加载配置和初始化系统信息
onMounted(async () => {
@@ -26,6 +28,9 @@ onMounted(async () => {
await configStore.initializeLanguage();
themeStore.initializeTheme();
// 初始化备份服务
await backupStore.initialize();
// 启动时检查更新
await updateStore.checkOnStartup();
});
@@ -33,7 +38,7 @@ onMounted(async () => {
<template>
<div class="app-container">
<WindowTitleBar />
<WindowTitleBar/>
<div class="app-content">
<router-view/>
</div>

View File

@@ -119,6 +119,7 @@ export default {
general: 'General',
editing: 'Editor',
appearance: 'Appearance',
backupPage: 'Backup',
keyBindings: 'Key Bindings',
updates: 'Updates',
reset: 'Reset',
@@ -242,6 +243,49 @@ export default {
restartNow: 'Restart Now',
hotkeyPreview: 'Preview:',
none: 'None',
backup: {
basicSettings: 'Basic Settings',
enableBackup: 'Enable Git Backup',
autoBackup: 'Auto Backup',
backupInterval: 'Backup Interval',
intervals: {
'5min': '5 minutes',
'10min': '10 minutes',
'15min': '15 minutes',
'30min': '30 minutes',
'1hour': '1 hour'
},
repositoryConfig: 'Repository Configuration',
repoUrl: 'Repository URL',
repoUrlPlaceholder: 'Enter Git repository URL',
authConfig: 'Authentication Configuration',
authMethod: 'Authentication Method',
authMethods: {
token: 'Access Token',
sshKey: 'SSH Key',
userPass: 'Username/Password'
},
username: 'Username',
usernamePlaceholder: 'Enter username',
password: 'Password',
passwordPlaceholder: 'Enter password',
token: 'Access Token',
tokenPlaceholder: 'Enter access token',
sshKeyPath: 'SSH Key Path',
sshKeyPathPlaceholder: 'Select SSH key file',
sshKeyPassphrase: 'SSH Key Passphrase',
sshKeyPassphrasePlaceholder: 'Enter SSH key passphrase',
backupOperations: 'Backup Operations',
pushToRemote: 'Push to Remote',
pushing: 'Pushing...',
actions: {
push: 'Push',
},
status: {
success: 'Success',
failed: 'Failed'
}
},
},
extensions: {
rainbowBrackets: {

View File

@@ -119,6 +119,7 @@ export default {
general: '常规',
editing: '编辑器',
appearance: '外观',
backupPage: '备份',
extensions: '扩展',
keyBindings: '快捷键',
updates: '更新',
@@ -243,6 +244,49 @@ export default {
},
hotkeyPreview: '预览:',
none: '无',
backup: {
basicSettings: '基本设置',
enableBackup: '启用备份',
autoBackup: '自动备份',
backupInterval: '备份间隔',
intervals: {
'5min': '5分钟',
'10min': '10分钟',
'15min': '15分钟',
'30min': '30分钟',
'1hour': '1小时'
},
repositoryConfig: '仓库配置',
repoUrl: '仓库地址',
repoUrlPlaceholder: '请输入Git仓库地址',
authConfig: '认证配置',
authMethod: '认证方式',
authMethods: {
token: '访问令牌',
sshKey: 'SSH密钥',
userPass: '用户名密码'
},
username: '用户名',
usernamePlaceholder: '请输入用户名',
password: '密码',
passwordPlaceholder: '请输入密码',
token: '访问令牌',
tokenPlaceholder: '请输入访问令牌',
sshKeyPath: 'SSH密钥路径',
sshKeyPathPlaceholder: '请选择SSH密钥文件',
sshKeyPassphrase: 'SSH密钥密码',
sshKeyPassphrasePlaceholder: '请输入SSH密钥密码',
backupOperations: '备份操作',
pushToRemote: '推送到远程',
pushing: '推送中...',
actions: {
push: '推送',
},
status: {
success: '成功',
failed: '失败'
}
},
},
extensions: {
rainbowBrackets: {

View File

@@ -7,6 +7,7 @@ import AppearancePage from '@/views/settings/pages/AppearancePage.vue';
import KeyBindingsPage from '@/views/settings/pages/KeyBindingsPage.vue';
import UpdatesPage from '@/views/settings/pages/UpdatesPage.vue';
import ExtensionsPage from '@/views/settings/pages/ExtensionsPage.vue';
import BackupPage from '@/views/settings/pages/BackupPage.vue';
const routes: RouteRecordRaw[] = [
{
@@ -49,6 +50,11 @@ const routes: RouteRecordRaw[] = [
path: 'updates',
name: 'SettingsUpdates',
component: UpdatesPage
},
{
path: 'backup',
name: 'SettingsBackup',
component: BackupPage
}
]
}
@@ -59,4 +65,4 @@ const router = createRouter({
routes: routes
});
export default router;
export default router;

View File

@@ -0,0 +1,125 @@
import {defineStore} from 'pinia'
import {computed, readonly, ref} from 'vue'
import type {GitBackupConfig} from '@/../bindings/voidraft/internal/models'
import {BackupService} from '@/../bindings/voidraft/internal/services'
import {useConfigStore} from '@/stores/configStore'
/**
* Minimalist Backup Store
*/
export const useBackupStore = defineStore('backup', () => {
// Core state
const config = ref<GitBackupConfig | null>(null)
const isPushing = ref(false)
const error = ref<string | null>(null)
const isInitialized = ref(false)
// Backup result states
const pushSuccess = ref(false)
const pushError = ref(false)
// Timers for auto-hiding status icons and error messages
let pushStatusTimer: number | null = null
let errorTimer: number | null = null
// 获取configStore
const configStore = useConfigStore()
// Computed properties
const isEnabled = computed(() => configStore.config.backup.enabled)
const isConfigured = computed(() => configStore.config.backup.repo_url)
// 清除状态显示
const clearPushStatus = () => {
if (pushStatusTimer !== null) {
window.clearTimeout(pushStatusTimer)
pushStatusTimer = null
}
pushSuccess.value = false
pushError.value = false
}
// 清除错误信息
const clearError = () => {
if (errorTimer !== null) {
window.clearTimeout(errorTimer)
errorTimer = null
}
error.value = null
}
// 设置错误信息并自动清除
const setErrorWithAutoHide = (errorMessage: string, hideAfter: number = 5000) => {
clearError() // 清除之前的错误定时器
error.value = errorMessage
errorTimer = window.setTimeout(() => {
error.value = null
errorTimer = null
}, hideAfter)
}
// Push to remote repository
const pushToRemote = async () => {
if (isPushing.value || !isConfigured.value) return
isPushing.value = true
clearError() // 清除之前的错误信息
clearPushStatus()
try {
await BackupService.PushToRemote()
// 显示成功状态并设置3秒后自动消失
pushSuccess.value = true
pushStatusTimer = window.setTimeout(() => {
pushSuccess.value = false
pushStatusTimer = null
}, 3000)
} catch (err: any) {
setErrorWithAutoHide(err?.message || 'Backup operation failed')
// 显示错误状态并设置3秒后自动消失
pushError.value = true
pushStatusTimer = window.setTimeout(() => {
pushError.value = false
pushStatusTimer = null
}, 3000)
} finally {
isPushing.value = false
}
}
// 初始化备份服务(只在应用启动时调用一次)
const initialize = async () => {
if (!isEnabled.value) return
// 避免重复初始化
if (isInitialized.value) return
clearError() // 清除之前的错误信息
try {
await BackupService.Initialize()
isInitialized.value = true
} catch (err: any) {
setErrorWithAutoHide(err?.message || 'Failed to initialize backup service')
}
}
return {
// State
config: readonly(config),
isPushing: readonly(isPushing),
error: readonly(error),
isInitialized: readonly(isInitialized),
pushSuccess: readonly(pushSuccess),
pushError: readonly(pushError),
// Computed
isEnabled,
isConfigured,
// Methods
pushToRemote,
initialize,
clearError
}
})

View File

@@ -11,14 +11,14 @@ import {
TabType,
UpdatesConfig,
UpdateSourceType,
GitSyncConfig,
AuthMethod,
SyncStrategy
GitBackupConfig,
AuthMethod
} from '@/../bindings/voidraft/internal/models/models';
import {useI18n} from 'vue-i18n';
import {ConfigUtils} from '@/utils/configUtils';
import {WindowController} from '@/utils/windowController';
import * as runtime from '@wailsio/runtime';
import {useBackupStore} from '@/stores/backupStore';
// 国际化相关导入
export type SupportedLocaleType = 'zh-CN' | 'en-US';
@@ -51,8 +51,8 @@ type UpdatesConfigKeyMap = {
readonly [K in keyof UpdatesConfig]: string;
};
type SyncConfigKeyMap = {
readonly [K in keyof GitSyncConfig]: string;
type BackupConfigKeyMap = {
readonly [K in keyof GitBackupConfig]: string;
};
type NumberConfigKey = 'fontSize' | 'tabSize' | 'lineHeight';
@@ -95,22 +95,18 @@ const UPDATES_CONFIG_KEY_MAP: UpdatesConfigKeyMap = {
gitea: 'updates.gitea'
} as const;
const SYNC_CONFIG_KEY_MAP: SyncConfigKeyMap = {
enabled: 'sync.enabled',
repo_url: 'sync.repo_url',
branch: 'sync.branch',
auth_method: 'sync.auth_method',
username: 'sync.username',
password: 'sync.password',
token: 'sync.token',
ssh_key_path: 'sync.ssh_key_path',
ssh_key_passphrase: 'sync.ssh_key_passphrase',
sync_interval: 'sync.sync_interval',
last_sync_time: 'sync.last_sync_time',
auto_sync: 'sync.auto_sync',
local_repo_path: 'sync.local_repo_path',
sync_strategy: 'sync.sync_strategy',
files_to_sync: 'sync.files_to_sync'
const BACKUP_CONFIG_KEY_MAP: BackupConfigKeyMap = {
enabled: 'backup.enabled',
repo_url: 'backup.repo_url',
auth_method: 'backup.auth_method',
username: 'backup.username',
password: 'backup.password',
token: 'backup.token',
ssh_key_path: 'backup.ssh_key_path',
ssh_key_passphrase: 'backup.ssh_key_passphrase',
backup_interval: 'backup.backup_interval',
auto_backup: 'backup.auto_backup',
} as const;
// 配置限制
@@ -286,22 +282,17 @@ const DEFAULT_CONFIG: AppConfig = {
repo: "voidraft",
}
},
sync: {
backup: {
enabled: false,
repo_url: "",
branch: "main",
auth_method: AuthMethod.Token,
auth_method: AuthMethod.UserPass,
username: "",
password: "",
token: "",
ssh_key_path: "",
ssh_key_passphrase: "",
sync_interval: 60,
last_sync_time: null,
auto_sync: true,
local_repo_path: "",
sync_strategy: SyncStrategy.LocalFirst,
files_to_sync: ["voidraft.db"]
backup_interval: 60,
auto_backup: true,
},
metadata: {
version: '1.0.0',
@@ -390,19 +381,19 @@ export const useConfigStore = defineStore('config', () => {
state.config.updates[key] = value;
};
const updateSyncConfig = async <K extends keyof GitSyncConfig>(key: K, value: GitSyncConfig[K]): Promise<void> => {
const updateBackupConfig = async <K extends keyof GitBackupConfig>(key: K, value: GitBackupConfig[K]): Promise<void> => {
// 确保配置已加载
if (!state.configLoaded && !state.isLoading) {
await initConfig();
}
const backendKey = SYNC_CONFIG_KEY_MAP[key];
const backendKey = BACKUP_CONFIG_KEY_MAP[key];
if (!backendKey) {
throw new Error(`No backend key mapping found for sync.${key.toString()}`);
throw new Error(`No backend key mapping found for backup.${key.toString()}`);
}
await ConfigService.Set(backendKey, value);
state.config.sync[key] = value;
state.config.backup[key] = value;
};
// 加载配置
@@ -419,7 +410,7 @@ export const useConfigStore = defineStore('config', () => {
if (appConfig.editing) Object.assign(state.config.editing, appConfig.editing);
if (appConfig.appearance) Object.assign(state.config.appearance, appConfig.appearance);
if (appConfig.updates) Object.assign(state.config.updates, appConfig.updates);
if (appConfig.sync) Object.assign(state.config.sync, appConfig.sync);
if (appConfig.backup) Object.assign(state.config.backup, appConfig.backup);
if (appConfig.metadata) Object.assign(state.config.metadata, appConfig.metadata);
}
@@ -654,18 +645,16 @@ export const useConfigStore = defineStore('config', () => {
// 更新配置相关方法
setAutoUpdate: async (value: boolean) => await updateUpdatesConfig('autoUpdate', value),
// Git同步配置相关方法
setGitSyncEnabled: (value: boolean) => updateSyncConfig('enabled', value),
setGitRepoUrl: (value: string) => updateSyncConfig('repo_url', value),
setGitBranch: (value: string) => updateSyncConfig('branch', value),
setAuthMethod: (value: AuthMethod) => updateSyncConfig('auth_method', value),
setGitUsername: (value: string) => updateSyncConfig('username', value),
setGitPassword: (value: string) => updateSyncConfig('password', value),
setGitToken: (value: string) => updateSyncConfig('token', value),
setSSHKeyPath: (value: string) => updateSyncConfig('ssh_key_path', value),
setSyncInterval: (value: number) => updateSyncConfig('sync_interval', value),
setAutoSync: (value: boolean) => updateSyncConfig('auto_sync', value),
setSyncStrategy: (value: SyncStrategy) => updateSyncConfig('sync_strategy', value),
setFilesToSync: (value: string[]) => updateSyncConfig('files_to_sync', value),
// 备份配置相关方法
setEnableBackup: async (value: boolean) => {await updateBackupConfig('enabled', value);},
setAutoBackup: async (value: boolean) => {await updateBackupConfig('auto_backup', value);},
setRepoUrl: async (value: string) => await updateBackupConfig('repo_url', value),
setAuthMethod: async (value: AuthMethod) => await updateBackupConfig('auth_method', value),
setUsername: async (value: string) => await updateBackupConfig('username', value),
setPassword: async (value: string) => await updateBackupConfig('password', value),
setToken: async (value: string) => await updateBackupConfig('token', value),
setSshKeyPath: async (value: string) => await updateBackupConfig('ssh_key_path', value),
setSshKeyPassphrase: async (value: string) => await updateBackupConfig('ssh_key_passphrase', value),
setBackupInterval: async (value: number) => await updateBackupConfig('backup_interval', value),
};
});

View File

@@ -13,6 +13,7 @@ const navItems = [
{ id: 'general', icon: '⚙️', route: '/settings/general' },
{ id: 'editing', icon: '✏️', route: '/settings/editing' },
{ id: 'appearance', icon: '🎨', route: '/settings/appearance' },
{ id: 'backupPage', icon: '🔗', route: '/settings/backup' },
{ id: 'extensions', icon: '🧩', route: '/settings/extensions' },
{ id: 'keyBindings', icon: '⌨️', route: '/settings/key-bindings' },
{ id: 'updates', icon: '🔄', route: '/settings/updates' }
@@ -194,11 +195,11 @@ const goBackToEditor = async () => {
.settings-content {
flex: 1;
height: 100%;
padding: 20px;
overflow-y: auto;
padding: 0 20px;
overflow-y: scroll;
background-color: var(--settings-bg);
}
}
</style>
</style>

View File

@@ -0,0 +1,503 @@
<script setup lang="ts">
import {useConfigStore} from '@/stores/configStore';
import {useBackupStore} from '@/stores/backupStore';
import {useI18n} from 'vue-i18n';
import {computed, onMounted, onUnmounted} from 'vue';
import SettingSection from '../components/SettingSection.vue';
import SettingItem from '../components/SettingItem.vue';
import ToggleSwitch from '../components/ToggleSwitch.vue';
import {AuthMethod} from '@/../bindings/voidraft/internal/models/models';
import {DialogService} from '@/../bindings/voidraft/internal/services';
const {t} = useI18n();
const configStore = useConfigStore();
const backupStore = useBackupStore();
// 确保配置已加载
onMounted(async () => {
if (!configStore.configLoaded) {
await configStore.initConfig();
}
});
onUnmounted(() => {
backupStore.clearError();
})
// 认证方式选项
const authMethodOptions = computed(() => [
{value: AuthMethod.Token, label: t('settings.backup.authMethods.token')},
{value: AuthMethod.SSHKey, label: t('settings.backup.authMethods.sshKey')},
{value: AuthMethod.UserPass, label: t('settings.backup.authMethods.userPass')}
]);
// 备份间隔选项(分钟)
const backupIntervalOptions = computed(() => [
{value: 5, label: t('settings.backup.intervals.5min')},
{value: 10, label: t('settings.backup.intervals.10min')},
{value: 15, label: t('settings.backup.intervals.15min')},
{value: 30, label: t('settings.backup.intervals.30min')},
{value: 60, label: t('settings.backup.intervals.1hour')}
]);
// 计算属性 - 启用备份
const enableBackup = computed({
get: () => configStore.config.backup.enabled,
set: (value: boolean) => configStore.setEnableBackup(value)
});
// 计算属性 - 自动备份
const autoBackup = computed({
get: () => configStore.config.backup.auto_backup,
set: (value: boolean) => configStore.setAutoBackup(value)
});
// 仓库URL
const repoUrl = computed({
get: () => configStore.config.backup.repo_url,
set: (value: string) => configStore.setRepoUrl(value)
});
// 认证方式
const authMethod = computed({
get: () => configStore.config.backup.auth_method,
set: (value: AuthMethod) => configStore.setAuthMethod(value)
});
// 备份间隔
const backupInterval = computed({
get: () => configStore.config.backup.backup_interval,
set: (value: number) => configStore.setBackupInterval(value)
});
// 用户名
const username = computed({
get: () => configStore.config.backup.username,
set: (value: string) => configStore.setUsername(value)
});
// 密码
const password = computed({
get: () => configStore.config.backup.password,
set: (value: string) => configStore.setPassword(value)
});
// 访问令牌
const token = computed({
get: () => configStore.config.backup.token,
set: (value: string) => configStore.setToken(value)
});
// SSH密钥路径
const sshKeyPath = computed({
get: () => configStore.config.backup.ssh_key_path,
set: (value: string) => configStore.setSshKeyPath(value)
});
// SSH密钥密码
const sshKeyPassphrase = computed({
get: () => configStore.config.backup.ssh_key_passphrase,
set: (value: string) => configStore.setSshKeyPassphrase(value)
});
// 处理输入变化
const handleRepoUrlChange = (event: Event) => {
const target = event.target as HTMLInputElement;
repoUrl.value = target.value;
};
const handleUsernameChange = (event: Event) => {
const target = event.target as HTMLInputElement;
username.value = target.value;
};
const handlePasswordChange = (event: Event) => {
const target = event.target as HTMLInputElement;
password.value = target.value;
};
const handleTokenChange = (event: Event) => {
const target = event.target as HTMLInputElement;
token.value = target.value;
};
const handleSshKeyPassphraseChange = (event: Event) => {
const target = event.target as HTMLInputElement;
sshKeyPassphrase.value = target.value;
};
const handleAuthMethodChange = (event: Event) => {
const target = event.target as HTMLSelectElement;
authMethod.value = target.value as AuthMethod;
};
const handleBackupIntervalChange = (event: Event) => {
const target = event.target as HTMLSelectElement;
backupInterval.value = parseInt(target.value);
};
// 推送到远程
const pushToRemote = async () => {
await backupStore.pushToRemote();
};
// 选择SSH密钥文件
const selectSshKeyFile = async () => {
// 使用DialogService选择文件
const selectedPath = await DialogService.SelectFile();
// 检查用户是否取消了选择或路径为空
if (!selectedPath.trim()) {
return;
}
// 更新SSH密钥路径
sshKeyPath.value = selectedPath.trim();
};
</script>
<template>
<div class="settings-page">
<!-- 基本设置 -->
<SettingSection :title="t('settings.backup.basicSettings')">
<SettingItem
:title="t('settings.backup.enableBackup')"
>
<ToggleSwitch v-model="enableBackup"/>
</SettingItem>
<SettingItem
:title="t('settings.backup.autoBackup')"
:class="{ 'disabled-setting': !enableBackup }"
>
<ToggleSwitch v-model="autoBackup" :disabled="!enableBackup"/>
</SettingItem>
<SettingItem
:title="t('settings.backup.backupInterval')"
:class="{ 'disabled-setting': !enableBackup || !autoBackup }"
>
<select
class="backup-interval-select"
:value="backupInterval"
@change="handleBackupIntervalChange"
:disabled="!enableBackup || !autoBackup"
>
<option
v-for="option in backupIntervalOptions"
:key="option.value"
:value="option.value"
>
{{ option.label }}
</option>
</select>
</SettingItem>
</SettingSection>
<!-- 仓库配置 -->
<SettingSection :title="t('settings.backup.repositoryConfig')">
<SettingItem
:title="t('settings.backup.repoUrl')"
>
<input
type="text"
class="repo-url-input"
:value="repoUrl"
@input="handleRepoUrlChange"
:placeholder="t('settings.backup.repoUrlPlaceholder')"
:disabled="!enableBackup"
/>
</SettingItem>
</SettingSection>
<!-- 认证配置 -->
<SettingSection :title="t('settings.backup.authConfig')">
<SettingItem
:title="t('settings.backup.authMethod')"
>
<select
class="auth-method-select"
:value="authMethod"
@change="handleAuthMethodChange"
:disabled="!enableBackup"
>
<option
v-for="option in authMethodOptions"
:key="option.value"
:value="option.value"
>
{{ option.label }}
</option>
</select>
</SettingItem>
<!-- 用户名密码认证 -->
<template v-if="authMethod === AuthMethod.UserPass">
<SettingItem :title="t('settings.backup.username')">
<input
type="text"
class="username-input"
:value="username"
@input="handleUsernameChange"
:placeholder="t('settings.backup.usernamePlaceholder')"
:disabled="!enableBackup"
/>
</SettingItem>
<SettingItem :title="t('settings.backup.password')">
<input
type="password"
class="password-input"
:value="password"
@input="handlePasswordChange"
:placeholder="t('settings.backup.passwordPlaceholder')"
:disabled="!enableBackup"
/>
</SettingItem>
</template>
<!-- 访问令牌认证 -->
<template v-if="authMethod === AuthMethod.Token">
<SettingItem
:title="t('settings.backup.token')"
>
<input
type="password"
class="token-input"
:value="token"
@input="handleTokenChange"
:placeholder="t('settings.backup.tokenPlaceholder')"
:disabled="!enableBackup"
/>
</SettingItem>
</template>
<!-- SSH密钥认证 -->
<template v-if="authMethod === AuthMethod.SSHKey">
<SettingItem
:title="t('settings.backup.sshKeyPath')"
>
<input
type="text"
class="ssh-key-path-input"
:value="sshKeyPath"
:placeholder="t('settings.backup.sshKeyPathPlaceholder')"
:disabled="!enableBackup"
readonly
@click="enableBackup && selectSshKeyFile()"
/>
</SettingItem>
<SettingItem
:title="t('settings.backup.sshKeyPassphrase')"
>
<input
type="password"
class="ssh-passphrase-input"
:value="sshKeyPassphrase"
@input="handleSshKeyPassphraseChange"
:placeholder="t('settings.backup.sshKeyPassphrasePlaceholder')"
:disabled="!enableBackup"
/>
</SettingItem>
</template>
</SettingSection>
<!-- 备份操作 -->
<SettingSection :title="t('settings.backup.backupOperations')">
<SettingItem
:title="t('settings.backup.pushToRemote')"
>
<div class="backup-operation-container">
<div class="backup-status-icons">
<span v-if="backupStore.pushSuccess" class="success-icon"></span>
<span v-if="backupStore.pushError" class="error-icon"></span>
</div>
<button
class="push-button"
@click="() => pushToRemote()"
:disabled="!enableBackup || !repoUrl || backupStore.isPushing"
:class="{ 'backing-up': backupStore.isPushing }"
>
<span v-if="backupStore.isPushing" class="loading-spinner"></span>
{{ backupStore.isPushing ? t('settings.backup.pushing') : t('settings.backup.actions.push') }}
</button>
</div>
</SettingItem>
<div v-if="backupStore.error" class="error-message-row">
{{ backupStore.error }}
</div>
</SettingSection>
</div>
</template>
<style scoped lang="scss">
.settings-page {
max-width: 800px;
}
// 统一的输入控件样式
.repo-url-input,
.branch-input,
.username-input,
.password-input,
.token-input,
.ssh-key-path-input,
.ssh-passphrase-input,
.backup-interval-select,
.auth-method-select {
width: 50%;
min-width: 200px;
padding: 10px 12px;
background-color: var(--settings-input-bg);
border: 1px solid var(--settings-input-border);
border-radius: 4px;
color: var(--settings-text);
font-size: 12px;
transition: all 0.2s ease;
&:focus {
outline: none;
border-color: #4a9eff;
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
background-color: var(--settings-hover);
}
&::placeholder {
color: var(--settings-text-secondary);
}
&[readonly]:not(:disabled) {
cursor: pointer;
&:hover {
border-color: var(--settings-hover);
background-color: var(--settings-hover);
}
}
}
// 选择框特有样式
.backup-interval-select,
.auth-method-select {
appearance: none;
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23999999' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
background-repeat: no-repeat;
background-position: right 8px center;
background-size: 16px;
padding-right: 30px;
option {
background-color: var(--settings-input-bg);
color: var(--settings-text);
}
}
// 备份操作容器
.backup-operation-container {
display: flex;
align-items: center;
gap: 12px;
}
// 备份状态图标
.backup-status-icons {
width: 24px;
display: flex;
justify-content: center;
align-items: center;
margin-right: 8px;
}
// 成功和错误图标
.success-icon {
color: #4caf50;
font-size: 18px;
font-weight: bold;
}
.error-icon {
color: #f44336;
font-size: 18px;
font-weight: bold;
}
// 按钮样式
.push-button {
padding: 8px 16px;
background-color: var(--settings-input-bg);
border: 1px solid var(--settings-input-border);
border-radius: 4px;
color: var(--settings-text);
cursor: pointer;
font-size: 12px;
transition: all 0.2s ease;
display: flex;
align-items: center;
gap: 8px;
&:hover:not(:disabled) {
background-color: var(--settings-hover);
border-color: var(--settings-border);
}
&:active:not(:disabled) {
transform: translateY(1px);
}
&:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.loading-spinner {
display: inline-block;
width: 14px;
height: 14px;
border: 2px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
border-top-color: var(--settings-text);
animation: spin 1s linear infinite;
}
&.backing-up {
background-color: #2196f3;
border-color: #2196f3;
color: white;
}
}
// 错误信息行样式
.error-message-row {
color: #f44336;
font-size: 11px;
line-height: 1.4;
word-wrap: break-word;
margin-top: 8px;
padding: 8px 16px;
background-color: rgba(244, 67, 54, 0.1);
border-left: 3px solid #f44336;
border-radius: 4px;
}
// 禁用状态
.disabled-setting {
opacity: 0.5;
pointer-events: none;
}
// 加载动画
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>

View File

@@ -266,6 +266,7 @@ const currentVersion = computed(() => {
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid var(--settings-border, rgba(0,0,0,0.1));
background: transparent;
.notes-title {
font-size: 12px;
@@ -278,24 +279,76 @@ const currentVersion = computed(() => {
font-size: 12px;
color: var(--settings-text);
line-height: 1.4;
background: transparent;
/* Markdown内容样式 */
:deep(p) {
margin: 0 0 6px 0;
background: transparent;
}
:deep(ul), :deep(ol) {
margin: 6px 0;
padding-left: 16px;
background: transparent;
}
:deep(li) {
margin-bottom: 4px;
background: transparent;
}
:deep(h1), :deep(h2), :deep(h3), :deep(h4), :deep(h5), :deep(h6) {
margin: 10px 0 6px 0;
font-size: 13px;
background: transparent;
}
:deep(pre), :deep(code) {
background-color: var(--settings-code-bg, rgba(0,0,0,0.05));
border-radius: 3px;
padding: 2px 4px;
font-family: monospace;
}
:deep(pre) {
padding: 8px;
overflow-x: auto;
margin: 6px 0;
}
:deep(blockquote) {
border-left: 3px solid var(--settings-border, rgba(0,0,0,0.1));
margin: 6px 0;
padding-left: 10px;
color: var(--settings-text-secondary, #757575);
background: transparent;
}
:deep(a) {
color: var(--theme-primary, #2196f3);
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
:deep(table) {
border-collapse: collapse;
width: 100%;
margin: 6px 0;
background: transparent;
}
:deep(th), :deep(td) {
border: 1px solid var(--settings-border, rgba(0,0,0,0.1));
padding: 4px 8px;
background: transparent;
}
:deep(th) {
background-color: var(--settings-table-header-bg, rgba(0,0,0,0.02));
}
}
}