Added context menu

This commit is contained in:
2025-07-04 14:37:03 +08:00
parent ebee33ea7c
commit a2a332e735
21 changed files with 2696 additions and 300 deletions

View File

@@ -11,6 +11,7 @@ import * as MigrationService from "./migrationservice.js";
import * as SelfUpdateService from "./selfupdateservice.js"; import * as SelfUpdateService from "./selfupdateservice.js";
import * as StartupService from "./startupservice.js"; import * as StartupService from "./startupservice.js";
import * as SystemService from "./systemservice.js"; import * as SystemService from "./systemservice.js";
import * as TranslationService from "./translationservice.js";
import * as TrayService from "./trayservice.js"; import * as TrayService from "./trayservice.js";
export { export {
ConfigService, ConfigService,
@@ -23,6 +24,7 @@ export {
SelfUpdateService, SelfUpdateService,
StartupService, StartupService,
SystemService, SystemService,
TranslationService,
TrayService TrayService
}; };

View File

@@ -0,0 +1,87 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
/**
* TranslationService 翻译服务
* @module
*/
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import {Call as $Call, Create as $Create} from "@wailsio/runtime";
/**
* 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;
}
/**
* SetActiveTranslator 设置活跃翻译器
* @param {string} translatorType - 翻译器类型 ("google", "bing", "youdao", "deepl")
* @returns {error} 可能的错误
*/
export function SetActiveTranslator(translatorType: string): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(620567821, translatorType) as any;
return $resultPromise;
}
/**
* SetTimeout 设置翻译超时时间
* @param {int} seconds - 超时秒数
*/
export function SetTimeout(seconds: number): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(3787687384, seconds) as any;
return $resultPromise;
}
/**
* Translate 使用当前活跃翻译器进行翻译
* @param {string} text - 待翻译文本
* @param {string} from - 源语言代码 (如 "en", "zh", "auto")
* @param {string} to - 目标语言代码 (如 "en", "zh")
* @returns {string} 翻译后的文本
* @returns {error} 可能的错误
*/
export function Translate(text: string, $from: string, to: string): Promise<string> & { cancel(): void } {
let $resultPromise = $Call.ByID(2536995103, text, $from, to) as any;
return $resultPromise;
}
/**
* TranslateWith 使用指定翻译器进行翻译
* @param {string} text - 待翻译文本
* @param {string} from - 源语言代码 (如 "en", "zh", "auto")
* @param {string} to - 目标语言代码 (如 "en", "zh")
* @param {string} translatorType - 翻译器类型 ("google", "bing", "youdao", "deepl")
* @returns {string} 翻译后的文本
* @returns {error} 可能的错误
*/
export function TranslateWith(text: string, $from: string, to: string, translatorType: string): Promise<string> & { cancel(): void } {
let $resultPromise = $Call.ByID(3577923623, text, $from, to, translatorType) as any;
return $resultPromise;
}
/**
* TranslateWithFallback 尝试使用当前活跃翻译器翻译,如果失败则尝试备用翻译器
* @param {string} text - 待翻译文本
* @param {string} from - 源语言代码 (如 "en", "zh", "auto")
* @param {string} to - 目标语言代码 (如 "en", "zh")
* @returns {string} 翻译后的文本
* @returns {string} 使用的翻译器类型
* @returns {error} 可能的错误
*/
export function TranslateWithFallback(text: string, $from: string, to: string): Promise<[string, string]> & { cancel(): void } {
let $resultPromise = $Call.ByID(1705788405, text, $from, to) as any;
return $resultPromise;
}
// Private type creation functions
const $$createType0 = $Create.Array($Create.Any);

View File

