🚧 Optimize

This commit is contained in:
2025-10-01 18:15:22 +08:00
parent cf8bf688bf
commit 1216b0b67c
22 changed files with 2015 additions and 2556 deletions

View File

@@ -68,4 +68,9 @@ export enum TranslatorType {
* DeeplTranslatorType DeepL翻译器
*/
DeeplTranslatorType = "deepl",
/**
* TartuNLPTranslatorType TartuNLP翻译器
*/
TartuNLPTranslatorType = "tartunlp",
};

View File

@@ -14,27 +14,6 @@ import {Call as $Call, Create as $Create} from "@wailsio/runtime";
// @ts-ignore: Unused imports
import * as translator$0 from "../common/translator/models.js";
/**
* GetAvailableTranslators 获取所有可用翻译器类型
* @returns {[]string} 翻译器类型列表
*/
export function GetAvailableTranslators(): Promise<string[]> & { cancel(): void } {
let $resultPromise = $Call.ByID(1186597995) as any;
let $typingPromise = $resultPromise.then(($result: any) => {
return $$createType0($result);
}) as any;
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
return $typingPromise;
}
/**
* GetStandardLanguageCode 获取标准化的语言代码
*/
export function GetStandardLanguageCode(translatorType: translator$0.TranslatorType, languageCode: string): Promise<string> & { cancel(): void } {
let $resultPromise = $Call.ByID(1158131995, translatorType, languageCode) as any;
return $resultPromise;
}
/**
* GetTranslatorLanguages 获取翻译器的语言列表
* @param {string} translatorType - 翻译器类型 ("google", "bing", "youdao", "deepl")
@@ -43,6 +22,19 @@ export function GetStandardLanguageCode(translatorType: translator$0.TranslatorT
*/
export function GetTranslatorLanguages(translatorType: translator$0.TranslatorType): Promise<{ [_: string]: translator$0.LanguageInfo }> & { cancel(): void } {
let $resultPromise = $Call.ByID(3976114458, translatorType) as any;
let $typingPromise = $resultPromise.then(($result: any) => {
return $$createType1($result);
}) as any;
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
return $typingPromise;
}
/**
* GetTranslators 获取所有可用翻译器类型
* @returns {[]string} 翻译器类型列表
*/
export function GetTranslators(): Promise<string[]> & { cancel(): void } {
let $resultPromise = $Call.ByID(3720069432) as any;
let $typingPromise = $resultPromise.then(($result: any) => {
return $$createType2($result);
}) as any;
@@ -73,6 +65,6 @@ export function TranslateWith(text: string, $from: string, to: string, translato
}
// Private type creation functions
const $$createType0 = $Create.Array($Create.Any);
const $$createType1 = translator$0.LanguageInfo.createFrom;
const $$createType2 = $Create.Map($Create.Any, $$createType1);
const $$createType0 = translator$0.LanguageInfo.createFrom;
const $$createType1 = $Create.Map($Create.Any, $$createType0);
const $$createType2 = $Create.Array($Create.Any);

View File

@@ -8,7 +8,7 @@
"name": "frontend",
"version": "0.0.0",
"dependencies": {
"@codemirror/autocomplete": "^6.18.7",
"@codemirror/autocomplete": "^6.19.0",
"@codemirror/commands": "^6.8.1",
"@codemirror/lang-angular": "^0.1.4",
"@codemirror/lang-cpp": "^6.0.3",
@@ -32,11 +32,11 @@
"@codemirror/lang-yaml": "^6.1.2",
"@codemirror/language": "^6.11.3",
"@codemirror/language-data": "^6.5.1",
"@codemirror/legacy-modes": "^6.5.1",
"@codemirror/legacy-modes": "^6.5.2",
"@codemirror/lint": "^6.8.5",
"@codemirror/search": "^6.5.11",
"@codemirror/state": "^6.5.2",
"@codemirror/view": "^6.38.3",
"@codemirror/view": "^6.38.4",
"@cospaia/prettier-plugin-clojure": "^0.0.2",
"@lezer/highlight": "^1.2.1",
"@lezer/lr": "^1.4.2",
@@ -48,7 +48,6 @@
"codemirror-lang-elixir": "^4.0.0",
"colors-named": "^1.0.2",
"colors-named-hex": "^1.0.2",
"franc-min": "^6.2.0",
"groovy-beautify": "^0.0.17",
"hsl-matcher": "^1.2.4",
"java-parser": "^3.0.1",
@@ -60,7 +59,7 @@
"prettier": "^3.6.2",
"remarkable": "^2.0.1",
"sass": "^1.93.2",
"vue": "^3.5.21",
"vue": "^3.5.22",
"vue-i18n": "^11.1.12",
"vue-pick-colors": "^1.8.0",
"vue-router": "^4.5.1"
@@ -72,17 +71,17 @@
"@types/remarkable": "^2.0.8",
"@vitejs/plugin-vue": "^6.0.1",
"@wailsio/runtime": "latest",
"cross-env": "^10.0.0",
"cross-env": "^10.1.0",
"eslint": "^9.36.0",
"eslint-plugin-vue": "^10.5.0",
"globals": "^16.4.0",
"typescript": "^5.9.2",
"typescript-eslint": "^8.44.1",
"typescript": "^5.9.3",
"typescript-eslint": "^8.45.0",
"unplugin-vue-components": "^29.1.0",
"vite": "^7.1.7",
"vite-plugin-node-polyfills": "^0.24.0",
"vue-eslint-parser": "^10.2.0",
"vue-tsc": "^3.0.8"
"vue-tsc": "^3.1.0"
}
},
"node_modules/@babel/helper-string-parser": {
@@ -104,12 +103,12 @@
}
},
"node_modules/@babel/parser": {
"version": "7.28.3",
"resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.28.3.tgz",
"integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==",
"version": "7.28.4",
"resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.28.4.tgz",
"integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.28.2"
"@babel/types": "^7.28.4"
},
"bin": {
"parser": "bin/babel-parser.js"
@@ -119,9 +118,9 @@
}
},
"node_modules/@babel/types": {
"version": "7.28.2",
"resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.28.2.tgz",
"integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==",
"version": "7.28.4",
"resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.28.4.tgz",
"integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==",
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.27.1",
@@ -171,9 +170,9 @@
"license": "Apache-2.0"
},
"node_modules/@codemirror/autocomplete": {
"version": "6.18.7",
"resolved": "https://registry.npmmirror.com/@codemirror/autocomplete/-/autocomplete-6.18.7.tgz",
"integrity": "sha512-8EzdeIoWPJDsMBwz3zdzwXnUpCzMiCyz5/A3FIPpriaclFCGDkAzK13sMcnsu5rowqiyeQN2Vs2TsOcoDPZirQ==",
"version": "6.19.0",
"resolved": "https://registry.npmmirror.com/@codemirror/autocomplete/-/autocomplete-6.19.0.tgz",
"integrity": "sha512-61Hfv3cF07XvUxNeC3E7jhG8XNi1Yom1G0lRC936oLnlF+jrbrv8rc/J98XlYzcsAoTVupfsf5fLej1aI8kyIg==",
"license": "MIT",
"dependencies": {
"@codemirror/language": "^6.0.0",
@@ -515,9 +514,9 @@
}
},
"node_modules/@codemirror/legacy-modes": {
"version": "6.5.1",
"resolved": "https://registry.npmmirror.com/@codemirror/legacy-modes/-/legacy-modes-6.5.1.tgz",
"integrity": "sha512-DJYQQ00N1/KdESpZV7jg9hafof/iBNp9h7TYo1SLMk86TWl9uDsVdho2dzd81K+v4retmK6mdC7WpuOQDytQqw==",
"version": "6.5.2",
"resolved": "https://registry.npmmirror.com/@codemirror/legacy-modes/-/legacy-modes-6.5.2.tgz",
"integrity": "sha512-/jJbwSTazlQEDOQw2FJ8LEEKVS72pU0lx6oM54kGpL8t/NJ2Jda3CZ4pcltiKTdqYSRk3ug1B3pil1gsjA6+8Q==",
"license": "MIT",
"dependencies": {
"@codemirror/language": "^6.0.0"
@@ -555,9 +554,9 @@
}
},
"node_modules/@codemirror/view": {
"version": "6.38.3",
"resolved": "https://registry.npmmirror.com/@codemirror/view/-/view-6.38.3.tgz",
"integrity": "sha512-x2t87+oqwB1mduiQZ6huIghjMt4uZKFEdj66IcXw7+a5iBEvv9lh7EWDRHI7crnD4BMGpnyq/RzmCGbiEZLcvQ==",
"version": "6.38.4",
"resolved": "https://registry.npmmirror.com/@codemirror/view/-/view-6.38.4.tgz",
"integrity": "sha512-hduz0suCcUSC/kM8Fq3A9iLwInJDl8fD1xLpTIk+5xkNm8z/FT7UsIa9sOXrkpChh+XXc18RzswE8QqELsVl+g==",
"license": "MIT",
"dependencies": {
"@codemirror/state": "^6.5.0",
@@ -2342,17 +2341,17 @@
"license": "MIT"
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.44.1",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.44.1.tgz",
"integrity": "sha512-molgphGqOBT7t4YKCSkbasmu1tb1MgrZ2szGzHbclF7PNmOkSTQVHy+2jXOSnxvR3+Xe1yySHFZoqMpz3TfQsw==",
"version": "8.45.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.45.0.tgz",
"integrity": "sha512-HC3y9CVuevvWCl/oyZuI47dOeDF9ztdMEfMH8/DW/Mhwa9cCLnK1oD7JoTVGW/u7kFzNZUKUoyJEqkaJh5y3Wg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "8.44.1",
"@typescript-eslint/type-utils": "8.44.1",
"@typescript-eslint/utils": "8.44.1",
"@typescript-eslint/visitor-keys": "8.44.1",
"@typescript-eslint/scope-manager": "8.45.0",
"@typescript-eslint/type-utils": "8.45.0",
"@typescript-eslint/utils": "8.45.0",
"@typescript-eslint/visitor-keys": "8.45.0",
"graphemer": "^1.4.0",
"ignore": "^7.0.0",
"natural-compare": "^1.4.0",
@@ -2366,7 +2365,7 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"@typescript-eslint/parser": "^8.44.1",
"@typescript-eslint/parser": "^8.45.0",
"eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <6.0.0"
}
@@ -2382,16 +2381,16 @@
}
},
"node_modules/@typescript-eslint/parser": {
"version": "8.44.1",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-8.44.1.tgz",
"integrity": "sha512-EHrrEsyhOhxYt8MTg4zTF+DJMuNBzWwgvvOYNj/zm1vnaD/IC5zCXFehZv94Piqa2cRFfXrTFxIvO95L7Qc/cw==",
"version": "8.45.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-8.45.0.tgz",
"integrity": "sha512-TGf22kon8KW+DeKaUmOibKWktRY8b2NSAZNdtWh798COm1NWx8+xJ6iFBtk3IvLdv6+LGLJLRlyhrhEDZWargQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/scope-manager": "8.44.1",
"@typescript-eslint/types": "8.44.1",
"@typescript-eslint/typescript-estree": "8.44.1",
"@typescript-eslint/visitor-keys": "8.44.1",
"@typescript-eslint/scope-manager": "8.45.0",
"@typescript-eslint/types": "8.45.0",
"@typescript-eslint/typescript-estree": "8.45.0",
"@typescript-eslint/visitor-keys": "8.45.0",
"debug": "^4.3.4"
},
"engines": {
@@ -2407,14 +2406,14 @@
}
},
"node_modules/@typescript-eslint/project-service": {
"version": "8.44.1",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/project-service/-/project-service-8.44.1.tgz",
"integrity": "sha512-ycSa60eGg8GWAkVsKV4E6Nz33h+HjTXbsDT4FILyL8Obk5/mx4tbvCNsLf9zret3ipSumAOG89UcCs/KRaKYrA==",
"version": "8.45.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/project-service/-/project-service-8.45.0.tgz",
"integrity": "sha512-3pcVHwMG/iA8afdGLMuTibGR7pDsn9RjDev6CCB+naRsSYs2pns5QbinF4Xqw6YC/Sj3lMrm/Im0eMfaa61WUg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/tsconfig-utils": "^8.44.1",
"@typescript-eslint/types": "^8.44.1",
"@typescript-eslint/tsconfig-utils": "^8.45.0",
"@typescript-eslint/types": "^8.45.0",
"debug": "^4.3.4"
},
"engines": {
@@ -2429,14 +2428,14 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "8.44.1",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-8.44.1.tgz",
"integrity": "sha512-NdhWHgmynpSvyhchGLXh+w12OMT308Gm25JoRIyTZqEbApiBiQHD/8xgb6LqCWCFcxFtWwaVdFsLPQI3jvhywg==",
"version": "8.45.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-8.45.0.tgz",
"integrity": "sha512-clmm8XSNj/1dGvJeO6VGH7EUSeA0FMs+5au/u3lrA3KfG8iJ4u8ym9/j2tTEoacAffdW1TVUzXO30W1JTJS7dA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.44.1",
"@typescript-eslint/visitor-keys": "8.44.1"
"@typescript-eslint/types": "8.45.0",
"@typescript-eslint/visitor-keys": "8.45.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2447,9 +2446,9 @@
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
"version": "8.44.1",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.44.1.tgz",
"integrity": "sha512-B5OyACouEjuIvof3o86lRMvyDsFwZm+4fBOqFHccIctYgBjqR3qT39FBYGN87khcgf0ExpdCBeGKpKRhSFTjKQ==",
"version": "8.45.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.45.0.tgz",
"integrity": "sha512-aFdr+c37sc+jqNMGhH+ajxPXwjv9UtFZk79k8pLoJ6p4y0snmYpPA52GuWHgt2ZF4gRRW6odsEj41uZLojDt5w==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2464,15 +2463,15 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "8.44.1",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-8.44.1.tgz",
"integrity": "sha512-KdEerZqHWXsRNKjF9NYswNISnFzXfXNDfPxoTh7tqohU/PRIbwTmsjGK6V9/RTYWau7NZvfo52lgVk+sJh0K3g==",
"version": "8.45.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-8.45.0.tgz",
"integrity": "sha512-bpjepLlHceKgyMEPglAeULX1vixJDgaKocp0RVJ5u4wLJIMNuKtUXIczpJCPcn2waII0yuvks/5m5/h3ZQKs0A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.44.1",
"@typescript-eslint/typescript-estree": "8.44.1",
"@typescript-eslint/utils": "8.44.1",
"@typescript-eslint/types": "8.45.0",
"@typescript-eslint/typescript-estree": "8.45.0",
"@typescript-eslint/utils": "8.45.0",
"debug": "^4.3.4",
"ts-api-utils": "^2.1.0"
},
@@ -2489,9 +2488,9 @@
}
},
"node_modules/@typescript-eslint/types": {
"version": "8.44.1",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/types/-/types-8.44.1.tgz",
"integrity": "sha512-Lk7uj7y9uQUOEguiDIDLYLJOrYHQa7oBiURYVFqIpGxclAFQ78f6VUOM8lI2XEuNOKNB7XuvM2+2cMXAoq4ALQ==",
"version": "8.45.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/types/-/types-8.45.0.tgz",
"integrity": "sha512-WugXLuOIq67BMgQInIxxnsSyRLFxdkJEJu8r4ngLR56q/4Q5LrbfkFRH27vMTjxEK8Pyz7QfzuZe/G15qQnVRA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2503,16 +2502,16 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "8.44.1",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.44.1.tgz",
"integrity": "sha512-qnQJ+mVa7szevdEyvfItbO5Vo+GfZ4/GZWWDRRLjrxYPkhM+6zYB2vRYwCsoJLzqFCdZT4mEqyJoyzkunsZ96A==",
"version": "8.45.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.45.0.tgz",
"integrity": "sha512-GfE1NfVbLam6XQ0LcERKwdTTPlLvHvXXhOeUGC1OXi4eQBoyy1iVsW+uzJ/J9jtCz6/7GCQ9MtrQ0fml/jWCnA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/project-service": "8.44.1",
"@typescript-eslint/tsconfig-utils": "8.44.1",
"@typescript-eslint/types": "8.44.1",
"@typescript-eslint/visitor-keys": "8.44.1",
"@typescript-eslint/project-service": "8.45.0",
"@typescript-eslint/tsconfig-utils": "8.45.0",
"@typescript-eslint/types": "8.45.0",
"@typescript-eslint/visitor-keys": "8.45.0",
"debug": "^4.3.4",
"fast-glob": "^3.3.2",
"is-glob": "^4.0.3",
@@ -2558,16 +2557,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
"version": "8.44.1",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-8.44.1.tgz",
"integrity": "sha512-DpX5Fp6edTlocMCwA+mHY8Mra+pPjRZ0TfHkXI8QFelIKcbADQz1LUPNtzOFUriBB2UYqw4Pi9+xV4w9ZczHFg==",
"version": "8.45.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-8.45.0.tgz",
"integrity": "sha512-bxi1ht+tLYg4+XV2knz/F7RVhU0k6VrSMc9sb8DQ6fyCTrGQLHfo7lDtN0QJjZjKkLA2ThrKuCdHEvLReqtIGg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.7.0",
"@typescript-eslint/scope-manager": "8.44.1",
"@typescript-eslint/types": "8.44.1",
"@typescript-eslint/typescript-estree": "8.44.1"
"@typescript-eslint/scope-manager": "8.45.0",
"@typescript-eslint/types": "8.45.0",
"@typescript-eslint/typescript-estree": "8.45.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2582,13 +2581,13 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "8.44.1",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.44.1.tgz",
"integrity": "sha512-576+u0QD+Jp3tZzvfRfxon0EA2lzcDt3lhUbsC6Lgzy9x2VR4E+JUiNyGHi5T8vk0TV+fpJ5GLG1JsJuWCaKhw==",
"version": "8.45.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.45.0.tgz",
"integrity": "sha512-qsaFBA3e09MIDAGFUrTk+dzqtfv1XPVz8t8d1f0ybTzrCY7BKiMC5cjrl1O/P7UmHsNyW90EYSkU/ZWpmXelag==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.44.1",
"@typescript-eslint/types": "8.45.0",
"eslint-visitor-keys": "^4.2.1"
},
"engines": {
@@ -2646,64 +2645,53 @@
}
},
"node_modules/@vue/compiler-core": {
"version": "3.5.21",
"resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.21.tgz",
"integrity": "sha512-8i+LZ0vf6ZgII5Z9XmUvrCyEzocvWT+TeR2VBUVlzIH6Tyv57E20mPZ1bCS+tbejgUgmjrEh7q/0F0bibskAmw==",
"version": "3.5.22",
"resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.22.tgz",
"integrity": "sha512-jQ0pFPmZwTEiRNSb+i9Ow/I/cHv2tXYqsnHKKyCQ08irI2kdF5qmYedmF8si8mA7zepUFmJ2hqzS8CQmNOWOkQ==",
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.28.3",
"@vue/shared": "3.5.21",
"@babel/parser": "^7.28.4",
"@vue/shared": "3.5.22",
"entities": "^4.5.0",
"estree-walker": "^2.0.2",
"source-map-js": "^1.2.1"
}
},
"node_modules/@vue/compiler-dom": {
"version": "3.5.21",
"resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.21.tgz",
"integrity": "sha512-jNtbu/u97wiyEBJlJ9kmdw7tAr5Vy0Aj5CgQmo+6pxWNQhXZDPsRr1UWPN4v3Zf82s2H3kF51IbzZ4jMWAgPlQ==",
"version": "3.5.22",
"resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.22.tgz",
"integrity": "sha512-W8RknzUM1BLkypvdz10OVsGxnMAuSIZs9Wdx1vzA3mL5fNMN15rhrSCLiTm6blWeACwUwizzPVqGJgOGBEN/hA==",
"license": "MIT",
"dependencies": {
"@vue/compiler-core": "3.5.21",
"@vue/shared": "3.5.21"
"@vue/compiler-core": "3.5.22",
"@vue/shared": "3.5.22"
}
},
"node_modules/@vue/compiler-sfc": {
"version": "3.5.21",
"resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.21.tgz",
"integrity": "sha512-SXlyk6I5eUGBd2v8Ie7tF6ADHE9kCR6mBEuPyH1nUZ0h6Xx6nZI29i12sJKQmzbDyr2tUHMhhTt51Z6blbkTTQ==",
"version": "3.5.22",
"resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.22.tgz",
"integrity": "sha512-tbTR1zKGce4Lj+JLzFXDq36K4vcSZbJ1RBu8FxcDv1IGRz//Dh2EBqksyGVypz3kXpshIfWKGOCcqpSbyGWRJQ==",
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.28.3",
"@vue/compiler-core": "3.5.21",
"@vue/compiler-dom": "3.5.21",
"@vue/compiler-ssr": "3.5.21",
"@vue/shared": "3.5.21",
"@babel/parser": "^7.28.4",
"@vue/compiler-core": "3.5.22",
"@vue/compiler-dom": "3.5.22",
"@vue/compiler-ssr": "3.5.22",
"@vue/shared": "3.5.22",
"estree-walker": "^2.0.2",
"magic-string": "^0.30.18",
"magic-string": "^0.30.19",
"postcss": "^8.5.6",
"source-map-js": "^1.2.1"
}
},
"node_modules/@vue/compiler-ssr": {
"version": "3.5.21",
"resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.21.tgz",
"integrity": "sha512-vKQ5olH5edFZdf5ZrlEgSO1j1DMA4u23TVK5XR1uMhvwnYvVdDF0nHXJUblL/GvzlShQbjhZZ2uvYmDlAbgo9w==",
"version": "3.5.22",
"resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.22.tgz",
"integrity": "sha512-GdgyLvg4R+7T8Nk2Mlighx7XGxq/fJf9jaVofc3IL0EPesTE86cP/8DD1lT3h1JeZr2ySBvyqKQJgbS54IX1Ww==",
"license": "MIT",
"dependencies": {
"@vue/compiler-dom": "3.5.21",
"@vue/shared": "3.5.21"
}
},
"node_modules/@vue/compiler-vue2": {
"version": "2.7.16",
"resolved": "https://registry.npmmirror.com/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz",
"integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==",
"dev": true,
"license": "MIT",
"dependencies": {
"de-indent": "^1.0.2",
"he": "^1.2.0"
"@vue/compiler-dom": "3.5.22",
"@vue/shared": "3.5.22"
}
},
"node_modules/@vue/devtools-api": {
@@ -2740,17 +2728,16 @@
}
},
"node_modules/@vue/language-core": {
"version": "3.0.8",
"resolved": "https://registry.npmmirror.com/@vue/language-core/-/language-core-3.0.8.tgz",
"integrity": "sha512-eYs6PF7bxoPYvek9qxceo1BCwFbJZYqJll+WaYC8o8ec60exqj+n+QRGGiJHSeUfYp0hDxARbMdxMq/fbPgU5g==",
"version": "3.1.0",
"resolved": "https://registry.npmmirror.com/@vue/language-core/-/language-core-3.1.0.tgz",
"integrity": "sha512-a7ns+X9vTbdmk7QLrvnZs8s4E1wwtxG/sELzr6F2j4pU+r/OoAv6jJGSz+5tVTU6e4+3rjepGhSP8jDmBBcb3w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@volar/language-core": "2.4.23",
"@vue/compiler-dom": "^3.5.0",
"@vue/compiler-vue2": "^2.7.16",
"@vue/shared": "^3.5.0",
"alien-signals": "^2.0.5",
"alien-signals": "^3.0.0",
"muggle-string": "^0.4.1",
"path-browserify": "^1.0.1",
"picomatch": "^4.0.2"
@@ -2778,53 +2765,53 @@
}
},
"node_modules/@vue/reactivity": {
"version": "3.5.21",
"resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.21.tgz",
"integrity": "sha512-3ah7sa+Cwr9iiYEERt9JfZKPw4A2UlbY8RbbnH2mGCE8NwHkhmlZt2VsH0oDA3P08X3jJd29ohBDtX+TbD9AsA==",
"version": "3.5.22",
"resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.22.tgz",
"integrity": "sha512-f2Wux4v/Z2pqc9+4SmgZC1p73Z53fyD90NFWXiX9AKVnVBEvLFOWCEgJD3GdGnlxPZt01PSlfmLqbLYzY/Fw4A==",
"license": "MIT",
"dependencies": {
"@vue/shared": "3.5.21"
"@vue/shared": "3.5.22"
}
},
"node_modules/@vue/runtime-core": {
"version": "3.5.21",
"resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.21.tgz",
"integrity": "sha512-+DplQlRS4MXfIf9gfD1BOJpk5RSyGgGXD/R+cumhe8jdjUcq/qlxDawQlSI8hCKupBlvM+3eS1se5xW+SuNAwA==",
"version": "3.5.22",
"resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.22.tgz",
"integrity": "sha512-EHo4W/eiYeAzRTN5PCextDUZ0dMs9I8mQ2Fy+OkzvRPUYQEyK9yAjbasrMCXbLNhF7P0OUyivLjIy0yc6VrLJQ==",
"license": "MIT",
"dependencies": {
"@vue/reactivity": "3.5.21",
"@vue/shared": "3.5.21"
"@vue/reactivity": "3.5.22",
"@vue/shared": "3.5.22"
}
},
"node_modules/@vue/runtime-dom": {
"version": "3.5.21",
"resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.21.tgz",
"integrity": "sha512-3M2DZsOFwM5qI15wrMmNF5RJe1+ARijt2HM3TbzBbPSuBHOQpoidE+Pa+XEaVN+czbHf81ETRoG1ltztP2em8w==",
"version": "3.5.22",
"resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.22.tgz",
"integrity": "sha512-Av60jsryAkI023PlN7LsqrfPvwfxOd2yAwtReCjeuugTJTkgrksYJJstg1e12qle0NarkfhfFu1ox2D+cQotww==",
"license": "MIT",
"dependencies": {
"@vue/reactivity": "3.5.21",
"@vue/runtime-core": "3.5.21",
"@vue/shared": "3.5.21",
"@vue/reactivity": "3.5.22",
"@vue/runtime-core": "3.5.22",
"@vue/shared": "3.5.22",
"csstype": "^3.1.3"
}
},
"node_modules/@vue/server-renderer": {
"version": "3.5.21",
"resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.21.tgz",
"integrity": "sha512-qr8AqgD3DJPJcGvLcJKQo2tAc8OnXRcfxhOJCPF+fcfn5bBGz7VCcO7t+qETOPxpWK1mgysXvVT/j+xWaHeMWA==",
"version": "3.5.22",
"resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.22.tgz",
"integrity": "sha512-gXjo+ao0oHYTSswF+a3KRHZ1WszxIqO7u6XwNHqcqb9JfyIL/pbWrrh/xLv7jeDqla9u+LK7yfZKHih1e1RKAQ==",
"license": "MIT",
"dependencies": {
"@vue/compiler-ssr": "3.5.21",
"@vue/shared": "3.5.21"
"@vue/compiler-ssr": "3.5.22",
"@vue/shared": "3.5.22"
},
"peerDependencies": {
"vue": "3.5.21"
"vue": "3.5.22"
}
},
"node_modules/@vue/shared": {
"version": "3.5.21",
"resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.21.tgz",
"integrity": "sha512-+2k1EQpnYuVuu3N7atWyG3/xoFWIVJZq4Mz8XNOdScFI0etES75fbny/oU4lKWk/577P1zmg0ioYvpGEDZ3DLw==",
"version": "3.5.22",
"resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.22.tgz",
"integrity": "sha512-F4yc6palwq3TT0u+FYf0Ns4Tfl9GRFURDN2gWG7L1ecIaS/4fCIuFOjMTnCyjsu/OK6vaDKLCrGAa+KvvH+h4w==",
"license": "MIT"
},
"node_modules/@wailsio/runtime": {
@@ -2893,9 +2880,9 @@
}
},
"node_modules/alien-signals": {
"version": "2.0.7",
"resolved": "https://registry.npmmirror.com/alien-signals/-/alien-signals-2.0.7.tgz",
"integrity": "sha512-wE7y3jmYeb0+h6mr5BOovuqhFv22O/MV9j5p0ndJsa7z1zJNPGQ4ph5pQk/kTTCWRC3xsA4SmtwmkzQO+7NCNg==",
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/alien-signals/-/alien-signals-3.0.0.tgz",
"integrity": "sha512-JHoRJf18Y6HN4/KZALr3iU+0vW9LKG+8FMThQlbn4+gv8utsLIkwpomjElGPccGeNwh0FI2HN6BLnyFLo6OyLQ==",
"dev": true,
"license": "MIT"
},
@@ -3469,16 +3456,6 @@
"lezer-elixir": "^1.0.0"
}
},
"node_modules/collapse-white-space": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/collapse-white-space/-/collapse-white-space-2.1.0.tgz",
"integrity": "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz",
@@ -3644,9 +3621,9 @@
"license": "MIT"
},
"node_modules/cross-env": {
"version": "10.0.0",
"resolved": "https://registry.npmmirror.com/cross-env/-/cross-env-10.0.0.tgz",
"integrity": "sha512-aU8qlEK/nHYtVuN4p7UQgAwVljzMg8hB4YK5ThRqD2l/ziSnryncPNn7bMLt5cFYsKVKBh8HqLqyCoTupEUu7Q==",
"version": "10.1.0",
"resolved": "https://registry.npmmirror.com/cross-env/-/cross-env-10.1.0.tgz",
"integrity": "sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3722,13 +3699,6 @@
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"license": "MIT"
},
"node_modules/de-indent": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/de-indent/-/de-indent-1.0.2.tgz",
"integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==",
"dev": true,
"license": "MIT"
},
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz",
@@ -4385,19 +4355,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/franc-min": {
"version": "6.2.0",
"resolved": "https://registry.npmmirror.com/franc-min/-/franc-min-6.2.0.tgz",
"integrity": "sha512-1uDIEUSlUZgvJa2AKYR/dmJC66v/PvGQ9mWfI9nOr/kPpMFyvswK0gPXOwpYJYiYD008PpHLkGfG58SPjQJFxw==",
"license": "MIT",
"dependencies": {
"trigram-utils": "^2.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz",
@@ -4623,16 +4580,6 @@
"node": ">= 0.4"
}
},
"node_modules/he": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/he/-/he-1.2.0.tgz",
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
"dev": true,
"license": "MIT",
"bin": {
"he": "bin/he"
}
},
"node_modules/hmac-drbg": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
@@ -5279,16 +5226,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/n-gram": {
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/n-gram/-/n-gram-2.0.2.tgz",
"integrity": "sha512-S24aGsn+HLBxUGVAUFOwGpKs7LBcG4RudKU//eWzt/mQ97/NMKQxDWHyHx63UNWk/OOdihgmzoETn1tf5nQDzQ==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/nanoid": {
"version": "3.3.11",
"resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz",
@@ -6643,20 +6580,6 @@
"node": ">=8.0"
}
},
"node_modules/trigram-utils": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/trigram-utils/-/trigram-utils-2.0.1.tgz",
"integrity": "sha512-nfWIXHEaB+HdyslAfMxSqWKDdmqY9I32jS7GnqpdWQnLH89r6A5sdk3fDVYqGAZ0CrT8ovAFSAo6HRiWcWNIGQ==",
"license": "MIT",
"dependencies": {
"collapse-white-space": "^2.0.0",
"n-gram": "^2.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/ts-api-utils": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
@@ -6712,9 +6635,9 @@
}
},
"node_modules/typescript": {
"version": "5.9.2",
"resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.2.tgz",
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
"version": "5.9.3",
"resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"devOptional": true,
"license": "Apache-2.0",
"bin": {
@@ -6726,16 +6649,16 @@
}
},
"node_modules/typescript-eslint": {
"version": "8.44.1",
"resolved": "https://registry.npmmirror.com/typescript-eslint/-/typescript-eslint-8.44.1.tgz",
"integrity": "sha512-0ws8uWGrUVTjEeN2OM4K1pLKHK/4NiNP/vz6ns+LjT/6sqpaYzIVFajZb1fj/IDwpsrrHb3Jy0Qm5u9CPcKaeg==",
"version": "8.45.0",
"resolved": "https://registry.npmmirror.com/typescript-eslint/-/typescript-eslint-8.45.0.tgz",
"integrity": "sha512-qzDmZw/Z5beNLUrXfd0HIW6MzIaAV5WNDxmMs9/3ojGOpYavofgNAAD/nC6tGV2PczIi0iw8vot2eAe/sBn7zg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/eslint-plugin": "8.44.1",
"@typescript-eslint/parser": "8.44.1",
"@typescript-eslint/typescript-estree": "8.44.1",
"@typescript-eslint/utils": "8.44.1"
"@typescript-eslint/eslint-plugin": "8.45.0",
"@typescript-eslint/parser": "8.45.0",
"@typescript-eslint/typescript-estree": "8.45.0",
"@typescript-eslint/utils": "8.45.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -7240,16 +7163,16 @@
"license": "MIT"
},
"node_modules/vue": {
"version": "3.5.21",
"resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.21.tgz",
"integrity": "sha512-xxf9rum9KtOdwdRkiApWL+9hZEMWE90FHh8yS1+KJAiWYh+iGWV1FquPjoO9VUHQ+VIhsCXNNyZ5Sf4++RVZBA==",
"version": "3.5.22",
"resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.22.tgz",
"integrity": "sha512-toaZjQ3a/G/mYaLSbV+QsQhIdMo9x5rrqIpYRObsJ6T/J+RyCSFwN2LHNVH9v8uIcljDNa3QzPVdv3Y6b9hAJQ==",
"license": "MIT",
"dependencies": {
"@vue/compiler-dom": "3.5.21",
"@vue/compiler-sfc": "3.5.21",
"@vue/runtime-dom": "3.5.21",
"@vue/server-renderer": "3.5.21",
"@vue/shared": "3.5.21"
"@vue/compiler-dom": "3.5.22",
"@vue/compiler-sfc": "3.5.22",
"@vue/runtime-dom": "3.5.22",
"@vue/server-renderer": "3.5.22",
"@vue/shared": "3.5.22"
},
"peerDependencies": {
"typescript": "*"
@@ -7345,14 +7268,14 @@
"license": "MIT"
},
"node_modules/vue-tsc": {
"version": "3.0.8",
"resolved": "https://registry.npmmirror.com/vue-tsc/-/vue-tsc-3.0.8.tgz",
"integrity": "sha512-H9yg/m6ywykmWS+pIAEs65v2FrVm5uOA0a0dHkX6Sx8dNg1a1m4iudt/6eGa9fAenmNHGlLFN9XpWQb8i5sU1w==",
"version": "3.1.0",
"resolved": "https://registry.npmmirror.com/vue-tsc/-/vue-tsc-3.1.0.tgz",
"integrity": "sha512-fbMynMG7kXSnqZTRBSCh9ROYaVpXfCZbEO0gY3lqOjLbp361uuS88n6BDajiUriDIF+SGLWoinjvf6stS2J3Gg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@volar/typescript": "2.4.23",
"@vue/language-core": "3.0.8"
"@vue/language-core": "3.1.0"
},
"bin": {
"vue-tsc": "bin/vue-tsc.js"

View File

@@ -13,7 +13,7 @@
"build:lang-parser": "node src/views/editor/extensions/codeblock/lang-parser/build-parser.js"
},
"dependencies": {
"@codemirror/autocomplete": "^6.18.7",
"@codemirror/autocomplete": "^6.19.0",
"@codemirror/commands": "^6.8.1",
"@codemirror/lang-angular": "^0.1.4",
"@codemirror/lang-cpp": "^6.0.3",
@@ -37,11 +37,11 @@
"@codemirror/lang-yaml": "^6.1.2",
"@codemirror/language": "^6.11.3",
"@codemirror/language-data": "^6.5.1",
"@codemirror/legacy-modes": "^6.5.1",
"@codemirror/legacy-modes": "^6.5.2",
"@codemirror/lint": "^6.8.5",
"@codemirror/search": "^6.5.11",
"@codemirror/state": "^6.5.2",
"@codemirror/view": "^6.38.3",
"@codemirror/view": "^6.38.4",
"@cospaia/prettier-plugin-clojure": "^0.0.2",
"@lezer/highlight": "^1.2.1",
"@lezer/lr": "^1.4.2",
@@ -53,7 +53,6 @@
"codemirror-lang-elixir": "^4.0.0",
"colors-named": "^1.0.2",
"colors-named-hex": "^1.0.2",
"franc-min": "^6.2.0",
"groovy-beautify": "^0.0.17",
"hsl-matcher": "^1.2.4",
"java-parser": "^3.0.1",
@@ -65,7 +64,7 @@
"prettier": "^3.6.2",
"remarkable": "^2.0.1",
"sass": "^1.93.2",
"vue": "^3.5.21",
"vue": "^3.5.22",
"vue-i18n": "^11.1.12",
"vue-pick-colors": "^1.8.0",
"vue-router": "^4.5.1"
@@ -77,16 +76,16 @@
"@types/remarkable": "^2.0.8",
"@vitejs/plugin-vue": "^6.0.1",
"@wailsio/runtime": "latest",
"cross-env": "^10.0.0",
"cross-env": "^10.1.0",
"eslint": "^9.36.0",
"eslint-plugin-vue": "^10.5.0",
"globals": "^16.4.0",
"typescript": "^5.9.2",
"typescript-eslint": "^8.44.1",
"typescript": "^5.9.3",
"typescript-eslint": "^8.45.0",
"unplugin-vue-components": "^29.1.0",
"vite": "^7.1.7",
"vite-plugin-node-polyfills": "^0.24.0",
"vue-eslint-parser": "^10.2.0",
"vue-tsc": "^3.0.8"
"vue-tsc": "^3.1.0"
}
}

