🎨 Optimize code & Upgrade dependencies

This commit is contained in:
2026-01-01 02:27:21 +08:00
parent 9ec22add55
commit 76f6c30b9d
17 changed files with 316 additions and 1247 deletions

View File

@@ -36,7 +36,7 @@
"@codemirror/lint": "^6.9.2", "@codemirror/lint": "^6.9.2",
"@codemirror/search": "^6.5.11", "@codemirror/search": "^6.5.11",
"@codemirror/state": "^6.5.3", "@codemirror/state": "^6.5.3",
"@codemirror/view": "^6.39.6", "@codemirror/view": "^6.39.8",
"@cospaia/prettier-plugin-clojure": "^0.0.2", "@cospaia/prettier-plugin-clojure": "^0.0.2",
"@lezer/highlight": "^1.2.3", "@lezer/highlight": "^1.2.3",
"@lezer/lr": "^1.4.5", "@lezer/lr": "^1.4.5",
@@ -63,7 +63,7 @@
"prettier": "^3.7.4", "prettier": "^3.7.4",
"sass": "^1.97.1", "sass": "^1.97.1",
"vue": "^3.5.26", "vue": "^3.5.26",
"vue-i18n": "^11.2.7", "vue-i18n": "^11.2.8",
"vue-pick-colors": "^1.8.0", "vue-pick-colors": "^1.8.0",
"vue-router": "^4.6.4" "vue-router": "^4.6.4"
}, },
@@ -77,9 +77,8 @@
"eslint": "^9.39.2", "eslint": "^9.39.2",
"eslint-plugin-vue": "^10.6.2", "eslint-plugin-vue": "^10.6.2",
"globals": "^16.5.0", "globals": "^16.5.0",
"happy-dom": "^20.0.11",
"typescript": "^5.9.3", "typescript": "^5.9.3",
"typescript-eslint": "^8.50.1", "typescript-eslint": "^8.51.0",
"unplugin-vue-components": "^30.0.0", "unplugin-vue-components": "^30.0.0",
"vite": "npm:rolldown-vite@latest", "vite": "npm:rolldown-vite@latest",
"vite-plugin-node-polyfills": "^0.24.0", "vite-plugin-node-polyfills": "^0.24.0",
@@ -610,9 +609,9 @@
} }
}, },
"node_modules/@codemirror/view": { "node_modules/@codemirror/view": {
"version": "6.39.6", "version": "6.39.8",
"resolved": "https://registry.npmmirror.com/@codemirror/view/-/view-6.39.6.tgz", "resolved": "https://registry.npmmirror.com/@codemirror/view/-/view-6.39.8.tgz",
"integrity": "sha512-/N+SoP5NndJjkGInp3BwlUa3KQKD6bDo0TV6ep37ueAdQ7BVu/PqlZNywmgjCq0MQoZadZd8T+MZucSr7fktyQ==", "integrity": "sha512-1rASYd9Z/mE3tkbC9wInRlCNyCkSn+nLsiQKZhEDUUJiUfs/5FHDpCUDaQpoTIaNGeDc6/bhaEAyLmeEucEFPw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@codemirror/state": "^6.5.0", "@codemirror/state": "^6.5.0",
@@ -1341,13 +1340,13 @@
} }
}, },
"node_modules/@intlify/core-base": { "node_modules/@intlify/core-base": {
"version": "11.2.7", "version": "11.2.8",
"resolved": "https://registry.npmmirror.com/@intlify/core-base/-/core-base-11.2.7.tgz", "resolved": "https://registry.npmmirror.com/@intlify/core-base/-/core-base-11.2.8.tgz",
"integrity": "sha512-+Ra9I/LAzXDnmv/IrTO03WMCiLya7pHRmGJvNl9fKwx/W4REJ0xaMk2PxCRqnxcBsX443amEMdebQ3R1geiuIw==", "integrity": "sha512-nBq6Y1tVkjIUsLsdOjDSJj4AsjvD0UG3zsg9Fyc+OivwlA/oMHSKooUy9tpKj0HqZ+NWFifweHavdljlBLTwdA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@intlify/message-compiler": "11.2.7", "@intlify/message-compiler": "11.2.8",
"@intlify/shared": "11.2.7" "@intlify/shared": "11.2.8"
}, },
"engines": { "engines": {
"node": ">= 16" "node": ">= 16"
@@ -1357,12 +1356,12 @@
} }
}, },
"node_modules/@intlify/message-compiler": { "node_modules/@intlify/message-compiler": {
"version": "11.2.7", "version": "11.2.8",
"resolved": "https://registry.npmmirror.com/@intlify/message-compiler/-/message-compiler-11.2.7.tgz", "resolved": "https://registry.npmmirror.com/@intlify/message-compiler/-/message-compiler-11.2.8.tgz",
"integrity": "sha512-TFamC+GzJAotAFwUNvbtRVBgvuSn2nCwKNresmPUHv3IIVMmXJt7QQJj/DORI1h8hs46ZF6L0Fs2xBohSOE4iQ==", "integrity": "sha512-A5n33doOjmHsBtCN421386cG1tWp5rpOjOYPNsnpjIJbQ4POF0QY2ezhZR9kr0boKwaHjbOifvyQvHj2UTrDFQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@intlify/shared": "11.2.7", "@intlify/shared": "11.2.8",
"source-map-js": "^1.0.2" "source-map-js": "^1.0.2"
}, },
"engines": { "engines": {
@@ -1373,9 +1372,9 @@
} }
}, },
"node_modules/@intlify/shared": { "node_modules/@intlify/shared": {
"version": "11.2.7", "version": "11.2.8",
"resolved": "https://registry.npmmirror.com/@intlify/shared/-/shared-11.2.7.tgz", "resolved": "https://registry.npmmirror.com/@intlify/shared/-/shared-11.2.8.tgz",
"integrity": "sha512-uvlkvc/0uQ4FDlHQZccpUnmcOwNcaI3i+69ck2YJ+GqM35AoVbuS63b+YfirV4G0SZh64Ij2UMcFRMmB4nr95w==", "integrity": "sha512-l6e4NZyUgv8VyXXH4DbuucFOBmxLF56C/mqh2tvApbzl2Hrhi1aTDcuv5TKdxzfHYmpO3UB0Cz04fgDT9vszfw==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 16" "node": ">= 16"
@@ -2865,23 +2864,25 @@
"resolved": "https://registry.npmmirror.com/@types/whatwg-mimetype/-/whatwg-mimetype-3.0.2.tgz", "resolved": "https://registry.npmmirror.com/@types/whatwg-mimetype/-/whatwg-mimetype-3.0.2.tgz",
"integrity": "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==", "integrity": "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT",
"optional": true,
"peer": true
}, },
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.50.1", "version": "8.51.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.50.1.tgz", "resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.51.0.tgz",
"integrity": "sha512-PKhLGDq3JAg0Jk/aK890knnqduuI/Qj+udH7wCf0217IGi4gt+acgCyPVe79qoT+qKUvHMDQkwJeKW9fwl8Cyw==", "integrity": "sha512-XtssGWJvypyM2ytBnSnKtHYOGT+4ZwTnBVl36TA4nRO2f4PRNGz5/1OszHzcZCvcBMh+qb7I06uoCmLTRdR9og==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/regexpp": "^4.10.0", "@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "8.50.1", "@typescript-eslint/scope-manager": "8.51.0",
"@typescript-eslint/type-utils": "8.50.1", "@typescript-eslint/type-utils": "8.51.0",
"@typescript-eslint/utils": "8.50.1", "@typescript-eslint/utils": "8.51.0",
"@typescript-eslint/visitor-keys": "8.50.1", "@typescript-eslint/visitor-keys": "8.51.0",
"ignore": "^7.0.0", "ignore": "^7.0.0",
"natural-compare": "^1.4.0", "natural-compare": "^1.4.0",
"ts-api-utils": "^2.1.0" "ts-api-utils": "^2.2.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2891,7 +2892,7 @@
"url": "https://opencollective.com/typescript-eslint" "url": "https://opencollective.com/typescript-eslint"
}, },
"peerDependencies": { "peerDependencies": {
"@typescript-eslint/parser": "^8.50.1", "@typescript-eslint/parser": "^8.51.0",
"eslint": "^8.57.0 || ^9.0.0", "eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <6.0.0" "typescript": ">=4.8.4 <6.0.0"
} }
@@ -2907,16 +2908,16 @@
} }
}, },
"node_modules/@typescript-eslint/parser": { "node_modules/@typescript-eslint/parser": {
"version": "8.50.1", "version": "8.51.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-8.50.1.tgz", "resolved": "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-8.51.0.tgz",
"integrity": "sha512-hM5faZwg7aVNa819m/5r7D0h0c9yC4DUlWAOvHAtISdFTc8xB86VmX5Xqabrama3wIPJ/q9RbGS1worb6JfnMg==", "integrity": "sha512-3xP4XzzDNQOIqBMWogftkwxhg5oMKApqY0BAflmLZiFYHqyhSOxv/cd/zPQLTcCXr4AkaKb25joocY0BD1WC6A==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "8.50.1", "@typescript-eslint/scope-manager": "8.51.0",
"@typescript-eslint/types": "8.50.1", "@typescript-eslint/types": "8.51.0",
"@typescript-eslint/typescript-estree": "8.50.1", "@typescript-eslint/typescript-estree": "8.51.0",
"@typescript-eslint/visitor-keys": "8.50.1", "@typescript-eslint/visitor-keys": "8.51.0",
"debug": "^4.3.4" "debug": "^4.3.4"
}, },
"engines": { "engines": {
@@ -2932,14 +2933,14 @@
} }
}, },
"node_modules/@typescript-eslint/project-service": { "node_modules/@typescript-eslint/project-service": {
"version": "8.50.1", "version": "8.51.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/project-service/-/project-service-8.50.1.tgz", "resolved": "https://registry.npmmirror.com/@typescript-eslint/project-service/-/project-service-8.51.0.tgz",
"integrity": "sha512-E1ur1MCVf+YiP89+o4Les/oBAVzmSbeRB0MQLfSlYtbWU17HPxZ6Bhs5iYmKZRALvEuBoXIZMOIRRc/P++Ortg==", "integrity": "sha512-Luv/GafO07Z7HpiI7qeEW5NW8HUtZI/fo/kE0YbtQEFpJRUuR0ajcWfCE5bnMvL7QQFrmT/odMe8QZww8X2nfQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/tsconfig-utils": "^8.50.1", "@typescript-eslint/tsconfig-utils": "^8.51.0",
"@typescript-eslint/types": "^8.50.1", "@typescript-eslint/types": "^8.51.0",
"debug": "^4.3.4" "debug": "^4.3.4"
}, },
"engines": { "engines": {
@@ -2954,14 +2955,14 @@
} }
}, },
"node_modules/@typescript-eslint/scope-manager": { "node_modules/@typescript-eslint/scope-manager": {
"version": "8.50.1", "version": "8.51.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-8.50.1.tgz", "resolved": "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-8.51.0.tgz",
"integrity": "sha512-mfRx06Myt3T4vuoHaKi8ZWNTPdzKPNBhiblze5N50//TSHOAQQevl/aolqA/BcqqbJ88GUnLqjjcBc8EWdBcVw==", "integrity": "sha512-JhhJDVwsSx4hiOEQPeajGhCWgBMBwVkxC/Pet53EpBVs7zHHtayKefw1jtPaNRXpI9RA2uocdmpdfE7T+NrizA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.50.1", "@typescript-eslint/types": "8.51.0",
"@typescript-eslint/visitor-keys": "8.50.1" "@typescript-eslint/visitor-keys": "8.51.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2972,9 +2973,9 @@
} }
}, },
"node_modules/@typescript-eslint/tsconfig-utils": { "node_modules/@typescript-eslint/tsconfig-utils": {
"version": "8.50.1", "version": "8.51.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.50.1.tgz", "resolved": "https://registry.npmmirror.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.51.0.tgz",
"integrity": "sha512-ooHmotT/lCWLXi55G4mvaUF60aJa012QzvLK0Y+Mp4WdSt17QhMhWOaBWeGTFVkb2gDgBe19Cxy1elPXylslDw==", "integrity": "sha512-Qi5bSy/vuHeWyir2C8u/uqGMIlIDu8fuiYWv48ZGlZ/k+PRPHtaAu7erpc7p5bzw2WNNSniuxoMSO4Ar6V9OXw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -2989,17 +2990,17 @@
} }
}, },
"node_modules/@typescript-eslint/type-utils": { "node_modules/@typescript-eslint/type-utils": {
"version": "8.50.1", "version": "8.51.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-8.50.1.tgz", "resolved": "https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-8.51.0.tgz",
"integrity": "sha512-7J3bf022QZE42tYMO6SL+6lTPKFk/WphhRPe9Tw/el+cEwzLz1Jjz2PX3GtGQVxooLDKeMVmMt7fWpYRdG5Etg==", "integrity": "sha512-0XVtYzxnobc9K0VU7wRWg1yiUrw4oQzexCG2V2IDxxCxhqBMSMbjB+6o91A+Uc0GWtgjCa3Y8bi7hwI0Tu4n5Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.50.1", "@typescript-eslint/types": "8.51.0",
"@typescript-eslint/typescript-estree": "8.50.1", "@typescript-eslint/typescript-estree": "8.51.0",
"@typescript-eslint/utils": "8.50.1", "@typescript-eslint/utils": "8.51.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"ts-api-utils": "^2.1.0" "ts-api-utils": "^2.2.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -3014,9 +3015,9 @@
} }
}, },
"node_modules/@typescript-eslint/types": { "node_modules/@typescript-eslint/types": {
"version": "8.50.1", "version": "8.51.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/types/-/types-8.50.1.tgz", "resolved": "https://registry.npmmirror.com/@typescript-eslint/types/-/types-8.51.0.tgz",
"integrity": "sha512-v5lFIS2feTkNyMhd7AucE/9j/4V9v5iIbpVRncjk/K0sQ6Sb+Np9fgYS/63n6nwqahHQvbmujeBL7mp07Q9mlA==", "integrity": "sha512-TizAvWYFM6sSscmEakjY3sPqGwxZRSywSsPEiuZF6d5GmGD9Gvlsv0f6N8FvAAA0CD06l3rIcWNbsN1e5F/9Ag==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -3028,21 +3029,21 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree": { "node_modules/@typescript-eslint/typescript-estree": {
"version": "8.50.1", "version": "8.51.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.50.1.tgz", "resolved": "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.51.0.tgz",
"integrity": "sha512-woHPdW+0gj53aM+cxchymJCrh0cyS7BTIdcDxWUNsclr9VDkOSbqC13juHzxOmQ22dDkMZEpZB+3X1WpUvzgVQ==", "integrity": "sha512-1qNjGqFRmlq0VW5iVlcyHBbCjPB7y6SxpBkrbhNWMy/65ZoncXCEPJxkRZL8McrseNH6lFhaxCIaX+vBuFnRng==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/project-service": "8.50.1", "@typescript-eslint/project-service": "8.51.0",
"@typescript-eslint/tsconfig-utils": "8.50.1", "@typescript-eslint/tsconfig-utils": "8.51.0",
"@typescript-eslint/types": "8.50.1", "@typescript-eslint/types": "8.51.0",
"@typescript-eslint/visitor-keys": "8.50.1", "@typescript-eslint/visitor-keys": "8.51.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"minimatch": "^9.0.4", "minimatch": "^9.0.4",
"semver": "^7.6.0", "semver": "^7.6.0",
"tinyglobby": "^0.2.15", "tinyglobby": "^0.2.15",
"ts-api-utils": "^2.1.0" "ts-api-utils": "^2.2.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -3082,16 +3083,16 @@
} }
}, },
"node_modules/@typescript-eslint/utils": { "node_modules/@typescript-eslint/utils": {
"version": "8.50.1", "version": "8.51.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-8.50.1.tgz", "resolved": "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-8.51.0.tgz",
"integrity": "sha512-lCLp8H1T9T7gPbEuJSnHwnSuO9mDf8mfK/Nion5mZmiEaQD9sWf9W4dfeFqRyqRjF06/kBuTmAqcs9sewM2NbQ==", "integrity": "sha512-11rZYxSe0zabiKaCP2QAwRf/dnmgFgvTmeDTtZvUvXG3UuAdg/GU02NExmmIXzz3vLGgMdtrIosI84jITQOxUA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.7.0", "@eslint-community/eslint-utils": "^4.7.0",
"@typescript-eslint/scope-manager": "8.50.1", "@typescript-eslint/scope-manager": "8.51.0",
"@typescript-eslint/types": "8.50.1", "@typescript-eslint/types": "8.51.0",
"@typescript-eslint/typescript-estree": "8.50.1" "@typescript-eslint/typescript-estree": "8.51.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -3106,13 +3107,13 @@
} }
}, },
"node_modules/@typescript-eslint/visitor-keys": { "node_modules/@typescript-eslint/visitor-keys": {
"version": "8.50.1", "version": "8.51.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.50.1.tgz", "resolved": "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.51.0.tgz",
"integrity": "sha512-IrDKrw7pCRUR94zeuCSUWQ+w8JEf5ZX5jl/e6AHGSLi1/zIr0lgutfn/7JpfCey+urpgQEdrZVYzCaVVKiTwhQ==", "integrity": "sha512-mM/JRQOzhVN1ykejrvwnBRV3+7yTKK8tVANVN3o1O0t0v7o+jqdVu9crPy5Y9dov15TJk/FTIgoUGHrTOVL3Zg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.50.1", "@typescript-eslint/types": "8.51.0",
"eslint-visitor-keys": "^4.2.1" "eslint-visitor-keys": "^4.2.1"
}, },
"engines": { "engines": {
@@ -5733,6 +5734,8 @@
"integrity": "sha512-QsCdAUHAmiDeKeaNojb1OHOPF7NjcWPBR7obdu3NwH2a/oyQaLg5d0aaCy/9My6CdPChYF07dvz5chaXBGaD4g==", "integrity": "sha512-QsCdAUHAmiDeKeaNojb1OHOPF7NjcWPBR7obdu3NwH2a/oyQaLg5d0aaCy/9My6CdPChYF07dvz5chaXBGaD4g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optional": true,
"peer": true,
"dependencies": { "dependencies": {
"@types/node": "^20.0.0", "@types/node": "^20.0.0",
"@types/whatwg-mimetype": "^3.0.2", "@types/whatwg-mimetype": "^3.0.2",
@@ -5748,6 +5751,8 @@
"integrity": "sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==", "integrity": "sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optional": true,
"peer": true,
"dependencies": { "dependencies": {
"undici-types": "~6.21.0" "undici-types": "~6.21.0"
} }
@@ -5757,7 +5762,9 @@
"resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-6.21.0.tgz", "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT",
"optional": true,
"peer": true
}, },
"node_modules/has-flag": { "node_modules/has-flag": {
"version": "4.0.0", "version": "4.0.0",
@@ -8160,9 +8167,9 @@
} }
}, },
"node_modules/ts-api-utils": { "node_modules/ts-api-utils": {
"version": "2.1.0", "version": "2.3.0",
"resolved": "https://registry.npmmirror.com/ts-api-utils/-/ts-api-utils-2.1.0.tgz", "resolved": "https://registry.npmmirror.com/ts-api-utils/-/ts-api-utils-2.3.0.tgz",
"integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", "integrity": "sha512-6eg3Y9SF7SsAvGzRHQvvc1skDAhwI4YQ32ui1scxD1Ccr0G5qIIbUBT3pFTKX8kmWIQClHobtUdNuaBgwdfdWg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -8231,16 +8238,16 @@
} }
}, },
"node_modules/typescript-eslint": { "node_modules/typescript-eslint": {
"version": "8.50.1", "version": "8.51.0",
"resolved": "https://registry.npmmirror.com/typescript-eslint/-/typescript-eslint-8.50.1.tgz", "resolved": "https://registry.npmmirror.com/typescript-eslint/-/typescript-eslint-8.51.0.tgz",
"integrity": "sha512-ytTHO+SoYSbhAH9CrYnMhiLx8To6PSSvqnvXyPUgPETCvB6eBKmTI9w6XMPS3HsBRGkwTVBX+urA8dYQx6bHfQ==", "integrity": "sha512-jh8ZuM5oEh2PSdyQG9YAEM1TCGuWenLSuSUhf/irbVUNW9O5FhbFVONviN2TgMTBnUmyHv7E56rYnfLZK6TkiA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/eslint-plugin": "8.50.1", "@typescript-eslint/eslint-plugin": "8.51.0",
"@typescript-eslint/parser": "8.50.1", "@typescript-eslint/parser": "8.51.0",
"@typescript-eslint/typescript-estree": "8.50.1", "@typescript-eslint/typescript-estree": "8.51.0",
"@typescript-eslint/utils": "8.50.1" "@typescript-eslint/utils": "8.51.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -8946,13 +8953,13 @@
} }
}, },
"node_modules/vue-i18n": { "node_modules/vue-i18n": {
"version": "11.2.7", "version": "11.2.8",
"resolved": "https://registry.npmmirror.com/vue-i18n/-/vue-i18n-11.2.7.tgz", "resolved": "https://registry.npmmirror.com/vue-i18n/-/vue-i18n-11.2.8.tgz",
"integrity": "sha512-LPv8bAY5OA0UvFEXl4vBQOBqJzRrlExy92tWgRuwW7tbykHf7CH71G2Y4TM2OwGcIS4+hyqKHS2EVBqaYwPY9Q==", "integrity": "sha512-vJ123v/PXCZntd6Qj5Jumy7UBmIuE92VrtdX+AXr+1WzdBHojiBxnAxdfctUFL+/JIN+VQH4BhsfTtiGsvVObg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@intlify/core-base": "11.2.7", "@intlify/core-base": "11.2.8",
"@intlify/shared": "11.2.7", "@intlify/shared": "11.2.8",
"@vue/devtools-api": "^6.5.0" "@vue/devtools-api": "^6.5.0"
}, },
"engines": { "engines": {
@@ -9041,6 +9048,8 @@
"integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optional": true,
"peer": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
} }

