From 9a15df01eec17d1581b755064d67391155bd5e87 Mon Sep 17 00:00:00 2001 From: landaiqing Date: Sun, 19 Oct 2025 23:57:03 +0800 Subject: [PATCH] :sparkles: Added preset theme --- .../voidraft/internal/models/models.ts | 103 ++- .../internal/services/themeservice.ts | 33 +- frontend/package-lock.json | 225 +++--- frontend/package.json | 18 +- frontend/src/i18n/locales/en-US.ts | 17 + frontend/src/i18n/locales/zh-CN.ts | 17 + frontend/src/stores/themeStore.ts | 267 +++++-- .../src/views/editor/basic/themeExtension.ts | 38 +- .../views/editor/theme/{dark.ts => base.ts} | 224 +++--- frontend/src/views/editor/theme/dark/aura.ts | 159 ++-- .../views/editor/theme/dark/default-dark.ts | 63 ++ .../src/views/editor/theme/dark/dracula.ts | 159 ++-- .../views/editor/theme/dark/github-dark.ts | 161 ++--- .../views/editor/theme/dark/material-dark.ts | 161 ++--- .../src/views/editor/theme/dark/one-dark.ts | 184 ++--- .../views/editor/theme/dark/solarized-dark.ts | 161 ++--- .../editor/theme/dark/tokyo-night-storm.ts | 161 ++--- .../views/editor/theme/dark/tokyo-night.ts | 161 ++--- frontend/src/views/editor/theme/light.ts | 273 ------- .../views/editor/theme/light/default-light.ts | 63 ++ .../views/editor/theme/light/github-light.ts | 161 ++--- .../editor/theme/light/material-light.ts | 161 ++--- .../editor/theme/light/solarized-light.ts | 161 ++--- .../editor/theme/light/tokyo-night-day.ts | 161 ++--- frontend/src/views/editor/theme/registry.ts | 59 ++ frontend/src/views/editor/theme/types.ts | 42 +- .../views/settings/pages/AppearancePage.vue | 256 +++---- go.mod | 6 +- go.sum | 12 +- internal/models/theme.go | 677 +++++++++++++++++- internal/services/database_service.go | 7 +- internal/services/theme_service.go | 316 +++++--- version.txt | 2 +- 33 files changed, 2362 insertions(+), 2307 deletions(-) rename frontend/src/views/editor/theme/{dark.ts => base.ts} (57%) create mode 100644 frontend/src/views/editor/theme/dark/default-dark.ts delete mode 100644 frontend/src/views/editor/theme/light.ts create mode 100644 frontend/src/views/editor/theme/light/default-light.ts create mode 100644 frontend/src/views/editor/theme/registry.ts diff --git a/frontend/bindings/voidraft/internal/models/models.ts b/frontend/bindings/voidraft/internal/models/models.ts index feea55a..29be623 100644 --- a/frontend/bindings/voidraft/internal/models/models.ts +++ b/frontend/bindings/voidraft/internal/models/models.ts @@ -1191,9 +1191,20 @@ export class Theme { } /** - * ThemeColorConfig 主题颜色配置 + * ThemeColorConfig 主题颜色配置(与前端 ThemeColors 接口保持一致) */ export class ThemeColorConfig { + /** + * 主题基本信息 + * 主题名称 + */ + "name": string; + + /** + * 是否为深色主题 + */ + "dark": boolean; + /** * 基础色调 * 主背景色 @@ -1201,7 +1212,7 @@ export class ThemeColorConfig { "background": string; /** - * 次要背景色 + * 次要背景色(用于代码块交替背景) */ "backgroundSecondary": string; @@ -1211,6 +1222,17 @@ export class ThemeColorConfig { "surface": string; /** + * 下拉菜单背景 + */ + "dropdownBackground": string; + + /** + * 下拉菜单边框 + */ + "dropdownBorder": string; + + /** + * 文本颜色 * 主文本色 */ "foreground": string; @@ -1221,12 +1243,12 @@ export class ThemeColorConfig { "foregroundSecondary": string; /** - * 语法高亮 * 注释色 */ "comment": string; /** + * 语法高亮色 - 核心 * 关键字 */ "keyword": string; @@ -1261,6 +1283,42 @@ export class ThemeColorConfig { */ "type": string; + /** + * 语法高亮色 - 扩展 + * 常量 + */ + "constant": string; + + /** + * 存储类型(如 static, const) + */ + "storage": string; + + /** + * 参数 + */ + "parameter": string; + + /** + * 类名 + */ + "class": string; + + /** + * 标题(Markdown等) + */ + "heading": string; + + /** + * 无效内容/错误 + */ + "invalid": string; + + /** + * 正则表达式 + */ + "regexp": string; + /** * 界面元素 * 光标 @@ -1288,12 +1346,12 @@ export class ThemeColorConfig { "lineNumber": string; /** - * 活动行号 + * 活动行号颜色 */ "activeLineNumber": string; /** - * 边框分割线 + * 边框和分割线 * 边框色 */ "borderColor": string; @@ -1304,7 +1362,7 @@ export class ThemeColorConfig { "borderLight": string; /** - * 搜索匹配 + * 搜索和匹配 * 搜索匹配 */ "searchMatch": string; @@ -1316,6 +1374,12 @@ export class ThemeColorConfig { /** Creates a new ThemeColorConfig instance. */ constructor($$source: Partial = {}) { + if (!("name" in $$source)) { + this["name"] = ""; + } + if (!("dark" in $$source)) { + this["dark"] = false; + } if (!("background" in $$source)) { this["background"] = ""; } @@ -1325,6 +1389,12 @@ export class ThemeColorConfig { if (!("surface" in $$source)) { this["surface"] = ""; } + if (!("dropdownBackground" in $$source)) { + this["dropdownBackground"] = ""; + } + if (!("dropdownBorder" in $$source)) { + this["dropdownBorder"] = ""; + } if (!("foreground" in $$source)) { this["foreground"] = ""; } @@ -1355,6 +1425,27 @@ export class ThemeColorConfig { if (!("type" in $$source)) { this["type"] = ""; } + if (!("constant" in $$source)) { + this["constant"] = ""; + } + if (!("storage" in $$source)) { + this["storage"] = ""; + } + if (!("parameter" in $$source)) { + this["parameter"] = ""; + } + if (!("class" in $$source)) { + this["class"] = ""; + } + if (!("heading" in $$source)) { + this["heading"] = ""; + } + if (!("invalid" in $$source)) { + this["invalid"] = ""; + } + if (!("regexp" in $$source)) { + this["regexp"] = ""; + } if (!("cursor" in $$source)) { this["cursor"] = ""; } diff --git a/frontend/bindings/voidraft/internal/services/themeservice.ts b/frontend/bindings/voidraft/internal/services/themeservice.ts index ba4f925..c4adde1 100644 --- a/frontend/bindings/voidraft/internal/services/themeservice.ts +++ b/frontend/bindings/voidraft/internal/services/themeservice.ts @@ -42,12 +42,12 @@ export function GetAllThemes(): Promise<(models$0.Theme | null)[]> & { cancel(): } /** - * GetDefaultThemes 获取默认主题 + * GetThemeByID 根据ID获取主题 */ -export function GetDefaultThemes(): Promise<{ [_: string]: models$0.Theme | null }> & { cancel(): void } { - let $resultPromise = $Call.ByID(3801788118) as any; +export function GetThemeByID(id: number): Promise & { cancel(): void } { + let $resultPromise = $Call.ByID(3053137052, id) as any; let $typingPromise = $resultPromise.then(($result: any) => { - return $$createType3($result); + return $$createType1($result); }) as any; $typingPromise.cancel = $resultPromise.cancel.bind($resultPromise); return $typingPromise; @@ -66,10 +66,22 @@ export function GetThemeByType(themeType: models$0.ThemeType): Promise & { cancel(): void } { - let $resultPromise = $Call.ByID(342461245, themeType) as any; +export function GetThemesByType(themeType: models$0.ThemeType): Promise<(models$0.Theme | null)[]> & { cancel(): void } { + let $resultPromise = $Call.ByID(1478417492, themeType) as any; + let $typingPromise = $resultPromise.then(($result: any) => { + return $$createType2($result); + }) as any; + $typingPromise.cancel = $resultPromise.cancel.bind($resultPromise); + return $typingPromise; +} + +/** + * ResetTheme 重置主题为预设配置 + */ +export function ResetTheme(id: number): Promise & { cancel(): void } { + let $resultPromise = $Call.ByID(1806334457, id) as any; return $resultPromise; } @@ -90,10 +102,10 @@ export function ServiceStartup(options: application$0.ServiceOptions): Promise & { cancel(): void } { - let $resultPromise = $Call.ByID(2750902529, themeType, colors) as any; +export function UpdateTheme(id: number, colors: models$0.ThemeColorConfig): Promise & { cancel(): void } { + let $resultPromise = $Call.ByID(70189749, id, colors) as any; return $resultPromise; } @@ -101,4 +113,3 @@ export function UpdateThemeColors(themeType: models$0.ThemeType, colors: models$ const $$createType0 = models$0.Theme.createFrom; const $$createType1 = $Create.Nullable($$createType0); const $$createType2 = $Create.Array($$createType1); -const $$createType3 = $Create.Map($Create.Any, $$createType1); diff --git a/frontend/package-lock.json b/frontend/package-lock.json index a08383e..8e5aa15 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -36,9 +36,9 @@ "@codemirror/lint": "^6.9.0", "@codemirror/search": "^6.5.11", "@codemirror/state": "^6.5.2", - "@codemirror/view": "^6.38.5", + "@codemirror/view": "^6.38.6", "@cospaia/prettier-plugin-clojure": "^0.0.2", - "@lezer/highlight": "^1.2.1", + "@lezer/highlight": "^1.2.2", "@lezer/lr": "^1.4.2", "@prettier/plugin-xml": "^3.4.2", "@replit/codemirror-lang-svelte": "^6.0.0", @@ -62,23 +62,23 @@ "vue": "^3.5.22", "vue-i18n": "^11.1.12", "vue-pick-colors": "^1.8.0", - "vue-router": "^4.5.1" + "vue-router": "^4.6.3" }, "devDependencies": { - "@eslint/js": "^9.37.0", + "@eslint/js": "^9.38.0", "@lezer/generator": "^1.8.0", - "@types/node": "^24.7.1", + "@types/node": "^24.8.1", "@types/remarkable": "^2.0.8", "@vitejs/plugin-vue": "^6.0.1", "@wailsio/runtime": "latest", "cross-env": "^10.1.0", - "eslint": "^9.37.0", - "eslint-plugin-vue": "^10.5.0", + "eslint": "^9.38.0", + "eslint-plugin-vue": "^10.5.1", "globals": "^16.4.0", "typescript": "^5.9.3", - "typescript-eslint": "^8.46.0", + "typescript-eslint": "^8.46.1", "unplugin-vue-components": "^29.1.0", - "vite": "^7.1.9", + "vite": "^7.1.10", "vite-plugin-node-polyfills": "^0.24.0", "vue-eslint-parser": "^10.2.0", "vue-tsc": "^3.1.1" @@ -554,9 +554,9 @@ } }, "node_modules/@codemirror/view": { - "version": "6.38.5", - "resolved": "https://registry.npmmirror.com/@codemirror/view/-/view-6.38.5.tgz", - "integrity": "sha512-SFVsNAgsAoou+BjRewMqN+m9jaztB9wCWN9RSRgePqUbq8UVlvJfku5zB2KVhLPgH/h0RLk38tvd4tGeAhygnw==", + "version": "6.38.6", + "resolved": "https://registry.npmmirror.com/@codemirror/view/-/view-6.38.6.tgz", + "integrity": "sha512-qiS0z1bKs5WOvHIAC0Cybmv4AJSkAXgX5aD6Mqd2epSLlVJsQl8NG23jCVouIgkh4All/mrbdsf2UOLFnJw0tw==", "license": "MIT", "dependencies": { "@codemirror/state": "^6.5.0", @@ -1046,13 +1046,13 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.21.0", - "resolved": "https://registry.npmmirror.com/@eslint/config-array/-/config-array-0.21.0.tgz", - "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", + "version": "0.21.1", + "resolved": "https://registry.npmmirror.com/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.6", + "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" }, @@ -1061,9 +1061,9 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.4.0", - "resolved": "https://registry.npmmirror.com/@eslint/config-helpers/-/config-helpers-0.4.0.tgz", - "integrity": "sha512-WUFvV4WoIwW8Bv0KeKCIIEgdSiFOsulyN0xrMu+7z43q/hkOLXjvb5u7UC9jDxvRzcrbEmuZBX5yJZz1741jog==", + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/@eslint/config-helpers/-/config-helpers-0.4.1.tgz", + "integrity": "sha512-csZAzkNhsgwb0I/UAV6/RGFTbiakPCf0ZrGmrIxQpYvGZ00PhTkSnyKNolphgIvmnJeGw6rcGVEXfTzUnFuEvw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1124,9 +1124,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.37.0", - "resolved": "https://registry.npmmirror.com/@eslint/js/-/js-9.37.0.tgz", - "integrity": "sha512-jaS+NJ+hximswBG6pjNX0uEJZkrT0zwpVi3BA3vX22aFGjJjmgSTSmPpZCRKmoBL5VY/M6p0xsSJx7rk7sy5gg==", + "version": "9.38.0", + "resolved": "https://registry.npmmirror.com/@eslint/js/-/js-9.38.0.tgz", + "integrity": "sha512-UZ1VpFvXf9J06YG9xQBdnzU+kthors6KjhMAl6f4gH4usHyh31rUf2DLGInT8RFYIReYXNSydgPY0V2LuWgl7A==", "dev": true, "license": "MIT", "engines": { @@ -1137,9 +1137,9 @@ } }, "node_modules/@eslint/object-schema": { - "version": "2.1.6", - "resolved": "https://registry.npmmirror.com/@eslint/object-schema/-/object-schema-2.1.6.tgz", - "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "version": "2.1.7", + "resolved": "https://registry.npmmirror.com/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1320,9 +1320,9 @@ } }, "node_modules/@lezer/common": { - "version": "1.2.3", - "resolved": "https://registry.npmmirror.com/@lezer/common/-/common-1.2.3.tgz", - "integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==", + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/@lezer/common/-/common-1.3.0.tgz", + "integrity": "sha512-L9X8uHCYU310o99L3/MpJKYxPzXPOS7S0NmBaM7UO/x2Kb2WbmMLSkfvdr1KxRIFYOpbY0Jhn7CfLSUDzL8arQ==", "license": "MIT" }, "node_modules/@lezer/cpp": { @@ -1373,12 +1373,12 @@ } }, "node_modules/@lezer/highlight": { - "version": "1.2.1", - "resolved": "https://registry.npmmirror.com/@lezer/highlight/-/highlight-1.2.1.tgz", - "integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==", + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/@lezer/highlight/-/highlight-1.2.2.tgz", + "integrity": "sha512-z8TQwaBXXQIvG6i2g3e9cgMwUUXu9Ib7jo2qRRggdhwKpM56Dw3PM3wmexn+EGaaOZ7az0K7sjc3/gcGW7sz7A==", "license": "MIT", "dependencies": { - "@lezer/common": "^1.0.0" + "@lezer/common": "^1.3.0" } }, "node_modules/@lezer/html": { @@ -2327,9 +2327,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.7.1", - "resolved": "https://registry.npmmirror.com/@types/node/-/node-24.7.1.tgz", - "integrity": "sha512-CmyhGZanP88uuC5GpWU9q+fI61j2SkhO3UGMUdfYRE6Bcy0ccyzn1Rqj9YAB/ZY4kOXmNf0ocah5GtphmLMP6Q==", + "version": "24.8.1", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-24.8.1.tgz", + "integrity": "sha512-alv65KGRadQVfVcG69MuB4IzdYVpRwMG/mq8KWOaoOdyY617P5ivaDiMCGOFDWD2sAn5Q0mR3mRtUOgm99hL9Q==", "dev": true, "license": "MIT", "dependencies": { @@ -2344,17 +2344,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.46.0", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.0.tgz", - "integrity": "sha512-hA8gxBq4ukonVXPy0OKhiaUh/68D0E88GSmtC1iAEnGaieuDi38LhS7jdCHRLi6ErJBNDGCzvh5EnzdPwUc0DA==", + "version": "8.46.1", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.1.tgz", + "integrity": "sha512-rUsLh8PXmBjdiPY+Emjz9NX2yHvhS11v0SR6xNJkm5GM1MO9ea/1GoDKlHHZGrOJclL/cZ2i/vRUYVtjRhrHVQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.46.0", - "@typescript-eslint/type-utils": "8.46.0", - "@typescript-eslint/utils": "8.46.0", - "@typescript-eslint/visitor-keys": "8.46.0", + "@typescript-eslint/scope-manager": "8.46.1", + "@typescript-eslint/type-utils": "8.46.1", + "@typescript-eslint/utils": "8.46.1", + "@typescript-eslint/visitor-keys": "8.46.1", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", @@ -2368,7 +2368,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.46.0", + "@typescript-eslint/parser": "^8.46.1", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } @@ -2384,16 +2384,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.46.0", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-8.46.0.tgz", - "integrity": "sha512-n1H6IcDhmmUEG7TNVSspGmiHHutt7iVKtZwRppD7e04wha5MrkV1h3pti9xQLcCMt6YWsncpoT0HMjkH1FNwWQ==", + "version": "8.46.1", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-8.46.1.tgz", + "integrity": "sha512-6JSSaBZmsKvEkbRUkf7Zj7dru/8ZCrJxAqArcLaVMee5907JdtEbKGsZ7zNiIm/UAkpGUkaSMZEXShnN2D1HZA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.46.0", - "@typescript-eslint/types": "8.46.0", - "@typescript-eslint/typescript-estree": "8.46.0", - "@typescript-eslint/visitor-keys": "8.46.0", + "@typescript-eslint/scope-manager": "8.46.1", + "@typescript-eslint/types": "8.46.1", + "@typescript-eslint/typescript-estree": "8.46.1", + "@typescript-eslint/visitor-keys": "8.46.1", "debug": "^4.3.4" }, "engines": { @@ -2409,14 +2409,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.46.0", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/project-service/-/project-service-8.46.0.tgz", - "integrity": "sha512-OEhec0mH+U5Je2NZOeK1AbVCdm0ChyapAyTeXVIYTPXDJ3F07+cu87PPXcGoYqZ7M9YJVvFnfpGg1UmCIqM+QQ==", + "version": "8.46.1", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/project-service/-/project-service-8.46.1.tgz", + "integrity": "sha512-FOIaFVMHzRskXr5J4Jp8lFVV0gz5ngv3RHmn+E4HYxSJ3DgDzU7fVI1/M7Ijh1zf6S7HIoaIOtln1H5y8V+9Zg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.46.0", - "@typescript-eslint/types": "^8.46.0", + "@typescript-eslint/tsconfig-utils": "^8.46.1", + "@typescript-eslint/types": "^8.46.1", "debug": "^4.3.4" }, "engines": { @@ -2431,14 +2431,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.46.0", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-8.46.0.tgz", - "integrity": "sha512-lWETPa9XGcBes4jqAMYD9fW0j4n6hrPtTJwWDmtqgFO/4HF4jmdH/Q6wggTw5qIT5TXjKzbt7GsZUBnWoO3dqw==", + "version": "8.46.1", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-8.46.1.tgz", + "integrity": "sha512-weL9Gg3/5F0pVQKiF8eOXFZp8emqWzZsOJuWRUNtHT+UNV2xSJegmpCNQHy37aEQIbToTq7RHKhWvOsmbM680A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.46.0", - "@typescript-eslint/visitor-keys": "8.46.0" + "@typescript-eslint/types": "8.46.1", + "@typescript-eslint/visitor-keys": "8.46.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2449,9 +2449,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.46.0", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.0.tgz", - "integrity": "sha512-WrYXKGAHY836/N7zoK/kzi6p8tXFhasHh8ocFL9VZSAkvH956gfeRfcnhs3xzRy8qQ/dq3q44v1jvQieMFg2cw==", + "version": "8.46.1", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.1.tgz", + "integrity": "sha512-X88+J/CwFvlJB+mK09VFqx5FE4H5cXD+H/Bdza2aEWkSb8hnWIQorNcscRl4IEo1Cz9VI/+/r/jnGWkbWPx54g==", "dev": true, "license": "MIT", "engines": { @@ -2466,15 +2466,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.46.0", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-8.46.0.tgz", - "integrity": "sha512-hy+lvYV1lZpVs2jRaEYvgCblZxUoJiPyCemwbQZ+NGulWkQRy0HRPYAoef/CNSzaLt+MLvMptZsHXHlkEilaeg==", + "version": "8.46.1", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-8.46.1.tgz", + "integrity": "sha512-+BlmiHIiqufBxkVnOtFwjah/vrkF4MtKKvpXrKSPLCkCtAp8H01/VV43sfqA98Od7nJpDcFnkwgyfQbOG0AMvw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.46.0", - "@typescript-eslint/typescript-estree": "8.46.0", - "@typescript-eslint/utils": "8.46.0", + "@typescript-eslint/types": "8.46.1", + "@typescript-eslint/typescript-estree": "8.46.1", + "@typescript-eslint/utils": "8.46.1", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, @@ -2491,9 +2491,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.46.0", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/types/-/types-8.46.0.tgz", - "integrity": "sha512-bHGGJyVjSE4dJJIO5yyEWt/cHyNwga/zXGJbJJ8TiO01aVREK6gCTu3L+5wrkb1FbDkQ+TKjMNe9R/QQQP9+rA==", + "version": "8.46.1", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/types/-/types-8.46.1.tgz", + "integrity": "sha512-C+soprGBHwWBdkDpbaRC4paGBrkIXxVlNohadL5o0kfhsXqOC6GYH2S/Obmig+I0HTDl8wMaRySwrfrXVP8/pQ==", "dev": true, "license": "MIT", "engines": { @@ -2505,16 +2505,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.46.0", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.0.tgz", - "integrity": "sha512-ekDCUfVpAKWJbRfm8T1YRrCot1KFxZn21oV76v5Fj4tr7ELyk84OS+ouvYdcDAwZL89WpEkEj2DKQ+qg//+ucg==", + "version": "8.46.1", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.1.tgz", + "integrity": "sha512-uIifjT4s8cQKFQ8ZBXXyoUODtRoAd7F7+G8MKmtzj17+1UbdzFl52AzRyZRyKqPHhgzvXunnSckVu36flGy8cg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.46.0", - "@typescript-eslint/tsconfig-utils": "8.46.0", - "@typescript-eslint/types": "8.46.0", - "@typescript-eslint/visitor-keys": "8.46.0", + "@typescript-eslint/project-service": "8.46.1", + "@typescript-eslint/tsconfig-utils": "8.46.1", + "@typescript-eslint/types": "8.46.1", + "@typescript-eslint/visitor-keys": "8.46.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -2560,16 +2560,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.46.0", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-8.46.0.tgz", - "integrity": "sha512-nD6yGWPj1xiOm4Gk0k6hLSZz2XkNXhuYmyIrOWcHoPuAhjT9i5bAG+xbWPgFeNR8HPHHtpNKdYUXJl/D3x7f5g==", + "version": "8.46.1", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-8.46.1.tgz", + "integrity": "sha512-vkYUy6LdZS7q1v/Gxb2Zs7zziuXN0wxqsetJdeZdRe/f5dwJFglmuvZBfTUivCtjH725C1jWCDfpadadD95EDQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.46.0", - "@typescript-eslint/types": "8.46.0", - "@typescript-eslint/typescript-estree": "8.46.0" + "@typescript-eslint/scope-manager": "8.46.1", + "@typescript-eslint/types": "8.46.1", + "@typescript-eslint/typescript-estree": "8.46.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2584,13 +2584,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.46.0", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.0.tgz", - "integrity": "sha512-FrvMpAK+hTbFy7vH5j1+tMYHMSKLE6RzluFJlkFNKD0p9YsUT75JlBSmr5so3QRzvMwU5/bIEdeNrxm8du8l3Q==", + "version": "8.46.1", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.1.tgz", + "integrity": "sha512-ptkmIf2iDkNUjdeu2bQqhFPV1m6qTnFFjg7PPDjxKWaMaP0Z6I9l30Jr3g5QqbZGdw8YdYvLp+XnqnWWZOg/NA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.46.0", + "@typescript-eslint/types": "8.46.1", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -3997,25 +3997,24 @@ } }, "node_modules/eslint": { - "version": "9.37.0", - "resolved": "https://registry.npmmirror.com/eslint/-/eslint-9.37.0.tgz", - "integrity": "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==", + "version": "9.38.0", + "resolved": "https://registry.npmmirror.com/eslint/-/eslint-9.38.0.tgz", + "integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.0", - "@eslint/config-helpers": "^0.4.0", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.1", "@eslint/core": "^0.16.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.37.0", + "@eslint/js": "9.38.0", "@eslint/plugin-kit": "^0.4.0", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", @@ -4058,9 +4057,9 @@ } }, "node_modules/eslint-plugin-vue": { - "version": "10.5.0", - "resolved": "https://registry.npmmirror.com/eslint-plugin-vue/-/eslint-plugin-vue-10.5.0.tgz", - "integrity": "sha512-7BZHsG3kC2vei8F2W8hnfDi9RK+cv5eKPMvzBdrl8Vuc0hR5odGQRli8VVzUkrmUHkxFEm4Iio1r5HOKslO0Aw==", + "version": "10.5.1", + "resolved": "https://registry.npmmirror.com/eslint-plugin-vue/-/eslint-plugin-vue-10.5.1.tgz", + "integrity": "sha512-SbR9ZBUFKgvWAbq3RrdCtWaW0IKm6wwUiApxf3BVTNfqUIo4IQQmreMg2iHFJJ6C/0wss3LXURBJ1OwS/MhFcQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6652,16 +6651,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.46.0", - "resolved": "https://registry.npmmirror.com/typescript-eslint/-/typescript-eslint-8.46.0.tgz", - "integrity": "sha512-6+ZrB6y2bT2DX3K+Qd9vn7OFOJR+xSLDj+Aw/N3zBwUt27uTw2sw2TE2+UcY1RiyBZkaGbTkVg9SSdPNUG6aUw==", + "version": "8.46.1", + "resolved": "https://registry.npmmirror.com/typescript-eslint/-/typescript-eslint-8.46.1.tgz", + "integrity": "sha512-VHgijW803JafdSsDO8I761r3SHrgk4T00IdyQ+/UsthtgPRsBWQLqoSxOolxTpxRKi1kGXK0bSz4CoAc9ObqJA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.46.0", - "@typescript-eslint/parser": "8.46.0", - "@typescript-eslint/typescript-estree": "8.46.0", - "@typescript-eslint/utils": "8.46.0" + "@typescript-eslint/eslint-plugin": "8.46.1", + "@typescript-eslint/parser": "8.46.1", + "@typescript-eslint/typescript-estree": "8.46.1", + "@typescript-eslint/utils": "8.46.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -7029,9 +7028,9 @@ "license": "MIT" }, "node_modules/vite": { - "version": "7.1.9", - "resolved": "https://registry.npmmirror.com/vite/-/vite-7.1.9.tgz", - "integrity": "sha512-4nVGliEpxmhCL8DslSAUdxlB6+SMrhB0a1v5ijlh1xB1nEPuy1mxaHxysVucLHuWryAxLWg6a5ei+U4TLn/rFg==", + "version": "7.1.10", + "resolved": "https://registry.npmmirror.com/vite/-/vite-7.1.10.tgz", + "integrity": "sha512-CmuvUBzVJ/e3HGxhg6cYk88NGgTnBoOo7ogtfJJ0fefUWAxN/WDSUa50o+oVBxuIhO8FoEZW0j2eW7sfjs5EtA==", "dev": true, "license": "MIT", "dependencies": { @@ -7250,9 +7249,9 @@ } }, "node_modules/vue-router": { - "version": "4.5.1", - "resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.5.1.tgz", - "integrity": "sha512-ogAF3P97NPm8fJsE4by9dwSYtDwXIY1nFY9T6DyQnGHd1E2Da94w9JIolpe42LJGIl0DwOHBi8TcRPlPGwbTtw==", + "version": "4.6.3", + "resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.6.3.tgz", + "integrity": "sha512-ARBedLm9YlbvQomnmq91Os7ck6efydTSpRP3nuOKCvgJOHNrhRoJDSKtee8kcL1Vf7nz6U+PMBL+hTvR3bTVQg==", "license": "MIT", "dependencies": { "@vue/devtools-api": "^6.6.4" @@ -7261,7 +7260,7 @@ "url": "https://github.com/sponsors/posva" }, "peerDependencies": { - "vue": "^3.2.0" + "vue": "^3.5.0" } }, "node_modules/vue-router/node_modules/@vue/devtools-api": { diff --git a/frontend/package.json b/frontend/package.json index d230c12..a849bc7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -41,9 +41,9 @@ "@codemirror/lint": "^6.9.0", "@codemirror/search": "^6.5.11", "@codemirror/state": "^6.5.2", - "@codemirror/view": "^6.38.5", + "@codemirror/view": "^6.38.6", "@cospaia/prettier-plugin-clojure": "^0.0.2", - "@lezer/highlight": "^1.2.1", + "@lezer/highlight": "^1.2.2", "@lezer/lr": "^1.4.2", "@prettier/plugin-xml": "^3.4.2", "@replit/codemirror-lang-svelte": "^6.0.0", @@ -67,23 +67,23 @@ "vue": "^3.5.22", "vue-i18n": "^11.1.12", "vue-pick-colors": "^1.8.0", - "vue-router": "^4.5.1" + "vue-router": "^4.6.3" }, "devDependencies": { - "@eslint/js": "^9.37.0", + "@eslint/js": "^9.38.0", "@lezer/generator": "^1.8.0", - "@types/node": "^24.7.1", + "@types/node": "^24.8.1", "@types/remarkable": "^2.0.8", "@vitejs/plugin-vue": "^6.0.1", "@wailsio/runtime": "latest", "cross-env": "^10.1.0", - "eslint": "^9.37.0", - "eslint-plugin-vue": "^10.5.0", + "eslint": "^9.38.0", + "eslint-plugin-vue": "^10.5.1", "globals": "^16.4.0", "typescript": "^5.9.3", - "typescript-eslint": "^8.46.0", + "typescript-eslint": "^8.46.1", "unplugin-vue-components": "^29.1.0", - "vite": "^7.1.9", + "vite": "^7.1.10", "vite-plugin-node-polyfills": "^0.24.0", "vue-eslint-parser": "^10.2.0", "vue-tsc": "^3.1.1" diff --git a/frontend/src/i18n/locales/en-US.ts b/frontend/src/i18n/locales/en-US.ts index 0bcbaeb..f5dc5c6 100644 --- a/frontend/src/i18n/locales/en-US.ts +++ b/frontend/src/i18n/locales/en-US.ts @@ -166,12 +166,17 @@ export default { interface: 'Interface Elements', border: 'Borders & Dividers', search: 'Search & Matching', + // Base Colors background: 'Main Background', backgroundSecondary: 'Secondary Background', surface: 'Panel Background', + dropdownBackground: 'Dropdown Background', + dropdownBorder: 'Dropdown Border', + // Text Colors foreground: 'Primary Text', foregroundSecondary: 'Secondary Text', comment: 'Comments', + // Syntax Highlighting - Core keyword: 'Keywords', string: 'Strings', function: 'Functions', @@ -179,14 +184,25 @@ export default { operator: 'Operators', variable: 'Variables', type: 'Types', + // Syntax Highlighting - Extended + constant: 'Constants', + storage: 'Storage Type', + parameter: 'Parameters', + class: 'Class Names', + heading: 'Headings', + invalid: 'Invalid/Error', + regexp: 'Regular Expressions', + // Interface Elements cursor: 'Cursor', selection: 'Selection Background', selectionBlur: 'Unfocused Selection', activeLine: 'Active Line Highlight', lineNumber: 'Line Numbers', activeLineNumber: 'Active Line Number', + // Borders & Dividers borderColor: 'Border Color', borderLight: 'Light Border', + // Search & Matching searchMatch: 'Search Match', matchingBracket: 'Matching Bracket' }, @@ -199,6 +215,7 @@ export default { enableTabIndent: 'Enable Tab Indent', language: 'Interface Language', systemTheme: 'System Theme', + presetTheme: 'Preset Theme', saveOptions: 'Save Options', autoSaveDelay: 'Auto Save Delay (ms)', updateSettings: 'Update Settings', diff --git a/frontend/src/i18n/locales/zh-CN.ts b/frontend/src/i18n/locales/zh-CN.ts index 8292539..1577c78 100644 --- a/frontend/src/i18n/locales/zh-CN.ts +++ b/frontend/src/i18n/locales/zh-CN.ts @@ -166,6 +166,7 @@ export default { enableTabIndent: '启用 Tab 缩进', language: '界面语言', systemTheme: '系统主题', + presetTheme: '预设主题', saveOptions: '保存选项', autoSaveDelay: '自动保存延迟(毫秒)', updateSettings: '更新设置', @@ -206,12 +207,17 @@ export default { interface: '界面元素', border: '边框分割线', search: '搜索匹配', + // 基础色调 background: '主背景色', backgroundSecondary: '次要背景色', surface: '面板背景', + dropdownBackground: '下拉菜单背景', + dropdownBorder: '下拉菜单边框', + // 文本颜色 foreground: '主文本色', foregroundSecondary: '次要文本色', comment: '注释色', + // 语法高亮 - 核心 keyword: '关键字', string: '字符串', function: '函数名', @@ -219,14 +225,25 @@ export default { operator: '操作符', variable: '变量', type: '类型', + // 语法高亮 - 扩展 + constant: '常量', + storage: '存储类型', + parameter: '参数', + class: '类名', + heading: '标题', + invalid: '无效内容', + regexp: '正则表达式', + // 界面元素 cursor: '光标', selection: '选中背景', selectionBlur: '失焦选中背景', activeLine: '当前行高亮', lineNumber: '行号', activeLineNumber: '活动行号', + // 边框和分割线 borderColor: '边框色', borderLight: '浅色边框', + // 搜索和匹配 searchMatch: '搜索匹配', matchingBracket: '匹配括号' }, diff --git a/frontend/src/stores/themeStore.ts b/frontend/src/stores/themeStore.ts index 9809b1b..c7d0ad5 100644 --- a/frontend/src/stores/themeStore.ts +++ b/frontend/src/stores/themeStore.ts @@ -1,33 +1,59 @@ import { defineStore } from 'pinia'; -import { computed, reactive } from 'vue'; -import { SystemThemeType, ThemeType, ThemeColorConfig } from '@/../bindings/voidraft/internal/models/models'; +import { computed, reactive, ref } from 'vue'; +import {SystemThemeType, ThemeType, Theme, ThemeColorConfig} from '@/../bindings/voidraft/internal/models/models'; import { ThemeService } from '@/../bindings/voidraft/internal/services'; import { useConfigStore } from './configStore'; import { useEditorStore } from './editorStore'; -import { defaultDarkColors } from '@/views/editor/theme/dark'; -import { defaultLightColors } from '@/views/editor/theme/light'; +import type { ThemeColors } from '@/views/editor/theme/types'; +import { getThemeConfig } from '@/views/editor/theme/registry'; /** * 主题管理 Store - * 职责:管理主题状态和颜色配置 + * 职责:管理主题状态、颜色配置和预设主题列表 */ export const useThemeStore = defineStore('theme', () => { const configStore = useConfigStore(); - // 响应式状态 - const themeColors = reactive({ - darkTheme: { ...defaultDarkColors }, - lightTheme: { ...defaultLightColors } + // 所有主题列表(从数据库加载) + const allThemes = ref([]); + + // 当前选中的主题 ID + const currentThemeIds = reactive({ + dark: 0, // 当前深色主题ID + light: 0, // 当前浅色主题ID }); - // 计算属性 + // 当前主题的颜色配置(用于编辑器渲染) + const currentColors = reactive<{ + dark: ThemeColors | null; + light: ThemeColors | null; + }>({ + dark: null, + light: null, + }); + + // 计算属性:当前系统主题模式 const currentTheme = computed(() => configStore.config?.appearance?.systemTheme || SystemThemeType.SystemThemeAuto ); - // 获取默认主题颜色 - const getDefaultColors = (themeType: ThemeType) => - themeType === ThemeType.ThemeTypeDark ? defaultDarkColors : defaultLightColors; + // 计算属性:根据类型获取主题列表 + const darkThemes = computed(() => + allThemes.value.filter(t => t.type === ThemeType.ThemeTypeDark) + ); + + const lightThemes = computed(() => + allThemes.value.filter(t => t.type === ThemeType.ThemeTypeLight) + ); + + // 计算属性:获取当前激活的主题对象 + const activeTheme = computed(() => { + const isDark = currentTheme.value === SystemThemeType.SystemThemeDark || + (currentTheme.value === SystemThemeType.SystemThemeAuto && + window.matchMedia('(prefers-color-scheme: dark)').matches); + + return isDark ? currentColors.dark : currentColors.light; + }); // 应用主题到 DOM const applyThemeToDOM = (theme: SystemThemeType) => { @@ -39,30 +65,77 @@ export const useThemeStore = defineStore('theme', () => { document.documentElement.setAttribute('data-theme', themeMap[theme]); }; - // 初始化主题颜色 + // 从数据库加载所有主题 + const loadAllThemes = async () => { + try { + const themes = await ThemeService.GetAllThemes(); + allThemes.value = (themes || []).filter((t): t is Theme => t !== null); + return allThemes.value; + } catch (error) { + console.error('Failed to load themes from database:', error); + allThemes.value = []; + return []; + } + }; + + // 根据主题对象加载颜色配置 + const loadThemeColors = (theme: Theme): ThemeColors => { + // 优先使用数据库中的颜色配置 + const dbColors = theme.colors as unknown as ThemeColors; + + // 如果数据库颜色不完整,尝试从预设主题获取 + if (!dbColors || Object.keys(dbColors).length < 10) { + const presetConfig = getThemeConfig(theme.name); + if (presetConfig) { + return presetConfig; + } + } + + return dbColors; + }; + + // 初始化主题颜色(加载默认主题) const initializeThemeColors = async () => { try { - const themes = await ThemeService.GetDefaultThemes(); + // 加载所有主题 + await loadAllThemes(); - // 如果没有获取到主题数据,使用默认值 - if (!themes) { - Object.assign(themeColors.darkTheme, defaultDarkColors); - Object.assign(themeColors.lightTheme, defaultLightColors); - return; + // 查找默认主题 + const defaultDark = allThemes.value.find( + t => t.type === ThemeType.ThemeTypeDark && t.isDefault + ); + const defaultLight = allThemes.value.find( + t => t.type === ThemeType.ThemeTypeLight && t.isDefault + ); + + // 设置默认主题 + if (defaultDark) { + currentThemeIds.dark = defaultDark.id; + currentColors.dark = loadThemeColors(defaultDark); } - - // 更新主题颜色 - if (themes[ThemeType.ThemeTypeDark]) { - Object.assign(themeColors.darkTheme, themes[ThemeType.ThemeTypeDark].colors); + + if (defaultLight) { + currentThemeIds.light = defaultLight.id; + currentColors.light = loadThemeColors(defaultLight); } - if (themes[ThemeType.ThemeTypeLight]) { - Object.assign(themeColors.lightTheme, themes[ThemeType.ThemeTypeLight].colors); + + // 如果没有找到默认主题,使用第一个可用主题 + if (!currentColors.dark && darkThemes.value.length > 0) { + const fallback = darkThemes.value[0]; + currentThemeIds.dark = fallback.id; + currentColors.dark = loadThemeColors(fallback); + } + + if (!currentColors.light && lightThemes.value.length > 0) { + const fallback = lightThemes.value[0]; + currentThemeIds.light = fallback.id; + currentColors.light = loadThemeColors(fallback); } } catch (error) { - console.warn('Failed to load themes from database, using defaults:', error); - // 如果数据库加载失败,使用默认主题 - Object.assign(themeColors.darkTheme, defaultDarkColors); - Object.assign(themeColors.lightTheme, defaultLightColors); + console.error('Failed to initialize theme colors:', error); + // 使用预设主题作为后备 + currentColors.dark = getThemeConfig('default-dark'); + currentColors.light = getThemeConfig('default-light'); } }; @@ -73,77 +146,109 @@ export const useThemeStore = defineStore('theme', () => { await initializeThemeColors(); }; - // 设置主题 + // 设置系统主题模式(深色/浅色/自动) const setTheme = async (theme: SystemThemeType) => { await configStore.setSystemTheme(theme); applyThemeToDOM(theme); refreshEditorTheme(); }; - // 更新主题颜色 - 合并逻辑,减少重复代码 - const updateThemeColors = (darkColors?: any, lightColors?: any): boolean => { - let hasChanges = false; - - const updateColors = (target: any, source: any) => { - if (!source) return false; + // 切换到指定的预设主题(通过主题ID) + const switchToTheme = async (themeId: number) => { + try { + const theme = allThemes.value.find(t => t.id === themeId); + if (!theme) { + console.error('Theme not found:', themeId); + return false; + } - let changed = false; - Object.entries(source).forEach(([key, value]) => { - if (value !== undefined && target[key] !== value) { - target[key] = value; - changed = true; - } - }); - return changed; - }; - - hasChanges = updateColors(themeColors.darkTheme, darkColors) || hasChanges; - hasChanges = updateColors(themeColors.lightTheme, lightColors) || hasChanges; - - return hasChanges; + // 加载主题颜色 + const colors = loadThemeColors(theme); + + // 根据主题类型更新对应的颜色配置 + if (theme.type === ThemeType.ThemeTypeDark) { + currentThemeIds.dark = themeId; + currentColors.dark = colors; + } else { + currentThemeIds.light = themeId; + currentColors.light = colors; + } + + // 刷新编辑器主题 + refreshEditorTheme(); + return true; + } catch (error) { + console.error('Failed to switch theme:', error); + return false; + } }; - // 保存主题颜色到数据库 - const saveThemeColors = async () => { + // 更新当前主题的颜色配置 + const updateCurrentColors = (colors: Partial, isDark: boolean) => { + const target = isDark ? currentColors.dark : currentColors.light; + if (!target) return; + + Object.assign(target, colors); + }; + + // 保存当前主题颜色到数据库 + const saveCurrentTheme = async (isDark: boolean) => { try { - const darkColors = ThemeColorConfig.createFrom(themeColors.darkTheme); - const lightColors = ThemeColorConfig.createFrom(themeColors.lightTheme); + const themeId = isDark ? currentThemeIds.dark : currentThemeIds.light; + const colors = isDark ? currentColors.dark : currentColors.light; - await Promise.all([ - ThemeService.UpdateThemeColors(ThemeType.ThemeTypeDark, darkColors), - ThemeService.UpdateThemeColors(ThemeType.ThemeTypeLight, lightColors) - ]); + if (!themeId || !colors) { + throw new Error('No theme selected'); + } + + // 转换为数据库格式并保存 + const dbColors = colors as ThemeColorConfig; // ThemeColorConfig from bindings + await ThemeService.UpdateTheme(themeId, dbColors); + + return true; } catch (error) { - console.error('Failed to save theme colors:', error); + console.error('Failed to save theme:', error); throw error; } }; - // 重置主题颜色 - const resetThemeColors = async (themeType: 'darkTheme' | 'lightTheme') => { + // 重置当前主题为预设配置 + const resetCurrentTheme = async (isDark: boolean) => { try { - const dbThemeType = themeType === 'darkTheme' ? ThemeType.ThemeTypeDark : ThemeType.ThemeTypeLight; + const themeId = isDark ? currentThemeIds.dark : currentThemeIds.light; - // 1. 调用后端重置服务 - await ThemeService.ResetThemeColors(dbThemeType); + if (!themeId) { + throw new Error('No theme selected'); + } - // 2. 更新内存中的颜色状态 - const defaultColors = getDefaultColors(dbThemeType); - Object.assign(themeColors[themeType], defaultColors); + // 调用后端重置服务 + await ThemeService.ResetTheme(themeId); - // 3. 刷新编辑器主题 + // 重新加载主题 + await loadAllThemes(); + const theme = allThemes.value.find(t => t.id === themeId); + + if (theme) { + const colors = loadThemeColors(theme); + if (isDark) { + currentColors.dark = colors; + } else { + currentColors.light = colors; + } + } + + // 刷新编辑器主题 refreshEditorTheme(); return true; } catch (error) { - console.error('Failed to reset theme colors:', error); + console.error('Failed to reset theme:', error); return false; } }; // 刷新编辑器主题 const refreshEditorTheme = () => { - // 使用当前主题重新应用DOM主题 applyThemeToDOM(currentTheme.value); const editorStore = useEditorStore(); @@ -151,14 +256,24 @@ export const useThemeStore = defineStore('theme', () => { }; return { + // 状态 + allThemes, + darkThemes, + lightThemes, currentTheme, - themeColors, + currentThemeIds, + currentColors, + activeTheme, + + // 方法 setTheme, + switchToTheme, initializeTheme, + loadAllThemes, + updateCurrentColors, + saveCurrentTheme, + resetCurrentTheme, + refreshEditorTheme, applyThemeToDOM, - updateThemeColors, - saveThemeColors, - resetThemeColors, - refreshEditorTheme }; }); \ No newline at end of file diff --git a/frontend/src/views/editor/basic/themeExtension.ts b/frontend/src/views/editor/basic/themeExtension.ts index aebd5ff..745511e 100644 --- a/frontend/src/views/editor/basic/themeExtension.ts +++ b/frontend/src/views/editor/basic/themeExtension.ts @@ -1,8 +1,7 @@ import { Extension, Compartment } from '@codemirror/state'; import { EditorView } from '@codemirror/view'; import { SystemThemeType } from '@/../bindings/voidraft/internal/models/models'; -import { createDarkTheme } from '@/views/editor/theme/dark'; -import { createLightTheme } from '@/views/editor/theme/light'; +import { createThemeByColors } from '@/views/editor/theme/registry'; import { useThemeStore } from '@/stores/themeStore'; // 主题区间 - 用于动态切换主题 @@ -11,23 +10,25 @@ export const themeCompartment = new Compartment(); /** * 根据主题类型获取主题扩展 */ -const getThemeExtension = (themeType: SystemThemeType): Extension => { +const getThemeExtension = (themeType: SystemThemeType): Extension | null => { const themeStore = useThemeStore(); // 处理 auto 主题类型 - let actualTheme: SystemThemeType = themeType; + let isDark = themeType === SystemThemeType.SystemThemeDark; if (themeType === SystemThemeType.SystemThemeAuto) { - actualTheme = window.matchMedia('(prefers-color-scheme: dark)').matches - ? SystemThemeType.SystemThemeDark - : SystemThemeType.SystemThemeLight; + isDark = window.matchMedia('(prefers-color-scheme: dark)').matches; } - // 根据主题类型创建主题 - if (actualTheme === SystemThemeType.SystemThemeLight) { - return createLightTheme(themeStore.themeColors.lightTheme); - } else { - return createDarkTheme(themeStore.themeColors.darkTheme); + // 根据主题类型获取对应的颜色配置 + const colors = isDark ? themeStore.currentColors.dark : themeStore.currentColors.light; + + if (!colors) { + console.warn('Theme colors not loaded yet'); + return null; } + + // 使用颜色配置创建主题 + return createThemeByColors(colors); }; /** @@ -35,6 +36,12 @@ const getThemeExtension = (themeType: SystemThemeType): Extension => { */ export const createThemeExtension = (themeType: SystemThemeType = SystemThemeType.SystemThemeDark): Extension => { const extension = getThemeExtension(themeType); + + // 如果主题未加载,返回空扩展 + if (!extension) { + return themeCompartment.of([]); + } + return themeCompartment.of(extension); }; @@ -48,6 +55,13 @@ export const updateEditorTheme = (view: EditorView, themeType: SystemThemeType): try { const extension = getThemeExtension(themeType); + + // 如果主题未加载,不更新 + if (!extension) { + console.warn('Cannot update theme: theme not loaded'); + return; + } + view.dispatch({ effects: themeCompartment.reconfigure(extension) }); diff --git a/frontend/src/views/editor/theme/dark.ts b/frontend/src/views/editor/theme/base.ts similarity index 57% rename from frontend/src/views/editor/theme/dark.ts rename to frontend/src/views/editor/theme/base.ts index fcb3905..f57410c 100644 --- a/frontend/src/views/editor/theme/dark.ts +++ b/frontend/src/views/editor/theme/base.ts @@ -1,48 +1,17 @@ import {EditorView} from '@codemirror/view'; import {HighlightStyle, syntaxHighlighting} from '@codemirror/language'; import {tags} from '@lezer/highlight'; +import {Extension} from '@codemirror/state'; +import type {ThemeColors} from './types'; -// 默认深色主题颜色 -export const defaultDarkColors = { - // 基础色调 - background: '#252B37', // 主背景色 - backgroundSecondary: '#213644', // 次要背景色 - surface: '#474747', // 面板背景 - - // 文本颜色 - foreground: '#9BB586', // 主文本色 - foregroundSecondary: '#9c9c9c', // 次要文本色 - comment: '#6272a4', // 注释色 - - // 语法高亮色 - keyword: '#ff79c6', // 关键字 - string: '#f1fa8c', // 字符串 - function: '#50fa7b', // 函数名 - number: '#bd93f9', // 数字 - operator: '#ff79c6', // 操作符 - variable: '#8fbcbb', // 变量 - type: '#8be9fd', // 类型 - - // 界面元素 - cursor: '#ffffff', // 光标 - selection: '#0865a9', // 选中背景 - selectionBlur: '#225377', // 失焦选中背景 - activeLine: '#ffffff0a', // 当前行高亮 - lineNumber: '#ffffff26', // 行号 - activeLineNumber: '#ffffff99', // 活动行号 - - // 边框和分割线 - borderColor: '#1e222a', // 边框色 - borderLight: '#ffffff19', // 浅色边框 - - // 搜索和匹配 - searchMatch: '#8fbcbb', // 搜索匹配 - matchingBracket: '#ffffff19', // 匹配括号 -}; - -// 创建深色主题 -export function createDarkTheme(colors = defaultDarkColors) { - const darkTheme = EditorView.theme({ +/** + * 创建通用主题 + * @param colors 主题颜色配置 + * @returns CodeMirror Extension数组 + */ +export function createBaseTheme(colors: ThemeColors): Extension { + // 编辑器主题样式 + const theme = EditorView.theme({ '&': { color: colors.foreground, backgroundColor: colors.background, @@ -79,6 +48,9 @@ export function createDarkTheme(colors = defaultDarkColors) { '&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground': { backgroundColor: colors.selection, }, + '.cm-content ::selection': { + backgroundColor: colors.selection, + }, '.cm-activeLine.code-empty-block-selected': { backgroundColor: colors.selection, }, @@ -90,9 +62,10 @@ export function createDarkTheme(colors = defaultDarkColors) { // 行号区域 '.cm-gutters': { - backgroundColor: 'rgba(0,0,0, 0.1)', + backgroundColor: colors.dark ? 'rgba(0,0,0, 0.1)' : 'rgba(0,0,0, 0.04)', color: colors.lineNumber, border: 'none', + borderRight: colors.dark ? 'none' : `1px solid ${colors.borderLight}`, padding: '0 2px 0 4px', userSelect: 'none', }, @@ -115,9 +88,20 @@ export function createDarkTheme(colors = defaultDarkColors) { '.cm-foldPlaceholder': { backgroundColor: 'transparent', border: 'none', - color: '#ddd', + color: colors.comment, }, + // 面板 + '.cm-panels': { + backgroundColor: colors.dropdownBackground, + color: colors.foreground + }, + '.cm-panels.cm-panels-top': { + borderBottom: '2px solid black' + }, + '.cm-panels.cm-panels-bottom': { + borderTop: '2px solid black' + }, // 搜索匹配 '.cm-searchMatch': { @@ -125,11 +109,11 @@ export function createDarkTheme(colors = defaultDarkColors) { outline: `1px solid ${colors.searchMatch}`, }, '.cm-searchMatch.cm-searchMatch-selected': { - backgroundColor: colors.foreground, + backgroundColor: colors.searchMatch, color: colors.background, }, '.cm-selectionMatch': { - backgroundColor: '#50606D', + backgroundColor: colors.dark ? '#50606D' : '#e6f3ff', }, // 括号匹配 @@ -141,7 +125,7 @@ export function createDarkTheme(colors = defaultDarkColors) { color: 'inherit', }, '&.cm-focused .cm-nonmatchingBracket': { - outline: '0.5px solid #bc8f8f', + outline: colors.dark ? '0.5px solid #bc8f8f' : '0.5px solid #d73a49', }, // 编辑器焦点 @@ -151,8 +135,10 @@ export function createDarkTheme(colors = defaultDarkColors) { // 工具提示 '.cm-tooltip': { - border: 'none', + border: colors.dark ? 'none' : `1px solid ${colors.dropdownBorder}`, backgroundColor: colors.surface, + color: colors.foreground, + boxShadow: colors.dark ? 'none' : '0 2px 8px rgba(0,0,0,0.1)', }, '.cm-tooltip .cm-tooltip-arrow:before': { borderTopColor: 'transparent', @@ -169,7 +155,7 @@ export function createDarkTheme(colors = defaultDarkColors) { }, }, - // 代码块层 + // 代码块层(自定义) '.code-blocks-layer': { width: '100%', }, @@ -188,34 +174,36 @@ export function createDarkTheme(colors = defaultDarkColors) { background: colors.backgroundSecondary, borderTop: `1px solid ${colors.borderColor}`, }, - '.code-blocks-math-result': { - paddingLeft: "12px", - position: "relative", - }, - ".code-blocks-math-result .inner": { - background: "#0e1217", - color: "#a0e7c7", - padding: '0px 4px', - borderRadius: '2px', - boxShadow: '0 0 3px rgba(0,0,0, 0.3)', - cursor: 'pointer', - whiteSpace: "nowrap", - }, - '.code-blocks-math-result-copied': { - position: "absolute", - top: "0px", - left: "0px", - marginLeft: "calc(100% + 10px)", - width: "60px", - transition: "opacity 500ms", - transitionDelay: "1000ms", - color: "rgba(220,240,230, 1.0)", - }, - '.code-blocks-math-result-copied.fade-out': { - opacity: 0, - }, - // 代码块开始标记 + // 数学计算结果(自定义) + '.code-blocks-math-result': { + paddingLeft: "12px", + position: "relative", + }, + ".code-blocks-math-result .inner": { + background: colors.dark ? '#0e1217' : '#48b57e', + color: colors.dark ? '#a0e7c7' : '#fff', + padding: '0px 4px', + borderRadius: '2px', + boxShadow: colors.dark ? '0 0 3px rgba(0,0,0, 0.3)' : '0 0 3px rgba(0,0,0, 0.1)', + cursor: 'pointer', + whiteSpace: "nowrap", + }, + '.code-blocks-math-result-copied': { + position: "absolute", + top: "0px", + left: "0px", + marginLeft: "calc(100% + 10px)", + width: "60px", + transition: "opacity 500ms", + transitionDelay: "1000ms", + color: colors.dark ? 'rgba(220,240,230, 1.0)' : 'rgba(0,0,0, 0.8)', + }, + '.code-blocks-math-result-copied.fade-out': { + opacity: 0, + }, + + // 代码块开始标记(自定义) '.code-block-start': { height: '12px', position: 'relative', @@ -223,47 +211,79 @@ export function createDarkTheme(colors = defaultDarkColors) { '.code-block-start.first': { height: '0px', }, - }, {dark: true}); + }, {dark: colors.dark}); // 语法高亮样式 - const darkHighlightStyle = HighlightStyle.define([ + const highlightStyle = HighlightStyle.define([ + // 关键字 {tag: tags.keyword, color: colors.keyword}, - {tag: [tags.name, tags.deleted, tags.character, tags.propertyName, tags.macroName], color: colors.variable}, - {tag: [tags.variableName], color: colors.variable}, - {tag: [tags.function(tags.variableName)], color: colors.function}, - {tag: [tags.labelName], color: colors.operator}, - {tag: [tags.color, tags.constant(tags.name), tags.standard(tags.name)], color: colors.keyword}, - {tag: [tags.definition(tags.name), tags.separator], color: colors.function}, - {tag: [tags.brace], color: colors.variable}, - {tag: [tags.annotation], color: '#d30102'}, - {tag: [tags.number, tags.changed, tags.annotation, tags.modifier, tags.self, tags.namespace], color: colors.number}, - {tag: [tags.typeName, tags.className], color: colors.type}, + + // 操作符 {tag: [tags.operator, tags.operatorKeyword], color: colors.operator}, - {tag: [tags.tagName], color: colors.number}, - {tag: [tags.squareBracket], color: '#bf616a'}, - {tag: [tags.angleBracket], color: '#d08770'}, - {tag: [tags.attributeName], color: colors.variable}, - {tag: [tags.regexp], color: colors.string}, + + // 名称、变量 + {tag: [tags.name, tags.deleted, tags.character, tags.macroName], color: colors.variable}, + {tag: [tags.variableName], color: colors.variable}, + {tag: [tags.labelName], color: colors.operator}, + {tag: [tags.atom, tags.bool, tags.special(tags.variableName)], color: colors.variable}, + + // 函数 + {tag: [tags.function(tags.variableName)], color: colors.function}, + {tag: [tags.propertyName], color: colors.function}, + + // 类型、类 + {tag: [tags.typeName], color: colors.type}, + {tag: [tags.className], color: colors.class}, + + // 常量 + {tag: [tags.color, tags.constant(tags.name), tags.standard(tags.name)], color: colors.constant}, + + // 字符串 + {tag: [tags.processingInstruction, tags.string, tags.inserted], color: colors.string}, + {tag: [tags.special(tags.string)], color: colors.string}, {tag: [tags.quote], color: colors.comment}, - {tag: [tags.string], color: colors.string}, - {tag: tags.link, color: colors.variable, textDecoration: 'underline'}, - {tag: [tags.url, tags.escape, tags.special(tags.string)], color: colors.string}, - {tag: [tags.meta], color: colors.comment}, - {tag: [tags.comment], color: colors.comment, fontStyle: 'italic'}, + + // 数字 + {tag: [tags.number, tags.changed, tags.annotation, tags.modifier, tags.self, tags.namespace], color: colors.number}, + + // 正则表达式 + {tag: [tags.url, tags.escape, tags.regexp, tags.link], color: colors.regexp}, + + // 注释 + {tag: [tags.meta, tags.comment], color: colors.comment, fontStyle: 'italic'}, + + // 分隔符、括号 + {tag: [tags.definition(tags.name), tags.separator], color: colors.variable}, + {tag: [tags.brace], color: colors.variable}, + {tag: [tags.squareBracket], color: colors.dark ? '#bf616a' : colors.keyword}, + {tag: [tags.angleBracket], color: colors.dark ? '#d08770' : colors.operator}, + {tag: [tags.attributeName], color: colors.variable}, + + // 标签 + {tag: [tags.tagName], color: colors.number}, + + // 注解 + {tag: [tags.annotation], color: colors.invalid}, + + // 特殊样式 {tag: tags.strong, fontWeight: 'bold'}, {tag: tags.emphasis, fontStyle: 'italic'}, {tag: tags.strikethrough, textDecoration: 'line-through'}, - {tag: tags.heading, fontWeight: 'bold', color: colors.keyword}, + {tag: tags.link, color: colors.variable, textDecoration: 'underline'}, + + // 标题 + {tag: tags.heading, fontWeight: 'bold', color: colors.heading}, {tag: [tags.heading1, tags.heading2], fontSize: '1.4em'}, {tag: [tags.heading3, tags.heading4], fontSize: '1.2em'}, {tag: [tags.heading5, tags.heading6], fontSize: '1.1em'}, + + // 无效内容 + {tag: tags.invalid, color: colors.invalid}, ]); return [ - darkTheme, - syntaxHighlighting(darkHighlightStyle), + theme, + syntaxHighlighting(highlightStyle), ]; } -// 默认深色主题 -export const dark = createDarkTheme(defaultDarkColors); \ No newline at end of file diff --git a/frontend/src/views/editor/theme/dark/aura.ts b/frontend/src/views/editor/theme/dark/aura.ts index b119b77..4a168c2 100644 --- a/frontend/src/views/editor/theme/dark/aura.ts +++ b/frontend/src/views/editor/theme/dark/aura.ts @@ -1,128 +1,57 @@ -import {EditorView} from '@codemirror/view' import {Extension} from '@codemirror/state' -import {HighlightStyle, syntaxHighlighting} from '@codemirror/language' -import {tags as t} from '@lezer/highlight' +import {createBaseTheme} from '../base' +import type {ThemeColors} from '../types' -export const config = { +export const config: ThemeColors = { name: 'aura', dark: true, + + // 基础色调 background: '#21202e', - foreground: '#edecee', - selection: '#3d375e7f', - cursor: '#a277ff', + backgroundSecondary: '#21202e', + surface: '#21202e', dropdownBackground: '#21202e', dropdownBorder: '#3b334b', - activeLine: '#4d4b6622', - lineNumber: '#a394f033', - activeLineNumber: '#cdccce', - matchingBracket: '#a394f033', - keyword: '#a277ff', - storage: '#a277ff', - variable: '#edecee', - parameter: '#edecee', - function: '#ffca85', - string: '#61ffca', - constant: '#61ffca', - type: '#82e2ff', - class: '#82e2ff', - number: '#61ffca', + + // 文本颜色 + foreground: '#edecee', + foregroundSecondary: '#edecee', comment: '#6d6d6d', + + // 语法高亮色 - 核心 + keyword: '#a277ff', + string: '#61ffca', + function: '#ffca85', + number: '#61ffca', + operator: '#a277ff', + variable: '#edecee', + type: '#82e2ff', + + // 语法高亮色 - 扩展 + constant: '#61ffca', + storage: '#a277ff', + parameter: '#edecee', + class: '#82e2ff', heading: '#a277ff', invalid: '#ff6767', regexp: '#61ffca', + + // 界面元素 + cursor: '#a277ff', + selection: '#3d375e7f', + selectionBlur: '#3d375e7f', + activeLine: '#4d4b6622', + lineNumber: '#a394f033', + activeLineNumber: '#cdccce', + + // 边框和分割线 + borderColor: '#3b334b', + borderLight: '#edecee19', + + // 搜索和匹配 + searchMatch: '#61ffca', + matchingBracket: '#a394f033', } -export const auraTheme = EditorView.theme({ - '&': { - color: config.foreground, - backgroundColor: config.background, - }, - - '.cm-content': {caretColor: config.cursor}, - - '.cm-cursor, .cm-dropCursor': {borderLeftColor: config.cursor}, - '&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection': {backgroundColor: config.selection}, - - '.cm-panels': {backgroundColor: config.dropdownBackground, color: config.foreground}, - '.cm-panels.cm-panels-top': {borderBottom: '2px solid black'}, - '.cm-panels.cm-panels-bottom': {borderTop: '2px solid black'}, - - '.cm-searchMatch': { - backgroundColor: config.dropdownBackground, - outline: `1px solid ${config.dropdownBorder}` - }, - '.cm-searchMatch.cm-searchMatch-selected': { - backgroundColor: config.selection - }, - - '.cm-activeLine': {backgroundColor: config.activeLine}, - '.cm-selectionMatch': {backgroundColor: config.selection}, - - '&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': { - backgroundColor: config.matchingBracket, - outline: 'none' - }, - - '.cm-gutters': { - backgroundColor: config.background, - color: config.foreground, - border: 'none' - }, - '.cm-activeLineGutter': {backgroundColor: config.background}, - - '.cm-lineNumbers .cm-gutterElement': {color: config.lineNumber}, - '.cm-lineNumbers .cm-activeLineGutter': {color: config.activeLineNumber}, - - '.cm-foldPlaceholder': { - backgroundColor: 'transparent', - border: 'none', - color: config.foreground - }, - '.cm-tooltip': { - border: `1px solid ${config.dropdownBorder}`, - backgroundColor: config.dropdownBackground, - color: config.foreground, - }, - '.cm-tooltip .cm-tooltip-arrow:before': { - borderTopColor: 'transparent', - borderBottomColor: 'transparent' - }, - '.cm-tooltip .cm-tooltip-arrow:after': { - borderTopColor: config.foreground, - borderBottomColor: config.foreground, - }, - '.cm-tooltip-autocomplete': { - '& > ul > li[aria-selected]': { - background: config.selection, - color: config.foreground, - } - } -}, {dark: config.dark}) - -export const auraHighlightStyle = HighlightStyle.define([ - {tag: t.keyword, color: config.keyword}, - {tag: [t.name, t.deleted, t.character, t.macroName], color: config.variable}, - {tag: [t.propertyName], color: config.function}, - {tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: config.string}, - {tag: [t.function(t.variableName), t.labelName], color: config.function}, - {tag: [t.color, t.constant(t.name), t.standard(t.name)], color: config.constant}, - {tag: [t.definition(t.name), t.separator], color: config.variable}, - {tag: [t.className], color: config.class}, - {tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: config.number}, - {tag: [t.typeName], color: config.type, fontStyle: config.type}, - {tag: [t.operator, t.operatorKeyword], color: config.keyword}, - {tag: [t.url, t.escape, t.regexp, t.link], color: config.regexp}, - {tag: [t.meta, t.comment], color: config.comment}, - {tag: t.strong, fontWeight: 'bold'}, - {tag: t.emphasis, fontStyle: 'italic'}, - {tag: t.link, textDecoration: 'underline'}, - {tag: t.heading, fontWeight: 'bold', color: config.heading}, - {tag: [t.atom, t.bool, t.special(t.variableName)], color: config.variable}, - {tag: t.invalid, color: config.invalid}, - {tag: t.strikethrough, textDecoration: 'line-through'}, -]) - -export const aura: Extension = [ - auraTheme, - syntaxHighlighting(auraHighlightStyle), -] +// 使用通用主题工厂函数创建 Aura 主题 +export const aura: Extension = createBaseTheme(config) diff --git a/frontend/src/views/editor/theme/dark/default-dark.ts b/frontend/src/views/editor/theme/dark/default-dark.ts new file mode 100644 index 0000000..5522b5c --- /dev/null +++ b/frontend/src/views/editor/theme/dark/default-dark.ts @@ -0,0 +1,63 @@ +import {createBaseTheme} from '../base'; +import type {ThemeColors} from '../types'; + +// 默认深色主题颜色 +export const defaultDarkColors: ThemeColors = { + // 主题信息 + name: 'default-dark', + dark: true, + + // 基础色调 + background: '#252B37', // 主背景色 + backgroundSecondary: '#213644', // 次要背景色 + surface: '#474747', // 面板背景 + dropdownBackground: '#252B37', // 下拉菜单背景 + dropdownBorder: '#ffffff19', // 下拉菜单边框 + + // 文本颜色 + foreground: '#9BB586', // 主文本色 + foregroundSecondary: '#9c9c9c', // 次要文本色 + comment: '#6272a4', // 注释色 + + // 语法高亮色 - 核心 + keyword: '#ff79c6', // 关键字 + string: '#f1fa8c', // 字符串 + function: '#50fa7b', // 函数名 + number: '#bd93f9', // 数字 + operator: '#ff79c6', // 操作符 + variable: '#8fbcbb', // 变量 + type: '#8be9fd', // 类型 + + // 语法高亮色 - 扩展 + constant: '#bd93f9', // 常量 + storage: '#ff79c6', // 存储类型 + parameter: '#8fbcbb', // 参数 + class: '#8be9fd', // 类名 + heading: '#ff79c6', // 标题 + invalid: '#d30102', // 无效内容 + regexp: '#f1fa8c', // 正则表达式 + + // 界面元素 + cursor: '#ffffff', // 光标 + selection: '#0865a9', // 选中背景 + selectionBlur: '#225377', // 失焦选中背景 + activeLine: '#ffffff0a', // 当前行高亮 + lineNumber: '#ffffff26', // 行号 + activeLineNumber: '#ffffff99', // 活动行号 + + // 边框和分割线 + borderColor: '#1e222a', // 边框色 + borderLight: '#ffffff19', // 浅色边框 + + // 搜索和匹配 + searchMatch: '#8fbcbb', // 搜索匹配 + matchingBracket: '#ffffff19', // 匹配括号 +}; + +// 创建深色主题 +export function createDarkTheme(colors: ThemeColors = defaultDarkColors) { + return createBaseTheme({...colors, dark: true}); +} + +// 默认深色主题 +export const defaultDark = createDarkTheme(defaultDarkColors); \ No newline at end of file diff --git a/frontend/src/views/editor/theme/dark/dracula.ts b/frontend/src/views/editor/theme/dark/dracula.ts index ee89c4c..7418b78 100644 --- a/frontend/src/views/editor/theme/dark/dracula.ts +++ b/frontend/src/views/editor/theme/dark/dracula.ts @@ -1,128 +1,57 @@ -import {EditorView} from '@codemirror/view' import {Extension} from '@codemirror/state' -import {HighlightStyle, syntaxHighlighting} from '@codemirror/language' -import {tags as t} from '@lezer/highlight' +import {createBaseTheme} from '../base' +import type {ThemeColors} from '../types' -export const config = { +export const config: ThemeColors = { name: 'dracula', dark: true, + + // 基础色调 background: '#282A36', - foreground: '#F8F8F2', - selection: '#44475A', - cursor: '#F8F8F2', + backgroundSecondary: '#282A36', + surface: '#282A36', dropdownBackground: '#282A36', dropdownBorder: '#191A21', - activeLine: '#53576c22', - lineNumber: '#6272A4', - activeLineNumber: '#F8F8F2', - matchingBracket: '#44475A', - keyword: '#FF79C6', - storage: '#FF79C6', - variable: '#F8F8F2', - parameter: '#F8F8F2', - function: '#50FA7B', - string: '#F1FA8C', - constant: '#BD93F9', - type: '#8BE9FD', - class: '#8BE9FD', - number: '#BD93F9', + + // 文本颜色 + foreground: '#F8F8F2', + foregroundSecondary: '#F8F8F2', comment: '#6272A4', + + // 语法高亮色 - 核心 + keyword: '#FF79C6', + string: '#F1FA8C', + function: '#50FA7B', + number: '#BD93F9', + operator: '#FF79C6', + variable: '#F8F8F2', + type: '#8BE9FD', + + // 语法高亮色 - 扩展 + constant: '#BD93F9', + storage: '#FF79C6', + parameter: '#F8F8F2', + class: '#8BE9FD', heading: '#BD93F9', invalid: '#FF5555', regexp: '#F1FA8C', + + // 界面元素 + cursor: '#F8F8F2', + selection: '#44475A', + selectionBlur: '#44475A', + activeLine: '#53576c22', + lineNumber: '#6272A4', + activeLineNumber: '#F8F8F2', + + // 边框和分割线 + borderColor: '#191A21', + borderLight: '#F8F8F219', + + // 搜索和匹配 + searchMatch: '#50FA7B', + matchingBracket: '#44475A', } -export const draculaTheme = EditorView.theme({ - '&': { - color: config.foreground, - backgroundColor: config.background, - }, - - '.cm-content': {caretColor: config.cursor}, - - '.cm-cursor, .cm-dropCursor': {borderLeftColor: config.cursor}, - '&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection': {backgroundColor: config.selection}, - - '.cm-panels': {backgroundColor: config.dropdownBackground, color: config.foreground}, - '.cm-panels.cm-panels-top': {borderBottom: '2px solid black'}, - '.cm-panels.cm-panels-bottom': {borderTop: '2px solid black'}, - - '.cm-searchMatch': { - backgroundColor: config.dropdownBackground, - outline: `1px solid ${config.dropdownBorder}` - }, - '.cm-searchMatch.cm-searchMatch-selected': { - backgroundColor: config.selection - }, - - '.cm-activeLine': {backgroundColor: config.activeLine}, - '.cm-selectionMatch': {backgroundColor: config.selection}, - - '&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': { - backgroundColor: config.matchingBracket, - outline: 'none' - }, - - '.cm-gutters': { - backgroundColor: config.background, - color: config.foreground, - border: 'none' - }, - '.cm-activeLineGutter': {backgroundColor: config.background}, - - '.cm-lineNumbers .cm-gutterElement': {color: config.lineNumber}, - '.cm-lineNumbers .cm-activeLineGutter': {color: config.activeLineNumber}, - - '.cm-foldPlaceholder': { - backgroundColor: 'transparent', - border: 'none', - color: config.foreground - }, - '.cm-tooltip': { - border: `1px solid ${config.dropdownBorder}`, - backgroundColor: config.dropdownBackground, - color: config.foreground, - }, - '.cm-tooltip .cm-tooltip-arrow:before': { - borderTopColor: 'transparent', - borderBottomColor: 'transparent' - }, - '.cm-tooltip .cm-tooltip-arrow:after': { - borderTopColor: config.foreground, - borderBottomColor: config.foreground, - }, - '.cm-tooltip-autocomplete': { - '& > ul > li[aria-selected]': { - background: config.selection, - color: config.foreground, - } - } -}, {dark: config.dark}) - -export const draculaHighlightStyle = HighlightStyle.define([ - {tag: t.keyword, color: config.keyword}, - {tag: [t.name, t.deleted, t.character, t.macroName], color: config.variable}, - {tag: [t.propertyName], color: config.function}, - {tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: config.string}, - {tag: [t.function(t.variableName), t.labelName], color: config.function}, - {tag: [t.color, t.constant(t.name), t.standard(t.name)], color: config.constant}, - {tag: [t.definition(t.name), t.separator], color: config.variable}, - {tag: [t.className], color: config.class}, - {tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: config.number}, - {tag: [t.typeName], color: config.type, fontStyle: config.type}, - {tag: [t.operator, t.operatorKeyword], color: config.keyword}, - {tag: [t.url, t.escape, t.regexp, t.link], color: config.regexp}, - {tag: [t.meta, t.comment], color: config.comment}, - {tag: t.strong, fontWeight: 'bold'}, - {tag: t.emphasis, fontStyle: 'italic'}, - {tag: t.link, textDecoration: 'underline'}, - {tag: t.heading, fontWeight: 'bold', color: config.heading}, - {tag: [t.atom, t.bool, t.special(t.variableName)], color: config.variable}, - {tag: t.invalid, color: config.invalid}, - {tag: t.strikethrough, textDecoration: 'line-through'}, -]) - -export const dracula: Extension = [ - draculaTheme, - syntaxHighlighting(draculaHighlightStyle), -] +// 使用通用主题工厂函数创建 Dracula 主题 +export const dracula: Extension = createBaseTheme(config) diff --git a/frontend/src/views/editor/theme/dark/github-dark.ts b/frontend/src/views/editor/theme/dark/github-dark.ts index e16d9ad..8a8c106 100644 --- a/frontend/src/views/editor/theme/dark/github-dark.ts +++ b/frontend/src/views/editor/theme/dark/github-dark.ts @@ -1,128 +1,57 @@ -import {EditorView, lineNumbers} from '@codemirror/view' import {Extension} from '@codemirror/state' -import {HighlightStyle, syntaxHighlighting} from '@codemirror/language' -import {tags as t} from '@lezer/highlight' +import {createBaseTheme} from '../base' +import type {ThemeColors} from '../types' -export const config = { - name: 'githubDark', +export const config: ThemeColors = { + name: 'github-dark', dark: true, + + // 基础色调 background: '#24292e', - foreground: '#d1d5da', - selection: '#3392FF44', - cursor: '#c8e1ff', + backgroundSecondary: '#24292e', + surface: '#24292e', dropdownBackground: '#24292e', dropdownBorder: '#1b1f23', - activeLine: '#4d566022', - lineNumber: '#444d56', - activeLineNumber: '#e1e4e8', - matchingBracket: '#17E5E650', - keyword: '#f97583', - storage: '#f97583', - variable: '#ffab70', - parameter: '#e1e4e8', - function: '#79b8ff', - string: '#9ecbff', - constant: '#79b8ff', - type: '#79b8ff', - class: '#b392f0', - number: '#79b8ff', + + // 文本颜色 + foreground: '#d1d5da', + foregroundSecondary: '#d1d5da', comment: '#6a737d', + + // 语法高亮色 - 核心 + keyword: '#f97583', + string: '#9ecbff', + function: '#79b8ff', + number: '#79b8ff', + operator: '#f97583', + variable: '#ffab70', + type: '#79b8ff', + + // 语法高亮色 - 扩展 + constant: '#79b8ff', + storage: '#f97583', + parameter: '#e1e4e8', + class: '#b392f0', heading: '#79b8ff', invalid: '#f97583', regexp: '#9ecbff', + + // 界面元素 + cursor: '#c8e1ff', + selection: '#3392FF44', + selectionBlur: '#3392FF44', + activeLine: '#4d566022', + lineNumber: '#444d56', + activeLineNumber: '#e1e4e8', + + // 边框和分割线 + borderColor: '#1b1f23', + borderLight: '#d1d5da19', + + // 搜索和匹配 + searchMatch: '#79b8ff', + matchingBracket: '#17E5E650', } -export const githubDarkTheme = EditorView.theme({ - '&': { - color: config.foreground, - backgroundColor: config.background, - }, - - '.cm-content': {caretColor: config.cursor}, - - '.cm-cursor, .cm-dropCursor': {borderLeftColor: config.cursor}, - '&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection': {backgroundColor: config.selection}, - - '.cm-panels': {backgroundColor: config.dropdownBackground, color: config.foreground}, - '.cm-panels.cm-panels-top': {borderBottom: '2px solid black'}, - '.cm-panels.cm-panels-bottom': {borderTop: '2px solid black'}, - - '.cm-searchMatch': { - backgroundColor: config.dropdownBackground, - outline: `1px solid ${config.dropdownBorder}` - }, - '.cm-searchMatch.cm-searchMatch-selected': { - backgroundColor: config.selection - }, - - '.cm-activeLine': {backgroundColor: config.activeLine}, - '.cm-selectionMatch': {backgroundColor: config.selection}, - - '&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': { - backgroundColor: config.matchingBracket, - outline: 'none' - }, - - '.cm-gutters': { - backgroundColor: config.background, - color: config.foreground, - border: 'none' - }, - '.cm-activeLineGutter': {backgroundColor: config.background}, - - '.cm-lineNumbers .cm-gutterElement': {color: config.lineNumber}, - '.cm-lineNumbers .cm-activeLineGutter': {color: config.activeLineNumber}, - - '.cm-foldPlaceholder': { - backgroundColor: 'transparent', - border: 'none', - color: config.foreground - }, - '.cm-tooltip': { - border: `1px solid ${config.dropdownBorder}`, - backgroundColor: config.dropdownBackground, - color: config.foreground, - }, - '.cm-tooltip .cm-tooltip-arrow:before': { - borderTopColor: 'transparent', - borderBottomColor: 'transparent' - }, - '.cm-tooltip .cm-tooltip-arrow:after': { - borderTopColor: config.foreground, - borderBottomColor: config.foreground, - }, - '.cm-tooltip-autocomplete': { - '& > ul > li[aria-selected]': { - background: config.selection, - color: config.foreground, - } - } -}, {dark: config.dark}) - -export const githubDarkHighlightStyle = HighlightStyle.define([ - {tag: t.keyword, color: config.keyword}, - {tag: [t.name, t.deleted, t.character, t.macroName], color: config.variable}, - {tag: [t.propertyName], color: config.function}, - {tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: config.string}, - {tag: [t.function(t.variableName), t.labelName], color: config.function}, - {tag: [t.color, t.constant(t.name), t.standard(t.name)], color: config.constant}, - {tag: [t.definition(t.name), t.separator], color: config.variable}, - {tag: [t.className], color: config.class}, - {tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: config.number}, - {tag: [t.typeName], color: config.type, fontStyle: config.type}, - {tag: [t.operator, t.operatorKeyword], color: config.keyword}, - {tag: [t.url, t.escape, t.regexp, t.link], color: config.regexp}, - {tag: [t.meta, t.comment], color: config.comment}, - {tag: t.strong, fontWeight: 'bold'}, - {tag: t.emphasis, fontStyle: 'italic'}, - {tag: t.link, textDecoration: 'underline'}, - {tag: t.heading, fontWeight: 'bold', color: config.heading}, - {tag: [t.atom, t.bool, t.special(t.variableName)], color: config.variable}, - {tag: t.invalid, color: config.invalid}, - {tag: t.strikethrough, textDecoration: 'line-through'}, -]) - -export const githubDark: Extension = [ - githubDarkTheme, - syntaxHighlighting(githubDarkHighlightStyle), -] +// 使用通用主题工厂函数创建 GitHub Dark 主题 +export const githubDark: Extension = createBaseTheme(config) diff --git a/frontend/src/views/editor/theme/dark/material-dark.ts b/frontend/src/views/editor/theme/dark/material-dark.ts index dd85f16..b91128f 100644 --- a/frontend/src/views/editor/theme/dark/material-dark.ts +++ b/frontend/src/views/editor/theme/dark/material-dark.ts @@ -1,128 +1,57 @@ -import {EditorView} from '@codemirror/view' import {Extension} from '@codemirror/state' -import {HighlightStyle, syntaxHighlighting} from '@codemirror/language' -import {tags as t} from '@lezer/highlight' +import {createBaseTheme} from '../base' +import type {ThemeColors} from '../types' -export const config = { - name: 'materialDark', +export const config: ThemeColors = { + name: 'material-dark', dark: true, + + // 基础色调 background: '#263238', - foreground: '#EEFFFF', - selection: '#80CBC420', - cursor: '#FFCC00', + backgroundSecondary: '#263238', + surface: '#263238', dropdownBackground: '#263238', dropdownBorder: '#FFFFFF10', - activeLine: '#4c616c22', - lineNumber: '#37474F', - activeLineNumber: '#607a86', - matchingBracket: '#263238', - keyword: '#C792EA', - storage: '#C792EA', - variable: '#EEFFFF', - parameter: '#EEFFFF', - function: '#82AAFF', - string: '#C3E88D', - constant: '#F78C6C', - type: '#B2CCD6', - class: '#FFCB6B', - number: '#F78C6C', + + // 文本颜色 + foreground: '#EEFFFF', + foregroundSecondary: '#EEFFFF', comment: '#546E7A', + + // 语法高亮色 - 核心 + keyword: '#C792EA', + string: '#C3E88D', + function: '#82AAFF', + number: '#F78C6C', + operator: '#C792EA', + variable: '#EEFFFF', + type: '#B2CCD6', + + // 语法高亮色 - 扩展 + constant: '#F78C6C', + storage: '#C792EA', + parameter: '#EEFFFF', + class: '#FFCB6B', heading: '#C3E88D', invalid: '#FF5370', regexp: '#89DDFF', + + // 界面元素 + cursor: '#FFCC00', + selection: '#80CBC420', + selectionBlur: '#80CBC420', + activeLine: '#4c616c22', + lineNumber: '#37474F', + activeLineNumber: '#607a86', + + // 边框和分割线 + borderColor: '#FFFFFF10', + borderLight: '#EEFFFF19', + + // 搜索和匹配 + searchMatch: '#82AAFF', + matchingBracket: '#263238', } -export const materialDarkTheme = EditorView.theme({ - '&': { - color: config.foreground, - backgroundColor: config.background, - }, - - '.cm-content': {caretColor: config.cursor}, - - '.cm-cursor, .cm-dropCursor': {borderLeftColor: config.cursor}, - '&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection': {backgroundColor: config.selection}, - - '.cm-panels': {backgroundColor: config.dropdownBackground, color: config.foreground}, - '.cm-panels.cm-panels-top': {borderBottom: '2px solid black'}, - '.cm-panels.cm-panels-bottom': {borderTop: '2px solid black'}, - - '.cm-searchMatch': { - backgroundColor: config.dropdownBackground, - outline: `1px solid ${config.dropdownBorder}` - }, - '.cm-searchMatch.cm-searchMatch-selected': { - backgroundColor: config.selection - }, - - '.cm-activeLine': {backgroundColor: config.activeLine}, - '.cm-selectionMatch': {backgroundColor: config.selection}, - - '&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': { - backgroundColor: config.matchingBracket, - outline: 'none' - }, - - '.cm-gutters': { - backgroundColor: config.background, - color: config.foreground, - border: 'none' - }, - '.cm-activeLineGutter': {backgroundColor: config.background}, - - '.cm-lineNumbers .cm-gutterElement': {color: config.lineNumber}, - '.cm-lineNumbers .cm-activeLineGutter': {color: config.activeLineNumber}, - - '.cm-foldPlaceholder': { - backgroundColor: 'transparent', - border: 'none', - color: config.foreground - }, - '.cm-tooltip': { - border: `1px solid ${config.dropdownBorder}`, - backgroundColor: config.dropdownBackground, - color: config.foreground, - }, - '.cm-tooltip .cm-tooltip-arrow:before': { - borderTopColor: 'transparent', - borderBottomColor: 'transparent' - }, - '.cm-tooltip .cm-tooltip-arrow:after': { - borderTopColor: config.foreground, - borderBottomColor: config.foreground, - }, - '.cm-tooltip-autocomplete': { - '& > ul > li[aria-selected]': { - background: config.selection, - color: config.foreground, - } - } -}, {dark: config.dark}) - -export const materialDarkHighlightStyle = HighlightStyle.define([ - {tag: t.keyword, color: config.keyword}, - {tag: [t.name, t.deleted, t.character, t.macroName], color: config.variable}, - {tag: [t.propertyName], color: config.function}, - {tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: config.string}, - {tag: [t.function(t.variableName), t.labelName], color: config.function}, - {tag: [t.color, t.constant(t.name), t.standard(t.name)], color: config.constant}, - {tag: [t.definition(t.name), t.separator], color: config.variable}, - {tag: [t.className], color: config.class}, - {tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: config.number}, - {tag: [t.typeName], color: config.type, fontStyle: config.type}, - {tag: [t.operator, t.operatorKeyword], color: config.keyword}, - {tag: [t.url, t.escape, t.regexp, t.link], color: config.regexp}, - {tag: [t.meta, t.comment], color: config.comment}, - {tag: t.strong, fontWeight: 'bold'}, - {tag: t.emphasis, fontStyle: 'italic'}, - {tag: t.link, textDecoration: 'underline'}, - {tag: t.heading, fontWeight: 'bold', color: config.heading}, - {tag: [t.atom, t.bool, t.special(t.variableName)], color: config.variable}, - {tag: t.invalid, color: config.invalid}, - {tag: t.strikethrough, textDecoration: 'line-through'}, -]) - -export const materialDark: Extension = [ - materialDarkTheme, - syntaxHighlighting(materialDarkHighlightStyle), -] +// 使用通用主题工厂函数创建 Material Dark 主题 +export const materialDark: Extension = createBaseTheme(config) diff --git a/frontend/src/views/editor/theme/dark/one-dark.ts b/frontend/src/views/editor/theme/dark/one-dark.ts index 4b9b764..ad79045 100644 --- a/frontend/src/views/editor/theme/dark/one-dark.ts +++ b/frontend/src/views/editor/theme/dark/one-dark.ts @@ -1,7 +1,6 @@ -import {EditorView} from "@codemirror/view" import {Extension} from "@codemirror/state" -import {HighlightStyle, syntaxHighlighting} from "@codemirror/language" -import {tags as t} from "@lezer/highlight" +import {createBaseTheme} from '../base' +import type {ThemeColors} from '../types' // Using https://github.com/one-dark/vscode-one-dark-theme/ as reference for the colors @@ -22,133 +21,56 @@ const chalky = "#e5c07b", selection = "#3E4451", cursor = "#528bff" -/// The colors used in the theme, as CSS color strings. -export const color = { - chalky, - coral, - cyan, - invalid, - ivory, - stone, - malibu, - sage, - whiskey, - violet, - darkBackground, - highlightBackground, - background, - tooltipBackground, - selection, - cursor +export const config: ThemeColors = { + name: 'one-dark', + dark: true, + + // 基础色调 + background: background, + backgroundSecondary: highlightBackground, + surface: tooltipBackground, + dropdownBackground: darkBackground, + dropdownBorder: stone, + + // 文本颜色 + foreground: ivory, + foregroundSecondary: stone, + comment: stone, + + // 语法高亮色 - 核心 + keyword: violet, + string: sage, + function: malibu, + number: chalky, + operator: cyan, + variable: coral, + type: chalky, + + // 语法高亮色 - 扩展 + constant: whiskey, + storage: violet, + parameter: coral, + class: chalky, + heading: coral, + invalid: invalid, + regexp: cyan, + + // 界面元素 + cursor: cursor, + selection: selection, + selectionBlur: selection, + activeLine: '#6699ff0b', + lineNumber: stone, + activeLineNumber: ivory, + + // 边框和分割线 + borderColor: darkBackground, + borderLight: ivory + '19', + + // 搜索和匹配 + searchMatch: malibu, + matchingBracket: '#bad0f847', } -/// The editor theme styles for One Dark. -export const oneDarkTheme = EditorView.theme({ - "&": { - color: ivory, - backgroundColor: background - }, - - ".cm-content": { - caretColor: cursor - }, - - ".cm-cursor, .cm-dropCursor": {borderLeftColor: cursor}, - "&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection": {backgroundColor: selection}, - - ".cm-panels": {backgroundColor: darkBackground, color: ivory}, - ".cm-panels.cm-panels-top": {borderBottom: "2px solid black"}, - ".cm-panels.cm-panels-bottom": {borderTop: "2px solid black"}, - - ".cm-searchMatch": { - backgroundColor: "#72a1ff59", - outline: "1px solid #457dff" - }, - ".cm-searchMatch.cm-searchMatch-selected": { - backgroundColor: "#6199ff2f" - }, - - ".cm-activeLine": {backgroundColor: "#6699ff0b"}, - ".cm-selectionMatch": {backgroundColor: "#aafe661a"}, - - "&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket": { - backgroundColor: "#bad0f847" - }, - - ".cm-gutters": { - backgroundColor: background, - color: stone, - border: "none" - }, - - ".cm-activeLineGutter": { - backgroundColor: highlightBackground - }, - - ".cm-foldPlaceholder": { - backgroundColor: "transparent", - border: "none", - color: "#ddd" - }, - - ".cm-tooltip": { - border: "none", - backgroundColor: tooltipBackground - }, - ".cm-tooltip .cm-tooltip-arrow:before": { - borderTopColor: "transparent", - borderBottomColor: "transparent" - }, - ".cm-tooltip .cm-tooltip-arrow:after": { - borderTopColor: tooltipBackground, - borderBottomColor: tooltipBackground - }, - ".cm-tooltip-autocomplete": { - "& > ul > li[aria-selected]": { - backgroundColor: highlightBackground, - color: ivory - } - } -}, {dark: true}) - -/// The highlighting style for code in the One Dark theme. -export const oneDarkHighlightStyle = HighlightStyle.define([ - {tag: t.keyword, - color: violet}, - {tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName], - color: coral}, - {tag: [t.function(t.variableName), t.labelName], - color: malibu}, - {tag: [t.color, t.constant(t.name), t.standard(t.name)], - color: whiskey}, - {tag: [t.definition(t.name), t.separator], - color: ivory}, - {tag: [t.typeName, t.className, t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], - color: chalky}, - {tag: [t.operator, t.operatorKeyword, t.url, t.escape, t.regexp, t.link, t.special(t.string)], - color: cyan}, - {tag: [t.meta, t.comment], - color: stone}, - {tag: t.strong, - fontWeight: "bold"}, - {tag: t.emphasis, - fontStyle: "italic"}, - {tag: t.strikethrough, - textDecoration: "line-through"}, - {tag: t.link, - color: stone, - textDecoration: "underline"}, - {tag: t.heading, - fontWeight: "bold", - color: coral}, - {tag: [t.atom, t.bool, t.special(t.variableName)], - color: whiskey }, - {tag: [t.processingInstruction, t.string, t.inserted], - color: sage}, - {tag: t.invalid, - color: invalid}, -]) - -/// Extension to enable the One Dark theme (both the editor theme and -/// the highlight style). -export const oneDark: Extension = [oneDarkTheme, syntaxHighlighting(oneDarkHighlightStyle)] \ No newline at end of file +// 使用通用主题工厂函数创建 One Dark 主题 +export const oneDark: Extension = createBaseTheme(config) \ No newline at end of file diff --git a/frontend/src/views/editor/theme/dark/solarized-dark.ts b/frontend/src/views/editor/theme/dark/solarized-dark.ts index 07ddc3a..59646fb 100644 --- a/frontend/src/views/editor/theme/dark/solarized-dark.ts +++ b/frontend/src/views/editor/theme/dark/solarized-dark.ts @@ -1,128 +1,57 @@ -import {EditorView} from '@codemirror/view' import {Extension} from '@codemirror/state' -import {HighlightStyle, syntaxHighlighting} from '@codemirror/language' -import {tags as t} from '@lezer/highlight' +import {createBaseTheme} from '../base' +import type {ThemeColors} from '../types' -export const config = { - name: 'solarizedDark', +export const config: ThemeColors = { + name: 'solarized-dark', dark: true, + + // 基础色调 background: '#002B36', - foreground: '#93A1A1', - selection: '#274642', - cursor: '#D30102', + backgroundSecondary: '#002B36', + surface: '#002B36', dropdownBackground: '#002B36', dropdownBorder: '#2AA19899', - activeLine: '#005b7022', - lineNumber: '#93A1A1', - activeLineNumber: '#949494', - matchingBracket: '#073642', - keyword: '#859900', - storage: '#93A1A1', - variable: '#268BD2', - parameter: '#268BD2', - function: '#268BD2', - string: '#2AA198', - constant: '#CB4B16', - type: '#CB4B16', - class: '#CB4B16', - number: '#D33682', + + // 文本颜色 + foreground: '#93A1A1', + foregroundSecondary: '#93A1A1', comment: '#586E75', + + // 语法高亮色 - 核心 + keyword: '#859900', + string: '#2AA198', + function: '#268BD2', + number: '#D33682', + operator: '#859900', + variable: '#268BD2', + type: '#CB4B16', + + // 语法高亮色 - 扩展 + constant: '#CB4B16', + storage: '#93A1A1', + parameter: '#268BD2', + class: '#CB4B16', heading: '#268BD2', invalid: '#DC322F', regexp: '#DC322F', + + // 界面元素 + cursor: '#D30102', + selection: '#274642', + selectionBlur: '#274642', + activeLine: '#005b7022', + lineNumber: '#93A1A1', + activeLineNumber: '#949494', + + // 边框和分割线 + borderColor: '#073642', + borderLight: '#93A1A119', + + // 搜索和匹配 + searchMatch: '#2AA198', + matchingBracket: '#073642', } -export const solarizedDarkTheme = EditorView.theme({ - '&': { - color: config.foreground, - backgroundColor: config.background, - }, - - '.cm-content': {caretColor: config.cursor}, - - '.cm-cursor, .cm-dropCursor': {borderLeftColor: config.cursor}, - '&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection': {backgroundColor: config.selection}, - - '.cm-panels': {backgroundColor: config.dropdownBackground, color: config.foreground}, - '.cm-panels.cm-panels-top': {borderBottom: '2px solid black'}, - '.cm-panels.cm-panels-bottom': {borderTop: '2px solid black'}, - - '.cm-searchMatch': { - backgroundColor: config.dropdownBackground, - outline: `1px solid ${config.dropdownBorder}` - }, - '.cm-searchMatch.cm-searchMatch-selected': { - backgroundColor: config.selection - }, - - '.cm-activeLine': {backgroundColor: config.activeLine}, - '.cm-selectionMatch': {backgroundColor: config.selection}, - - '&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': { - backgroundColor: config.matchingBracket, - outline: 'none' - }, - - '.cm-gutters': { - backgroundColor: config.background, - color: config.foreground, - border: 'none' - }, - '.cm-activeLineGutter': {backgroundColor: config.background}, - - '.cm-lineNumbers .cm-gutterElement': {color: config.lineNumber}, - '.cm-lineNumbers .cm-activeLineGutter': {color: config.activeLineNumber}, - - '.cm-foldPlaceholder': { - backgroundColor: 'transparent', - border: 'none', - color: config.foreground - }, - '.cm-tooltip': { - border: `1px solid ${config.dropdownBorder}`, - backgroundColor: config.dropdownBackground, - color: config.foreground, - }, - '.cm-tooltip .cm-tooltip-arrow:before': { - borderTopColor: 'transparent', - borderBottomColor: 'transparent' - }, - '.cm-tooltip .cm-tooltip-arrow:after': { - borderTopColor: config.foreground, - borderBottomColor: config.foreground, - }, - '.cm-tooltip-autocomplete': { - '& > ul > li[aria-selected]': { - background: config.selection, - color: config.foreground, - } - } -}, {dark: config.dark}) - -export const solarizedDarkHighlightStyle = HighlightStyle.define([ - {tag: t.keyword, color: config.keyword}, - {tag: [t.name, t.deleted, t.character, t.macroName], color: config.variable}, - {tag: [t.propertyName], color: config.function}, - {tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: config.string}, - {tag: [t.function(t.variableName), t.labelName], color: config.function}, - {tag: [t.color, t.constant(t.name), t.standard(t.name)], color: config.constant}, - {tag: [t.definition(t.name), t.separator], color: config.variable}, - {tag: [t.className], color: config.class}, - {tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: config.number}, - {tag: [t.typeName], color: config.type, fontStyle: config.type}, - {tag: [t.operator, t.operatorKeyword], color: config.keyword}, - {tag: [t.url, t.escape, t.regexp, t.link], color: config.regexp}, - {tag: [t.meta, t.comment], color: config.comment}, - {tag: t.strong, fontWeight: 'bold'}, - {tag: t.emphasis, fontStyle: 'italic'}, - {tag: t.link, textDecoration: 'underline'}, - {tag: t.heading, fontWeight: 'bold', color: config.heading}, - {tag: [t.atom, t.bool, t.special(t.variableName)], color: config.variable}, - {tag: t.invalid, color: config.invalid}, - {tag: t.strikethrough, textDecoration: 'line-through'}, -]) - -export const solarizedDark: Extension = [ - solarizedDarkTheme, - syntaxHighlighting(solarizedDarkHighlightStyle), -] +// 使用通用主题工厂函数创建 Solarized Dark 主题 +export const solarizedDark: Extension = createBaseTheme(config) diff --git a/frontend/src/views/editor/theme/dark/tokyo-night-storm.ts b/frontend/src/views/editor/theme/dark/tokyo-night-storm.ts index d842d69..93f87bf 100644 --- a/frontend/src/views/editor/theme/dark/tokyo-night-storm.ts +++ b/frontend/src/views/editor/theme/dark/tokyo-night-storm.ts @@ -1,128 +1,57 @@ -import {EditorView} from '@codemirror/view' import {Extension} from '@codemirror/state' -import {HighlightStyle, syntaxHighlighting} from '@codemirror/language' -import {tags as t} from '@lezer/highlight' +import {createBaseTheme} from '../base' +import type {ThemeColors} from '../types' -export const config = { - name: 'tokyoNightStorm', +export const config: ThemeColors = { + name: 'tokyo-night-storm', dark: true, + + // 基础色调 background: '#24283b', - foreground: '#7982a9', - selection: '#6f7bb630', - cursor: '#c0caf5', + backgroundSecondary: '#24283b', + surface: '#24283b', dropdownBackground: '#24283b', dropdownBorder: '#7982a9', - activeLine: '#4d547722', - lineNumber: '#3b4261', - activeLineNumber: '#737aa2', - matchingBracket: '#1f2335', - keyword: '#bb9af7', - storage: '#bb9af7', - variable: '#c0caf5', - parameter: '#c0caf5', - function: '#7aa2f7', - string: '#9ece6a', - constant: '#bb9af7', - type: '#2ac3de', - class: '#c0caf5', - number: '#ff9e64', + + // 文本颜色 + foreground: '#7982a9', + foregroundSecondary: '#7982a9', comment: '#565f89', + + // 语法高亮色 - 核心 + keyword: '#bb9af7', + string: '#9ece6a', + function: '#7aa2f7', + number: '#ff9e64', + operator: '#bb9af7', + variable: '#c0caf5', + type: '#2ac3de', + + // 语法高亮色 - 扩展 + constant: '#bb9af7', + storage: '#bb9af7', + parameter: '#c0caf5', + class: '#c0caf5', heading: '#89ddff', invalid: '#ff5370', regexp: '#b4f9f8', + + // 界面元素 + cursor: '#c0caf5', + selection: '#6f7bb630', + selectionBlur: '#6f7bb630', + activeLine: '#4d547722', + lineNumber: '#3b4261', + activeLineNumber: '#737aa2', + + // 边框和分割线 + borderColor: '#1f2335', + borderLight: '#7982a919', + + // 搜索和匹配 + searchMatch: '#7aa2f7', + matchingBracket: '#1f2335', } -export const tokyoNightStormTheme = EditorView.theme({ - '&': { - color: config.foreground, - backgroundColor: config.background, - }, - - '.cm-content': {caretColor: config.cursor}, - - '.cm-cursor, .cm-dropCursor': {borderLeftColor: config.cursor}, - '&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection': {backgroundColor: config.selection}, - - '.cm-panels': {backgroundColor: config.dropdownBackground, color: config.foreground}, - '.cm-panels.cm-panels-top': {borderBottom: '2px solid black'}, - '.cm-panels.cm-panels-bottom': {borderTop: '2px solid black'}, - - '.cm-searchMatch': { - backgroundColor: config.dropdownBackground, - outline: `1px solid ${config.dropdownBorder}` - }, - '.cm-searchMatch.cm-searchMatch-selected': { - backgroundColor: config.selection - }, - - '.cm-activeLine': {backgroundColor: config.activeLine}, - '.cm-selectionMatch': {backgroundColor: config.selection}, - - '&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': { - backgroundColor: config.matchingBracket, - outline: 'none' - }, - - '.cm-gutters': { - backgroundColor: config.background, - color: config.foreground, - border: 'none' - }, - '.cm-activeLineGutter': {backgroundColor: config.background}, - - '.cm-lineNumbers .cm-gutterElement': {color: config.lineNumber}, - '.cm-lineNumbers .cm-activeLineGutter': {color: config.activeLineNumber}, - - '.cm-foldPlaceholder': { - backgroundColor: 'transparent', - border: 'none', - color: config.foreground - }, - '.cm-tooltip': { - border: `1px solid ${config.dropdownBorder}`, - backgroundColor: config.dropdownBackground, - color: config.foreground, - }, - '.cm-tooltip .cm-tooltip-arrow:before': { - borderTopColor: 'transparent', - borderBottomColor: 'transparent' - }, - '.cm-tooltip .cm-tooltip-arrow:after': { - borderTopColor: config.foreground, - borderBottomColor: config.foreground, - }, - '.cm-tooltip-autocomplete': { - '& > ul > li[aria-selected]': { - background: config.selection, - color: config.foreground, - } - } -}, {dark: config.dark}) - -export const tokyoNightStormHighlightStyle = HighlightStyle.define([ - {tag: t.keyword, color: config.keyword}, - {tag: [t.name, t.deleted, t.character, t.macroName], color: config.variable}, - {tag: [t.propertyName], color: config.function}, - {tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: config.string}, - {tag: [t.function(t.variableName), t.labelName], color: config.function}, - {tag: [t.color, t.constant(t.name), t.standard(t.name)], color: config.constant}, - {tag: [t.definition(t.name), t.separator], color: config.variable}, - {tag: [t.className], color: config.class}, - {tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: config.number}, - {tag: [t.typeName], color: config.type, fontStyle: config.type}, - {tag: [t.operator, t.operatorKeyword], color: config.keyword}, - {tag: [t.url, t.escape, t.regexp, t.link], color: config.regexp}, - {tag: [t.meta, t.comment], color: config.comment}, - {tag: t.strong, fontWeight: 'bold'}, - {tag: t.emphasis, fontStyle: 'italic'}, - {tag: t.link, textDecoration: 'underline'}, - {tag: t.heading, fontWeight: 'bold', color: config.heading}, - {tag: [t.atom, t.bool, t.special(t.variableName)], color: config.variable}, - {tag: t.invalid, color: config.invalid}, - {tag: t.strikethrough, textDecoration: 'line-through'}, -]) - -export const tokyoNightStorm: Extension = [ - tokyoNightStormTheme, - syntaxHighlighting(tokyoNightStormHighlightStyle), -] +// 使用通用主题工厂函数创建 Tokyo Night Storm 主题 +export const tokyoNightStorm: Extension = createBaseTheme(config) diff --git a/frontend/src/views/editor/theme/dark/tokyo-night.ts b/frontend/src/views/editor/theme/dark/tokyo-night.ts index ddd3336..74d90d4 100644 --- a/frontend/src/views/editor/theme/dark/tokyo-night.ts +++ b/frontend/src/views/editor/theme/dark/tokyo-night.ts @@ -1,128 +1,57 @@ -import {EditorView} from '@codemirror/view' import {Extension} from '@codemirror/state' -import {HighlightStyle, syntaxHighlighting} from '@codemirror/language' -import {tags as t} from '@lezer/highlight' +import {createBaseTheme} from '../base' +import type {ThemeColors} from '../types' -export const config = { - name: 'tokyoNight', +export const config: ThemeColors = { + name: 'tokyo-night', dark: true, + + // 基础色调 background: '#1a1b26', - foreground: '#787c99', - selection: '#515c7e40', - cursor: '#c0caf5', + backgroundSecondary: '#1a1b26', + surface: '#1a1b26', dropdownBackground: '#1a1b26', dropdownBorder: '#787c99', - activeLine: '#43455c22', - lineNumber: '#363b54', - activeLineNumber: '#737aa2', - matchingBracket: '#16161e', - keyword: '#bb9af7', - storage: '#bb9af7', - variable: '#c0caf5', - parameter: '#c0caf5', - function: '#7aa2f7', - string: '#9ece6a', - constant: '#bb9af7', - type: '#0db9d7', - class: '#c0caf5', - number: '#ff9e64', + + // 文本颜色 + foreground: '#787c99', + foregroundSecondary: '#787c99', comment: '#444b6a', + + // 语法高亮色 - 核心 + keyword: '#bb9af7', + string: '#9ece6a', + function: '#7aa2f7', + number: '#ff9e64', + operator: '#bb9af7', + variable: '#c0caf5', + type: '#0db9d7', + + // 语法高亮色 - 扩展 + constant: '#bb9af7', + storage: '#bb9af7', + parameter: '#c0caf5', + class: '#c0caf5', heading: '#89ddff', invalid: '#ff5370', regexp: '#b4f9f8', + + // 界面元素 + cursor: '#c0caf5', + selection: '#515c7e40', + selectionBlur: '#515c7e40', + activeLine: '#43455c22', + lineNumber: '#363b54', + activeLineNumber: '#737aa2', + + // 边框和分割线 + borderColor: '#16161e', + borderLight: '#787c9919', + + // 搜索和匹配 + searchMatch: '#7aa2f7', + matchingBracket: '#16161e', } -export const tokyoNightTheme = EditorView.theme({ - '&': { - color: config.foreground, - backgroundColor: config.background, - }, - - '.cm-content': {caretColor: config.cursor}, - - '.cm-cursor, .cm-dropCursor': {borderLeftColor: config.cursor}, - '&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection': {backgroundColor: config.selection}, - - '.cm-panels': {backgroundColor: config.dropdownBackground, color: config.foreground}, - '.cm-panels.cm-panels-top': {borderBottom: '2px solid black'}, - '.cm-panels.cm-panels-bottom': {borderTop: '2px solid black'}, - - '.cm-searchMatch': { - backgroundColor: config.dropdownBackground, - outline: `1px solid ${config.dropdownBorder}` - }, - '.cm-searchMatch.cm-searchMatch-selected': { - backgroundColor: config.selection - }, - - '.cm-activeLine': {backgroundColor: config.activeLine}, - '.cm-selectionMatch': {backgroundColor: config.selection}, - - '&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': { - backgroundColor: config.matchingBracket, - outline: 'none' - }, - - '.cm-gutters': { - backgroundColor: config.background, - color: config.foreground, - border: 'none' - }, - '.cm-activeLineGutter': {backgroundColor: config.background}, - - '.cm-lineNumbers .cm-gutterElement': {color: config.lineNumber}, - '.cm-lineNumbers .cm-activeLineGutter': {color: config.activeLineNumber}, - - '.cm-foldPlaceholder': { - backgroundColor: 'transparent', - border: 'none', - color: config.foreground - }, - '.cm-tooltip': { - border: `1px solid ${config.dropdownBorder}`, - backgroundColor: config.dropdownBackground, - color: config.foreground, - }, - '.cm-tooltip .cm-tooltip-arrow:before': { - borderTopColor: 'transparent', - borderBottomColor: 'transparent' - }, - '.cm-tooltip .cm-tooltip-arrow:after': { - borderTopColor: config.foreground, - borderBottomColor: config.foreground, - }, - '.cm-tooltip-autocomplete': { - '& > ul > li[aria-selected]': { - background: config.selection, - color: config.foreground, - } - } -}, {dark: config.dark}) - -export const tokyoNightHighlightStyle = HighlightStyle.define([ - {tag: t.keyword, color: config.keyword}, - {tag: [t.name, t.deleted, t.character, t.macroName], color: config.variable}, - {tag: [t.propertyName], color: config.function}, - {tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: config.string}, - {tag: [t.function(t.variableName), t.labelName], color: config.function}, - {tag: [t.color, t.constant(t.name), t.standard(t.name)], color: config.constant}, - {tag: [t.definition(t.name), t.separator], color: config.variable}, - {tag: [t.className], color: config.class}, - {tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: config.number}, - {tag: [t.typeName], color: config.type, fontStyle: config.type}, - {tag: [t.operator, t.operatorKeyword], color: config.keyword}, - {tag: [t.url, t.escape, t.regexp, t.link], color: config.regexp}, - {tag: [t.meta, t.comment], color: config.comment}, - {tag: t.strong, fontWeight: 'bold'}, - {tag: t.emphasis, fontStyle: 'italic'}, - {tag: t.link, textDecoration: 'underline'}, - {tag: t.heading, fontWeight: 'bold', color: config.heading}, - {tag: [t.atom, t.bool, t.special(t.variableName)], color: config.variable}, - {tag: t.invalid, color: config.invalid}, - {tag: t.strikethrough, textDecoration: 'line-through'}, -]) - -export const tokyoNight: Extension = [ - tokyoNightTheme, - syntaxHighlighting(tokyoNightHighlightStyle), -] +// 使用通用主题工厂函数创建 Tokyo Night 主题 +export const tokyoNight: Extension = createBaseTheme(config) diff --git a/frontend/src/views/editor/theme/light.ts b/frontend/src/views/editor/theme/light.ts deleted file mode 100644 index 285b459..0000000 --- a/frontend/src/views/editor/theme/light.ts +++ /dev/null @@ -1,273 +0,0 @@ -import {EditorView} from '@codemirror/view'; -import {HighlightStyle, syntaxHighlighting} from '@codemirror/language'; -import {tags} from '@lezer/highlight'; - -// 默认浅色主题颜色 -export const defaultLightColors = { - // 基础色调 - background: '#ffffff', // 主背景色 - backgroundSecondary: '#f1faf1', // 次要背景色 - surface: '#f5f5f5', // 面板背景 - - // 文本颜色 - foreground: '#444d56', // 主文本色 - foregroundSecondary: '#6a737d', // 次要文本色 - comment: '#6a737d', // 注释色 - - // 语法高亮色 - keyword: '#d73a49', // 关键字 - string: '#032f62', // 字符串 - function: '#005cc5', // 函数名 - number: '#005cc5', // 数字 - operator: '#d73a49', // 操作符 - variable: '#24292e', // 变量 - type: '#6f42c1', // 类型 - - // 界面元素 - cursor: '#000000', // 光标 - selection: '#77baff', // 选中背景 - selectionBlur: '#b2c2ca', // 失焦选中背景 - activeLine: '#0000000a', // 当前行高亮 - lineNumber: '#00000040', // 行号 - activeLineNumber: '#000000aa', // 活动行号 - - // 边框和分割线 - borderColor: '#dfdfdf', // 边框色 - borderLight: '#0000000c', // 浅色边框 - - // 搜索和匹配 - searchMatch: '#005cc5', // 搜索匹配 - matchingBracket: '#00000019', // 匹配括号 -}; - -// 创建浅色主题 -export function createLightTheme(colors = defaultLightColors) { - const lightTheme = EditorView.theme({ - '&': { - color: colors.foreground, - backgroundColor: colors.background, - }, - - // 确保编辑器容器背景一致 - '.cm-editor': { - backgroundColor: colors.background, - }, - - // 确保滚动区域背景一致 - '.cm-scroller': { - backgroundColor: colors.background, - }, - - // 编辑器内容 - '.cm-content': { - caretColor: colors.cursor, - paddingTop: '4px', - }, - - // 光标 - '.cm-cursor, .cm-dropCursor': { - borderLeftColor: colors.cursor, - borderLeftWidth: '2px', - paddingTop: '4px', - marginTop: '-2px', - }, - - // 选择 - '.cm-selectionBackground': { - backgroundColor: colors.selectionBlur, - }, - '&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground': { - backgroundColor: colors.selection, - }, - '.cm-activeLine.code-empty-block-selected': { - backgroundColor: colors.selection, - }, - - // 当前行高亮 - '.cm-activeLine': { - backgroundColor: colors.activeLine - }, - - // 行号区域 - '.cm-gutters': { - backgroundColor: 'rgba(0,0,0, 0.04)', - color: colors.lineNumber, - border: 'none', - borderRight: `1px solid ${colors.borderLight}`, - padding: '0 2px 0 4px', - userSelect: 'none', - }, - '.cm-activeLineGutter': { - backgroundColor: 'transparent', - color: colors.activeLineNumber, - }, - - // 折叠功能 - '.cm-foldGutter': { - marginLeft: '0px', - }, - '.cm-foldGutter .cm-gutterElement': { - opacity: 0, - transition: 'opacity 400ms', - }, - '.cm-gutters:hover .cm-gutterElement': { - opacity: 1, - }, - '.cm-foldPlaceholder': { - backgroundColor: 'transparent', - border: 'none', - color: colors.comment, - }, - - - // 搜索匹配 - '.cm-searchMatch': { - backgroundColor: 'transparent', - outline: `1px solid ${colors.searchMatch}`, - }, - '.cm-searchMatch.cm-searchMatch-selected': { - backgroundColor: colors.searchMatch, - color: colors.background, - }, - '.cm-selectionMatch': { - backgroundColor: '#e6f3ff', - }, - - // 括号匹配 - '&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': { - outline: `0.5px solid ${colors.searchMatch}`, - }, - '&.cm-focused .cm-matchingBracket': { - backgroundColor: colors.matchingBracket, - color: 'inherit', - }, - '&.cm-focused .cm-nonmatchingBracket': { - outline: '0.5px solid #d73a49', - }, - - // 编辑器焦点 - '&.cm-editor.cm-focused': { - outline: 'none', - }, - - // 工具提示 - '.cm-tooltip': { - border: 'none', - backgroundColor: colors.surface, - boxShadow: '0 2px 8px rgba(0,0,0,0.1)', - }, - '.cm-tooltip .cm-tooltip-arrow:before': { - borderTopColor: 'transparent', - borderBottomColor: 'transparent', - }, - '.cm-tooltip .cm-tooltip-arrow:after': { - borderTopColor: colors.surface, - borderBottomColor: colors.surface, - }, - '.cm-tooltip-autocomplete': { - '& > ul > li[aria-selected]': { - backgroundColor: colors.activeLine, - color: colors.foreground, - }, - }, - - // 代码块层 - '.code-blocks-layer': { - width: '100%', - }, - '.code-blocks-layer .block-even, .code-blocks-layer .block-odd': { - width: '100%', - boxSizing: 'content-box', - }, - '.code-blocks-layer .block-even': { - background: colors.background, - borderTop: `1px solid ${colors.borderColor}`, - }, - '.code-blocks-layer .block-even:first-child': { - borderTop: 'none', - }, - '.code-blocks-layer .block-odd': { - background: colors.backgroundSecondary, - borderTop: `1px solid ${colors.borderColor}`, - }, - '.code-blocks-math-result': { - paddingLeft: "12px", - position: "relative", - }, - '.code-blocks-math-result .inner': { - background: '#48b57e', - color: '#fff', - padding: '0px 4px', - borderRadius: '2px', - boxShadow: '0 0 3px rgba(0,0,0, 0.1)', - cursor: 'pointer', - whiteSpace: "nowrap", - }, - '.code-blocks-math-result-copied': { - position: "absolute", - top: "0px", - left: "0px", - marginLeft: "calc(100% + 10px)", - width: "60px", - transition: "opacity 500ms", - transitionDelay: "1000ms", - color: "rgba(0,0,0, 0.8)", - }, - '.code-blocks-math-result-copied.fade-out': { - opacity: 0, - }, - - // 代码块开始标记 - '.code-block-start': { - height: '12px', - }, - '.code-block-start.first': { - height: '0px', - }, - }, {dark: false}); - - // 语法高亮样式 - const lightHighlightStyle = HighlightStyle.define([ - {tag: tags.keyword, color: colors.keyword}, - {tag: [tags.name, tags.deleted, tags.character, tags.propertyName, tags.macroName], color: colors.variable}, - {tag: [tags.variableName], color: colors.variable}, - {tag: [tags.function(tags.variableName)], color: colors.function}, - {tag: [tags.labelName], color: colors.operator}, - {tag: [tags.color, tags.constant(tags.name), tags.standard(tags.name)], color: colors.keyword}, - {tag: [tags.definition(tags.name), tags.separator], color: colors.function}, - {tag: [tags.brace], color: colors.variable}, - {tag: [tags.annotation], color: '#d73a49'}, - { - tag: [tags.number, tags.changed, tags.annotation, tags.modifier, tags.self, tags.namespace], - color: colors.number - }, - {tag: [tags.typeName, tags.className], color: colors.type}, - {tag: [tags.operator, tags.operatorKeyword], color: colors.operator}, - {tag: [tags.tagName], color: colors.type}, - {tag: [tags.squareBracket], color: colors.keyword}, - {tag: [tags.angleBracket], color: colors.operator}, - {tag: [tags.attributeName], color: colors.variable}, - {tag: [tags.regexp], color: colors.string}, - {tag: [tags.quote], color: colors.comment}, - {tag: [tags.string], color: colors.string}, - {tag: tags.link, color: colors.function, textDecoration: 'underline'}, - {tag: [tags.url, tags.escape, tags.special(tags.string)], color: colors.string}, - {tag: [tags.meta], color: colors.comment}, - {tag: [tags.comment], color: colors.comment, fontStyle: 'italic'}, - {tag: tags.strong, fontWeight: 'bold'}, - {tag: tags.emphasis, fontStyle: 'italic'}, - {tag: tags.strikethrough, textDecoration: 'line-through'}, - {tag: tags.heading, fontWeight: 'bold', color: colors.keyword}, - {tag: [tags.heading1, tags.heading2], fontSize: '1.4em'}, - {tag: [tags.heading3, tags.heading4], fontSize: '1.2em'}, - {tag: [tags.heading5, tags.heading6], fontSize: '1.1em'}, - ]); - - return [ - lightTheme, - syntaxHighlighting(lightHighlightStyle), - ]; -} - -// 默认浅色主题 -export const light = createLightTheme(defaultLightColors); \ No newline at end of file diff --git a/frontend/src/views/editor/theme/light/default-light.ts b/frontend/src/views/editor/theme/light/default-light.ts new file mode 100644 index 0000000..acaadce --- /dev/null +++ b/frontend/src/views/editor/theme/light/default-light.ts @@ -0,0 +1,63 @@ +import {createBaseTheme} from '../base'; +import type {ThemeColors} from '../types'; + +// 默认浅色主题颜色 +export const defaultLightColors: ThemeColors = { + // 主题信息 + name: 'default-light', + dark: false, + + // 基础色调 + background: '#ffffff', // 主背景色 + backgroundSecondary: '#f1faf1', // 次要背景色 + surface: '#f5f5f5', // 面板背景 + dropdownBackground: '#ffffff', // 下拉菜单背景 + dropdownBorder: '#e1e4e8', // 下拉菜单边框 + + // 文本颜色 + foreground: '#444d56', // 主文本色 + foregroundSecondary: '#6a737d', // 次要文本色 + comment: '#6a737d', // 注释色 + + // 语法高亮色 - 核心 + keyword: '#d73a49', // 关键字 + string: '#032f62', // 字符串 + function: '#005cc5', // 函数名 + number: '#005cc5', // 数字 + operator: '#d73a49', // 操作符 + variable: '#24292e', // 变量 + type: '#6f42c1', // 类型 + + // 语法高亮色 - 扩展 + constant: '#005cc5', // 常量 + storage: '#d73a49', // 存储类型 + parameter: '#24292e', // 参数 + class: '#6f42c1', // 类名 + heading: '#d73a49', // 标题 + invalid: '#cb2431', // 无效内容 + regexp: '#032f62', // 正则表达式 + + // 界面元素 + cursor: '#000000', // 光标 + selection: '#77baff', // 选中背景 + selectionBlur: '#b2c2ca', // 失焦选中背景 + activeLine: '#0000000a', // 当前行高亮 + lineNumber: '#00000040', // 行号 + activeLineNumber: '#000000aa', // 活动行号 + + // 边框和分割线 + borderColor: '#dfdfdf', // 边框色 + borderLight: '#0000000c', // 浅色边框 + + // 搜索和匹配 + searchMatch: '#005cc5', // 搜索匹配 + matchingBracket: '#00000019', // 匹配括号 +}; + +// 创建浅色主题 +export function createLightTheme(colors: ThemeColors = defaultLightColors) { + return createBaseTheme({...colors, dark: false}); +} + +// 默认浅色主题 +export const defaultLight = createLightTheme(defaultLightColors); \ No newline at end of file diff --git a/frontend/src/views/editor/theme/light/github-light.ts b/frontend/src/views/editor/theme/light/github-light.ts index 65d4977..6fbc4d7 100644 --- a/frontend/src/views/editor/theme/light/github-light.ts +++ b/frontend/src/views/editor/theme/light/github-light.ts @@ -1,128 +1,57 @@ -import {EditorView} from '@codemirror/view' import {Extension} from '@codemirror/state' -import {HighlightStyle, syntaxHighlighting} from '@codemirror/language' -import {tags as t} from '@lezer/highlight' +import {createBaseTheme} from '../base' +import type {ThemeColors} from '../types' -export const config = { - name: 'githubLight', +export const config: ThemeColors = { + name: 'github-light', dark: false, + + // 基础色调 background: '#fff', - foreground: '#444d56', - selection: '#0366d625', - cursor: '#044289', + backgroundSecondary: '#fff', + surface: '#fff', dropdownBackground: '#fff', dropdownBorder: '#e1e4e8', - activeLine: '#c6c6c622', - lineNumber: '#1b1f234d', - activeLineNumber: '#24292e', - matchingBracket: '#34d05840', - keyword: '#d73a49', - storage: '#d73a49', - variable: '#e36209', - parameter: '#24292e', - function: '#005cc5', - string: '#032f62', - constant: '#005cc5', - type: '#005cc5', - class: '#6f42c1', - number: '#005cc5', + + // 文本颜色 + foreground: '#444d56', + foregroundSecondary: '#444d56', comment: '#6a737d', + + // 语法高亮色 - 核心 + keyword: '#d73a49', + string: '#032f62', + function: '#005cc5', + number: '#005cc5', + operator: '#d73a49', + variable: '#e36209', + type: '#005cc5', + + // 语法高亮色 - 扩展 + constant: '#005cc5', + storage: '#d73a49', + parameter: '#24292e', + class: '#6f42c1', heading: '#005cc5', invalid: '#cb2431', regexp: '#032f62', + + // 界面元素 + cursor: '#044289', + selection: '#0366d625', + selectionBlur: '#0366d625', + activeLine: '#c6c6c622', + lineNumber: '#1b1f234d', + activeLineNumber: '#24292e', + + // 边框和分割线 + borderColor: '#e1e4e8', + borderLight: '#444d5619', + + // 搜索和匹配 + searchMatch: '#005cc5', + matchingBracket: '#34d05840', } -export const githubLightTheme = EditorView.theme({ - '&': { - color: config.foreground, - backgroundColor: config.background, - }, - - '.cm-content': {caretColor: config.cursor}, - - '.cm-cursor, .cm-dropCursor': {borderLeftColor: config.cursor}, - '&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection': {backgroundColor: config.selection}, - - '.cm-panels': {backgroundColor: config.dropdownBackground, color: config.foreground}, - '.cm-panels.cm-panels-top': {borderBottom: '2px solid black'}, - '.cm-panels.cm-panels-bottom': {borderTop: '2px solid black'}, - - '.cm-searchMatch': { - backgroundColor: config.dropdownBackground, - outline: `1px solid ${config.dropdownBorder}` - }, - '.cm-searchMatch.cm-searchMatch-selected': { - backgroundColor: config.selection - }, - - '.cm-activeLine': {backgroundColor: config.activeLine}, - '.cm-selectionMatch': {backgroundColor: config.selection}, - - '&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': { - backgroundColor: config.matchingBracket, - outline: 'none' - }, - - '.cm-gutters': { - backgroundColor: config.background, - color: config.foreground, - border: 'none' - }, - '.cm-activeLineGutter': {backgroundColor: config.background}, - - '.cm-lineNumbers .cm-gutterElement': {color: config.lineNumber}, - '.cm-lineNumbers .cm-activeLineGutter': {color: config.activeLineNumber}, - - '.cm-foldPlaceholder': { - backgroundColor: 'transparent', - border: 'none', - color: config.foreground - }, - '.cm-tooltip': { - border: `1px solid ${config.dropdownBorder}`, - backgroundColor: config.dropdownBackground, - color: config.foreground, - }, - '.cm-tooltip .cm-tooltip-arrow:before': { - borderTopColor: 'transparent', - borderBottomColor: 'transparent' - }, - '.cm-tooltip .cm-tooltip-arrow:after': { - borderTopColor: config.foreground, - borderBottomColor: config.foreground, - }, - '.cm-tooltip-autocomplete': { - '& > ul > li[aria-selected]': { - background: config.selection, - color: config.foreground, - } - } -}, {dark: config.dark}) - -export const githubLightHighlightStyle = HighlightStyle.define([ - {tag: t.keyword, color: config.keyword}, - {tag: [t.name, t.deleted, t.character, t.macroName], color: config.variable}, - {tag: [t.propertyName], color: config.function}, - {tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: config.string}, - {tag: [t.function(t.variableName), t.labelName], color: config.function}, - {tag: [t.color, t.constant(t.name), t.standard(t.name)], color: config.constant}, - {tag: [t.definition(t.name), t.separator], color: config.variable}, - {tag: [t.className], color: config.class}, - {tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: config.number}, - {tag: [t.typeName], color: config.type, fontStyle: config.type}, - {tag: [t.operator, t.operatorKeyword], color: config.keyword}, - {tag: [t.url, t.escape, t.regexp, t.link], color: config.regexp}, - {tag: [t.meta, t.comment], color: config.comment}, - {tag: t.strong, fontWeight: 'bold'}, - {tag: t.emphasis, fontStyle: 'italic'}, - {tag: t.link, textDecoration: 'underline'}, - {tag: t.heading, fontWeight: 'bold', color: config.heading}, - {tag: [t.atom, t.bool, t.special(t.variableName)], color: config.variable}, - {tag: t.invalid, color: config.invalid}, - {tag: t.strikethrough, textDecoration: 'line-through'}, -]) - -export const githubLight: Extension = [ - githubLightTheme, - syntaxHighlighting(githubLightHighlightStyle), -] +// 使用通用主题工厂函数创建 GitHub Light 主题 +export const githubLight: Extension = createBaseTheme(config) diff --git a/frontend/src/views/editor/theme/light/material-light.ts b/frontend/src/views/editor/theme/light/material-light.ts index 316dc83..82a0d63 100644 --- a/frontend/src/views/editor/theme/light/material-light.ts +++ b/frontend/src/views/editor/theme/light/material-light.ts @@ -1,128 +1,57 @@ -import {EditorView} from '@codemirror/view' import {Extension} from '@codemirror/state' -import {HighlightStyle, syntaxHighlighting} from '@codemirror/language' -import {tags as t} from '@lezer/highlight' +import {createBaseTheme} from '../base' +import type {ThemeColors} from '../types' -export const config = { - name: 'materialLight', +export const config: ThemeColors = { + name: 'material-light', dark: false, + + // 基础色调 background: '#FAFAFA', - foreground: '#90A4AE', - selection: '#80CBC440', - cursor: '#272727', + backgroundSecondary: '#FAFAFA', + surface: '#FAFAFA', dropdownBackground: '#FAFAFA', dropdownBorder: '#00000010', - activeLine: '#c2c2c222', - lineNumber: '#CFD8DC', - activeLineNumber: '#7E939E', - matchingBracket: '#FAFAFA', - keyword: '#7C4DFF', - storage: '#7C4DFF', - variable: '#90A4AE', - parameter: '#90A4AE', - function: '#6182B8', - string: '#91B859', - constant: '#F76D47', - type: '#8796B0', - class: '#FFB62C', - number: '#F76D47', + + // 文本颜色 + foreground: '#90A4AE', + foregroundSecondary: '#90A4AE', comment: '#90A4AE', + + // 语法高亮色 - 核心 + keyword: '#7C4DFF', + string: '#91B859', + function: '#6182B8', + number: '#F76D47', + operator: '#7C4DFF', + variable: '#90A4AE', + type: '#8796B0', + + // 语法高亮色 - 扩展 + constant: '#F76D47', + storage: '#7C4DFF', + parameter: '#90A4AE', + class: '#FFB62C', heading: '#91B859', invalid: '#E53935', regexp: '#39ADB5', + + // 界面元素 + cursor: '#272727', + selection: '#80CBC440', + selectionBlur: '#80CBC440', + activeLine: '#c2c2c222', + lineNumber: '#CFD8DC', + activeLineNumber: '#7E939E', + + // 边框和分割线 + borderColor: '#00000010', + borderLight: '#90A4AE19', + + // 搜索和匹配 + searchMatch: '#6182B8', + matchingBracket: '#FAFAFA', } -export const materialLightTheme = EditorView.theme({ - '&': { - color: config.foreground, - backgroundColor: config.background, - }, - - '.cm-content': {caretColor: config.cursor}, - - '.cm-cursor, .cm-dropCursor': {borderLeftColor: config.cursor}, - '&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection': {backgroundColor: config.selection}, - - '.cm-panels': {backgroundColor: config.dropdownBackground, color: config.foreground}, - '.cm-panels.cm-panels-top': {borderBottom: '2px solid black'}, - '.cm-panels.cm-panels-bottom': {borderTop: '2px solid black'}, - - '.cm-searchMatch': { - backgroundColor: config.dropdownBackground, - outline: `1px solid ${config.dropdownBorder}` - }, - '.cm-searchMatch.cm-searchMatch-selected': { - backgroundColor: config.selection - }, - - '.cm-activeLine': {backgroundColor: config.activeLine}, - '.cm-selectionMatch': {backgroundColor: config.selection}, - - '&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': { - backgroundColor: config.matchingBracket, - outline: 'none' - }, - - '.cm-gutters': { - backgroundColor: config.background, - color: config.foreground, - border: 'none' - }, - '.cm-activeLineGutter': {backgroundColor: config.background}, - - '.cm-lineNumbers .cm-gutterElement': {color: config.lineNumber}, - '.cm-lineNumbers .cm-activeLineGutter': {color: config.activeLineNumber}, - - '.cm-foldPlaceholder': { - backgroundColor: 'transparent', - border: 'none', - color: config.foreground - }, - '.cm-tooltip': { - border: `1px solid ${config.dropdownBorder}`, - backgroundColor: config.dropdownBackground, - color: config.foreground, - }, - '.cm-tooltip .cm-tooltip-arrow:before': { - borderTopColor: 'transparent', - borderBottomColor: 'transparent' - }, - '.cm-tooltip .cm-tooltip-arrow:after': { - borderTopColor: config.foreground, - borderBottomColor: config.foreground, - }, - '.cm-tooltip-autocomplete': { - '& > ul > li[aria-selected]': { - background: config.selection, - color: config.foreground, - } - } -}, {dark: config.dark}) - -export const materialLightHighlightStyle = HighlightStyle.define([ - {tag: t.keyword, color: config.keyword}, - {tag: [t.name, t.deleted, t.character, t.macroName], color: config.variable}, - {tag: [t.propertyName], color: config.function}, - {tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: config.string}, - {tag: [t.function(t.variableName), t.labelName], color: config.function}, - {tag: [t.color, t.constant(t.name), t.standard(t.name)], color: config.constant}, - {tag: [t.definition(t.name), t.separator], color: config.variable}, - {tag: [t.className], color: config.class}, - {tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: config.number}, - {tag: [t.typeName], color: config.type, fontStyle: config.type}, - {tag: [t.operator, t.operatorKeyword], color: config.keyword}, - {tag: [t.url, t.escape, t.regexp, t.link], color: config.regexp}, - {tag: [t.meta, t.comment], color: config.comment}, - {tag: t.strong, fontWeight: 'bold'}, - {tag: t.emphasis, fontStyle: 'italic'}, - {tag: t.link, textDecoration: 'underline'}, - {tag: t.heading, fontWeight: 'bold', color: config.heading}, - {tag: [t.atom, t.bool, t.special(t.variableName)], color: config.variable}, - {tag: t.invalid, color: config.invalid}, - {tag: t.strikethrough, textDecoration: 'line-through'}, -]) - -export const materialLight: Extension = [ - materialLightTheme, - syntaxHighlighting(materialLightHighlightStyle), -] +// 使用通用主题工厂函数创建 Material Light 主题 +export const materialLight: Extension = createBaseTheme(config) diff --git a/frontend/src/views/editor/theme/light/solarized-light.ts b/frontend/src/views/editor/theme/light/solarized-light.ts index c422a9f..9559c1b 100644 --- a/frontend/src/views/editor/theme/light/solarized-light.ts +++ b/frontend/src/views/editor/theme/light/solarized-light.ts @@ -1,128 +1,57 @@ -import {EditorView} from '@codemirror/view' import {Extension} from '@codemirror/state' -import {HighlightStyle, syntaxHighlighting} from '@codemirror/language' -import {tags as t} from '@lezer/highlight' +import {createBaseTheme} from '../base' +import type {ThemeColors} from '../types' -export const config = { - name: 'solarizedLight', +export const config: ThemeColors = { + name: 'solarized-light', dark: false, + + // 基础色调 background: '#FDF6E3', - foreground: '#586E75', - selection: '#EEE8D5', - cursor: '#657B83', + backgroundSecondary: '#FDF6E3', + surface: '#FDF6E3', dropdownBackground: '#FDF6E3', dropdownBorder: '#D3AF86', - activeLine: '#d5bd5c22', - lineNumber: '#586E75', - activeLineNumber: '#567983', - matchingBracket: '#EEE8D5', - keyword: '#859900', - storage: '#586E75', - variable: '#268BD2', - parameter: '#268BD2', - function: '#268BD2', - string: '#2AA198', - constant: '#CB4B16', - type: '#CB4B16', - class: '#CB4B16', - number: '#D33682', + + // 文本颜色 + foreground: '#586E75', + foregroundSecondary: '#586E75', comment: '#93A1A1', + + // 语法高亮色 - 核心 + keyword: '#859900', + string: '#2AA198', + function: '#268BD2', + number: '#D33682', + operator: '#859900', + variable: '#268BD2', + type: '#CB4B16', + + // 语法高亮色 - 扩展 + constant: '#CB4B16', + storage: '#586E75', + parameter: '#268BD2', + class: '#CB4B16', heading: '#268BD2', invalid: '#DC322F', regexp: '#DC322F', + + // 界面元素 + cursor: '#657B83', + selection: '#EEE8D5', + selectionBlur: '#EEE8D5', + activeLine: '#d5bd5c22', + lineNumber: '#586E75', + activeLineNumber: '#567983', + + // 边框和分割线 + borderColor: '#EEE8D5', + borderLight: '#586E7519', + + // 搜索和匹配 + searchMatch: '#268BD2', + matchingBracket: '#EEE8D5', } -export const solarizedLightTheme = EditorView.theme({ - '&': { - color: config.foreground, - backgroundColor: config.background, - }, - - '.cm-content': {caretColor: config.cursor}, - - '.cm-cursor, .cm-dropCursor': {borderLeftColor: config.cursor}, - '&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection': {backgroundColor: config.selection}, - - '.cm-panels': {backgroundColor: config.dropdownBackground, color: config.foreground}, - '.cm-panels.cm-panels-top': {borderBottom: '2px solid black'}, - '.cm-panels.cm-panels-bottom': {borderTop: '2px solid black'}, - - '.cm-searchMatch': { - backgroundColor: config.dropdownBackground, - outline: `1px solid ${config.dropdownBorder}` - }, - '.cm-searchMatch.cm-searchMatch-selected': { - backgroundColor: config.selection - }, - - '.cm-activeLine': {backgroundColor: config.activeLine}, - '.cm-selectionMatch': {backgroundColor: config.selection}, - - '&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': { - backgroundColor: config.matchingBracket, - outline: 'none' - }, - - '.cm-gutters': { - backgroundColor: config.background, - color: config.foreground, - border: 'none' - }, - '.cm-activeLineGutter': {backgroundColor: config.background}, - - '.cm-lineNumbers .cm-gutterElement': {color: config.lineNumber}, - '.cm-lineNumbers .cm-activeLineGutter': {color: config.activeLineNumber}, - - '.cm-foldPlaceholder': { - backgroundColor: 'transparent', - border: 'none', - color: config.foreground - }, - '.cm-tooltip': { - border: `1px solid ${config.dropdownBorder}`, - backgroundColor: config.dropdownBackground, - color: config.foreground, - }, - '.cm-tooltip .cm-tooltip-arrow:before': { - borderTopColor: 'transparent', - borderBottomColor: 'transparent' - }, - '.cm-tooltip .cm-tooltip-arrow:after': { - borderTopColor: config.foreground, - borderBottomColor: config.foreground, - }, - '.cm-tooltip-autocomplete': { - '& > ul > li[aria-selected]': { - background: config.selection, - color: config.foreground, - } - } -}, {dark: config.dark}) - -export const solarizedLightHighlightStyle = HighlightStyle.define([ - {tag: t.keyword, color: config.keyword}, - {tag: [t.name, t.deleted, t.character, t.macroName], color: config.variable}, - {tag: [t.propertyName], color: config.function}, - {tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: config.string}, - {tag: [t.function(t.variableName), t.labelName], color: config.function}, - {tag: [t.color, t.constant(t.name), t.standard(t.name)], color: config.constant}, - {tag: [t.definition(t.name), t.separator], color: config.variable}, - {tag: [t.className], color: config.class}, - {tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: config.number}, - {tag: [t.typeName], color: config.type, fontStyle: config.type}, - {tag: [t.operator, t.operatorKeyword], color: config.keyword}, - {tag: [t.url, t.escape, t.regexp, t.link], color: config.regexp}, - {tag: [t.meta, t.comment], color: config.comment}, - {tag: t.strong, fontWeight: 'bold'}, - {tag: t.emphasis, fontStyle: 'italic'}, - {tag: t.link, textDecoration: 'underline'}, - {tag: t.heading, fontWeight: 'bold', color: config.heading}, - {tag: [t.atom, t.bool, t.special(t.variableName)], color: config.variable}, - {tag: t.invalid, color: config.invalid}, - {tag: t.strikethrough, textDecoration: 'line-through'}, -]) - -export const solarizedLight: Extension = [ - solarizedLightTheme, - syntaxHighlighting(solarizedLightHighlightStyle), -] +// 使用通用主题工厂函数创建 Solarized Light 主题 +export const solarizedLight: Extension = createBaseTheme(config) diff --git a/frontend/src/views/editor/theme/light/tokyo-night-day.ts b/frontend/src/views/editor/theme/light/tokyo-night-day.ts index 2f671ee..608cb31 100644 --- a/frontend/src/views/editor/theme/light/tokyo-night-day.ts +++ b/frontend/src/views/editor/theme/light/tokyo-night-day.ts @@ -1,128 +1,57 @@ -import {EditorView} from '@codemirror/view' import {Extension} from '@codemirror/state' -import {HighlightStyle, syntaxHighlighting} from '@codemirror/language' -import {tags as t} from '@lezer/highlight' +import {createBaseTheme} from '../base' +import type {ThemeColors} from '../types' -export const config = { - name: 'tokyoNightDay', +export const config: ThemeColors = { + name: 'tokyo-night-day', dark: false, + + // 基础色调 background: '#e1e2e7', - foreground: '#6a6f8e', - selection: '#8591b840', - cursor: '#3760bf', + backgroundSecondary: '#e1e2e7', + surface: '#e1e2e7', dropdownBackground: '#e1e2e7', dropdownBorder: '#6a6f8e', - activeLine: '#a7aaba22', - lineNumber: '#b3b6cd', - activeLineNumber: '#68709a', - matchingBracket: '#e9e9ec', - keyword: '#9854f1', - storage: '#9854f1', - variable: '#3760bf', - parameter: '#3760bf', - function: '#2e7de9', - string: '#587539', - constant: '#9854f1', - type: '#07879d', - class: '#3760bf', - number: '#b15c00', + + // 文本颜色 + foreground: '#6a6f8e', + foregroundSecondary: '#6a6f8e', comment: '#9da3c2', + + // 语法高亮色 - 核心 + keyword: '#9854f1', + string: '#587539', + function: '#2e7de9', + number: '#b15c00', + operator: '#9854f1', + variable: '#3760bf', + type: '#07879d', + + // 语法高亮色 - 扩展 + constant: '#9854f1', + storage: '#9854f1', + parameter: '#3760bf', + class: '#3760bf', heading: '#006a83', invalid: '#ff3e64', regexp: '#2e5857', + + // 界面元素 + cursor: '#3760bf', + selection: '#8591b840', + selectionBlur: '#8591b840', + activeLine: '#a7aaba22', + lineNumber: '#b3b6cd', + activeLineNumber: '#68709a', + + // 边框和分割线 + borderColor: '#e9e9ec', + borderLight: '#6a6f8e19', + + // 搜索和匹配 + searchMatch: '#2e7de9', + matchingBracket: '#e9e9ec', } -export const tokyoNightDayTheme = EditorView.theme({ - '&': { - color: config.foreground, - backgroundColor: config.background, - }, - - '.cm-content': {caretColor: config.cursor}, - - '.cm-cursor, .cm-dropCursor': {borderLeftColor: config.cursor}, - '&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection': {backgroundColor: config.selection}, - - '.cm-panels': {backgroundColor: config.dropdownBackground, color: config.foreground}, - '.cm-panels.cm-panels-top': {borderBottom: '2px solid black'}, - '.cm-panels.cm-panels-bottom': {borderTop: '2px solid black'}, - - '.cm-searchMatch': { - backgroundColor: config.dropdownBackground, - outline: `1px solid ${config.dropdownBorder}` - }, - '.cm-searchMatch.cm-searchMatch-selected': { - backgroundColor: config.selection - }, - - '.cm-activeLine': {backgroundColor: config.activeLine}, - '.cm-selectionMatch': {backgroundColor: config.selection}, - - '&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': { - backgroundColor: config.matchingBracket, - outline: 'none' - }, - - '.cm-gutters': { - backgroundColor: config.background, - color: config.foreground, - border: 'none' - }, - '.cm-activeLineGutter': {backgroundColor: config.background}, - - '.cm-lineNumbers .cm-gutterElement': {color: config.lineNumber}, - '.cm-lineNumbers .cm-activeLineGutter': {color: config.activeLineNumber}, - - '.cm-foldPlaceholder': { - backgroundColor: 'transparent', - border: 'none', - color: config.foreground - }, - '.cm-tooltip': { - border: `1px solid ${config.dropdownBorder}`, - backgroundColor: config.dropdownBackground, - color: config.foreground, - }, - '.cm-tooltip .cm-tooltip-arrow:before': { - borderTopColor: 'transparent', - borderBottomColor: 'transparent' - }, - '.cm-tooltip .cm-tooltip-arrow:after': { - borderTopColor: config.foreground, - borderBottomColor: config.foreground, - }, - '.cm-tooltip-autocomplete': { - '& > ul > li[aria-selected]': { - background: config.selection, - color: config.foreground, - } - } -}, {dark: config.dark}) - -export const tokyoNightDayHighlightStyle = HighlightStyle.define([ - {tag: t.keyword, color: config.keyword}, - {tag: [t.name, t.deleted, t.character, t.macroName], color: config.variable}, - {tag: [t.propertyName], color: config.function}, - {tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: config.string}, - {tag: [t.function(t.variableName), t.labelName], color: config.function}, - {tag: [t.color, t.constant(t.name), t.standard(t.name)], color: config.constant}, - {tag: [t.definition(t.name), t.separator], color: config.variable}, - {tag: [t.className], color: config.class}, - {tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: config.number}, - {tag: [t.typeName], color: config.type, fontStyle: config.type}, - {tag: [t.operator, t.operatorKeyword], color: config.keyword}, - {tag: [t.url, t.escape, t.regexp, t.link], color: config.regexp}, - {tag: [t.meta, t.comment], color: config.comment}, - {tag: t.strong, fontWeight: 'bold'}, - {tag: t.emphasis, fontStyle: 'italic'}, - {tag: t.link, textDecoration: 'underline'}, - {tag: t.heading, fontWeight: 'bold', color: config.heading}, - {tag: [t.atom, t.bool, t.special(t.variableName)], color: config.variable}, - {tag: t.invalid, color: config.invalid}, - {tag: t.strikethrough, textDecoration: 'line-through'}, -]) - -export const tokyoNightDay: Extension = [ - tokyoNightDayTheme, - syntaxHighlighting(tokyoNightDayHighlightStyle), -] +// 使用通用主题工厂函数创建 Tokyo Night Day 主题 +export const tokyoNightDay: Extension = createBaseTheme(config) diff --git a/frontend/src/views/editor/theme/registry.ts b/frontend/src/views/editor/theme/registry.ts new file mode 100644 index 0000000..faf94a0 --- /dev/null +++ b/frontend/src/views/editor/theme/registry.ts @@ -0,0 +1,59 @@ +import { Extension } from '@codemirror/state'; +import type { ThemeColors } from './types'; +import { createBaseTheme } from './base'; + +// 深色主题导入 +import { config as draculaConfig } from './dark/dracula'; +import { config as auraConfig } from './dark/aura'; +import { config as githubDarkConfig } from './dark/github-dark'; +import { config as materialDarkConfig } from './dark/material-dark'; +import { config as oneDarkConfig } from './dark/one-dark'; +import { config as solarizedDarkConfig } from './dark/solarized-dark'; +import { config as tokyoNightConfig } from './dark/tokyo-night'; +import { config as tokyoNightStormConfig } from './dark/tokyo-night-storm'; + +// 浅色主题导入 +import { config as githubLightConfig } from './light/github-light'; +import { config as materialLightConfig } from './light/material-light'; +import { config as solarizedLightConfig } from './light/solarized-light'; +import { config as tokyoNightDayConfig } from './light/tokyo-night-day'; + +/** + * 主题配置映射表 + * key: 主题名称(与数据库中的 name 字段一致) + * value: 主题颜色配置 + */ +const themeConfigMap: Record = { + // 深色主题 + 'dracula': draculaConfig, + 'aura': auraConfig, + 'github-dark': githubDarkConfig, + 'material-dark': materialDarkConfig, + 'one-dark': oneDarkConfig, + 'solarized-dark': solarizedDarkConfig, + 'tokyo-night': tokyoNightConfig, + 'tokyo-night-storm': tokyoNightStormConfig, + + // 浅色主题 + 'github-light': githubLightConfig, + 'material-light': materialLightConfig, + 'solarized-light': solarizedLightConfig, + 'tokyo-night-day': tokyoNightDayConfig, +}; + +/** + * 根据主题名称获取主题配置 + */ +export function getThemeConfig(themeName: string): ThemeColors | null { + return themeConfigMap[themeName] || null; +} + + +/** + * 根据自定义颜色配置创建主题 + */ +export function createThemeByColors(colors: ThemeColors): Extension { + return createBaseTheme(colors); +} + + diff --git a/frontend/src/views/editor/theme/types.ts b/frontend/src/views/editor/theme/types.ts index ee70c7e..106403b 100644 --- a/frontend/src/views/editor/theme/types.ts +++ b/frontend/src/views/editor/theme/types.ts @@ -1,50 +1,52 @@ export interface ThemeColors { // 主题基本信息 - name?: string; // 主题名称 - dark?: boolean; // 是否为深色主题标识 + name: string; // 主题名称 + dark: boolean; // 是否为深色主题标识 // 基础色调 background: string; // 主背景色 - backgroundSecondary?: string; // 次要背景色 - surface?: string; // 面板背景 - dropdownBackground?: string; // 下拉菜单背景 - dropdownBorder?: string; // 下拉菜单边框 + backgroundSecondary: string; // 次要背景色(用于代码块交替背景) + surface: string; // 面板背景 + dropdownBackground: string; // 下拉菜单背景 + dropdownBorder: string; // 下拉菜单边框 // 文本颜色 foreground: string; // 主文本色 - foregroundSecondary?: string; // 次要文本色 + foregroundSecondary: string; // 次要文本色 comment: string; // 注释色 - // 语法高亮色 + // 语法高亮色 - 核心 keyword: string; // 关键字 string: string; // 字符串 function: string; // 函数名 number: string; // 数字 - operator?: string; // 操作符 + operator: string; // 操作符 variable: string; // 变量 type: string; // 类型 + + // 语法高亮色 - 扩展 constant: string; // 常量 - storage?: string; // 存储类型 - parameter?: string; // 参数 - class?: string; // 类名 - heading?: string; // 标题 - invalid?: string; // 无效内容 - regexp?: string; // 正则表达式 + storage: string; // 存储类型(如 static, const) + parameter: string; // 参数 + class: string; // 类名 + heading: string; // 标题(Markdown等) + invalid: string; // 无效内容/错误 + regexp: string; // 正则表达式 // 界面元素 cursor: string; // 光标 selection: string; // 选中背景 - selectionBlur?: string; // 失焦选中背景 + selectionBlur: string; // 失焦选中背景 activeLine: string; // 当前行高亮 lineNumber: string; // 行号 activeLineNumber: string; // 活动行号颜色 // 边框和分割线 - borderColor?: string; // 边框色 - borderLight?: string; // 浅色边框 + borderColor: string; // 边框色 + borderLight: string; // 浅色边框 // 搜索和匹配 - searchMatch?: string; // 搜索匹配 - matchingBracket?: string; // 匹配括号 + searchMatch: string; // 搜索匹配 + matchingBracket: string; // 匹配括号 } diff --git a/frontend/src/views/settings/pages/AppearancePage.vue b/frontend/src/views/settings/pages/AppearancePage.vue index 5d2e2e6..648b963 100644 --- a/frontend/src/views/settings/pages/AppearancePage.vue +++ b/frontend/src/views/settings/pages/AppearancePage.vue @@ -6,11 +6,10 @@ import { computed, watch, onMounted, ref } from 'vue'; import SettingSection from '../components/SettingSection.vue'; import SettingItem from '../components/SettingItem.vue'; import { SystemThemeType, LanguageType } from '@/../bindings/voidraft/internal/models/models'; -import { defaultDarkColors } from '@/views/editor/theme/dark'; -import { defaultLightColors } from '@/views/editor/theme/light'; import { createDebounce } from '@/common/utils/debounce'; import { createTimerManager } from '@/common/utils/timerUtils'; import PickColors from 'vue-pick-colors'; +import type { ThemeColors } from '@/views/editor/theme/types'; const { t } = useI18n(); const configStore = useConfigStore(); @@ -24,14 +23,12 @@ const { debouncedFn: debouncedUpdateColor } = createDebounce( const { debouncedFn: debouncedResetTheme } = createDebounce( async () => { - const themeType = activeThemeType.value; - const success = await themeStore.resetThemeColors(themeType); + const isDark = isDarkMode.value; + const success = await themeStore.resetCurrentTheme(isDark); if (success) { - tempColors.value = { - darkTheme: { ...themeStore.themeColors.darkTheme }, - lightTheme: { ...themeStore.themeColors.lightTheme } - }; + // 重新加载临时颜色 + syncTempColors(); hasUnsavedChanges.value = false; } }, @@ -41,11 +38,8 @@ const { debouncedFn: debouncedResetTheme } = createDebounce( // 创建定时器管理器 const resetTimer = createTimerManager(); -// 添加临时颜色状态 -const tempColors = ref({ - darkTheme: { ...defaultDarkColors }, - lightTheme: { ...defaultLightColors } -}); +// 临时颜色状态(用于编辑) +const tempColors = ref(null); // 标记是否有未保存的更改 const hasUnsavedChanges = ref(false); @@ -55,81 +49,72 @@ const resetButtonState = ref({ confirming: false }); -// 当前激活的主题类型 +// 当前激活的主题类型(深色/浅色) const isDarkMode = computed(() => themeStore.currentTheme === SystemThemeType.SystemThemeDark || (themeStore.currentTheme === SystemThemeType.SystemThemeAuto && window.matchMedia('(prefers-color-scheme: dark)').matches) ); -const activeThemeType = computed(() => isDarkMode.value ? 'darkTheme' : 'lightTheme'); +// 当前可用的预设主题列表 +const availableThemes = computed(() => + isDarkMode.value ? themeStore.darkThemes : themeStore.lightThemes +); -// 当前主题的颜色配置 -const currentColors = computed(() => { - const themeType = activeThemeType.value; - return tempColors.value[themeType] || - (themeType === 'darkTheme' ? defaultDarkColors : defaultLightColors); +// 当前选中的主题ID +const currentThemeId = computed({ + get: () => isDarkMode.value ? themeStore.currentThemeIds.dark : themeStore.currentThemeIds.light, + set: async (themeId: number) => { + await themeStore.switchToTheme(themeId); + syncTempColors(); + hasUnsavedChanges.value = false; + } }); -// 获取当前主题模式 +// 当前主题的颜色配置 +const currentColors = computed(() => tempColors.value || ({} as ThemeColors)); + +// 获取当前主题模式(用于颜色选择器) const currentThemeMode = computed(() => isDarkMode.value ? 'dark' : 'light'); -// 监听主题颜色变更, +// 同步临时颜色从 store +const syncTempColors = () => { + const colors = isDarkMode.value ? themeStore.currentColors.dark : themeStore.currentColors.light; + if (colors) { + tempColors.value = { ...colors }; + } +}; + +// 监听主题切换,同步临时颜色 watch( - () => themeStore.themeColors, - (newValue) => { + [() => themeStore.currentColors.dark, () => themeStore.currentColors.light, isDarkMode], + () => { if (!hasUnsavedChanges.value) { - tempColors.value.darkTheme = { ...newValue.darkTheme }; - tempColors.value.lightTheme = { ...newValue.lightTheme }; + syncTempColors(); } }, - { deep: true, immediate: true } + { deep: true } ); onMounted(() => { - tempColors.value = { - darkTheme: { ...themeStore.themeColors.darkTheme }, - lightTheme: { ...themeStore.themeColors.lightTheme } - }; + syncTempColors(); }); -// 颜色配置 -const colorConfig = [ - { - key: 'basic', - colors: ['background', 'backgroundSecondary', 'surface'] - }, - { - key: 'text', - colors: ['foreground', 'foregroundSecondary', 'comment'] - }, - { - key: 'syntax', - colors: ['keyword', 'string', 'function', 'number', 'operator', 'variable', 'type'] - }, - { - key: 'interface', - colors: ['cursor', 'selection', 'selectionBlur', 'activeLine', 'lineNumber', 'activeLineNumber'] - }, - { - key: 'border', - colors: ['borderColor', 'borderLight'] - }, - { - key: 'search', - colors: ['searchMatch', 'matchingBracket'] - } -]; +// 从 ThemeColors 接口自动提取所有颜色字段 +const colorKeys = computed(() => { + if (!tempColors.value) return []; + + // 获取所有字段,排除 name 和 dark(这两个是元数据) + return Object.keys(tempColors.value).filter(key => + key !== 'name' && key !== 'dark' + ); +}); -// 颜色配置分组 -const colorGroups = computed(() => - colorConfig.map(group => ({ - key: group.key, - title: t(`settings.themeColors.${group.key}`), - colors: group.colors.map(colorKey => ({ - key: colorKey, - label: t(`settings.themeColors.${colorKey}`) - })) +// 颜色配置列表 +const colorList = computed(() => + colorKeys.value.map(colorKey => ({ + key: colorKey, + label: t(`settings.themeColors.${colorKey}`) })) ); @@ -152,15 +137,12 @@ const handleResetClick = () => { // 更新本地颜色配置 const updateLocalColor = (colorKey: string, value: string) => { - const themeType = activeThemeType.value; + if (!tempColors.value) return; // 更新临时颜色 tempColors.value = { ...tempColors.value, - [themeType]: { - ...tempColors.value[themeType], - [colorKey]: value - } + [colorKey]: value }; hasUnsavedChanges.value = true; @@ -169,17 +151,15 @@ const updateLocalColor = (colorKey: string, value: string) => { // 应用颜色更改到系统 const applyChanges = async () => { try { - // 获取当前主题的自定义颜色 - const customTheme = { - darkTheme: tempColors.value.darkTheme, - lightTheme: tempColors.value.lightTheme - }; + if (!tempColors.value) return; - // 更新themeStore中的颜色 - themeStore.updateThemeColors(customTheme.darkTheme, customTheme.lightTheme); + const isDark = isDarkMode.value; - // 保存到配置 - await themeStore.saveThemeColors(); + // 更新 store 中的颜色 + themeStore.updateCurrentColors(tempColors.value, isDark); + + // 保存到数据库 + await themeStore.saveCurrentTheme(isDark); // 刷新编辑器主题 themeStore.refreshEditorTheme(); @@ -193,11 +173,8 @@ const applyChanges = async () => { // 取消颜色更改 const cancelChanges = () => { - // 恢复到themeStore中的颜色 - tempColors.value = { - darkTheme: { ...themeStore.themeColors.darkTheme }, - lightTheme: { ...themeStore.themeColors.lightTheme } - }; + // 恢复到 store 中的颜色 + syncTempColors(); // 清除未保存标记 hasUnsavedChanges.value = false; @@ -266,6 +243,15 @@ const handlePickerClose = () => { + + + + + @@ -290,45 +276,41 @@ const handlePickerClose = () => { -
-
-

{{ group.title }}

-
- -
-
- -
- -
-
+ +
+ +
+
+ +
+
-
+
@@ -430,27 +412,11 @@ const handlePickerClose = () => { } } -.color-groups { - display: flex; - flex-direction: column; - gap: 24px; -} - -.color-group { - .group-title { - font-size: 14px; - font-weight: 600; - color: var(--settings-text); - margin: 0 0 12px 0; - padding-bottom: 6px; - border-bottom: 1px solid var(--settings-input-border); - } - - .color-items { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); - gap: 8px; - } +.color-list { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); + gap: 12px; + margin-top: 12px; } .color-setting-item { diff --git a/go.mod b/go.mod index b0bae0d..a83e76c 100644 --- a/go.mod +++ b/go.mod @@ -10,11 +10,11 @@ require ( github.com/knadh/koanf/providers/structs v1.0.0 github.com/knadh/koanf/v2 v2.3.0 github.com/stretchr/testify v1.11.1 - github.com/wailsapp/wails/v3 v3.0.0-alpha.34 + github.com/wailsapp/wails/v3 v3.0.0-alpha.36 golang.org/x/net v0.46.0 golang.org/x/sys v0.37.0 golang.org/x/text v0.30.0 - modernc.org/sqlite v1.39.0 + modernc.org/sqlite v1.39.1 ) require ( @@ -72,7 +72,7 @@ require ( github.com/sergi/go-diff v1.4.0 // indirect github.com/skeema/knownhosts v1.3.2 // indirect github.com/ulikunitz/xz v0.5.15 // indirect - github.com/wailsapp/go-webview2 v1.0.21 // indirect + github.com/wailsapp/go-webview2 v1.0.22 // indirect github.com/wailsapp/mimetype v1.4.1 // indirect github.com/xanzy/go-gitlab v0.115.0 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect diff --git a/go.sum b/go.sum index 27cf009..8d8df52 100644 --- a/go.sum +++ b/go.sum @@ -162,12 +162,12 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY= github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/wailsapp/go-webview2 v1.0.21 h1:k3dtoZU4KCoN/AEIbWiPln3P2661GtA2oEgA2Pb+maA= -github.com/wailsapp/go-webview2 v1.0.21/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc= +github.com/wailsapp/go-webview2 v1.0.22 h1:YT61F5lj+GGaat5OB96Aa3b4QA+mybD0Ggq6NZijQ58= +github.com/wailsapp/go-webview2 v1.0.22/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc= github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= -github.com/wailsapp/wails/v3 v3.0.0-alpha.34 h1:t6NwHOLJzXuESb3YSXarSd1C/U1h2CpXF+BLr0ekj2g= -github.com/wailsapp/wails/v3 v3.0.0-alpha.34/go.mod h1:UZpnhYuju4saspCJrIHAvC0H5XjtKnqd26FRxJLrQ0M= +github.com/wailsapp/wails/v3 v3.0.0-alpha.36 h1:GQ8vSrFgafITwMd/p4k+WBjG9K/anma9Pk2eJ/5CLsI= +github.com/wailsapp/wails/v3 v3.0.0-alpha.36/go.mod h1:7i8tSuA74q97zZ5qEJlcVZdnO+IR7LT2KU8UpzYMPsw= github.com/xanzy/go-gitlab v0.115.0 h1:6DmtItNcVe+At/liXSgfE/DZNZrGfalQmBRmOcJjOn8= github.com/xanzy/go-gitlab v0.115.0/go.mod h1:5XCDtM7AM6WMKmfDdOiEpyRWUqui2iS9ILfvCZ2gJ5M= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= @@ -256,8 +256,8 @@ modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= -modernc.org/sqlite v1.39.0 h1:6bwu9Ooim0yVYA7IZn9demiQk/Ejp0BtTjBWFLymSeY= -modernc.org/sqlite v1.39.0/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E= +modernc.org/sqlite v1.39.1 h1:H+/wGFzuSCIEVCvXYVHX5RQglwhMOvtHSv+VtidL2r4= +modernc.org/sqlite v1.39.1/go.mod h1:9fjQZ0mB1LLP0GYrp39oOJXx/I2sxEnZtzCmEQIKvGE= modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/internal/models/theme.go b/internal/models/theme.go index 38933c2..20c11ae 100644 --- a/internal/models/theme.go +++ b/internal/models/theme.go @@ -14,17 +14,25 @@ const ( ThemeTypeLight ThemeType = "light" ) -// ThemeColorConfig 主题颜色配置 +// ThemeColorConfig 主题颜色配置(与前端 ThemeColors 接口保持一致) type ThemeColorConfig struct { + // 主题基本信息 + Name string `json:"name"` // 主题名称 + Dark bool `json:"dark"` // 是否为深色主题 + // 基础色调 Background string `json:"background"` // 主背景色 - BackgroundSecondary string `json:"backgroundSecondary"` // 次要背景色 + BackgroundSecondary string `json:"backgroundSecondary"` // 次要背景色(用于代码块交替背景) Surface string `json:"surface"` // 面板背景 + DropdownBackground string `json:"dropdownBackground"` // 下拉菜单背景 + DropdownBorder string `json:"dropdownBorder"` // 下拉菜单边框 + + // 文本颜色 Foreground string `json:"foreground"` // 主文本色 ForegroundSecondary string `json:"foregroundSecondary"` // 次要文本色 + Comment string `json:"comment"` // 注释色 - // 语法高亮 - Comment string `json:"comment"` // 注释色 + // 语法高亮色 - 核心 Keyword string `json:"keyword"` // 关键字 String string `json:"string"` // 字符串 Function string `json:"function"` // 函数名 @@ -33,34 +41,32 @@ type ThemeColorConfig struct { Variable string `json:"variable"` // 变量 Type string `json:"type"` // 类型 + // 语法高亮色 - 扩展 + Constant string `json:"constant"` // 常量 + Storage string `json:"storage"` // 存储类型(如 static, const) + Parameter string `json:"parameter"` // 参数 + Class string `json:"class"` // 类名 + Heading string `json:"heading"` // 标题(Markdown等) + Invalid string `json:"invalid"` // 无效内容/错误 + Regexp string `json:"regexp"` // 正则表达式 + // 界面元素 Cursor string `json:"cursor"` // 光标 Selection string `json:"selection"` // 选中背景 SelectionBlur string `json:"selectionBlur"` // 失焦选中背景 ActiveLine string `json:"activeLine"` // 当前行高亮 LineNumber string `json:"lineNumber"` // 行号 - ActiveLineNumber string `json:"activeLineNumber"` // 活动行号 + ActiveLineNumber string `json:"activeLineNumber"` // 活动行号颜色 - // 边框分割线 + // 边框和分割线 BorderColor string `json:"borderColor"` // 边框色 BorderLight string `json:"borderLight"` // 浅色边框 - // 搜索匹配 + // 搜索和匹配 SearchMatch string `json:"searchMatch"` // 搜索匹配 MatchingBracket string `json:"matchingBracket"` // 匹配括号 } -// Theme 主题数据库模型 -type Theme struct { - ID int `db:"id" json:"id"` - Name string `db:"name" json:"name"` - Type ThemeType `db:"type" json:"type"` - Colors ThemeColorConfig `db:"colors" json:"colors"` - IsDefault bool `db:"is_default" json:"isDefault"` - CreatedAt string `db:"created_at" json:"createdAt"` - UpdatedAt string `db:"updated_at" json:"updatedAt"` -} - // Value 实现 driver.Valuer 接口,用于将 ThemeColorConfig 存储到数据库 func (tc ThemeColorConfig) Value() (driver.Value, error) { return json.Marshal(tc) @@ -85,18 +91,37 @@ func (tc *ThemeColorConfig) Scan(value interface{}) error { return json.Unmarshal(bytes, tc) } -// NewDefaultDarkTheme 创建默认深色主题配置 +// Theme 主题数据库模型 +type Theme struct { + ID int `db:"id" json:"id"` + Name string `db:"name" json:"name"` + Type ThemeType `db:"type" json:"type"` + Colors ThemeColorConfig `db:"colors" json:"colors"` + IsDefault bool `db:"is_default" json:"isDefault"` + CreatedAt string `db:"created_at" json:"createdAt"` + UpdatedAt string `db:"updated_at" json:"updatedAt"` +} + +// NewDefaultDarkTheme 创建默认深色主题配置(与前端 defaultDarkColors 完全一致) func NewDefaultDarkTheme() *ThemeColorConfig { return &ThemeColorConfig{ + // 主题信息 + Name: "default-dark", + Dark: true, + // 基础色调 Background: "#252B37", BackgroundSecondary: "#213644", Surface: "#474747", + DropdownBackground: "#252B37", + DropdownBorder: "#ffffff19", + + // 文本颜色 Foreground: "#9BB586", ForegroundSecondary: "#9c9c9c", + Comment: "#6272a4", - // 语法高亮 - Comment: "#6272a4", + // 语法高亮色 - 核心 Keyword: "#ff79c6", String: "#f1fa8c", Function: "#50fa7b", @@ -105,6 +130,15 @@ func NewDefaultDarkTheme() *ThemeColorConfig { Variable: "#8fbcbb", Type: "#8be9fd", + // 语法高亮色 - 扩展 + Constant: "#bd93f9", + Storage: "#ff79c6", + Parameter: "#8fbcbb", + Class: "#8be9fd", + Heading: "#ff79c6", + Invalid: "#d30102", + Regexp: "#f1fa8c", + // 界面元素 Cursor: "#ffffff", Selection: "#0865a9", @@ -113,28 +147,36 @@ func NewDefaultDarkTheme() *ThemeColorConfig { LineNumber: "#ffffff26", ActiveLineNumber: "#ffffff99", - // 边框分割线 + // 边框和分割线 BorderColor: "#1e222a", - BorderLight: "#ffffff1a", + BorderLight: "#ffffff19", - // 搜索匹配 + // 搜索和匹配 SearchMatch: "#8fbcbb", - MatchingBracket: "#ffffff1a", + MatchingBracket: "#ffffff19", } } -// NewDefaultLightTheme 创建默认浅色主题配置 +// NewDefaultLightTheme 创建默认浅色主题配置(与前端 defaultLightColors 完全一致) func NewDefaultLightTheme() *ThemeColorConfig { return &ThemeColorConfig{ + // 主题信息 + Name: "default-light", + Dark: false, + // 基础色调 Background: "#ffffff", BackgroundSecondary: "#f1faf1", Surface: "#f5f5f5", + DropdownBackground: "#ffffff", + DropdownBorder: "#e1e4e8", + + // 文本颜色 Foreground: "#444d56", ForegroundSecondary: "#6a737d", + Comment: "#6a737d", - // 语法高亮 - Comment: "#6a737d", + // 语法高亮色 - 核心 Keyword: "#d73a49", String: "#032f62", Function: "#005cc5", @@ -143,6 +185,15 @@ func NewDefaultLightTheme() *ThemeColorConfig { Variable: "#24292e", Type: "#6f42c1", + // 语法高亮色 - 扩展 + Constant: "#005cc5", + Storage: "#d73a49", + Parameter: "#24292e", + Class: "#6f42c1", + Heading: "#d73a49", + Invalid: "#cb2431", + Regexp: "#032f62", + // 界面元素 Cursor: "#000000", Selection: "#77baff", @@ -151,12 +202,578 @@ func NewDefaultLightTheme() *ThemeColorConfig { LineNumber: "#00000040", ActiveLineNumber: "#000000aa", - // 边框分割线 + // 边框和分割线 BorderColor: "#dfdfdf", BorderLight: "#0000000c", - // 搜索匹配 + // 搜索和匹配 SearchMatch: "#005cc5", MatchingBracket: "#00000019", } } + +// NewDraculaTheme 创建 Dracula 深色主题配置 +func NewDraculaTheme() *ThemeColorConfig { + return &ThemeColorConfig{ + Name: "dracula", + Dark: true, + + Background: "#282A36", + BackgroundSecondary: "#282A36", + Surface: "#282A36", + DropdownBackground: "#282A36", + DropdownBorder: "#191A21", + + Foreground: "#F8F8F2", + ForegroundSecondary: "#F8F8F2", + Comment: "#6272A4", + + Keyword: "#FF79C6", + String: "#F1FA8C", + Function: "#50FA7B", + Number: "#BD93F9", + Operator: "#FF79C6", + Variable: "#F8F8F2", + Type: "#8BE9FD", + + Constant: "#BD93F9", + Storage: "#FF79C6", + Parameter: "#F8F8F2", + Class: "#8BE9FD", + Heading: "#BD93F9", + Invalid: "#FF5555", + Regexp: "#F1FA8C", + + Cursor: "#F8F8F2", + Selection: "#44475A", + SelectionBlur: "#44475A", + ActiveLine: "#53576c22", + LineNumber: "#6272A4", + ActiveLineNumber: "#F8F8F2", + + BorderColor: "#191A21", + BorderLight: "#F8F8F219", + + SearchMatch: "#50FA7B", + MatchingBracket: "#44475A", + } +} + +// NewAuraTheme 创建 Aura 深色主题配置 +func NewAuraTheme() *ThemeColorConfig { + return &ThemeColorConfig{ + Name: "aura", + Dark: true, + + Background: "#21202e", + BackgroundSecondary: "#21202e", + Surface: "#21202e", + DropdownBackground: "#21202e", + DropdownBorder: "#3b334b", + + Foreground: "#edecee", + ForegroundSecondary: "#edecee", + Comment: "#6d6d6d", + + Keyword: "#a277ff", + String: "#61ffca", + Function: "#ffca85", + Number: "#61ffca", + Operator: "#a277ff", + Variable: "#edecee", + Type: "#82e2ff", + + Constant: "#61ffca", + Storage: "#a277ff", + Parameter: "#edecee", + Class: "#82e2ff", + Heading: "#a277ff", + Invalid: "#ff6767", + Regexp: "#61ffca", + + Cursor: "#a277ff", + Selection: "#3d375e7f", + SelectionBlur: "#3d375e7f", + ActiveLine: "#4d4b6622", + LineNumber: "#a394f033", + ActiveLineNumber: "#cdccce", + + BorderColor: "#3b334b", + BorderLight: "#edecee19", + + SearchMatch: "#61ffca", + MatchingBracket: "#a394f033", + } +} + +// NewGitHubDarkTheme 创建 GitHub Dark 主题配置 +func NewGitHubDarkTheme() *ThemeColorConfig { + return &ThemeColorConfig{ + Name: "github-dark", + Dark: true, + + Background: "#24292e", + BackgroundSecondary: "#24292e", + Surface: "#24292e", + DropdownBackground: "#24292e", + DropdownBorder: "#1b1f23", + + Foreground: "#d1d5da", + ForegroundSecondary: "#d1d5da", + Comment: "#6a737d", + + Keyword: "#f97583", + String: "#9ecbff", + Function: "#79b8ff", + Number: "#79b8ff", + Operator: "#f97583", + Variable: "#ffab70", + Type: "#79b8ff", + + Constant: "#79b8ff", + Storage: "#f97583", + Parameter: "#e1e4e8", + Class: "#b392f0", + Heading: "#79b8ff", + Invalid: "#f97583", + Regexp: "#9ecbff", + + Cursor: "#c8e1ff", + Selection: "#3392FF44", + SelectionBlur: "#3392FF44", + ActiveLine: "#4d566022", + LineNumber: "#444d56", + ActiveLineNumber: "#e1e4e8", + + BorderColor: "#1b1f23", + BorderLight: "#d1d5da19", + + SearchMatch: "#79b8ff", + MatchingBracket: "#17E5E650", + } +} + +// NewMaterialDarkTheme 创建 Material Dark 主题配置 +func NewMaterialDarkTheme() *ThemeColorConfig { + return &ThemeColorConfig{ + Name: "material-dark", + Dark: true, + + Background: "#263238", + BackgroundSecondary: "#263238", + Surface: "#263238", + DropdownBackground: "#263238", + DropdownBorder: "#FFFFFF10", + + Foreground: "#EEFFFF", + ForegroundSecondary: "#EEFFFF", + Comment: "#546E7A", + + Keyword: "#C792EA", + String: "#C3E88D", + Function: "#82AAFF", + Number: "#F78C6C", + Operator: "#C792EA", + Variable: "#EEFFFF", + Type: "#B2CCD6", + + Constant: "#F78C6C", + Storage: "#C792EA", + Parameter: "#EEFFFF", + Class: "#FFCB6B", + Heading: "#C3E88D", + Invalid: "#FF5370", + Regexp: "#89DDFF", + + Cursor: "#FFCC00", + Selection: "#80CBC420", + SelectionBlur: "#80CBC420", + ActiveLine: "#4c616c22", + LineNumber: "#37474F", + ActiveLineNumber: "#607a86", + + BorderColor: "#FFFFFF10", + BorderLight: "#EEFFFF19", + + SearchMatch: "#82AAFF", + MatchingBracket: "#263238", + } +} + +// NewOneDarkTheme 创建 One Dark 主题配置 +func NewOneDarkTheme() *ThemeColorConfig { + return &ThemeColorConfig{ + Name: "one-dark", + Dark: true, + + Background: "#282c34", + BackgroundSecondary: "#2c313a", + Surface: "#353a42", + DropdownBackground: "#21252b", + DropdownBorder: "#7d8799", + + Foreground: "#abb2bf", + ForegroundSecondary: "#7d8799", + Comment: "#7d8799", + + Keyword: "#c678dd", + String: "#98c379", + Function: "#61afef", + Number: "#e5c07b", + Operator: "#56b6c2", + Variable: "#e06c75", + Type: "#e5c07b", + + Constant: "#d19a66", + Storage: "#c678dd", + Parameter: "#e06c75", + Class: "#e5c07b", + Heading: "#e06c75", + Invalid: "#ffffff", + Regexp: "#56b6c2", + + Cursor: "#528bff", + Selection: "#3E4451", + SelectionBlur: "#3E4451", + ActiveLine: "#6699ff0b", + LineNumber: "#7d8799", + ActiveLineNumber: "#abb2bf", + + BorderColor: "#21252b", + BorderLight: "#abb2bf19", + + SearchMatch: "#61afef", + MatchingBracket: "#bad0f847", + } +} + +// NewSolarizedDarkTheme 创建 Solarized Dark 主题配置 +func NewSolarizedDarkTheme() *ThemeColorConfig { + return &ThemeColorConfig{ + Name: "solarized-dark", + Dark: true, + + Background: "#002B36", + BackgroundSecondary: "#002B36", + Surface: "#002B36", + DropdownBackground: "#002B36", + DropdownBorder: "#2AA19899", + + Foreground: "#93A1A1", + ForegroundSecondary: "#93A1A1", + Comment: "#586E75", + + Keyword: "#859900", + String: "#2AA198", + Function: "#268BD2", + Number: "#D33682", + Operator: "#859900", + Variable: "#268BD2", + Type: "#CB4B16", + + Constant: "#CB4B16", + Storage: "#93A1A1", + Parameter: "#268BD2", + Class: "#CB4B16", + Heading: "#268BD2", + Invalid: "#DC322F", + Regexp: "#DC322F", + + Cursor: "#D30102", + Selection: "#274642", + SelectionBlur: "#274642", + ActiveLine: "#005b7022", + LineNumber: "#93A1A1", + ActiveLineNumber: "#949494", + + BorderColor: "#073642", + BorderLight: "#93A1A119", + + SearchMatch: "#2AA198", + MatchingBracket: "#073642", + } +} + +// NewTokyoNightTheme 创建 Tokyo Night 主题配置 +func NewTokyoNightTheme() *ThemeColorConfig { + return &ThemeColorConfig{ + Name: "tokyo-night", + Dark: true, + + Background: "#1a1b26", + BackgroundSecondary: "#1a1b26", + Surface: "#1a1b26", + DropdownBackground: "#1a1b26", + DropdownBorder: "#787c99", + + Foreground: "#787c99", + ForegroundSecondary: "#787c99", + Comment: "#444b6a", + + Keyword: "#bb9af7", + String: "#9ece6a", + Function: "#7aa2f7", + Number: "#ff9e64", + Operator: "#bb9af7", + Variable: "#c0caf5", + Type: "#0db9d7", + + Constant: "#bb9af7", + Storage: "#bb9af7", + Parameter: "#c0caf5", + Class: "#c0caf5", + Heading: "#89ddff", + Invalid: "#ff5370", + Regexp: "#b4f9f8", + + Cursor: "#c0caf5", + Selection: "#515c7e40", + SelectionBlur: "#515c7e40", + ActiveLine: "#43455c22", + LineNumber: "#363b54", + ActiveLineNumber: "#737aa2", + + BorderColor: "#16161e", + BorderLight: "#787c9919", + + SearchMatch: "#7aa2f7", + MatchingBracket: "#16161e", + } +} + +// NewTokyoNightStormTheme 创建 Tokyo Night Storm 主题配置 +func NewTokyoNightStormTheme() *ThemeColorConfig { + return &ThemeColorConfig{ + Name: "tokyo-night-storm", + Dark: true, + + Background: "#24283b", + BackgroundSecondary: "#24283b", + Surface: "#24283b", + DropdownBackground: "#24283b", + DropdownBorder: "#7982a9", + + Foreground: "#7982a9", + ForegroundSecondary: "#7982a9", + Comment: "#565f89", + + Keyword: "#bb9af7", + String: "#9ece6a", + Function: "#7aa2f7", + Number: "#ff9e64", + Operator: "#bb9af7", + Variable: "#c0caf5", + Type: "#2ac3de", + + Constant: "#bb9af7", + Storage: "#bb9af7", + Parameter: "#c0caf5", + Class: "#c0caf5", + Heading: "#89ddff", + Invalid: "#ff5370", + Regexp: "#b4f9f8", + + Cursor: "#c0caf5", + Selection: "#6f7bb630", + SelectionBlur: "#6f7bb630", + ActiveLine: "#4d547722", + LineNumber: "#3b4261", + ActiveLineNumber: "#737aa2", + + BorderColor: "#1f2335", + BorderLight: "#7982a919", + + SearchMatch: "#7aa2f7", + MatchingBracket: "#1f2335", + } +} + +// 浅色主题预设配置 + +// NewGitHubLightTheme 创建 GitHub Light 主题配置 +func NewGitHubLightTheme() *ThemeColorConfig { + return &ThemeColorConfig{ + Name: "github-light", + Dark: false, + + Background: "#fff", + BackgroundSecondary: "#fff", + Surface: "#fff", + DropdownBackground: "#fff", + DropdownBorder: "#e1e4e8", + + Foreground: "#444d56", + ForegroundSecondary: "#444d56", + Comment: "#6a737d", + + Keyword: "#d73a49", + String: "#032f62", + Function: "#005cc5", + Number: "#005cc5", + Operator: "#d73a49", + Variable: "#e36209", + Type: "#005cc5", + + Constant: "#005cc5", + Storage: "#d73a49", + Parameter: "#24292e", + Class: "#6f42c1", + Heading: "#005cc5", + Invalid: "#cb2431", + Regexp: "#032f62", + + Cursor: "#044289", + Selection: "#0366d625", + SelectionBlur: "#0366d625", + ActiveLine: "#c6c6c622", + LineNumber: "#1b1f234d", + ActiveLineNumber: "#24292e", + + BorderColor: "#e1e4e8", + BorderLight: "#444d5619", + + SearchMatch: "#005cc5", + MatchingBracket: "#34d05840", + } +} + +// NewMaterialLightTheme 创建 Material Light 主题配置 +func NewMaterialLightTheme() *ThemeColorConfig { + return &ThemeColorConfig{ + Name: "material-light", + Dark: false, + + Background: "#FAFAFA", + BackgroundSecondary: "#FAFAFA", + Surface: "#FAFAFA", + DropdownBackground: "#FAFAFA", + DropdownBorder: "#00000010", + + Foreground: "#90A4AE", + ForegroundSecondary: "#90A4AE", + Comment: "#90A4AE", + + Keyword: "#7C4DFF", + String: "#91B859", + Function: "#6182B8", + Number: "#F76D47", + Operator: "#7C4DFF", + Variable: "#90A4AE", + Type: "#8796B0", + + Constant: "#F76D47", + Storage: "#7C4DFF", + Parameter: "#90A4AE", + Class: "#FFB62C", + Heading: "#91B859", + Invalid: "#E53935", + Regexp: "#39ADB5", + + Cursor: "#272727", + Selection: "#80CBC440", + SelectionBlur: "#80CBC440", + ActiveLine: "#c2c2c222", + LineNumber: "#CFD8DC", + ActiveLineNumber: "#7E939E", + + BorderColor: "#00000010", + BorderLight: "#90A4AE19", + + SearchMatch: "#6182B8", + MatchingBracket: "#FAFAFA", + } +} + +// NewSolarizedLightTheme 创建 Solarized Light 主题配置 +func NewSolarizedLightTheme() *ThemeColorConfig { + return &ThemeColorConfig{ + Name: "solarized-light", + Dark: false, + + Background: "#FDF6E3", + BackgroundSecondary: "#FDF6E3", + Surface: "#FDF6E3", + DropdownBackground: "#FDF6E3", + DropdownBorder: "#D3AF86", + + Foreground: "#586E75", + ForegroundSecondary: "#586E75", + Comment: "#93A1A1", + + Keyword: "#859900", + String: "#2AA198", + Function: "#268BD2", + Number: "#D33682", + Operator: "#859900", + Variable: "#268BD2", + Type: "#CB4B16", + + Constant: "#CB4B16", + Storage: "#586E75", + Parameter: "#268BD2", + Class: "#CB4B16", + Heading: "#268BD2", + Invalid: "#DC322F", + Regexp: "#DC322F", + + Cursor: "#657B83", + Selection: "#EEE8D5", + SelectionBlur: "#EEE8D5", + ActiveLine: "#d5bd5c22", + LineNumber: "#586E75", + ActiveLineNumber: "#567983", + + BorderColor: "#EEE8D5", + BorderLight: "#586E7519", + + SearchMatch: "#268BD2", + MatchingBracket: "#EEE8D5", + } +} + +// NewTokyoNightDayTheme 创建 Tokyo Night Day 主题配置 +func NewTokyoNightDayTheme() *ThemeColorConfig { + return &ThemeColorConfig{ + Name: "tokyo-night-day", + Dark: false, + + Background: "#e1e2e7", + BackgroundSecondary: "#e1e2e7", + Surface: "#e1e2e7", + DropdownBackground: "#e1e2e7", + DropdownBorder: "#6a6f8e", + + Foreground: "#6a6f8e", + ForegroundSecondary: "#6a6f8e", + Comment: "#9da3c2", + + Keyword: "#9854f1", + String: "#587539", + Function: "#2e7de9", + Number: "#b15c00", + Operator: "#9854f1", + Variable: "#3760bf", + Type: "#07879d", + + Constant: "#9854f1", + Storage: "#9854f1", + Parameter: "#3760bf", + Class: "#3760bf", + Heading: "#006a83", + Invalid: "#ff3e64", + Regexp: "#2e5857", + + Cursor: "#3760bf", + Selection: "#8591b840", + SelectionBlur: "#8591b840", + ActiveLine: "#a7aaba22", + LineNumber: "#b3b6cd", + ActiveLineNumber: "#68709a", + + BorderColor: "#e9e9ec", + BorderLight: "#6a6f8e19", + + SearchMatch: "#2e7de9", + MatchingBracket: "#e9e9ec", + } +} diff --git a/internal/services/database_service.go b/internal/services/database_service.go index 053eb04..963c0bc 100644 --- a/internal/services/database_service.go +++ b/internal/services/database_service.go @@ -67,13 +67,12 @@ CREATE TABLE IF NOT EXISTS key_bindings ( sqlCreateThemesTable = ` CREATE TABLE IF NOT EXISTS themes ( id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL, + name TEXT NOT NULL UNIQUE, type TEXT NOT NULL, colors TEXT NOT NULL, is_default INTEGER NOT NULL DEFAULT 0, created_at TEXT NOT NULL, - updated_at TEXT NOT NULL, - UNIQUE(type, is_default) + updated_at TEXT NOT NULL )` ) @@ -222,6 +221,8 @@ func (ds *DatabaseService) createIndexes() error { // Themes indexes `CREATE INDEX IF NOT EXISTS idx_themes_type ON themes(type)`, `CREATE INDEX IF NOT EXISTS idx_themes_is_default ON themes(is_default)`, + // 条件唯一索引:确保每种类型只能有一个默认主题 + `CREATE UNIQUE INDEX IF NOT EXISTS idx_themes_type_default ON themes(type) WHERE is_default = 1`, } for _, index := range indexes { diff --git a/internal/services/theme_service.go b/internal/services/theme_service.go index 2f3e54f..968a716 100644 --- a/internal/services/theme_service.go +++ b/internal/services/theme_service.go @@ -3,6 +3,7 @@ package services import ( "context" "database/sql" + "errors" "fmt" "time" "voidraft/internal/models" @@ -43,96 +44,141 @@ func (ts *ThemeService) getDB() *sql.DB { return ts.databaseService.db } -// initializeDefaultThemes 初始化默认主题 +// initializeDefaultThemes 初始化所有预设主题 func (ts *ThemeService) initializeDefaultThemes() error { db := ts.getDB() if db == nil { return fmt.Errorf("database not available") } - // 检查是否已存在默认主题 - var count int - err := db.QueryRow("SELECT COUNT(*) FROM themes WHERE is_default = 1").Scan(&count) + // 获取所有已存在的主题名称 + existingThemes := make(map[string]bool) + rows, err := db.Query("SELECT name FROM themes") if err != nil { - return fmt.Errorf("failed to check existing themes: %w", err) - } - - if count > 0 { - return nil // 默认主题已存在 - } - - // 创建默认深色主题 - now := time.Now().Format("2006-01-02 15:04:05") - darkTheme := &models.Theme{ - Name: "Default Dark", - Type: models.ThemeTypeDark, - Colors: *models.NewDefaultDarkTheme(), - IsDefault: true, - CreatedAt: now, - UpdatedAt: now, - } - - // 创建默认浅色主题 - lightTheme := &models.Theme{ - Name: "Default Light", - Type: models.ThemeTypeLight, - Colors: *models.NewDefaultLightTheme(), - IsDefault: true, - CreatedAt: now, - UpdatedAt: now, - } - - // 插入默认主题 - if _, err := ts.CreateTheme(darkTheme); err != nil { - return fmt.Errorf("failed to create default dark theme: %w", err) - } - - if _, err := ts.CreateTheme(lightTheme); err != nil { - return fmt.Errorf("failed to create default light theme: %w", err) - } - - return nil -} - -// GetDefaultThemes 获取默认主题 -func (ts *ThemeService) GetDefaultThemes() (map[string]*models.Theme, error) { - query := ` - SELECT id, name, type, colors, is_default, created_at, updated_at - FROM themes - WHERE is_default = 1 - ORDER BY type - ` - - db := ts.getDB() - rows, err := db.Query(query) - if err != nil { - return nil, fmt.Errorf("failed to query default themes: %w", err) + return fmt.Errorf("failed to query existing themes: %w", err) } defer rows.Close() - themes := make(map[string]*models.Theme) for rows.Next() { - theme := &models.Theme{} - err := rows.Scan( - &theme.ID, - &theme.Name, - &theme.Type, - &theme.Colors, - &theme.IsDefault, - &theme.CreatedAt, - &theme.UpdatedAt, + var name string + if err := rows.Scan(&name); err != nil { + return fmt.Errorf("failed to scan theme name: %w", err) + } + existingThemes[name] = true + } + + // 定义所有预设主题配置 + now := time.Now().Format("2006-01-02 15:04:05") + presetThemes := []struct { + config *models.ThemeColorConfig + themeType models.ThemeType + isDefault bool + }{ + // 默认主题 + {models.NewDefaultDarkTheme(), models.ThemeTypeDark, true}, + {models.NewDefaultLightTheme(), models.ThemeTypeLight, true}, + + // 深色主题预设 + {models.NewDraculaTheme(), models.ThemeTypeDark, false}, + {models.NewAuraTheme(), models.ThemeTypeDark, false}, + {models.NewGitHubDarkTheme(), models.ThemeTypeDark, false}, + {models.NewMaterialDarkTheme(), models.ThemeTypeDark, false}, + {models.NewOneDarkTheme(), models.ThemeTypeDark, false}, + {models.NewSolarizedDarkTheme(), models.ThemeTypeDark, false}, + {models.NewTokyoNightTheme(), models.ThemeTypeDark, false}, + {models.NewTokyoNightStormTheme(), models.ThemeTypeDark, false}, + + // 浅色主题预设 + {models.NewGitHubLightTheme(), models.ThemeTypeLight, false}, + {models.NewMaterialLightTheme(), models.ThemeTypeLight, false}, + {models.NewSolarizedLightTheme(), models.ThemeTypeLight, false}, + {models.NewTokyoNightDayTheme(), models.ThemeTypeLight, false}, + } + + // 筛选出需要创建的主题 + var themesToCreate []*models.Theme + for _, preset := range presetThemes { + if !existingThemes[preset.config.Name] { + themesToCreate = append(themesToCreate, &models.Theme{ + Name: preset.config.Name, + Type: preset.themeType, + Colors: *preset.config, + IsDefault: preset.isDefault, + CreatedAt: now, + UpdatedAt: now, + }) + } + } + + if len(themesToCreate) == 0 { + return nil + } + + // 批量插入主题 + tx, err := db.Begin() + if err != nil { + return fmt.Errorf("failed to begin transaction: %w", err) + } + defer tx.Rollback() + + stmt, err := tx.Prepare(` + INSERT INTO themes (name, type, colors, is_default, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?) + `) + if err != nil { + return fmt.Errorf("failed to prepare statement: %w", err) + } + defer stmt.Close() + + for _, theme := range themesToCreate { + _, err := stmt.Exec( + theme.Name, + theme.Type, + theme.Colors, + theme.IsDefault, + theme.CreatedAt, + theme.UpdatedAt, ) if err != nil { - return nil, fmt.Errorf("failed to scan theme: %w", err) + return fmt.Errorf("failed to insert theme %s: %w", theme.Name, err) } - themes[string(theme.Type)] = theme } - if err := rows.Err(); err != nil { - return nil, fmt.Errorf("failed to iterate themes: %w", err) + if err := tx.Commit(); err != nil { + return fmt.Errorf("failed to commit transaction: %w", err) + } + return nil +} + +// GetThemeByID 根据ID获取主题 +func (ts *ThemeService) GetThemeByID(id int) (*models.Theme, error) { + query := ` + SELECT id, name, type, colors, is_default, created_at, updated_at + FROM themes + WHERE id = ? + LIMIT 1 + ` + + theme := &models.Theme{} + db := ts.getDB() + err := db.QueryRow(query, id).Scan( + &theme.ID, + &theme.Name, + &theme.Type, + &theme.Colors, + &theme.IsDefault, + &theme.CreatedAt, + &theme.UpdatedAt, + ) + + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, fmt.Errorf("theme not found with id: %d", id) + } + return nil, fmt.Errorf("failed to get theme by id: %w", err) } - return themes, nil + return theme, nil } // GetThemeByType 根据类型获取默认主题 @@ -166,37 +212,123 @@ func (ts *ThemeService) GetThemeByType(themeType models.ThemeType) (*models.Them return theme, nil } -// UpdateThemeColors 更新主题颜色 -func (ts *ThemeService) UpdateThemeColors(themeType models.ThemeType, colors models.ThemeColorConfig) error { +// GetThemesByType 根据类型获取所有主题 +func (ts *ThemeService) GetThemesByType(themeType models.ThemeType) ([]*models.Theme, error) { query := ` - UPDATE themes - SET colors = ?, updated_at = ? - WHERE type = ? AND is_default = 1 + SELECT id, name, type, colors, is_default, created_at, updated_at + FROM themes + WHERE type = ? + ORDER BY is_default DESC, name ASC ` db := ts.getDB() - _, err := db.Exec(query, colors, time.Now().Format("2006-01-02 15:04:05"), themeType) + rows, err := db.Query(query, themeType) if err != nil { - return fmt.Errorf("failed to update theme colors: %w", err) + return nil, fmt.Errorf("failed to query themes by type: %w", err) + } + defer rows.Close() + + var themes []*models.Theme + for rows.Next() { + theme := &models.Theme{} + err := rows.Scan( + &theme.ID, + &theme.Name, + &theme.Type, + &theme.Colors, + &theme.IsDefault, + &theme.CreatedAt, + &theme.UpdatedAt, + ) + if err != nil { + return nil, fmt.Errorf("failed to scan theme: %w", err) + } + themes = append(themes, theme) + } + + if err := rows.Err(); err != nil { + return nil, fmt.Errorf("failed to iterate themes: %w", err) + } + + return themes, nil +} + +// UpdateTheme 更新主题 +func (ts *ThemeService) UpdateTheme(id int, colors models.ThemeColorConfig) error { + query := ` + UPDATE themes + SET colors = ?, updated_at = ? + WHERE id = ? + ` + + db := ts.getDB() + result, err := db.Exec(query, colors, time.Now().Format("2006-01-02 15:04:05"), id) + if err != nil { + return fmt.Errorf("failed to update theme: %w", err) + } + + rowsAffected, err := result.RowsAffected() + if err != nil { + return fmt.Errorf("failed to get rows affected: %w", err) + } + + if rowsAffected == 0 { + return fmt.Errorf("theme not found with id: %d", id) } return nil } -// ResetThemeColors 重置主题颜色为默认值 -func (ts *ThemeService) ResetThemeColors(themeType models.ThemeType) error { - var defaultColors models.ThemeColorConfig - - switch themeType { - case models.ThemeTypeDark: - defaultColors = *models.NewDefaultDarkTheme() - case models.ThemeTypeLight: - defaultColors = *models.NewDefaultLightTheme() - default: - return fmt.Errorf("unknown theme type: %s", themeType) +// ResetTheme 重置主题为预设配置 +func (ts *ThemeService) ResetTheme(id int) error { + // 先获取主题信息 + theme, err := ts.GetThemeByID(id) + if err != nil { + return err } - return ts.UpdateThemeColors(themeType, defaultColors) + // 根据主题名称获取预设配置 + var presetConfig *models.ThemeColorConfig + switch theme.Name { + // 默认主题 + case "default-dark": + presetConfig = models.NewDefaultDarkTheme() + case "default-light": + presetConfig = models.NewDefaultLightTheme() + + // 深色主题预设 + case "dracula": + presetConfig = models.NewDraculaTheme() + case "aura": + presetConfig = models.NewAuraTheme() + case "github-dark": + presetConfig = models.NewGitHubDarkTheme() + case "material-dark": + presetConfig = models.NewMaterialDarkTheme() + case "one-dark": + presetConfig = models.NewOneDarkTheme() + case "solarized-dark": + presetConfig = models.NewSolarizedDarkTheme() + case "tokyo-night": + presetConfig = models.NewTokyoNightTheme() + case "tokyo-night-storm": + presetConfig = models.NewTokyoNightStormTheme() + + // 浅色主题预设 + case "github-light": + presetConfig = models.NewGitHubLightTheme() + case "material-light": + presetConfig = models.NewMaterialLightTheme() + case "solarized-light": + presetConfig = models.NewSolarizedLightTheme() + case "tokyo-night-day": + presetConfig = models.NewTokyoNightDayTheme() + + default: + return fmt.Errorf("no preset configuration found for theme: %s", theme.Name) + } + + return ts.UpdateTheme(id, *presetConfig) } // CreateTheme 创建新主题 @@ -235,7 +367,7 @@ func (ts *ThemeService) GetAllThemes() ([]*models.Theme, error) { query := ` SELECT id, name, type, colors, is_default, created_at, updated_at FROM themes - ORDER BY is_default DESC, created_at ASC + ORDER BY is_default DESC, type DESC, name ASC ` db := ts.getDB() diff --git a/version.txt b/version.txt index eb1b364..a4359ff 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -VERSION=1.5.0 +VERSION=1.5.1