diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 26cf7c2..a7de5e1 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -21,7 +21,7 @@ "@codemirror/lang-less": "^6.0.2", "@codemirror/lang-lezer": "^6.0.1", "@codemirror/lang-liquid": "^6.2.3", - "@codemirror/lang-markdown": "^6.3.2", + "@codemirror/lang-markdown": "^6.3.3", "@codemirror/lang-php": "^6.0.1", "@codemirror/lang-python": "^6.2.1", "@codemirror/lang-rust": "^6.0.1", @@ -37,7 +37,7 @@ "@codemirror/lint": "^6.8.5", "@codemirror/search": "^6.5.11", "@codemirror/state": "^6.5.2", - "@codemirror/view": "^6.37.1", + "@codemirror/view": "^6.37.2", "@lezer/highlight": "^1.2.1", "@lezer/lr": "^1.4.2", "@types/uuid": "^10.0.0", @@ -48,24 +48,24 @@ "colors-named-hex": "^1.0.2", "hsl-matcher": "^1.2.4", "lezer": "^0.13.5", - "pinia": "^3.0.2", - "sass": "^1.89.1", + "pinia": "^3.0.3", + "sass": "^1.89.2", "uuid": "^11.1.0", - "vue": "^3.5.16", - "vue-i18n": "^11.1.5", + "vue": "^3.5.17", + "vue-i18n": "^11.1.6", "vue-router": "^4.5.1" }, "devDependencies": { - "@eslint/js": "^9.28.0", + "@eslint/js": "^9.29.0", "@lezer/generator": "^1.7.3", - "@types/node": "^22.15.29", + "@types/node": "^24.0.3", "@vitejs/plugin-vue": "^5.2.4", "@wailsio/runtime": "latest", - "eslint": "^9.28.0", - "eslint-plugin-vue": "^10.1.0", + "eslint": "^9.29.0", + "eslint-plugin-vue": "^10.2.0", "globals": "^16.2.0", "typescript": "^5.8.3", - "typescript-eslint": "^8.33.1", + "typescript-eslint": "^8.34.1", "unplugin-vue-components": "^28.7.0", "vite": "^6.3.5", "vue-eslint-parser": "^10.1.3", @@ -91,12 +91,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.27.2", - "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.27.2.tgz", - "integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==", + "version": "7.27.5", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.27.5.tgz", + "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==", "license": "MIT", "dependencies": { - "@babel/types": "^7.27.1" + "@babel/types": "^7.27.3" }, "bin": { "parser": "bin/babel-parser.js" @@ -106,9 +106,9 @@ } }, "node_modules/@babel/types": { - "version": "7.27.1", - "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.27.1.tgz", - "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==", + "version": "7.27.6", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.27.6.tgz", + "integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -286,9 +286,9 @@ } }, "node_modules/@codemirror/lang-markdown": { - "version": "6.3.2", - "resolved": "https://registry.npmmirror.com/@codemirror/lang-markdown/-/lang-markdown-6.3.2.tgz", - "integrity": "sha512-c/5MYinGbFxYl4itE9q/rgN/sMTjOr8XL5OWnC+EaRMLfCbVUmmubTJfdgpfcSS2SCaT7b+Q+xi3l6CgoE+BsA==", + "version": "6.3.3", + "resolved": "https://registry.npmmirror.com/@codemirror/lang-markdown/-/lang-markdown-6.3.3.tgz", + "integrity": "sha512-1fn1hQAPWlSSMCvnF810AkhWpNLkJpl66CRfIy3vVl20Sl4NwChkorCHqpMtNbXr1EuMJsrDnhEpjZxKZ2UX3A==", "license": "MIT", "dependencies": { "@codemirror/autocomplete": "^6.7.1", @@ -503,9 +503,9 @@ } }, "node_modules/@codemirror/view": { - "version": "6.37.1", - "resolved": "https://registry.npmmirror.com/@codemirror/view/-/view-6.37.1.tgz", - "integrity": "sha512-Qy4CAUwngy/VQkEz0XzMKVRcckQuqLYWKqVpDDDghBe5FSXSqfVrJn49nw3ePZHxRUz4nRmb05Lgi+9csWo4eg==", + "version": "6.37.2", + "resolved": "https://registry.npmmirror.com/@codemirror/view/-/view-6.37.2.tgz", + "integrity": "sha512-XD3LdgQpxQs5jhOOZ2HRVT+Rj59O4Suc7g2ULvZ+Yi8eCkickrkZ5JFuoDhs2ST1mNI5zSsNYgR3NGa4OUrbnw==", "license": "MIT", "dependencies": { "@codemirror/state": "^6.5.0", @@ -982,9 +982,9 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.20.0", - "resolved": "https://registry.npmmirror.com/@eslint/config-array/-/config-array-0.20.0.tgz", - "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", + "version": "0.20.1", + "resolved": "https://registry.npmmirror.com/@eslint/config-array/-/config-array-0.20.1.tgz", + "integrity": "sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1057,9 +1057,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.28.0", - "resolved": "https://registry.npmmirror.com/@eslint/js/-/js-9.28.0.tgz", - "integrity": "sha512-fnqSjGWd/CoIp4EXIxWVK/sHA6DOHN4+8Ix2cX5ycOY7LG0UY8nHCU5pIp2eaE1Mc7Qd8kHspYNzYXT2ojPLzg==", + "version": "9.29.0", + "resolved": "https://registry.npmmirror.com/@eslint/js/-/js-9.29.0.tgz", + "integrity": "sha512-3PIF4cBw/y+1u2EazflInpV+lYsSG0aByVIQzAgb1m1MhHFSbqTyNqtBKHgWf/9Ykud+DhILS9EGkmekVhbKoQ==", "dev": true, "license": "MIT", "engines": { @@ -1160,13 +1160,13 @@ } }, "node_modules/@intlify/core-base": { - "version": "11.1.5", - "resolved": "https://registry.npmmirror.com/@intlify/core-base/-/core-base-11.1.5.tgz", - "integrity": "sha512-xGRkISwV/2Trqb8yVQevlHm5roaQqy+75qwUzEQrviaQF0o4c5VDhjBW7WEGEoKFx09HSgq7NkvK/DAyuerTDg==", + "version": "11.1.6", + "resolved": "https://registry.npmmirror.com/@intlify/core-base/-/core-base-11.1.6.tgz", + "integrity": "sha512-gfMLnoWGiQkA1BwK6Qbrog/e3I6Lnkhqk08XObJb0lMq6sLG1Ggl2MazVaMfGnv/E1Td8pCS5UwR54Ys+fOxmQ==", "license": "MIT", "dependencies": { - "@intlify/message-compiler": "11.1.5", - "@intlify/shared": "11.1.5" + "@intlify/message-compiler": "11.1.6", + "@intlify/shared": "11.1.6" }, "engines": { "node": ">= 16" @@ -1176,12 +1176,12 @@ } }, "node_modules/@intlify/message-compiler": { - "version": "11.1.5", - "resolved": "https://registry.npmmirror.com/@intlify/message-compiler/-/message-compiler-11.1.5.tgz", - "integrity": "sha512-YLSBbjD7qUdShe3ZAat9Hnf9E8FRpN6qmNFD/x5Xg5JVXjsks0kJ90Zj6aAuyoppJQA/YJdWZ8/bB7k3dg2TjQ==", + "version": "11.1.6", + "resolved": "https://registry.npmmirror.com/@intlify/message-compiler/-/message-compiler-11.1.6.tgz", + "integrity": "sha512-w0LYo5sqgQZF3vEmjLlx+5PYk5EEiB+uigsBkka/DKoAIH2c5xlXcjAxhTgSw35Vrck+GOGriahFsfbHL+ZjPw==", "license": "MIT", "dependencies": { - "@intlify/shared": "11.1.5", + "@intlify/shared": "11.1.6", "source-map-js": "^1.0.2" }, "engines": { @@ -1192,9 +1192,9 @@ } }, "node_modules/@intlify/shared": { - "version": "11.1.5", - "resolved": "https://registry.npmmirror.com/@intlify/shared/-/shared-11.1.5.tgz", - "integrity": "sha512-+I4vRzHm38VjLr/CAciEPJhGYFzWWW4HMTm+6H3WqknXLh0ozNX9oC8ogMUwTSXYR/wGUb1/lTpNziiCH5MybQ==", + "version": "11.1.6", + "resolved": "https://registry.npmmirror.com/@intlify/shared/-/shared-11.1.6.tgz", + "integrity": "sha512-G1Pe4UILhiGOItuehRW+Pk9/NlnRaMFsdnhZ1fwBjiHvrzitmPNZdLx7Eo3GPfRrsk1mdkilZSfgH8SnM419vA==", "license": "MIT", "engines": { "node": ">= 16" @@ -2093,13 +2093,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.15.29", - "resolved": "https://registry.npmmirror.com/@types/node/-/node-22.15.29.tgz", - "integrity": "sha512-LNdjOkUDlU1RZb8e1kOIUpN1qQUlzGkEtbVNo53vbrwDg5om6oduhm4SiUaPW5ASTXhAiP0jInWG8Qx9fVlOeQ==", + "version": "24.0.3", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-24.0.3.tgz", + "integrity": "sha512-R4I/kzCYAdRLzfiCabn9hxWfbuHS573x+r0dJMkkzThEa7pbrcDWK+9zu3e7aBOouf+rQAciqPFMnxwr0aWgKg==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~6.21.0" + "undici-types": "~7.8.0" } }, "node_modules/@types/uuid": { @@ -2115,17 +2115,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.33.1", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.33.1.tgz", - "integrity": "sha512-TDCXj+YxLgtvxvFlAvpoRv9MAncDLBV2oT9Bd7YBGC/b/sEURoOYuIwLI99rjWOfY3QtDzO+mk0n4AmdFExW8A==", + "version": "8.34.1", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.1.tgz", + "integrity": "sha512-STXcN6ebF6li4PxwNeFnqF8/2BNDvBupf2OPx2yWNzr6mKNGF7q49VM00Pz5FaomJyqvbXpY6PhO+T9w139YEQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.33.1", - "@typescript-eslint/type-utils": "8.33.1", - "@typescript-eslint/utils": "8.33.1", - "@typescript-eslint/visitor-keys": "8.33.1", + "@typescript-eslint/scope-manager": "8.34.1", + "@typescript-eslint/type-utils": "8.34.1", + "@typescript-eslint/utils": "8.34.1", + "@typescript-eslint/visitor-keys": "8.34.1", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", @@ -2139,7 +2139,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.33.1", + "@typescript-eslint/parser": "^8.34.1", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } @@ -2155,16 +2155,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.33.1", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-8.33.1.tgz", - "integrity": "sha512-qwxv6dq682yVvgKKp2qWwLgRbscDAYktPptK4JPojCwwi3R9cwrvIxS4lvBpzmcqzR4bdn54Z0IG1uHFskW4dA==", + "version": "8.34.1", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-8.34.1.tgz", + "integrity": "sha512-4O3idHxhyzjClSMJ0a29AcoK0+YwnEqzI6oz3vlRf3xw0zbzt15MzXwItOlnr5nIth6zlY2RENLsOPvhyrKAQA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.33.1", - "@typescript-eslint/types": "8.33.1", - "@typescript-eslint/typescript-estree": "8.33.1", - "@typescript-eslint/visitor-keys": "8.33.1", + "@typescript-eslint/scope-manager": "8.34.1", + "@typescript-eslint/types": "8.34.1", + "@typescript-eslint/typescript-estree": "8.34.1", + "@typescript-eslint/visitor-keys": "8.34.1", "debug": "^4.3.4" }, "engines": { @@ -2180,14 +2180,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.33.1", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/project-service/-/project-service-8.33.1.tgz", - "integrity": "sha512-DZR0efeNklDIHHGRpMpR5gJITQpu6tLr9lDJnKdONTC7vvzOlLAG/wcfxcdxEWrbiZApcoBCzXqU/Z458Za5Iw==", + "version": "8.34.1", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/project-service/-/project-service-8.34.1.tgz", + "integrity": "sha512-nuHlOmFZfuRwLJKDGQOVc0xnQrAmuq1Mj/ISou5044y1ajGNp2BNliIqp7F2LPQ5sForz8lempMFCovfeS1XoA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.33.1", - "@typescript-eslint/types": "^8.33.1", + "@typescript-eslint/tsconfig-utils": "^8.34.1", + "@typescript-eslint/types": "^8.34.1", "debug": "^4.3.4" }, "engines": { @@ -2202,14 +2202,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.33.1", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-8.33.1.tgz", - "integrity": "sha512-dM4UBtgmzHR9bS0Rv09JST0RcHYearoEoo3pG5B6GoTR9XcyeqX87FEhPo+5kTvVfKCvfHaHrcgeJQc6mrDKrA==", + "version": "8.34.1", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-8.34.1.tgz", + "integrity": "sha512-beu6o6QY4hJAgL1E8RaXNC071G4Kso2MGmJskCFQhRhg8VOH/FDbC8soP8NHN7e/Hdphwp8G8cE6OBzC8o41ZA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.33.1", - "@typescript-eslint/visitor-keys": "8.33.1" + "@typescript-eslint/types": "8.34.1", + "@typescript-eslint/visitor-keys": "8.34.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2220,9 +2220,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.33.1", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.33.1.tgz", - "integrity": "sha512-STAQsGYbHCF0/e+ShUQ4EatXQ7ceh3fBCXkNU7/MZVKulrlq1usH7t2FhxvCpuCi5O5oi1vmVaAjrGeL71OK1g==", + "version": "8.34.1", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.34.1.tgz", + "integrity": "sha512-K4Sjdo4/xF9NEeA2khOb7Y5nY6NSXBnod87uniVYW9kHP+hNlDV8trUSFeynA2uxWam4gIWgWoygPrv9VMWrYg==", "dev": true, "license": "MIT", "engines": { @@ -2237,14 +2237,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.33.1", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-8.33.1.tgz", - "integrity": "sha512-1cG37d9xOkhlykom55WVwG2QRNC7YXlxMaMzqw2uPeJixBFfKWZgaP/hjAObqMN/u3fr5BrTwTnc31/L9jQ2ww==", + "version": "8.34.1", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-8.34.1.tgz", + "integrity": "sha512-Tv7tCCr6e5m8hP4+xFugcrwTOucB8lshffJ6zf1mF1TbU67R+ntCc6DzLNKM+s/uzDyv8gLq7tufaAhIBYeV8g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.33.1", - "@typescript-eslint/utils": "8.33.1", + "@typescript-eslint/typescript-estree": "8.34.1", + "@typescript-eslint/utils": "8.34.1", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, @@ -2261,9 +2261,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.33.1", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/types/-/types-8.33.1.tgz", - "integrity": "sha512-xid1WfizGhy/TKMTwhtVOgalHwPtV8T32MS9MaH50Cwvz6x6YqRIPdD2WvW0XaqOzTV9p5xdLY0h/ZusU5Lokg==", + "version": "8.34.1", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/types/-/types-8.34.1.tgz", + "integrity": "sha512-rjLVbmE7HR18kDsjNIZQHxmv9RZwlgzavryL5Lnj2ujIRTeXlKtILHgRNmQ3j4daw7zd+mQgy+uyt6Zo6I0IGA==", "dev": true, "license": "MIT", "engines": { @@ -2275,16 +2275,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.33.1", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.33.1.tgz", - "integrity": "sha512-+s9LYcT8LWjdYWu7IWs7FvUxpQ/DGkdjZeE/GGulHvv8rvYwQvVaUZ6DE+j5x/prADUgSbbCWZ2nPI3usuVeOA==", + "version": "8.34.1", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.34.1.tgz", + "integrity": "sha512-rjCNqqYPuMUF5ODD+hWBNmOitjBWghkGKJg6hiCHzUvXRy6rK22Jd3rwbP2Xi+R7oYVvIKhokHVhH41BxPV5mA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.33.1", - "@typescript-eslint/tsconfig-utils": "8.33.1", - "@typescript-eslint/types": "8.33.1", - "@typescript-eslint/visitor-keys": "8.33.1", + "@typescript-eslint/project-service": "8.34.1", + "@typescript-eslint/tsconfig-utils": "8.34.1", + "@typescript-eslint/types": "8.34.1", + "@typescript-eslint/visitor-keys": "8.34.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -2304,9 +2304,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2330,16 +2330,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.33.1", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-8.33.1.tgz", - "integrity": "sha512-52HaBiEQUaRYqAXpfzWSR2U3gxk92Kw006+xZpElaPMg3C4PgM+A5LqwoQI1f9E5aZ/qlxAZxzm42WX+vn92SQ==", + "version": "8.34.1", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-8.34.1.tgz", + "integrity": "sha512-mqOwUdZ3KjtGk7xJJnLbHxTuWVn3GO2WZZuM+Slhkun4+qthLdXx32C8xIXbO1kfCECb3jIs3eoxK3eryk7aoQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.33.1", - "@typescript-eslint/types": "8.33.1", - "@typescript-eslint/typescript-estree": "8.33.1" + "@typescript-eslint/scope-manager": "8.34.1", + "@typescript-eslint/types": "8.34.1", + "@typescript-eslint/typescript-estree": "8.34.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2354,14 +2354,14 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.33.1", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.33.1.tgz", - "integrity": "sha512-3i8NrFcZeeDHJ+7ZUuDkGT+UHq+XoFGsymNK2jZCOHcfEzRQ0BdpRtdpSx/Iyf3MHLWIcLS0COuOPibKQboIiQ==", + "version": "8.34.1", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.34.1.tgz", + "integrity": "sha512-xoh5rJ+tgsRKoXnkBPFRLZ7rjKM0AfVbC68UZ/ECXoDbfggb9RbEySN359acY1vS3qZ0jVTVWzbtfapwm5ztxw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.33.1", - "eslint-visitor-keys": "^4.2.0" + "@typescript-eslint/types": "8.34.1", + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2415,53 +2415,53 @@ } }, "node_modules/@vue/compiler-core": { - "version": "3.5.16", - "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.16.tgz", - "integrity": "sha512-AOQS2eaQOaaZQoL1u+2rCJIKDruNXVBZSiUD3chnUrsoX5ZTQMaCvXlWNIfxBJuU15r1o7+mpo5223KVtIhAgQ==", + "version": "3.5.17", + "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.17.tgz", + "integrity": "sha512-Xe+AittLbAyV0pabcN7cP7/BenRBNcteM4aSDCtRvGw0d9OL+HG1u/XHLY/kt1q4fyMeZYXyIYrsHuPSiDPosA==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.27.2", - "@vue/shared": "3.5.16", + "@babel/parser": "^7.27.5", + "@vue/shared": "3.5.17", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "node_modules/@vue/compiler-dom": { - "version": "3.5.16", - "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.16.tgz", - "integrity": "sha512-SSJIhBr/teipXiXjmWOVWLnxjNGo65Oj/8wTEQz0nqwQeP75jWZ0n4sF24Zxoht1cuJoWopwj0J0exYwCJ0dCQ==", + "version": "3.5.17", + "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.17.tgz", + "integrity": "sha512-+2UgfLKoaNLhgfhV5Ihnk6wB4ljyW1/7wUIog2puUqajiC29Lp5R/IKDdkebh9jTbTogTbsgB+OY9cEWzG95JQ==", "license": "MIT", "dependencies": { - "@vue/compiler-core": "3.5.16", - "@vue/shared": "3.5.16" + "@vue/compiler-core": "3.5.17", + "@vue/shared": "3.5.17" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.5.16", - "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.16.tgz", - "integrity": "sha512-rQR6VSFNpiinDy/DVUE0vHoIDUF++6p910cgcZoaAUm3POxgNOOdS/xgoll3rNdKYTYPnnbARDCZOyZ+QSe6Pw==", + "version": "3.5.17", + "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.17.tgz", + "integrity": "sha512-rQQxbRJMgTqwRugtjw0cnyQv9cP4/4BxWfTdRBkqsTfLOHWykLzbOc3C4GGzAmdMDxhzU/1Ija5bTjMVrddqww==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.27.2", - "@vue/compiler-core": "3.5.16", - "@vue/compiler-dom": "3.5.16", - "@vue/compiler-ssr": "3.5.16", - "@vue/shared": "3.5.16", + "@babel/parser": "^7.27.5", + "@vue/compiler-core": "3.5.17", + "@vue/compiler-dom": "3.5.17", + "@vue/compiler-ssr": "3.5.17", + "@vue/shared": "3.5.17", "estree-walker": "^2.0.2", "magic-string": "^0.30.17", - "postcss": "^8.5.3", + "postcss": "^8.5.6", "source-map-js": "^1.2.1" } }, "node_modules/@vue/compiler-ssr": { - "version": "3.5.16", - "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.16.tgz", - "integrity": "sha512-d2V7kfxbdsjrDSGlJE7my1ZzCXViEcqN6w14DOsDrUCHEA6vbnVCpRFfrc4ryCP/lCKzX2eS1YtnLE/BuC9f/A==", + "version": "3.5.17", + "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.17.tgz", + "integrity": "sha512-hkDbA0Q20ZzGgpj5uZjb9rBzQtIHLS78mMilwrlpWk2Ep37DYntUz0PonQ6kr113vfOEdM+zTBuJDaceNIW0tQ==", "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.16", - "@vue/shared": "3.5.16" + "@vue/compiler-dom": "3.5.17", + "@vue/shared": "3.5.17" } }, "node_modules/@vue/compiler-vue2": { @@ -2560,53 +2560,53 @@ } }, "node_modules/@vue/reactivity": { - "version": "3.5.16", - "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.16.tgz", - "integrity": "sha512-FG5Q5ee/kxhIm1p2bykPpPwqiUBV3kFySsHEQha5BJvjXdZTUfmya7wP7zC39dFuZAcf/PD5S4Lni55vGLMhvA==", + "version": "3.5.17", + "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.17.tgz", + "integrity": "sha512-l/rmw2STIscWi7SNJp708FK4Kofs97zc/5aEPQh4bOsReD/8ICuBcEmS7KGwDj5ODQLYWVN2lNibKJL1z5b+Lw==", "license": "MIT", "dependencies": { - "@vue/shared": "3.5.16" + "@vue/shared": "3.5.17" } }, "node_modules/@vue/runtime-core": { - "version": "3.5.16", - "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.16.tgz", - "integrity": "sha512-bw5Ykq6+JFHYxrQa7Tjr+VSzw7Dj4ldR/udyBZbq73fCdJmyy5MPIFR9IX/M5Qs+TtTjuyUTCnmK3lWWwpAcFQ==", + "version": "3.5.17", + "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.17.tgz", + "integrity": "sha512-QQLXa20dHg1R0ri4bjKeGFKEkJA7MMBxrKo2G+gJikmumRS7PTD4BOU9FKrDQWMKowz7frJJGqBffYMgQYS96Q==", "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.16", - "@vue/shared": "3.5.16" + "@vue/reactivity": "3.5.17", + "@vue/shared": "3.5.17" } }, "node_modules/@vue/runtime-dom": { - "version": "3.5.16", - "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.16.tgz", - "integrity": "sha512-T1qqYJsG2xMGhImRUV9y/RseB9d0eCYZQ4CWca9ztCuiPj/XWNNN+lkNBuzVbia5z4/cgxdL28NoQCvC0Xcfww==", + "version": "3.5.17", + "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.17.tgz", + "integrity": "sha512-8El0M60TcwZ1QMz4/os2MdlQECgGoVHPuLnQBU3m9h3gdNRW9xRmI8iLS4t/22OQlOE6aJvNNlBiCzPHur4H9g==", "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.16", - "@vue/runtime-core": "3.5.16", - "@vue/shared": "3.5.16", + "@vue/reactivity": "3.5.17", + "@vue/runtime-core": "3.5.17", + "@vue/shared": "3.5.17", "csstype": "^3.1.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.5.16", - "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.16.tgz", - "integrity": "sha512-BrX0qLiv/WugguGsnQUJiYOE0Fe5mZTwi6b7X/ybGB0vfrPH9z0gD/Y6WOR1sGCgX4gc25L1RYS5eYQKDMoNIg==", + "version": "3.5.17", + "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.17.tgz", + "integrity": "sha512-BOHhm8HalujY6lmC3DbqF6uXN/K00uWiEeF22LfEsm9Q93XeJ/plHTepGwf6tqFcF7GA5oGSSAAUock3VvzaCA==", "license": "MIT", "dependencies": { - "@vue/compiler-ssr": "3.5.16", - "@vue/shared": "3.5.16" + "@vue/compiler-ssr": "3.5.17", + "@vue/shared": "3.5.17" }, "peerDependencies": { - "vue": "3.5.16" + "vue": "3.5.17" } }, "node_modules/@vue/shared": { - "version": "3.5.16", - "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.16.tgz", - "integrity": "sha512-c/0fWy3Jw6Z8L9FmTyYfkpM5zklnqqa9+a6dz3DvONRKW2NEbh46BP0FHuLFSWi2TnQEtp91Z6zOWNrU6QiyPg==", + "version": "3.5.17", + "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.17.tgz", + "integrity": "sha512-CabR+UN630VnsJO/jHWYBC1YVXyMq94KKp6iF5MQgZJs5I8cmjw6oVMO1oDbtBkENSHSSn/UadWlW/OAgdmKrg==", "license": "MIT" }, "node_modules/@vueuse/core": { @@ -2655,9 +2655,9 @@ "license": "MIT" }, "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "version": "8.15.0", + "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", "bin": { @@ -3186,19 +3186,19 @@ } }, "node_modules/eslint": { - "version": "9.28.0", - "resolved": "https://registry.npmmirror.com/eslint/-/eslint-9.28.0.tgz", - "integrity": "sha512-ocgh41VhRlf9+fVpe7QKzwLj9c92fDiqOj8Y3Sd4/ZmVA4Btx4PlUYPq4pp9JDyupkf1upbEXecxL2mwNV7jPQ==", + "version": "9.29.0", + "resolved": "https://registry.npmmirror.com/eslint/-/eslint-9.29.0.tgz", + "integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.20.0", + "@eslint/config-array": "^0.20.1", "@eslint/config-helpers": "^0.2.1", "@eslint/core": "^0.14.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.28.0", + "@eslint/js": "9.29.0", "@eslint/plugin-kit": "^0.3.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -3210,9 +3210,9 @@ "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.3.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -3247,9 +3247,9 @@ } }, "node_modules/eslint-plugin-vue": { - "version": "10.1.0", - "resolved": "https://registry.npmmirror.com/eslint-plugin-vue/-/eslint-plugin-vue-10.1.0.tgz", - "integrity": "sha512-/VTiJ1eSfNLw6lvG9ENySbGmcVvz6wZ9nA7ZqXlLBY2RkaF15iViYKxglWiIch12KiLAj0j1iXPYU6W4wTROFA==", + "version": "10.2.0", + "resolved": "https://registry.npmmirror.com/eslint-plugin-vue/-/eslint-plugin-vue-10.2.0.tgz", + "integrity": "sha512-tl9s+KN3z0hN2b8fV2xSs5ytGl7Esk1oSCxULLwFcdaElhZ8btYYZFrWxvh4En+czrSDtuLCeCOGa8HhEZuBdQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3269,9 +3269,9 @@ } }, "node_modules/eslint-scope": { - "version": "8.3.0", - "resolved": "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-8.3.0.tgz", - "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", + "version": "8.4.0", + "resolved": "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -3286,9 +3286,9 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "version": "4.2.1", + "resolved": "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -3299,15 +3299,15 @@ } }, "node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmmirror.com/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "version": "10.4.0", + "resolved": "https://registry.npmmirror.com/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.14.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4204,9 +4204,9 @@ } }, "node_modules/pinia": { - "version": "3.0.2", - "resolved": "https://registry.npmmirror.com/pinia/-/pinia-3.0.2.tgz", - "integrity": "sha512-sH2JK3wNY809JOeiiURUR0wehJ9/gd9qFN2Y828jCbxEzKEmEt0pzCXwqiSTfuRsK9vQsOflSdnbdBOGrhtn+g==", + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/pinia/-/pinia-3.0.3.tgz", + "integrity": "sha512-ttXO/InUULUXkMHpTdp9Fj4hLpD/2AoJdmAbAeW2yu1iy1k+pkFekQXw5VpC0/5p51IOR/jDaDRfRWRnMMsGOA==", "license": "MIT", "dependencies": { "@vue/devtools-api": "^7.7.2" @@ -4237,9 +4237,9 @@ } }, "node_modules/postcss": { - "version": "8.5.3", - "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.3.tgz", - "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "version": "8.5.6", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "funding": [ { "type": "opencollective", @@ -4256,7 +4256,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.8", + "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -4454,9 +4454,9 @@ } }, "node_modules/sass": { - "version": "1.89.1", - "resolved": "https://registry.npmmirror.com/sass/-/sass-1.89.1.tgz", - "integrity": "sha512-eMLLkl+qz7tx/0cJ9wI+w09GQ2zodTkcE/aVfywwdlRcI3EO19xGnbmJwg/JMIm+5MxVJ6outddLZ4Von4E++Q==", + "version": "1.89.2", + "resolved": "https://registry.npmmirror.com/sass/-/sass-1.89.2.tgz", + "integrity": "sha512-xCmtksBKd/jdJ9Bt9p7nPKiuqrlBMBuuGkQlkhZjjQk3Ty48lv93k5Dq6OPkKt4XwxDJ7tvlfrTa1MPA9bf+QA==", "license": "MIT", "dependencies": { "chokidar": "^4.0.0", @@ -4712,15 +4712,15 @@ } }, "node_modules/typescript-eslint": { - "version": "8.33.1", - "resolved": "https://registry.npmmirror.com/typescript-eslint/-/typescript-eslint-8.33.1.tgz", - "integrity": "sha512-AgRnV4sKkWOiZ0Kjbnf5ytTJXMUZQ0qhSVdQtDNYLPLnjsATEYhaO94GlRQwi4t4gO8FfjM6NnikHeKjUm8D7A==", + "version": "8.34.1", + "resolved": "https://registry.npmmirror.com/typescript-eslint/-/typescript-eslint-8.34.1.tgz", + "integrity": "sha512-XjS+b6Vg9oT1BaIUfkW3M3LvqZE++rbzAMEHuccCfO/YkP43ha6w3jTEMilQxMF92nVOYCcdjv1ZUhAa1D/0ow==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.33.1", - "@typescript-eslint/parser": "8.33.1", - "@typescript-eslint/utils": "8.33.1" + "@typescript-eslint/eslint-plugin": "8.34.1", + "@typescript-eslint/parser": "8.34.1", + "@typescript-eslint/utils": "8.34.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4769,9 +4769,9 @@ } }, "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "version": "7.8.0", + "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.8.0.tgz", + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", "dev": true, "license": "MIT" }, @@ -5150,16 +5150,16 @@ "license": "MIT" }, "node_modules/vue": { - "version": "3.5.16", - "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.16.tgz", - "integrity": "sha512-rjOV2ecxMd5SiAmof2xzh2WxntRcigkX/He4YFJ6WdRvVUrbt6DxC1Iujh10XLl8xCDRDtGKMeO3D+pRQ1PP9w==", + "version": "3.5.17", + "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.17.tgz", + "integrity": "sha512-LbHV3xPN9BeljML+Xctq4lbz2lVHCR6DtbpTf5XIO6gugpXUN49j2QQPcMj086r9+AkJ0FfUT8xjulKKBkkr9g==", "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.16", - "@vue/compiler-sfc": "3.5.16", - "@vue/runtime-dom": "3.5.16", - "@vue/server-renderer": "3.5.16", - "@vue/shared": "3.5.16" + "@vue/compiler-dom": "3.5.17", + "@vue/compiler-sfc": "3.5.17", + "@vue/runtime-dom": "3.5.17", + "@vue/server-renderer": "3.5.17", + "@vue/shared": "3.5.17" }, "peerDependencies": { "typescript": "*" @@ -5196,13 +5196,13 @@ } }, "node_modules/vue-i18n": { - "version": "11.1.5", - "resolved": "https://registry.npmmirror.com/vue-i18n/-/vue-i18n-11.1.5.tgz", - "integrity": "sha512-XCwuaEA5AF97g1frvH/EI1zI9uo1XKTf2/OCFgts7NvUWRsjlgeHPrkJV+a3gpzai2pC4quZ4AnOHFO8QK9hsg==", + "version": "11.1.6", + "resolved": "https://registry.npmmirror.com/vue-i18n/-/vue-i18n-11.1.6.tgz", + "integrity": "sha512-+IbsW/sTZHj7U1w0rPOYJbuSB0/7DeO1nvUo3BxvO20OQgHs+ukJ3QeLqvoUA6DiLk+8SA9+djRmKC9+FC6cAg==", "license": "MIT", "dependencies": { - "@intlify/core-base": "11.1.5", - "@intlify/shared": "11.1.5", + "@intlify/core-base": "11.1.6", + "@intlify/shared": "11.1.6", "@vue/devtools-api": "^6.5.0" }, "engines": { diff --git a/frontend/package.json b/frontend/package.json index 9d5ac59..48afbe8 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -25,7 +25,7 @@ "@codemirror/lang-less": "^6.0.2", "@codemirror/lang-lezer": "^6.0.1", "@codemirror/lang-liquid": "^6.2.3", - "@codemirror/lang-markdown": "^6.3.2", + "@codemirror/lang-markdown": "^6.3.3", "@codemirror/lang-php": "^6.0.1", "@codemirror/lang-python": "^6.2.1", "@codemirror/lang-rust": "^6.0.1", @@ -41,7 +41,7 @@ "@codemirror/lint": "^6.8.5", "@codemirror/search": "^6.5.11", "@codemirror/state": "^6.5.2", - "@codemirror/view": "^6.37.1", + "@codemirror/view": "^6.37.2", "@lezer/highlight": "^1.2.1", "@lezer/lr": "^1.4.2", "@types/uuid": "^10.0.0", @@ -52,24 +52,24 @@ "colors-named-hex": "^1.0.2", "hsl-matcher": "^1.2.4", "lezer": "^0.13.5", - "pinia": "^3.0.2", - "sass": "^1.89.1", + "pinia": "^3.0.3", + "sass": "^1.89.2", "uuid": "^11.1.0", - "vue": "^3.5.16", - "vue-i18n": "^11.1.5", + "vue": "^3.5.17", + "vue-i18n": "^11.1.6", "vue-router": "^4.5.1" }, "devDependencies": { - "@eslint/js": "^9.28.0", + "@eslint/js": "^9.29.0", "@lezer/generator": "^1.7.3", - "@types/node": "^22.15.29", + "@types/node": "^24.0.3", "@vitejs/plugin-vue": "^5.2.4", "@wailsio/runtime": "latest", - "eslint": "^9.28.0", - "eslint-plugin-vue": "^10.1.0", + "eslint": "^9.29.0", + "eslint-plugin-vue": "^10.2.0", "globals": "^16.2.0", "typescript": "^5.8.3", - "typescript-eslint": "^8.33.1", + "typescript-eslint": "^8.34.1", "unplugin-vue-components": "^28.7.0", "vite": "^6.3.5", "vue-eslint-parser": "^10.1.3", diff --git a/frontend/public/guesslang.min.js b/frontend/public/guesslang.min.js new file mode 100644 index 0000000..0057eae --- /dev/null +++ b/frontend/public/guesslang.min.js @@ -0,0 +1,28 @@ +/*! guesslang.min.js v0.0.3 */ +/** @license zlib.js 2012 - imaya [ https://github.com/imaya/zlib.js ] The MIT License */(function() {'use strict';function n(e){throw e;}var p=void 0,aa=this;function t(e,b){var d=e.split("."),c=aa;!(d[0]in c)&&c.execScript&&c.execScript("var "+d[0]);for(var a;d.length&&(a=d.shift());)!d.length&&b!==p?c[a]=b:c=c[a]?c[a]:c[a]={}};var x="undefined"!==typeof Uint8Array&&"undefined"!==typeof Uint16Array&&"undefined"!==typeof Uint32Array&&"undefined"!==typeof DataView;new (x?Uint8Array:Array)(256);var y;for(y=0;256>y;++y)for(var A=y,ba=7,A=A>>>1;A;A>>>=1)--ba;function B(e,b,d){var c,a="number"===typeof b?b:b=0,f="number"===typeof d?d:e.length;c=-1;for(a=f&7;a--;++b)c=c>>>8^C[(c^e[b])&255];for(a=f>>3;a--;b+=8)c=c>>>8^C[(c^e[b])&255],c=c>>>8^C[(c^e[b+1])&255],c=c>>>8^C[(c^e[b+2])&255],c=c>>>8^C[(c^e[b+3])&255],c=c>>>8^C[(c^e[b+4])&255],c=c>>>8^C[(c^e[b+5])&255],c=c>>>8^C[(c^e[b+6])&255],c=c>>>8^C[(c^e[b+7])&255];return(c^4294967295)>>>0} +var D=[0,1996959894,3993919788,2567524794,124634137,1886057615,3915621685,2657392035,249268274,2044508324,3772115230,2547177864,162941995,2125561021,3887607047,2428444049,498536548,1789927666,4089016648,2227061214,450548861,1843258603,4107580753,2211677639,325883990,1684777152,4251122042,2321926636,335633487,1661365465,4195302755,2366115317,997073096,1281953886,3579855332,2724688242,1006888145,1258607687,3524101629,2768942443,901097722,1119000684,3686517206,2898065728,853044451,1172266101,3705015759, +2882616665,651767980,1373503546,3369554304,3218104598,565507253,1454621731,3485111705,3099436303,671266974,1594198024,3322730930,2970347812,795835527,1483230225,3244367275,3060149565,1994146192,31158534,2563907772,4023717930,1907459465,112637215,2680153253,3904427059,2013776290,251722036,2517215374,3775830040,2137656763,141376813,2439277719,3865271297,1802195444,476864866,2238001368,4066508878,1812370925,453092731,2181625025,4111451223,1706088902,314042704,2344532202,4240017532,1658658271,366619977, +2362670323,4224994405,1303535960,984961486,2747007092,3569037538,1256170817,1037604311,2765210733,3554079995,1131014506,879679996,2909243462,3663771856,1141124467,855842277,2852801631,3708648649,1342533948,654459306,3188396048,3373015174,1466479909,544179635,3110523913,3462522015,1591671054,702138776,2966460450,3352799412,1504918807,783551873,3082640443,3233442989,3988292384,2596254646,62317068,1957810842,3939845945,2647816111,81470997,1943803523,3814918930,2489596804,225274430,2053790376,3826175755, +2466906013,167816743,2097651377,4027552580,2265490386,503444072,1762050814,4150417245,2154129355,426522225,1852507879,4275313526,2312317920,282753626,1742555852,4189708143,2394877945,397917763,1622183637,3604390888,2714866558,953729732,1340076626,3518719985,2797360999,1068828381,1219638859,3624741850,2936675148,906185462,1090812512,3747672003,2825379669,829329135,1181335161,3412177804,3160834842,628085408,1382605366,3423369109,3138078467,570562233,1426400815,3317316542,2998733608,733239954,1555261956, +3268935591,3050360625,752459403,1541320221,2607071920,3965973030,1969922972,40735498,2617837225,3943577151,1913087877,83908371,2512341634,3803740692,2075208622,213261112,2463272603,3855990285,2094854071,198958881,2262029012,4057260610,1759359992,534414190,2176718541,4139329115,1873836001,414664567,2282248934,4279200368,1711684554,285281116,2405801727,4167216745,1634467795,376229701,2685067896,3608007406,1308918612,956543938,2808555105,3495958263,1231636301,1047427035,2932959818,3654703836,1088359270, +936918E3,2847714899,3736837829,1202900863,817233897,3183342108,3401237130,1404277552,615818150,3134207493,3453421203,1423857449,601450431,3009837614,3294710456,1567103746,711928724,3020668471,3272380065,1510334235,755167117],C=x?new Uint32Array(D):D;function E(){}E.prototype.getName=function(){return this.name};E.prototype.getData=function(){return this.data};E.prototype.G=function(){return this.H};function G(e){var b=e.length,d=0,c=Number.POSITIVE_INFINITY,a,f,k,l,m,r,q,g,h,v;for(g=0;gd&&(d=e[g]),e[g]>=1;v=k<<16|g;for(h=r;hK;K++)switch(!0){case 143>=K:J.push([K+48,8]);break;case 255>=K:J.push([K-144+400,9]);break;case 279>=K:J.push([K-256+0,7]);break;case 287>=K:J.push([K-280+192,8]);break;default:n("invalid literal: "+K)} +var ca=function(){function e(a){switch(!0){case 3===a:return[257,a-3,0];case 4===a:return[258,a-4,0];case 5===a:return[259,a-5,0];case 6===a:return[260,a-6,0];case 7===a:return[261,a-7,0];case 8===a:return[262,a-8,0];case 9===a:return[263,a-9,0];case 10===a:return[264,a-10,0];case 12>=a:return[265,a-11,1];case 14>=a:return[266,a-13,1];case 16>=a:return[267,a-15,1];case 18>=a:return[268,a-17,1];case 22>=a:return[269,a-19,2];case 26>=a:return[270,a-23,2];case 30>=a:return[271,a-27,2];case 34>=a:return[272, +a-31,2];case 42>=a:return[273,a-35,3];case 50>=a:return[274,a-43,3];case 58>=a:return[275,a-51,3];case 66>=a:return[276,a-59,3];case 82>=a:return[277,a-67,4];case 98>=a:return[278,a-83,4];case 114>=a:return[279,a-99,4];case 130>=a:return[280,a-115,4];case 162>=a:return[281,a-131,5];case 194>=a:return[282,a-163,5];case 226>=a:return[283,a-195,5];case 257>=a:return[284,a-227,5];case 258===a:return[285,a-258,0];default:n("invalid length: "+a)}}var b=[],d,c;for(d=3;258>=d;d++)c=e(d),b[d]=c[2]<<24|c[1]<< +16|c[0];return b}();x&&new Uint32Array(ca);function L(e,b){this.i=[];this.j=32768;this.d=this.f=this.c=this.n=0;this.input=x?new Uint8Array(e):e;this.o=!1;this.k=M;this.w=!1;if(b||!(b={}))b.index&&(this.c=b.index),b.bufferSize&&(this.j=b.bufferSize),b.bufferType&&(this.k=b.bufferType),b.resize&&(this.w=b.resize);switch(this.k){case N:this.a=32768;this.b=new (x?Uint8Array:Array)(32768+this.j+258);break;case M:this.a=0;this.b=new (x?Uint8Array:Array)(this.j);this.e=this.D;this.q=this.A;this.l=this.C;break;default:n(Error("invalid inflate mode"))}} +var N=0,M=1; +L.prototype.g=function(){for(;!this.o;){var e=P(this,3);e&1&&(this.o=!0);e>>>=1;switch(e){case 0:var b=this.input,d=this.c,c=this.b,a=this.a,f=b.length,k=p,l=p,m=c.length,r=p;this.d=this.f=0;d+1>=f&&n(Error("invalid uncompressed block header: LEN"));k=b[d++]|b[d++]<<8;d+1>=f&&n(Error("invalid uncompressed block header: NLEN"));l=b[d++]|b[d++]<<8;k===~l&&n(Error("invalid uncompressed block header: length verify"));d+k>b.length&&n(Error("input buffer is broken"));switch(this.k){case N:for(;a+k>c.length;){r= +m-a;k-=r;if(x)c.set(b.subarray(d,d+r),a),a+=r,d+=r;else for(;r--;)c[a++]=b[d++];this.a=a;c=this.e();a=this.a}break;case M:for(;a+k>c.length;)c=this.e({t:2});break;default:n(Error("invalid inflate mode"))}if(x)c.set(b.subarray(d,d+k),a),a+=k,d+=k;else for(;k--;)c[a++]=b[d++];this.c=d;this.a=a;this.b=c;break;case 1:this.l(da,ea);break;case 2:for(var q=P(this,5)+257,g=P(this,5)+1,h=P(this,4)+4,v=new (x?Uint8Array:Array)(Q.length),s=p,F=p,H=p,w=p,z=p,O=p,I=p,u=p,Z=p,u=0;u=W?8:255>=W?9:279>=W?7:8;var da=G(V),X=new (x?Uint8Array:Array)(30),Y,ma;Y=0;for(ma=X.length;Y=k&&n(Error("input buffer is broken")),d|=a[f++]<>>b;e.d=c-b;e.c=f;return l} +function R(e,b){for(var d=e.f,c=e.d,a=e.input,f=e.c,k=a.length,l=b[0],m=b[1],r,q;c=k);)d|=a[f++]<>>16;q>c&&n(Error("invalid code length: "+q));e.f=d>>q;e.d=c-q;e.c=f;return r&65535} +L.prototype.l=function(e,b){var d=this.b,c=this.a;this.r=e;for(var a=d.length-258,f,k,l,m;256!==(f=R(this,e));)if(256>f)c>=a&&(this.a=c,d=this.e(),c=this.a),d[c++]=f;else{k=f-257;m=ga[k];0=a&&(this.a=c,d=this.e(),c=this.a);for(;m--;)d[c]=d[c++-l]}for(;8<=this.d;)this.d-=8,this.c--;this.a=c}; +L.prototype.C=function(e,b){var d=this.b,c=this.a;this.r=e;for(var a=d.length,f,k,l,m;256!==(f=R(this,e));)if(256>f)c>=a&&(d=this.e(),a=d.length),d[c++]=f;else{k=f-257;m=ga[k];0a&&(d=this.e(),a=d.length);for(;m--;)d[c]=d[c++-l]}for(;8<=this.d;)this.d-=8,this.c--;this.a=c}; +L.prototype.e=function(){var e=new (x?Uint8Array:Array)(this.a-32768),b=this.a-32768,d,c,a=this.b;if(x)e.set(a.subarray(32768,e.length));else{d=0;for(c=e.length;dd;++d)a[d]=a[b+d];this.a=32768;return a}; +L.prototype.D=function(e){var b,d=this.input.length/this.c+1|0,c,a,f,k=this.input,l=this.b;e&&("number"===typeof e.t&&(d=e.t),"number"===typeof e.z&&(d+=e.z));2>d?(c=(k.length-this.c)/this.r[2],f=258*(c/2)|0,a=fb&&(this.b.length=b),e=this.b);return this.buffer=e};function $(e){this.input=e;this.c=0;this.m=[];this.s=!1}$.prototype.F=function(){this.s||this.g();return this.m.slice()}; +$.prototype.g=function(){for(var e=this.input.length;this.c>>0;B(a,p,p)!==q&&n(Error("invalid CRC-32 checksum: 0x"+B(a,p,p).toString(16)+" / 0x"+q.toString(16)));b.L= +d=(g[h++]|g[h++]<<8|g[h++]<<16|g[h++]<<24)>>>0;(a.length&4294967295)!==d&&n(Error("invalid input size: "+(a.length&4294967295)+" / "+d));this.m.push(b);this.c=h}this.s=!0;var v=this.m,s,F,H=0,w=0,z;s=0;for(F=v.length;s { + const {content, idx} = event.data + + // JSON 快速检测 + const trimmed = content.trim() + if ((trimmed.startsWith("{") && trimmed.endsWith("}")) || + (trimmed.startsWith("[") && trimmed.endsWith("]"))) { + try { + if (typeof JSON.parse(trimmed) === "object") { + sendResult("json", 1.0, idx) + return + } + } catch (e) { + + } + } + + guessLang.runModel(content).then((result) => { + if (result.length > 0) { + const lang = result[0] + if (LANGUAGES.includes(lang.languageId) && lang.confidence > 0.15) { + sendResult(lang.languageId, lang.confidence, idx) + return + } + } + + for (let lang of result) { + if (LANGUAGES.includes(lang.languageId) && lang.confidence > 0.5) { + sendResult(lang.languageId, lang.confidence, idx) + return + } + } + + sendResult("text", 0.0, idx) + }).catch(() => { + sendResult("text", 0.0, idx) + }) +} diff --git a/frontend/src/components/toolbar/BlockLanguageSelector.vue b/frontend/src/components/toolbar/BlockLanguageSelector.vue index 4fc2ec6..3a58fe9 100644 --- a/frontend/src/components/toolbar/BlockLanguageSelector.vue +++ b/frontend/src/components/toolbar/BlockLanguageSelector.vue @@ -13,28 +13,71 @@ const searchInputRef = ref(); // 语言别名映射 const LANGUAGE_ALIASES: Record = { text: 'txt', - javascript: 'js', - typescript: 'ts', - python: 'py', - html: 'htm', - css: '', - json: '', - markdown: 'md', - shell: 'sh', - sql: '', - yaml: 'yml', - xml: '', - php: '', - java: '', + json: 'JSON', + py: 'python', + html: 'HTML', + sql: 'SQL', + md: 'markdown', + java: 'Java', + php: 'PHP', + css: 'CSS', + xml: 'XML', cpp: 'c++', - c: '', - go: '', - rust: 'rs', - ruby: 'rb' + rs: 'rust', + cs: 'c#', + rb: 'ruby', + sh: 'shell', + yaml: 'yml', + toml: 'TOML', + go: 'Go', + clj: 'clojure', + ex: 'elixir', + erl: 'erlang', + js: 'javascript', + ts: 'typescript', + swift: 'Swift', + kt: 'kotlin', + groovy: 'Groovy', + ps1: 'powershell', + dart: 'Dart', + scala: 'Scala' +}; + +// 语言显示名称映射 +const LANGUAGE_NAMES: Record = { + text: 'Plain Text', + json: 'JSON', + py: 'Python', + html: 'HTML', + sql: 'SQL', + md: 'Markdown', + java: 'Java', + php: 'PHP', + css: 'CSS', + xml: 'XML', + cpp: 'C++', + rs: 'Rust', + cs: 'C#', + rb: 'Ruby', + sh: 'Shell', + yaml: 'YAML', + toml: 'TOML', + go: 'Go', + clj: 'Clojure', + ex: 'Elixir', + erl: 'Erlang', + js: 'JavaScript', + ts: 'TypeScript', + swift: 'Swift', + kt: 'Kotlin', + groovy: 'Groovy', + ps1: 'PowerShell', + dart: 'Dart', + scala: 'Scala' }; // 当前选中的语言 -const currentLanguage = ref('javascript'); +const currentLanguage = ref('text'); // 过滤后的语言列表 const filteredLanguages = computed(() => { @@ -44,8 +87,10 @@ const filteredLanguages = computed(() => { const query = searchQuery.value.toLowerCase(); return SUPPORTED_LANGUAGES.filter(langId => { + const name = LANGUAGE_NAMES[langId]; const alias = LANGUAGE_ALIASES[langId]; return langId.toLowerCase().includes(query) || + (name && name.toLowerCase().includes(query)) || (alias && alias.toLowerCase().includes(query)); }); }); @@ -96,7 +141,7 @@ onUnmounted(() => { // 获取当前语言的显示名称 const getCurrentLanguageName = computed(() => { - return currentLanguage.value; + return LANGUAGE_NAMES[currentLanguage.value] || currentLanguage.value; }); @@ -143,8 +188,8 @@ const getCurrentLanguageName = computed(() => { :class="{ 'active': currentLanguage === language }" @click="selectLanguage(language)" > - {{ language }} - {{ LANGUAGE_ALIASES[language] }} + {{ LANGUAGE_NAMES[language] || language }} + {{ language }} diff --git a/frontend/src/views/editor/extensions/codeblock/index.ts b/frontend/src/views/editor/extensions/codeblock/index.ts index 98713fa..fd2931c 100644 --- a/frontend/src/views/editor/extensions/codeblock/index.ts +++ b/frontend/src/views/editor/extensions/codeblock/index.ts @@ -1,33 +1,32 @@ /** * CodeBlock 扩展主入口 - * + * * 配置说明: * - showBackground: 控制是否显示代码块的背景色区分 * - enableAutoDetection: 控制是否启用内容的语言自动检测功能 * - defaultLanguage: 新建代码块时使用的默认语言(也是自动检测的回退语言) * - defaultAutoDetect: 新建代码块时是否默认添加-a标记启用自动检测 - * + * * 注意:defaultLanguage 和 defaultAutoDetect 是配合使用的: * - 如果 defaultAutoDetect=true,新建块会是 ∞∞∞javascript-a(会根据内容自动检测语言) * - 如果 defaultAutoDetect=false,新建块会是 ∞∞∞javascript(固定使用指定语言) */ import {Extension} from '@codemirror/state'; -import {keymap} from '@codemirror/view'; +import {keymap, lineNumbers} from '@codemirror/view'; // 导入核心模块 import {blockState} from './state'; import {getBlockDecorationExtensions} from './decorations'; import * as commands from './commands'; -import {selectAll, getBlockSelectExtensions} from './selectAll'; +import {getBlockSelectExtensions, selectAll} from './selectAll'; import {getCopyPasteExtensions, getCopyPasteKeymap} from './copyPaste'; import {deleteLineCommand} from './deleteLine'; -import {moveLineUp, moveLineDown} from './moveLines'; +import {moveLineDown, moveLineUp} from './moveLines'; import {transposeChars} from './transposeChars'; import {getCodeBlockLanguageExtension} from './lang-parser'; import {createLanguageDetection} from './language-detection'; import {EditorOptions, SupportedLanguage} from './types'; -import {lineNumbers} from '@codemirror/view'; /** * 代码块扩展配置选项 @@ -38,10 +37,10 @@ export interface CodeBlockOptions { /** 是否启用语言自动检测功能 */ enableAutoDetection?: boolean; - + /** 新建块时的默认语言 */ defaultLanguage?: SupportedLanguage; - + /** 新建块时是否默认启用自动检测(添加-a标记) */ defaultAutoDetect?: boolean; } @@ -52,10 +51,10 @@ export interface CodeBlockOptions { function getBlockLineFromPos(state: any, pos: number) { const line = state.doc.lineAt(pos); const blocks = state.field(blockState); - const block = blocks.find((block: any) => + const block = blocks.find((block: any) => block.content.from <= line.from && block.content.to >= line.from ); - + if (block) { const firstBlockLine = state.doc.lineAt(block.content.from).number; return { @@ -112,8 +111,8 @@ export function createCodeBlockExtension(options: CodeBlockOptions = {}): Extens // 语言自动检测(如果启用) ...(enableAutoDetection ? [createLanguageDetection({ defaultLanguage: defaultLanguage, - confidenceThreshold: 0.3, - minContentLength: 8, + confidenceThreshold: 0.15, + minContentLength: 8 })] : []), // 视觉装饰系统 @@ -297,14 +296,12 @@ export { createLanguageDetection, detectLanguage, detectLanguages, - detectLanguageHeuristic, - getSupportedDetectionLanguages, levenshteinDistance, type LanguageDetectionResult } from './language-detection'; // 行号相关 -export { getBlockLineFromPos, blockLineNumbers }; +export {getBlockLineFromPos, blockLineNumbers}; /** * 默认导出 diff --git a/frontend/src/views/editor/extensions/codeblock/lang-parser/languages.ts b/frontend/src/views/editor/extensions/codeblock/lang-parser/languages.ts index ae6d080..e2bb869 100644 --- a/frontend/src/views/editor/extensions/codeblock/lang-parser/languages.ts +++ b/frontend/src/views/editor/extensions/codeblock/lang-parser/languages.ts @@ -14,12 +14,21 @@ import { cssLanguage } from "@codemirror/lang-css"; import { cppLanguage } from "@codemirror/lang-cpp"; import { xmlLanguage } from "@codemirror/lang-xml"; import { rustLanguage } from "@codemirror/lang-rust"; +import { yamlLanguage } from "@codemirror/lang-yaml"; import { StreamLanguage } from "@codemirror/language"; import { ruby } from "@codemirror/legacy-modes/mode/ruby"; import { shell } from "@codemirror/legacy-modes/mode/shell"; import { go } from "@codemirror/legacy-modes/mode/go"; -import { yamlLanguage } from "@codemirror/lang-yaml"; +import { csharp } from "@codemirror/legacy-modes/mode/clike"; +import { clojure } from "@codemirror/legacy-modes/mode/clojure"; +import { erlang } from "@codemirror/legacy-modes/mode/erlang"; +import { swift } from "@codemirror/legacy-modes/mode/swift"; +import { kotlin } from "@codemirror/legacy-modes/mode/clike"; +import { groovy } from "@codemirror/legacy-modes/mode/groovy"; +import { powerShell } from "@codemirror/legacy-modes/mode/powershell"; +import { scala } from "@codemirror/legacy-modes/mode/clike"; +import { toml } from "@codemirror/legacy-modes/mode/toml"; import { SupportedLanguage } from '../types'; @@ -30,34 +39,43 @@ export class LanguageInfo { constructor( public token: SupportedLanguage, public name: string, - public parser: any, - public guesslang?: string | null + public parser: any ) {} } /** - * 支持的语言列表 + * 支持的语言列表(与 Worker 中的 LANGUAGES 对应) */ export const LANGUAGES: LanguageInfo[] = [ new LanguageInfo("text", "Plain Text", null), - new LanguageInfo("json", "JSON", jsonLanguage.parser, "json"), - new LanguageInfo("python", "Python", pythonLanguage.parser, "py"), - new LanguageInfo("javascript", "JavaScript", javascriptLanguage.parser, "js"), - new LanguageInfo("typescript", "TypeScript", typescriptLanguage.parser, "ts"), - new LanguageInfo("html", "HTML", htmlLanguage.parser, "html"), - new LanguageInfo("css", "CSS", cssLanguage.parser, "css"), - new LanguageInfo("sql", "SQL", StandardSQL.language.parser, "sql"), - new LanguageInfo("markdown", "Markdown", markdownLanguage.parser, "md"), - new LanguageInfo("java", "Java", javaLanguage.parser, "java"), - new LanguageInfo("php", "PHP", phpLanguage.configure({top:"Program"}).parser, "php"), - new LanguageInfo("xml", "XML", xmlLanguage.parser, "xml"), - new LanguageInfo("cpp", "C++", cppLanguage.parser, "cpp"), - new LanguageInfo("c", "C", cppLanguage.parser, "c"), - new LanguageInfo("rust", "Rust", rustLanguage.parser, "rs"), - new LanguageInfo("ruby", "Ruby", StreamLanguage.define(ruby).parser, "rb"), - new LanguageInfo("shell", "Shell", StreamLanguage.define(shell).parser, "sh"), - new LanguageInfo("yaml", "YAML", yamlLanguage.parser, "yaml"), - new LanguageInfo("go", "Go", StreamLanguage.define(go).parser, "go"), + new LanguageInfo("json", "JSON", jsonLanguage.parser), + new LanguageInfo("py", "Python", pythonLanguage.parser), + new LanguageInfo("html", "HTML", htmlLanguage.parser), + new LanguageInfo("sql", "SQL", StandardSQL.language.parser), + new LanguageInfo("md", "Markdown", markdownLanguage.parser), + new LanguageInfo("java", "Java", javaLanguage.parser), + new LanguageInfo("php", "PHP", phpLanguage.configure({top:"Program"}).parser), + new LanguageInfo("css", "CSS", cssLanguage.parser), + new LanguageInfo("xml", "XML", xmlLanguage.parser), + new LanguageInfo("cpp", "C++", cppLanguage.parser), + new LanguageInfo("rs", "Rust", rustLanguage.parser), + new LanguageInfo("cs", "C#", StreamLanguage.define(csharp).parser), + new LanguageInfo("rb", "Ruby", StreamLanguage.define(ruby).parser), + new LanguageInfo("sh", "Shell", StreamLanguage.define(shell).parser), + new LanguageInfo("yaml", "YAML", yamlLanguage.parser), + new LanguageInfo("toml", "TOML", StreamLanguage.define(toml).parser), + new LanguageInfo("go", "Go", StreamLanguage.define(go).parser), + new LanguageInfo("clj", "Clojure", StreamLanguage.define(clojure).parser), + new LanguageInfo("ex", "Elixir", null), // 暂无解析器 + new LanguageInfo("erl", "Erlang", StreamLanguage.define(erlang).parser), + new LanguageInfo("js", "JavaScript", javascriptLanguage.parser), + new LanguageInfo("ts", "TypeScript", typescriptLanguage.parser), + new LanguageInfo("swift", "Swift", StreamLanguage.define(swift).parser), + new LanguageInfo("kt", "Kotlin", StreamLanguage.define(kotlin).parser), + new LanguageInfo("groovy", "Groovy", StreamLanguage.define(groovy).parser), + new LanguageInfo("ps1", "PowerShell", StreamLanguage.define(powerShell).parser), + new LanguageInfo("dart", "Dart", null), // 暂无解析器 + new LanguageInfo("scala", "Scala", StreamLanguage.define(scala).parser), ]; /** @@ -79,4 +97,4 @@ export function getLanguage(token: SupportedLanguage): LanguageInfo | undefined */ export function getLanguageTokens(): SupportedLanguage[] { return LANGUAGES.map(lang => lang.token); -} \ No newline at end of file +} \ No newline at end of file diff --git a/frontend/src/views/editor/extensions/codeblock/language-detection/autodetect.ts b/frontend/src/views/editor/extensions/codeblock/language-detection/autodetect.ts index 96f6a11..46d5f0e 100644 --- a/frontend/src/views/editor/extensions/codeblock/language-detection/autodetect.ts +++ b/frontend/src/views/editor/extensions/codeblock/language-detection/autodetect.ts @@ -1,62 +1,92 @@ /** - * 自动语言检测 - * 基于内容变化自动检测和更新代码块语言 + * 基于 Web Worker 的语言自动检测 */ import { EditorState, Annotation } from '@codemirror/state'; import { EditorView, ViewPlugin } from '@codemirror/view'; -import { blockState, getActiveNoteBlock } from '../state'; +import { blockState } from '../state'; import { levenshteinDistance } from './levenshtein'; -import { detectLanguageHeuristic, LanguageDetectionResult } from './heuristics'; import { LANGUAGES } from '../lang-parser/languages'; import { SupportedLanguage, Block } from '../types'; +// ===== 类型定义 ===== + /** - * 语言检测配置 + * 语言检测配置选项 */ -interface LanguageDetectionConfig { - /** 最小内容长度阈值 */ - minContentLength: number; - /** 变化阈值比例 */ - changeThreshold: number; - /** 检测置信度阈值 */ - confidenceThreshold: number; - /** 空闲检测延迟 (ms) */ - idleDelay: number; - /** 默认语言 */ - defaultLanguage: SupportedLanguage; +export interface LanguageDetectionConfig { + minContentLength?: number; + confidenceThreshold?: number; + idleDelay?: number; + defaultLanguage?: SupportedLanguage; } +/** + * 语言检测结果 + */ +export interface LanguageDetectionResult { + language: SupportedLanguage; + confidence: number; +} + +/** + * Worker 消息接口 + */ +interface WorkerMessage { + content: string; + idx: number; +} + +/** + * Worker 响应接口 + */ +interface WorkerResponse { + language: string; + confidence: number; + idx: number; +} + +// ===== 常量配置 ===== + /** * 默认配置 */ -const DEFAULT_CONFIG: LanguageDetectionConfig = { - minContentLength: 8, - changeThreshold: 0.1, - confidenceThreshold: 0.3, +const DEFAULT_CONFIG = { + minContentLength: 20, + confidenceThreshold: 0.15, idleDelay: 1000, - defaultLanguage: 'text', + defaultLanguage: 'text' as SupportedLanguage, }; /** - * 语言标记映射 - * 将检测结果映射到我们的语言标记 + * 支持的语言列表 */ -const DETECTION_TO_TOKEN = Object.fromEntries( - LANGUAGES.map(l => [l.token, l.token]) -); +const SUPPORTED_LANGUAGES = new Set([ + "json", "py", "html", "sql", "md", "java", "php", "css", "xml", + "cpp", "rs", "cs", "rb", "sh", "yaml", "toml", "go", "clj", + "ex", "erl", "js", "ts", "swift", "kt", "groovy", "ps1", "dart", "scala" +]); /** - * 兼容性函数 + * 语言标记映射表 */ -function requestIdleCallbackCompat(cb: () => void): number { +const LANGUAGE_MAP = new Map(LANGUAGES.map(lang => [lang.token, lang.token])); + +// ===== 工具函数 ===== + +/** + * 兼容性函数:requestIdleCallback + */ +function requestIdleCallbackCompat(callback: () => void): number { if (typeof window !== 'undefined' && window.requestIdleCallback) { - return window.requestIdleCallback(cb); - } else { - return setTimeout(cb, 0) as any; + return window.requestIdleCallback(callback); } + return setTimeout(callback, 0) as any; } +/** + * 兼容性函数:cancelIdleCallback + */ function cancelIdleCallbackCompat(id: number): void { if (typeof window !== 'undefined' && window.cancelIdleCallback) { window.cancelIdleCallback(id); @@ -71,167 +101,192 @@ function cancelIdleCallbackCompat(id: number): void { const languageChangeAnnotation = Annotation.define(); /** - * 语言更改命令 - * 简化版本,直接更新块的语言标记 + * 更新代码块语言 */ -function changeLanguageTo( +function updateBlockLanguage( state: EditorState, - dispatch: (tr: any) => void, + dispatch: (transaction: any) => void, block: Block, - newLanguage: SupportedLanguage, - isAuto: boolean + newLanguage: SupportedLanguage ): void { - // 构建新的分隔符文本 - const autoSuffix = isAuto ? '-a' : ''; - const newDelimiter = `\n∞∞∞${newLanguage}${autoSuffix}\n`; - - // 创建事务来替换分隔符 + const newDelimiter = `\n∞∞∞${newLanguage}-a\n`; const transaction = state.update({ changes: { from: block.delimiter.from, to: block.delimiter.to, insert: newDelimiter, }, - annotations: [ - languageChangeAnnotation.of(true) - ] + annotations: [languageChangeAnnotation.of(true)] }); - dispatch(transaction); } +// ===== Web Worker 管理器 ===== +/** + * 语言检测 Worker 管理器 + * 负责 Worker 的生命周期管理和消息通信 + */ +class LanguageDetectionWorker { + private worker: Worker | null = null; + private pendingRequests = new Map void; + reject: (error: Error) => void; + }>(); + private requestId = 0; + + constructor() { + this.initWorker(); + } + + /** + * 初始化 Worker + */ + private initWorker(): void { + try { + this.worker = new Worker('/langdetect-worker.js'); + this.worker.onmessage = (event) => { + const response: WorkerResponse = event.data; + const request = this.pendingRequests.get(response.idx); + if (request) { + this.pendingRequests.delete(response.idx); + if (response.language) { + request.resolve({ + language: response.language as SupportedLanguage, + confidence: response.confidence + }); + } else { + request.reject(new Error('No detection result')); + } + } + }; + this.worker.onerror = () => { + this.pendingRequests.forEach(request => request.reject(new Error('Worker error'))); + this.pendingRequests.clear(); + }; + } catch (error) { + console.error('Failed to initialize worker:', error); + } + } + + /** + * 检测语言 + */ + async detectLanguage(content: string): Promise { + if (!this.worker) { + throw new Error('Worker not initialized'); + } + + return new Promise((resolve, reject) => { + const id = ++this.requestId; + this.pendingRequests.set(id, { resolve, reject }); + + this.worker!.postMessage({ content, idx: id } as WorkerMessage); + + // 5秒超时 + setTimeout(() => { + if (this.pendingRequests.has(id)) { + this.pendingRequests.delete(id); + reject(new Error('Detection timeout')); + } + }, 5000); + }); + } + + /** + * 销毁 Worker + */ + destroy(): void { + if (this.worker) { + this.worker.terminate(); + this.worker = null; + } + this.pendingRequests.clear(); + } +} + +// ===== 语言检测插件 ===== /** * 创建语言检测插件 */ -export function createLanguageDetection( - config: Partial = {} -): ViewPlugin { +export function createLanguageDetection(config: LanguageDetectionConfig = {}): ViewPlugin { const finalConfig = { ...DEFAULT_CONFIG, ...config }; - const previousBlockContent: Record = {}; + const contentCache = new Map(); let idleCallbackId: number | null = null; + let worker: LanguageDetectionWorker | null = null; return ViewPlugin.fromClass( class LanguageDetectionPlugin { - constructor(public view: EditorView) {} + constructor(public view: EditorView) { + worker = new LanguageDetectionWorker(); + } update(update: any) { - if (update.docChanged) { - // 取消之前的检测 + if (update.docChanged && !update.transactions.some((tr: any) => + tr.annotation(languageChangeAnnotation))) { + if (idleCallbackId !== null) { cancelIdleCallbackCompat(idleCallbackId); - idleCallbackId = null; } - - // 安排新的检测 + idleCallbackId = requestIdleCallbackCompat(() => { - idleCallbackId = null; - this.performLanguageDetection(update); + this.performDetection(update.state); }); } } - private performLanguageDetection(update: any) { - const range = update.state.selection.asSingle().ranges[0]; - const blocks = update.state.field(blockState); + private performDetection(state: EditorState): void { + const selection = state.selection.asSingle().ranges[0]; + const blocks = state.field(blockState); - let block: Block | null = null; - let blockIndex: number | null = null; + const block = blocks.find(b => + b.content.from <= selection.from && b.content.to >= selection.from + ); + + if (!block || !block.language.auto) return; - // 找到当前块 - for (let i = 0; i < blocks.length; i++) { - if (blocks[i].content.from <= range.from && blocks[i].content.to >= range.from) { - block = blocks[i]; - blockIndex = i; - break; - } - } + const blockIndex = blocks.indexOf(block); + const content = state.doc.sliceString(block.content.from, block.content.to); - if (block === null || blockIndex === null) { - return; - } - - - // 如果不是自动检测模式,清除缓存并返回 - if (!block.language.auto) { - delete previousBlockContent[blockIndex]; - return; - } - - const content = update.state.doc.sliceString(block.content.from, block.content.to); - - // 如果内容为空,重置为默认语言 + // 内容为空时重置为默认语言 if (content === "") { if (block.language.name !== finalConfig.defaultLanguage) { - changeLanguageTo( - update.state, - this.view.dispatch, - block, - finalConfig.defaultLanguage, - true - ); + updateBlockLanguage(state, this.view.dispatch, block, finalConfig.defaultLanguage); } - delete previousBlockContent[blockIndex]; + contentCache.delete(blockIndex); return; } - // 内容太短,跳过检测 - if (content.length <= finalConfig.minContentLength) { + // 内容太短则跳过 + if (content.length <= finalConfig.minContentLength) return; + + // 检查内容变化 + const cachedContent = contentCache.get(blockIndex); + if (cachedContent && levenshteinDistance(cachedContent, content) < content.length * 0.1) { return; } - // 检查内容是否有显著变化 - const threshold = content.length * finalConfig.changeThreshold; - const previousContent = previousBlockContent[blockIndex]; - - if (!previousContent) { - // 执行语言检测 - this.detectAndUpdateLanguage(content, block, blockIndex, update.state); - previousBlockContent[blockIndex] = content; - } else { - const distance = levenshteinDistance(previousContent, content); - - if (distance >= threshold) { - // 执行语言检测 - this.detectAndUpdateLanguage(content, block, blockIndex, update.state); - previousBlockContent[blockIndex] = content; - } - } + this.detectAndUpdate(content, block, blockIndex, state); } - private detectAndUpdateLanguage( - content: string, - block: any, - blockIndex: number, - state: EditorState - ) { + private async detectAndUpdate(content: string, block: Block, blockIndex: number, state: EditorState): Promise { + if (!worker) return; - // 使用启发式检测 - const detectionResult = detectLanguageHeuristic(content); - - // 检查置信度和语言变化 - if (detectionResult.confidence >= finalConfig.confidenceThreshold && - detectionResult.language !== block.language.name && - DETECTION_TO_TOKEN[detectionResult.language]) { + try { + const result = await worker.detectLanguage(content); - - // 验证内容未显著变化 - const currentContent = state.doc.sliceString(block.content.from, block.content.to); - const threshold = currentContent.length * finalConfig.changeThreshold; - const contentDistance = levenshteinDistance(content, currentContent); - - - if (contentDistance <= threshold) { - // 内容未显著变化,安全更新语言 - changeLanguageTo( - state, - this.view.dispatch, - block, - detectionResult.language, - true - ); + if (result.confidence >= finalConfig.confidenceThreshold && + result.language !== block.language.name && + SUPPORTED_LANGUAGES.has(result.language) && + LANGUAGE_MAP.has(result.language)) { + + updateBlockLanguage(state, this.view.dispatch, block, result.language); } + + contentCache.set(blockIndex, content); + } catch (error) { + console.warn('Language detection failed:', error); } } @@ -239,21 +294,38 @@ export function createLanguageDetection( if (idleCallbackId !== null) { cancelIdleCallbackCompat(idleCallbackId); } + if (worker) { + worker.destroy(); + worker = null; + } + contentCache.clear(); } } ); } +// ===== 公共 API ===== + /** - * 手动检测语言 + * 手动检测单个内容的语言 */ -export function detectLanguage(content: string): LanguageDetectionResult { - return detectLanguageHeuristic(content); +export async function detectLanguage(content: string): Promise { + const worker = new LanguageDetectionWorker(); + try { + return await worker.detectLanguage(content); + } finally { + worker.destroy(); + } } /** - * 批量检测多个内容块的语言 + * 批量检测多个内容的语言 */ -export function detectLanguages(contents: string[]): LanguageDetectionResult[] { - return contents.map(content => detectLanguageHeuristic(content)); +export async function detectLanguages(contents: string[]): Promise { + const worker = new LanguageDetectionWorker(); + try { + return await Promise.all(contents.map(content => worker.detectLanguage(content))); + } finally { + worker.destroy(); + } } \ No newline at end of file diff --git a/frontend/src/views/editor/extensions/codeblock/language-detection/heuristics.ts b/frontend/src/views/editor/extensions/codeblock/language-detection/heuristics.ts deleted file mode 100644 index 8813dc0..0000000 --- a/frontend/src/views/editor/extensions/codeblock/language-detection/heuristics.ts +++ /dev/null @@ -1,269 +0,0 @@ -/** - * 基于启发式规则的语言检测 - * 用于快速识别常见的编程语言模式 - */ - -import { SupportedLanguage } from '../types'; - -/** - * 语言检测结果 - */ -export interface LanguageDetectionResult { - language: SupportedLanguage; - confidence: number; -} - -/** - * 语言模式定义 - */ -interface LanguagePattern { - patterns: RegExp[]; - weight: number; -} - -/** - * 语言检测规则映射 - */ -const LANGUAGE_PATTERNS: Record = { - javascript: { - patterns: [ - /\b(function|const|let|var|class|extends|import|export|async|await)\b/g, - /\b(console\.log|document\.|window\.)\b/g, - /=>\s*[{(]/g, - /\b(require|module\.exports)\b/g, - ], - weight: 1.0, - }, - typescript: { - patterns: [ - /\b(interface|type|enum|namespace|implements|declare)\b/g, - /:\s*(string|number|boolean|object|any)\b/g, - /<[A-Z][a-zA-Z0-9<>,\s]*>/g, - /\b(public|private|protected|readonly)\b/g, - ], - weight: 1.2, - }, - python: { - patterns: [ - /\b(def|class|import|from|if __name__|print|len|range)\b/g, - /^\s*#.*$/gm, - /\b(True|False|None)\b/g, - /:\s*$/gm, - ], - weight: 1.0, - }, - java: { - patterns: [ - /\b(public|private|protected|static|final|class|interface)\b/g, - /\b(System\.out\.println|String|int|void)\b/g, - /import\s+[a-zA-Z0-9_.]+;/g, - /\b(extends|implements)\b/g, - ], - weight: 1.0, - }, - html: { - patterns: [ - /<\/?[a-zA-Z][^>]*>/g, - //gi, - /<(div|span|p|h[1-6]|body|head|html)\b/g, - /\s(class|id|src|href)=/g, - ], - weight: 1.5, - }, - css: { - patterns: [ - /[.#][a-zA-Z][\w-]*\s*{/g, - /\b(color|background|margin|padding|font-size):\s*[^;]+;/g, - /@(media|keyframes|import)\b/g, - /\{[^}]*\}/g, - ], - weight: 1.3, - }, - json: { - patterns: [ - /^\s*[{\[][\s\S]*[}\]]\s*$/, - /"[^"]*":\s*(".*"|[\d.]+|true|false|null)/g, - /,\s*$/gm, - ], - weight: 2.0, - }, - sql: { - patterns: [ - /\b(SELECT|FROM|WHERE|INSERT|UPDATE|DELETE|CREATE|ALTER|DROP)\b/gi, - /\b(JOIN|LEFT|RIGHT|INNER|OUTER|ON|GROUP BY|ORDER BY)\b/gi, - /;\s*$/gm, - /\b(TABLE|DATABASE|INDEX)\b/gi, - ], - weight: 1.4, - }, - shell: { - patterns: [ - /^#!/g, - /\b(echo|cd|ls|grep|awk|sed|cat|chmod)\b/g, - /\$\{?\w+\}?/g, - /\|\s*\w+/g, - ], - weight: 1.2, - }, - markdown: { - patterns: [ - /^#+\s+/gm, - /\*\*.*?\*\*/g, - /\[.*?\]\(.*?\)/g, - /^```/gm, - ], - weight: 1.1, - }, - php: { - patterns: [ - /<\?php/g, - /\$\w+/g, - /\b(function|class|extends|implements)\b/g, - /echo\s+/g, - ], - weight: 1.3, - }, - cpp: { - patterns: [ - /#include\s*<.*>/g, - /\b(int|char|float|double|void|class|struct)\b/g, - /std::/g, - /cout\s*<<|cin\s*>>/g, - ], - weight: 1.1, - }, - rust: { - patterns: [ - /\bfn\s+\w+/g, - /\b(let|mut|struct|enum|impl|trait)\b/g, - /println!\(/g, - /::\w+/g, - ], - weight: 1.2, - }, - go: { - patterns: [ - /\bfunc\s+\w+/g, - /\b(var|const|type|package|import)\b/g, - /fmt\.\w+/g, - /:=\s*/g, - ], - weight: 1.1, - }, - ruby: { - patterns: [ - /\b(def|class|module|end)\b/g, - /\b(puts|print|require)\b/g, - /@\w+/g, - /\|\w+\|/g, - ], - weight: 1.0, - }, - yaml: { - patterns: [ - /^\s*\w+:\s*.*$/gm, // key: value 模式 - /^\s*-\s+\w+/gm, // 列表项 - /^---\s*$/gm, // 文档分隔符 - /^\s*\w+:\s*\|/gm, // 多行字符串 - /^\s*\w+:\s*>/gm, // 折叠字符串 - /^\s*#.*$/gm, // 注释 - /:\s*\[.*\]/g, // 内联数组 - /:\s*\{.*\}/g, // 内联对象 - ], - weight: 1.5, - }, - xml: { - patterns: [ - /<\?xml/g, - /<\/\w+>/g, - /<\w+[^>]*\/>/g, - /\s\w+="[^"]*"/g, - ], - weight: 1.3, - }, -}; - -/** - * JSON 特殊检测 - * 使用更严格的规则检测 JSON - */ -function detectJSON(content: string): LanguageDetectionResult | null { - const trimmed = content.trim(); - - if ((trimmed.startsWith('{') && trimmed.endsWith('}')) || - (trimmed.startsWith('[') && trimmed.endsWith(']'))) { - try { - JSON.parse(trimmed); - return { - language: 'json', - confidence: 1.0, - }; - } catch (e) { - // JSON 解析失败,继续其他检测 - } - } - - return null; -} - -/** - * 计算文本与语言模式的匹配分数 - */ -function calculateScore(content: string, pattern: LanguagePattern): number { - let score = 0; - const contentLength = Math.max(content.length, 1); - - for (const regex of pattern.patterns) { - const matches = content.match(regex); - if (matches) { - score += matches.length; - } - } - - // 根据内容长度和权重标准化分数 - return (score * pattern.weight) / (contentLength / 100); -} - -/** - * 基于启发式规则检测语言 - */ -export function detectLanguageHeuristic(content: string): LanguageDetectionResult { - if (!content.trim()) { - return { language: 'text', confidence: 1.0 }; - } - - // 首先尝试 JSON 特殊检测 - const jsonResult = detectJSON(content); - if (jsonResult) { - return jsonResult; - } - - const scores: Record = {}; - - // 计算每种语言的匹配分数 - for (const [language, pattern] of Object.entries(LANGUAGE_PATTERNS)) { - scores[language] = calculateScore(content, pattern); - } - - // 找到最高分的语言 - const sortedScores = Object.entries(scores) - .sort(([, a], [, b]) => b - a) - .filter(([, score]) => score > 0); - - if (sortedScores.length > 0) { - const [bestLanguage, bestScore] = sortedScores[0]; - return { - language: bestLanguage as SupportedLanguage, - confidence: Math.min(bestScore, 1.0), - }; - } - - return { language: 'text', confidence: 1.0 }; -} - -/** - * 获取所有支持的检测语言 - */ -export function getSupportedDetectionLanguages(): SupportedLanguage[] { - return Object.keys(LANGUAGE_PATTERNS) as SupportedLanguage[]; -} \ No newline at end of file diff --git a/frontend/src/views/editor/extensions/codeblock/language-detection/index.ts b/frontend/src/views/editor/extensions/codeblock/language-detection/index.ts index 1bf4e95..035553d 100644 --- a/frontend/src/views/editor/extensions/codeblock/language-detection/index.ts +++ b/frontend/src/views/editor/extensions/codeblock/language-detection/index.ts @@ -1,26 +1,14 @@ /** - * 语言检测模块入口 - * 导出所有语言检测相关的功能 + * 语言检测模块 */ -// 主要检测功能 export { createLanguageDetection, detectLanguage, - detectLanguages + detectLanguages, + type LanguageDetectionResult, + type LanguageDetectionConfig } from './autodetect'; -// 启发式检测 -export { - detectLanguageHeuristic, - getSupportedDetectionLanguages, - type LanguageDetectionResult -} from './heuristics'; - -// 工具函数 -export { - levenshteinDistance -} from './levenshtein'; - -// 重新导出类型 -export type { SupportedLanguage } from '../types'; \ No newline at end of file +export { levenshteinDistance } from './levenshtein'; +export type { SupportedLanguage } from '../types'; \ No newline at end of file diff --git a/frontend/src/views/editor/extensions/codeblock/language-detection/levenshtein.ts b/frontend/src/views/editor/extensions/codeblock/language-detection/levenshtein.ts index fc00b33..7e52de3 100644 --- a/frontend/src/views/editor/extensions/codeblock/language-detection/levenshtein.ts +++ b/frontend/src/views/editor/extensions/codeblock/language-detection/levenshtein.ts @@ -1,78 +1,70 @@ /** * Levenshtein 距离算法 - * 用于计算两个字符串之间的编辑距离 */ /** * 内部最小值计算函数 + * 用于计算编辑距离的最小成本 + * + * @param d0 - 对角线上的距离 + * @param d1 - 左侧的距离 + * @param d2 - 上方的距离 + * @param bx - 字符串 b 的当前字符 + * @param ay - 字符串 a 的当前字符 + * @returns 最小编辑距离 */ -function _min(d0: number, d1: number, d2: number, bx: number, ay: number): number { +function min(d0: number, d1: number, d2: number, bx: number, ay: number): number { return d0 < d1 || d2 < d1 - ? d0 > d2 - ? d2 + 1 - : d0 + 1 - : bx === ay - ? d1 - : d1 + 1; + ? d0 > d2 ? d2 + 1 : d0 + 1 + : bx === ay ? d1 : d1 + 1; } /** * 计算两个字符串之间的 Levenshtein 距离 - * @param a 第一个字符串 - * @param b 第二个字符串 - * @returns 编辑距离 + * + * 该实现使用了多项优化: + * 1. 确保较短的字符串作为第一个参数 + * 2. 跳过公共前缀和后缀 + * 3. 使用滚动数组减少空间复杂度 + * 4. 批量处理以提高性能 + * + * @param stringA - 第一个字符串 + * @param stringB - 第二个字符串 + * @returns 编辑距离(非负整数) */ export function levenshteinDistance(a: string, b: string): number { - if (a === b) { - return 0; - } + if (a === b) return 0; if (a.length > b.length) { - const tmp = a; - a = b; - b = tmp; + [a, b] = [b, a]; } let la = a.length; let lb = b.length; - while (la > 0 && (a.charCodeAt(la - 1) === b.charCodeAt(lb - 1))) { + // 跳过公共后缀 + while (la > 0 && a.charCodeAt(la - 1) === b.charCodeAt(lb - 1)) { la--; lb--; } let offset = 0; - - while (offset < la && (a.charCodeAt(offset) === b.charCodeAt(offset))) { + // 跳过公共前缀 + while (offset < la && a.charCodeAt(offset) === b.charCodeAt(offset)) { offset++; } la -= offset; lb -= offset; - if (la === 0 || lb < 3) { - return lb; - } + if (la === 0 || lb < 3) return lb; - let x = 0; - let y: number; - let d0: number; - let d1: number; - let d2: number; - let d3: number; - let dd = 0; - let dy: number; - let ay: number; - let bx0: number; - let bx1: number; - let bx2: number; - let bx3: number; + let x = 0, y: number, d0: number, d1: number, d2: number, d3: number; + let dd = 0, dy: number, ay: number, bx0: number, bx1: number, bx2: number, bx3: number; const vector: number[] = []; - for (y = 0; y < la; y++) { - vector.push(y + 1); - vector.push(a.charCodeAt(offset + y)); + vector.push(y + 1, a.charCodeAt(offset + y)); } const len = vector.length - 1; @@ -84,18 +76,16 @@ export function levenshteinDistance(a: string, b: string): number { bx3 = b.charCodeAt(offset + (d3 = x + 3)); x += 4; dd = x; + for (y = 0; y < len; y += 2) { dy = vector[y]; ay = vector[y + 1]; - d0 = _min(dy, d0, d1, bx0, ay); - d1 = _min(d0, d1, d2, bx1, ay); - d2 = _min(d1, d2, d3, bx2, ay); - dd = _min(d2, d3, dd, bx3, ay); + d0 = min(dy, d0, d1, bx0, ay); + d1 = min(d0, d1, d2, bx1, ay); + d2 = min(d1, d2, d3, bx2, ay); + dd = min(d2, d3, dd, bx3, ay); vector[y] = dd; - d3 = d2; - d2 = d1; - d1 = d0; - d0 = dy; + d3 = d2; d2 = d1; d1 = d0; d0 = dy; } } @@ -104,7 +94,7 @@ export function levenshteinDistance(a: string, b: string): number { dd = ++x; for (y = 0; y < len; y += 2) { dy = vector[y]; - vector[y] = dd = _min(dy, d0, dd, bx0, vector[y + 1]); + vector[y] = dd = min(dy, d0, dd, bx0, vector[y + 1]); d0 = dy; } } diff --git a/frontend/src/views/editor/extensions/codeblock/types.ts b/frontend/src/views/editor/extensions/codeblock/types.ts index fac35df..4bcd5aa 100644 --- a/frontend/src/views/editor/extensions/codeblock/types.ts +++ b/frontend/src/views/editor/extensions/codeblock/types.ts @@ -25,48 +25,68 @@ export interface Block { */ export type SupportedLanguage = | 'text' - | 'javascript' - | 'typescript' - | 'python' - | 'html' - | 'css' | 'json' - | 'markdown' - | 'shell' + | 'py' // Python + | 'html' | 'sql' - | 'yaml' - | 'xml' - | 'php' + | 'md' // Markdown | 'java' - | 'cpp' - | 'c' + | 'php' + | 'css' + | 'xml' + | 'cpp' // C++ + | 'rs' // Rust + | 'cs' // C# + | 'rb' // Ruby + | 'sh' // Shell + | 'yaml' + | 'toml' | 'go' - | 'rust' - | 'ruby'; + | 'clj' // Clojure + | 'ex' // Elixir + | 'erl' // Erlang + | 'js' // JavaScript + | 'ts' // TypeScript + | 'swift' + | 'kt' // Kotlin + | 'groovy' + | 'ps1' // PowerShell + | 'dart' + | 'scala'; /** * 支持的语言列表 */ export const SUPPORTED_LANGUAGES: SupportedLanguage[] = [ 'text', - 'javascript', - 'typescript', - 'python', - 'html', - 'css', 'json', - 'markdown', - 'shell', + 'py', + 'html', 'sql', - 'yaml', - 'xml', - 'php', + 'md', 'java', + 'php', + 'css', + 'xml', 'cpp', - 'c', + 'rs', + 'cs', + 'rb', + 'sh', + 'yaml', + 'toml', 'go', - 'rust', - 'ruby' + 'clj', + 'ex', + 'erl', + 'js', + 'ts', + 'swift', + 'kt', + 'groovy', + 'ps1', + 'dart', + 'scala' ]; /** @@ -134,9 +154,6 @@ export interface BlockStateUpdate { operation?: BlockOperation; } -// 块导航方向 -export type NavigationDirection = 'next' | 'previous' | 'first' | 'last'; - // 语言检测结果 export interface LanguageDetectionResult { language: SupportedLanguage;