View File

@@ -1,20 +1,21 @@
<script setup lang="ts">
import {onMounted} from 'vue';
import {onBeforeMount} 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 WindowTitleBar from '@/components/titlebar/WindowTitleBar.vue';
import {useTranslationStore} from "@/stores/translationStore";
const configStore = useConfigStore();
const systemStore = useSystemStore();
const keybindingStore = useKeybindingStore();
const themeStore = useThemeStore();
const updateStore = useUpdateStore();
const translationStore = useTranslationStore();
// 应用启动时加载配置和初始化系统信息
onMounted(async () => {
onBeforeMount(async () => {
// 并行初始化配置、系统信息和快捷键配置
await Promise.all([
configStore.initConfig(),
@@ -24,7 +25,8 @@ onMounted(async () => {
// 初始化语言和主题
await configStore.initializeLanguage();
themeStore.initializeTheme();
await themeStore.initializeTheme();
await translationStore.loadTranslators();
// 启动时检查更新
await updateStore.checkOnStartup();

View File

@@ -0,0 +1,49 @@
/**
* 默认翻译配置
*/
export const DEFAULT_TRANSLATION_CONFIG = {
minSelectionLength: 2,
maxTranslationLength: 5000,
} as const;
/**
* 翻译相关的错误消息
*/
export const TRANSLATION_ERRORS = {
NO_TEXT: 'no text to translate',
TRANSLATION_FAILED: 'translation failed',
} as const;
/**
* 翻译结果接口
*/
export interface TranslationResult {
translatedText: string;
error?: string;
}
/**
* 语言信息接口
*/
export interface LanguageInfo {
Code: string; // 语言代码
Name: string; // 语言名称
}
/**
* 翻译器扩展配置
*/
export interface TranslatorConfig {
/** 最小选择字符数才显示翻译按钮 */
minSelectionLength: number;
/** 最大翻译字符数 */
maxTranslationLength: number;
}
/**
* 翻译图标SVG
*/
export const TRANSLATION_ICON_SVG = `
<svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24">
<path d="M599.68 485.056h-8l30.592 164.672c20.352-7.04 38.72-17.344 54.912-31.104a271.36 271.36 0 0 1-40.704-64.64l32.256-4.032c8.896 17.664 19.072 33.28 30.592 46.72 23.872-27.968 42.24-65.152 55.04-111.744l-154.688 0.128z m121.92 133.76c18.368 15.36 39.36 26.56 62.848 33.472l14.784 4.416-8.64 30.336-14.72-4.352a205.696 205.696 0 0 1-76.48-41.728c-20.672 17.92-44.928 31.552-71.232 40.064l20.736 110.912H519.424l-9.984 72.512h385.152c18.112 0 32.704-14.144 32.704-31.616V295.424a32.128 32.128 0 0 0-32.704-31.552H550.528l35.2 189.696h79.424v-31.552h61.44v31.552h102.4v31.616h-42.688c-14.272 55.488-35.712 100.096-64.64 133.568zM479.36 791.68H193.472c-36.224 0-65.472-28.288-65.472-63.168V191.168C128 156.16 157.312 128 193.472 128h327.68l20.544 104.32h352.832c36.224 0 65.472 28.224 65.472 63.104v537.408c0 34.944-29.312 63.168-65.472 63.168H468.608l10.688-104.32zM337.472 548.352v-33.28H272.768v-48.896h60.16V433.28h-60.16v-41.728h64.704v-32.896h-102.4v189.632h102.4z m158.272 0V453.76c0-17.216-4.032-30.272-12.16-39.488-8.192-9.152-20.288-13.696-36.032-13.696a55.04 55.04 0 0 0-24.768 5.376 39.04 39.04 0 0 0-17.088 15.936h-1.984l-5.056-18.56h-28.352V548.48h37.12V480c0-17.088 2.304-29.376 6.912-36.736 4.608-7.424 12.16-11.072 22.528-11.072 7.616 0 13.248 2.56 16.64 7.872 3.52 5.248 5.312 13.056 5.312 23.488v84.736h36.928z" fill="currentColor"></path>
</svg>`;

View File

@@ -1,11 +1,11 @@
import {defineStore} from 'pinia';
import {computed, reactive} from 'vue';
import {SystemThemeType, ThemeType, ThemeColorConfig} from '@/../bindings/voidraft/internal/models/models';
import {ThemeService} from '@/../bindings/voidraft/internal/services';
import {useConfigStore} from './configStore';
import {useEditorStore} from './editorStore';
import {defaultDarkColors} from '@/views/editor/theme/dark';
import {defaultLightColors} from '@/views/editor/theme/light';
import { defineStore } from 'pinia';
import { computed, reactive } from 'vue';
import { SystemThemeType, ThemeType, ThemeColorConfig } from '@/../bindings/voidraft/internal/models/models';
import { ThemeService } from '@/../bindings/voidraft/internal/services';
import { useConfigStore } from './configStore';
import { useEditorStore } from './editorStore';
import { defaultDarkColors } from '@/views/editor/theme/dark';
import { defaultLightColors } from '@/views/editor/theme/light';
/**
* 主题管理 Store
@@ -14,25 +14,44 @@ import {defaultLightColors} from '@/views/editor/theme/light';
export const useThemeStore = defineStore('theme', () => {
const configStore = useConfigStore();
// 响应式状态 - 存储当前使用的主题颜色
// 响应式状态
const themeColors = reactive({
darkTheme: { ...defaultDarkColors },
lightTheme: { ...defaultLightColors }
});
// 计算属性 - 当前选择的主题类型
// 计算属性
const currentTheme = computed(() =>
configStore.config?.appearance?.systemTheme || SystemThemeType.SystemThemeAuto
);
// 初始化主题颜色 - 从数据库加载
// 获取默认主题颜色
const getDefaultColors = (themeType: ThemeType) =>
themeType === ThemeType.ThemeTypeDark ? defaultDarkColors : defaultLightColors;
// 应用主题到 DOM
const applyThemeToDOM = (theme: SystemThemeType) => {
const themeMap = {
[SystemThemeType.SystemThemeAuto]: 'auto',
[SystemThemeType.SystemThemeDark]: 'dark',
[SystemThemeType.SystemThemeLight]: 'light'
};
document.documentElement.setAttribute('data-theme', themeMap[theme]);
};
// 初始化主题颜色
const initializeThemeColors = async () => {
try {
const themes = await ThemeService.GetDefaultThemes();
// 如果没有获取到主题数据,使用默认值
if (!themes) {
Object.assign(themeColors.darkTheme, defaultDarkColors);
Object.assign(themeColors.lightTheme, defaultLightColors);
Object.assign(themeColors.darkTheme, defaultDarkColors);
Object.assign(themeColors.lightTheme, defaultLightColors);
return;
}
// 更新主题颜色
if (themes[ThemeType.ThemeTypeDark]) {
Object.assign(themeColors.darkTheme, themes[ThemeType.ThemeTypeDark].colors);
}
@@ -47,17 +66,9 @@ export const useThemeStore = defineStore('theme', () => {
}
};
// 应用主题到 DOM
const applyThemeToDOM = (theme: SystemThemeType) => {
document.documentElement.setAttribute('data-theme',
theme === SystemThemeType.SystemThemeAuto ? 'auto' :
theme === SystemThemeType.SystemThemeDark ? 'dark' : 'light'
);
};
// 初始化主题
const initializeTheme = async () => {
const theme = configStore.config?.appearance?.systemTheme || SystemThemeType.SystemThemeAuto;
const theme = currentTheme.value;
applyThemeToDOM(theme);
await initializeThemeColors();
};
@@ -69,27 +80,25 @@ export const useThemeStore = defineStore('theme', () => {
refreshEditorTheme();
};
// 更新主题颜色
const updateThemeColors = (darkColors: any = null, lightColors: any = null): boolean => {
// 更新主题颜色 - 合并逻辑,减少重复代码
const updateThemeColors = (darkColors?: any, lightColors?: any): boolean => {
let hasChanges = false;
if (darkColors) {
Object.entries(darkColors).forEach(([key, value]) => {
if (value !== undefined && themeColors.darkTheme[key] !== value) {
themeColors.darkTheme[key] = value;
hasChanges = true;
const updateColors = (target: any, source: any) => {
if (!source) return false;
let changed = false;
Object.entries(source).forEach(([key, value]) => {
if (value !== undefined && target[key] !== value) {
target[key] = value;
changed = true;
}
});
}
return changed;
};
if (lightColors) {
Object.entries(lightColors).forEach(([key, value]) => {
if (value !== undefined && themeColors.lightTheme[key] !== value) {
themeColors.lightTheme[key] = value;
hasChanges = true;
}
});
}
hasChanges = updateColors(themeColors.darkTheme, darkColors) || hasChanges;
hasChanges = updateColors(themeColors.lightTheme, lightColors) || hasChanges;
return hasChanges;
};
@@ -100,8 +109,10 @@ export const useThemeStore = defineStore('theme', () => {
const darkColors = ThemeColorConfig.createFrom(themeColors.darkTheme);
const lightColors = ThemeColorConfig.createFrom(themeColors.lightTheme);
await ThemeService.UpdateThemeColors(ThemeType.ThemeTypeDark, darkColors);
await ThemeService.UpdateThemeColors(ThemeType.ThemeTypeLight, lightColors);
await Promise.all([
ThemeService.UpdateThemeColors(ThemeType.ThemeTypeDark, darkColors),
ThemeService.UpdateThemeColors(ThemeType.ThemeTypeLight, lightColors)
]);
} catch (error) {
console.error('Failed to save theme colors:', error);
throw error;
@@ -117,11 +128,8 @@ export const useThemeStore = defineStore('theme', () => {
await ThemeService.ResetThemeColors(dbThemeType);
// 2. 更新内存中的颜色状态
if (themeType === 'darkTheme') {
Object.assign(themeColors.darkTheme, defaultDarkColors);
} else {
Object.assign(themeColors.lightTheme, defaultLightColors);
}
const defaultColors = getDefaultColors(dbThemeType);
Object.assign(themeColors[themeType], defaultColors);
// 3. 刷新编辑器主题
refreshEditorTheme();
@@ -136,13 +144,10 @@ export const useThemeStore = defineStore('theme', () => {
// 刷新编辑器主题
const refreshEditorTheme = () => {
// 使用当前主题重新应用DOM主题
const theme = currentTheme.value;
applyThemeToDOM(theme);
applyThemeToDOM(currentTheme.value);
const editorStore = useEditorStore();
if (editorStore) {
editorStore.applyThemeSettings();
}
editorStore?.applyThemeSettings();
};
return {

View File

@@ -1,593 +1,117 @@
import {defineStore} from 'pinia';
import {computed, ref, watch} from 'vue';
import {ref} from 'vue';
import {TranslationService} from '@/../bindings/voidraft/internal/services';
import {franc} from 'franc-min';
import {LanguageInfo, TRANSLATION_ERRORS, TranslationResult} from '@/common/constant/translation';
export interface TranslationResult {
sourceText: string;
translatedText: string;
sourceLang: string;
targetLang: string;
translatorType: string;
error?: string;
}
/**
* ISO 639-3 到 ISO 639-1/2 语言代码的映射
* franc-min 返回的是 ISO 639-3 代码需要转换为翻译API常用的 ISO 639-1/2 代码
*/
const ISO_LANGUAGE_MAP: Record<string, string> = {
// 常见语言
'cmn': 'zh', // 中文 (Mandarin Chinese)
'eng': 'en', // 英文 (English)
'jpn': 'ja', // 日语 (Japanese)
'kor': 'ko', // 韩语 (Korean)
'fra': 'fr', // 法语 (French)
'deu': 'de', // 德语 (German)
'spa': 'es', // 西班牙语 (Spanish)
'rus': 'ru', // 俄语 (Russian)
'ita': 'it', // 意大利语 (Italian)
'nld': 'nl', // 荷兰语 (Dutch)
'por': 'pt', // 葡萄牙语 (Portuguese)
'vie': 'vi', // 越南语 (Vietnamese)
'arb': 'ar', // 阿拉伯语 (Arabic)
'hin': 'hi', // 印地语 (Hindi)
'ben': 'bn', // 孟加拉语 (Bengali)
'tha': 'th', // 泰语 (Thai)
'tur': 'tr', // 土耳其语 (Turkish)
'heb': 'he', // 希伯来语 (Hebrew)
'pol': 'pl', // 波兰语 (Polish)
'swe': 'sv', // 瑞典语 (Swedish)
'fin': 'fi', // 芬兰语 (Finnish)
'dan': 'da', // 丹麦语 (Danish)
'ron': 'ro', // 罗马尼亚语 (Romanian)
'hun': 'hu', // 匈牙利语 (Hungarian)
'ces': 'cs', // 捷克语 (Czech)
'ell': 'el', // 希腊语 (Greek)
'bul': 'bg', // 保加利亚语 (Bulgarian)
'cat': 'ca', // 加泰罗尼亚语 (Catalan)
'ukr': 'uk', // 乌克兰语 (Ukrainian)
'hrv': 'hr', // 克罗地亚语 (Croatian)
'ind': 'id', // 印尼语 (Indonesian)
'mal': 'ms', // 马来语 (Malay)
'nob': 'no', // 挪威语 (Norwegian)
'lat': 'la', // 拉丁语 (Latin)
'lit': 'lt', // 立陶宛语 (Lithuanian)
'slk': 'sk', // 斯洛伐克语 (Slovak)
'slv': 'sl', // 斯洛文尼亚语 (Slovenian)
'srp': 'sr', // 塞尔维亚语 (Serbian)
'est': 'et', // 爱沙尼亚语 (Estonian)
'lav': 'lv', // 拉脱维亚语 (Latvian)
'fil': 'tl', // 菲律宾语/他加禄语 (Filipino/Tagalog)
// 未知/不确定
'und': 'auto' // 未知语言
};
// 语言代码的通用映射关系,适用于大部分翻译器
const COMMON_LANGUAGE_ALIASES: Record<string, string[]> = {
'zh': ['zh-CN', 'zh-TW', 'zh-Hans', 'zh-Hant', 'chinese', 'zhong'],
'en': ['en-US', 'en-GB', 'english', 'eng'],
'ja': ['jp', 'jpn', 'japanese'],
'ko': ['kr', 'kor', 'korean'],
'fr': ['fra', 'french'],
'de': ['deu', 'german', 'ger'],
'es': ['spa', 'spanish', 'esp'],
'ru': ['rus', 'russian'],
'pt': ['por', 'portuguese'],
'it': ['ita', 'italian'],
'nl': ['nld', 'dutch'],
'ar': ['ara', 'arabic'],
'hi': ['hin', 'hindi'],
'th': ['tha', 'thai'],
'tr': ['tur', 'turkish'],
'vi': ['vie', 'vietnamese'],
'id': ['ind', 'indonesian'],
'ms': ['mal', 'malay'],
'fi': ['fin', 'finnish'],
};
/**
* 翻译存储
*/
export const useTranslationStore = defineStore('translation', () => {
// 状态
const availableTranslators = ref<string[]>([]);
const isTranslating = ref(false);
const lastResult = ref<TranslationResult | null>(null);
const error = ref<string | null>(null);
// 语言列表 - 将类型设置为any以避免类型错误
const languageMaps = ref<Record<string, Record<string, any>>>({});
// 语言使用频率计数 - 使用pinia持久化
const languageUsageCount = ref<Record<string, number>>({});
// 最近使用的翻译语言 - 最多记录10个
const recentLanguages = ref<string[]>([]);
// 默认配置
// 注意:确保默认值在初始化和持久化后正确设置
const defaultTargetLang = ref('zh');
const defaultTranslator = ref('bing');
// 检测到的源语言,初始为空字符串表示尚未检测
const detectedSourceLang = ref('');
// 计算属性
const hasTranslators = computed(() => availableTranslators.value.length > 0);
const currentLanguageMap = computed(() => {
return languageMaps.value[defaultTranslator.value] || {};
});
// 监听默认语言变更,确保目标语言在当前翻译器支持的范围内
watch([defaultTranslator], () => {
// 当切换翻译器时,验证默认目标语言是否支持
if (Object.keys(languageMaps.value).length > 0) {
const validatedLang = validateLanguage(defaultTargetLang.value, defaultTranslator.value);
if (validatedLang !== defaultTargetLang.value) {
console.log(`目标语言 ${defaultTargetLang.value} 不受支持,已切换到 ${validatedLang}`);
defaultTargetLang.value = validatedLang;
}
}
});
// 基础状态
const translators = ref<string[]>([]);
const isTranslating = ref<boolean>(false);
// 语言映射
const translatorLanguages = ref<Record<string, Record<string, LanguageInfo>>>({});
/**
* 加载可用翻译器
* 加载可用翻译器列表并预先加载所有语言映射
*/
const loadAvailableTranslators = async (): Promise<void> => {
const loadTranslators = async (): Promise<void> => {
try {
const translators = await TranslationService.GetAvailableTranslators();
availableTranslators.value = translators;
// 如果默认翻译器不在可用列表中,则使用第一个可用的翻译器
if (translators.length > 0 && !translators.includes(defaultTranslator.value)) {
defaultTranslator.value = translators[0];
}
// 加载所有翻译器的语言列表
await Promise.all(translators.map(loadTranslatorLanguages));
// 在加载完所有语言列表后,确保默认目标语言有效
if (defaultTargetLang.value) {
const validatedLang = validateLanguage(defaultTargetLang.value, defaultTranslator.value);
if (validatedLang !== defaultTargetLang.value) {
defaultTargetLang.value = validatedLang;
translators.value = await TranslationService.GetTranslators();
const loadPromises = translators.value.map(async (translatorType) => {
try {
const languages = await TranslationService.GetTranslatorLanguages(translatorType as any);
if (languages) {
translatorLanguages.value[translatorType] = languages;
}
} catch (err) {
console.error(`Failed to preload languages for ${translatorType}:`, err);
}
}
} catch (_err) {
error.value = 'no available translators';
});
// 等待所有语言映射加载完成
await Promise.all(loadPromises);
} catch (err) {
console.error('Failed to load available translators:', err);
}
};
/**
* 加载指定翻译器的语言列表
* @param translatorType 翻译器类型
*/
const loadTranslatorLanguages = async (translatorType: string): Promise<void> => {
try {
const languages = await TranslationService.GetTranslatorLanguages(translatorType as any);
if (languages) {
languageMaps.value[translatorType] = languages;
translatorLanguages.value[translatorType] = languages;
}
} catch (err) {
console.error(`Failed to load languages for ${translatorType}:`, err);
}
};
/**
* 检测文本语言
* @param text 待检测的文本
* @returns 检测到的语言代码,如未检测到则返回空字符串
*/
const detectLanguage = (text: string): string => {
if (!text || text.trim().length < 10) {
return '';
}
try {
// franc返回ISO 639-3代码
const detectedIso639_3 = franc(text);
// 如果是未知语言,返回空字符串
if (detectedIso639_3 === 'und') {
return '';
}
// 转换为常用语言代码
return ISO_LANGUAGE_MAP[detectedIso639_3] || '';
} catch (err) {
console.error('语言检测失败:', err);
return '';
}
};
/**
* 在翻译器语言列表中查找相似的语言代码
* @param langCode 待查找的语言代码
* @param translatorType 翻译器类型
* @returns 找到的语言代码或空字符串
*/
const findSimilarLanguage = (langCode: string, translatorType: string): string => {
if (!langCode) return '';
const languageMap = languageMaps.value[translatorType] || {};
const langCodeLower = langCode.toLowerCase();
// 1. 尝试精确匹配
if (languageMap[langCode]) {
return langCode;
}
// 2. 检查通用别名映射
const possibleAliases = Object.entries(COMMON_LANGUAGE_ALIASES).find(
([code, aliases]) => code === langCodeLower || aliases.includes(langCodeLower)
);
if (possibleAliases) {
// 检查主代码是否可用
const [mainCode, aliases] = possibleAliases;
if (languageMap[mainCode]) {
return mainCode;
}
// 检查别名是否可用
for (const alias of aliases) {
if (languageMap[alias]) {
return alias;
}
}
}
// 3. 尝试正则表达式匹配
// 创建一个基于语言代码的正则表达式:例如 'en' 会匹配 'en-US', 'en_GB' 等
const codePattern = new RegExp(`^${langCodeLower}[-_]?`, 'i');
// 在语言列表中查找匹配的语言代码
const availableCodes = Object.keys(languageMap);
const matchedCode = availableCodes.find(code =>
codePattern.test(code.toLowerCase())
);
if (matchedCode) {
return matchedCode;
}
// 4. 反向匹配,例如 'zh-CN' 应该能匹配到 'zh'
if (langCodeLower.includes('-') || langCodeLower.includes('_')) {
const baseCode = langCodeLower.split(/[-_]/)[0];
if (languageMap[baseCode]) {
return baseCode;
}
// 通过基础代码查找别名
const baseCodeAliases = Object.entries(COMMON_LANGUAGE_ALIASES).find(
([code, aliases]) => code === baseCode || aliases.includes(baseCode)
);
if (baseCodeAliases) {
const [mainCode, aliases] = baseCodeAliases;
if (languageMap[mainCode]) {
return mainCode;
}
for (const alias of aliases) {
if (languageMap[alias]) {
return alias;
}
}
}
}
// 5. 最后尝试查找与部分代码匹配的任何语言
const partialMatch = availableCodes.find(code =>
code.toLowerCase().includes(langCodeLower) ||
langCodeLower.includes(code.toLowerCase())
);
if (partialMatch) {
return partialMatch;
}
// 如果所有匹配都失败,返回英语作为默认值
return 'en';
};
/**
* 验证语言代码是否受当前翻译器支持
* @param langCode 语言代码
* @param translatorType 翻译器类型(可选,默认使用当前翻译器)
* @returns 验证后的语言代码
*/
const validateLanguage = (langCode: string, translatorType?: string): string => {
// 如果语言代码为空返回auto作为API调用的默认值
if (!langCode) return 'auto';
const currentType = translatorType || defaultTranslator.value;
// 尝试在指定翻译器的语言列表中查找相似的语言代码
return findSimilarLanguage(langCode, currentType) || 'auto';
};
/**
* 增加语言使用次数并添加到最近使用列表
* @param langCode 语言代码
* @param weight 权重默认为1
*/
const incrementLanguageUsage = (langCode: string, weight: number = 1): void => {
if (!langCode || langCode === 'auto') return;
// 转换为小写,确保一致性
const normalizedCode = langCode.toLowerCase();
// 更新使用次数,乘以权重
const currentCount = languageUsageCount.value[normalizedCode] || 0;
languageUsageCount.value[normalizedCode] = currentCount + weight;
// 更新最近使用的语言列表
updateRecentLanguages(normalizedCode);
};
/**
* 更新最近使用的语言列表
* @param langCode 语言代码
*/
const updateRecentLanguages = (langCode: string): void => {
if (!langCode) return;
// 如果已经在列表中,先移除它
const index = recentLanguages.value.indexOf(langCode);
if (index !== -1) {
recentLanguages.value.splice(index, 1);
}
// 添加到列表开头
recentLanguages.value.unshift(langCode);
// 保持列表最多10个元素
if (recentLanguages.value.length > 10) {
recentLanguages.value = recentLanguages.value.slice(0, 10);
}
};
/**
* 获取按使用频率排序的语言列表
* @param translatorType 翻译器类型
* @param grouped 是否分组返回(常用/其他)
* @returns 排序后的语言列表或分组后的语言列表
*/
const getSortedLanguages = (translatorType: string, grouped: boolean = false): [string, any][] | {frequent: [string, any][], others: [string, any][]} => {
const languageMap = languageMaps.value[translatorType] || {};
// 获取语言列表
const languages = Object.entries(languageMap);
// 按使用频率排序
const sortedLanguages = languages.sort(([codeA, infoA], [codeB, infoB]) => {
// 获取使用次数默认为0
const countA = languageUsageCount.value[codeA.toLowerCase()] || 0;
const countB = languageUsageCount.value[codeB.toLowerCase()] || 0;
// 首先按使用频率降序排序
if (countB !== countA) {
return countB - countA;
}
// 其次按最近使用情况排序
const recentIndexA = recentLanguages.value.indexOf(codeA.toLowerCase());
const recentIndexB = recentLanguages.value.indexOf(codeB.toLowerCase());
if (recentIndexA !== -1 && recentIndexB !== -1) {
return recentIndexA - recentIndexB;
} else if (recentIndexA !== -1) {
return -1;
} else if (recentIndexB !== -1) {
return 1;
}
// 如果使用频率和最近使用情况都相同,按名称排序
const nameA = infoA.Name || infoA.name || codeA;
const nameB = infoB.Name || infoB.name || codeB;
return nameA.localeCompare(nameB);
});
// 如果不需要分组,直接返回排序后的列表
if (!grouped) {
return sortedLanguages;
}
// 分组:将有使用记录的语言归为常用组,其他归为其他组
const frequentLanguages: [string, any][] = [];
const otherLanguages: [string, any][] = [];
sortedLanguages.forEach(lang => {
const [code] = lang;
const usageCount = languageUsageCount.value[code.toLowerCase()] || 0;
const isInRecent = recentLanguages.value.includes(code.toLowerCase());
if (usageCount > 0 || isInRecent) {
frequentLanguages.push(lang);
} else {
otherLanguages.push(lang);
}
});
return {
frequent: frequentLanguages,
others: otherLanguages
};
};
/**
* 翻译文本
* @param text 待翻译文本
* @param to 目标语言代码
* @param translatorType 翻译器类型
* @returns 翻译结果
*/
const translateText = async (
text: string,
to?: string,
translatorType?: string
text: string,
sourceLang: string,
targetLang: string,
translatorType: string
): Promise<TranslationResult> => {
// 使用提供的参数或默认值
const targetLang = to || defaultTargetLang.value;
const translator = translatorType || defaultTranslator.value;
// 处理空文本
if (!text) {
if (!text.trim()) {
return {
sourceText: '',
translatedText: '',
sourceLang: '',
targetLang: targetLang,
translatorType: translator,
error: 'no text to translate'
error: TRANSLATION_ERRORS.NO_TEXT
};
}
// 检测源语言
const detected = detectLanguage(text);
if (detected) {
detectedSourceLang.value = detected;
}
// 使用检测到的语言或回退到auto
let actualSourceLang = detectedSourceLang.value || 'auto';
// 特殊处理有道翻译器:有道翻译器允许源语言和目标语言都是auto
const isYoudaoTranslator = translatorType === 'youdao';
const bothAuto = sourceLang === 'auto' && targetLang === 'auto';
// 确认语言代码有效并针对当前翻译器进行匹配
actualSourceLang = validateLanguage(actualSourceLang, translator);
const actualTargetLang = validateLanguage(targetLang, translator);
// 如果源语言和目标语言相同,则直接返回原文
if (actualSourceLang !== 'auto' && actualSourceLang === actualTargetLang) {
if (sourceLang === targetLang && !(isYoudaoTranslator && bothAuto)) {
return {
sourceText: text,
translatedText: text,
sourceLang: actualSourceLang,
targetLang: actualTargetLang,
translatorType: translator
translatedText: text
};
}
isTranslating.value = true;
error.value = null;
try {
console.log(`翻译文本: 从 ${actualSourceLang}${actualTargetLang} 使用 ${translator} 翻译器`);
// 调用翻译服务
const translatedText = await TranslationService.TranslateWith(
text,
actualSourceLang,
actualTargetLang,
translator
text,
sourceLang,
targetLang,
translatorType
);
// 增加目标语言的使用频率,使用较大的权重
incrementLanguageUsage(actualTargetLang, 3);
// 如果源语言不是auto也记录其使用情况但权重较小
if (actualSourceLang !== 'auto') {
incrementLanguageUsage(actualSourceLang, 1);
}
// 构建结果
const result: TranslationResult = {
sourceText: text,
translatedText,
sourceLang: actualSourceLang,
targetLang: actualTargetLang,
translatorType: translator
return {
translatedText
};
lastResult.value = result;
return result;
} catch (err) {
// 处理错误
const errorMessage = err instanceof Error ? err.message : 'translation failed';
const errorMessage = err instanceof Error ? err.message : TRANSLATION_ERRORS.TRANSLATION_FAILED;
error.value = errorMessage;
const result: TranslationResult = {
sourceText: text,
return {
translatedText: '',
sourceLang: actualSourceLang,
targetLang: actualTargetLang,
translatorType: translator,
error: errorMessage
};
lastResult.value = result;
return result;
} finally {
isTranslating.value = false;
}
};
/**
* 设置默认翻译配置
* @param config 配置对象
*/
const setDefaultConfig = (config: {
targetLang?: string;
translatorType?: string;
}): void => {
let changed = false;
if (config.translatorType && config.translatorType !== defaultTranslator.value) {
defaultTranslator.value = config.translatorType;
// 切换翻译器时清空检测到的源语言,以便重新检测
detectedSourceLang.value = '';
changed = true;
}
if (config.targetLang) {
// 验证目标语言是否支持
const validLang = validateLanguage(config.targetLang, defaultTranslator.value);
defaultTargetLang.value = validLang;
changed = true;
}
if (changed) {
console.log(`已更新默认翻译配置:翻译器=${defaultTranslator.value},目标语言=${defaultTargetLang.value}`);
}
};
// 初始化时加载可用翻译器
loadAvailableTranslators();
return {
// 状态
availableTranslators,
translators,
isTranslating,
lastResult,
error,
detectedSourceLang,
defaultTargetLang,
defaultTranslator,
languageMaps,
languageUsageCount,
recentLanguages,
// 计算属性
hasTranslators,
currentLanguageMap,
translatorLanguages,
// 方法
loadAvailableTranslators,
loadTranslators,
loadTranslatorLanguages,
translateText,
setDefaultConfig,
detectLanguage,
validateLanguage,
findSimilarLanguage,
getSortedLanguages,
incrementLanguageUsage
};
}, {
persist: {
key: 'voidraft-translation',
storage: localStorage,
pick: ['languageUsageCount', 'defaultTargetLang', 'defaultTranslator', 'recentLanguages']
}
});
});

View File

@@ -1,364 +1,355 @@
import { Extension, StateField, StateEffect } from '@codemirror/state';
import { Extension, StateField, StateEffect, StateEffectType } from '@codemirror/state';
import { EditorView, showTooltip, Tooltip } from '@codemirror/view';
import { createTranslationTooltip } from './tooltip';
/**
* 翻译器扩展配置
*/
export interface TranslatorConfig {
/** 默认翻译服务提供商 */
defaultTranslator: string;
/** 最小选择字符数才显示翻译按钮 */
minSelectionLength: number;
/** 最大翻译字符数 */
maxTranslationLength: number;
}
/**
* 默认翻译器配置
*/
export const defaultConfig: TranslatorConfig = {
defaultTranslator: 'bing',
minSelectionLength: 2,
maxTranslationLength: 5000,
};
// 全局配置存储
let currentConfig: TranslatorConfig = {...defaultConfig};
// 存储选择的文本用于翻译
let selectedTextForTranslation = "";
import {
TranslatorConfig,
DEFAULT_TRANSLATION_CONFIG,
TRANSLATION_ICON_SVG
} from '@/common/constant/translation';
/**
* 翻译图标SVG
*/
const translationIconSvg = `
<svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24">
<path d="M599.68 485.056h-8l30.592 164.672c20.352-7.04 38.72-17.344 54.912-31.104a271.36 271.36 0 0 1-40.704-64.64l32.256-4.032c8.896 17.664 19.072 33.28 30.592 46.72 23.872-27.968 42.24-65.152 55.04-111.744l-154.688 0.128z m121.92 133.76c18.368 15.36 39.36 26.56 62.848 33.472l14.784 4.416-8.64 30.336-14.72-4.352a205.696 205.696 0 0 1-76.48-41.728c-20.672 17.92-44.928 31.552-71.232 40.064l20.736 110.912H519.424l-9.984 72.512h385.152c18.112 0 32.704-14.144 32.704-31.616V295.424a32.128 32.128 0 0 0-32.704-31.552H550.528l35.2 189.696h79.424v-31.552h61.44v31.552h102.4v31.616h-42.688c-14.272 55.488-35.712 100.096-64.64 133.568zM479.36 791.68H193.472c-36.224 0-65.472-28.288-65.472-63.168V191.168C128 156.16 157.312 128 193.472 128h327.68l20.544 104.32h352.832c36.224 0 65.472 28.224 65.472 63.104v537.408c0 34.944-29.312 63.168-65.472 63.168H468.608l10.688-104.32zM337.472 548.352v-33.28H272.768v-48.896h60.16V433.28h-60.16v-41.728h64.704v-32.896h-102.4v189.632h102.4z m158.272 0V453.76c0-17.216-4.032-30.272-12.16-39.488-8.192-9.152-20.288-13.696-36.032-13.696a55.04 55.04 0 0 0-24.768 5.376 39.04 39.04 0 0 0-17.088 15.936h-1.984l-5.056-18.56h-28.352V548.48h37.12V480c0-17.088 2.304-29.376 6.912-36.736 4.608-7.424 12.16-11.072 22.528-11.072 7.616 0 13.248 2.56 16.64 7.872 3.52 5.248 5.312 13.056 5.312 23.488v84.736h36.928z" fill="currentColor"></path>
</svg>`;
class TranslatorExtension {
private config: TranslatorConfig;
private setTranslationTooltip: StateEffectType<Tooltip | null>;
private translationTooltipField: StateField<readonly Tooltip[]>;
private translationButtonField: StateField<readonly Tooltip[]>;
// 用于设置翻译气泡的状态效果
const setTranslationTooltip = StateEffect.define<Tooltip | null>();
constructor(config?: Partial<TranslatorConfig>) {
// 初始化配置
this.config = {
minSelectionLength: DEFAULT_TRANSLATION_CONFIG.minSelectionLength,
maxTranslationLength: DEFAULT_TRANSLATION_CONFIG.maxTranslationLength,
...config
};
/**
* 翻译气泡的状态字段
*/
const translationTooltipField = StateField.define<readonly Tooltip[]>({
create() {
return [];
},
update(tooltips, tr) {
// 如果文档或选择变化,隐藏气泡
if (tr.docChanged || tr.selection) {
return [];
}
// 检查是否有特定的状态效果来更新tooltips
for (const effect of tr.effects) {
if (effect.is(setTranslationTooltip)) {
return effect.value ? [effect.value] : [];
}
}
return tooltips;
},
provide: field => showTooltip.computeN([field], state => state.field(field))
});
// 初始化状态效果
this.setTranslationTooltip = StateEffect.define<Tooltip | null>();
/**
* 根据当前选择获取翻译按钮tooltip
*/
function getTranslationButtonTooltips(state: any): readonly Tooltip[] {
// 如果气泡已显示,则不显示按钮
if (state.field(translationTooltipField).length > 0) return [];
const selection = state.selection.main;
// 如果没有选中文本,不显示按钮
if (selection.empty) return [];
// 获取选中的文本
const selectedText = state.sliceDoc(selection.from, selection.to);
// 检查文本是否只包含空格
if (!selectedText.trim()) {
return [];
}
// 检查文本长度条件
if (selectedText.length < currentConfig.minSelectionLength ||
selectedText.length > currentConfig.maxTranslationLength) {
return [];
}
// 保存选中的文本用于翻译
selectedTextForTranslation = selectedText;
// 返回翻译按钮tooltip配置
return [{
pos: selection.to,
above: false,
strictSide: true,
arrow: false,
create: (view) => {
// 创建按钮DOM
const dom = document.createElement('div');
dom.className = 'cm-translator-button';
dom.innerHTML = translationIconSvg;
// 点击事件
dom.addEventListener('mousedown', (e) => {
e.preventDefault();
e.stopPropagation();
// 初始化翻译气泡状态字段
this.translationTooltipField = StateField.define<readonly Tooltip[]>({
create: () => [],
update: (tooltips, tr) => {
// 检查是否有特定的状态效果来更新tooltips
for (const effect of tr.effects) {
if (effect.is(this.setTranslationTooltip)) {
return effect.value ? [effect.value] : [];
}
}
// 显示翻译气泡
showTranslationTooltip(view);
});
// 如果文档或选择变化,隐藏气泡
if (tr.docChanged || tr.selection) {
return [];
}
return tooltips;
},
provide: field => showTooltip.computeN([field], state => state.field(field))
});
return { dom };
}
}];
}
// 初始化翻译按钮状态字段
this.translationButtonField = StateField.define<readonly Tooltip[]>({
create: (state) => this.getTranslationButtonTooltips(state),
update: (tooltips, tr) => {
// 如果文档或选择变化重新计算tooltip
if (tr.docChanged || tr.selection) {
return this.getTranslationButtonTooltips(tr.state);
}
// 检查是否有翻译气泡显示,如果有则不显示按钮
if (tr.state.field(this.translationTooltipField).length > 0) {
return [];
}
return tooltips;
},
provide: field => showTooltip.computeN([field], state => state.field(field))
});
}
/**
* 显示翻译气泡
*/
function showTranslationTooltip(view: EditorView) {
if (!selectedTextForTranslation) return;
// 创建翻译气泡
const tooltip = createTranslationTooltip(view, selectedTextForTranslation);
// 更新状态以显示气泡
view.dispatch({
effects: setTranslationTooltip.of(tooltip)
});
}
/**
* 翻译按钮的状态字段
*/
const translationButtonField = StateField.define<readonly Tooltip[]>({
create(state) {
return getTranslationButtonTooltips(state);
},
update(tooltips, tr) {
// 如果文档或选择变化重新计算tooltip
if (tr.docChanged || tr.selection) {
return getTranslationButtonTooltips(tr.state);
}
/**
* 根据当前选择获取翻译按钮tooltip
*/
private getTranslationButtonTooltips(state: any): readonly Tooltip[] {
// 如果气泡已显示,则不显示按钮
if (state.field(this.translationTooltipField).length > 0) return [];
// 检查是否有翻译气泡显示,如果有则不显示按钮
if (tr.state.field(translationTooltipField).length > 0) {
const selection = state.selection.main;
// 如果没有选中文本,不显示按钮
if (selection.empty) return [];
// 获取选中的文本
const selectedText = state.sliceDoc(selection.from, selection.to);
// 检查文本是否只包含空格
if (!selectedText.trim()) {
return [];
}
return tooltips;
},
// 检查文本长度条件
if (selectedText.length < this.config.minSelectionLength ||
selectedText.length > this.config.maxTranslationLength) {
return [];
}
// 返回翻译按钮tooltip配置
return [{
pos: selection.to,
above: false,
strictSide: true,
arrow: false,
create: (view) => {
// 创建按钮DOM
const dom = document.createElement('div');
dom.className = 'cm-translator-button';
dom.innerHTML = TRANSLATION_ICON_SVG;
provide: field => showTooltip.computeN([field], state => state.field(field))
});
// 点击事件
dom.addEventListener('mousedown', (e) => {
e.preventDefault();
e.stopPropagation();
// 显示翻译气泡
this.showTranslationTooltip(view);
});
return { dom };
}
}];
}
/**
* 显示翻译气泡
*/
private showTranslationTooltip(view: EditorView) {
// 直接从当前选择获取文本
const selection = view.state.selection.main;
if (selection.empty) return;
const selectedText = view.state.sliceDoc(selection.from, selection.to);
if (!selectedText.trim()) return;
// 创建翻译气泡
const tooltip = createTranslationTooltip(view, selectedText);
// 更新状态以显示气泡
view.dispatch({
effects: this.setTranslationTooltip.of(tooltip)
});
}
/**
* 创建扩展
*/
createExtension(): Extension {
return [
// 翻译按钮tooltip
this.translationButtonField,
// 翻译气泡tooltip
this.translationTooltipField,
// 添加基础样式
EditorView.baseTheme({
".cm-translator-button": {
display: "flex",
alignItems: "center",
justifyContent: "center",
cursor: "pointer",
background: "var(--bg-secondary, transparent)",
color: "var(--text-muted, #4285f4)",
border: "1px solid var(--border-color, #dadce0)",
borderRadius: "3px",
padding: "2px",
width: "24px",
height: "24px",
boxShadow: "0 1px 2px rgba(0, 0, 0, 0.08)",
userSelect: "none",
"&:hover": {
background: "var(--bg-hover, rgba(66, 133, 244, 0.1))"
}
},
// 翻译气泡样式
".cm-translation-tooltip": {
background: "var(--bg-secondary, #fff)",
color: "var(--text-primary, #333)",
border: "1px solid var(--border-color, #dadce0)",
borderRadius: "3px",
boxShadow: "0 2px 8px rgba(0, 0, 0, 0.1)",
padding: "8px",
maxWidth: "300px",
maxHeight: "200px",
display: "flex",
flexDirection: "column",
overflow: "hidden",
fontFamily: "var(--font-family, system-ui, -apple-system, sans-serif)",
fontSize: "11px",
userSelect: "none",
cursor: "grab"
},
// 拖拽状态样式
".cm-translation-dragging": {
boxShadow: "0 4px 16px rgba(0, 0, 0, 0.2)",
zIndex: "1000",
cursor: "grabbing !important"
},
".cm-translation-header": {
marginBottom: "8px",
flexShrink: "0"
},
".cm-translation-controls": {
display: "flex",
alignItems: "center",
gap: "4px",
flexWrap: "nowrap"
},
".cm-translation-select": {
padding: "2px 4px",
borderRadius: "3px",
border: "1px solid var(--border-color, #dadce0)",
background: "var(--bg-primary, #f5f5f5)",
fontSize: "11px",
color: "var(--text-primary, #333)",
flex: "1",
minWidth: "0",
maxWidth: "80px"
},
".cm-translation-swap": {
display: "flex",
alignItems: "center",
justifyContent: "center",
width: "16px",
height: "16px",
borderRadius: "3px",
border: "1px solid var(--border-color, #dadce0)",
background: "var(--bg-primary, transparent)",
color: "var(--text-muted, #666)",
cursor: "pointer",
padding: "0",
flexShrink: "0",
"&:hover": {
background: "var(--bg-hover, rgba(66, 133, 244, 0.1))"
}
},
// 滚动容器
".cm-translation-scroll-container": {
overflowY: "auto",
flex: "1",
minHeight: "0"
},
".cm-translation-result": {
display: "flex",
flexDirection: "column"
},
".cm-translation-result-header": {
display: "flex",
justifyContent: "flex-end",
marginBottom: "4px"
},
".cm-translation-result-wrapper": {
position: "relative",
width: "100%"
},
".cm-translation-copy-btn": {
display: "flex",
alignItems: "center",
justifyContent: "center",
width: "20px",
height: "20px",
borderRadius: "3px",
border: "1px solid var(--border-color, #dadce0)",
background: "var(--bg-primary, transparent)",
color: "var(--text-muted, #666)",
cursor: "pointer",
padding: "0",
position: "absolute",
top: "4px",
right: "4px",
zIndex: "2",
opacity: "0.7",
"&:hover": {
background: "var(--bg-hover, rgba(66, 133, 244, 0.1))",
opacity: "1"
},
"&.copied": {
background: "var(--bg-success, #4caf50)",
color: "white",
border: "1px solid var(--bg-success, #4caf50)",
opacity: "1"
}
},
".cm-translation-target": {
padding: "6px",
paddingRight: "28px", // 为复制按钮留出空间
background: "var(--bg-primary, rgba(66, 133, 244, 0.05))",
color: "var(--text-primary, #333)",
borderRadius: "3px",
whiteSpace: "pre-wrap",
wordBreak: "break-word"
},
".cm-translation-notice": {
fontSize: "10px",
color: "var(--text-muted, #888)",
padding: "2px 0",
fontStyle: "italic",
textAlign: "center",
marginBottom: "2px"
},
".cm-translation-error": {
color: "var(--text-danger, #d32f2f)",
fontStyle: "italic"
},
".cm-translation-loading": {
padding: "8px",
textAlign: "center",
color: "var(--text-muted, #666)",
fontSize: "11px",
display: "flex",
alignItems: "center",
justifyContent: "center",
gap: "6px"
},
".cm-translation-loading::before": {
content: "''",
display: "inline-block",
width: "12px",
height: "12px",
borderRadius: "50%",
border: "2px solid var(--text-muted, #666)",
borderTopColor: "transparent",
animation: "cm-translation-spin 1s linear infinite"
},
"@keyframes cm-translation-spin": {
"0%": { transform: "rotate(0deg)" },
"100%": { transform: "rotate(360deg)" }
}
})
];
}
}
/**
* 创建翻译扩展
*/
export function createTranslatorExtension(config?: Partial<TranslatorConfig>): Extension {
// 更新配置
currentConfig = { ...defaultConfig, ...config };
return [
// 翻译按钮tooltip
translationButtonField,
// 翻译气泡tooltip
translationTooltipField,
// 添加基础样式
EditorView.baseTheme({
".cm-translator-button": {
display: "flex",
alignItems: "center",
justifyContent: "center",
cursor: "pointer",
background: "var(--bg-secondary, transparent)",
color: "var(--text-muted, #4285f4)",
border: "1px solid var(--border-color, #dadce0)",
borderRadius: "3px",
padding: "2px",
width: "24px",
height: "24px",
boxShadow: "0 1px 2px rgba(0, 0, 0, 0.08)",
userSelect: "none",
"&:hover": {
background: "var(--bg-hover, rgba(66, 133, 244, 0.1))"
}
},
// 翻译气泡样式
".cm-translation-tooltip": {
background: "var(--bg-secondary, #fff)",
color: "var(--text-primary, #333)",
border: "1px solid var(--border-color, #dadce0)",
borderRadius: "3px",
boxShadow: "0 2px 8px rgba(0, 0, 0, 0.1)",
padding: "8px",
maxWidth: "300px",
maxHeight: "200px",
display: "flex",
flexDirection: "column",
overflow: "hidden",
fontFamily: "var(--font-family, system-ui, -apple-system, sans-serif)",
fontSize: "11px"
},
".cm-translation-header": {
marginBottom: "8px",
flexShrink: "0"
},
".cm-translation-controls": {
display: "flex",
alignItems: "center",
gap: "4px",
flexWrap: "nowrap"
},
".cm-translation-select": {
padding: "2px 4px",
borderRadius: "3px",
border: "1px solid var(--border-color, #dadce0)",
background: "var(--bg-primary, #f5f5f5)",
fontSize: "11px",
color: "var(--text-primary, #333)",
flex: "1",
minWidth: "0",
maxWidth: "80px"
},
".cm-translation-swap": {
display: "flex",
alignItems: "center",
justifyContent: "center",
width: "16px",
height: "16px",
borderRadius: "3px",
border: "1px solid var(--border-color, #dadce0)",
background: "var(--bg-primary, transparent)",
color: "var(--text-muted, #666)",
cursor: "pointer",
padding: "0",
flexShrink: "0",
"&:hover": {
background: "var(--bg-hover, rgba(66, 133, 244, 0.1))"
}
},
// 滚动容器
".cm-translation-scroll-container": {
overflowY: "auto",
flex: "1",
minHeight: "0"
},
".cm-translation-result": {
display: "flex",
flexDirection: "column"
},
".cm-translation-result-header": {
display: "flex",
justifyContent: "flex-end",
marginBottom: "4px"
},
".cm-translation-result-wrapper": {
position: "relative",
width: "100%"
},
".cm-translation-copy-btn": {
display: "flex",
alignItems: "center",
justifyContent: "center",
width: "20px",
height: "20px",
borderRadius: "3px",
border: "1px solid var(--border-color, #dadce0)",
background: "var(--bg-primary, transparent)",
color: "var(--text-muted, #666)",
cursor: "pointer",
padding: "0",
position: "absolute",
top: "4px",
right: "4px",
zIndex: "2",
opacity: "0.7",
"&:hover": {
background: "var(--bg-hover, rgba(66, 133, 244, 0.1))",
opacity: "1"
},
"&.copied": {
background: "var(--bg-success, #4caf50)",
color: "white",
border: "1px solid var(--bg-success, #4caf50)",
opacity: "1"
}
},
".cm-translation-target": {
padding: "6px",
paddingRight: "28px", // 为复制按钮留出空间
background: "var(--bg-primary, rgba(66, 133, 244, 0.05))",
color: "var(--text-primary, #333)",
borderRadius: "3px",
whiteSpace: "pre-wrap",
wordBreak: "break-word"
},
".cm-translation-notice": {
fontSize: "10px",
color: "var(--text-muted, #888)",
padding: "2px 0",
fontStyle: "italic",
textAlign: "center",
marginBottom: "2px"
},
".cm-translation-error": {
color: "var(--text-danger, #d32f2f)",
fontStyle: "italic"
},
".cm-translation-loading": {
padding: "8px",
textAlign: "center",
color: "var(--text-muted, #666)",
fontSize: "11px",
display: "flex",
alignItems: "center",
justifyContent: "center",
gap: "6px"
},
".cm-translation-loading::before": {
content: "''",
display: "inline-block",
width: "12px",
height: "12px",
borderRadius: "50%",
border: "2px solid var(--text-muted, #666)",
borderTopColor: "transparent",
animation: "cm-translation-spin 1s linear infinite"
},
"@keyframes cm-translation-spin": {
"0%": { transform: "rotate(0deg)" },
"100%": { transform: "rotate(360deg)" }
}
})
];
const translatorExtension = new TranslatorExtension(config);
return translatorExtension.createExtension();
}
export default createTranslatorExtension;
export default createTranslatorExtension;

File diff suppressed because it is too large Load Diff

View File

@@ -158,14 +158,12 @@ export const checkboxFactory: ExtensionFactory = {
export const translatorFactory: ExtensionFactory = {
create(config: any) {
return createTranslatorExtension({
defaultTranslator: config.defaultTranslator || 'bing',
minSelectionLength: config.minSelectionLength || 2,
maxTranslationLength: config.maxTranslationLength || 5000,
});
},
getDefaultConfig() {
return {
defaultTranslator: 'bing',
minSelectionLength: 2,
maxTranslationLength: 5000,
};

View File

@@ -8,12 +8,39 @@ import SettingItem from '../components/SettingItem.vue';
import { SystemThemeType, LanguageType } from '@/../bindings/voidraft/internal/models/models';
import { defaultDarkColors } from '@/views/editor/theme/dark';
import { defaultLightColors } from '@/views/editor/theme/light';
import { createDebounce } from '@/common/utils/debounce';
import { createTimerManager } from '@/common/utils/timerUtils';
import PickColors from 'vue-pick-colors';
const { t } = useI18n();
const configStore = useConfigStore();
const themeStore = useThemeStore();
// 创建防抖函数实例
const { debouncedFn: debouncedUpdateColor } = createDebounce(
(colorKey: string, value: string) => updateLocalColor(colorKey, value),
{ delay: 100 }
);
const { debouncedFn: debouncedResetTheme } = createDebounce(
async () => {
const themeType = activeThemeType.value;
const success = await themeStore.resetThemeColors(themeType);
if (success) {
tempColors.value = {
darkTheme: { ...themeStore.themeColors.darkTheme },
lightTheme: { ...themeStore.themeColors.lightTheme }
};
hasUnsavedChanges.value = false;
}
},
{ delay: 300 }
);
// 创建定时器管理器
const resetTimer = createTimerManager();
// 添加临时颜色状态
const tempColors = ref({
darkTheme: { ...defaultDarkColors },
@@ -25,36 +52,19 @@ const hasUnsavedChanges = ref(false);
// 重置按钮状态
const resetButtonState = ref({
confirming: false,
timer: null as number | null
confirming: false
});
// 防抖函数
const debounce = <T extends (...args: any[]) => any>(
func: T,
wait: number
): ((...args: Parameters<T>) => void) => {
let timeout: number | undefined;
return function(...args: Parameters<T>): void {
clearTimeout(timeout);
timeout = window.setTimeout(() => {
func(...args);
}, wait);
};
};
// 当前激活的主题类型
const isDarkMode = computed(() =>
themeStore.currentTheme === SystemThemeType.SystemThemeDark ||
(themeStore.currentTheme === SystemThemeType.SystemThemeAuto &&
window.matchMedia('(prefers-color-scheme: dark)').matches)
);
// 当前激活的主题类型(基于当前系统主题)
const activeThemeType = computed(() => {
const isDark =
themeStore.currentTheme === SystemThemeType.SystemThemeDark ||
(themeStore.currentTheme === SystemThemeType.SystemThemeAuto &&
window.matchMedia('(prefers-color-scheme: dark)').matches);
return isDark ? 'darkTheme' : 'lightTheme';
});
const activeThemeType = computed(() => isDarkMode.value ? 'darkTheme' : 'lightTheme');
// 当前主题的颜色配置 - 使用临时状态
// 当前主题的颜色配置
const currentColors = computed(() => {
const themeType = activeThemeType.value;
return tempColors.value[themeType] ||
@@ -62,144 +72,85 @@ const currentColors = computed(() => {
});
// 获取当前主题模式
const currentThemeMode = computed(() => {
const isDark =
themeStore.currentTheme === SystemThemeType.SystemThemeDark ||
(themeStore.currentTheme === SystemThemeType.SystemThemeAuto &&
window.matchMedia('(prefers-color-scheme: dark)').matches);
return isDark ? 'dark' : 'light';
});
const currentThemeMode = computed(() => isDarkMode.value ? 'dark' : 'light');
// 监听主题颜色变更,更新临时颜色
// 监听主题颜色变更,
watch(
() => themeStore.themeColors,
(newValue) => {
if (!hasUnsavedChanges.value) {
tempColors.value = {
darkTheme: { ...newValue.darkTheme },
lightTheme: { ...newValue.lightTheme }
};
tempColors.value.darkTheme = { ...newValue.darkTheme };
tempColors.value.lightTheme = { ...newValue.lightTheme };
}
},
{ deep: true, immediate: true }
);
// 初始化时加载主题颜色
onMounted(() => {
// 使用themeStore中的颜色作为初始值
tempColors.value = {
darkTheme: { ...themeStore.themeColors.darkTheme },
lightTheme: { ...themeStore.themeColors.lightTheme }
};
});
// 颜色配置分组
const colorGroups = computed(() => [
// 颜色配置
const colorConfig = [
{
key: 'basic',
title: t('settings.themeColors.basic'),
colors: [
{ key: 'background', label: t('settings.themeColors.background') },
{ key: 'backgroundSecondary', label: t('settings.themeColors.backgroundSecondary') },
{ key: 'surface', label: t('settings.themeColors.surface') }
]
colors: ['background', 'backgroundSecondary', 'surface']
},
{
key: 'text',
title: t('settings.themeColors.text'),
colors: [
{ key: 'foreground', label: t('settings.themeColors.foreground') },
{ key: 'foregroundSecondary', label: t('settings.themeColors.foregroundSecondary') },
{ key: 'comment', label: t('settings.themeColors.comment') }
]
key: 'text',
colors: ['foreground', 'foregroundSecondary', 'comment']
},
{
key: 'syntax',
title: t('settings.themeColors.syntax'),
colors: [
{ key: 'keyword', label: t('settings.themeColors.keyword') },
{ key: 'string', label: t('settings.themeColors.string') },
{ key: 'function', label: t('settings.themeColors.function') },
{ key: 'number', label: t('settings.themeColors.number') },
{ key: 'operator', label: t('settings.themeColors.operator') },
{ key: 'variable', label: t('settings.themeColors.variable') },
{ key: 'type', label: t('settings.themeColors.type') }
]
colors: ['keyword', 'string', 'function', 'number', 'operator', 'variable', 'type']
},
{
key: 'interface',
title: t('settings.themeColors.interface'),
colors: [
{ key: 'cursor', label: t('settings.themeColors.cursor') },
{ key: 'selection', label: t('settings.themeColors.selection') },
{ key: 'selectionBlur', label: t('settings.themeColors.selectionBlur') },
{ key: 'activeLine', label: t('settings.themeColors.activeLine') },
{ key: 'lineNumber', label: t('settings.themeColors.lineNumber') },
{ key: 'activeLineNumber', label: t('settings.themeColors.activeLineNumber') }
]
colors: ['cursor', 'selection', 'selectionBlur', 'activeLine', 'lineNumber', 'activeLineNumber']
},
{
key: 'border',
title: t('settings.themeColors.border'),
colors: [
{ key: 'borderColor', label: t('settings.themeColors.borderColor') },
{ key: 'borderLight', label: t('settings.themeColors.borderLight') }
]
colors: ['borderColor', 'borderLight']
},
{
key: 'search',
title: t('settings.themeColors.search'),
colors: [
{ key: 'searchMatch', label: t('settings.themeColors.searchMatch') },
{ key: 'matchingBracket', label: t('settings.themeColors.matchingBracket') }
]
colors: ['searchMatch', 'matchingBracket']
}
]);
];
// 颜色配置分组
const colorGroups = computed(() =>
colorConfig.map(group => ({
key: group.key,
title: t(`settings.themeColors.${group.key}`),
colors: group.colors.map(colorKey => ({
key: colorKey,
label: t(`settings.themeColors.${colorKey}`)
}))
}))
);
// 处理重置按钮点击
const handleResetClick = () => {
if (resetButtonState.value.confirming) {
// 如果已经在确认状态,执行重置操作
resetCurrentTheme();
// 重置按钮状态
debouncedResetTheme();
resetButtonState.value.confirming = false;
if (resetButtonState.value.timer !== null) {
clearTimeout(resetButtonState.value.timer);
resetButtonState.value.timer = null;
}
resetTimer.clear();
} else {
// 进入确认状态
resetButtonState.value.confirming = true;
// 设置3秒后自动恢复
resetButtonState.value.timer = window.setTimeout(() => {
resetTimer.set(() => {
resetButtonState.value.confirming = false;
resetButtonState.value.timer = null;
}, 3000);
}
};
// 重置当前主题为默认配置
const resetCurrentTheme = debounce(async () => {
// 使用themeStore的原子重置操作
const themeType = activeThemeType.value;
const success = await themeStore.resetThemeColors(themeType);
if (success) {
// 更新临时颜色状态
tempColors.value = {
darkTheme: { ...themeStore.themeColors.darkTheme },
lightTheme: { ...themeStore.themeColors.lightTheme }
};
// 标记没有未保存的更改
hasUnsavedChanges.value = false;
}
}, 300);
// 更新本地颜色配置 - 仅更新临时状态,不提交到后端
// 更新本地颜色配置
const updateLocalColor = (colorKey: string, value: string) => {
const themeType = activeThemeType.value;
@@ -211,14 +162,10 @@ const updateLocalColor = (colorKey: string, value: string) => {
[colorKey]: value
}
};
// 标记有未保存的更改
hasUnsavedChanges.value = true;
};
// 防抖包装的颜色更新函数
const updateColor = debounce(updateLocalColor, 100);
// 应用颜色更改到系统
const applyChanges = async () => {
try {
@@ -290,7 +237,7 @@ const showPickerMap = ref<Record<string, boolean>>({});
// 颜色变更处理
const handleColorChange = (colorKey: string, value: string) => {
updateColor(colorKey, value);
debouncedUpdateColor(colorKey, value);
};
// 颜色选择器关闭处理
@@ -374,7 +321,7 @@ const handlePickerClose = () => {
<input
type="text"
:value="currentColors[color.key] || ''"
@input="updateColor(color.key, ($event.target as HTMLInputElement).value)"
@input="debouncedUpdateColor(color.key, ($event.target as HTMLInputElement).value)"
class="color-text-input"
:placeholder="t('settings.colorValue')"
/>