@@ -11,36 +11,36 @@
"@codemirror/autocomplete": "^6.18.6", "@codemirror/autocomplete": "^6.18.6",
"@codemirror/commands": "^6.8.1", "@codemirror/commands": "^6.8.1",
"@codemirror/lang-angular": "^0.1.4", "@codemirror/lang-angular": "^0.1.4",
"@codemirror/lang-cpp": "^6.0.2", "@codemirror/lang-cpp": "^6.0.3",
"@codemirror/lang-css": "^6.3.1", "@codemirror/lang-css": "^6.3.1",
"@codemirror/lang-go": "^6.0.1", "@codemirror/lang-go": "^6.0.1",
"@codemirror/lang-html": "^6.4.9", "@codemirror/lang-html": "^6.4.9",
"@codemirror/lang-java": "^6.0.1", "@codemirror/lang-java": "^6.0.2",
"@codemirror/lang-javascript": "^6.2.4", "@codemirror/lang-javascript": "^6.2.4",
"@codemirror/lang-json": "^6.0.1", "@codemirror/lang-json": "^6.0.2",
"@codemirror/lang-less": "^6.0.2", "@codemirror/lang-less": "^6.0.2",
"@codemirror/lang-lezer": "^6.0.1", "@codemirror/lang-lezer": "^6.0.2",
"@codemirror/lang-liquid": "^6.2.3", "@codemirror/lang-liquid": "^6.2.3",
"@codemirror/lang-markdown": "^6.3.3", "@codemirror/lang-markdown": "^6.3.3",
"@codemirror/lang-php": "^6.0.1", "@codemirror/lang-php": "^6.0.2",
"@codemirror/lang-python": "^6.2.1", "@codemirror/lang-python": "^6.2.1",
"@codemirror/lang-rust": "^6.0.1", "@codemirror/lang-rust": "^6.0.2",
"@codemirror/lang-sass": "^6.0.2", "@codemirror/lang-sass": "^6.0.2",
"@codemirror/lang-sql": "^6.9.0", "@codemirror/lang-sql": "^6.9.0",
"@codemirror/lang-vue": "^0.1.3", "@codemirror/lang-vue": "^0.1.3",
"@codemirror/lang-wast": "^6.0.2", "@codemirror/lang-wast": "^6.0.2",
"@codemirror/lang-xml": "^6.1.0", "@codemirror/lang-xml": "^6.1.0",
"@codemirror/lang-yaml": "^6.1.2", "@codemirror/lang-yaml": "^6.1.2",
"@codemirror/language": "^6.11.1", "@codemirror/language": "^6.11.2",
"@codemirror/language-data": "^6.5.1", "@codemirror/language-data": "^6.5.1",
"@codemirror/legacy-modes": "^6.5.1", "@codemirror/legacy-modes": "^6.5.1",
"@codemirror/lint": "^6.8.5", "@codemirror/lint": "^6.8.5",
"@codemirror/search": "^6.5.11", "@codemirror/search": "^6.5.11",
"@codemirror/state": "^6.5.2", "@codemirror/state": "^6.5.2",
"@codemirror/view": "^6.37.2", "@codemirror/view": "^6.38.0",
"@lezer/highlight": "^1.2.1", "@lezer/highlight": "^1.2.1",
"@lezer/lr": "^1.4.2", "@lezer/lr": "^1.4.2",
"codemirror": "^6.0.1", "codemirror": "^6.0.2",
"codemirror-lang-elixir": "^4.0.0", "codemirror-lang-elixir": "^4.0.0",
"colors-named": "^1.0.2", "colors-named": "^1.0.2",
"colors-named-hex": "^1.0.2", "colors-named-hex": "^1.0.2",
@@ -48,29 +48,29 @@
"lezer": "^0.13.5", "lezer": "^0.13.5",
"pinia": "^3.0.3", "pinia": "^3.0.3",
"pinia-plugin-persistedstate": "^4.4.1", "pinia-plugin-persistedstate": "^4.4.1",
"prettier": "^3.5.3", "prettier": "^3.6.2",
"remarkable": "^2.0.1", "remarkable": "^2.0.1",
"sass": "^1.89.2", "sass": "^1.89.2",
"vue": "^3.5.17", "vue": "^3.5.17",
"vue-i18n": "^11.1.6", "vue-i18n": "^11.1.9",
"vue-router": "^4.5.1" "vue-router": "^4.5.1"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.29.0", "@eslint/js": "^9.30.1",
"@lezer/generator": "^1.7.3", "@lezer/generator": "^1.8.0",
"@types/node": "^24.0.3", "@types/node": "^24.0.10",
"@types/remarkable": "^2.0.8", "@types/remarkable": "^2.0.8",
"@vitejs/plugin-vue": "^5.2.4", "@vitejs/plugin-vue": "^6.0.0",
"@wailsio/runtime": "latest", "@wailsio/runtime": "latest",
"eslint": "^9.29.0", "eslint": "^9.30.1",
"eslint-plugin-vue": "^10.2.0", "eslint-plugin-vue": "^10.3.0",
"globals": "^16.2.0", "globals": "^16.3.0",
"typescript": "^5.8.3", "typescript": "^5.8.3",
"typescript-eslint": "^8.34.1", "typescript-eslint": "^8.35.1",
"unplugin-vue-components": "^28.7.0", "unplugin-vue-components": "^28.8.0",
"vite": "^6.3.5", "vite": "^7.0.1",
"vue-eslint-parser": "^10.1.3", "vue-eslint-parser": "^10.2.0",
"vue-tsc": "^2.2.10" "vue-tsc": "^3.0.1"
} }
}, },
"node_modules/@babel/helper-string-parser": { "node_modules/@babel/helper-string-parser": {
@@ -158,9 +158,9 @@
} }
}, },
"node_modules/@codemirror/lang-cpp": { "node_modules/@codemirror/lang-cpp": {
"version": "6.0.2", "version": "6.0.3",
"resolved": "https://registry.npmmirror.com/@codemirror/lang-cpp/-/lang-cpp-6.0.2.tgz", "resolved": "https://registry.npmmirror.com/@codemirror/lang-cpp/-/lang-cpp-6.0.3.tgz",
"integrity": "sha512-6oYEYUKHvrnacXxWxYa6t4puTlbN3dgV662BDfSH8+MfjQjVmP697/KYTDOqpxgerkvoNm7q5wlFMBeX8ZMocg==", "integrity": "sha512-URM26M3vunFFn9/sm6rzqrBzDgfWuDixp85uTY49wKudToc2jTHUrKIGGKs+QWND+YLofNNZpxcNGRynFJfvgA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@codemirror/language": "^6.0.0", "@codemirror/language": "^6.0.0",
@@ -211,9 +211,9 @@
} }
}, },
"node_modules/@codemirror/lang-java": { "node_modules/@codemirror/lang-java": {
"version": "6.0.1", "version": "6.0.2",
"resolved": "https://registry.npmmirror.com/@codemirror/lang-java/-/lang-java-6.0.1.tgz", "resolved": "https://registry.npmmirror.com/@codemirror/lang-java/-/lang-java-6.0.2.tgz",
"integrity": "sha512-OOnmhH67h97jHzCuFaIEspbmsT98fNdhVhmA3zCxW0cn7l8rChDhZtwiwJ/JOKXgfm4J+ELxQihxaI7bj7mJRg==", "integrity": "sha512-m5Nt1mQ/cznJY7tMfQTJchmrjdjQ71IDs+55d1GAa8DGaB8JXWsVCkVT284C3RTASaY43YknrK2X3hPO/J3MOQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@codemirror/language": "^6.0.0", "@codemirror/language": "^6.0.0",
@@ -236,9 +236,9 @@
} }
}, },
"node_modules/@codemirror/lang-json": { "node_modules/@codemirror/lang-json": {
"version": "6.0.1", "version": "6.0.2",
"resolved": "https://registry.npmmirror.com/@codemirror/lang-json/-/lang-json-6.0.1.tgz", "resolved": "https://registry.npmmirror.com/@codemirror/lang-json/-/lang-json-6.0.2.tgz",
"integrity": "sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ==", "integrity": "sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@codemirror/language": "^6.0.0", "@codemirror/language": "^6.0.0",
@@ -259,9 +259,9 @@
} }
}, },
"node_modules/@codemirror/lang-lezer": { "node_modules/@codemirror/lang-lezer": {
"version": "6.0.1", "version": "6.0.2",
"resolved": "https://registry.npmmirror.com/@codemirror/lang-lezer/-/lang-lezer-6.0.1.tgz", "resolved": "https://registry.npmmirror.com/@codemirror/lang-lezer/-/lang-lezer-6.0.2.tgz",
"integrity": "sha512-WHwjI7OqKFBEfkunohweqA5B/jIlxaZso6Nl3weVckz8EafYbPZldQEKSDb4QQ9H9BUkle4PVELP4sftKoA0uQ==", "integrity": "sha512-mcVAf8lw+sCfSlr2ivMqV8JtNmOQjSXdA1vHKRtoW0OZsz1k6qhF+DX0K2TbWlAThqiGgRkRSZyYzIoEtKB2uQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@codemirror/language": "^6.0.0", "@codemirror/language": "^6.0.0",
@@ -302,9 +302,9 @@
} }
}, },
"node_modules/@codemirror/lang-php": { "node_modules/@codemirror/lang-php": {
"version": "6.0.1", "version": "6.0.2",
"resolved": "https://registry.npmmirror.com/@codemirror/lang-php/-/lang-php-6.0.1.tgz", "resolved": "https://registry.npmmirror.com/@codemirror/lang-php/-/lang-php-6.0.2.tgz",
"integrity": "sha512-ublojMdw/PNWa7qdN5TMsjmqkNuTBD3k6ndZ4Z0S25SBAiweFGyY68AS3xNcIOlb6DDFDvKlinLQ40vSLqf8xA==", "integrity": "sha512-ZKy2v1n8Fc8oEXj0Th0PUMXzQJ0AIR6TaZU+PbDHExFwdu+guzOA4jmCHS1Nz4vbFezwD7LyBdDnddSJeScMCA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@codemirror/lang-html": "^6.0.0", "@codemirror/lang-html": "^6.0.0",
@@ -328,9 +328,9 @@
} }
}, },
"node_modules/@codemirror/lang-rust": { "node_modules/@codemirror/lang-rust": {
"version": "6.0.1", "version": "6.0.2",
"resolved": "https://registry.npmmirror.com/@codemirror/lang-rust/-/lang-rust-6.0.1.tgz", "resolved": "https://registry.npmmirror.com/@codemirror/lang-rust/-/lang-rust-6.0.2.tgz",
"integrity": "sha512-344EMWFBzWArHWdZn/NcgkwMvZIWUR1GEBdwG8FEp++6o6vT6KL9V7vGs2ONsKxxFUPXKI0SPcWhyYyl2zPYxQ==", "integrity": "sha512-EZaGjCUegtiU7kSMvOfEZpaCReowEf3yNidYu7+vfuGTm9ow4mthAparY5hisJqOHmJowVH3Upu+eJlUji6qqA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@codemirror/language": "^6.0.0", "@codemirror/language": "^6.0.0",
@@ -420,9 +420,9 @@
} }
}, },
"node_modules/@codemirror/language": { "node_modules/@codemirror/language": {
"version": "6.11.1", "version": "6.11.2",
"resolved": "https://registry.npmmirror.com/@codemirror/language/-/language-6.11.1.tgz", "resolved": "https://registry.npmmirror.com/@codemirror/language/-/language-6.11.2.tgz",
"integrity": "sha512-5kS1U7emOGV84vxC+ruBty5sUgcD0te6dyupyRVG2zaSjhTDM73LhVKUtVwiqSe6QwmEoA4SCiU8AKPFyumAWQ==", "integrity": "sha512-p44TsNArL4IVXDTbapUmEkAlvWs2CFQbcfc0ymDsis1kH2wh0gcY96AS29c/vp2d0y2Tquk1EDSaawpzilUiAw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@codemirror/state": "^6.0.0", "@codemirror/state": "^6.0.0",
@@ -504,9 +504,9 @@
} }
}, },
"node_modules/@codemirror/view": { "node_modules/@codemirror/view": {
"version": "6.37.2", "version": "6.38.0",
"resolved": "https://registry.npmmirror.com/@codemirror/view/-/view-6.37.2.tgz", "resolved": "https://registry.npmmirror.com/@codemirror/view/-/view-6.38.0.tgz",
"integrity": "sha512-XD3LdgQpxQs5jhOOZ2HRVT+Rj59O4Suc7g2ULvZ+Yi8eCkickrkZ5JFuoDhs2ST1mNI5zSsNYgR3NGa4OUrbnw==", "integrity": "sha512-yvSchUwHOdupXkd7xJ0ob36jdsSR/I+/C+VbY0ffBiL5NiSTEBDfB1ZGWbbIlDd5xgdUkody+lukAdOxYrOBeg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@codemirror/state": "^6.5.0", "@codemirror/state": "^6.5.0",
@@ -983,9 +983,9 @@
} }
}, },
"node_modules/@eslint/config-array": { "node_modules/@eslint/config-array": {
"version": "0.20.1", "version": "0.21.0",
"resolved": "https://registry.npmmirror.com/@eslint/config-array/-/config-array-0.20.1.tgz", "resolved": "https://registry.npmmirror.com/@eslint/config-array/-/config-array-0.21.0.tgz",
"integrity": "sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw==", "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
@@ -998,9 +998,9 @@
} }
}, },
"node_modules/@eslint/config-helpers": { "node_modules/@eslint/config-helpers": {
"version": "0.2.1", "version": "0.3.0",
"resolved": "https://registry.npmmirror.com/@eslint/config-helpers/-/config-helpers-0.2.1.tgz", "resolved": "https://registry.npmmirror.com/@eslint/config-helpers/-/config-helpers-0.3.0.tgz",
"integrity": "sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==", "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"engines": { "engines": {
@@ -1058,9 +1058,9 @@
} }
}, },
"node_modules/@eslint/js": { "node_modules/@eslint/js": {
"version": "9.29.0", "version": "9.30.1",
"resolved": "https://registry.npmmirror.com/@eslint/js/-/js-9.29.0.tgz", "resolved": "https://registry.npmmirror.com/@eslint/js/-/js-9.30.1.tgz",
"integrity": "sha512-3PIF4cBw/y+1u2EazflInpV+lYsSG0aByVIQzAgb1m1MhHFSbqTyNqtBKHgWf/9Ykud+DhILS9EGkmekVhbKoQ==", "integrity": "sha512-zXhuECFlyep42KZUhWjfvsmXGX39W8K8LFb8AWXM9gSV9dQB+MrJGLKvW6Zw0Ggnbpw0VHTtrhFXYe3Gym18jg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -1161,13 +1161,13 @@
} }
}, },
"node_modules/@intlify/core-base": { "node_modules/@intlify/core-base": {
"version": "11.1.6", "version": "11.1.9",
"resolved": "https://registry.npmmirror.com/@intlify/core-base/-/core-base-11.1.6.tgz", "resolved": "https://registry.npmmirror.com/@intlify/core-base/-/core-base-11.1.9.tgz",
"integrity": "sha512-gfMLnoWGiQkA1BwK6Qbrog/e3I6Lnkhqk08XObJb0lMq6sLG1Ggl2MazVaMfGnv/E1Td8pCS5UwR54Ys+fOxmQ==", "integrity": "sha512-Lrdi4wp3XnGhWmB/mMD/XtfGUw1Jt+PGpZI/M63X1ZqhTDjNHRVCs/i8vv8U1cwaj1A9fb0bkCQHLSL0SK+pIQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@intlify/message-compiler": "11.1.6", "@intlify/message-compiler": "11.1.9",
"@intlify/shared": "11.1.6" "@intlify/shared": "11.1.9"
}, },
"engines": { "engines": {
"node": ">= 16" "node": ">= 16"
@@ -1177,12 +1177,12 @@
} }
}, },
"node_modules/@intlify/message-compiler": { "node_modules/@intlify/message-compiler": {
"version": "11.1.6", "version": "11.1.9",
"resolved": "https://registry.npmmirror.com/@intlify/message-compiler/-/message-compiler-11.1.6.tgz", "resolved": "https://registry.npmmirror.com/@intlify/message-compiler/-/message-compiler-11.1.9.tgz",
"integrity": "sha512-w0LYo5sqgQZF3vEmjLlx+5PYk5EEiB+uigsBkka/DKoAIH2c5xlXcjAxhTgSw35Vrck+GOGriahFsfbHL+ZjPw==", "integrity": "sha512-84SNs3Ikjg0rD1bOuchzb3iK1vR2/8nxrkyccIl5DjFTeMzE/Fxv6X+A7RN5ZXjEWelc1p5D4kHA6HEOhlKL5Q==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@intlify/shared": "11.1.6", "@intlify/shared": "11.1.9",
"source-map-js": "^1.0.2" "source-map-js": "^1.0.2"
}, },
"engines": { "engines": {
@@ -1193,9 +1193,9 @@
} }
}, },
"node_modules/@intlify/shared": { "node_modules/@intlify/shared": {
"version": "11.1.6", "version": "11.1.9",
"resolved": "https://registry.npmmirror.com/@intlify/shared/-/shared-11.1.6.tgz", "resolved": "https://registry.npmmirror.com/@intlify/shared/-/shared-11.1.9.tgz",
"integrity": "sha512-G1Pe4UILhiGOItuehRW+Pk9/NlnRaMFsdnhZ1fwBjiHvrzitmPNZdLx7Eo3GPfRrsk1mdkilZSfgH8SnM419vA==", "integrity": "sha512-H/83xgU1l8ox+qG305p6ucmoy93qyjIPnvxGWRA7YdOoHe1tIiW9IlEu4lTdsOR7cfP1ecrwyflQSqXdXBacXA==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 16" "node": ">= 16"
@@ -1204,6 +1204,29 @@
"url": "https://github.com/sponsors/kazupon" "url": "https://github.com/sponsors/kazupon"
} }
}, },
"node_modules/@isaacs/balanced-match": {
"version": "4.0.1",
"resolved": "https://registry.npmmirror.com/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz",
"integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": "20 || >=22"
}
},
"node_modules/@isaacs/brace-expansion": {
"version": "5.0.0",
"resolved": "https://registry.npmmirror.com/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz",
"integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@isaacs/balanced-match": "^4.0.1"
},
"engines": {
"node": "20 || >=22"
}
},
"node_modules/@jridgewell/sourcemap-codec": { "node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.0", "version": "1.5.0",
"resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
@@ -1239,9 +1262,9 @@
} }
}, },
"node_modules/@lezer/generator": { "node_modules/@lezer/generator": {
"version": "1.7.3", "version": "1.8.0",
"resolved": "https://registry.npmmirror.com/@lezer/generator/-/generator-1.7.3.tgz", "resolved": "https://registry.npmmirror.com/@lezer/generator/-/generator-1.8.0.tgz",
"integrity": "sha512-vAI2O1tPF8QMMgp+bdUeeJCneJNkOZvqsrtyb4ohnFVFdboSqPwBEacnt0HH4E+5h+qsIwTHUSAhffU4hzKl1A==", "integrity": "sha512-/SF4EDWowPqV1jOgoGSGTIFsE7Ezdr7ZYxyihl5eMKVO5tlnpIhFcDavgm1hHY5GEonoOAEnJ0CU0x+tvuAuUg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -1797,6 +1820,13 @@
"url": "https://opencollective.com/parcel" "url": "https://opencollective.com/parcel"
} }
}, },
"node_modules/@rolldown/pluginutils": {
"version": "1.0.0-beta.19",
"resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.19.tgz",
"integrity": "sha512-3FL3mnMbPu0muGOCaKAhhFEYmqv9eTfPSJRJmANrCwtgK8VuxpsZDGK+m0LYAGoyO8+0j5uRe4PeyPDK1yA/hA==",
"dev": true,
"license": "MIT"
},
"node_modules/@rollup/rollup-android-arm-eabi": { "node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.40.0", "version": "4.40.0",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.0.tgz", "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.0.tgz",
@@ -2092,9 +2122,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "24.0.3", "version": "24.0.10",
"resolved": "https://registry.npmmirror.com/@types/node/-/node-24.0.3.tgz", "resolved": "https://registry.npmmirror.com/@types/node/-/node-24.0.10.tgz",
"integrity": "sha512-R4I/kzCYAdRLzfiCabn9hxWfbuHS573x+r0dJMkkzThEa7pbrcDWK+9zu3e7aBOouf+rQAciqPFMnxwr0aWgKg==", "integrity": "sha512-ENHwaH+JIRTDIEEbDK6QSQntAYGtbvdDXnMXnZaZ6k13Du1dPMmprkEHIL7ok2Wl2aZevetwTAb5S+7yIF+enA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -2109,17 +2139,17 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.34.1", "version": "8.35.1",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.1.tgz", "resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.35.1.tgz",
"integrity": "sha512-STXcN6ebF6li4PxwNeFnqF8/2BNDvBupf2OPx2yWNzr6mKNGF7q49VM00Pz5FaomJyqvbXpY6PhO+T9w139YEQ==", "integrity": "sha512-9XNTlo7P7RJxbVeICaIIIEipqxLKguyh+3UbXuT2XQuFp6d8VOeDEGuz5IiX0dgZo8CiI6aOFLg4e8cF71SFVg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/regexpp": "^4.10.0", "@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "8.34.1", "@typescript-eslint/scope-manager": "8.35.1",
"@typescript-eslint/type-utils": "8.34.1", "@typescript-eslint/type-utils": "8.35.1",
"@typescript-eslint/utils": "8.34.1", "@typescript-eslint/utils": "8.35.1",
"@typescript-eslint/visitor-keys": "8.34.1", "@typescript-eslint/visitor-keys": "8.35.1",
"graphemer": "^1.4.0", "graphemer": "^1.4.0",
"ignore": "^7.0.0", "ignore": "^7.0.0",
"natural-compare": "^1.4.0", "natural-compare": "^1.4.0",
@@ -2133,7 +2163,7 @@
"url": "https://opencollective.com/typescript-eslint" "url": "https://opencollective.com/typescript-eslint"
}, },
"peerDependencies": { "peerDependencies": {
"@typescript-eslint/parser": "^8.34.1", "@typescript-eslint/parser": "^8.35.1",
"eslint": "^8.57.0 || ^9.0.0", "eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <5.9.0" "typescript": ">=4.8.4 <5.9.0"
} }
@@ -2149,16 +2179,16 @@
} }
}, },
"node_modules/@typescript-eslint/parser": { "node_modules/@typescript-eslint/parser": {
"version": "8.34.1", "version": "8.35.1",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-8.34.1.tgz", "resolved": "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-8.35.1.tgz",
"integrity": "sha512-4O3idHxhyzjClSMJ0a29AcoK0+YwnEqzI6oz3vlRf3xw0zbzt15MzXwItOlnr5nIth6zlY2RENLsOPvhyrKAQA==", "integrity": "sha512-3MyiDfrfLeK06bi/g9DqJxP5pV74LNv4rFTyvGDmT3x2p1yp1lOd+qYZfiRPIOf/oON+WRZR5wxxuF85qOar+w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "8.34.1", "@typescript-eslint/scope-manager": "8.35.1",
"@typescript-eslint/types": "8.34.1", "@typescript-eslint/types": "8.35.1",
"@typescript-eslint/typescript-estree": "8.34.1", "@typescript-eslint/typescript-estree": "8.35.1",
"@typescript-eslint/visitor-keys": "8.34.1", "@typescript-eslint/visitor-keys": "8.35.1",
"debug": "^4.3.4" "debug": "^4.3.4"
}, },
"engines": { "engines": {
@@ -2174,14 +2204,14 @@
} }
}, },
"node_modules/@typescript-eslint/project-service": { "node_modules/@typescript-eslint/project-service": {
"version": "8.34.1", "version": "8.35.1",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/project-service/-/project-service-8.34.1.tgz", "resolved": "https://registry.npmmirror.com/@typescript-eslint/project-service/-/project-service-8.35.1.tgz",
"integrity": "sha512-nuHlOmFZfuRwLJKDGQOVc0xnQrAmuq1Mj/ISou5044y1ajGNp2BNliIqp7F2LPQ5sForz8lempMFCovfeS1XoA==", "integrity": "sha512-VYxn/5LOpVxADAuP3NrnxxHYfzVtQzLKeldIhDhzC8UHaiQvYlXvKuVho1qLduFbJjjy5U5bkGwa3rUGUb1Q6Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/tsconfig-utils": "^8.34.1", "@typescript-eslint/tsconfig-utils": "^8.35.1",
"@typescript-eslint/types": "^8.34.1", "@typescript-eslint/types": "^8.35.1",
"debug": "^4.3.4" "debug": "^4.3.4"
}, },
"engines": { "engines": {
@@ -2196,14 +2226,14 @@
} }
}, },
"node_modules/@typescript-eslint/scope-manager": { "node_modules/@typescript-eslint/scope-manager": {
"version": "8.34.1", "version": "8.35.1",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-8.34.1.tgz", "resolved": "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-8.35.1.tgz",
"integrity": "sha512-beu6o6QY4hJAgL1E8RaXNC071G4Kso2MGmJskCFQhRhg8VOH/FDbC8soP8NHN7e/Hdphwp8G8cE6OBzC8o41ZA==", "integrity": "sha512-s/Bpd4i7ht2934nG+UoSPlYXd08KYz3bmjLEb7Ye1UVob0d1ENiT3lY8bsCmik4RqfSbPw9xJJHbugpPpP5JUg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.34.1", "@typescript-eslint/types": "8.35.1",
"@typescript-eslint/visitor-keys": "8.34.1" "@typescript-eslint/visitor-keys": "8.35.1"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2214,9 +2244,9 @@
} }
}, },
"node_modules/@typescript-eslint/tsconfig-utils": { "node_modules/@typescript-eslint/tsconfig-utils": {
"version": "8.34.1", "version": "8.35.1",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.34.1.tgz", "resolved": "https://registry.npmmirror.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.35.1.tgz",
"integrity": "sha512-K4Sjdo4/xF9NEeA2khOb7Y5nY6NSXBnod87uniVYW9kHP+hNlDV8trUSFeynA2uxWam4gIWgWoygPrv9VMWrYg==", "integrity": "sha512-K5/U9VmT9dTHoNowWZpz+/TObS3xqC5h0xAIjXPw+MNcKV9qg6eSatEnmeAwkjHijhACH0/N7bkhKvbt1+DXWQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -2231,14 +2261,14 @@
} }
}, },
"node_modules/@typescript-eslint/type-utils": { "node_modules/@typescript-eslint/type-utils": {
"version": "8.34.1", "version": "8.35.1",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-8.34.1.tgz", "resolved": "https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-8.35.1.tgz",
"integrity": "sha512-Tv7tCCr6e5m8hP4+xFugcrwTOucB8lshffJ6zf1mF1TbU67R+ntCc6DzLNKM+s/uzDyv8gLq7tufaAhIBYeV8g==", "integrity": "sha512-HOrUBlfVRz5W2LIKpXzZoy6VTZzMu2n8q9C2V/cFngIC5U1nStJgv0tMV4sZPzdf4wQm9/ToWUFPMN9Vq9VJQQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/typescript-estree": "8.34.1", "@typescript-eslint/typescript-estree": "8.35.1",
"@typescript-eslint/utils": "8.34.1", "@typescript-eslint/utils": "8.35.1",
"debug": "^4.3.4", "debug": "^4.3.4",
"ts-api-utils": "^2.1.0" "ts-api-utils": "^2.1.0"
}, },
@@ -2255,9 +2285,9 @@
} }
}, },
"node_modules/@typescript-eslint/types": { "node_modules/@typescript-eslint/types": {
"version": "8.34.1", "version": "8.35.1",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/types/-/types-8.34.1.tgz", "resolved": "https://registry.npmmirror.com/@typescript-eslint/types/-/types-8.35.1.tgz",
"integrity": "sha512-rjLVbmE7HR18kDsjNIZQHxmv9RZwlgzavryL5Lnj2ujIRTeXlKtILHgRNmQ3j4daw7zd+mQgy+uyt6Zo6I0IGA==", "integrity": "sha512-q/O04vVnKHfrrhNAscndAn1tuQhIkwqnaW+eu5waD5IPts2eX1dgJxgqcPx5BX109/qAz7IG6VrEPTOYKCNfRQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -2269,16 +2299,16 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree": { "node_modules/@typescript-eslint/typescript-estree": {
"version": "8.34.1", "version": "8.35.1",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.34.1.tgz", "resolved": "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.35.1.tgz",
"integrity": "sha512-rjCNqqYPuMUF5ODD+hWBNmOitjBWghkGKJg6hiCHzUvXRy6rK22Jd3rwbP2Xi+R7oYVvIKhokHVhH41BxPV5mA==", "integrity": "sha512-Vvpuvj4tBxIka7cPs6Y1uvM7gJgdF5Uu9F+mBJBPY4MhvjrjWGK4H0lVgLJd/8PWZ23FTqsaJaLEkBCFUk8Y9g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/project-service": "8.34.1", "@typescript-eslint/project-service": "8.35.1",
"@typescript-eslint/tsconfig-utils": "8.34.1", "@typescript-eslint/tsconfig-utils": "8.35.1",
"@typescript-eslint/types": "8.34.1", "@typescript-eslint/types": "8.35.1",
"@typescript-eslint/visitor-keys": "8.34.1", "@typescript-eslint/visitor-keys": "8.35.1",
"debug": "^4.3.4", "debug": "^4.3.4",
"fast-glob": "^3.3.2", "fast-glob": "^3.3.2",
"is-glob": "^4.0.3", "is-glob": "^4.0.3",
@@ -2324,16 +2354,16 @@
} }
}, },
"node_modules/@typescript-eslint/utils": { "node_modules/@typescript-eslint/utils": {
"version": "8.34.1", "version": "8.35.1",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-8.34.1.tgz", "resolved": "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-8.35.1.tgz",
"integrity": "sha512-mqOwUdZ3KjtGk7xJJnLbHxTuWVn3GO2WZZuM+Slhkun4+qthLdXx32C8xIXbO1kfCECb3jIs3eoxK3eryk7aoQ==", "integrity": "sha512-lhnwatFmOFcazAsUm3ZnZFpXSxiwoa1Lj50HphnDe1Et01NF4+hrdXONSUHIcbVu2eFb1bAf+5yjXkGVkXBKAQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.7.0", "@eslint-community/eslint-utils": "^4.7.0",
"@typescript-eslint/scope-manager": "8.34.1", "@typescript-eslint/scope-manager": "8.35.1",
"@typescript-eslint/types": "8.34.1", "@typescript-eslint/types": "8.35.1",
"@typescript-eslint/typescript-estree": "8.34.1" "@typescript-eslint/typescript-estree": "8.35.1"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2348,13 +2378,13 @@
} }
}, },
"node_modules/@typescript-eslint/visitor-keys": { "node_modules/@typescript-eslint/visitor-keys": {
"version": "8.34.1", "version": "8.35.1",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.34.1.tgz", "resolved": "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.35.1.tgz",
"integrity": "sha512-xoh5rJ+tgsRKoXnkBPFRLZ7rjKM0AfVbC68UZ/ECXoDbfggb9RbEySN359acY1vS3qZ0jVTVWzbtfapwm5ztxw==", "integrity": "sha512-VRwixir4zBWCSTP/ljEo091lbpypz57PoeAQ9imjG+vbeof9LplljsL1mos4ccG6H9IjfrVGM359RozUnuFhpw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.34.1", "@typescript-eslint/types": "8.35.1",
"eslint-visitor-keys": "^4.2.1" "eslint-visitor-keys": "^4.2.1"
}, },
"engines": { "engines": {
@@ -2366,44 +2396,47 @@
} }
}, },
"node_modules/@vitejs/plugin-vue": { "node_modules/@vitejs/plugin-vue": {
"version": "5.2.4", "version": "6.0.0",
"resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-6.0.0.tgz",
"integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", "integrity": "sha512-iAliE72WsdhjzTOp2DtvKThq1VBC4REhwRcaA+zPAAph6I+OQhUXv+Xu2KS7ElxYtb7Zc/3R30Hwv1DxEo7NXQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": {
"@rolldown/pluginutils": "1.0.0-beta.19"
},
"engines": { "engines": {
"node": "^18.0.0 || >=20.0.0" "node": "^20.19.0 || >=22.12.0"
}, },
"peerDependencies": { "peerDependencies": {
"vite": "^5.0.0 || ^6.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0",
"vue": "^3.2.25" "vue": "^3.2.25"
} }
}, },
"node_modules/@volar/language-core": { "node_modules/@volar/language-core": {
"version": "2.4.12", "version": "2.4.17",
"resolved": "https://registry.npmmirror.com/@volar/language-core/-/language-core-2.4.12.tgz", "resolved": "https://registry.npmmirror.com/@volar/language-core/-/language-core-2.4.17.tgz",
"integrity": "sha512-RLrFdXEaQBWfSnYGVxvR2WrO6Bub0unkdHYIdC31HzIEqATIuuhRRzYu76iGPZ6OtA4Au1SnW0ZwIqPP217YhA==", "integrity": "sha512-chmRZMbKmcGpKMoO7Reb70uiLrzo0KWC2CkFttKUuKvrE+VYgi+fL9vWMJ07Fv5ulX0V1TAyyacN9q3nc5/ecA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@volar/source-map": "2.4.12" "@volar/source-map": "2.4.17"
} }
}, },
"node_modules/@volar/source-map": { "node_modules/@volar/source-map": {
"version": "2.4.12", "version": "2.4.17",
"resolved": "https://registry.npmmirror.com/@volar/source-map/-/source-map-2.4.12.tgz", "resolved": "https://registry.npmmirror.com/@volar/source-map/-/source-map-2.4.17.tgz",
"integrity": "sha512-bUFIKvn2U0AWojOaqf63ER0N/iHIBYZPpNGogfLPQ68F5Eet6FnLlyho7BS0y2HJ1jFhSif7AcuTx1TqsCzRzw==", "integrity": "sha512-QDybtQyO3Ms/NjFqNHTC5tbDN2oK5VH7ZaKrcubtfHBDj63n2pizHC3wlMQ+iT55kQXZUUAbmBX5L1C8CHFeBw==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@volar/typescript": { "node_modules/@volar/typescript": {
"version": "2.4.12", "version": "2.4.17",
"resolved": "https://registry.npmmirror.com/@volar/typescript/-/typescript-2.4.12.tgz", "resolved": "https://registry.npmmirror.com/@volar/typescript/-/typescript-2.4.17.tgz",
"integrity": "sha512-HJB73OTJDgPc80K30wxi3if4fSsZZAOScbj2fcicMuOPoOkcf9NNAINb33o+DzhBdF9xTKC1gnPmIRDous5S0g==", "integrity": "sha512-3paEFNh4P5DkgNUB2YkTRrfUekN4brAXxd3Ow1syMqdIPtCZHbUy4AW99S5RO/7mzyTWPMdDSo3mqTpB/LPObQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@volar/language-core": "2.4.12", "@volar/language-core": "2.4.17",
"path-browserify": "^1.0.1", "path-browserify": "^1.0.1",
"vscode-uri": "^3.0.8" "vscode-uri": "^3.0.8"
} }
@@ -2503,18 +2536,18 @@
} }
}, },
"node_modules/@vue/language-core": { "node_modules/@vue/language-core": {
"version": "2.2.10", "version": "3.0.1",
"resolved": "https://registry.npmmirror.com/@vue/language-core/-/language-core-2.2.10.tgz", "resolved": "https://registry.npmmirror.com/@vue/language-core/-/language-core-3.0.1.tgz",
"integrity": "sha512-+yNoYx6XIKuAO8Mqh1vGytu8jkFEOH5C8iOv3i8Z/65A7x9iAOXA97Q+PqZ3nlm2lxf5rOJuIGI/wDtx/riNYw==", "integrity": "sha512-sq+/Mc1IqIexWEQ+Q2XPiDb5SxSvY5JPqHnMOl/PlF5BekslzduX8dglSkpC17VeiAQB6dpS+4aiwNLJRduCNw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@volar/language-core": "~2.4.11", "@volar/language-core": "2.4.17",
"@vue/compiler-dom": "^3.5.0", "@vue/compiler-dom": "^3.5.0",
"@vue/compiler-vue2": "^2.7.16", "@vue/compiler-vue2": "^2.7.16",
"@vue/shared": "^3.5.0", "@vue/shared": "^3.5.0",
"alien-signals": "^1.0.3", "alien-signals": "^2.0.5",
"minimatch": "^9.0.3", "minimatch": "^10.0.1",
"muggle-string": "^0.4.1", "muggle-string": "^0.4.1",
"path-browserify": "^1.0.1" "path-browserify": "^1.0.1"
}, },
@@ -2527,27 +2560,17 @@
} }
} }
}, },
"node_modules/@vue/language-core/node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/@vue/language-core/node_modules/minimatch": { "node_modules/@vue/language-core/node_modules/minimatch": {
"version": "9.0.5", "version": "10.0.3",
"resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.5.tgz", "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-10.0.3.tgz",
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"brace-expansion": "^2.0.1" "@isaacs/brace-expansion": "^5.0.0"
}, },
"engines": { "engines": {
"node": ">=16 || 14 >=14.17" "node": "20 || >=22"
}, },
"funding": { "funding": {
"url": "https://github.com/sponsors/isaacs" "url": "https://github.com/sponsors/isaacs"
@@ -2651,9 +2674,9 @@
} }
}, },
"node_modules/alien-signals": { "node_modules/alien-signals": {
"version": "1.0.13", "version": "2.0.5",
"resolved": "https://registry.npmmirror.com/alien-signals/-/alien-signals-1.0.13.tgz", "resolved": "https://registry.npmmirror.com/alien-signals/-/alien-signals-2.0.5.tgz",
"integrity": "sha512-OGj9yyTnJEttvzhTUWuscOvtqxq5vrhF7vL9oS0xJ2mK0ItPYP1/y+vCFebfxoEyAz0++1AIwJ5CMr+Fk3nDmg==", "integrity": "sha512-PdJB6+06nUNAClInE3Dweq7/2xVAYM64vvvS1IHVHSJmgeOtEdrAGyp7Z2oJtYm0B342/Exd2NT0uMJaThcjLQ==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
@@ -2847,9 +2870,9 @@
} }
}, },
"node_modules/codemirror": { "node_modules/codemirror": {
"version": "6.0.1", "version": "6.0.2",
"resolved": "https://registry.npmmirror.com/codemirror/-/codemirror-6.0.1.tgz", "resolved": "https://registry.npmmirror.com/codemirror/-/codemirror-6.0.2.tgz",
"integrity": "sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==", "integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@codemirror/autocomplete": "^6.0.0", "@codemirror/autocomplete": "^6.0.0",
@@ -3146,19 +3169,19 @@
} }
}, },
"node_modules/eslint": { "node_modules/eslint": {
"version": "9.29.0", "version": "9.30.1",
"resolved": "https://registry.npmmirror.com/eslint/-/eslint-9.29.0.tgz", "resolved": "https://registry.npmmirror.com/eslint/-/eslint-9.30.1.tgz",
"integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==", "integrity": "sha512-zmxXPNMOXmwm9E0yQLi5uqXHs7uq2UIiqEKo3Gq+3fwo1XrJ+hijAZImyF7hclW3E6oHz43Yk3RP8at6OTKflQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1", "@eslint-community/regexpp": "^4.12.1",
"@eslint/config-array": "^0.20.1", "@eslint/config-array": "^0.21.0",
"@eslint/config-helpers": "^0.2.1", "@eslint/config-helpers": "^0.3.0",
"@eslint/core": "^0.14.0", "@eslint/core": "^0.14.0",
"@eslint/eslintrc": "^3.3.1", "@eslint/eslintrc": "^3.3.1",
"@eslint/js": "9.29.0", "@eslint/js": "9.30.1",
"@eslint/plugin-kit": "^0.3.1", "@eslint/plugin-kit": "^0.3.1",
"@humanfs/node": "^0.16.6", "@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/module-importer": "^1.0.1",
@@ -3207,9 +3230,9 @@
} }
}, },
"node_modules/eslint-plugin-vue": { "node_modules/eslint-plugin-vue": {
"version": "10.2.0", "version": "10.3.0",
"resolved": "https://registry.npmmirror.com/eslint-plugin-vue/-/eslint-plugin-vue-10.2.0.tgz", "resolved": "https://registry.npmmirror.com/eslint-plugin-vue/-/eslint-plugin-vue-10.3.0.tgz",
"integrity": "sha512-tl9s+KN3z0hN2b8fV2xSs5ytGl7Esk1oSCxULLwFcdaElhZ8btYYZFrWxvh4En+czrSDtuLCeCOGa8HhEZuBdQ==", "integrity": "sha512-A0u9snqjCfYaPnqqOaH6MBLVWDUIN4trXn8J3x67uDcXvR7X6Ut8p16N+nYhMCQ9Y7edg2BIRGzfyZsY0IdqoQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -3224,8 +3247,14 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}, },
"peerDependencies": { "peerDependencies": {
"@typescript-eslint/parser": "^7.0.0 || ^8.0.0",
"eslint": "^8.57.0 || ^9.0.0", "eslint": "^8.57.0 || ^9.0.0",
"vue-eslint-parser": "^10.0.0" "vue-eslint-parser": "^10.0.0"
},
"peerDependenciesMeta": {
"@typescript-eslint/parser": {
"optional": true
}
} }
}, },
"node_modules/eslint-scope": { "node_modules/eslint-scope": {
@@ -3508,9 +3537,9 @@
} }
}, },
"node_modules/globals": { "node_modules/globals": {
"version": "16.2.0", "version": "16.3.0",
"resolved": "https://registry.npmmirror.com/globals/-/globals-16.2.0.tgz", "resolved": "https://registry.npmmirror.com/globals/-/globals-16.3.0.tgz",
"integrity": "sha512-O+7l9tPdHCU320IigZZPj5zmRCFG9xHmx9cU8FqU2Rp+JN714seHV+2S9+JslCpY4gJwU2vOGox0wzgae/MCEg==", "integrity": "sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -3829,13 +3858,6 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true,
"license": "MIT"
},
"node_modules/lodash.merge": { "node_modules/lodash.merge": {
"version": "4.6.2", "version": "4.6.2",
"resolved": "https://registry.npmmirror.com/lodash.merge/-/lodash.merge-4.6.2.tgz", "resolved": "https://registry.npmmirror.com/lodash.merge/-/lodash.merge-4.6.2.tgz",
@@ -4268,9 +4290,9 @@
} }
}, },
"node_modules/prettier": { "node_modules/prettier": {
"version": "3.5.3", "version": "3.6.2",
"resolved": "https://registry.npmmirror.com/prettier/-/prettier-3.5.3.tgz", "resolved": "https://registry.npmmirror.com/prettier/-/prettier-3.6.2.tgz",
"integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
"license": "MIT", "license": "MIT",
"bin": { "bin": {
"prettier": "bin/prettier.cjs" "prettier": "bin/prettier.cjs"
@@ -4738,15 +4760,15 @@
} }
}, },
"node_modules/typescript-eslint": { "node_modules/typescript-eslint": {
"version": "8.34.1", "version": "8.35.1",
"resolved": "https://registry.npmmirror.com/typescript-eslint/-/typescript-eslint-8.34.1.tgz", "resolved": "https://registry.npmmirror.com/typescript-eslint/-/typescript-eslint-8.35.1.tgz",
"integrity": "sha512-XjS+b6Vg9oT1BaIUfkW3M3LvqZE++rbzAMEHuccCfO/YkP43ha6w3jTEMilQxMF92nVOYCcdjv1ZUhAa1D/0ow==", "integrity": "sha512-xslJjFzhOmHYQzSB/QTeASAHbjmxOGEP6Coh93TXmUBFQoJ1VU35UHIDmG06Jd6taf3wqqC1ntBnCMeymy5Ovw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/eslint-plugin": "8.34.1", "@typescript-eslint/eslint-plugin": "8.35.1",
"@typescript-eslint/parser": "8.34.1", "@typescript-eslint/parser": "8.35.1",
"@typescript-eslint/utils": "8.34.1" "@typescript-eslint/utils": "8.35.1"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -4911,9 +4933,9 @@
} }
}, },
"node_modules/unplugin-vue-components": { "node_modules/unplugin-vue-components": {
"version": "28.7.0", "version": "28.8.0",
"resolved": "https://registry.npmmirror.com/unplugin-vue-components/-/unplugin-vue-components-28.7.0.tgz", "resolved": "https://registry.npmmirror.com/unplugin-vue-components/-/unplugin-vue-components-28.8.0.tgz",
"integrity": "sha512-3SuWAHlTjOiZckqRBGXRdN/k6IMmKyt2Ch5/+DKwYaT321H0ItdZDvW4r8/YkEKQpN9TN3F/SZ0W342gQROC3Q==", "integrity": "sha512-2Q6ZongpoQzuXDK0ZsVzMoshH0MWZQ1pzVL538G7oIDKRTVzHjppBDS8aB99SADGHN3lpGU7frraCG6yWNoL5Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -4923,7 +4945,7 @@
"magic-string": "^0.30.17", "magic-string": "^0.30.17",
"mlly": "^1.7.4", "mlly": "^1.7.4",
"tinyglobby": "^0.2.14", "tinyglobby": "^0.2.14",
"unplugin": "^2.3.4", "unplugin": "^2.3.5",
"unplugin-utils": "^0.2.4" "unplugin-utils": "^0.2.4"
}, },
"engines": { "engines": {
@@ -4934,7 +4956,7 @@
}, },
"peerDependencies": { "peerDependencies": {
"@babel/parser": "^7.15.8", "@babel/parser": "^7.15.8",
"@nuxt/kit": "^3.2.2", "@nuxt/kit": "^3.2.2 || ^4.0.0",
"vue": "2 || 3" "vue": "2 || 3"
}, },
"peerDependenciesMeta": { "peerDependenciesMeta": {
@@ -5046,24 +5068,24 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/vite": { "node_modules/vite": {
"version": "6.3.5", "version": "7.0.1",
"resolved": "https://registry.npmmirror.com/vite/-/vite-6.3.5.tgz", "resolved": "https://registry.npmmirror.com/vite/-/vite-7.0.1.tgz",
"integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", "integrity": "sha512-BiKOQoW5HGR30E6JDeNsati6HnSPMVEKbkIWbCiol+xKeu3g5owrjy7kbk/QEMuzCV87dSUTvycYKmlcfGKq3Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"esbuild": "^0.25.0", "esbuild": "^0.25.0",
"fdir": "^6.4.4", "fdir": "^6.4.6",
"picomatch": "^4.0.2", "picomatch": "^4.0.2",
"postcss": "^8.5.3", "postcss": "^8.5.6",
"rollup": "^4.34.9", "rollup": "^4.40.0",
"tinyglobby": "^0.2.13" "tinyglobby": "^0.2.14"
}, },
"bin": { "bin": {
"vite": "bin/vite.js" "vite": "bin/vite.js"
}, },
"engines": { "engines": {
"node": "^18.0.0 || ^20.0.0 || >=22.0.0" "node": "^20.19.0 || >=22.12.0"
}, },
"funding": { "funding": {
"url": "https://github.com/vitejs/vite?sponsor=1" "url": "https://github.com/vitejs/vite?sponsor=1"
@@ -5072,14 +5094,14 @@
"fsevents": "~2.3.3" "fsevents": "~2.3.3"
}, },
"peerDependencies": { "peerDependencies": {
"@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "@types/node": "^20.19.0 || >=22.12.0",
"jiti": ">=1.21.0", "jiti": ">=1.21.0",
"less": "*", "less": "^4.0.0",
"lightningcss": "^1.21.0", "lightningcss": "^1.21.0",
"sass": "*", "sass": "^1.70.0",
"sass-embedded": "*", "sass-embedded": "^1.70.0",
"stylus": "*", "stylus": ">=0.54.8",
"sugarss": "*", "sugarss": "^5.0.0",
"terser": "^5.16.0", "terser": "^5.16.0",
"tsx": "^4.8.1", "tsx": "^4.8.1",
"yaml": "^2.4.2" "yaml": "^2.4.2"
@@ -5121,9 +5143,9 @@
} }
}, },
"node_modules/vite/node_modules/fdir": { "node_modules/vite/node_modules/fdir": {
"version": "6.4.4", "version": "6.4.6",
"resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.4.4.tgz", "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.4.6.tgz",
"integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peerDependencies": { "peerDependencies": {
@@ -5177,9 +5199,9 @@
} }
}, },
"node_modules/vue-eslint-parser": { "node_modules/vue-eslint-parser": {
"version": "10.1.3", "version": "10.2.0",
"resolved": "https://registry.npmmirror.com/vue-eslint-parser/-/vue-eslint-parser-10.1.3.tgz", "resolved": "https://registry.npmmirror.com/vue-eslint-parser/-/vue-eslint-parser-10.2.0.tgz",
"integrity": "sha512-dbCBnd2e02dYWsXoqX5yKUZlOt+ExIpq7hmHKPb5ZqKcjf++Eo0hMseFTZMLKThrUk61m+Uv6A2YSBve6ZvuDQ==", "integrity": "sha512-CydUvFOQKD928UzZhTp4pr2vWz1L+H99t7Pkln2QSPdvmURT0MoC4wUccfCnuEaihNsu9aYYyk+bep8rlfkUXw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -5188,7 +5210,6 @@
"eslint-visitor-keys": "^4.2.0", "eslint-visitor-keys": "^4.2.0",
"espree": "^10.3.0", "espree": "^10.3.0",
"esquery": "^1.6.0", "esquery": "^1.6.0",
"lodash": "^4.17.21",
"semver": "^7.6.3" "semver": "^7.6.3"
}, },
"engines": { "engines": {
@@ -5202,13 +5223,13 @@
} }
}, },
"node_modules/vue-i18n": { "node_modules/vue-i18n": {
"version": "11.1.6", "version": "11.1.9",
"resolved": "https://registry.npmmirror.com/vue-i18n/-/vue-i18n-11.1.6.tgz", "resolved": "https://registry.npmmirror.com/vue-i18n/-/vue-i18n-11.1.9.tgz",
"integrity": "sha512-+IbsW/sTZHj7U1w0rPOYJbuSB0/7DeO1nvUo3BxvO20OQgHs+ukJ3QeLqvoUA6DiLk+8SA9+djRmKC9+FC6cAg==", "integrity": "sha512-N9ZTsXdRmX38AwS9F6Rh93RtPkvZTkSy/zNv63FTIwZCUbLwwrpqlKz9YQuzFLdlvRdZTnWAUE5jMxr8exdl7g==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@intlify/core-base": "11.1.6", "@intlify/core-base": "11.1.9",
"@intlify/shared": "11.1.6", "@intlify/shared": "11.1.9",
"@vue/devtools-api": "^6.5.0" "@vue/devtools-api": "^6.5.0"
}, },
"engines": { "engines": {
@@ -5249,14 +5270,14 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/vue-tsc": { "node_modules/vue-tsc": {
"version": "2.2.10", "version": "3.0.1",
"resolved": "https://registry.npmmirror.com/vue-tsc/-/vue-tsc-2.2.10.tgz", "resolved": "https://registry.npmmirror.com/vue-tsc/-/vue-tsc-3.0.1.tgz",
"integrity": "sha512-jWZ1xSaNbabEV3whpIDMbjVSVawjAyW+x1n3JeGQo7S0uv2n9F/JMgWW90tGWNFRKya4YwKMZgCtr0vRAM7DeQ==", "integrity": "sha512-UvMLQD0hAGL1g/NfEQelnSVB4H5gtf/gz2lJKjMMwWNOUmSNyWkejwJagAxEbSjtV5CPPJYslOtoSuqJ63mhdg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@volar/typescript": "~2.4.11", "@volar/typescript": "2.4.17",
"@vue/language-core": "2.2.10" "@vue/language-core": "3.0.1"
}, },
"bin": { "bin": {
"vue-tsc": "bin/vue-tsc.js" "vue-tsc": "bin/vue-tsc.js"

View File

@@ -15,36 +15,36 @@
"@codemirror/autocomplete": "^6.18.6", "@codemirror/autocomplete": "^6.18.6",
"@codemirror/commands": "^6.8.1", "@codemirror/commands": "^6.8.1",
"@codemirror/lang-angular": "^0.1.4", "@codemirror/lang-angular": "^0.1.4",
"@codemirror/lang-cpp": "^6.0.2", "@codemirror/lang-cpp": "^6.0.3",
"@codemirror/lang-css": "^6.3.1", "@codemirror/lang-css": "^6.3.1",
"@codemirror/lang-go": "^6.0.1", "@codemirror/lang-go": "^6.0.1",
"@codemirror/lang-html": "^6.4.9", "@codemirror/lang-html": "^6.4.9",
"@codemirror/lang-java": "^6.0.1", "@codemirror/lang-java": "^6.0.2",
"@codemirror/lang-javascript": "^6.2.4", "@codemirror/lang-javascript": "^6.2.4",
"@codemirror/lang-json": "^6.0.1", "@codemirror/lang-json": "^6.0.2",
"@codemirror/lang-less": "^6.0.2", "@codemirror/lang-less": "^6.0.2",
"@codemirror/lang-lezer": "^6.0.1", "@codemirror/lang-lezer": "^6.0.2",
"@codemirror/lang-liquid": "^6.2.3", "@codemirror/lang-liquid": "^6.2.3",
"@codemirror/lang-markdown": "^6.3.3", "@codemirror/lang-markdown": "^6.3.3",
"@codemirror/lang-php": "^6.0.1", "@codemirror/lang-php": "^6.0.2",
"@codemirror/lang-python": "^6.2.1", "@codemirror/lang-python": "^6.2.1",
"@codemirror/lang-rust": "^6.0.1", "@codemirror/lang-rust": "^6.0.2",
"@codemirror/lang-sass": "^6.0.2", "@codemirror/lang-sass": "^6.0.2",
"@codemirror/lang-sql": "^6.9.0", "@codemirror/lang-sql": "^6.9.0",
"@codemirror/lang-vue": "^0.1.3", "@codemirror/lang-vue": "^0.1.3",
"@codemirror/lang-wast": "^6.0.2", "@codemirror/lang-wast": "^6.0.2",
"@codemirror/lang-xml": "^6.1.0", "@codemirror/lang-xml": "^6.1.0",
"@codemirror/lang-yaml": "^6.1.2", "@codemirror/lang-yaml": "^6.1.2",
"@codemirror/language": "^6.11.1", "@codemirror/language": "^6.11.2",
"@codemirror/language-data": "^6.5.1", "@codemirror/language-data": "^6.5.1",
"@codemirror/legacy-modes": "^6.5.1", "@codemirror/legacy-modes": "^6.5.1",
"@codemirror/lint": "^6.8.5", "@codemirror/lint": "^6.8.5",
"@codemirror/search": "^6.5.11", "@codemirror/search": "^6.5.11",
"@codemirror/state": "^6.5.2", "@codemirror/state": "^6.5.2",
"@codemirror/view": "^6.37.2", "@codemirror/view": "^6.38.0",
"@lezer/highlight": "^1.2.1", "@lezer/highlight": "^1.2.1",
"@lezer/lr": "^1.4.2", "@lezer/lr": "^1.4.2",
"codemirror": "^6.0.1", "codemirror": "^6.0.2",
"codemirror-lang-elixir": "^4.0.0", "codemirror-lang-elixir": "^4.0.0",
"colors-named": "^1.0.2", "colors-named": "^1.0.2",
"colors-named-hex": "^1.0.2", "colors-named-hex": "^1.0.2",
@@ -52,28 +52,28 @@
"lezer": "^0.13.5", "lezer": "^0.13.5",
"pinia": "^3.0.3", "pinia": "^3.0.3",
"pinia-plugin-persistedstate": "^4.4.1", "pinia-plugin-persistedstate": "^4.4.1",
"prettier": "^3.5.3", "prettier": "^3.6.2",
"remarkable": "^2.0.1", "remarkable": "^2.0.1",
"sass": "^1.89.2", "sass": "^1.89.2",
"vue": "^3.5.17", "vue": "^3.5.17",
"vue-i18n": "^11.1.6", "vue-i18n": "^11.1.9",
"vue-router": "^4.5.1" "vue-router": "^4.5.1"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.29.0", "@eslint/js": "^9.30.1",
"@lezer/generator": "^1.7.3", "@lezer/generator": "^1.8.0",
"@types/node": "^24.0.3", "@types/node": "^24.0.10",
"@types/remarkable": "^2.0.8", "@types/remarkable": "^2.0.8",
"@vitejs/plugin-vue": "^5.2.4", "@vitejs/plugin-vue": "^6.0.0",
"@wailsio/runtime": "latest", "@wailsio/runtime": "latest",
"eslint": "^9.29.0", "eslint": "^9.30.1",
"eslint-plugin-vue": "^10.2.0", "eslint-plugin-vue": "^10.3.0",
"globals": "^16.2.0", "globals": "^16.3.0",
"typescript": "^5.8.3", "typescript": "^5.8.3",
"typescript-eslint": "^8.34.1", "typescript-eslint": "^8.35.1",
"unplugin-vue-components": "^28.7.0", "unplugin-vue-components": "^28.8.0",
"vite": "^6.3.5", "vite": "^7.0.1",
"vue-eslint-parser": "^10.1.3", "vue-eslint-parser": "^10.2.0",
"vue-tsc": "^2.2.10" "vue-tsc": "^3.0.1"
} }
} }

