Use guesslang to detect the language

This commit is contained in:
2025-06-19 16:55:02 +08:00
parent f2894b2a89
commit 25858cb42b
12 changed files with 724 additions and 790 deletions

View File

@@ -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": {

View File

@@ -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",

28
frontend/public/guesslang.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,48 @@
importScripts("guesslang.min.js")
const LANGUAGES = ["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"]
const guessLang = new self.GuessLang()
function sendResult(language, confidence, idx) {
postMessage({language, confidence, idx})
}
onmessage = (event) => {
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)
})
}

View File

@@ -13,28 +13,71 @@ const searchInputRef = ref<HTMLInputElement>();
// 语言别名映射
const LANGUAGE_ALIASES: Record<SupportedLanguage, string> = {
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<SupportedLanguage, string> = {
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<SupportedLanguage>('javascript');
const currentLanguage = ref<SupportedLanguage>('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;
});
</script>
@@ -143,8 +188,8 @@ const getCurrentLanguageName = computed(() => {
:class="{ 'active': currentLanguage === language }"
@click="selectLanguage(language)"
>
<span class="language-name">{{ language }}</span>
<span class="language-alias" v-if="LANGUAGE_ALIASES[language]">{{ LANGUAGE_ALIASES[language] }}</span>
<span class="language-name">{{ LANGUAGE_NAMES[language] || language }}</span>
<span class="language-alias">{{ language }}</span>
</div>
<!-- 无结果提示 -->

View File

@@ -13,21 +13,20 @@
*/
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';
/**
* 代码块扩展配置选项
@@ -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,8 +296,6 @@ export {
createLanguageDetection,
detectLanguage,
detectLanguages,
detectLanguageHeuristic,
getSupportedDetectionLanguages,
levenshteinDistance,
type LanguageDetectionResult
} from './language-detection';

View File

@@ -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),
];
/**

View File

@@ -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<boolean>();
/**
* 语言更改命令
* 简化版本,直接更新块的语言标记
* 更新代码块语言
*/
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<number, {
resolve: (result: LanguageDetectionResult) => 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<LanguageDetectionResult> {
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<LanguageDetectionConfig> = {}
): ViewPlugin<any> {
export function createLanguageDetection(config: LanguageDetectionConfig = {}): ViewPlugin<any> {
const finalConfig = { ...DEFAULT_CONFIG, ...config };
const previousBlockContent: Record<number, string> = {};
const contentCache = new Map<number, string>();
let idleCallbackId: number | null = null;
let worker: LanguageDetectionWorker | null = null;
return ViewPlugin.fromClass(
class LanguageDetectionPlugin {
constructor(public view: EditorView) {}
update(update: any) {
if (update.docChanged) {
// 取消之前的检测
if (idleCallbackId !== null) {
cancelIdleCallbackCompat(idleCallbackId);
idleCallbackId = null;
constructor(public view: EditorView) {
worker = new LanguageDetectionWorker();
}
update(update: any) {
if (update.docChanged && !update.transactions.some((tr: any) =>
tr.annotation(languageChangeAnnotation))) {
if (idleCallbackId !== null) {
cancelIdleCallbackCompat(idleCallbackId);
}
// 安排新的检测
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
);
// 找到当前块
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;
}
}
if (!block || !block.language.auto) return;
if (block === null || blockIndex === null) {
return;
}
const blockIndex = blocks.indexOf(block);
const content = state.doc.sliceString(block.content.from, block.content.to);
// 如果不是自动检测模式,清除缓存并返回
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<void> {
if (!worker) return;
// 使用启发式检测
const detectionResult = detectLanguageHeuristic(content);
try {
const result = await worker.detectLanguage(content);
// 检查置信度和语言变化
if (detectionResult.confidence >= finalConfig.confidenceThreshold &&
detectionResult.language !== block.language.name &&
DETECTION_TO_TOKEN[detectionResult.language]) {
if (result.confidence >= finalConfig.confidenceThreshold &&
result.language !== block.language.name &&
SUPPORTED_LANGUAGES.has(result.language) &&
LANGUAGE_MAP.has(result.language)) {
// 验证内容未显著变化
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
);
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<LanguageDetectionResult> {
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<LanguageDetectionResult[]> {
const worker = new LanguageDetectionWorker();
try {
return await Promise.all(contents.map(content => worker.detectLanguage(content)));
} finally {
worker.destroy();
}
}

View File

@@ -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<string, LanguagePattern> = {
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,
/<!DOCTYPE\s+html>/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<string, number> = {};
// 计算每种语言的匹配分数
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[];
}

View File

@@ -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 { levenshteinDistance } from './levenshtein';
export type { SupportedLanguage } from '../types';

View File

@@ -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;
}
}

View File

@@ -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;