diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 9e3b0b7..8cf1144 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -36,7 +36,7 @@ "@codemirror/lint": "^6.9.2", "@codemirror/search": "^6.5.11", "@codemirror/state": "^6.5.3", - "@codemirror/view": "^6.39.6", + "@codemirror/view": "^6.39.8", "@cospaia/prettier-plugin-clojure": "^0.0.2", "@lezer/highlight": "^1.2.3", "@lezer/lr": "^1.4.5", @@ -63,7 +63,7 @@ "prettier": "^3.7.4", "sass": "^1.97.1", "vue": "^3.5.26", - "vue-i18n": "^11.2.7", + "vue-i18n": "^11.2.8", "vue-pick-colors": "^1.8.0", "vue-router": "^4.6.4" }, @@ -77,9 +77,8 @@ "eslint": "^9.39.2", "eslint-plugin-vue": "^10.6.2", "globals": "^16.5.0", - "happy-dom": "^20.0.11", "typescript": "^5.9.3", - "typescript-eslint": "^8.50.1", + "typescript-eslint": "^8.51.0", "unplugin-vue-components": "^30.0.0", "vite": "npm:rolldown-vite@latest", "vite-plugin-node-polyfills": "^0.24.0", @@ -610,9 +609,9 @@ } }, "node_modules/@codemirror/view": { - "version": "6.39.6", - "resolved": "https://registry.npmmirror.com/@codemirror/view/-/view-6.39.6.tgz", - "integrity": "sha512-/N+SoP5NndJjkGInp3BwlUa3KQKD6bDo0TV6ep37ueAdQ7BVu/PqlZNywmgjCq0MQoZadZd8T+MZucSr7fktyQ==", + "version": "6.39.8", + "resolved": "https://registry.npmmirror.com/@codemirror/view/-/view-6.39.8.tgz", + "integrity": "sha512-1rASYd9Z/mE3tkbC9wInRlCNyCkSn+nLsiQKZhEDUUJiUfs/5FHDpCUDaQpoTIaNGeDc6/bhaEAyLmeEucEFPw==", "license": "MIT", "dependencies": { "@codemirror/state": "^6.5.0", @@ -1341,13 +1340,13 @@ } }, "node_modules/@intlify/core-base": { - "version": "11.2.7", - "resolved": "https://registry.npmmirror.com/@intlify/core-base/-/core-base-11.2.7.tgz", - "integrity": "sha512-+Ra9I/LAzXDnmv/IrTO03WMCiLya7pHRmGJvNl9fKwx/W4REJ0xaMk2PxCRqnxcBsX443amEMdebQ3R1geiuIw==", + "version": "11.2.8", + "resolved": "https://registry.npmmirror.com/@intlify/core-base/-/core-base-11.2.8.tgz", + "integrity": "sha512-nBq6Y1tVkjIUsLsdOjDSJj4AsjvD0UG3zsg9Fyc+OivwlA/oMHSKooUy9tpKj0HqZ+NWFifweHavdljlBLTwdA==", "license": "MIT", "dependencies": { - "@intlify/message-compiler": "11.2.7", - "@intlify/shared": "11.2.7" + "@intlify/message-compiler": "11.2.8", + "@intlify/shared": "11.2.8" }, "engines": { "node": ">= 16" @@ -1357,12 +1356,12 @@ } }, "node_modules/@intlify/message-compiler": { - "version": "11.2.7", - "resolved": "https://registry.npmmirror.com/@intlify/message-compiler/-/message-compiler-11.2.7.tgz", - "integrity": "sha512-TFamC+GzJAotAFwUNvbtRVBgvuSn2nCwKNresmPUHv3IIVMmXJt7QQJj/DORI1h8hs46ZF6L0Fs2xBohSOE4iQ==", + "version": "11.2.8", + "resolved": "https://registry.npmmirror.com/@intlify/message-compiler/-/message-compiler-11.2.8.tgz", + "integrity": "sha512-A5n33doOjmHsBtCN421386cG1tWp5rpOjOYPNsnpjIJbQ4POF0QY2ezhZR9kr0boKwaHjbOifvyQvHj2UTrDFQ==", "license": "MIT", "dependencies": { - "@intlify/shared": "11.2.7", + "@intlify/shared": "11.2.8", "source-map-js": "^1.0.2" }, "engines": { @@ -1373,9 +1372,9 @@ } }, "node_modules/@intlify/shared": { - "version": "11.2.7", - "resolved": "https://registry.npmmirror.com/@intlify/shared/-/shared-11.2.7.tgz", - "integrity": "sha512-uvlkvc/0uQ4FDlHQZccpUnmcOwNcaI3i+69ck2YJ+GqM35AoVbuS63b+YfirV4G0SZh64Ij2UMcFRMmB4nr95w==", + "version": "11.2.8", + "resolved": "https://registry.npmmirror.com/@intlify/shared/-/shared-11.2.8.tgz", + "integrity": "sha512-l6e4NZyUgv8VyXXH4DbuucFOBmxLF56C/mqh2tvApbzl2Hrhi1aTDcuv5TKdxzfHYmpO3UB0Cz04fgDT9vszfw==", "license": "MIT", "engines": { "node": ">= 16" @@ -2865,23 +2864,25 @@ "resolved": "https://registry.npmmirror.com/@types/whatwg-mimetype/-/whatwg-mimetype-3.0.2.tgz", "integrity": "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.50.1", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.50.1.tgz", - "integrity": "sha512-PKhLGDq3JAg0Jk/aK890knnqduuI/Qj+udH7wCf0217IGi4gt+acgCyPVe79qoT+qKUvHMDQkwJeKW9fwl8Cyw==", + "version": "8.51.0", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.51.0.tgz", + "integrity": "sha512-XtssGWJvypyM2ytBnSnKtHYOGT+4ZwTnBVl36TA4nRO2f4PRNGz5/1OszHzcZCvcBMh+qb7I06uoCmLTRdR9og==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.50.1", - "@typescript-eslint/type-utils": "8.50.1", - "@typescript-eslint/utils": "8.50.1", - "@typescript-eslint/visitor-keys": "8.50.1", + "@typescript-eslint/scope-manager": "8.51.0", + "@typescript-eslint/type-utils": "8.51.0", + "@typescript-eslint/utils": "8.51.0", + "@typescript-eslint/visitor-keys": "8.51.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", - "ts-api-utils": "^2.1.0" + "ts-api-utils": "^2.2.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2891,7 +2892,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.50.1", + "@typescript-eslint/parser": "^8.51.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } @@ -2907,16 +2908,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.50.1", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-8.50.1.tgz", - "integrity": "sha512-hM5faZwg7aVNa819m/5r7D0h0c9yC4DUlWAOvHAtISdFTc8xB86VmX5Xqabrama3wIPJ/q9RbGS1worb6JfnMg==", + "version": "8.51.0", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-8.51.0.tgz", + "integrity": "sha512-3xP4XzzDNQOIqBMWogftkwxhg5oMKApqY0BAflmLZiFYHqyhSOxv/cd/zPQLTcCXr4AkaKb25joocY0BD1WC6A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.50.1", - "@typescript-eslint/types": "8.50.1", - "@typescript-eslint/typescript-estree": "8.50.1", - "@typescript-eslint/visitor-keys": "8.50.1", + "@typescript-eslint/scope-manager": "8.51.0", + "@typescript-eslint/types": "8.51.0", + "@typescript-eslint/typescript-estree": "8.51.0", + "@typescript-eslint/visitor-keys": "8.51.0", "debug": "^4.3.4" }, "engines": { @@ -2932,14 +2933,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.50.1", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/project-service/-/project-service-8.50.1.tgz", - "integrity": "sha512-E1ur1MCVf+YiP89+o4Les/oBAVzmSbeRB0MQLfSlYtbWU17HPxZ6Bhs5iYmKZRALvEuBoXIZMOIRRc/P++Ortg==", + "version": "8.51.0", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/project-service/-/project-service-8.51.0.tgz", + "integrity": "sha512-Luv/GafO07Z7HpiI7qeEW5NW8HUtZI/fo/kE0YbtQEFpJRUuR0ajcWfCE5bnMvL7QQFrmT/odMe8QZww8X2nfQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.50.1", - "@typescript-eslint/types": "^8.50.1", + "@typescript-eslint/tsconfig-utils": "^8.51.0", + "@typescript-eslint/types": "^8.51.0", "debug": "^4.3.4" }, "engines": { @@ -2954,14 +2955,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.50.1", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-8.50.1.tgz", - "integrity": "sha512-mfRx06Myt3T4vuoHaKi8ZWNTPdzKPNBhiblze5N50//TSHOAQQevl/aolqA/BcqqbJ88GUnLqjjcBc8EWdBcVw==", + "version": "8.51.0", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-8.51.0.tgz", + "integrity": "sha512-JhhJDVwsSx4hiOEQPeajGhCWgBMBwVkxC/Pet53EpBVs7zHHtayKefw1jtPaNRXpI9RA2uocdmpdfE7T+NrizA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.50.1", - "@typescript-eslint/visitor-keys": "8.50.1" + "@typescript-eslint/types": "8.51.0", + "@typescript-eslint/visitor-keys": "8.51.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2972,9 +2973,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.50.1", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.50.1.tgz", - "integrity": "sha512-ooHmotT/lCWLXi55G4mvaUF60aJa012QzvLK0Y+Mp4WdSt17QhMhWOaBWeGTFVkb2gDgBe19Cxy1elPXylslDw==", + "version": "8.51.0", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.51.0.tgz", + "integrity": "sha512-Qi5bSy/vuHeWyir2C8u/uqGMIlIDu8fuiYWv48ZGlZ/k+PRPHtaAu7erpc7p5bzw2WNNSniuxoMSO4Ar6V9OXw==", "dev": true, "license": "MIT", "engines": { @@ -2989,17 +2990,17 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.50.1", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-8.50.1.tgz", - "integrity": "sha512-7J3bf022QZE42tYMO6SL+6lTPKFk/WphhRPe9Tw/el+cEwzLz1Jjz2PX3GtGQVxooLDKeMVmMt7fWpYRdG5Etg==", + "version": "8.51.0", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-8.51.0.tgz", + "integrity": "sha512-0XVtYzxnobc9K0VU7wRWg1yiUrw4oQzexCG2V2IDxxCxhqBMSMbjB+6o91A+Uc0GWtgjCa3Y8bi7hwI0Tu4n5Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.50.1", - "@typescript-eslint/typescript-estree": "8.50.1", - "@typescript-eslint/utils": "8.50.1", + "@typescript-eslint/types": "8.51.0", + "@typescript-eslint/typescript-estree": "8.51.0", + "@typescript-eslint/utils": "8.51.0", "debug": "^4.3.4", - "ts-api-utils": "^2.1.0" + "ts-api-utils": "^2.2.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3014,9 +3015,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.50.1", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/types/-/types-8.50.1.tgz", - "integrity": "sha512-v5lFIS2feTkNyMhd7AucE/9j/4V9v5iIbpVRncjk/K0sQ6Sb+Np9fgYS/63n6nwqahHQvbmujeBL7mp07Q9mlA==", + "version": "8.51.0", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/types/-/types-8.51.0.tgz", + "integrity": "sha512-TizAvWYFM6sSscmEakjY3sPqGwxZRSywSsPEiuZF6d5GmGD9Gvlsv0f6N8FvAAA0CD06l3rIcWNbsN1e5F/9Ag==", "dev": true, "license": "MIT", "engines": { @@ -3028,21 +3029,21 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.50.1", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.50.1.tgz", - "integrity": "sha512-woHPdW+0gj53aM+cxchymJCrh0cyS7BTIdcDxWUNsclr9VDkOSbqC13juHzxOmQ22dDkMZEpZB+3X1WpUvzgVQ==", + "version": "8.51.0", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.51.0.tgz", + "integrity": "sha512-1qNjGqFRmlq0VW5iVlcyHBbCjPB7y6SxpBkrbhNWMy/65ZoncXCEPJxkRZL8McrseNH6lFhaxCIaX+vBuFnRng==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.50.1", - "@typescript-eslint/tsconfig-utils": "8.50.1", - "@typescript-eslint/types": "8.50.1", - "@typescript-eslint/visitor-keys": "8.50.1", + "@typescript-eslint/project-service": "8.51.0", + "@typescript-eslint/tsconfig-utils": "8.51.0", + "@typescript-eslint/types": "8.51.0", + "@typescript-eslint/visitor-keys": "8.51.0", "debug": "^4.3.4", "minimatch": "^9.0.4", "semver": "^7.6.0", "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.1.0" + "ts-api-utils": "^2.2.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3082,16 +3083,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.50.1", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-8.50.1.tgz", - "integrity": "sha512-lCLp8H1T9T7gPbEuJSnHwnSuO9mDf8mfK/Nion5mZmiEaQD9sWf9W4dfeFqRyqRjF06/kBuTmAqcs9sewM2NbQ==", + "version": "8.51.0", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-8.51.0.tgz", + "integrity": "sha512-11rZYxSe0zabiKaCP2QAwRf/dnmgFgvTmeDTtZvUvXG3UuAdg/GU02NExmmIXzz3vLGgMdtrIosI84jITQOxUA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.50.1", - "@typescript-eslint/types": "8.50.1", - "@typescript-eslint/typescript-estree": "8.50.1" + "@typescript-eslint/scope-manager": "8.51.0", + "@typescript-eslint/types": "8.51.0", + "@typescript-eslint/typescript-estree": "8.51.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3106,13 +3107,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.50.1", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.50.1.tgz", - "integrity": "sha512-IrDKrw7pCRUR94zeuCSUWQ+w8JEf5ZX5jl/e6AHGSLi1/zIr0lgutfn/7JpfCey+urpgQEdrZVYzCaVVKiTwhQ==", + "version": "8.51.0", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.51.0.tgz", + "integrity": "sha512-mM/JRQOzhVN1ykejrvwnBRV3+7yTKK8tVANVN3o1O0t0v7o+jqdVu9crPy5Y9dov15TJk/FTIgoUGHrTOVL3Zg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.50.1", + "@typescript-eslint/types": "8.51.0", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -5733,6 +5734,8 @@ "integrity": "sha512-QsCdAUHAmiDeKeaNojb1OHOPF7NjcWPBR7obdu3NwH2a/oyQaLg5d0aaCy/9My6CdPChYF07dvz5chaXBGaD4g==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@types/node": "^20.0.0", "@types/whatwg-mimetype": "^3.0.2", @@ -5748,6 +5751,8 @@ "integrity": "sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -5757,7 +5762,9 @@ "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/has-flag": { "version": "4.0.0", @@ -8160,9 +8167,9 @@ } }, "node_modules/ts-api-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/ts-api-utils/-/ts-api-utils-2.1.0.tgz", - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/ts-api-utils/-/ts-api-utils-2.3.0.tgz", + "integrity": "sha512-6eg3Y9SF7SsAvGzRHQvvc1skDAhwI4YQ32ui1scxD1Ccr0G5qIIbUBT3pFTKX8kmWIQClHobtUdNuaBgwdfdWg==", "dev": true, "license": "MIT", "engines": { @@ -8231,16 +8238,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.50.1", - "resolved": "https://registry.npmmirror.com/typescript-eslint/-/typescript-eslint-8.50.1.tgz", - "integrity": "sha512-ytTHO+SoYSbhAH9CrYnMhiLx8To6PSSvqnvXyPUgPETCvB6eBKmTI9w6XMPS3HsBRGkwTVBX+urA8dYQx6bHfQ==", + "version": "8.51.0", + "resolved": "https://registry.npmmirror.com/typescript-eslint/-/typescript-eslint-8.51.0.tgz", + "integrity": "sha512-jh8ZuM5oEh2PSdyQG9YAEM1TCGuWenLSuSUhf/irbVUNW9O5FhbFVONviN2TgMTBnUmyHv7E56rYnfLZK6TkiA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.50.1", - "@typescript-eslint/parser": "8.50.1", - "@typescript-eslint/typescript-estree": "8.50.1", - "@typescript-eslint/utils": "8.50.1" + "@typescript-eslint/eslint-plugin": "8.51.0", + "@typescript-eslint/parser": "8.51.0", + "@typescript-eslint/typescript-estree": "8.51.0", + "@typescript-eslint/utils": "8.51.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -8946,13 +8953,13 @@ } }, "node_modules/vue-i18n": { - "version": "11.2.7", - "resolved": "https://registry.npmmirror.com/vue-i18n/-/vue-i18n-11.2.7.tgz", - "integrity": "sha512-LPv8bAY5OA0UvFEXl4vBQOBqJzRrlExy92tWgRuwW7tbykHf7CH71G2Y4TM2OwGcIS4+hyqKHS2EVBqaYwPY9Q==", + "version": "11.2.8", + "resolved": "https://registry.npmmirror.com/vue-i18n/-/vue-i18n-11.2.8.tgz", + "integrity": "sha512-vJ123v/PXCZntd6Qj5Jumy7UBmIuE92VrtdX+AXr+1WzdBHojiBxnAxdfctUFL+/JIN+VQH4BhsfTtiGsvVObg==", "license": "MIT", "dependencies": { - "@intlify/core-base": "11.2.7", - "@intlify/shared": "11.2.7", + "@intlify/core-base": "11.2.8", + "@intlify/shared": "11.2.8", "@vue/devtools-api": "^6.5.0" }, "engines": { @@ -9041,6 +9048,8 @@ "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=12" } diff --git a/frontend/package.json b/frontend/package.json index e5b18e1..6155074 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -50,7 +50,7 @@ "@codemirror/lint": "^6.9.2", "@codemirror/search": "^6.5.11", "@codemirror/state": "^6.5.3", - "@codemirror/view": "^6.39.6", + "@codemirror/view": "^6.39.8", "@cospaia/prettier-plugin-clojure": "^0.0.2", "@lezer/highlight": "^1.2.3", "@lezer/lr": "^1.4.5", @@ -77,7 +77,7 @@ "prettier": "^3.7.4", "sass": "^1.97.1", "vue": "^3.5.26", - "vue-i18n": "^11.2.7", + "vue-i18n": "^11.2.8", "vue-pick-colors": "^1.8.0", "vue-router": "^4.6.4" }, @@ -91,9 +91,8 @@ "eslint": "^9.39.2", "eslint-plugin-vue": "^10.6.2", "globals": "^16.5.0", - "happy-dom": "^20.0.11", "typescript": "^5.9.3", - "typescript-eslint": "^8.50.1", + "typescript-eslint": "^8.51.0", "unplugin-vue-components": "^30.0.0", "vite": "npm:rolldown-vite@latest", "vite-plugin-node-polyfills": "^0.24.0", diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 039bbe5..2725e15 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -7,6 +7,8 @@ import {useThemeStore} from '@/stores/themeStore'; import {useUpdateStore} from '@/stores/updateStore'; import WindowTitleBar from '@/components/titlebar/WindowTitleBar.vue'; import {useTranslationStore} from "@/stores/translationStore"; +import {useI18n} from "vue-i18n"; +import {LanguageType} from "../bindings/voidraft/internal/models"; const configStore = useConfigStore(); const systemStore = useSystemStore(); @@ -14,6 +16,7 @@ const keybindingStore = useKeybindingStore(); const themeStore = useThemeStore(); const updateStore = useUpdateStore(); const translationStore = useTranslationStore(); +const {locale} = useI18n(); onBeforeMount(async () => { // 并行初始化配置、系统信息和快捷键配置 @@ -22,9 +25,8 @@ onBeforeMount(async () => { systemStore.initSystemInfo(), keybindingStore.loadKeyBindings(), ]); - - // 初始化语言和主题 - await configStore.initLanguage(); + + locale.value = configStore.config.appearance.language || LanguageType.LangEnUS; await themeStore.initTheme(); await translationStore.loadTranslators(); diff --git a/frontend/src/common/utils/asyncManager.ts b/frontend/src/common/utils/asyncManager.ts deleted file mode 100644 index 2ad46a1..0000000 --- a/frontend/src/common/utils/asyncManager.ts +++ /dev/null @@ -1,265 +0,0 @@ -/** - * 操作信息接口 - */ -interface OperationInfo { - controller: AbortController; - createdAt: number; - timeout?: number; - timeoutId?: NodeJS.Timeout; -} - -/** - * 异步操作管理器 - * 用于管理异步操作的竞态条件,确保只有最新的操作有效 - * 支持操作超时和自动清理机制 - * - * @template T 操作上下文的类型 - */ -export class AsyncManager { - private operationSequence = 0; - private pendingOperations = new Map(); - private currentContext: T | null = null; - private defaultTimeout: number; - - /** - * 创建异步操作管理器 - * - * @param defaultTimeout 默认超时时间(毫秒),0表示不设置超时 - */ - constructor(defaultTimeout: number = 0) { - this.defaultTimeout = defaultTimeout; - } - - /** - * 生成新的操作ID - * - * @returns 新的操作ID - */ - getNextOperationId(): number { - return ++this.operationSequence; - } - - /** - * 开始新的操作 - * - * @param context 操作上下文 - * @param options 操作选项 - * @returns 操作ID和AbortController - */ - startOperation( - context: T, - options?: { - excludeId?: number; - timeout?: number; - } - ): { operationId: number; abortController: AbortController } { - const operationId = this.getNextOperationId(); - const abortController = new AbortController(); - const timeout = options?.timeout ?? this.defaultTimeout; - - // 取消之前的操作 - this.cancelPreviousOperations(options?.excludeId); - - // 创建操作信息 - const operationInfo: OperationInfo = { - controller: abortController, - createdAt: Date.now(), - timeout: timeout > 0 ? timeout : undefined - }; - - // 设置超时处理 - if (timeout > 0) { - operationInfo.timeoutId = setTimeout(() => { - this.cancelOperation(operationId, 'timeout'); - }, timeout); - } - - // 设置当前上下文和操作 - this.currentContext = context; - this.pendingOperations.set(operationId, operationInfo); - - return { operationId, abortController }; - } - - /** - * 检查操作是否仍然有效 - * - * @param operationId 操作ID - * @param context 操作上下文 - * @returns 操作是否有效 - */ - isOperationValid(operationId: number, context?: T): boolean { - const operationInfo = this.pendingOperations.get(operationId); - const contextValid = context === undefined || this.currentContext === context; - - return ( - operationInfo !== undefined && - !operationInfo.controller.signal.aborted && - contextValid - ); - } - - /** - * 完成操作 - * - * @param operationId 操作ID - */ - completeOperation(operationId: number): void { - const operationInfo = this.pendingOperations.get(operationId); - if (operationInfo) { - // 清理超时定时器 - if (operationInfo.timeoutId) { - clearTimeout(operationInfo.timeoutId); - } - this.pendingOperations.delete(operationId); - } - } - - /** - * 取消指定操作 - * - * @param operationId 操作ID - * @param reason 取消原因 - */ - cancelOperation(operationId: number, reason?: string): void { - const operationInfo = this.pendingOperations.get(operationId); - if (operationInfo) { - // 清理超时定时器 - if (operationInfo.timeoutId) { - clearTimeout(operationInfo.timeoutId); - } - // 取消操作 - operationInfo.controller.abort(reason); - this.pendingOperations.delete(operationId); - } - } - - /** - * 取消之前的操作(修复并发bug) - * - * @param excludeId 要排除的操作ID(不取消该操作) - */ - cancelPreviousOperations(excludeId?: number): void { - // 创建要取消的操作ID数组,避免在遍历时修改Map - const operationIdsToCancel: number[] = []; - - for (const [operationId] of this.pendingOperations) { - if (excludeId === undefined || operationId !== excludeId) { - operationIdsToCancel.push(operationId); - } - } - - // 批量取消操作 - for (const operationId of operationIdsToCancel) { - this.cancelOperation(operationId, 'superseded'); - } - } - - /** - * 取消所有操作 - */ - cancelAllOperations(): void { - // 创建要取消的操作ID数组,避免在遍历时修改Map - const operationIdsToCancel = Array.from(this.pendingOperations.keys()); - - // 批量取消操作 - for (const operationId of operationIdsToCancel) { - this.cancelOperation(operationId, 'cancelled'); - } - this.currentContext = null; - } - - /** - * 清理过期操作(手动清理超时操作) - * - * @param maxAge 最大存活时间(毫秒) - * @returns 清理的操作数量 - */ - cleanupExpiredOperations(maxAge: number): number { - const now = Date.now(); - const expiredOperationIds: number[] = []; - - for (const [operationId, operationInfo] of this.pendingOperations) { - if (now - operationInfo.createdAt > maxAge) { - expiredOperationIds.push(operationId); - } - } - - // 批量取消过期操作 - for (const operationId of expiredOperationIds) { - this.cancelOperation(operationId, 'expired'); - } - - return expiredOperationIds.length; - } - - /** - * 获取操作统计信息 - * - * @returns 操作统计信息 - */ - getOperationStats(): { - total: number; - withTimeout: number; - averageAge: number; - oldestAge: number; - } { - const now = Date.now(); - let withTimeout = 0; - let totalAge = 0; - let oldestAge = 0; - - for (const operationInfo of this.pendingOperations.values()) { - const age = now - operationInfo.createdAt; - totalAge += age; - oldestAge = Math.max(oldestAge, age); - - if (operationInfo.timeout) { - withTimeout++; - } - } - - return { - total: this.pendingOperations.size, - withTimeout, - averageAge: this.pendingOperations.size > 0 ? totalAge / this.pendingOperations.size : 0, - oldestAge - }; - } - - /** - * 获取当前上下文 - * - * @returns 当前上下文 - */ - getCurrentContext(): T | null { - return this.currentContext; - } - - /** - * 设置当前上下文 - * - * @param context 新的上下文 - */ - setCurrentContext(context: T | null): void { - this.currentContext = context; - } - - /** - * 获取待处理操作数量 - * - * @returns 待处理操作数量 - */ - get pendingCount(): number { - return this.pendingOperations.size; - } - - /** - * 检查是否有待处理的操作 - * - * @returns 是否有待处理的操作 - */ - hasPendingOperations(): boolean { - return this.pendingOperations.size > 0; - } -} \ No newline at end of file diff --git a/frontend/src/common/utils/configUtils.ts b/frontend/src/common/utils/configUtils.ts index 267da48..f6559bc 100644 --- a/frontend/src/common/utils/configUtils.ts +++ b/frontend/src/common/utils/configUtils.ts @@ -1,42 +1,13 @@ -import { LanguageType } from '@/../bindings/voidraft/internal/models/models'; -import type { SupportedLocaleType } from '@/common/constant/locales'; - /** * 配置工具类 */ export class ConfigUtils { - /** - * 将后端语言类型转换为前端语言代码 - */ - static backendLanguageToFrontend(language: LanguageType): SupportedLocaleType { - return language === LanguageType.LangZhCN ? 'zh-CN' : 'en-US'; - } - /** - * 将前端语言代码转换为后端语言类型 - */ - static frontendLanguageToBackend(locale: SupportedLocaleType): LanguageType { - return locale === 'zh-CN' ? LanguageType.LangZhCN : LanguageType.LangEnUS; - } + /** + * 验证数值是否在指定范围内 + */ + static clamp(value: number, min: number, max: number): number { + return Math.max(min, Math.min(max, value)); + } - /** - * 验证数值是否在指定范围内 - */ - static clamp(value: number, min: number, max: number): number { - return Math.max(min, Math.min(max, value)); - } - - /** - * 验证配置值是否有效 - */ - static isValidConfigValue(value: T, validValues: readonly T[]): boolean { - return validValues.includes(value); - } - - /** - * 获取配置的默认值 - */ - static getDefaultValue(key: string, defaults: Record): T { - return defaults[key]?.default; - } } \ No newline at end of file diff --git a/frontend/src/common/utils/domDiff.test.ts b/frontend/src/common/utils/domDiff.test.ts deleted file mode 100644 index 5b11d6f..0000000 --- a/frontend/src/common/utils/domDiff.test.ts +++ /dev/null @@ -1,329 +0,0 @@ -/** - * DOM Diff 算法单元测试 - */ -import { describe, test, expect, beforeEach, afterEach } from 'vitest'; -import { morphNode, morphHTML, morphWithKeys } from './domDiff'; - -describe('DOM Diff Algorithm', () => { - let container: HTMLElement; - - beforeEach(() => { - container = document.createElement('div'); - document.body.appendChild(container); - }); - - afterEach(() => { - document.body.removeChild(container); - }); - - describe('morphNode - 基础功能', () => { - test('应该更新文本节点内容', () => { - const fromNode = document.createTextNode('Hello'); - const toNode = document.createTextNode('World'); - container.appendChild(fromNode); - - morphNode(fromNode, toNode); - - expect(fromNode.nodeValue).toBe('World'); - }); - - test('应该保持相同的文本节点不变', () => { - const fromNode = document.createTextNode('Hello'); - const toNode = document.createTextNode('Hello'); - container.appendChild(fromNode); - - const originalNode = fromNode; - morphNode(fromNode, toNode); - - expect(fromNode).toBe(originalNode); - expect(fromNode.nodeValue).toBe('Hello'); - }); - - test('应该替换不同类型的节点', () => { - const fromNode = document.createElement('span'); - fromNode.textContent = 'Hello'; - const toNode = document.createElement('div'); - toNode.textContent = 'World'; - container.appendChild(fromNode); - - morphNode(fromNode, toNode); - - expect(container.firstChild?.nodeName).toBe('DIV'); - expect(container.firstChild?.textContent).toBe('World'); - }); - }); - - describe('morphNode - 属性更新', () => { - test('应该添加新属性', () => { - const fromEl = document.createElement('div'); - const toEl = document.createElement('div'); - toEl.setAttribute('class', 'test'); - toEl.setAttribute('id', 'myid'); - container.appendChild(fromEl); - - morphNode(fromEl, toEl); - - expect(fromEl.getAttribute('class')).toBe('test'); - expect(fromEl.getAttribute('id')).toBe('myid'); - }); - - test('应该更新已存在的属性', () => { - const fromEl = document.createElement('div'); - fromEl.setAttribute('class', 'old'); - const toEl = document.createElement('div'); - toEl.setAttribute('class', 'new'); - container.appendChild(fromEl); - - morphNode(fromEl, toEl); - - expect(fromEl.getAttribute('class')).toBe('new'); - }); - - test('应该删除不存在的属性', () => { - const fromEl = document.createElement('div'); - fromEl.setAttribute('class', 'test'); - fromEl.setAttribute('id', 'myid'); - const toEl = document.createElement('div'); - toEl.setAttribute('class', 'test'); - container.appendChild(fromEl); - - morphNode(fromEl, toEl); - - expect(fromEl.getAttribute('class')).toBe('test'); - expect(fromEl.hasAttribute('id')).toBe(false); - }); - }); - - describe('morphNode - 子节点更新', () => { - test('应该添加新子节点', () => { - const fromEl = document.createElement('ul'); - fromEl.innerHTML = '
  • 1
  • 2
  • '; - const toEl = document.createElement('ul'); - toEl.innerHTML = '
  • 1
  • 2
  • 3
  • '; - container.appendChild(fromEl); - - morphNode(fromEl, toEl); - - expect(fromEl.children.length).toBe(3); - expect(fromEl.children[2].textContent).toBe('3'); - }); - - test('应该删除多余的子节点', () => { - const fromEl = document.createElement('ul'); - fromEl.innerHTML = '
  • 1
  • 2
  • 3
  • '; - const toEl = document.createElement('ul'); - toEl.innerHTML = '
  • 1
  • 2
  • '; - container.appendChild(fromEl); - - morphNode(fromEl, toEl); - - expect(fromEl.children.length).toBe(2); - expect(fromEl.textContent).toBe('12'); - }); - - test('应该更新子节点内容', () => { - const fromEl = document.createElement('div'); - fromEl.innerHTML = '

    Old

    '; - const toEl = document.createElement('div'); - toEl.innerHTML = '

    New

    '; - container.appendChild(fromEl); - - const originalP = fromEl.querySelector('p'); - morphNode(fromEl, toEl); - - // 应该保持同一个 p 元素,只更新内容 - expect(fromEl.querySelector('p')).toBe(originalP); - expect(fromEl.querySelector('p')?.textContent).toBe('New'); - }); - }); - - describe('morphHTML - HTML 字符串更新', () => { - test('应该从 HTML 字符串更新元素', () => { - const element = document.createElement('div'); - element.innerHTML = '

    Old

    '; - container.appendChild(element); - - morphHTML(element, '

    New

    '); - - expect(element.innerHTML).toBe('

    New

    '); - }); - - test('应该处理复杂的 HTML 结构', () => { - const element = document.createElement('div'); - element.innerHTML = '

    Title

    Paragraph

    '; - container.appendChild(element); - - morphHTML(element, '

    New Title

    New Paragraph

    Extra'); - - expect(element.children.length).toBe(3); - expect(element.querySelector('h1')?.textContent).toBe('New Title'); - expect(element.querySelector('p')?.textContent).toBe('New Paragraph'); - expect(element.querySelector('span')?.textContent).toBe('Extra'); - }); - }); - - describe('morphWithKeys - 基于 key 的智能 diff', () => { - test('应该保持相同 key 的节点', () => { - const fromEl = document.createElement('ul'); - fromEl.innerHTML = ` -
  • A
  • -
  • B
  • -
  • C
  • - `; - const toEl = document.createElement('ul'); - toEl.innerHTML = ` -
  • A Updated
  • -
  • B
  • -
  • C
  • - `; - container.appendChild(fromEl); - - const originalA = fromEl.querySelector('[data-key="a"]'); - morphWithKeys(fromEl, toEl); - - expect(fromEl.querySelector('[data-key="a"]')).toBe(originalA); - expect(originalA?.textContent).toBe('A Updated'); - }); - - test('应该重新排序节点', () => { - const fromEl = document.createElement('ul'); - fromEl.innerHTML = ` -
  • A
  • -
  • B
  • -
  • C
  • - `; - const toEl = document.createElement('ul'); - toEl.innerHTML = ` -
  • C
  • -
  • A
  • -
  • B
  • - `; - container.appendChild(fromEl); - - morphWithKeys(fromEl, toEl); - - const keys = Array.from(fromEl.children).map(child => child.getAttribute('data-key')); - expect(keys).toEqual(['c', 'a', 'b']); - }); - - test('应该添加新的 key 节点', () => { - const fromEl = document.createElement('ul'); - fromEl.innerHTML = ` -
  • A
  • -
  • B
  • - `; - const toEl = document.createElement('ul'); - toEl.innerHTML = ` -
  • A
  • -
  • B
  • -
  • C
  • - `; - container.appendChild(fromEl); - - morphWithKeys(fromEl, toEl); - - expect(fromEl.children.length).toBe(3); - expect(fromEl.querySelector('[data-key="c"]')?.textContent).toBe('C'); - }); - - test('应该删除不存在的 key 节点', () => { - const fromEl = document.createElement('ul'); - fromEl.innerHTML = ` -
  • A
  • -
  • B
  • -
  • C
  • - `; - const toEl = document.createElement('ul'); - toEl.innerHTML = ` -
  • A
  • -
  • C
  • - `; - container.appendChild(fromEl); - - morphWithKeys(fromEl, toEl); - - expect(fromEl.children.length).toBe(2); - expect(fromEl.querySelector('[data-key="b"]')).toBeNull(); - }); - }); - - describe('性能测试', () => { - test('应该高效处理大量节点', () => { - const fromEl = document.createElement('ul'); - for (let i = 0; i < 1000; i++) { - const li = document.createElement('li'); - li.textContent = `Item ${i}`; - fromEl.appendChild(li); - } - - const toEl = document.createElement('ul'); - for (let i = 0; i < 1000; i++) { - const li = document.createElement('li'); - li.textContent = `Updated Item ${i}`; - toEl.appendChild(li); - } - - container.appendChild(fromEl); - - const startTime = performance.now(); - morphNode(fromEl, toEl); - const endTime = performance.now(); - - expect(endTime - startTime).toBeLessThan(100); // 应该在 100ms 内完成 - expect(fromEl.children.length).toBe(1000); - expect(fromEl.children[0].textContent).toBe('Updated Item 0'); - }); - }); - - describe('边界情况', () => { - test('应该处理空节点', () => { - const fromEl = document.createElement('div'); - const toEl = document.createElement('div'); - container.appendChild(fromEl); - - expect(() => morphNode(fromEl, toEl)).not.toThrow(); - }); - - test('应该处理只有文本的节点', () => { - const fromEl = document.createElement('div'); - fromEl.textContent = 'Hello'; - const toEl = document.createElement('div'); - toEl.textContent = 'World'; - container.appendChild(fromEl); - - morphNode(fromEl, toEl); - - expect(fromEl.textContent).toBe('World'); - }); - - test('应该处理嵌套的复杂结构', () => { - const fromEl = document.createElement('div'); - fromEl.innerHTML = ` -
    -
    - Text -
    -
    - `; - - const toEl = document.createElement('div'); - toEl.innerHTML = ` -
    -
    - Updated Text - New -
    -
    - `; - - container.appendChild(fromEl); - - morphNode(fromEl, toEl); - - expect(fromEl.querySelector('.outer')?.classList.contains('modified')).toBe(true); - expect(fromEl.querySelector('span')?.textContent).toBe('Updated Text'); - expect(fromEl.querySelector('strong')?.textContent).toBe('New'); - }); - }); -}); - diff --git a/frontend/src/common/utils/domDiff.ts b/frontend/src/common/utils/domDiff.ts deleted file mode 100644 index b898338..0000000 --- a/frontend/src/common/utils/domDiff.ts +++ /dev/null @@ -1,180 +0,0 @@ -/** - * 轻量级 DOM Diff 算法实现 - * 基于 morphdom 思路,只更新变化的节点,保持未变化的节点不动 - */ - -/** - * 比较并更新两个 DOM 节点 - * @param fromNode 原节点 - * @param toNode 目标节点 - */ -export function morphNode(fromNode: Node, toNode: Node): void { - // 节点类型不同,直接替换 - if (fromNode.nodeType !== toNode.nodeType || fromNode.nodeName !== toNode.nodeName) { - fromNode.parentNode?.replaceChild(toNode.cloneNode(true), fromNode); - return; - } - - // 文本节点:比较内容 - if (fromNode.nodeType === Node.TEXT_NODE) { - if (fromNode.nodeValue !== toNode.nodeValue) { - fromNode.nodeValue = toNode.nodeValue; - } - return; - } - - // 元素节点:更新属性和子节点 - if (fromNode.nodeType === Node.ELEMENT_NODE) { - const fromEl = fromNode as Element; - const toEl = toNode as Element; - - // 更新属性 - morphAttributes(fromEl, toEl); - - // 更新子节点 - morphChildren(fromEl, toEl); - } -} - -/** - * 更新元素属性 - */ -function morphAttributes(fromEl: Element, toEl: Element): void { - // 移除旧属性 - const fromAttrs = fromEl.attributes; - for (let i = fromAttrs.length - 1; i >= 0; i--) { - const attr = fromAttrs[i]; - if (!toEl.hasAttribute(attr.name)) { - fromEl.removeAttribute(attr.name); - } - } - - // 添加/更新新属性 - const toAttrs = toEl.attributes; - for (let i = 0; i < toAttrs.length; i++) { - const attr = toAttrs[i]; - const fromValue = fromEl.getAttribute(attr.name); - if (fromValue !== attr.value) { - fromEl.setAttribute(attr.name, attr.value); - } - } -} - -/** - * 更新子节点(核心 diff 算法) - */ -function morphChildren(fromEl: Element, toEl: Element): void { - const fromChildren = Array.from(fromEl.childNodes); - const toChildren = Array.from(toEl.childNodes); - - const fromLen = fromChildren.length; - const toLen = toChildren.length; - const minLen = Math.min(fromLen, toLen); - - // 1. 更新公共部分 - for (let i = 0; i < minLen; i++) { - morphNode(fromChildren[i], toChildren[i]); - } - - // 2. 移除多余的旧节点 - if (fromLen > toLen) { - for (let i = fromLen - 1; i >= toLen; i--) { - fromEl.removeChild(fromChildren[i]); - } - } - - // 3. 添加新节点 - if (toLen > fromLen) { - for (let i = fromLen; i < toLen; i++) { - fromEl.appendChild(toChildren[i].cloneNode(true)); - } - } -} - -/** - * 优化版:使用 key 进行更智能的 diff(可选) - * 适用于有 data-key 属性的元素 - */ -export function morphWithKeys(fromEl: Element, toEl: Element): void { - const toChildren = Array.from(toEl.children) as Element[]; - - // 构建 from 的 key 映射 - const fromKeyMap = new Map(); - Array.from(fromEl.children).forEach((child) => { - const key = child.getAttribute('data-key'); - if (key) { - fromKeyMap.set(key, child); - } - }); - - const processedKeys = new Set(); - - // 按照 toChildren 的顺序处理 - toChildren.forEach((toChild, toIndex) => { - const key = toChild.getAttribute('data-key'); - if (!key) return; - - processedKeys.add(key); - const fromChild = fromKeyMap.get(key); - - if (fromChild) { - // 找到对应节点,更新内容 - morphNode(fromChild, toChild); - - // 确保节点在正确的位置 - const currentNode = fromEl.children[toIndex]; - if (currentNode !== fromChild) { - // 将 fromChild 移动到正确位置 - fromEl.insertBefore(fromChild, currentNode); - } - } else { - // 新节点,插入到正确位置 - const currentNode = fromEl.children[toIndex]; - fromEl.insertBefore(toChild.cloneNode(true), currentNode || null); - } - }); - - // 删除不再存在的节点(从后往前删除,避免索引问题) - const childrenToRemove: Element[] = []; - fromKeyMap.forEach((child, key) => { - if (!processedKeys.has(key)) { - childrenToRemove.push(child); - } - }); - childrenToRemove.forEach(child => { - fromEl.removeChild(child); - }); -} - -/** - * 高级 API:直接从 HTML 字符串更新元素 - */ -export function morphHTML(element: Element, htmlString: string): void { - const tempContainer = document.createElement('div'); - tempContainer.innerHTML = htmlString; - - // 更新元素的子节点列表 - morphChildren(element, tempContainer); -} - -/** - * 批量更新(使用 DocumentFragment) - */ -export function batchMorph(element: Element, htmlString: string): void { - const tempContainer = document.createElement('div'); - tempContainer.innerHTML = htmlString; - - const fragment = document.createDocumentFragment(); - Array.from(tempContainer.childNodes).forEach(node => { - fragment.appendChild(node); - }); - - // 清空原内容 - while (element.firstChild) { - element.removeChild(element.firstChild); - } - - // 批量插入 - element.appendChild(fragment); -} - diff --git a/frontend/src/components/tabs/TabContainer.vue b/frontend/src/components/tabs/TabContainer.vue index 55a824a..2a37e23 100644 --- a/frontend/src/components/tabs/TabContainer.vue +++ b/frontend/src/components/tabs/TabContainer.vue @@ -7,7 +7,7 @@ v-for="tab in tabStore.tabs" :key="tab.documentId" :tab="tab" - :isActive="tab.documentId === tabStore.currentDocumentId" + :isActive="tab.documentId === documentStore.currentDocumentId" :canClose="tabStore.canCloseTab" @click="switchToTab" @close="closeTab" @@ -35,8 +35,12 @@ import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue'; import TabItem from './TabItem.vue'; import TabContextMenu from './TabContextMenu.vue'; import { useTabStore } from '@/stores/tabStore'; +import { useDocumentStore } from '@/stores/documentStore'; +import { useEditorStore } from '@/stores/editorStore'; const tabStore = useTabStore(); +const documentStore = useDocumentStore(); +const editorStore = useEditorStore(); // DOM 引用 const tabBarRef = ref(); @@ -50,8 +54,17 @@ const contextMenuTargetId = ref(null); // 标签页操作 -const switchToTab = (documentId: number) => { - tabStore.switchToTabAndDocument(documentId); +const switchToTab = async (documentId: number) => { + await tabStore.switchToTabAndDocument(documentId); + + const doc = documentStore.currentDocument; + if (doc && doc.id !== undefined && editorStore.hasContainer) { + await editorStore.loadEditor(doc.id, doc.content || ''); + } + + if (doc && tabStore.isTabsEnabled) { + tabStore.addOrActivateTab(doc); + } }; const closeTab = (documentId: number) => { @@ -150,7 +163,7 @@ onUnmounted(() => { }); // 监听当前活跃标签页的变化 -watch(() => tabStore.currentDocumentId, () => { +watch(() => documentStore.currentDocumentId, () => { scrollToActiveTab(); }); diff --git a/frontend/src/components/toolbar/DocumentSelector.vue b/frontend/src/components/toolbar/DocumentSelector.vue index 77eb67f..743f536 100644 --- a/frontend/src/components/toolbar/DocumentSelector.vue +++ b/frontend/src/components/toolbar/DocumentSelector.vue @@ -2,6 +2,7 @@ import {computed, nextTick, reactive, ref, watch} from 'vue'; import {useDocumentStore} from '@/stores/documentStore'; import {useTabStore} from '@/stores/tabStore'; +import {useEditorStore} from '@/stores/editorStore'; import {useWindowStore} from '@/stores/windowStore'; import {useI18n} from 'vue-i18n'; import {useConfirm} from '@/composables'; @@ -16,6 +17,7 @@ interface DocumentItem extends Document { const documentStore = useDocumentStore(); const tabStore = useTabStore(); +const editorStore = useEditorStore(); const windowStore = useWindowStore(); const {t} = useI18n(); @@ -103,13 +105,20 @@ const selectDoc = async (doc: Document) => { return; } + const success = await documentStore.openDocument(doc.id); - if (success) { - if (tabStore.isTabsEnabled) { - tabStore.addOrActivateTab(doc); - } - closeMenu(); + if (!success) return; + + const fullDoc = documentStore.currentDocument; + if (fullDoc && editorStore.hasContainer) { + await editorStore.loadEditor(fullDoc.id!, fullDoc.content || ''); } + + if (fullDoc && tabStore.isTabsEnabled) { + tabStore.addOrActivateTab(fullDoc); + } + + closeMenu(); }; const createDoc = async (title: string) => { @@ -190,6 +199,10 @@ const openInNewWindow = async (doc: Document, event: Event) => { event.stopPropagation(); if (doc.id === undefined) return; try { + // 在打开新窗口前,如果启用了标签且该文档有标签,先关闭标签 + if (tabStore.isTabsEnabled && tabStore.hasTab(doc.id)) { + await tabStore.closeTab(doc.id); + } await documentStore.openDocumentInNewWindow(doc.id); } catch (error) { console.error('Failed to open document in new window:', error); diff --git a/frontend/src/stores/configStore.ts b/frontend/src/stores/configStore.ts index eb51e92..9962061 100644 --- a/frontend/src/stores/configStore.ts +++ b/frontend/src/stores/configStore.ts @@ -1,18 +1,10 @@ import {defineStore} from 'pinia'; import {computed, reactive} from 'vue'; import {ConfigService, StartupService} from '@/../bindings/voidraft/internal/services'; -import { - AppConfig, - AuthMethod, - EditingConfig, - LanguageType, - SystemThemeType, - TabType -} from '@/../bindings/voidraft/internal/models/models'; +import {AppConfig, AuthMethod, LanguageType, SystemThemeType, TabType} from '@/../bindings/voidraft/internal/models/models'; import {useI18n} from 'vue-i18n'; import {ConfigUtils} from '@/common/utils/configUtils'; import {FONT_OPTIONS} from '@/common/constant/fonts'; -import {SUPPORTED_LOCALES} from '@/common/constant/locales'; import { CONFIG_KEY_MAP, CONFIG_LIMITS, @@ -36,12 +28,6 @@ export const useConfigStore = defineStore('config', () => { // Font options (no longer localized) const fontOptions = computed(() => FONT_OPTIONS); - // 计算属性 - const createLimitComputed = (key: NumberConfigKey) => computed(() => CONFIG_LIMITS[key]); - const limits = Object.fromEntries( - (['fontSize', 'tabSize', 'lineHeight'] as const).map(key => [key, createLimitComputed(key)]) - ) as Record>; - // 统一配置更新方法 const updateConfig = async (key: K, value: any): Promise => { if (!state.configLoaded && !state.isLoading) { @@ -99,39 +85,12 @@ export const useConfigStore = defineStore('config', () => { } }; - // 通用数值调整器工厂 - const createAdjuster = (key: T) => { - const limit = CONFIG_LIMITS[key]; - const clamp = (value: number) => ConfigUtils.clamp(value, limit.min, limit.max); - - return { - increase: async () => await updateConfig(key, clamp(state.config.editing[key] + 1)), - decrease: async () => await updateConfig(key, clamp(state.config.editing[key] - 1)), - set: async (value: number) => await updateConfig(key, clamp(value)), - reset: async () => await updateConfig(key, limit.default), - increaseLocal: () => updateConfigLocal(key, clamp(state.config.editing[key] + 1)), - decreaseLocal: () => updateConfigLocal(key, clamp(state.config.editing[key] - 1)) - }; - }; - - const createEditingToggler = (key: T) => - async () => await updateConfig(key as ConfigKey, !state.config.editing[key] as EditingConfig[T]); - - // 枚举值切换器 - const createEnumToggler = (key: 'tabType', values: readonly T[]) => - async () => { - const currentIndex = values.indexOf(state.config.editing[key] as T); - const nextIndex = (currentIndex + 1) % values.length; - return await updateConfig(key, values[nextIndex]); - }; - // 重置配置 const resetConfig = async (): Promise => { if (state.isLoading) return; state.isLoading = true; try { - await ConfigService.ResetConfig(); const appConfig = await ConfigService.GetConfig(); if (appConfig) { @@ -142,57 +101,25 @@ export const useConfigStore = defineStore('config', () => { } }; - // 语言设置方法 - const setLanguage = async (language: LanguageType): Promise => { - await updateConfig('language', language); - const frontendLocale = ConfigUtils.backendLanguageToFrontend(language); - locale.value = frontendLocale as any; + // 辅助函数:限制数值范围 + const clampValue = (value: number, key: NumberConfigKey): number => { + const limit = CONFIG_LIMITS[key]; + return ConfigUtils.clamp(value, limit.min, limit.max); }; - // 系统主题设置方法 - const setSystemTheme = async (systemTheme: SystemThemeType): Promise => { - await updateConfig('systemTheme', systemTheme); - }; + // 计算属性 + const fontConfig = computed(() => ({ + fontSize: state.config.editing.fontSize, + fontFamily: state.config.editing.fontFamily, + lineHeight: state.config.editing.lineHeight, + fontWeight: state.config.editing.fontWeight + })); - // 当前主题设置方法 - const setCurrentTheme = async (themeName: string): Promise => { - await updateConfig('currentTheme', themeName); - }; - - - // 初始化语言设置 - const initLanguage = async (): Promise => { - try { - // 如果配置未加载,先加载配置 - if (!state.configLoaded) { - await initConfig(); - } - - // 同步前端语言设置 - const frontendLocale = ConfigUtils.backendLanguageToFrontend(state.config.appearance.language); - locale.value = frontendLocale as any; - } catch (_error) { - const browserLang = SUPPORTED_LOCALES[0].code; - locale.value = browserLang as any; - } - }; - - // 创建数值调整器实例 - const adjusters = { - fontSize: createAdjuster('fontSize'), - tabSize: createAdjuster('tabSize'), - lineHeight: createAdjuster('lineHeight') - }; - - // 创建切换器实例 - const togglers = { - tabIndent: createEditingToggler('enableTabIndent'), - alwaysOnTop: async () => { - await updateConfig('alwaysOnTop', !state.config.general.alwaysOnTop); - await runtime.Window.SetAlwaysOnTop(state.config.general.alwaysOnTop); - }, - tabType: createEnumToggler('tabType', CONFIG_LIMITS.tabType.values) - }; + const tabConfig = computed(() => ({ + tabSize: state.config.editing.tabSize, + enableTabIndent: state.config.editing.enableTabIndent, + tabType: state.config.editing.tabType + })); return { // 状态 @@ -200,53 +127,84 @@ export const useConfigStore = defineStore('config', () => { configLoaded: computed(() => state.configLoaded), isLoading: computed(() => state.isLoading), fontOptions, - - // 限制常量 - ...limits, + fontConfig, + tabConfig, // 核心方法 initConfig, resetConfig, // 语言相关方法 - setLanguage, - initLanguage, + setLanguage: (value: LanguageType) => { + updateConfig('language', value); + locale.value = value as any; + }, // 主题相关方法 - setSystemTheme, - setCurrentTheme, + setSystemTheme: (value: SystemThemeType) => updateConfig('systemTheme', value), + setCurrentTheme: (value: string) => updateConfig('currentTheme', value), // 字体大小操作 - ...adjusters.fontSize, - increaseFontSize: adjusters.fontSize.increase, - decreaseFontSize: adjusters.fontSize.decrease, - resetFontSize: adjusters.fontSize.reset, - setFontSize: adjusters.fontSize.set, - // 字体大小操作 - increaseFontSizeLocal: adjusters.fontSize.increaseLocal, - decreaseFontSizeLocal: adjusters.fontSize.decreaseLocal, - saveFontSize: () => saveConfig('fontSize'), - - // Tab操作 - toggleTabIndent: togglers.tabIndent, - setEnableTabIndent: (value: boolean) => updateConfig('enableTabIndent', value), - ...adjusters.tabSize, - increaseTabSize: adjusters.tabSize.increase, - decreaseTabSize: adjusters.tabSize.decrease, - setTabSize: adjusters.tabSize.set, - toggleTabType: togglers.tabType, - - // 行高操作 - setLineHeight: adjusters.lineHeight.set, - - // 窗口操作 - toggleAlwaysOnTop: togglers.alwaysOnTop, - setAlwaysOnTop: (value: boolean) => updateConfig('alwaysOnTop', value), + setFontSize: async (value: number) => { + await updateConfig('fontSize', clampValue(value, 'fontSize')); + }, + increaseFontSize: async () => { + const newValue = state.config.editing.fontSize + 1; + await updateConfig('fontSize', clampValue(newValue, 'fontSize')); + }, + decreaseFontSize: async () => { + const newValue = state.config.editing.fontSize - 1; + await updateConfig('fontSize', clampValue(newValue, 'fontSize')); + }, + resetFontSize: async () => { + await updateConfig('fontSize', CONFIG_LIMITS.fontSize.default); + }, + increaseFontSizeLocal: () => { + updateConfigLocal('fontSize', clampValue(state.config.editing.fontSize + 1, 'fontSize')); + }, + decreaseFontSizeLocal: () => { + updateConfigLocal('fontSize', clampValue(state.config.editing.fontSize - 1, 'fontSize')); + }, + saveFontSize: async () => { + await saveConfig('fontSize'); + }, // 字体操作 setFontFamily: (value: string) => updateConfig('fontFamily', value), setFontWeight: (value: string) => updateConfig('fontWeight', value), + // 行高操作 + setLineHeight: async (value: number) => { + await updateConfig('lineHeight', clampValue(value, 'lineHeight')); + }, + + // Tab操作 + setEnableTabIndent: (value: boolean) => updateConfig('enableTabIndent', value), + setTabSize: async (value: number) => { + await updateConfig('tabSize', clampValue(value, 'tabSize')); + }, + increaseTabSize: async () => { + const newValue = state.config.editing.tabSize + 1; + await updateConfig('tabSize', clampValue(newValue, 'tabSize')); + }, + decreaseTabSize: async () => { + const newValue = state.config.editing.tabSize - 1; + await updateConfig('tabSize', clampValue(newValue, 'tabSize')); + }, + toggleTabType: async () => { + const values = CONFIG_LIMITS.tabType.values; + const currentIndex = values.indexOf(state.config.editing.tabType as typeof values[number]); + const nextIndex = (currentIndex + 1) % values.length; + await updateConfig('tabType', values[nextIndex]); + }, + + // 窗口操作 + toggleAlwaysOnTop: async () => { + await updateConfig('alwaysOnTop', !state.config.general.alwaysOnTop); + await runtime.Window.SetAlwaysOnTop(state.config.general.alwaysOnTop); + }, + setAlwaysOnTop: (value: boolean) => updateConfig('alwaysOnTop', value), + // 路径操作 setDataPath: (value: string) => updateConfigLocal('dataPath', value), diff --git a/frontend/src/stores/documentStore.ts b/frontend/src/stores/documentStore.ts index c5ee635..dd584aa 100644 --- a/frontend/src/stores/documentStore.ts +++ b/frontend/src/stores/documentStore.ts @@ -3,7 +3,6 @@ import {computed, ref} from 'vue'; import {DocumentService} from '@/../bindings/voidraft/internal/services'; import {OpenDocumentWindow} from '@/../bindings/voidraft/internal/services/windowservice'; import {Document} from '@/../bindings/voidraft/internal/models/ent/models'; -import {useTabStore} from "@/stores/tabStore"; import type {EditorViewState} from '@/stores/editorStore'; export const useDocumentStore = defineStore('document', () => { @@ -70,10 +69,6 @@ export const useDocumentStore = defineStore('document', () => { // 在新窗口中打开文档 const openDocumentInNewWindow = async (docId: number): Promise => { try { - const tabStore = useTabStore(); - if (tabStore.isTabsEnabled && tabStore.hasTab(docId)) { - tabStore.closeTab(docId); - } await OpenDocumentWindow(docId); return true; } catch (error) { @@ -112,7 +107,7 @@ export const useDocumentStore = defineStore('document', () => { } }; - // 打开文档 + // 打开文档 - 只负责文档数据管理 const openDocument = async (docId: number): Promise => { try { // 获取完整文档数据 @@ -131,7 +126,7 @@ export const useDocumentStore = defineStore('document', () => { } }; - // 更新文档元数据 + // 更新文档元数据 - 只负责文档数据管理 const updateDocumentMetadata = async (docId: number, title: string): Promise => { try { await DocumentService.UpdateDocumentTitle(docId, title); @@ -148,10 +143,6 @@ export const useDocumentStore = defineStore('document', () => { currentDocument.value.updated_at = new Date().toISOString(); } - // 同步更新标签页标题 - const tabStore = useTabStore(); - tabStore.updateTabTitle(docId, title); - return true; } catch (error) { console.error('Failed to update document metadata:', error); @@ -159,7 +150,7 @@ export const useDocumentStore = defineStore('document', () => { } }; - // 删除文档 + // 删除文档 - 只负责文档数据管理 const deleteDocument = async (docId: number): Promise => { try { await DocumentService.DeleteDocument(docId); @@ -167,12 +158,6 @@ export const useDocumentStore = defineStore('document', () => { // 更新本地状态 delete documents.value[docId]; - // 同步清理标签页 - const tabStore = useTabStore(); - if (tabStore.hasTab(docId)) { - tabStore.closeTab(docId); - } - // 如果删除的是当前文档,切换到第一个可用文档 if (currentDocumentId.value === docId) { const availableDocs = Object.values(documents.value); @@ -192,7 +177,7 @@ export const useDocumentStore = defineStore('document', () => { }; // === 初始化 === - const initialize = async (urlDocumentId?: number): Promise => { + const initDocument = async (urlDocumentId?: number): Promise => { try { await getDocumentMetaList(); @@ -235,7 +220,7 @@ export const useDocumentStore = defineStore('document', () => { closeDocumentSelector, setError, clearError, - initialize, + initDocument, }; }, { persist: { diff --git a/frontend/src/stores/editorStore.ts b/frontend/src/stores/editorStore.ts index 0688d95..a5b504f 100644 --- a/frontend/src/stores/editorStore.ts +++ b/frontend/src/stores/editorStore.ts @@ -1,5 +1,5 @@ import {defineStore} from 'pinia'; -import {computed, nextTick, ref, watch} from 'vue'; +import {computed, nextTick, ref} from 'vue'; import {EditorView} from '@codemirror/view'; import {EditorState, Extension} from '@codemirror/state'; import {useConfigStore} from './configStore'; @@ -24,7 +24,6 @@ import { import {useExtensionStore} from './extensionStore'; import createCodeBlockExtension from "@/views/editor/extensions/codeblock"; import {LruCache} from '@/common/utils/lruCache'; -import {AsyncManager} from '@/common/utils/asyncManager'; import {generateContentHash} from "@/common/utils/hashUtils"; import {createTimerManager, type TimerManager} from '@/common/utils/timerUtils'; import {EDITOR_CONFIG} from '@/common/constant/editor'; @@ -37,7 +36,6 @@ export interface DocumentStats { selectedCharacters: number; } -// 修复:只保存光标位置,恢复时自动滚动到光标处 export interface EditorViewState { cursorPos: number; } @@ -54,7 +52,6 @@ interface EditorInstance { lastContentHash: string; lastParsed: Date; } | null; - // 修复:使用统一的类型,可选但不是 undefined | {...} editorState?: EditorViewState; } @@ -74,14 +71,9 @@ export const useEditorStore = defineStore('editor', () => { characters: 0, selectedCharacters: 0 }); - + // 编辑器加载状态 const isLoading = ref(false); - // 修复:使用操作计数器精确管理加载状态 - const loadingOperations = ref(0); - - // 异步操作管理器 - const operationManager = new AsyncManager(); // 自动保存设置 - 从配置动态获取 const getAutoSaveDelay = () => configStore.config.editing.autoSaveDelay; @@ -91,7 +83,7 @@ export const useEditorStore = defineStore('editor', () => { if (instance) { instance.syntaxTreeCache = null; } - }, { delay: 500 }); // 500ms 内的多次输入只清理一次 + }, {delay: 500}); // 500ms 内的多次输入只清理一次 // 缓存化的语法树确保方法 @@ -106,42 +98,33 @@ export const useEditorStore = defineStore('editor', () => { // 检查是否需要重新构建语法树 const cache = instance.syntaxTreeCache; - const shouldRebuild = !cache || - cache.lastDocLength !== docLength || + const shouldRebuild = !cache || + cache.lastDocLength !== docLength || cache.lastContentHash !== contentHash || (now.getTime() - cache.lastParsed.getTime()) > EDITOR_CONFIG.SYNTAX_TREE_CACHE_TIMEOUT; if (shouldRebuild) { - try { - ensureSyntaxTree(view.state, docLength, 5000); - - // 更新缓存 - instance.syntaxTreeCache = { - lastDocLength: docLength, - lastContentHash: contentHash, - lastParsed: now - }; - } catch (error) { - console.warn('Failed to ensure syntax tree:', error); - } + ensureSyntaxTree(view.state, docLength, 5000); + + // 更新缓存 + instance.syntaxTreeCache = { + lastDocLength: docLength, + lastContentHash: contentHash, + lastParsed: now + }; + } }; // 创建编辑器实例 const createEditorInstance = async ( - content: string, - operationId: number, + content: string, documentId: number ): Promise => { if (!containerElement.value) { throw new Error('Editor container not set'); } - // 检查操作是否仍然有效 - if (!operationManager.isOperationValid(operationId, documentId)) { - throw new Error('Operation cancelled'); - } - // 获取基本扩展 const basicExtensions = createBasicSetup(); @@ -185,27 +168,12 @@ export const useEditorStore = defineStore('editor', () => { // 光标位置持久化扩展 const cursorPositionExtension = createCursorPositionExtension(documentId); - // 再次检查操作有效性 - if (!operationManager.isOperationValid(operationId, documentId)) { - throw new Error('Operation cancelled'); - } - // 快捷键扩展 const keymapExtension = await createDynamicKeymapExtension(); - // 检查操作有效性 - if (!operationManager.isOperationValid(operationId, documentId)) { - throw new Error('Operation cancelled'); - } - // 动态扩展,传递文档ID以便扩展管理器可以预初始化 const dynamicExtensions = await createDynamicExtensions(); - // 最终检查操作有效性 - if (!operationManager.isOperationValid(operationId, documentId)) { - throw new Error('Operation cancelled'); - } - // 组合所有扩展 const extensions: Extension[] = [ keymapExtension, @@ -224,8 +192,8 @@ export const useEditorStore = defineStore('editor', () => { // 获取保存的光标位置 const savedState = documentStore.documentStates[documentId]; const docLength = content.length; - const initialCursorPos = savedState?.cursorPos !== undefined - ? Math.min(savedState.cursorPos, docLength) + const initialCursorPos = savedState?.cursorPos !== undefined + ? Math.min(savedState.cursorPos, docLength) : docLength; @@ -233,7 +201,7 @@ export const useEditorStore = defineStore('editor', () => { const state = EditorState.create({ doc: content, extensions, - selection: { anchor: initialCursorPos, head: initialCursorPos } + selection: {anchor: initialCursorPos, head: initialCursorPos} }); return new EditorView({ @@ -271,9 +239,8 @@ export const useEditorStore = defineStore('editor', () => { // 获取或创建编辑器 const getOrCreateEditor = async ( - documentId: number, - content: string, - operationId: number + documentId: number, + content: string ): Promise => { // 检查缓存 const cached = editorCache.get(documentId); @@ -281,29 +248,8 @@ export const useEditorStore = defineStore('editor', () => { return cached.view; } - // 检查操作是否仍然有效 - if (!operationManager.isOperationValid(operationId, documentId)) { - throw new Error('Operation cancelled'); - } - // 创建新的编辑器实例 - const view = await createEditorInstance(content, operationId, documentId); - - // 完善取消操作时的清理逻辑 - if (!operationManager.isOperationValid(operationId, documentId)) { - // 如果操作已取消,彻底清理创建的实例 - try { - // 移除 DOM 元素(如果已添加到文档) - if (view.dom && view.dom.parentElement) { - view.dom.remove(); - } - // 销毁编辑器视图 - view.destroy(); - } catch (error) { - console.error('Error cleaning up cancelled editor:', error); - } - throw new Error('Operation cancelled'); - } + const view = await createEditorInstance(content, documentId); addEditorToCache(documentId, view, content); @@ -333,10 +279,10 @@ export const useEditorStore = defineStore('editor', () => { requestAnimationFrame(() => { // 滚动到当前光标位置 scrollToCursor(instance.view); - + // 聚焦编辑器 instance.view.focus(); - + // 使用缓存的语法树确保方法 ensureSyntaxTreeCached(instance.view, documentId); }); @@ -354,7 +300,7 @@ export const useEditorStore = defineStore('editor', () => { try { const content = instance.view.state.doc.toString(); const lastModified = instance.lastModified; - + await DocumentService.UpdateDocumentContent(documentId, content); // 检查在保存期间内容是否又被修改了 @@ -381,7 +327,7 @@ export const useEditorStore = defineStore('editor', () => { // 立即设置脏标记和修改时间(切换文档时需要判断) instance.isDirty = true; instance.lastModified = new Date(); - + // 优使用防抖清理语法树缓存 debouncedClearSyntaxCache.debouncedFn(instance); @@ -392,24 +338,18 @@ export const useEditorStore = defineStore('editor', () => { }; + // 检查容器是否已设置 + const hasContainer = computed(() => containerElement.value !== null); + // 设置编辑器容器 const setEditorContainer = (container: HTMLElement | null) => { containerElement.value = container; - - // 如果设置容器时已有当前文档,立即加载编辑器 - if (container && documentStore.currentDocument && documentStore.currentDocument.id !== undefined) { - loadEditor(documentStore.currentDocument.id, documentStore.currentDocument.content || ''); - } + // watch 会自动监听并加载编辑器,无需手动调用 }; // 加载编辑器 const loadEditor = async (documentId: number, content: string) => { - // 修复:使用计数器精确管理加载状态 - loadingOperations.value++; isLoading.value = true; - - // 开始新的操作 - const { operationId } = operationManager.startOperation(documentId); try { // 验证参数 @@ -422,33 +362,25 @@ export const useEditorStore = defineStore('editor', () => { const currentDocId = documentStore.currentDocumentId; if (currentDocId && currentDocId !== documentId) { await saveEditorContent(currentDocId); - - // 检查操作是否仍然有效 - if (!operationManager.isOperationValid(operationId, documentId)) { - return; - } } } // 获取或创建编辑器 - const view = await getOrCreateEditor(documentId, content, operationId); + const view = await getOrCreateEditor(documentId, content); - // 检查操作是否仍然有效 - if (!operationManager.isOperationValid(operationId, documentId)) { - return; - } - - // 更新内容(如果需要) + // 更新内容 const instance = editorCache.get(documentId); if (instance && instance.content !== content) { // 确保编辑器视图有效 if (view && view.state && view.dispatch) { + const contentLength = content.length; view.dispatch({ changes: { from: 0, to: view.state.doc.length, insert: content - } + }, + selection: {anchor: contentLength, head: contentLength} }); instance.content = content; instance.isDirty = false; @@ -460,32 +392,14 @@ export const useEditorStore = defineStore('editor', () => { } } - // 最终检查操作有效性 - if (!operationManager.isOperationValid(operationId, documentId)) { - return; - } - // 显示编辑器 showEditor(documentId); } catch (error) { - if (error instanceof Error && error.message === 'Operation cancelled') { - console.log(`Editor loading cancelled for document ${documentId}`); - } else { - console.error('Failed to load editor:', error); - } + console.error('Failed to load editor:', error); } finally { - // 完成操作 - operationManager.completeOperation(operationId); - - // 修复:使用计数器精确管理加载状态,避免快速切换时状态不准确 - loadingOperations.value--; - // 延迟一段时间后再取消加载状态,但要确保所有操作都完成了 setTimeout(() => { - if (loadingOperations.value <= 0) { - loadingOperations.value = 0; - isLoading.value = false; - } + isLoading.value = false; }, EDITOR_CONFIG.LOADING_DELAY); } }; @@ -495,12 +409,6 @@ export const useEditorStore = defineStore('editor', () => { const instance = editorCache.get(documentId); if (instance) { try { - // 如果正在加载这个文档,取消操作 - if (operationManager.getCurrentContext() === documentId) { - operationManager.cancelAllOperations(); - } - - // 修复:移除前先保存内容(如果有未保存的修改) if (instance.isDirty) { await saveEditorContent(documentId); } @@ -583,9 +491,6 @@ export const useEditorStore = defineStore('editor', () => { // 清空所有编辑器 const clearAllEditors = () => { - // 取消所有挂起的操作 - operationManager.cancelAllOperations(); - editorCache.clear((_documentId, instance) => { // 清除自动保存定时器 instance.autoSaveTimer.clear(); @@ -598,7 +503,7 @@ export const useEditorStore = defineStore('editor', () => { // 销毁编辑器 instance.view.destroy(); }); - + currentEditor.value = null; }; @@ -606,7 +511,7 @@ export const useEditorStore = defineStore('editor', () => { const updateExtension = async (id: number, enabled: boolean, config?: any) => { // 更新启用状态 await ExtensionService.UpdateExtensionEnabled(id, enabled); - + // 如果需要更新配置 if (config !== undefined) { await ExtensionService.UpdateExtensionConfig(id, config); @@ -614,7 +519,7 @@ export const useEditorStore = defineStore('editor', () => { // 重新加载扩展配置 await extensionStore.loadExtensions(); - + // 获取更新后的扩展名称 const extension = extensionStore.extensions.find(ext => ext.id === id); if (!extension) return; @@ -630,38 +535,12 @@ export const useEditorStore = defineStore('editor', () => { await applyKeymapSettings(); }; - // 监听文档切换 - watch(() => documentStore.currentDocument, async (newDoc, oldDoc) => { - if (newDoc && newDoc.id !== undefined && containerElement.value) { - // 等待 DOM 更新完成,再加载新文档的编辑器 - await nextTick(); - loadEditor(newDoc.id, newDoc.content || ''); - } - }); - - // 创建字体配置的计算属性 - const fontConfig = computed(() => ({ - fontSize: configStore.config.editing.fontSize, - fontFamily: configStore.config.editing.fontFamily, - lineHeight: configStore.config.editing.lineHeight, - fontWeight: configStore.config.editing.fontWeight - })); - // 创建Tab配置的计算属性 - const tabConfig = computed(() => ({ - tabSize: configStore.config.editing.tabSize, - enableTabIndent: configStore.config.editing.enableTabIndent, - tabType: configStore.config.editing.tabType - })); - // 监听字体配置变化 - watch(fontConfig, applyFontSettings, { deep: true }); - // 监听Tab配置变化 - watch(tabConfig, applyTabSettings, { deep: true }); - return { // 状态 currentEditor, documentStats, isLoading, + hasContainer, // 方法 setEditorContainer, @@ -670,7 +549,6 @@ export const useEditorStore = defineStore('editor', () => { clearAllEditors, onContentChange, - // 配置更新方法 applyFontSettings, applyThemeSettings, applyTabSettings, diff --git a/frontend/src/stores/tabStore.ts b/frontend/src/stores/tabStore.ts index dcd1413..bd398bc 100644 --- a/frontend/src/stores/tabStore.ts +++ b/frontend/src/stores/tabStore.ts @@ -18,12 +18,9 @@ export const useTabStore = defineStore('tab', () => { const tabsMap = ref>({}); const tabOrder = ref([]); // 维护标签页顺序 const draggedTabId = ref(null); - - // === 计算属性 === - + const isTabsEnabled = computed(() => configStore.config.general.enableTabs); const canCloseTab = computed(() => tabOrder.value.length > 1); - const currentDocumentId = computed(() => documentStore.currentDocumentId); // 按顺序返回标签页数组(用于UI渲染) const tabs = computed(() => { @@ -75,7 +72,7 @@ export const useTabStore = defineStore('tab', () => { /** * 关闭标签页 */ - const closeTab = (documentId: number) => { + const closeTab = async (documentId: number) => { if (!hasTab(documentId)) return; const tabIndex = tabOrder.value.indexOf(documentId); @@ -95,7 +92,7 @@ export const useTabStore = defineStore('tab', () => { if (nextIndex >= 0 && tabOrder.value[nextIndex]) { const nextDocumentId = tabOrder.value[nextIndex]; - switchToTabAndDocument(nextDocumentId); + await switchToTabAndDocument(nextDocumentId); } } }; @@ -120,15 +117,15 @@ export const useTabStore = defineStore('tab', () => { /** * 切换到指定标签页并打开对应文档 */ - const switchToTabAndDocument = (documentId: number) => { + const switchToTabAndDocument = async (documentId: number) => { if (!hasTab(documentId)) return; - + // 如果点击的是当前已激活的文档,不需要重复请求 if (documentStore.currentDocumentId === documentId) { return; } - documentStore.openDocument(documentId); + await documentStore.openDocument(documentId); }; /** @@ -172,8 +169,8 @@ export const useTabStore = defineStore('tab', () => { /** * 初始化标签页(当前文档) */ - const initializeTab = () => { - // 先验证并清理无效的标签页(处理持久化的脏数据) + const initTab = () => { + // 先验证并清理无效的标签页 validateTabs(); if (isTabsEnabled.value) { @@ -189,7 +186,7 @@ export const useTabStore = defineStore('tab', () => { /** * 关闭其他标签页(除了指定的标签页) */ - const closeOtherTabs = (keepDocumentId: number) => { + const closeOtherTabs = async (keepDocumentId: number) => { if (!hasTab(keepDocumentId)) return; // 获取所有其他标签页的ID @@ -200,14 +197,14 @@ export const useTabStore = defineStore('tab', () => { // 如果当前打开的文档在被关闭的标签中,需要切换到保留的文档 if (otherTabIds.includes(documentStore.currentDocumentId!)) { - switchToTabAndDocument(keepDocumentId); + await switchToTabAndDocument(keepDocumentId); } }; /** * 关闭指定标签页右侧的所有标签页 */ - const closeTabsToRight = (documentId: number) => { + const closeTabsToRight = async (documentId: number) => { const index = getTabIndex(documentId); if (index === -1) return; @@ -219,14 +216,14 @@ export const useTabStore = defineStore('tab', () => { // 如果当前打开的文档在被关闭的右侧标签中,需要切换到指定的文档 if (rightTabIds.includes(documentStore.currentDocumentId!)) { - switchToTabAndDocument(documentId); + await switchToTabAndDocument(documentId); } }; /** * 关闭指定标签页左侧的所有标签页 */ - const closeTabsToLeft = (documentId: number) => { + const closeTabsToLeft = async (documentId: number) => { const index = getTabIndex(documentId); if (index <= 0) return; @@ -238,7 +235,7 @@ export const useTabStore = defineStore('tab', () => { // 如果当前打开的文档在被关闭的左侧标签中,需要切换到指定的文档 if (leftTabIds.includes(documentStore.currentDocumentId!)) { - switchToTabAndDocument(documentId); + await switchToTabAndDocument(documentId); } }; @@ -262,7 +259,6 @@ export const useTabStore = defineStore('tab', () => { // 计算属性 isTabsEnabled, canCloseTab, - currentDocumentId, // 方法 addOrActivateTab, @@ -273,7 +269,7 @@ export const useTabStore = defineStore('tab', () => { switchToTabAndDocument, moveTab, getTabIndex, - initializeTab, + initTab, clearAllTabs, updateTabTitle, validateTabs, diff --git a/frontend/src/stores/themeStore.ts b/frontend/src/stores/themeStore.ts index c37c239..2eeea5f 100644 --- a/frontend/src/stores/themeStore.ts +++ b/frontend/src/stores/themeStore.ts @@ -6,6 +6,7 @@ import {ThemeService} from '@/../bindings/voidraft/internal/services'; import {useConfigStore} from './configStore'; import type {ThemeColors} from '@/views/editor/theme/types'; import {cloneThemeColors, FALLBACK_THEME_NAME, themePresetList, themePresetMap} from '@/views/editor/theme/presets'; +import {useEditorStore} from "@/stores/editorStore"; // 类型定义 type ThemeOption = { name: string; type: ThemeType }; @@ -91,11 +92,12 @@ export const useThemeStore = defineStore('theme', () => { // 同步应用到 DOM 与编辑器 const applyAllThemes = () => { applyThemeToDOM(currentTheme.value); + const editorStore = useEditorStore(); + editorStore.applyThemeSettings(); }; // 初始化主题 const initTheme = async () => { - applyThemeToDOM(currentTheme.value); await loadThemeColors(); applyAllThemes(); }; diff --git a/frontend/src/views/editor/Editor.vue b/frontend/src/views/editor/Editor.vue index 4e584a9..2d68ec1 100644 --- a/frontend/src/views/editor/Editor.vue +++ b/frontend/src/views/editor/Editor.vue @@ -28,11 +28,16 @@ onMounted(async () => { const urlDocumentId = windowStore.currentDocumentId ? parseInt(windowStore.currentDocumentId) : undefined; - await documentStore.initialize(urlDocumentId); + await documentStore.initDocument(urlDocumentId); editorStore.setEditorContainer(editorElement.value); - await tabStore.initializeTab(); + const currentDoc = documentStore.currentDocument; + if (currentDoc && currentDoc.id !== undefined) { + await editorStore.loadEditor(currentDoc.id, currentDoc.content || ''); + } + + await tabStore.initTab(); }); onBeforeUnmount(() => { diff --git a/frontend/src/views/settings/pages/EditingPage.vue b/frontend/src/views/settings/pages/EditingPage.vue index 3e70bbc..2574220 100644 --- a/frontend/src/views/settings/pages/EditingPage.vue +++ b/frontend/src/views/settings/pages/EditingPage.vue @@ -1,5 +1,6 @@