View File

@@ -18,7 +18,7 @@ export default {
blockLanguage: 'Block Language', blockLanguage: 'Block Language',
searchLanguage: 'Search language...', searchLanguage: 'Search language...',
noLanguageFound: 'No language found', noLanguageFound: 'No language found',
formatHint: 'Current block supports formatting, use Ctrl+Shift+F shortcut for formatting', formatHint: 'Click Format Block (Ctrl+Shift+F)',
// Document selector // Document selector
selectDocument: 'Select Document', selectDocument: 'Select Document',
searchOrCreateDocument: 'Search or enter new document name...', searchOrCreateDocument: 'Search or enter new document name...',
@@ -201,12 +201,10 @@ export default {
name: 'Minimap', name: 'Minimap',
description: 'Display minimap overview of the document' description: 'Display minimap overview of the document'
}, },
search: { search: {
name: 'Search', name: 'Search',
description: 'Text search and replace functionality' description: 'Text search and replace functionality'
}, },
fold: { fold: {
name: 'Code Folding', name: 'Code Folding',
description: 'Collapse and expand code sections for better readability' description: 'Collapse and expand code sections for better readability'
@@ -220,6 +218,10 @@ export default {
checkbox: { checkbox: {
name: 'Checkbox', name: 'Checkbox',
description: 'Render [x] and [ ] as interactive checkboxes' description: 'Render [x] and [ ] as interactive checkboxes'
},
codeblock: {
name: 'Code Block',
description: 'Code block related functionality'
} }
}, },
monitor: { monitor: {

View File

@@ -18,7 +18,7 @@ export default {
blockLanguage: '块语言', blockLanguage: '块语言',
searchLanguage: '搜索语言...', searchLanguage: '搜索语言...',
noLanguageFound: '未找到匹配的语言', noLanguageFound: '未找到匹配的语言',
formatHint: '当前区块支持格式化,使用 Ctrl+Shift+F 进行格式化', formatHint: '点击格式化区块(Ctrl+Shift+F',
// 文档选择器 // 文档选择器
selectDocument: '选择文档', selectDocument: '选择文档',
searchOrCreateDocument: '搜索或输入新文档名...', searchOrCreateDocument: '搜索或输入新文档名...',
@@ -202,12 +202,10 @@ export default {
name: '小地图', name: '小地图',
description: '显示小地图视图' description: '显示小地图视图'
}, },
search: { search: {
name: '搜索功能', name: '搜索功能',
description: '文本搜索和替换功能' description: '文本搜索和替换功能'
}, },
fold: { fold: {
name: '代码折叠', name: '代码折叠',
description: '折叠和展开代码段以提高代码可读性' description: '折叠和展开代码段以提高代码可读性'
@@ -221,6 +219,10 @@ export default {
checkbox: { checkbox: {
name: '选择框', name: '选择框',
description: '将 [x] 和 [ ] 渲染为可交互的选择框' description: '将 [x] 和 [ ] 渲染为可交互的选择框'
},
codeblock: {
name: '代码块',
description: '代码块相关功能'
} }
}, },
monitor: { monitor: {

View File

@@ -21,6 +21,8 @@ import {
import {history} from '@codemirror/commands'; import {history} from '@codemirror/commands';
import {highlightSelectionMatches} from '@codemirror/search'; import {highlightSelectionMatches} from '@codemirror/search';
import {autocompletion, closeBrackets, closeBracketsKeymap} from '@codemirror/autocomplete'; import {autocompletion, closeBrackets, closeBracketsKeymap} from '@codemirror/autocomplete';
import createEditorContextMenu from '../contextMenu';
// 基本编辑器设置 // 基本编辑器设置
export const createBasicSetup = (): Extension[] => { export const createBasicSetup = (): Extension[] => {
return [ return [
@@ -53,6 +55,9 @@ export const createBasicSetup = (): Extension[] => {
// 自动完成 // 自动完成
autocompletion(), autocompletion(),
// 上下文菜单
createEditorContextMenu(),
// 键盘映射 // 键盘映射
keymap.of([ keymap.of([
...closeBracketsKeymap, ...closeBracketsKeymap,

View File

@@ -0,0 +1,156 @@
/**
* 编辑器上下文菜单样式
* 支持系统主题自动适配
*/
.cm-context-menu {
position: fixed;
background-color: var(--settings-card-bg);
color: var(--settings-text);
border: 1px solid var(--border-color);
border-radius: 6px;
padding: 4px 0;
/* 优化阴影效果,只在右下角显示自然的阴影 */
box-shadow: 2px 2px 8px rgba(0, 0, 0, 0.12);
min-width: 200px;
max-width: 320px;
z-index: 9999;
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
opacity: 0;
transform: scale(0.95);
transition: opacity 0.15s ease-out, transform 0.15s ease-out;
overflow: visible; /* 确保子菜单可以显示在外部 */
}
.cm-context-menu-item {
padding: 8px 12px;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 14px;
transition: all 0.1s ease;
position: relative;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.cm-context-menu-item:hover {
background-color: var(--toolbar-button-hover);
color: var(--toolbar-text);
}
.cm-context-menu-item-label {
display: flex;
align-items: center;
gap: 8px;
}
.cm-context-menu-item-shortcut {
opacity: 0.7;
font-size: 12px;
padding: 2px 4px;
border-radius: 4px;
background-color: var(--settings-input-bg);
color: var(--settings-text-secondary);
margin-left: 16px;
}
.cm-context-menu-item-ripple {
position: absolute;
border-radius: 50%;
background-color: var(--selection-bg);
width: 100px;
height: 100px;
opacity: 0.5;
transform: scale(0);
transition: transform 0.3s ease-out, opacity 0.3s ease-out;
}
/* 菜单分组标题样式 */
.cm-context-menu-group-title {
padding: 6px 12px;
font-size: 12px;
color: var(--text-secondary);
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
user-select: none;
}
/* 菜单分隔线样式 */
.cm-context-menu-divider {
height: 1px;
background-color: var(--border-color);
margin: 4px 0;
}
/* 子菜单样式 */
.cm-context-submenu-container {
position: relative;
}
.cm-context-menu-item-with-submenu {
position: relative;
}
.cm-context-menu-item-with-submenu::after {
content: "";
position: absolute;
right: 12px;
font-size: 16px;
opacity: 0.7;
}
.cm-context-submenu {
position: fixed; /* 改为fixed定位避免受父元素影响 */
min-width: 180px;
opacity: 0;
pointer-events: none;
transform: translateX(10px);
transition: opacity 0.2s ease, transform 0.2s ease;
z-index: 10000;
border-radius: 6px;
background-color: var(--settings-card-bg);
color: var(--settings-text);
border: 1px solid var(--border-color);
padding: 4px 0;
/* 子菜单也使用相同的阴影效果 */
box-shadow: 2px 2px 8px rgba(0, 0, 0, 0.12);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
}
.cm-context-menu-item-with-submenu:hover .cm-context-submenu {
opacity: 1;
pointer-events: auto;
transform: translateX(0);
}
/* 深色主题下的特殊样式 */
:root[data-theme="dark"] .cm-context-menu {
/* 深色主题下阴影更深,但仍然只在右下角 */
box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.25);
}
:root[data-theme="dark"] .cm-context-submenu {
/* 深色主题下子菜单阴影 */
box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.25);
}
:root[data-theme="dark"] .cm-context-menu-divider {
background-color: var(--dark-border-color);
opacity: 0.6;
}
/* 动画相关类 */
.cm-context-menu.show {
opacity: 1;
transform: scale(1);
}
.cm-context-menu.hide {
opacity: 0;
}

View File

@@ -0,0 +1,426 @@
/**
* 上下文菜单视图实现
* 处理菜单的创建、定位和事件绑定
* 优化为单例模式避免频繁创建和销毁DOM元素
*/
import { EditorView } from "@codemirror/view";
import { MenuItem } from "../contextMenu";
import "./contextMenu.css";
// 为Window对象添加cmSubmenus属性
declare global {
interface Window {
cmSubmenus?: Map<string, HTMLElement>;
}
}
// 菜单DOM元素缓存
let menuElement: HTMLElement | null = null;
let clickOutsideHandler: ((e: MouseEvent) => void) | null = null;
// 子菜单缓存池
let submenuPool: Map<string, HTMLElement> = new Map();
/**
* 获取或创建菜单DOM元素
*/
function getOrCreateMenuElement(): HTMLElement {
if (!menuElement) {
menuElement = document.createElement("div");
menuElement.className = "cm-context-menu";
menuElement.style.display = "none";
document.body.appendChild(menuElement);
// 阻止菜单内右键点击冒泡
menuElement.addEventListener('contextmenu', (e) => {
e.preventDefault();
e.stopPropagation();
return false;
});
}
return menuElement;
}
/**
* 创建或获取子菜单元素
* @param id 子菜单唯一标识
*/
function getOrCreateSubmenu(id: string): HTMLElement {
if (!submenuPool.has(id)) {
const submenu = document.createElement("div");
submenu.className = "cm-context-menu cm-context-submenu";
submenu.style.display = "none";
document.body.appendChild(submenu);
submenuPool.set(id, submenu);
// 阻止子菜单点击事件冒泡
submenu.addEventListener('click', (e) => {
e.stopPropagation();
});
}
return submenuPool.get(id)!;
}
/**
* 创建菜单项DOM元素
*/
function createMenuItemElement(item: MenuItem, view: EditorView): HTMLElement {
// 创建菜单项容器
const menuItem = document.createElement("div");
menuItem.className = "cm-context-menu-item";
// 如果有子菜单,添加相应类
if (item.submenu && item.submenu.length > 0) {
menuItem.classList.add("cm-context-menu-item-with-submenu");
}
// 创建内容容器
const contentContainer = document.createElement("div");
contentContainer.className = "cm-context-menu-item-label";
// 标签文本
const label = document.createElement("span");
label.textContent = item.label;
contentContainer.appendChild(label);
menuItem.appendChild(contentContainer);
// 快捷键提示(如果有)
if (item.shortcut) {
const shortcut = document.createElement("span");
shortcut.className = "cm-context-menu-item-shortcut";
shortcut.textContent = item.shortcut;
menuItem.appendChild(shortcut);
}
// 如果有子菜单,创建或获取子菜单
if (item.submenu && item.submenu.length > 0) {
// 使用菜单项标签作为子菜单ID
const submenuId = `submenu-${item.label.replace(/\s+/g, '-').toLowerCase()}`;
const submenu = getOrCreateSubmenu(submenuId);
// 清空现有子菜单内容
while (submenu.firstChild) {
submenu.removeChild(submenu.firstChild);
}
// 添加子菜单项
item.submenu.forEach(subItem => {
const subMenuItemElement = createMenuItemElement(subItem, view);
submenu.appendChild(subMenuItemElement);
});
// 初始状态设置为隐藏
submenu.style.opacity = '0';
submenu.style.pointerEvents = 'none';
submenu.style.visibility = 'hidden';
submenu.style.display = 'block';
// 当鼠标悬停在菜单项上时,显示子菜单
menuItem.addEventListener('mouseenter', () => {
const rect = menuItem.getBoundingClientRect();
// 计算子菜单位置
submenu.style.left = `${rect.right}px`;
submenu.style.top = `${rect.top}px`;
// 检查子菜单是否会超出屏幕右侧
setTimeout(() => {
const submenuRect = submenu.getBoundingClientRect();
if (submenuRect.right > window.innerWidth) {
// 如果会超出右侧,则显示在左侧
submenu.style.left = `${rect.left - submenuRect.width}px`;
}
// 检查子菜单是否会超出屏幕底部
if (submenuRect.bottom > window.innerHeight) {
// 如果会超出底部,则向上调整
const newTop = rect.top - (submenuRect.bottom - window.innerHeight);
submenu.style.top = `${Math.max(0, newTop)}px`;
}
}, 0);
// 显示子菜单
submenu.style.opacity = '1';
submenu.style.pointerEvents = 'auto';
submenu.style.visibility = 'visible';
submenu.style.transform = 'translateX(0)';
});
// 当鼠标离开菜单项时,隐藏子菜单
menuItem.addEventListener('mouseleave', (e) => {
// 检查是否移动到子菜单上
const toElement = e.relatedTarget as HTMLElement;
if (submenu.contains(toElement)) {
return; // 如果移动到子菜单上,不隐藏
}
// 隐藏子菜单
submenu.style.opacity = '0';
submenu.style.pointerEvents = 'none';
submenu.style.transform = 'translateX(10px)';
// 延迟设置visibility以便过渡动画能够完成
setTimeout(() => {
if (submenu.style.opacity === '0') {
submenu.style.visibility = 'hidden';
}
}, 200);
});
// 当鼠标离开子菜单时,隐藏它
submenu.addEventListener('mouseleave', (e) => {
// 检查是否移动回父菜单项
const toElement = e.relatedTarget as HTMLElement;
if (menuItem.contains(toElement)) {
return; // 如果移动回父菜单项,不隐藏
}
// 隐藏子菜单
submenu.style.opacity = '0';
submenu.style.pointerEvents = 'none';
submenu.style.transform = 'translateX(10px)';
// 延迟设置visibility以便过渡动画能够完成
setTimeout(() => {
if (submenu.style.opacity === '0') {
submenu.style.visibility = 'hidden';
}
}, 200);
});
// 记录子菜单
if (!window.cmSubmenus) {
window.cmSubmenus = new Map();
}
window.cmSubmenus.set(submenuId, submenu);
}
// 点击事件仅当有command时添加
if (item.command) {
menuItem.addEventListener("click", (e) => {
e.preventDefault();
e.stopPropagation();
// 添加点击动画效果
const ripple = document.createElement("div");
ripple.className = "cm-context-menu-item-ripple";
// 计算相对位置
const rect = menuItem.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
ripple.style.left = (x - 50) + "px";
ripple.style.top = (y - 50) + "px";
menuItem.appendChild(ripple);
// 执行点击动画
setTimeout(() => {
ripple.style.transform = "scale(1)";
ripple.style.opacity = "0";
// 动画完成后移除ripple元素
setTimeout(() => {
if (ripple.parentNode === menuItem) {
menuItem.removeChild(ripple);
}
}, 300);
}, 10);
// 执行命令
item.command!(view);
// 隐藏菜单
hideContextMenu();
});
}
return menuItem;
}
/**
* 创建分隔线
*/
function createDivider(): HTMLElement {
const divider = document.createElement("div");
divider.className = "cm-context-menu-divider";
return divider;
}
/**
* 添加菜单组
* @param menuElement 菜单元素
* @param title 菜单组标题
* @param items 菜单项
* @param view 编辑器视图
*/
function addMenuGroup(menuElement: HTMLElement, title: string | null, items: MenuItem[], view: EditorView): void {
// 如果有标题,添加组标题
if (title) {
const groupTitle = document.createElement("div");
groupTitle.className = "cm-context-menu-group-title";
groupTitle.textContent = title;
menuElement.appendChild(groupTitle);
}
// 添加菜单项
items.forEach(item => {
const menuItemElement = createMenuItemElement(item, view);
menuElement.appendChild(menuItemElement);
});
}
/**
* 显示上下文菜单
*/
export function showContextMenu(view: EditorView, clientX: number, clientY: number, items: MenuItem[]): void {
// 获取或创建菜单元素
const menu = getOrCreateMenuElement();
// 如果已经有菜单显示,先隐藏所有子菜单
hideAllSubmenus();
// 清空现有菜单项
while (menu.firstChild) {
menu.removeChild(menu.firstChild);
}
// 添加主菜单项
items.forEach(item => {
const menuItemElement = createMenuItemElement(item, view);
menu.appendChild(menuItemElement);
});
// 显示菜单
menu.style.display = "block";
// 定位菜单
positionMenu(menu, clientX, clientY);
// 添加点击外部关闭事件
if (clickOutsideHandler) {
document.removeEventListener("click", clickOutsideHandler, true);
}
clickOutsideHandler = (e: MouseEvent) => {
// 检查点击是否在菜单外
if (menu && !menu.contains(e.target as Node)) {
let isInSubmenu = false;
// 检查是否点击在子菜单内
if (window.cmSubmenus) {
window.cmSubmenus.forEach((submenu) => {
if (submenu.contains(e.target as Node)) {
isInSubmenu = true;
}
});
}
if (!isInSubmenu) {
hideContextMenu();
}
}
};
// 使用捕获阶段确保事件被处理
document.addEventListener("click", clickOutsideHandler, true);
// ESC键关闭
document.addEventListener("keydown", handleKeyDown);
// 触发显示动画
setTimeout(() => {
if (menu) {
menu.classList.add("show");
}
}, 10);
}
/**
* 隐藏所有子菜单
*/
function hideAllSubmenus(): void {
if (window.cmSubmenus) {
window.cmSubmenus.forEach((submenu) => {
submenu.style.opacity = '0';
submenu.style.pointerEvents = 'none';
submenu.style.visibility = 'hidden';
submenu.style.transform = 'translateX(10px)';
});
}
}
/**
* 处理键盘事件
*/
function handleKeyDown(e: KeyboardEvent): void {
if (e.key === "Escape") {
hideContextMenu();
document.removeEventListener("keydown", handleKeyDown);
}
}
/**
* 隐藏上下文菜单
*/
export function hideContextMenu(): void {
// 隐藏所有子菜单
hideAllSubmenus();
if (menuElement) {
// 添加淡出动画
menuElement.classList.remove("show");
menuElement.classList.add("hide");
// 等待动画完成后隐藏不移除DOM元素
setTimeout(() => {
if (menuElement) {
menuElement.style.display = "none";
menuElement.classList.remove("hide");
}
}, 150);
}
if (clickOutsideHandler) {
document.removeEventListener("click", clickOutsideHandler, true);
clickOutsideHandler = null;
}
document.removeEventListener("keydown", handleKeyDown);
}
/**
* 定位菜单元素
*/
function positionMenu(menu: HTMLElement, clientX: number, clientY: number): void {
// 获取窗口尺寸
const windowWidth = window.innerWidth;
const windowHeight = window.innerHeight;
// 初始位置设置
let left = clientX;
let top = clientY;
// 确保菜单在视窗内
setTimeout(() => {
// 计算菜单尺寸
const menuWidth = menu.offsetWidth;
const menuHeight = menu.offsetHeight;
// 确保菜单不会超出右侧边界
if (left + menuWidth > windowWidth) {
left = windowWidth - menuWidth - 5;
}
// 确保菜单不会超出底部边界
if (top + menuHeight > windowHeight) {
top = windowHeight - menuHeight - 5;
}
// 应用位置
menu.style.left = `${left}px`;
menu.style.top = `${top}px`;
}, 0);
}

View File

@@ -0,0 +1,229 @@
/**
* 编辑器上下文菜单实现
* 提供基本的复制、剪切、粘贴等操作,支持动态快捷键显示
*/
import { EditorView } from "@codemirror/view";
import { Extension } from "@codemirror/state";
import { copyCommand, cutCommand, pasteCommand } from "../extensions/codeblock/copyPaste";
import { KeyBindingCommand } from "@/../bindings/voidraft/internal/models/models";
import { useKeybindingStore } from "@/stores/keybindingStore";
import {
undo, redo
} from "@codemirror/commands";
import {
deleteBlock, formatCurrentBlock,
addNewBlockAfterCurrent, addNewBlockAfterLast, addNewBlockBeforeCurrent
} from "../extensions/codeblock/commands";
import { commandRegistry } from "@/views/editor/keymap";
import i18n from "@/i18n";
import {useSystemStore} from "@/stores/systemStore";
/**
* 菜单项类型定义
*/
export interface MenuItem {
/** 菜单项显示文本 */
label: string;
/** 点击时执行的命令 (如果有子菜单可以为null) */
command?: (view: EditorView) => boolean;
/** 快捷键提示文本 (可选) */
shortcut?: string;
/** 子菜单项 (可选) */
submenu?: MenuItem[];
}
// 导入相关功能
import { showContextMenu } from "./contextMenuView";
/**
* 获取翻译文本
* @param key 翻译键
* @returns 翻译后的文本
*/
function t(key: string): string {
return i18n.global.t(key);
}
/**
* 获取快捷键显示文本
* @param command 命令ID
* @returns 快捷键显示文本
*/
function getShortcutText(command: KeyBindingCommand): string {
try {
const keybindingStore = useKeybindingStore();
// 如果找到该命令的快捷键配置
const binding = keybindingStore.keyBindings.find(kb =>
kb.command === command && kb.enabled
);
if (binding && binding.key) {
// 格式化快捷键显示
return formatKeyBinding(binding.key);
}
} catch (error) {
console.warn("An error occurred while getting the shortcut:", error);
}
return "";
}
/**
* 格式化快捷键显示
* @param keyBinding 快捷键字符串
* @returns 格式化后的显示文本
*/
function formatKeyBinding(keyBinding: string): string {
// 获取系统信息
const systemStore = useSystemStore();
const isMac = systemStore.isMacOS;
// 替换修饰键名称为更友好的显示
return keyBinding
.replace("Mod", isMac ? "⌘" : "Ctrl")
.replace("Shift", isMac ? "⇧" : "Shift")
.replace("Alt", isMac ? "⌥" : "Alt")
.replace("Ctrl", isMac ? "⌃" : "Ctrl")
.replace(/-/g, " + ");
}
/**
* 从命令注册表获取命令处理程序和翻译键
* @param command 命令ID
* @returns 命令处理程序和翻译键
*/
function getCommandInfo(command: KeyBindingCommand): { handler: (view: EditorView) => boolean, descriptionKey: string } | undefined {
return commandRegistry[command];
}
/**
* 创建编辑菜单项
*/
function createEditItems(): MenuItem[] {
return [
{
label: t("keybindings.commands.blockCopy"),
command: copyCommand,
shortcut: getShortcutText(KeyBindingCommand.BlockCopyCommand)
},
{
label: t("keybindings.commands.blockCut"),
command: cutCommand,
shortcut: getShortcutText(KeyBindingCommand.BlockCutCommand)
},
{
label: t("keybindings.commands.blockPaste"),
command: pasteCommand,
shortcut: getShortcutText(KeyBindingCommand.BlockPasteCommand)
}
];
}
/**
* 创建历史操作菜单项
*/
function createHistoryItems(): MenuItem[] {
return [
{
label: t("keybindings.commands.historyUndo"),
command: undo,
shortcut: getShortcutText(KeyBindingCommand.HistoryUndoCommand)
},
{
label: t("keybindings.commands.historyRedo"),
command: redo,
shortcut: getShortcutText(KeyBindingCommand.HistoryRedoCommand)
}
];
}
/**
* 创建代码块相关菜单项
*/
function createCodeBlockItems(): MenuItem[] {
const defaultOptions = { defaultBlockToken: 'text', defaultBlockAutoDetect: true };
return [
// 格式化
{
label: t("keybindings.commands.blockFormat"),
command: formatCurrentBlock,
shortcut: getShortcutText(KeyBindingCommand.BlockFormatCommand)
},
// 删除
{
label: t("keybindings.commands.blockDelete"),
command: deleteBlock(defaultOptions),
shortcut: getShortcutText(KeyBindingCommand.BlockDeleteCommand)
},
// 在当前块后添加新块
{
label: t("keybindings.commands.blockAddAfterCurrent"),
command: addNewBlockAfterCurrent(defaultOptions),
shortcut: getShortcutText(KeyBindingCommand.BlockAddAfterCurrentCommand)
},
// 在当前块前添加新块
{
label: t("keybindings.commands.blockAddBeforeCurrent"),
command: addNewBlockBeforeCurrent(defaultOptions),
shortcut: getShortcutText(KeyBindingCommand.BlockAddBeforeCurrentCommand)
},
// 在最后添加新块
{
label: t("keybindings.commands.blockAddAfterLast"),
command: addNewBlockAfterLast(defaultOptions),
shortcut: getShortcutText(KeyBindingCommand.BlockAddAfterLastCommand)
}
];
}
/**
* 创建主菜单项
*/
function createMainMenuItems(): MenuItem[] {
// 基本编辑操作放在主菜单
const basicItems = createEditItems();
// 历史操作放在主菜单
const historyItems = createHistoryItems();
// 构建主菜单
return [
...basicItems,
...historyItems,
{
label: t("extensions.codeblock.name"),
submenu: createCodeBlockItems()
}
];
}
/**
* 创建编辑器上下文菜单
*/
export function createEditorContextMenu(): Extension {
// 为编辑器添加右键事件处理
return EditorView.domEventHandlers({
contextmenu: (event, view) => {
// 阻止默认右键菜单
event.preventDefault();
// 获取菜单项
const menuItems = createMainMenuItems();
// 显示上下文菜单
showContextMenu(view, event.clientX, event.clientY, menuItems);
return true;
}
});
}
/**
* 默认导出
*/
export default createEditorContextMenu;

View File

@@ -30,11 +30,15 @@ class HyperLinkIcon extends WidgetType {
toDOM() { toDOM() {
const wrapper = document.createElement('a'); const wrapper = document.createElement('a');
wrapper.href = this.state.url; wrapper.href = this.state.url;
wrapper.target = '_blank';
wrapper.innerHTML = pathStr; wrapper.innerHTML = pathStr;
wrapper.className = 'cm-hyper-link-icon'; wrapper.className = 'cm-hyper-link-icon cm-hyper-link-underline';
wrapper.rel = 'nofollow';
wrapper.title = this.state.url; wrapper.title = this.state.url;
wrapper.setAttribute('data-url', this.state.url);
wrapper.onclick = (e) => {
e.preventDefault();
runtime.Browser.OpenURL(this.state.url);
return false;
};
const anchor = this.state.anchor && this.state.anchor(wrapper); const anchor = this.state.anchor && this.state.anchor(wrapper);
return anchor || wrapper; return anchor || wrapper;
} }
@@ -141,10 +145,12 @@ export const hyperLinkStyle = EditorView.baseTheme({
color: '#0969da', color: '#0969da',
cursor: 'pointer', cursor: 'pointer',
transition: 'color 0.2s ease', transition: 'color 0.2s ease',
textDecoration: 'none', textDecoration: 'underline',
textDecorationColor: '#0969da',
textDecorationThickness: '1px',
textUnderlineOffset: '2px',
'&:hover': { '&:hover': {
color: '#0550ae', color: '#0550ae',
textDecoration: 'underline',
} }
}, },
@@ -160,9 +166,9 @@ export const hyperLinkStyle = EditorView.baseTheme({
verticalAlign: 'middle', verticalAlign: 'middle',
marginLeft: '0.2ch', marginLeft: '0.2ch',
color: '#656d76', color: '#656d76',
textDecoration: 'none',
opacity: 0.7, opacity: 0.7,
transition: 'opacity 0.2s ease, color 0.2s ease', transition: 'opacity 0.2s ease, color 0.2s ease',
cursor: 'pointer',
'&:hover': { '&:hover': {
opacity: 1, opacity: 1,
color: '#0969da', color: '#0969da',
@@ -197,12 +203,17 @@ export const hyperLinkStyle = EditorView.baseTheme({
export const hyperLinkClickHandler = EditorView.domEventHandlers({ export const hyperLinkClickHandler = EditorView.domEventHandlers({
click: (event, view) => { click: (event, view) => {
const target = event.target as HTMLElement; const target = event.target as HTMLElement;
let urlElement = target;
if (target.classList.contains('cm-hyper-link-text')) { while (urlElement && !urlElement.hasAttribute('data-url')) {
const url = target.getAttribute('data-url'); urlElement = urlElement.parentElement as HTMLElement;
if (!urlElement || urlElement === document.body) break;
}
if (urlElement && urlElement.hasAttribute('data-url')) {
const url = urlElement.getAttribute('data-url');
if (url) { if (url) {
// window.open(url, '_blank', 'noopener,noreferrer'); runtime.Browser.OpenURL(url)
runtime.Browser.OpenURL(url).then()
event.preventDefault(); event.preventDefault();
return true; return true;
} }

View File

@@ -97,6 +97,26 @@ const minimapClass = ViewPlugin.fromClass(
} }
} }
// 阻止小地图上的右键菜单
this.dom.addEventListener('contextmenu', (e) => {
e.preventDefault();
e.stopPropagation();
return false;
});
// 阻止小地图内部元素和画布上的右键菜单
this.inner.addEventListener('contextmenu', (e) => {
e.preventDefault();
e.stopPropagation();
return false;
});
this.canvas.addEventListener('contextmenu', (e) => {
e.preventDefault();
e.stopPropagation();
return false;
});
if (config.autohide) { if (config.autohide) {
this.dom.classList.add('cm-minimap-autohide'); this.dom.classList.add('cm-minimap-autohide');
} }

6
go.mod
View File

@@ -9,8 +9,11 @@ require (
github.com/knadh/koanf/providers/file v1.2.0 github.com/knadh/koanf/providers/file v1.2.0
github.com/knadh/koanf/providers/structs v1.0.0 github.com/knadh/koanf/providers/structs v1.0.0
github.com/knadh/koanf/v2 v2.2.1 github.com/knadh/koanf/v2 v2.2.1
github.com/robertkrimen/otto v0.5.1
github.com/wailsapp/wails/v3 v3.0.0-alpha.9 github.com/wailsapp/wails/v3 v3.0.0-alpha.9
golang.org/x/net v0.41.0
golang.org/x/sys v0.33.0 golang.org/x/sys v0.33.0
golang.org/x/text v0.26.0
modernc.org/sqlite v1.38.0 modernc.org/sqlite v1.38.0
) )
@@ -71,11 +74,10 @@ require (
github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect
golang.org/x/crypto v0.39.0 // indirect golang.org/x/crypto v0.39.0 // indirect
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect
golang.org/x/net v0.41.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/text v0.26.0 // indirect
golang.org/x/time v0.12.0 // indirect golang.org/x/time v0.12.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/sourcemap.v1 v1.0.5 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/libc v1.66.2 // indirect modernc.org/libc v1.66.2 // indirect

4
go.sum
View File

@@ -142,6 +142,8 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qq
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/robertkrimen/otto v0.5.1 h1:avDI4ToRk8k1hppLdYFTuuzND41n37vPGJU7547dGf0=
github.com/robertkrimen/otto v0.5.1/go.mod h1:bS433I4Q9p+E5pZLu7r17vP6FkE6/wLxBdmKjoqJXF8=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/samber/lo v1.51.0 h1:kysRYLbHy/MB7kQZf5DSN50JHmMsNEdeY24VzJFu7wI= github.com/samber/lo v1.51.0 h1:kysRYLbHy/MB7kQZf5DSN50JHmMsNEdeY24VzJFu7wI=
@@ -224,6 +226,8 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI=
gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@@ -0,0 +1,277 @@
// Package translator 提供文本翻译功能
package translator
import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"regexp"
"strings"
"time"
"golang.org/x/text/language"
)
// BingTranslator Bing翻译器结构体
type BingTranslator struct {
BingHost string // Bing服务主机
httpClient *http.Client // HTTP客户端
Timeout time.Duration // 请求超时时间
}
// 常量定义
const (
bingDefaultTimeout = 30 * time.Second
defaultBingHost = "cn.bing.com" // 使用cn.bing.com作为默认域名
)
// 错误定义
var (
ErrBingNetworkError = errors.New("bing translator network error")
ErrBingParseError = errors.New("bing translator parse error")
ErrBingTokenError = errors.New("failed to get bing translator token")
)
// BingTranslationParams Bing翻译所需的参数
type BingTranslationParams struct {
Token string // token参数
Key string // key参数
IG string // IG参数
}
// NewBingTranslator 创建一个新的Bing翻译器实例
func NewBingTranslator() *BingTranslator {
translator := &BingTranslator{
BingHost: defaultBingHost,
Timeout: bingDefaultTimeout,
httpClient: &http.Client{Timeout: bingDefaultTimeout},
}
return translator
}
// SetTimeout 设置请求超时时间
func (t *BingTranslator) SetTimeout(timeout time.Duration) {
t.Timeout = timeout
t.httpClient.Timeout = timeout
}
// SetBingHost 设置Bing主机
func (t *BingTranslator) SetBingHost(host string) {
t.BingHost = host
}
// Translate 使用标准语言标签进行文本翻译
func (t *BingTranslator) Translate(text string, from language.Tag, to language.Tag) (string, error) {
return t.translate(text, from.String(), to.String())
}
// TranslateWithParams 使用简单字符串参数进行文本翻译
func (t *BingTranslator) TranslateWithParams(text string, params TranslationParams) (string, error) {
return t.translate(text, params.From, params.To)
}
// translate 执行实际翻译操作
func (t *BingTranslator) translate(text, from, to string) (string, error) {
// 获取翻译所需的参数
params, err := t.ExtractBingTranslationParams()
if err != nil {
return "", fmt.Errorf("failed to extract bing translation params: %w", err)
}
// 执行翻译
return t.GetBingTranslation(params.Token, params.Key, params.IG, text, from, to)
}
// ExtractBingTranslationParams 提取Bing翻译所需的参数
func (t *BingTranslator) ExtractBingTranslationParams() (*BingTranslationParams, error) {
// 发送GET请求获取网页内容
url := fmt.Sprintf("https://%s/translator?mkt=zh-CN", t.BingHost)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36")
req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7")
req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8")
resp, err := t.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("%w: %v", ErrBingNetworkError, err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to access Bing translator page: status code %d", resp.StatusCode)
}
// 读取响应内容
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response: %w", err)
}
pageContent := string(body)
// 模式1: 标准的params_AbusePreventionHelper数组
keyPattern := regexp.MustCompile(`params_AbusePreventionHelper\s*=\s*\[([^\]]+)\]`)
keyMatch := keyPattern.FindStringSubmatch(pageContent)
var key, token string
if len(keyMatch) >= 2 {
// 提取并解析数组
paramsStr := keyMatch[1]
paramsList := strings.Split(paramsStr, ",")
if len(paramsList) >= 2 {
// 清理引号
key = strings.Trim(paramsList[0], `"' `)
token = strings.Trim(paramsList[1], `"' `)
}
}
// 如果标准模式失败,尝试备用模式
if key == "" || token == "" {
// 模式2: 查找_G.Token和_G.Key
tokenPattern := regexp.MustCompile(`_G\.Token\s*=\s*["']([^"']+)["']`)
tokenMatch := tokenPattern.FindStringSubmatch(pageContent)
keyPattern := regexp.MustCompile(`_G\.Key\s*=\s*["']?([^"',]+)["']?`)
keyMatch := keyPattern.FindStringSubmatch(pageContent)
if len(tokenMatch) >= 2 && len(keyMatch) >= 2 {
token = tokenMatch[1]
key = keyMatch[1]
}
}
// 如果仍然失败尝试JSON格式
if key == "" || token == "" {
jsonPattern := regexp.MustCompile(`"token"\s*:\s*"([^"]+)"\s*,\s*"key"\s*:\s*"?([^",]+)"?`)
jsonMatch := jsonPattern.FindStringSubmatch(pageContent)
if len(jsonMatch) >= 3 {
token = jsonMatch[1]
key = jsonMatch[2]
}
}
// 如果所有模式都失败
if key == "" || token == "" {
return nil, fmt.Errorf("%w: unable to extract token and key", ErrBingTokenError)
}
// 查找并提取 IG 参数,尝试多种格式
var ig string
// 模式1: 标准IG格式
igPattern := regexp.MustCompile(`IG["']?\s*:\s*["']([^"']+)["']`)
igMatch := igPattern.FindStringSubmatch(pageContent)
if len(igMatch) >= 2 {
ig = igMatch[1]
} else {
// 模式2: 备用IG格式
igPattern = regexp.MustCompile(`"IG"\s*:\s*"([^"]+)"`)
igMatch = igPattern.FindStringSubmatch(pageContent)
if len(igMatch) >= 2 {
ig = igMatch[1]
} else {
// 模式3: _G.IG格式
igPattern = regexp.MustCompile(`_G\.IG\s*=\s*["']([^"']+)["']`)
igMatch = igPattern.FindStringSubmatch(pageContent)
if len(igMatch) >= 2 {
ig = igMatch[1]
}
}
}
// 如果所有IG提取模式都失败
if ig == "" {
return nil, fmt.Errorf("%w: unable to extract IG parameter", ErrBingTokenError)
}
return &BingTranslationParams{
Token: token,
Key: key,
IG: ig,
}, nil
}
// GetBingTranslation 获取Bing翻译结果
func (t *BingTranslator) GetBingTranslation(token, key, ig, text, fromLang, toLang string) (string, error) {
// URL编码文本
encodedText := url.QueryEscape(text)
// 构建POST请求的payload
payload := fmt.Sprintf("fromLang=%s&to=%s&text=%s&token=%s&key=%s",
fromLang, toLang, encodedText, token, key)
// 构建URL
urlStr := fmt.Sprintf("https://%s/ttranslatev3?isVertical=1&IG=%s&IID=translator.5028", t.BingHost, ig)
// 创建请求
req, err := http.NewRequest("POST", urlStr, strings.NewReader(payload))
if err != nil {
return "", fmt.Errorf("failed to create request: %w", err)
}
// 设置请求头
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36")
req.Header.Set("Accept", "*/*")
req.Header.Set("Host", t.BingHost)
req.Header.Set("Connection", "keep-alive")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Origin", fmt.Sprintf("https://%s", t.BingHost))
req.Header.Set("Referer", fmt.Sprintf("https://%s/translator", t.BingHost))
// 发送请求
resp, err := t.httpClient.Do(req)
if err != nil {
return "", fmt.Errorf("%w: %v", ErrBingNetworkError, err)
}
defer resp.Body.Close()
// 判断请求是否成功
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("API error: status code %d", resp.StatusCode)
}
// 读取响应体
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("failed to read response: %w", err)
}
// 检查响应内容
if len(body) == 0 {
return "", fmt.Errorf("translation API returned empty response")
}
// 使用最简单的结构体解析JSON
var response []struct {
Translations []struct {
Text string `json:"text"`
} `json:"translations"`
}
if err := json.Unmarshal(body, &response); err != nil {
return "", fmt.Errorf("%w: JSON parsing error: %v", ErrBingParseError, err)
}
// 检查解析结果
if len(response) == 0 || len(response[0].Translations) == 0 {
return "", fmt.Errorf("%w: invalid response format", ErrBingParseError)
}
// 返回翻译结果
return response[0].Translations[0].Text, nil
}

View File

@@ -0,0 +1,318 @@
// Package translator 提供文本翻译功能
package translator
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"math/rand"
"net/http"
"strings"
"time"
"golang.org/x/text/language"
)
// DeeplTranslator DeepL翻译器结构体
type DeeplTranslator struct {
DeeplHost string // DeepL服务主机
httpClient *http.Client // HTTP客户端
Timeout time.Duration // 请求超时时间
}
// 常量定义
const (
deeplDefaultTimeout = 30 * time.Second
defaultDeeplHost = "www2.deepl.com" // 默认DeepL API主机
deeplJsonRpcUrl = "https://www2.deepl.com/jsonrpc" // DeepL JSON-RPC API
)
// 错误定义
var (
ErrDeeplNetworkError = errors.New("deepl translator network error")
ErrDeeplUnsupportedLang = errors.New("deepl translator unsupported language")
ErrDeeplResponseError = errors.New("deepl translator response error")
)
// 语言映射
var deeplLangMap = map[string]string{
"auto": "auto",
"de": "DE",
"en": "EN",
"es": "ES",
"fr": "FR",
"it": "IT",
"ja": "JA",
"ko": "KO",
"nl": "NL",
"pl": "PL",
"pt": "PT",
"ru": "RU",
"zh": "ZH",
"bg": "BG",
"cs": "CS",
"da": "DA",
"el": "EL",
"et": "ET",
"fi": "FI",
"hu": "HU",
"lt": "LT",
"lv": "LV",
"ro": "RO",
"sk": "SK",
"sl": "SL",
"sv": "SV",
}
// 反向语言映射
var deeplLangMapReverse = map[string]string{
"auto": "auto",
"DE": "de",
"EN": "en",
"ES": "es",
"FR": "fr",
"IT": "it",
"JA": "ja",
"KO": "ko",
"NL": "nl",
"PL": "pl",
"PT": "pt",
"RU": "ru",
"ZH": "zh",
"BG": "bg",
"CS": "cs",
"DA": "da",
"EL": "el",
"ET": "et",
"FI": "fi",
"HU": "hu",
"LT": "lt",
"LV": "lv",
"RO": "ro",
"SK": "sk",
"SL": "sl",
"SV": "sv",
}
// DeeplRequest DeepL请求结构体
type DeeplRequest struct {
Jsonrpc string `json:"jsonrpc"`
Method string `json:"method"`
ID int64 `json:"id"`
Params DeeplReqParams `json:"params"`
}
// DeeplReqParams DeepL请求参数结构体
type DeeplReqParams struct {
Texts []DeeplText `json:"texts"`
Splitting string `json:"splitting"`
Lang DeeplLang `json:"lang"`
Timestamp int64 `json:"timestamp"`
}
// DeeplText DeepL文本结构体
type DeeplText struct {
Text string `json:"text"`
RequestAlternatives int `json:"requestAlternatives"`
}
// DeeplLang DeepL语言结构体
type DeeplLang struct {
SourceLangUserSelected string `json:"source_lang_user_selected"`
TargetLang string `json:"target_lang"`
}
// DeeplResponse DeepL响应结构体
type DeeplResponse struct {
Jsonrpc string `json:"jsonrpc"`
ID int64 `json:"id"`
Result DeeplResult `json:"result"`
}
// DeeplResult DeepL结果结构体
type DeeplResult struct {
Texts []DeeplResultText `json:"texts"`
Lang string `json:"lang"`
LangIsConfident bool `json:"lang_is_confident"`
DetectedLanguages map[string]float64 `json:"detectedLanguages"`
}
// DeeplResultText DeepL结果文本结构体
type DeeplResultText struct {
Text string `json:"text"`
Alternatives []DeeplAlternative `json:"alternatives,omitempty"`
}
// DeeplAlternative DeepL替代翻译结构体
type DeeplAlternative struct {
Text string `json:"text"`
}
// NewDeeplTranslator 创建一个新的DeepL翻译器实例
func NewDeeplTranslator() *DeeplTranslator {
translator := &DeeplTranslator{
DeeplHost: defaultDeeplHost,
Timeout: deeplDefaultTimeout,
httpClient: &http.Client{Timeout: deeplDefaultTimeout},
}
return translator
}
// SetTimeout 设置请求超时时间
func (t *DeeplTranslator) SetTimeout(timeout time.Duration) {
t.Timeout = timeout
t.httpClient.Timeout = timeout
}
// SetDeeplHost 设置DeepL主机
func (t *DeeplTranslator) SetDeeplHost(host string) {
t.DeeplHost = host
}
// Translate 使用标准语言标签进行文本翻译
func (t *DeeplTranslator) Translate(text string, from language.Tag, to language.Tag) (string, error) {
return t.translate(text, from.String(), to.String())
}
// TranslateWithParams 使用简单字符串参数进行文本翻译
func (t *DeeplTranslator) TranslateWithParams(text string, params TranslationParams) (string, error) {
tries := params.Tries
if tries == 0 {
tries = defaultNumberOfRetries
}
var result string
var lastError error
for i := 0; i < tries; i++ {
if i > 0 && params.Delay > 0 {
time.Sleep(params.Delay)
}
result, lastError = t.translate(text, params.From, params.To)
if lastError == nil {
return result, nil
}
}
return "", lastError
}
// translate 执行实际翻译操作
func (t *DeeplTranslator) translate(text, from, to string) (string, error) {
// 转换语言代码为DeepL格式
sourceLang, ok := deeplLangMap[strings.ToLower(from)]
if !ok && from != "auto" {
sourceLang = "auto"
}
targetLang, ok := deeplLangMap[strings.ToLower(to)]
if !ok {
return "", fmt.Errorf("%w: language '%s' not supported by DeepL", ErrDeeplUnsupportedLang, to)
}
// 准备请求数据
id := getRandomNumber()
iCount := getICount(text)
timestamp := getTimeStamp(iCount)
// 构建请求体
reqParams := DeeplReqParams{
Texts: []DeeplText{
{
Text: text,
RequestAlternatives: 3,
},
},
Splitting: "newlines",
Lang: DeeplLang{
SourceLangUserSelected: sourceLang,
TargetLang: targetLang,
},
Timestamp: timestamp,
}
request := DeeplRequest{
Jsonrpc: "2.0",
Method: "LMT_handle_texts",
ID: id,
Params: reqParams,
}
// 序列化请求
jsonData, err := json.Marshal(request)
if err != nil {
return "", fmt.Errorf("failed to marshal request: %w", err)
}
// 特殊处理method字段格式
postStr := string(jsonData)
if (id+5)%29 == 0 || (id+3)%13 == 0 {
postStr = strings.Replace(postStr, `"method":"`, `"method" : "`, 1)
} else {
postStr = strings.Replace(postStr, `"method":"`, `"method": "`, 1)
}
// 发送请求
req, err := http.NewRequest("POST", deeplJsonRpcUrl, bytes.NewBuffer([]byte(postStr)))
if err != nil {
return "", fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36")
resp, err := t.httpClient.Do(req)
if err != nil {
return "", fmt.Errorf("%w: %v", ErrDeeplNetworkError, err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("%w: status code %d", ErrDeeplNetworkError, resp.StatusCode)
}
// 读取响应
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("failed to read response: %w", err)
}
// 解析响应
var deeplResp DeeplResponse
err = json.Unmarshal(body, &deeplResp)
if err != nil {
return "", fmt.Errorf("%w: %v", ErrDeeplResponseError, err)
}
// 检查是否有有效的结果
if len(deeplResp.Result.Texts) == 0 {
return "", fmt.Errorf("%w: no translation result", ErrDeeplResponseError)
}
// 返回翻译结果
return deeplResp.Result.Texts[0].Text, nil
}
// getICount 获取文本中'i'字符的数量
func getICount(text string) int {
return strings.Count(text, "i")
}
// getRandomNumber 生成随机数
func getRandomNumber() int64 {
return int64(rand.Intn(99999)+100000) * 1000
}
// getTimeStamp 获取时间戳
func getTimeStamp(iCount int) int64 {
ts := time.Now().UnixMilli()
if iCount != 0 {
iCount++
return ts - (ts % int64(iCount)) + int64(iCount)
}
return ts
}

View File

@@ -0,0 +1,311 @@
// Package translator 提供文本翻译功能
package translator
import (
"encoding/json"
"errors"
"fmt"
"io"
"log"
"math"
"net/http"
"net/url"
"regexp"
"strconv"
"strings"
"time"
"github.com/robertkrimen/otto"
"golang.org/x/text/language"
)
// 错误定义
var (
ErrBadNetwork = errors.New("bad network, please check your internet connection")
)
// GoogleTranslator Google翻译器结构体统一管理翻译功能
type GoogleTranslator struct {
GoogleHost string // Google服务主机
vm *otto.Otto // JavaScript虚拟机
ttk otto.Value // 翻译token缓存
httpClient *http.Client // HTTP客户端
Timeout time.Duration // 请求超时时间
}
// NewGoogleTranslator 创建一个新的Google翻译器实例
func NewGoogleTranslator() *GoogleTranslator {
translator := &GoogleTranslator{
GoogleHost: "google.com",
vm: otto.New(),
Timeout: defaultTimeout,
httpClient: &http.Client{Timeout: defaultTimeout},
}
// 初始化ttk
translator.ttk, _ = otto.ToValue("0")
return translator
}
// SetTimeout 设置请求超时时间
func (t *GoogleTranslator) SetTimeout(timeout time.Duration) {
t.Timeout = timeout
t.httpClient.Timeout = timeout
}
// SetGoogleHost 设置Google主机
func (t *GoogleTranslator) SetGoogleHost(host string) {
t.GoogleHost = host
}
// Translate 使用Go语言提供的标准语言标签进行文本翻译
func (t *GoogleTranslator) Translate(text string, from language.Tag, to language.Tag) (string, error) {
return t.translate(text, from.String(), to.String(), false, defaultNumberOfRetries, 0)
}
// TranslateWithParams 使用简单字符串参数进行文本翻译
func (t *GoogleTranslator) TranslateWithParams(text string, params TranslationParams) (string, error) {
tries := params.Tries
if tries == 0 {
tries = defaultNumberOfRetries
}
return t.translate(text, params.From, params.To, true, tries, params.Delay)
}
// translate 执行实际翻译操作
func (t *GoogleTranslator) translate(text, from, to string, withVerification bool, tries int, delay time.Duration) (string, error) {
if tries == 0 {
tries = defaultNumberOfRetries
}
if withVerification {
if _, err := language.Parse(from); err != nil && from != "auto" {
log.Println("[WARNING], '" + from + "' is a invalid language, switching to 'auto'")
from = "auto"
}
if _, err := language.Parse(to); err != nil {
log.Println("[WARNING], '" + to + "' is a invalid language, switching to 'en'")
to = "en"
}
}
textValue, _ := otto.ToValue(text)
urlStr := fmt.Sprintf("https://translate.%s/translate_a/single", t.GoogleHost)
token := t.getToken(textValue)
data := map[string]string{
"client": "gtx",
"sl": from,
"tl": to,
"hl": to,
"ie": "UTF-8",
"oe": "UTF-8",
"otf": "1",
"ssel": "0",
"tsel": "0",
"kc": "7",
"q": text,
}
u, err := url.Parse(urlStr)
if err != nil {
return "", err
}
parameters := url.Values{}
for k, v := range data {
parameters.Add(k, v)
}
for _, v := range []string{"at", "bd", "ex", "ld", "md", "qca", "rw", "rm", "ss", "t"} {
parameters.Add("dt", v)
}
parameters.Add("tk", token)
u.RawQuery = parameters.Encode()
var r *http.Response
for tries > 0 {
req, err := http.NewRequest("GET", u.String(), nil)
if err != nil {
return "", err
}
r, err = t.httpClient.Do(req)
if err != nil {
if errors.Is(err, http.ErrHandlerTimeout) {
return "", ErrBadNetwork
}
return "", err
}
if r.StatusCode == http.StatusOK {
break
}
if r.StatusCode == http.StatusForbidden {
tries--
time.Sleep(delay)
}
}
raw, err := io.ReadAll(r.Body)
if err != nil {
return "", err
}
defer r.Body.Close()
var resp []interface{}
err = json.Unmarshal(raw, &resp)
if err != nil {
return "", err
}
responseText := ""
for _, obj := range resp[0].([]interface{}) {
if len(obj.([]interface{})) == 0 {
break
}
t, ok := obj.([]interface{})[0].(string)
if ok {
responseText += t
}
}
return responseText, nil
}
// getToken 获取翻译API所需的token
func (t *GoogleTranslator) getToken(text otto.Value) string {
ttk, err := t.updateTTK()
if err != nil {
return ""
}
tk, err := t.generateToken(text, ttk)
if err != nil {
return ""
}
return strings.Replace(tk.String(), "&tk=", "", -1)
}
// updateTTK 更新TTK值
func (t *GoogleTranslator) updateTTK() (otto.Value, error) {
timestamp := time.Now().UnixNano() / 3600000
now := math.Floor(float64(timestamp))
ttk, err := strconv.ParseFloat(t.ttk.String(), 64)
if err != nil {
return otto.UndefinedValue(), err
}
if ttk == now {
return t.ttk, nil
}
req, err := http.NewRequest("GET", fmt.Sprintf("https://translate.%s", t.GoogleHost), nil)
if err != nil {
return otto.UndefinedValue(), err
}
resp, err := t.httpClient.Do(req)
if err != nil {
return otto.UndefinedValue(), err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return otto.UndefinedValue(), err
}
matches := regexp.MustCompile(`tkk:\s?'(.+?)'`).FindStringSubmatch(string(body))
if len(matches) > 0 {
v, err := otto.ToValue(matches[0])
if err != nil {
return otto.UndefinedValue(), err
}
t.ttk = v
return v, nil
}
return t.ttk, nil
}
// generateToken 生成翻译API所需的token
func (t *GoogleTranslator) generateToken(a otto.Value, TTK otto.Value) (otto.Value, error) {
err := t.vm.Set("x", a)
if err != nil {
return otto.UndefinedValue(), err
}
_ = t.vm.Set("internalTTK", TTK)
result, err := t.vm.Run(`
function sM(a) {
var b;
if (null !== yr)
b = yr;
else {
b = wr(String.fromCharCode(84));
var c = wr(String.fromCharCode(75));
b = [b(), b()];
b[1] = c();
b = (yr = window[b.join(c())] || "") || ""
}
var d = wr(String.fromCharCode(116))
, c = wr(String.fromCharCode(107))
, d = [d(), d()];
d[1] = c();
c = "&" + d.join("") + "=";
d = b.split(".");
b = Number(d[0]) || 0;
for (var e = [], f = 0, g = 0; g < a.length; g++) {
var l = a.charCodeAt(g);
128 > l ? e[f++] = l : (2048 > l ? e[f++] = l >> 6 | 192 : (55296 == (l & 64512) && g + 1 < a.length && 56320 == (a.charCodeAt(g + 1) & 64512) ? (l = 65536 + ((l & 1023) << 10) + (a.charCodeAt(++g) & 1023),
e[f++] = l >> 18 | 240,
e[f++] = l >> 12 & 63 | 128) : e[f++] = l >> 12 | 224,
e[f++] = l >> 6 & 63 | 128),
e[f++] = l & 63 | 128)
}
a = b;
for (f = 0; f < e.length; f++)
a += e[f],
a = xr(a, "+-a^+6");
a = xr(a, "+-3^+b+-f");
a ^= Number(d[1]) || 0;
0 > a && (a = (a & 2147483647) + 2147483648);
a %= 1E6;
return c + (a.toString() + "." + (a ^ b))
}
var yr = null;
var wr = function(a) {
return function() {
return a
}
}
, xr = function(a, b) {
for (var c = 0; c < b.length - 2; c += 3) {
var d = b.charAt(c + 2)
, d = "a" <= d ? d.charCodeAt(0) - 87 : Number(d)
, d = "+" == b.charAt(c + 1) ? a >>> d : a << d;
a = "+" == b.charAt(c) ? a + d & 4294967295 : a ^ d
}
return a
};
var window = {
TKK: internalTTK
};
sM(x)
`)
if err != nil {
return otto.UndefinedValue(), err
}
return result, nil
}

View File

@@ -0,0 +1,73 @@
// Package translator 提供文本翻译功能
package translator
import (
"fmt"
"time"
"golang.org/x/text/language"
)
// TranslationParams 用于指定翻译参数
type TranslationParams struct {
From string // 源语言
To string // 目标语言
Tries int // 重试次数
Delay time.Duration // 重试延迟
}
// 常量定义
const (
defaultNumberOfRetries = 2
defaultTimeout = 30 * time.Second
)
// TranslatorType 翻译器类型
type TranslatorType string
const (
// GoogleTranslatorType 谷歌翻译器
GoogleTranslatorType TranslatorType = "google"
// BingTranslatorType 必应翻译器
BingTranslatorType TranslatorType = "bing"
// YoudaoTranslatorType 有道翻译器
YoudaoTranslatorType TranslatorType = "youdao"
// DeeplTranslatorType DeepL翻译器
DeeplTranslatorType TranslatorType = "deepl"
)
// Translator 翻译器接口,定义所有翻译器必须实现的方法
type Translator interface {
// Translate 使用Go语言提供的标准语言标签进行文本翻译
Translate(text string, from language.Tag, to language.Tag) (string, error)
// TranslateWithParams 使用简单字符串参数进行文本翻译
TranslateWithParams(text string, params TranslationParams) (string, error)
// SetTimeout 设置请求超时时间
SetTimeout(timeout time.Duration)
}
// TranslatorFactory 翻译器工厂,用于创建不同类型的翻译器
type TranslatorFactory struct{}
// NewTranslatorFactory 创建一个新的翻译器工厂
func NewTranslatorFactory() *TranslatorFactory {
return &TranslatorFactory{}
}
// Create 根据类型创建翻译器
func (f *TranslatorFactory) Create(translatorType TranslatorType) (Translator, error) {
switch translatorType {
case GoogleTranslatorType:
return NewGoogleTranslator(), nil
case BingTranslatorType:
return NewBingTranslator(), nil
case YoudaoTranslatorType:
return NewYoudaoTranslator(), nil
case DeeplTranslatorType:
return NewDeeplTranslator(), nil
default:
return nil, fmt.Errorf("unsupported translator type: %s", translatorType)
}
}

View File

@@ -0,0 +1,186 @@
// Package translator 提供文本翻译功能
package translator
import (
"errors"
"fmt"
"io"
"net/http"
"net/url"
"regexp"
"strings"
"time"
"golang.org/x/net/html"
"golang.org/x/text/language"
)
// YoudaoTranslator 有道翻译器结构体
type YoudaoTranslator struct {
httpClient *http.Client // HTTP客户端
Timeout time.Duration // 请求超时时间
}
// 常量定义
const (
youdaoDefaultTimeout = 30 * time.Second
youdaoTranslateURL = "https://m.youdao.com/translate"
)
// 错误定义
var (
ErrYoudaoNetworkError = errors.New("youdao translator network error")
ErrYoudaoParseError = errors.New("youdao translator parse error")
)
// NewYoudaoTranslator 创建一个新的有道翻译器实例
func NewYoudaoTranslator() *YoudaoTranslator {
translator := &YoudaoTranslator{
Timeout: youdaoDefaultTimeout,
httpClient: &http.Client{Timeout: youdaoDefaultTimeout},
}
return translator
}
// SetTimeout 设置请求超时时间
func (t *YoudaoTranslator) SetTimeout(timeout time.Duration) {
t.Timeout = timeout
t.httpClient.Timeout = timeout
}
// Translate 使用标准语言标签进行文本翻译
func (t *YoudaoTranslator) Translate(text string, from language.Tag, to language.Tag) (string, error) {
// 有道翻译不需要指定源语言和目标语言,它会自动检测
return t.translate(text)
}
// TranslateWithParams 使用简单字符串参数进行文本翻译
func (t *YoudaoTranslator) TranslateWithParams(text string, params TranslationParams) (string, error) {
// 有道翻译不需要指定源语言和目标语言,它会自动检测
return t.translate(text)
}
// translate 执行实际翻译操作
func (t *YoudaoTranslator) translate(text string) (string, error) {
// 构建表单数据
form := url.Values{}
form.Add("inputtext", text)
form.Add("type", "AUTO")
// 创建请求
req, err := http.NewRequest("POST", youdaoTranslateURL, strings.NewReader(form.Encode()))
if err != nil {
return "", fmt.Errorf("failed to create request: %w", err)
}
// 设置请求头
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9")
req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8")
// 发送请求
resp, err := t.httpClient.Do(req)
if err != nil {
return "", fmt.Errorf("%w: %v", ErrYoudaoNetworkError, err)
}
defer resp.Body.Close()
// 判断请求是否成功
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("API error: status code %d", resp.StatusCode)
}
// 读取响应体
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("failed to read response: %w", err)
}
// 解析HTML响应
result, err := t.extractTranslationResult(string(body))
if err != nil {
return "", err
}
return result, nil
}
// extractTranslationResult 从HTML响应中提取翻译结果
func (t *YoudaoTranslator) extractTranslationResult(htmlContent string) (string, error) {
// 方法1使用正则表达式提取翻译结果
pattern := regexp.MustCompile(`<ul id="translateResult"[^>]*>.*?<li[^>]*>(.*?)</li>`)
matches := pattern.FindStringSubmatch(htmlContent)
if len(matches) >= 2 {
// 清理HTML标签
result := matches[1]
result = strings.ReplaceAll(result, "<br>", "\n")
result = t.stripHTMLTags(result)
return result, nil
}
// 方法2使用HTML解析器提取翻译结果
doc, err := html.Parse(strings.NewReader(htmlContent))
if err != nil {
return "", fmt.Errorf("%w: failed to parse HTML", ErrYoudaoParseError)
}
// 查找翻译结果元素
result := t.findTranslateResult(doc)
if result != "" {
return result, nil
}
return "", fmt.Errorf("%w: could not find translation result", ErrYoudaoParseError)
}
// stripHTMLTags 移除HTML标签
func (t *YoudaoTranslator) stripHTMLTags(input string) string {
// 简单的HTML标签移除
re := regexp.MustCompile("<[^>]*>")
return re.ReplaceAllString(input, "")
}
// findTranslateResult 在HTML文档中查找翻译结果
func (t *YoudaoTranslator) findTranslateResult(n *html.Node) string {
if n.Type == html.ElementNode && n.Data == "ul" {
for _, attr := range n.Attr {
if attr.Key == "id" && attr.Val == "translateResult" {
// 找到了translateResult元素提取其中的文本
var result string
for c := n.FirstChild; c != nil; c = c.NextSibling {
if c.Type == html.ElementNode && c.Data == "li" {
return t.extractText(c)
}
}
return result
}
}
}
// 递归查找子节点
for c := n.FirstChild; c != nil; c = c.NextSibling {
result := t.findTranslateResult(c)
if result != "" {
return result
}
}
return ""
}
// extractText 提取节点中的文本内容
func (t *YoudaoTranslator) extractText(n *html.Node) string {
if n.Type == html.TextNode {
return n.Data
}
var result string
for c := n.FirstChild; c != nil; c = c.NextSibling {
result += t.extractText(c)
}
return result
}

View File

@@ -9,19 +9,20 @@ import (
// ServiceManager 服务管理器,负责协调各个服务 // ServiceManager 服务管理器,负责协调各个服务
type ServiceManager struct { type ServiceManager struct {
pathManager *PathManager pathManager *PathManager
configService *ConfigService configService *ConfigService
documentService *DocumentService documentService *DocumentService
migrationService *MigrationService migrationService *MigrationService
systemService *SystemService systemService *SystemService
hotkeyService *HotkeyService hotkeyService *HotkeyService
dialogService *DialogService dialogService *DialogService
trayService *TrayService trayService *TrayService
keyBindingService *KeyBindingService keyBindingService *KeyBindingService
extensionService *ExtensionService extensionService *ExtensionService
startupService *StartupService startupService *StartupService
selfUpdateService *SelfUpdateService selfUpdateService *SelfUpdateService
logger *log.LoggerService translationService *TranslationService
logger *log.LoggerService
} }
// NewServiceManager 创建新的服务管理器实例 // NewServiceManager 创建新的服务管理器实例
@@ -68,6 +69,9 @@ func NewServiceManager() *ServiceManager {
panic(err) panic(err)
} }
// 初始化翻译服务
translationService := NewTranslationService(logger)
// 使用新的配置通知系统设置热键配置变更监听 // 使用新的配置通知系统设置热键配置变更监听
err = configService.SetHotkeyChangeCallback(func(enable bool, hotkey *models.HotkeyCombo) error { err = configService.SetHotkeyChangeCallback(func(enable bool, hotkey *models.HotkeyCombo) error {
return hotkeyService.UpdateHotkey(enable, hotkey) return hotkeyService.UpdateHotkey(enable, hotkey)
@@ -85,18 +89,19 @@ func NewServiceManager() *ServiceManager {
} }
return &ServiceManager{ return &ServiceManager{
configService: configService, configService: configService,
documentService: documentService, documentService: documentService,
migrationService: migrationService, migrationService: migrationService,
systemService: systemService, systemService: systemService,
hotkeyService: hotkeyService, hotkeyService: hotkeyService,
dialogService: dialogService, dialogService: dialogService,
trayService: trayService, trayService: trayService,
keyBindingService: keyBindingService, keyBindingService: keyBindingService,
extensionService: extensionService, extensionService: extensionService,
startupService: startupService, startupService: startupService,
selfUpdateService: selfUpdateService, selfUpdateService: selfUpdateService,
logger: logger, translationService: translationService,
logger: logger,
} }
} }
@@ -114,6 +119,7 @@ func (sm *ServiceManager) GetServices() []application.Service {
application.NewService(sm.extensionService), application.NewService(sm.extensionService),
application.NewService(sm.startupService), application.NewService(sm.startupService),
application.NewService(sm.selfUpdateService), application.NewService(sm.selfUpdateService),
application.NewService(sm.translationService),
} }
return services return services
} }
@@ -162,3 +168,8 @@ func (sm *ServiceManager) GetExtensionService() *ExtensionService {
func (sm *ServiceManager) GetSelfUpdateService() *SelfUpdateService { func (sm *ServiceManager) GetSelfUpdateService() *SelfUpdateService {
return sm.selfUpdateService return sm.selfUpdateService
} }
// GetTranslationService 获取翻译服务实例
func (sm *ServiceManager) GetTranslationService() *TranslationService {
return sm.translationService
}

View File

@@ -0,0 +1,253 @@
package services
import (
"sync"
"time"
"voidraft/internal/common/translator"
"github.com/wailsapp/wails/v3/pkg/services/log"
"golang.org/x/text/language"
)
// TranslationService 翻译服务
type TranslationService struct {
logger *log.LoggerService
factory *translator.TranslatorFactory
defaultTimeout time.Duration
activeTranslator translator.TranslatorType
translators map[translator.TranslatorType]translator.Translator
mutex sync.RWMutex
}
// NewTranslationService 创建翻译服务实例
func NewTranslationService(logger *log.LoggerService) *TranslationService {
factory := translator.NewTranslatorFactory()
defaultTimeout := 10 * time.Second
// 默认使用bin翻译
activeType := translator.BingTranslatorType
// 预初始化所有翻译器
translators := make(map[translator.TranslatorType]translator.Translator)
service := &TranslationService{
logger: logger,
factory: factory,
defaultTimeout: defaultTimeout,
activeTranslator: activeType,
translators: translators,
}
// 延迟初始化翻译器以提高启动速度
go service.initTranslators()
return service
}
// initTranslators 初始化所有翻译器
func (s *TranslationService) initTranslators() {
types := []translator.TranslatorType{
translator.GoogleTranslatorType,
translator.BingTranslatorType,
translator.YoudaoTranslatorType,
translator.DeeplTranslatorType,
}
for _, t := range types {
trans, err := s.factory.Create(t)
if err != nil {
s.logger.Error("Failed to create translator: %v", err)
continue
}
trans.SetTimeout(s.defaultTimeout)
s.mutex.Lock()
s.translators[t] = trans
s.mutex.Unlock()
}
}
// getTranslator 获取指定类型的翻译器
func (s *TranslationService) getTranslator(translatorType translator.TranslatorType) (translator.Translator, error) {
s.mutex.RLock()
trans, exists := s.translators[translatorType]
s.mutex.RUnlock()
if exists {
return trans, nil
}
// 如果翻译器尚未初始化,则立即创建
trans, err := s.factory.Create(translatorType)
if err != nil {
return nil, err
}
trans.SetTimeout(s.defaultTimeout)
s.mutex.Lock()
s.translators[translatorType] = trans
s.mutex.Unlock()
return trans, nil
}
// Translate 使用当前活跃翻译器进行翻译
// @param {string} text - 待翻译文本
// @param {string} from - 源语言代码 (如 "en", "zh", "auto")
// @param {string} to - 目标语言代码 (如 "en", "zh")
// @returns {string} 翻译后的文本
// @returns {error} 可能的错误
func (s *TranslationService) Translate(text string, from string, to string) (string, error) {
// 解析语言标签
var fromLang, toLang language.Tag
var err error
if from == "auto" {
fromLang = language.Und // 未定义,表示自动检测
} else {
fromLang, err = language.Parse(from)
if err != nil {
return "", err
}
}
toLang, err = language.Parse(to)
if err != nil {
return "", err
}
// 获取活跃翻译器
s.mutex.RLock()
activeType := s.activeTranslator
s.mutex.RUnlock()
trans, err := s.getTranslator(activeType)
if err != nil {
return "", err
}
// 执行翻译
return trans.Translate(text, fromLang, toLang)
}
// TranslateWithFallback 尝试使用当前活跃翻译器翻译,如果失败则尝试备用翻译器
// @param {string} text - 待翻译文本
// @param {string} from - 源语言代码 (如 "en", "zh", "auto")
// @param {string} to - 目标语言代码 (如 "en", "zh")
// @returns {string} 翻译后的文本
// @returns {string} 使用的翻译器类型
// @returns {error} 可能的错误
func (s *TranslationService) TranslateWithFallback(text string, from string, to string) (string, string, error) {
// 首先尝试活跃翻译器
s.mutex.RLock()
primaryType := s.activeTranslator
s.mutex.RUnlock()
result, err := s.TranslateWith(text, from, to, string(primaryType))
if err == nil {
return result, string(primaryType), nil
}
// 备用翻译器列表
fallbacks := []translator.TranslatorType{
translator.GoogleTranslatorType,
translator.BingTranslatorType,
translator.DeeplTranslatorType,
translator.YoudaoTranslatorType,
}
// 尝试备用翻译器
for _, fallbackType := range fallbacks {
if fallbackType == primaryType {
continue // 跳过已尝试的主要翻译器
}
result, err := s.TranslateWith(text, from, to, string(fallbackType))
if err == nil {
return result, string(fallbackType), nil
}
}
return "", "", err // 所有翻译器都失败时返回最后一个错误
}
// TranslateWith 使用指定翻译器进行翻译
// @param {string} text - 待翻译文本
// @param {string} from - 源语言代码 (如 "en", "zh", "auto")
// @param {string} to - 目标语言代码 (如 "en", "zh")
// @param {string} translatorType - 翻译器类型 ("google", "bing", "youdao", "deepl")
// @returns {string} 翻译后的文本
// @returns {error} 可能的错误
func (s *TranslationService) TranslateWith(text string, from string, to string, translatorType string) (string, error) {
// 参数验证
if text == "" {
return "", nil // 空文本无需翻译
}
// 转换为翻译器类型
transType := translator.TranslatorType(translatorType)
// 获取指定翻译器
trans, err := s.getTranslator(transType)
if err != nil {
return "", err
}
// 创建翻译参数
params := translator.TranslationParams{
From: from,
To: to,
Tries: 2,
Delay: 500 * time.Millisecond,
}
// 执行翻译
return trans.TranslateWithParams(text, params)
}
// SetActiveTranslator 设置活跃翻译器
// @param {string} translatorType - 翻译器类型 ("google", "bing", "youdao", "deepl")
// @returns {error} 可能的错误
func (s *TranslationService) SetActiveTranslator(translatorType string) error {
transType := translator.TranslatorType(translatorType)
// 验证翻译器类型
_, err := s.factory.Create(transType)
if err != nil {
return err
}
s.mutex.Lock()
s.activeTranslator = transType
s.mutex.Unlock()
return nil
}
// GetAvailableTranslators 获取所有可用翻译器类型
// @returns {[]string} 翻译器类型列表
func (s *TranslationService) GetAvailableTranslators() []string {
return []string{
string(translator.GoogleTranslatorType),
string(translator.BingTranslatorType),
string(translator.YoudaoTranslatorType),
string(translator.DeeplTranslatorType),
}
}
// SetTimeout 设置翻译超时时间
// @param {int} seconds - 超时秒数
func (s *TranslationService) SetTimeout(seconds int) {
timeout := time.Duration(seconds) * time.Second
s.mutex.Lock()
s.defaultTimeout = timeout
s.mutex.Unlock()
// 更新所有现有翻译器的超时设置
for _, t := range s.translators {
t.SetTimeout(timeout)
}
}