View File

@@ -50,7 +50,7 @@
"@codemirror/lint": "^6.9.2", "@codemirror/lint": "^6.9.2",
"@codemirror/search": "^6.5.11", "@codemirror/search": "^6.5.11",
"@codemirror/state": "^6.5.3", "@codemirror/state": "^6.5.3",
"@codemirror/view": "^6.39.6", "@codemirror/view": "^6.39.8",
"@cospaia/prettier-plugin-clojure": "^0.0.2", "@cospaia/prettier-plugin-clojure": "^0.0.2",
"@lezer/highlight": "^1.2.3", "@lezer/highlight": "^1.2.3",
"@lezer/lr": "^1.4.5", "@lezer/lr": "^1.4.5",
@@ -77,7 +77,7 @@
"prettier": "^3.7.4", "prettier": "^3.7.4",
"sass": "^1.97.1", "sass": "^1.97.1",
"vue": "^3.5.26", "vue": "^3.5.26",
"vue-i18n": "^11.2.7", "vue-i18n": "^11.2.8",
"vue-pick-colors": "^1.8.0", "vue-pick-colors": "^1.8.0",
"vue-router": "^4.6.4" "vue-router": "^4.6.4"
}, },
@@ -91,9 +91,8 @@
"eslint": "^9.39.2", "eslint": "^9.39.2",
"eslint-plugin-vue": "^10.6.2", "eslint-plugin-vue": "^10.6.2",
"globals": "^16.5.0", "globals": "^16.5.0",
"happy-dom": "^20.0.11",
"typescript": "^5.9.3", "typescript": "^5.9.3",
"typescript-eslint": "^8.50.1", "typescript-eslint": "^8.51.0",
"unplugin-vue-components": "^30.0.0", "unplugin-vue-components": "^30.0.0",
"vite": "npm:rolldown-vite@latest", "vite": "npm:rolldown-vite@latest",
"vite-plugin-node-polyfills": "^0.24.0", "vite-plugin-node-polyfills": "^0.24.0",

View File

@@ -7,6 +7,8 @@ import {useThemeStore} from '@/stores/themeStore';
import {useUpdateStore} from '@/stores/updateStore'; import {useUpdateStore} from '@/stores/updateStore';
import WindowTitleBar from '@/components/titlebar/WindowTitleBar.vue'; import WindowTitleBar from '@/components/titlebar/WindowTitleBar.vue';
import {useTranslationStore} from "@/stores/translationStore"; import {useTranslationStore} from "@/stores/translationStore";
import {useI18n} from "vue-i18n";
import {LanguageType} from "../bindings/voidraft/internal/models";
const configStore = useConfigStore(); const configStore = useConfigStore();
const systemStore = useSystemStore(); const systemStore = useSystemStore();
@@ -14,6 +16,7 @@ const keybindingStore = useKeybindingStore();
const themeStore = useThemeStore(); const themeStore = useThemeStore();
const updateStore = useUpdateStore(); const updateStore = useUpdateStore();
const translationStore = useTranslationStore(); const translationStore = useTranslationStore();
const {locale} = useI18n();
onBeforeMount(async () => { onBeforeMount(async () => {
// 并行初始化配置、系统信息和快捷键配置 // 并行初始化配置、系统信息和快捷键配置
@@ -22,9 +25,8 @@ onBeforeMount(async () => {
systemStore.initSystemInfo(), systemStore.initSystemInfo(),
keybindingStore.loadKeyBindings(), keybindingStore.loadKeyBindings(),
]); ]);
// 初始化语言和主题 locale.value = configStore.config.appearance.language || LanguageType.LangEnUS;
await configStore.initLanguage();
await themeStore.initTheme(); await themeStore.initTheme();
await translationStore.loadTranslators(); await translationStore.loadTranslators();

View File

@@ -1,265 +0,0 @@
/**
* 操作信息接口
*/
interface OperationInfo {
controller: AbortController;
createdAt: number;
timeout?: number;
timeoutId?: NodeJS.Timeout;
}
/**
* 异步操作管理器
* 用于管理异步操作的竞态条件,确保只有最新的操作有效
* 支持操作超时和自动清理机制
*
* @template T 操作上下文的类型
*/
export class AsyncManager<T = any> {
private operationSequence = 0;
private pendingOperations = new Map<number, OperationInfo>();
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;
}
}

View File

@@ -1,42 +1,13 @@
import { LanguageType } from '@/../bindings/voidraft/internal/models/models';
import type { SupportedLocaleType } from '@/common/constant/locales';
/** /**
* 配置工具类 * 配置工具类
*/ */
export class ConfigUtils { export class ConfigUtils {
/**
* 将后端语言类型转换为前端语言代码
*/
static backendLanguageToFrontend(language: LanguageType): SupportedLocaleType {
return language === LanguageType.LangZhCN ? 'zh-CN' : 'en-US';
}
/** /**
* 将前端语言代码转换为后端语言类型 * 验证数值是否在指定范围内
*/ */
static frontendLanguageToBackend(locale: SupportedLocaleType): LanguageType { static clamp(value: number, min: number, max: number): number {
return locale === 'zh-CN' ? LanguageType.LangZhCN : LanguageType.LangEnUS; 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<T>(value: T, validValues: readonly T[]): boolean {
return validValues.includes(value);
}
/**
* 获取配置的默认值
*/
static getDefaultValue<T>(key: string, defaults: Record<string, { default: T }>): T {
return defaults[key]?.default;
}
} }

View File

@@ -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 = '<li>1</li><li>2</li>';
const toEl = document.createElement('ul');
toEl.innerHTML = '<li>1</li><li>2</li><li>3</li>';
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 = '<li>1</li><li>2</li><li>3</li>';
const toEl = document.createElement('ul');
toEl.innerHTML = '<li>1</li><li>2</li>';
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 = '<p>Old</p>';
const toEl = document.createElement('div');
toEl.innerHTML = '<p>New</p>';
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 = '<p>Old</p>';
container.appendChild(element);
morphHTML(element, '<p>New</p>');
expect(element.innerHTML).toBe('<p>New</p>');
});
test('应该处理复杂的 HTML 结构', () => {
const element = document.createElement('div');
element.innerHTML = '<h1>Title</h1><p>Paragraph</p>';
container.appendChild(element);
morphHTML(element, '<h1>New Title</h1><p>New Paragraph</p><span>Extra</span>');
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 = `
<li data-key="a">A</li>
<li data-key="b">B</li>
<li data-key="c">C</li>
`;
const toEl = document.createElement('ul');
toEl.innerHTML = `
<li data-key="a">A Updated</li>
<li data-key="b">B</li>
<li data-key="c">C</li>
`;
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 = `
<li data-key="a">A</li>
<li data-key="b">B</li>
<li data-key="c">C</li>
`;
const toEl = document.createElement('ul');
toEl.innerHTML = `
<li data-key="c">C</li>
<li data-key="a">A</li>
<li data-key="b">B</li>
`;
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 = `
<li data-key="a">A</li>
<li data-key="b">B</li>
`;
const toEl = document.createElement('ul');
toEl.innerHTML = `
<li data-key="a">A</li>
<li data-key="b">B</li>
<li data-key="c">C</li>
`;
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 = `
<li data-key="a">A</li>
<li data-key="b">B</li>
<li data-key="c">C</li>
`;
const toEl = document.createElement('ul');
toEl.innerHTML = `
<li data-key="a">A</li>
<li data-key="c">C</li>
`;
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 = `
<div class="outer">
<div class="inner">
<span>Text</span>
</div>
</div>
`;
const toEl = document.createElement('div');
toEl.innerHTML = `
<div class="outer modified">
<div class="inner">
<span>Updated Text</span>
<strong>New</strong>
</div>
</div>
`;
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');
});
});
});

View File

@@ -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<string, Element>();
Array.from(fromEl.children).forEach((child) => {
const key = child.getAttribute('data-key');
if (key) {
fromKeyMap.set(key, child);
}
});
const processedKeys = new Set<string>();
// 按照 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);
}

View File

@@ -7,7 +7,7 @@
v-for="tab in tabStore.tabs" v-for="tab in tabStore.tabs"
:key="tab.documentId" :key="tab.documentId"
:tab="tab" :tab="tab"
:isActive="tab.documentId === tabStore.currentDocumentId" :isActive="tab.documentId === documentStore.currentDocumentId"
:canClose="tabStore.canCloseTab" :canClose="tabStore.canCloseTab"
@click="switchToTab" @click="switchToTab"
@close="closeTab" @close="closeTab"
@@ -35,8 +35,12 @@ import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue';
import TabItem from './TabItem.vue'; import TabItem from './TabItem.vue';
import TabContextMenu from './TabContextMenu.vue'; import TabContextMenu from './TabContextMenu.vue';
import { useTabStore } from '@/stores/tabStore'; import { useTabStore } from '@/stores/tabStore';
import { useDocumentStore } from '@/stores/documentStore';
import { useEditorStore } from '@/stores/editorStore';
const tabStore = useTabStore(); const tabStore = useTabStore();
const documentStore = useDocumentStore();
const editorStore = useEditorStore();
// DOM 引用 // DOM 引用
const tabBarRef = ref<HTMLElement>(); const tabBarRef = ref<HTMLElement>();
@@ -50,8 +54,17 @@ const contextMenuTargetId = ref<number | null>(null);
// 标签页操作 // 标签页操作
const switchToTab = (documentId: number) => { const switchToTab = async (documentId: number) => {
tabStore.switchToTabAndDocument(documentId); 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) => { const closeTab = (documentId: number) => {
@@ -150,7 +163,7 @@ onUnmounted(() => {
}); });
// 监听当前活跃标签页的变化 // 监听当前活跃标签页的变化
watch(() => tabStore.currentDocumentId, () => { watch(() => documentStore.currentDocumentId, () => {
scrollToActiveTab(); scrollToActiveTab();
}); });

View File

@@ -2,6 +2,7 @@
import {computed, nextTick, reactive, ref, watch} from 'vue'; import {computed, nextTick, reactive, ref, watch} from 'vue';
import {useDocumentStore} from '@/stores/documentStore'; import {useDocumentStore} from '@/stores/documentStore';
import {useTabStore} from '@/stores/tabStore'; import {useTabStore} from '@/stores/tabStore';
import {useEditorStore} from '@/stores/editorStore';
import {useWindowStore} from '@/stores/windowStore'; import {useWindowStore} from '@/stores/windowStore';
import {useI18n} from 'vue-i18n'; import {useI18n} from 'vue-i18n';
import {useConfirm} from '@/composables'; import {useConfirm} from '@/composables';
@@ -16,6 +17,7 @@ interface DocumentItem extends Document {
const documentStore = useDocumentStore(); const documentStore = useDocumentStore();
const tabStore = useTabStore(); const tabStore = useTabStore();
const editorStore = useEditorStore();
const windowStore = useWindowStore(); const windowStore = useWindowStore();
const {t} = useI18n(); const {t} = useI18n();
@@ -103,13 +105,20 @@ const selectDoc = async (doc: Document) => {
return; return;
} }
const success = await documentStore.openDocument(doc.id); const success = await documentStore.openDocument(doc.id);
if (success) { if (!success) return;
if (tabStore.isTabsEnabled) {
tabStore.addOrActivateTab(doc); const fullDoc = documentStore.currentDocument;
} if (fullDoc && editorStore.hasContainer) {
closeMenu(); await editorStore.loadEditor(fullDoc.id!, fullDoc.content || '');
} }
if (fullDoc && tabStore.isTabsEnabled) {
tabStore.addOrActivateTab(fullDoc);
}
closeMenu();
}; };
const createDoc = async (title: string) => { const createDoc = async (title: string) => {
@@ -190,6 +199,10 @@ const openInNewWindow = async (doc: Document, event: Event) => {
event.stopPropagation(); event.stopPropagation();
if (doc.id === undefined) return; if (doc.id === undefined) return;
try { try {
// 在打开新窗口前,如果启用了标签且该文档有标签,先关闭标签
if (tabStore.isTabsEnabled && tabStore.hasTab(doc.id)) {
await tabStore.closeTab(doc.id);
}
await documentStore.openDocumentInNewWindow(doc.id); await documentStore.openDocumentInNewWindow(doc.id);
} catch (error) { } catch (error) {
console.error('Failed to open document in new window:', error); console.error('Failed to open document in new window:', error);

View File

@@ -1,18 +1,10 @@
import {defineStore} from 'pinia'; import {defineStore} from 'pinia';
import {computed, reactive} from 'vue'; import {computed, reactive} from 'vue';
import {ConfigService, StartupService} from '@/../bindings/voidraft/internal/services'; import {ConfigService, StartupService} from '@/../bindings/voidraft/internal/services';
import { import {AppConfig, AuthMethod, LanguageType, SystemThemeType, TabType} from '@/../bindings/voidraft/internal/models/models';
AppConfig,
AuthMethod,
EditingConfig,
LanguageType,
SystemThemeType,
TabType
} from '@/../bindings/voidraft/internal/models/models';
import {useI18n} from 'vue-i18n'; import {useI18n} from 'vue-i18n';
import {ConfigUtils} from '@/common/utils/configUtils'; import {ConfigUtils} from '@/common/utils/configUtils';
import {FONT_OPTIONS} from '@/common/constant/fonts'; import {FONT_OPTIONS} from '@/common/constant/fonts';
import {SUPPORTED_LOCALES} from '@/common/constant/locales';
import { import {
CONFIG_KEY_MAP, CONFIG_KEY_MAP,
CONFIG_LIMITS, CONFIG_LIMITS,
@@ -36,12 +28,6 @@ export const useConfigStore = defineStore('config', () => {
// Font options (no longer localized) // Font options (no longer localized)
const fontOptions = computed(() => FONT_OPTIONS); 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<NumberConfigKey, ReturnType<typeof createLimitComputed>>;
// 统一配置更新方法 // 统一配置更新方法
const updateConfig = async <K extends ConfigKey>(key: K, value: any): Promise<void> => { const updateConfig = async <K extends ConfigKey>(key: K, value: any): Promise<void> => {
if (!state.configLoaded && !state.isLoading) { if (!state.configLoaded && !state.isLoading) {
@@ -99,39 +85,12 @@ export const useConfigStore = defineStore('config', () => {
} }
}; };
// 通用数值调整器工厂
const createAdjuster = <T extends NumberConfigKey>(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 = <T extends keyof EditingConfig>(key: T) =>
async () => await updateConfig(key as ConfigKey, !state.config.editing[key] as EditingConfig[T]);
// 枚举值切换器
const createEnumToggler = <T extends TabType>(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<void> => { const resetConfig = async (): Promise<void> => {
if (state.isLoading) return; if (state.isLoading) return;
state.isLoading = true; state.isLoading = true;
try { try {
await ConfigService.ResetConfig(); await ConfigService.ResetConfig();
const appConfig = await ConfigService.GetConfig(); const appConfig = await ConfigService.GetConfig();
if (appConfig) { if (appConfig) {
@@ -142,57 +101,25 @@ export const useConfigStore = defineStore('config', () => {
} }
}; };
// 语言设置方法 // 辅助函数:限制数值范围
const setLanguage = async (language: LanguageType): Promise<void> => { const clampValue = (value: number, key: NumberConfigKey): number => {
await updateConfig('language', language); const limit = CONFIG_LIMITS[key];
const frontendLocale = ConfigUtils.backendLanguageToFrontend(language); return ConfigUtils.clamp(value, limit.min, limit.max);
locale.value = frontendLocale as any;
}; };
// 系统主题设置方法 // 计算属性
const setSystemTheme = async (systemTheme: SystemThemeType): Promise<void> => { const fontConfig = computed(() => ({
await updateConfig('systemTheme', systemTheme); fontSize: state.config.editing.fontSize,
}; fontFamily: state.config.editing.fontFamily,
lineHeight: state.config.editing.lineHeight,
fontWeight: state.config.editing.fontWeight
}));
// 当前主题设置方法 const tabConfig = computed(() => ({
const setCurrentTheme = async (themeName: string): Promise<void> => { tabSize: state.config.editing.tabSize,
await updateConfig('currentTheme', themeName); enableTabIndent: state.config.editing.enableTabIndent,
}; tabType: state.config.editing.tabType
}));
// 初始化语言设置
const initLanguage = async (): Promise<void> => {
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)
};
return { return {
// 状态 // 状态
@@ -200,53 +127,84 @@ export const useConfigStore = defineStore('config', () => {
configLoaded: computed(() => state.configLoaded), configLoaded: computed(() => state.configLoaded),
isLoading: computed(() => state.isLoading), isLoading: computed(() => state.isLoading),
fontOptions, fontOptions,
fontConfig,
// 限制常量 tabConfig,
...limits,
// 核心方法 // 核心方法
initConfig, initConfig,
resetConfig, resetConfig,
// 语言相关方法 // 语言相关方法
setLanguage, setLanguage: (value: LanguageType) => {
initLanguage, updateConfig('language', value);
locale.value = value as any;
},
// 主题相关方法 // 主题相关方法
setSystemTheme, setSystemTheme: (value: SystemThemeType) => updateConfig('systemTheme', value),
setCurrentTheme, setCurrentTheme: (value: string) => updateConfig('currentTheme', value),
// 字体大小操作 // 字体大小操作
...adjusters.fontSize, setFontSize: async (value: number) => {
increaseFontSize: adjusters.fontSize.increase, await updateConfig('fontSize', clampValue(value, 'fontSize'));
decreaseFontSize: adjusters.fontSize.decrease, },
resetFontSize: adjusters.fontSize.reset, increaseFontSize: async () => {
setFontSize: adjusters.fontSize.set, const newValue = state.config.editing.fontSize + 1;
// 字体大小操作 await updateConfig('fontSize', clampValue(newValue, 'fontSize'));
increaseFontSizeLocal: adjusters.fontSize.increaseLocal, },
decreaseFontSizeLocal: adjusters.fontSize.decreaseLocal, decreaseFontSize: async () => {
saveFontSize: () => saveConfig('fontSize'), const newValue = state.config.editing.fontSize - 1;
await updateConfig('fontSize', clampValue(newValue, 'fontSize'));
// Tab操作 },
toggleTabIndent: togglers.tabIndent, resetFontSize: async () => {
setEnableTabIndent: (value: boolean) => updateConfig('enableTabIndent', value), await updateConfig('fontSize', CONFIG_LIMITS.fontSize.default);
...adjusters.tabSize, },
increaseTabSize: adjusters.tabSize.increase, increaseFontSizeLocal: () => {
decreaseTabSize: adjusters.tabSize.decrease, updateConfigLocal('fontSize', clampValue(state.config.editing.fontSize + 1, 'fontSize'));
setTabSize: adjusters.tabSize.set, },
toggleTabType: togglers.tabType, decreaseFontSizeLocal: () => {
updateConfigLocal('fontSize', clampValue(state.config.editing.fontSize - 1, 'fontSize'));
// 行高操作 },
setLineHeight: adjusters.lineHeight.set, saveFontSize: async () => {
await saveConfig('fontSize');
// 窗口操作 },
toggleAlwaysOnTop: togglers.alwaysOnTop,
setAlwaysOnTop: (value: boolean) => updateConfig('alwaysOnTop', value),
// 字体操作 // 字体操作
setFontFamily: (value: string) => updateConfig('fontFamily', value), setFontFamily: (value: string) => updateConfig('fontFamily', value),
setFontWeight: (value: string) => updateConfig('fontWeight', 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), setDataPath: (value: string) => updateConfigLocal('dataPath', value),

View File

@@ -3,7 +3,6 @@ import {computed, ref} from 'vue';
import {DocumentService} from '@/../bindings/voidraft/internal/services'; import {DocumentService} from '@/../bindings/voidraft/internal/services';
import {OpenDocumentWindow} from '@/../bindings/voidraft/internal/services/windowservice'; import {OpenDocumentWindow} from '@/../bindings/voidraft/internal/services/windowservice';
import {Document} from '@/../bindings/voidraft/internal/models/ent/models'; import {Document} from '@/../bindings/voidraft/internal/models/ent/models';
import {useTabStore} from "@/stores/tabStore";
import type {EditorViewState} from '@/stores/editorStore'; import type {EditorViewState} from '@/stores/editorStore';
export const useDocumentStore = defineStore('document', () => { export const useDocumentStore = defineStore('document', () => {
@@ -70,10 +69,6 @@ export const useDocumentStore = defineStore('document', () => {
// 在新窗口中打开文档 // 在新窗口中打开文档
const openDocumentInNewWindow = async (docId: number): Promise<boolean> => { const openDocumentInNewWindow = async (docId: number): Promise<boolean> => {
try { try {
const tabStore = useTabStore();
if (tabStore.isTabsEnabled && tabStore.hasTab(docId)) {
tabStore.closeTab(docId);
}
await OpenDocumentWindow(docId); await OpenDocumentWindow(docId);
return true; return true;
} catch (error) { } catch (error) {
@@ -112,7 +107,7 @@ export const useDocumentStore = defineStore('document', () => {
} }
}; };
// 打开文档 // 打开文档 - 只负责文档数据管理
const openDocument = async (docId: number): Promise<boolean> => { const openDocument = async (docId: number): Promise<boolean> => {
try { try {
// 获取完整文档数据 // 获取完整文档数据
@@ -131,7 +126,7 @@ export const useDocumentStore = defineStore('document', () => {
} }
}; };
// 更新文档元数据 // 更新文档元数据 - 只负责文档数据管理
const updateDocumentMetadata = async (docId: number, title: string): Promise<boolean> => { const updateDocumentMetadata = async (docId: number, title: string): Promise<boolean> => {
try { try {
await DocumentService.UpdateDocumentTitle(docId, title); await DocumentService.UpdateDocumentTitle(docId, title);
@@ -148,10 +143,6 @@ export const useDocumentStore = defineStore('document', () => {
currentDocument.value.updated_at = new Date().toISOString(); currentDocument.value.updated_at = new Date().toISOString();
} }
// 同步更新标签页标题
const tabStore = useTabStore();
tabStore.updateTabTitle(docId, title);
return true; return true;
} catch (error) { } catch (error) {
console.error('Failed to update document metadata:', error); console.error('Failed to update document metadata:', error);
@@ -159,7 +150,7 @@ export const useDocumentStore = defineStore('document', () => {
} }
}; };
// 删除文档 // 删除文档 - 只负责文档数据管理
const deleteDocument = async (docId: number): Promise<boolean> => { const deleteDocument = async (docId: number): Promise<boolean> => {
try { try {
await DocumentService.DeleteDocument(docId); await DocumentService.DeleteDocument(docId);
@@ -167,12 +158,6 @@ export const useDocumentStore = defineStore('document', () => {
// 更新本地状态 // 更新本地状态
delete documents.value[docId]; delete documents.value[docId];
// 同步清理标签页
const tabStore = useTabStore();
if (tabStore.hasTab(docId)) {
tabStore.closeTab(docId);
}
// 如果删除的是当前文档,切换到第一个可用文档 // 如果删除的是当前文档,切换到第一个可用文档
if (currentDocumentId.value === docId) { if (currentDocumentId.value === docId) {
const availableDocs = Object.values(documents.value); const availableDocs = Object.values(documents.value);
@@ -192,7 +177,7 @@ export const useDocumentStore = defineStore('document', () => {
}; };
// === 初始化 === // === 初始化 ===
const initialize = async (urlDocumentId?: number): Promise<void> => { const initDocument = async (urlDocumentId?: number): Promise<void> => {
try { try {
await getDocumentMetaList(); await getDocumentMetaList();
@@ -235,7 +220,7 @@ export const useDocumentStore = defineStore('document', () => {
closeDocumentSelector, closeDocumentSelector,
setError, setError,
clearError, clearError,
initialize, initDocument,
}; };
}, { }, {
persist: { persist: {

View File

@@ -1,5 +1,5 @@
import {defineStore} from 'pinia'; import {defineStore} from 'pinia';
import {computed, nextTick, ref, watch} from 'vue'; import {computed, nextTick, ref} from 'vue';
import {EditorView} from '@codemirror/view'; import {EditorView} from '@codemirror/view';
import {EditorState, Extension} from '@codemirror/state'; import {EditorState, Extension} from '@codemirror/state';
import {useConfigStore} from './configStore'; import {useConfigStore} from './configStore';
@@ -24,7 +24,6 @@ import {
import {useExtensionStore} from './extensionStore'; import {useExtensionStore} from './extensionStore';
import createCodeBlockExtension from "@/views/editor/extensions/codeblock"; import createCodeBlockExtension from "@/views/editor/extensions/codeblock";
import {LruCache} from '@/common/utils/lruCache'; import {LruCache} from '@/common/utils/lruCache';
import {AsyncManager} from '@/common/utils/asyncManager';
import {generateContentHash} from "@/common/utils/hashUtils"; import {generateContentHash} from "@/common/utils/hashUtils";
import {createTimerManager, type TimerManager} from '@/common/utils/timerUtils'; import {createTimerManager, type TimerManager} from '@/common/utils/timerUtils';
import {EDITOR_CONFIG} from '@/common/constant/editor'; import {EDITOR_CONFIG} from '@/common/constant/editor';
@@ -37,7 +36,6 @@ export interface DocumentStats {
selectedCharacters: number; selectedCharacters: number;
} }
// 修复:只保存光标位置,恢复时自动滚动到光标处
export interface EditorViewState { export interface EditorViewState {
cursorPos: number; cursorPos: number;
} }
@@ -54,7 +52,6 @@ interface EditorInstance {
lastContentHash: string; lastContentHash: string;
lastParsed: Date; lastParsed: Date;
} | null; } | null;
// 修复:使用统一的类型,可选但不是 undefined | {...}
editorState?: EditorViewState; editorState?: EditorViewState;
} }
@@ -74,14 +71,9 @@ export const useEditorStore = defineStore('editor', () => {
characters: 0, characters: 0,
selectedCharacters: 0 selectedCharacters: 0
}); });
// 编辑器加载状态 // 编辑器加载状态
const isLoading = ref(false); const isLoading = ref(false);
// 修复:使用操作计数器精确管理加载状态
const loadingOperations = ref(0);
// 异步操作管理器
const operationManager = new AsyncManager<number>();
// 自动保存设置 - 从配置动态获取 // 自动保存设置 - 从配置动态获取
const getAutoSaveDelay = () => configStore.config.editing.autoSaveDelay; const getAutoSaveDelay = () => configStore.config.editing.autoSaveDelay;
@@ -91,7 +83,7 @@ export const useEditorStore = defineStore('editor', () => {
if (instance) { if (instance) {
instance.syntaxTreeCache = null; instance.syntaxTreeCache = null;
} }
}, { delay: 500 }); // 500ms 内的多次输入只清理一次 }, {delay: 500}); // 500ms 内的多次输入只清理一次
// 缓存化的语法树确保方法 // 缓存化的语法树确保方法
@@ -106,42 +98,33 @@ export const useEditorStore = defineStore('editor', () => {
// 检查是否需要重新构建语法树 // 检查是否需要重新构建语法树
const cache = instance.syntaxTreeCache; const cache = instance.syntaxTreeCache;
const shouldRebuild = !cache || const shouldRebuild = !cache ||
cache.lastDocLength !== docLength || cache.lastDocLength !== docLength ||
cache.lastContentHash !== contentHash || cache.lastContentHash !== contentHash ||
(now.getTime() - cache.lastParsed.getTime()) > EDITOR_CONFIG.SYNTAX_TREE_CACHE_TIMEOUT; (now.getTime() - cache.lastParsed.getTime()) > EDITOR_CONFIG.SYNTAX_TREE_CACHE_TIMEOUT;
if (shouldRebuild) { if (shouldRebuild) {
try { ensureSyntaxTree(view.state, docLength, 5000);
ensureSyntaxTree(view.state, docLength, 5000);
// 更新缓存
// 更新缓存 instance.syntaxTreeCache = {
instance.syntaxTreeCache = { lastDocLength: docLength,
lastDocLength: docLength, lastContentHash: contentHash,
lastContentHash: contentHash, lastParsed: now
lastParsed: now };
};
} catch (error) {
console.warn('Failed to ensure syntax tree:', error);
}
} }
}; };
// 创建编辑器实例 // 创建编辑器实例
const createEditorInstance = async ( const createEditorInstance = async (
content: string, content: string,
operationId: number,
documentId: number documentId: number
): Promise<EditorView> => { ): Promise<EditorView> => {
if (!containerElement.value) { if (!containerElement.value) {
throw new Error('Editor container not set'); throw new Error('Editor container not set');
} }
// 检查操作是否仍然有效
if (!operationManager.isOperationValid(operationId, documentId)) {
throw new Error('Operation cancelled');
}
// 获取基本扩展 // 获取基本扩展
const basicExtensions = createBasicSetup(); const basicExtensions = createBasicSetup();
@@ -185,27 +168,12 @@ export const useEditorStore = defineStore('editor', () => {
// 光标位置持久化扩展 // 光标位置持久化扩展
const cursorPositionExtension = createCursorPositionExtension(documentId); const cursorPositionExtension = createCursorPositionExtension(documentId);
// 再次检查操作有效性
if (!operationManager.isOperationValid(operationId, documentId)) {
throw new Error('Operation cancelled');
}
// 快捷键扩展 // 快捷键扩展
const keymapExtension = await createDynamicKeymapExtension(); const keymapExtension = await createDynamicKeymapExtension();
// 检查操作有效性
if (!operationManager.isOperationValid(operationId, documentId)) {
throw new Error('Operation cancelled');
}
// 动态扩展传递文档ID以便扩展管理器可以预初始化 // 动态扩展传递文档ID以便扩展管理器可以预初始化
const dynamicExtensions = await createDynamicExtensions(); const dynamicExtensions = await createDynamicExtensions();
// 最终检查操作有效性
if (!operationManager.isOperationValid(operationId, documentId)) {
throw new Error('Operation cancelled');
}
// 组合所有扩展 // 组合所有扩展
const extensions: Extension[] = [ const extensions: Extension[] = [
keymapExtension, keymapExtension,
@@ -224,8 +192,8 @@ export const useEditorStore = defineStore('editor', () => {
// 获取保存的光标位置 // 获取保存的光标位置
const savedState = documentStore.documentStates[documentId]; const savedState = documentStore.documentStates[documentId];
const docLength = content.length; const docLength = content.length;
const initialCursorPos = savedState?.cursorPos !== undefined const initialCursorPos = savedState?.cursorPos !== undefined
? Math.min(savedState.cursorPos, docLength) ? Math.min(savedState.cursorPos, docLength)
: docLength; : docLength;
@@ -233,7 +201,7 @@ export const useEditorStore = defineStore('editor', () => {
const state = EditorState.create({ const state = EditorState.create({
doc: content, doc: content,
extensions, extensions,
selection: { anchor: initialCursorPos, head: initialCursorPos } selection: {anchor: initialCursorPos, head: initialCursorPos}
}); });
return new EditorView({ return new EditorView({
@@ -271,9 +239,8 @@ export const useEditorStore = defineStore('editor', () => {
// 获取或创建编辑器 // 获取或创建编辑器
const getOrCreateEditor = async ( const getOrCreateEditor = async (
documentId: number, documentId: number,
content: string, content: string
operationId: number
): Promise<EditorView> => { ): Promise<EditorView> => {
// 检查缓存 // 检查缓存
const cached = editorCache.get(documentId); const cached = editorCache.get(documentId);
@@ -281,29 +248,8 @@ export const useEditorStore = defineStore('editor', () => {
return cached.view; return cached.view;
} }
// 检查操作是否仍然有效
if (!operationManager.isOperationValid(operationId, documentId)) {
throw new Error('Operation cancelled');
}
// 创建新的编辑器实例 // 创建新的编辑器实例
const view = await createEditorInstance(content, operationId, documentId); const view = await createEditorInstance(content, 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');
}
addEditorToCache(documentId, view, content); addEditorToCache(documentId, view, content);
@@ -333,10 +279,10 @@ export const useEditorStore = defineStore('editor', () => {
requestAnimationFrame(() => { requestAnimationFrame(() => {
// 滚动到当前光标位置 // 滚动到当前光标位置
scrollToCursor(instance.view); scrollToCursor(instance.view);
// 聚焦编辑器 // 聚焦编辑器
instance.view.focus(); instance.view.focus();
// 使用缓存的语法树确保方法 // 使用缓存的语法树确保方法
ensureSyntaxTreeCached(instance.view, documentId); ensureSyntaxTreeCached(instance.view, documentId);
}); });
@@ -354,7 +300,7 @@ export const useEditorStore = defineStore('editor', () => {
try { try {
const content = instance.view.state.doc.toString(); const content = instance.view.state.doc.toString();
const lastModified = instance.lastModified; const lastModified = instance.lastModified;
await DocumentService.UpdateDocumentContent(documentId, content); await DocumentService.UpdateDocumentContent(documentId, content);
// 检查在保存期间内容是否又被修改了 // 检查在保存期间内容是否又被修改了
@@ -381,7 +327,7 @@ export const useEditorStore = defineStore('editor', () => {
// 立即设置脏标记和修改时间(切换文档时需要判断) // 立即设置脏标记和修改时间(切换文档时需要判断)
instance.isDirty = true; instance.isDirty = true;
instance.lastModified = new Date(); instance.lastModified = new Date();
// 优使用防抖清理语法树缓存 // 优使用防抖清理语法树缓存
debouncedClearSyntaxCache.debouncedFn(instance); debouncedClearSyntaxCache.debouncedFn(instance);
@@ -392,24 +338,18 @@ export const useEditorStore = defineStore('editor', () => {
}; };
// 检查容器是否已设置
const hasContainer = computed(() => containerElement.value !== null);
// 设置编辑器容器 // 设置编辑器容器
const setEditorContainer = (container: HTMLElement | null) => { const setEditorContainer = (container: HTMLElement | null) => {
containerElement.value = container; containerElement.value = container;
// watch 会自动监听并加载编辑器,无需手动调用
// 如果设置容器时已有当前文档,立即加载编辑器
if (container && documentStore.currentDocument && documentStore.currentDocument.id !== undefined) {
loadEditor(documentStore.currentDocument.id, documentStore.currentDocument.content || '');
}
}; };
// 加载编辑器 // 加载编辑器
const loadEditor = async (documentId: number, content: string) => { const loadEditor = async (documentId: number, content: string) => {
// 修复:使用计数器精确管理加载状态
loadingOperations.value++;
isLoading.value = true; isLoading.value = true;
// 开始新的操作
const { operationId } = operationManager.startOperation(documentId);
try { try {
// 验证参数 // 验证参数
@@ -422,33 +362,25 @@ export const useEditorStore = defineStore('editor', () => {
const currentDocId = documentStore.currentDocumentId; const currentDocId = documentStore.currentDocumentId;
if (currentDocId && currentDocId !== documentId) { if (currentDocId && currentDocId !== documentId) {
await saveEditorContent(currentDocId); 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); const instance = editorCache.get(documentId);
if (instance && instance.content !== content) { if (instance && instance.content !== content) {
// 确保编辑器视图有效 // 确保编辑器视图有效
if (view && view.state && view.dispatch) { if (view && view.state && view.dispatch) {
const contentLength = content.length;
view.dispatch({ view.dispatch({
changes: { changes: {
from: 0, from: 0,
to: view.state.doc.length, to: view.state.doc.length,
insert: content insert: content
} },
selection: {anchor: contentLength, head: contentLength}
}); });
instance.content = content; instance.content = content;
instance.isDirty = false; instance.isDirty = false;
@@ -460,32 +392,14 @@ export const useEditorStore = defineStore('editor', () => {
} }
} }
// 最终检查操作有效性
if (!operationManager.isOperationValid(operationId, documentId)) {
return;
}
// 显示编辑器 // 显示编辑器
showEditor(documentId); showEditor(documentId);
} catch (error) { } catch (error) {
if (error instanceof Error && error.message === 'Operation cancelled') { console.error('Failed to load editor:', error);
console.log(`Editor loading cancelled for document ${documentId}`);
} else {
console.error('Failed to load editor:', error);
}
} finally { } finally {
// 完成操作
operationManager.completeOperation(operationId);
// 修复:使用计数器精确管理加载状态,避免快速切换时状态不准确
loadingOperations.value--;
// 延迟一段时间后再取消加载状态,但要确保所有操作都完成了
setTimeout(() => { setTimeout(() => {
if (loadingOperations.value <= 0) { isLoading.value = false;
loadingOperations.value = 0;
isLoading.value = false;
}
}, EDITOR_CONFIG.LOADING_DELAY); }, EDITOR_CONFIG.LOADING_DELAY);
} }
}; };
@@ -495,12 +409,6 @@ export const useEditorStore = defineStore('editor', () => {
const instance = editorCache.get(documentId); const instance = editorCache.get(documentId);
if (instance) { if (instance) {
try { try {
// 如果正在加载这个文档,取消操作
if (operationManager.getCurrentContext() === documentId) {
operationManager.cancelAllOperations();
}
// 修复:移除前先保存内容(如果有未保存的修改)
if (instance.isDirty) { if (instance.isDirty) {
await saveEditorContent(documentId); await saveEditorContent(documentId);
} }
@@ -583,9 +491,6 @@ export const useEditorStore = defineStore('editor', () => {
// 清空所有编辑器 // 清空所有编辑器
const clearAllEditors = () => { const clearAllEditors = () => {
// 取消所有挂起的操作
operationManager.cancelAllOperations();
editorCache.clear((_documentId, instance) => { editorCache.clear((_documentId, instance) => {
// 清除自动保存定时器 // 清除自动保存定时器
instance.autoSaveTimer.clear(); instance.autoSaveTimer.clear();
@@ -598,7 +503,7 @@ export const useEditorStore = defineStore('editor', () => {
// 销毁编辑器 // 销毁编辑器
instance.view.destroy(); instance.view.destroy();
}); });
currentEditor.value = null; currentEditor.value = null;
}; };
@@ -606,7 +511,7 @@ export const useEditorStore = defineStore('editor', () => {
const updateExtension = async (id: number, enabled: boolean, config?: any) => { const updateExtension = async (id: number, enabled: boolean, config?: any) => {
// 更新启用状态 // 更新启用状态
await ExtensionService.UpdateExtensionEnabled(id, enabled); await ExtensionService.UpdateExtensionEnabled(id, enabled);
// 如果需要更新配置 // 如果需要更新配置
if (config !== undefined) { if (config !== undefined) {
await ExtensionService.UpdateExtensionConfig(id, config); await ExtensionService.UpdateExtensionConfig(id, config);
@@ -614,7 +519,7 @@ export const useEditorStore = defineStore('editor', () => {
// 重新加载扩展配置 // 重新加载扩展配置
await extensionStore.loadExtensions(); await extensionStore.loadExtensions();
// 获取更新后的扩展名称 // 获取更新后的扩展名称
const extension = extensionStore.extensions.find(ext => ext.id === id); const extension = extensionStore.extensions.find(ext => ext.id === id);
if (!extension) return; if (!extension) return;
@@ -630,38 +535,12 @@ export const useEditorStore = defineStore('editor', () => {
await applyKeymapSettings(); 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 { return {
// 状态 // 状态
currentEditor, currentEditor,
documentStats, documentStats,
isLoading, isLoading,
hasContainer,
// 方法 // 方法
setEditorContainer, setEditorContainer,
@@ -670,7 +549,6 @@ export const useEditorStore = defineStore('editor', () => {
clearAllEditors, clearAllEditors,
onContentChange, onContentChange,
// 配置更新方法
applyFontSettings, applyFontSettings,
applyThemeSettings, applyThemeSettings,
applyTabSettings, applyTabSettings,

View File

@@ -18,12 +18,9 @@ export const useTabStore = defineStore('tab', () => {
const tabsMap = ref<Record<number, Tab>>({}); const tabsMap = ref<Record<number, Tab>>({});
const tabOrder = ref<number[]>([]); // 维护标签页顺序 const tabOrder = ref<number[]>([]); // 维护标签页顺序
const draggedTabId = ref<number | null>(null); const draggedTabId = ref<number | null>(null);
// === 计算属性 ===
const isTabsEnabled = computed(() => configStore.config.general.enableTabs); const isTabsEnabled = computed(() => configStore.config.general.enableTabs);
const canCloseTab = computed(() => tabOrder.value.length > 1); const canCloseTab = computed(() => tabOrder.value.length > 1);
const currentDocumentId = computed(() => documentStore.currentDocumentId);
// 按顺序返回标签页数组用于UI渲染 // 按顺序返回标签页数组用于UI渲染
const tabs = computed(() => { 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; if (!hasTab(documentId)) return;
const tabIndex = tabOrder.value.indexOf(documentId); const tabIndex = tabOrder.value.indexOf(documentId);
@@ -95,7 +92,7 @@ export const useTabStore = defineStore('tab', () => {
if (nextIndex >= 0 && tabOrder.value[nextIndex]) { if (nextIndex >= 0 && tabOrder.value[nextIndex]) {
const nextDocumentId = 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 (!hasTab(documentId)) return;
// 如果点击的是当前已激活的文档,不需要重复请求 // 如果点击的是当前已激活的文档,不需要重复请求
if (documentStore.currentDocumentId === documentId) { if (documentStore.currentDocumentId === documentId) {
return; return;
} }
documentStore.openDocument(documentId); await documentStore.openDocument(documentId);
}; };
/** /**
@@ -172,8 +169,8 @@ export const useTabStore = defineStore('tab', () => {
/** /**
* 初始化标签页(当前文档) * 初始化标签页(当前文档)
*/ */
const initializeTab = () => { const initTab = () => {
// 先验证并清理无效的标签页(处理持久化的脏数据) // 先验证并清理无效的标签页
validateTabs(); validateTabs();
if (isTabsEnabled.value) { 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; if (!hasTab(keepDocumentId)) return;
// 获取所有其他标签页的ID // 获取所有其他标签页的ID
@@ -200,14 +197,14 @@ export const useTabStore = defineStore('tab', () => {
// 如果当前打开的文档在被关闭的标签中,需要切换到保留的文档 // 如果当前打开的文档在被关闭的标签中,需要切换到保留的文档
if (otherTabIds.includes(documentStore.currentDocumentId!)) { if (otherTabIds.includes(documentStore.currentDocumentId!)) {
switchToTabAndDocument(keepDocumentId); await switchToTabAndDocument(keepDocumentId);
} }
}; };
/** /**
* 关闭指定标签页右侧的所有标签页 * 关闭指定标签页右侧的所有标签页
*/ */
const closeTabsToRight = (documentId: number) => { const closeTabsToRight = async (documentId: number) => {
const index = getTabIndex(documentId); const index = getTabIndex(documentId);
if (index === -1) return; if (index === -1) return;
@@ -219,14 +216,14 @@ export const useTabStore = defineStore('tab', () => {
// 如果当前打开的文档在被关闭的右侧标签中,需要切换到指定的文档 // 如果当前打开的文档在被关闭的右侧标签中,需要切换到指定的文档
if (rightTabIds.includes(documentStore.currentDocumentId!)) { if (rightTabIds.includes(documentStore.currentDocumentId!)) {
switchToTabAndDocument(documentId); await switchToTabAndDocument(documentId);
} }
}; };
/** /**
* 关闭指定标签页左侧的所有标签页 * 关闭指定标签页左侧的所有标签页
*/ */
const closeTabsToLeft = (documentId: number) => { const closeTabsToLeft = async (documentId: number) => {
const index = getTabIndex(documentId); const index = getTabIndex(documentId);
if (index <= 0) return; if (index <= 0) return;
@@ -238,7 +235,7 @@ export const useTabStore = defineStore('tab', () => {
// 如果当前打开的文档在被关闭的左侧标签中,需要切换到指定的文档 // 如果当前打开的文档在被关闭的左侧标签中,需要切换到指定的文档
if (leftTabIds.includes(documentStore.currentDocumentId!)) { if (leftTabIds.includes(documentStore.currentDocumentId!)) {
switchToTabAndDocument(documentId); await switchToTabAndDocument(documentId);
} }
}; };
@@ -262,7 +259,6 @@ export const useTabStore = defineStore('tab', () => {
// 计算属性 // 计算属性
isTabsEnabled, isTabsEnabled,
canCloseTab, canCloseTab,
currentDocumentId,
// 方法 // 方法
addOrActivateTab, addOrActivateTab,
@@ -273,7 +269,7 @@ export const useTabStore = defineStore('tab', () => {
switchToTabAndDocument, switchToTabAndDocument,
moveTab, moveTab,
getTabIndex, getTabIndex,
initializeTab, initTab,
clearAllTabs, clearAllTabs,
updateTabTitle, updateTabTitle,
validateTabs, validateTabs,

View File

@@ -6,6 +6,7 @@ import {ThemeService} from '@/../bindings/voidraft/internal/services';
import {useConfigStore} from './configStore'; import {useConfigStore} from './configStore';
import type {ThemeColors} from '@/views/editor/theme/types'; import type {ThemeColors} from '@/views/editor/theme/types';
import {cloneThemeColors, FALLBACK_THEME_NAME, themePresetList, themePresetMap} from '@/views/editor/theme/presets'; import {cloneThemeColors, FALLBACK_THEME_NAME, themePresetList, themePresetMap} from '@/views/editor/theme/presets';
import {useEditorStore} from "@/stores/editorStore";
// 类型定义 // 类型定义
type ThemeOption = { name: string; type: ThemeType }; type ThemeOption = { name: string; type: ThemeType };
@@ -91,11 +92,12 @@ export const useThemeStore = defineStore('theme', () => {
// 同步应用到 DOM 与编辑器 // 同步应用到 DOM 与编辑器
const applyAllThemes = () => { const applyAllThemes = () => {
applyThemeToDOM(currentTheme.value); applyThemeToDOM(currentTheme.value);
const editorStore = useEditorStore();
editorStore.applyThemeSettings();
}; };
// 初始化主题 // 初始化主题
const initTheme = async () => { const initTheme = async () => {
applyThemeToDOM(currentTheme.value);
await loadThemeColors(); await loadThemeColors();
applyAllThemes(); applyAllThemes();
}; };

View File

@@ -28,11 +28,16 @@ onMounted(async () => {
const urlDocumentId = windowStore.currentDocumentId ? parseInt(windowStore.currentDocumentId) : undefined; const urlDocumentId = windowStore.currentDocumentId ? parseInt(windowStore.currentDocumentId) : undefined;
await documentStore.initialize(urlDocumentId); await documentStore.initDocument(urlDocumentId);
editorStore.setEditorContainer(editorElement.value); 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(() => { onBeforeUnmount(() => {

View File

@@ -1,5 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { useConfigStore } from '@/stores/configStore'; import { useConfigStore } from '@/stores/configStore';
import { useEditorStore } from '@/stores/editorStore';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import {computed, onMounted } from 'vue'; import {computed, onMounted } from 'vue';
import SettingSection from '../components/SettingSection.vue'; import SettingSection from '../components/SettingSection.vue';
@@ -9,6 +10,7 @@ import { TabType } from '@/../bindings/voidraft/internal/models/';
const { t } = useI18n(); const { t } = useI18n();
const configStore = useConfigStore(); const configStore = useConfigStore();
const editorStore = useEditorStore();
// 确保配置已加载 // 确保配置已加载
onMounted(async () => { onMounted(async () => {
@@ -27,6 +29,7 @@ const fontFamilyModel = computed({
set: async (fontFamily: string) => { set: async (fontFamily: string) => {
if (fontFamily) { if (fontFamily) {
await configStore.setFontFamily(fontFamily); await configStore.setFontFamily(fontFamily);
editorStore.applyFontSettings();
} }
} }
}); });
@@ -50,6 +53,7 @@ const fontWeightModel = computed({
set: async (value: string) => { set: async (value: string) => {
if (value) { if (value) {
await configStore.setFontWeight(value); await configStore.setFontWeight(value);
editorStore.applyFontSettings();
} }
} }
}); });
@@ -58,20 +62,24 @@ const fontWeightModel = computed({
const increaseLineHeight = async () => { const increaseLineHeight = async () => {
const newLineHeight = Math.min(3.0, configStore.config.editing.lineHeight + 0.1); const newLineHeight = Math.min(3.0, configStore.config.editing.lineHeight + 0.1);
await configStore.setLineHeight(Math.round(newLineHeight * 10) / 10); await configStore.setLineHeight(Math.round(newLineHeight * 10) / 10);
editorStore.applyFontSettings();
}; };
const decreaseLineHeight = async () => { const decreaseLineHeight = async () => {
const newLineHeight = Math.max(1.0, configStore.config.editing.lineHeight - 0.1); const newLineHeight = Math.max(1.0, configStore.config.editing.lineHeight - 0.1);
await configStore.setLineHeight(Math.round(newLineHeight * 10) / 10); await configStore.setLineHeight(Math.round(newLineHeight * 10) / 10);
editorStore.applyFontSettings();
}; };
// 字体大小控制 // 字体大小控制
const increaseFontSize = async () => { const increaseFontSize = async () => {
await configStore.increaseFontSize(); await configStore.increaseFontSize();
editorStore.applyFontSettings();
}; };
const decreaseFontSize = async () => { const decreaseFontSize = async () => {
await configStore.decreaseFontSize(); await configStore.decreaseFontSize();
editorStore.applyFontSettings();
}; };
// Tab类型切换 // Tab类型切换
@@ -84,15 +92,18 @@ const tabTypeText = computed(() => {
// Tab大小增减 // Tab大小增减
const increaseTabSize = async () => { const increaseTabSize = async () => {
await configStore.increaseTabSize(); await configStore.increaseTabSize();
editorStore.applyTabSettings();
}; };
const decreaseTabSize = async () => { const decreaseTabSize = async () => {
await configStore.decreaseTabSize(); await configStore.decreaseTabSize();
editorStore.applyTabSettings();
}; };
// Tab相关操作 // Tab相关操作
const handleToggleTabType = async () => { const handleToggleTabType = async () => {
await configStore.toggleTabType(); await configStore.toggleTabType();
editorStore.applyTabSettings();
}; };
// 创建双向绑定的计算属性 // 创建双向绑定的计算属性
@@ -100,6 +111,7 @@ const enableTabIndent = computed({
get: () => configStore.config.editing.enableTabIndent, get: () => configStore.config.editing.enableTabIndent,
set: async (value: boolean) => { set: async (value: boolean) => {
await configStore.setEnableTabIndent(value); await configStore.setEnableTabIndent(value);
editorStore.applyTabSettings();
} }
}); });
@@ -187,13 +199,13 @@ const handleAutoSaveDelayChange = async (event: Event) => {
<button <button
@click="decreaseTabSize" @click="decreaseTabSize"
class="control-button" class="control-button"
:disabled="!enableTabIndent || configStore.config.editing.tabSize <= configStore.tabSize.min" :disabled="!enableTabIndent || configStore.config.editing.tabSize <= 2"
>-</button> >-</button>
<span>{{ configStore.config.editing.tabSize }}</span> <span>{{ configStore.config.editing.tabSize }}</span>
<button <button
@click="increaseTabSize" @click="increaseTabSize"
class="control-button" class="control-button"
:disabled="!enableTabIndent || configStore.config.editing.tabSize >= configStore.tabSize.max" :disabled="!enableTabIndent || configStore.config.editing.tabSize >= 8"
>+</button> >+</button>
</div> </div>
</SettingItem> </SettingItem>

View File

@@ -125,7 +125,7 @@ const enableTabs = computed({
await setEnableTabs(value); await setEnableTabs(value);
if (value) { if (value) {
// 开启tabs功能时初始化当前文档到标签页 // 开启tabs功能时初始化当前文档到标签页
tabStore.initializeTab(); tabStore.initTab();
} else { } else {
// 关闭tabs功能时清空所有标签页 // 关闭tabs功能时清空所有标签页
tabStore.clearAllTabs(); tabStore.clearAllTabs();