Compare commits
21 Commits
f66499467c
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ddba2dc3e1 | ||
|
|
730d453371 | ||
|
|
9edb9d6aca | ||
|
|
34eb6bea56 | ||
|
|
e5ca68423c | ||
|
|
5755b02b38 | ||
|
|
c5c72aa067 | ||
|
|
be199461c6 | ||
|
|
329352fbc1 | ||
|
|
3763e8ad74 | ||
|
|
ef8743529a | ||
|
|
99920fca56 | ||
|
|
045cb4232a | ||
|
|
b397860513 | ||
|
|
bfc3646223 | ||
| 751ccb307f | |||
|
|
9594a85dfd | ||
| 41897783c8 | |||
| 3a0d729042 | |||
| bc7f9d25ac | |||
| 21c89e3a5b |
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.exe filter=lfs diff=lfs merge=lfs -text
|
||||
19
build.sh
Executable file
19
build.sh
Executable file
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "=== Building frontend ==="
|
||||
npm run build
|
||||
|
||||
echo "=== Copying dist to server/ ==="
|
||||
rm -rf server/dist
|
||||
cp -r dist server/dist
|
||||
|
||||
echo "=== Building Go binary ==="
|
||||
cd server
|
||||
go mod init protoc-frontend-server 2>/dev/null || true
|
||||
GOOS=windows GOARCH=amd64 go build -o ../protoc-frontend.exe .
|
||||
|
||||
echo ""
|
||||
echo "=== Done! ==="
|
||||
echo "Binary: protoc-frontend.exe"
|
||||
echo "Usage: protoc-frontend.exe [-port 1111] [-api http://localhost:8000]"
|
||||
382
bun.lock
382
bun.lock
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 0,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "typescript-react-tailwind-vite",
|
||||
@@ -39,9 +40,13 @@
|
||||
"react-day-picker": "^9.8.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-grid-layout": "^1.5.2",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-rnd": "^10.5.2",
|
||||
"react-window": "^1.8.11",
|
||||
"redux": "^4.2.1",
|
||||
"rehype-raw": "^7.0.0",
|
||||
"rehype-sanitize": "^6.0.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"use-debounce": "^10.0.5",
|
||||
@@ -71,6 +76,7 @@
|
||||
"ts-prune": "^0.10.3",
|
||||
"typescript": "^4.4.4",
|
||||
"vite": "^4.3.9",
|
||||
"vitest": "^4.1.1",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -173,7 +179,7 @@
|
||||
|
||||
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
|
||||
|
||||
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.4", "", {}, "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw=="],
|
||||
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
|
||||
|
||||
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.29", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ=="],
|
||||
|
||||
@@ -185,6 +191,8 @@
|
||||
|
||||
"@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
|
||||
|
||||
"@oxc-project/types": ["@oxc-project/types@0.122.0", "", {}, "sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA=="],
|
||||
|
||||
"@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
|
||||
|
||||
"@radix-ui/number": ["@radix-ui/number@1.1.1", "", {}, "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g=="],
|
||||
@@ -271,12 +279,44 @@
|
||||
|
||||
"@remix-run/router": ["@remix-run/router@1.23.0", "", {}, "sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA=="],
|
||||
|
||||
"@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-rc.11", "", { "os": "android", "cpu": "arm64" }, "sha512-SJ+/g+xNnOh6NqYxD0V3uVN4W3VfnrGsC9/hoglicgTNfABFG9JjISvkkU0dNY84MNHLWyOgxP9v9Y9pX4S7+A=="],
|
||||
|
||||
"@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-rc.11", "", { "os": "darwin", "cpu": "arm64" }, "sha512-7WQgR8SfOPwmDZGFkThUvsmd/nwAWv91oCO4I5LS7RKrssPZmOt7jONN0cW17ydGC1n/+puol1IpoieKqQidmg=="],
|
||||
|
||||
"@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-rc.11", "", { "os": "darwin", "cpu": "x64" }, "sha512-39Ks6UvIHq4rEogIfQBoBRusj0Q0nPVWIvqmwBLaT6aqQGIakHdESBVOPRRLacy4WwUPIx4ZKzfZ9PMW+IeyUQ=="],
|
||||
|
||||
"@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-rc.11", "", { "os": "freebsd", "cpu": "x64" }, "sha512-jfsm0ZHfhiqrvWjJAmzsqiIFPz5e7mAoCOPBNTcNgkiid/LaFKiq92+0ojH+nmJmKYkre4t71BWXUZDNp7vsag=="],
|
||||
|
||||
"@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.11", "", { "os": "linux", "cpu": "arm" }, "sha512-zjQaUtSyq1nVe3nxmlSCuR96T1LPlpvmJ0SZy0WJFEsV4kFbXcq2u68L4E6O0XeFj4aex9bEauqjW8UQBeAvfQ=="],
|
||||
|
||||
"@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-rc.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-WMW1yE6IOnehTcFE9eipFkm3XN63zypWlrJQ2iF7NrQ9b2LDRjumFoOGJE8RJJTJCTBAdmLMnJ8uVitACUUo1Q=="],
|
||||
|
||||
"@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-rc.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-jfndI9tsfm4APzjNt6QdBkYwre5lRPUgHeDHoI7ydKUuJvz3lZeCfMsI56BZj+7BYqiKsJm7cfd/6KYV7ubrBg=="],
|
||||
|
||||
"@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.11", "", { "os": "linux", "cpu": "ppc64" }, "sha512-ZlFgw46NOAGMgcdvdYwAGu2Q+SLFA9LzbJLW+iyMOJyhj5wk6P3KEE9Gct4xWwSzFoPI7JCdYmYMzVtlgQ+zfw=="],
|
||||
|
||||
"@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.0.0-rc.11", "", { "os": "linux", "cpu": "s390x" }, "sha512-hIOYmuT6ofM4K04XAZd3OzMySEO4K0/nc9+jmNcxNAxRi6c5UWpqfw3KMFV4MVFWL+jQsSh+bGw2VqmaPMTLyw=="],
|
||||
|
||||
"@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-rc.11", "", { "os": "linux", "cpu": "x64" }, "sha512-qXBQQO9OvkjjQPLdUVr7Nr2t3QTZI7s4KZtfw7HzBgjbmAPSFwSv4rmET9lLSgq3rH/ndA3ngv3Qb8l2njoPNA=="],
|
||||
|
||||
"@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-rc.11", "", { "os": "linux", "cpu": "x64" }, "sha512-/tpFfoSTzUkH9LPY+cYbqZBDyyX62w5fICq9qzsHLL8uTI6BHip3Q9Uzft0wylk/i8OOwKik8OxW+QAhDmzwmg=="],
|
||||
|
||||
"@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0-rc.11", "", { "os": "none", "cpu": "arm64" }, "sha512-mcp3Rio2w72IvdZG0oQ4bM2c2oumtwHfUfKncUM6zGgz0KgPz4YmDPQfnXEiY5t3+KD/i8HG2rOB/LxdmieK2g=="],
|
||||
|
||||
"@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-rc.11", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.1.1" }, "cpu": "none" }, "sha512-LXk5Hii1Ph9asuGRjBuz8TUxdc1lWzB7nyfdoRgI0WGPZKmCxvlKk8KfYysqtr4MfGElu/f/pEQRh8fcEgkrWw=="],
|
||||
|
||||
"@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-rc.11", "", { "os": "win32", "cpu": "arm64" }, "sha512-dDwf5otnx0XgRY1yqxOC4ITizcdzS/8cQ3goOWv3jFAo4F+xQYni+hnMuO6+LssHHdJW7+OCVL3CoU4ycnh35Q=="],
|
||||
|
||||
"@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-rc.11", "", { "os": "win32", "cpu": "x64" }, "sha512-LN4/skhSggybX71ews7dAj6r2geaMJfm3kMbK2KhFMg9B10AZXnKoLCVVgzhMHL0S+aKtr4p8QbAW8k+w95bAA=="],
|
||||
|
||||
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="],
|
||||
|
||||
"@rtsao/scc": ["@rtsao/scc@1.1.0", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="],
|
||||
|
||||
"@sinclair/typebox": ["@sinclair/typebox@0.27.8", "", {}, "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA=="],
|
||||
|
||||
"@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||
|
||||
"@swc/core": ["@swc/core@1.13.3", "", { "dependencies": { "@swc/counter": "^0.1.3", "@swc/types": "^0.1.23" }, "optionalDependencies": { "@swc/core-darwin-arm64": "1.13.3", "@swc/core-darwin-x64": "1.13.3", "@swc/core-linux-arm-gnueabihf": "1.13.3", "@swc/core-linux-arm64-gnu": "1.13.3", "@swc/core-linux-arm64-musl": "1.13.3", "@swc/core-linux-x64-gnu": "1.13.3", "@swc/core-linux-x64-musl": "1.13.3", "@swc/core-win32-arm64-msvc": "1.13.3", "@swc/core-win32-ia32-msvc": "1.13.3", "@swc/core-win32-x64-msvc": "1.13.3" }, "peerDependencies": { "@swc/helpers": ">=0.5.17" }, "optionalPeers": ["@swc/helpers"] }, "sha512-ZaDETVWnm6FE0fc+c2UE8MHYVS3Fe91o5vkmGfgwGXFbxYvAjKSqxM/j4cRc9T7VZNSJjriXq58XkfCp3Y6f+w=="],
|
||||
|
||||
"@swc/core-darwin-arm64": ["@swc/core-darwin-arm64@1.13.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ux0Ws4pSpBTqbDS9GlVP354MekB1DwYlbxXU3VhnDr4GBcCOimpocx62x7cFJkSpEBF8bmX8+/TTCGKh4PbyXw=="],
|
||||
@@ -311,6 +351,18 @@
|
||||
|
||||
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ=="],
|
||||
|
||||
"@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="],
|
||||
|
||||
"@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="],
|
||||
|
||||
"@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="],
|
||||
|
||||
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
||||
|
||||
"@types/estree-jsx": ["@types/estree-jsx@1.0.5", "", { "dependencies": { "@types/estree": "*" } }, "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg=="],
|
||||
|
||||
"@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="],
|
||||
|
||||
"@types/hoist-non-react-statics": ["@types/hoist-non-react-statics@3.3.7", "", { "dependencies": { "hoist-non-react-statics": "^3.3.0" }, "peerDependencies": { "@types/react": "*" } }, "sha512-PQTyIulDkIDro8P+IHbKCsw7U2xxBYflVzW/FgWdCAePD9xGSidgA76/GeJ6lBKoblyhf9pBY763gbrN+1dI8g=="],
|
||||
|
||||
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
|
||||
@@ -321,6 +373,10 @@
|
||||
|
||||
"@types/lodash.isequal": ["@types/lodash.isequal@4.5.8", "", { "dependencies": { "@types/lodash": "*" } }, "sha512-uput6pg4E/tj2LGxCZo9+y27JNyB2OZuuI/T5F+ylVDYuqICLG2/ktjxx0v6GvVntAf8TvEzeQLcV0ffRirXuA=="],
|
||||
|
||||
"@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="],
|
||||
|
||||
"@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
|
||||
|
||||
"@types/node": ["@types/node@24.1.0", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w=="],
|
||||
|
||||
"@types/parse-json": ["@types/parse-json@4.0.2", "", {}, "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw=="],
|
||||
@@ -339,6 +395,8 @@
|
||||
|
||||
"@types/semver": ["@types/semver@7.7.0", "", {}, "sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA=="],
|
||||
|
||||
"@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
|
||||
|
||||
"@types/use-sync-external-store": ["@types/use-sync-external-store@0.0.3", "", {}, "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA=="],
|
||||
|
||||
"@types/uuid": ["@types/uuid@10.0.0", "", {}, "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ=="],
|
||||
@@ -401,6 +459,20 @@
|
||||
|
||||
"@vitejs/plugin-react-swc": ["@vitejs/plugin-react-swc@3.11.0", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-beta.27", "@swc/core": "^1.12.11" }, "peerDependencies": { "vite": "^4 || ^5 || ^6 || ^7" } }, "sha512-YTJCGFdNMHCMfjODYtxRNVAYmTWQ1Lb8PulP/2/f/oEEtglw8oKxKIZmmRkyXrVrHfsKOaVkAc3NT9/dMutO5w=="],
|
||||
|
||||
"@vitest/expect": ["@vitest/expect@4.1.1", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.1", "@vitest/utils": "4.1.1", "chai": "^6.2.2", "tinyrainbow": "^3.0.3" } }, "sha512-xAV0fqBTk44Rn6SjJReEQkHP3RrqbJo6JQ4zZ7/uVOiJZRarBtblzrOfFIZeYUrukp2YD6snZG6IBqhOoHTm+A=="],
|
||||
|
||||
"@vitest/mocker": ["@vitest/mocker@4.1.1", "", { "dependencies": { "@vitest/spy": "4.1.1", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-h3BOylsfsCLPeceuCPAAJ+BvNwSENgJa4hXoXu4im0bs9Lyp4URc4JYK4pWLZ4pG/UQn7AT92K6IByi6rE6g3A=="],
|
||||
|
||||
"@vitest/pretty-format": ["@vitest/pretty-format@4.1.1", "", { "dependencies": { "tinyrainbow": "^3.0.3" } }, "sha512-GM+TEQN5WhOygr1lp7skeVjdLPqqWMHsfzXrcHAqZJi/lIVh63H0kaRCY8MDhNWikx19zBUK8ceaLB7X5AH9NQ=="],
|
||||
|
||||
"@vitest/runner": ["@vitest/runner@4.1.1", "", { "dependencies": { "@vitest/utils": "4.1.1", "pathe": "^2.0.3" } }, "sha512-f7+FPy75vN91QGWsITueq0gedwUZy1fLtHOCMeQpjs8jTekAHeKP80zfDEnhrleviLHzVSDXIWuCIOFn3D3f8A=="],
|
||||
|
||||
"@vitest/snapshot": ["@vitest/snapshot@4.1.1", "", { "dependencies": { "@vitest/pretty-format": "4.1.1", "@vitest/utils": "4.1.1", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-kMVSgcegWV2FibXEx9p9WIKgje58lcTbXgnJixfcg15iK8nzCXhmalL0ZLtTWLW9PH1+1NEDShiFFedB3tEgWg=="],
|
||||
|
||||
"@vitest/spy": ["@vitest/spy@4.1.1", "", {}, "sha512-6Ti/KT5OVaiupdIZEuZN7l3CZcR0cxnxt70Z0//3CtwgObwA6jZhmVBA3yrXSVN3gmwjgd7oDNLlsXz526gpRA=="],
|
||||
|
||||
"@vitest/utils": ["@vitest/utils@4.1.1", "", { "dependencies": { "@vitest/pretty-format": "4.1.1", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.0.3" } }, "sha512-cNxAlaB3sHoCdL6pj6yyUXv9Gry1NHNg0kFTXdvSIZXLHsqKH7chiWOkwJ5s5+d/oMwcoG9T0bKU38JZWKusrQ=="],
|
||||
|
||||
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
|
||||
|
||||
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
|
||||
@@ -435,12 +507,16 @@
|
||||
|
||||
"arraybuffer.prototype.slice": ["arraybuffer.prototype.slice@1.0.4", "", { "dependencies": { "array-buffer-byte-length": "^1.0.1", "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "is-array-buffer": "^3.0.4" } }, "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ=="],
|
||||
|
||||
"assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="],
|
||||
|
||||
"async-function": ["async-function@1.0.0", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="],
|
||||
|
||||
"autoprefixer": ["autoprefixer@10.4.21", "", { "dependencies": { "browserslist": "^4.24.4", "caniuse-lite": "^1.0.30001702", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ=="],
|
||||
|
||||
"available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="],
|
||||
|
||||
"bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="],
|
||||
|
||||
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
|
||||
"binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="],
|
||||
@@ -463,8 +539,20 @@
|
||||
|
||||
"caniuse-lite": ["caniuse-lite@1.0.30001731", "", {}, "sha512-lDdp2/wrOmTRWuoB5DpfNkC0rJDU8DqRa6nYL6HK6sytw70QMopt/NIc/9SM7ylItlBWfACXk0tEn37UWM/+mg=="],
|
||||
|
||||
"ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="],
|
||||
|
||||
"chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="],
|
||||
|
||||
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
"character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="],
|
||||
|
||||
"character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="],
|
||||
|
||||
"character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="],
|
||||
|
||||
"character-reference-invalid": ["character-reference-invalid@2.0.1", "", {}, "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw=="],
|
||||
|
||||
"chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
|
||||
|
||||
"class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="],
|
||||
@@ -479,12 +567,16 @@
|
||||
|
||||
"colord": ["colord@2.9.3", "", {}, "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw=="],
|
||||
|
||||
"comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="],
|
||||
|
||||
"commander": ["commander@6.2.1", "", {}, "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA=="],
|
||||
|
||||
"common-tags": ["common-tags@1.8.2", "", {}, "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA=="],
|
||||
|
||||
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
|
||||
|
||||
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
|
||||
|
||||
"cosmiconfig": ["cosmiconfig@7.1.0", "", { "dependencies": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", "parse-json": "^5.0.0", "path-type": "^4.0.0", "yaml": "^1.10.0" } }, "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA=="],
|
||||
|
||||
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
|
||||
@@ -509,14 +601,22 @@
|
||||
|
||||
"debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
|
||||
|
||||
"decode-named-character-reference": ["decode-named-character-reference@1.2.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q=="],
|
||||
|
||||
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
|
||||
|
||||
"define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="],
|
||||
|
||||
"define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="],
|
||||
|
||||
"dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
|
||||
|
||||
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
||||
|
||||
"detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="],
|
||||
|
||||
"devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="],
|
||||
|
||||
"didyoumean": ["didyoumean@1.2.2", "", {}, "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw=="],
|
||||
|
||||
"dir-glob": ["dir-glob@3.0.1", "", { "dependencies": { "path-type": "^4.0.0" } }, "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA=="],
|
||||
@@ -533,6 +633,8 @@
|
||||
|
||||
"emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
|
||||
|
||||
"entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
|
||||
|
||||
"error-ex": ["error-ex@1.3.2", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g=="],
|
||||
|
||||
"es-abstract": ["es-abstract@1.24.0", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.3.0", "get-proto": "^1.0.1", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-negative-zero": "^2.0.3", "is-regex": "^1.2.1", "is-set": "^2.0.3", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.1", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.4", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.4", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "stop-iteration-iterator": "^1.1.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.19" } }, "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg=="],
|
||||
@@ -541,6 +643,8 @@
|
||||
|
||||
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
|
||||
|
||||
"es-module-lexer": ["es-module-lexer@2.0.0", "", {}, "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw=="],
|
||||
|
||||
"es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
|
||||
|
||||
"es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="],
|
||||
@@ -583,8 +687,16 @@
|
||||
|
||||
"estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
|
||||
|
||||
"estree-util-is-identifier-name": ["estree-util-is-identifier-name@3.0.0", "", {}, "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg=="],
|
||||
|
||||
"estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],
|
||||
|
||||
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
|
||||
|
||||
"expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="],
|
||||
|
||||
"extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
|
||||
|
||||
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
|
||||
|
||||
"fast-equals": ["fast-equals@4.0.3", "", {}, "sha512-G3BSX9cfKttjr+2o1O22tYMLq0DPluZnYtq1rXumE1SpL/F/SLIfHx08WYQoWSIpeMYf8sRbJ8++71+v6Pnxfg=="],
|
||||
@@ -599,7 +711,7 @@
|
||||
|
||||
"fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="],
|
||||
|
||||
"fdir": ["fdir@6.4.6", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w=="],
|
||||
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
||||
|
||||
"file-entry-cache": ["file-entry-cache@6.0.1", "", { "dependencies": { "flat-cache": "^3.0.4" } }, "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg=="],
|
||||
|
||||
@@ -669,8 +781,28 @@
|
||||
|
||||
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
|
||||
|
||||
"hast-util-from-parse5": ["hast-util-from-parse5@8.0.3", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "devlop": "^1.0.0", "hastscript": "^9.0.0", "property-information": "^7.0.0", "vfile": "^6.0.0", "vfile-location": "^5.0.0", "web-namespaces": "^2.0.0" } }, "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg=="],
|
||||
|
||||
"hast-util-parse-selector": ["hast-util-parse-selector@4.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A=="],
|
||||
|
||||
"hast-util-raw": ["hast-util-raw@9.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "@ungap/structured-clone": "^1.0.0", "hast-util-from-parse5": "^8.0.0", "hast-util-to-parse5": "^8.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "parse5": "^7.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0", "web-namespaces": "^2.0.0", "zwitch": "^2.0.0" } }, "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw=="],
|
||||
|
||||
"hast-util-sanitize": ["hast-util-sanitize@5.0.2", "", { "dependencies": { "@types/hast": "^3.0.0", "@ungap/structured-clone": "^1.0.0", "unist-util-position": "^5.0.0" } }, "sha512-3yTWghByc50aGS7JlGhk61SPenfE/p1oaFeNwkOOyrscaOkMGrcW9+Cy/QAIOBpZxP1yqDIzFMR0+Np0i0+usg=="],
|
||||
|
||||
"hast-util-to-jsx-runtime": ["hast-util-to-jsx-runtime@2.3.6", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "hast-util-whitespace": "^3.0.0", "mdast-util-mdx-expression": "^2.0.0", "mdast-util-mdx-jsx": "^3.0.0", "mdast-util-mdxjs-esm": "^2.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "style-to-js": "^1.0.0", "unist-util-position": "^5.0.0", "vfile-message": "^4.0.0" } }, "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg=="],
|
||||
|
||||
"hast-util-to-parse5": ["hast-util-to-parse5@8.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "property-information": "^6.0.0", "space-separated-tokens": "^2.0.0", "web-namespaces": "^2.0.0", "zwitch": "^2.0.0" } }, "sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw=="],
|
||||
|
||||
"hast-util-whitespace": ["hast-util-whitespace@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="],
|
||||
|
||||
"hastscript": ["hastscript@9.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-parse-selector": "^4.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0" } }, "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w=="],
|
||||
|
||||
"hoist-non-react-statics": ["hoist-non-react-statics@3.3.2", "", { "dependencies": { "react-is": "^16.7.0" } }, "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw=="],
|
||||
|
||||
"html-url-attributes": ["html-url-attributes@3.0.1", "", {}, "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ=="],
|
||||
|
||||
"html-void-elements": ["html-void-elements@3.0.0", "", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="],
|
||||
|
||||
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
||||
|
||||
"immer": ["immer@10.1.1", "", {}, "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw=="],
|
||||
@@ -685,8 +817,14 @@
|
||||
|
||||
"inherits": ["inherits@2.0.3", "", {}, "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw=="],
|
||||
|
||||
"inline-style-parser": ["inline-style-parser@0.2.4", "", {}, "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q=="],
|
||||
|
||||
"internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="],
|
||||
|
||||
"is-alphabetical": ["is-alphabetical@2.0.1", "", {}, "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ=="],
|
||||
|
||||
"is-alphanumerical": ["is-alphanumerical@2.0.1", "", { "dependencies": { "is-alphabetical": "^2.0.0", "is-decimal": "^2.0.0" } }, "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw=="],
|
||||
|
||||
"is-array-buffer": ["is-array-buffer@3.0.5", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A=="],
|
||||
|
||||
"is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="],
|
||||
@@ -709,6 +847,8 @@
|
||||
|
||||
"is-date-object": ["is-date-object@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" } }, "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg=="],
|
||||
|
||||
"is-decimal": ["is-decimal@2.0.1", "", {}, "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A=="],
|
||||
|
||||
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
|
||||
|
||||
"is-finalizationregistry": ["is-finalizationregistry@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg=="],
|
||||
@@ -719,6 +859,8 @@
|
||||
|
||||
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
|
||||
|
||||
"is-hexadecimal": ["is-hexadecimal@2.0.1", "", {}, "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg=="],
|
||||
|
||||
"is-map": ["is-map@2.0.3", "", {}, "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw=="],
|
||||
|
||||
"is-negative-zero": ["is-negative-zero@2.0.3", "", {}, "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw=="],
|
||||
@@ -729,6 +871,8 @@
|
||||
|
||||
"is-path-inside": ["is-path-inside@3.0.3", "", {}, "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ=="],
|
||||
|
||||
"is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="],
|
||||
|
||||
"is-regex": ["is-regex@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g=="],
|
||||
|
||||
"is-set": ["is-set@2.0.3", "", {}, "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg=="],
|
||||
@@ -773,6 +917,30 @@
|
||||
|
||||
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
|
||||
|
||||
"lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="],
|
||||
|
||||
"lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="],
|
||||
|
||||
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="],
|
||||
|
||||
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="],
|
||||
|
||||
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="],
|
||||
|
||||
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="],
|
||||
|
||||
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="],
|
||||
|
||||
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="],
|
||||
|
||||
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="],
|
||||
|
||||
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="],
|
||||
|
||||
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="],
|
||||
|
||||
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="],
|
||||
|
||||
"lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="],
|
||||
|
||||
"lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
|
||||
@@ -789,18 +957,110 @@
|
||||
|
||||
"loglevel-colored-level-prefix": ["loglevel-colored-level-prefix@1.0.0", "", { "dependencies": { "chalk": "^1.1.3", "loglevel": "^1.4.1" } }, "sha512-u45Wcxxc+SdAlh4yeF/uKlC1SPUPCy0gullSNKXod5I4bmifzk+Q4lSLExNEVn19tGaJipbZ4V4jbFn79/6mVA=="],
|
||||
|
||||
"longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="],
|
||||
|
||||
"loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
|
||||
|
||||
"lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
|
||||
|
||||
"lucide-react": ["lucide-react@0.525.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Tm1txJ2OkymCGkvwoHt33Y2JpN5xucVq1slHcgE6Lk0WjDfjgKWor5CdVER8U6DvcfMwh4M8XxmpTiyzfmfDYQ=="],
|
||||
|
||||
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
|
||||
|
||||
"markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="],
|
||||
|
||||
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
|
||||
|
||||
"mdast-util-find-and-replace": ["mdast-util-find-and-replace@3.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "escape-string-regexp": "^5.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg=="],
|
||||
|
||||
"mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA=="],
|
||||
|
||||
"mdast-util-gfm": ["mdast-util-gfm@3.1.0", "", { "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-gfm-autolink-literal": "^2.0.0", "mdast-util-gfm-footnote": "^2.0.0", "mdast-util-gfm-strikethrough": "^2.0.0", "mdast-util-gfm-table": "^2.0.0", "mdast-util-gfm-task-list-item": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ=="],
|
||||
|
||||
"mdast-util-gfm-autolink-literal": ["mdast-util-gfm-autolink-literal@2.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "ccount": "^2.0.0", "devlop": "^1.0.0", "mdast-util-find-and-replace": "^3.0.0", "micromark-util-character": "^2.0.0" } }, "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ=="],
|
||||
|
||||
"mdast-util-gfm-footnote": ["mdast-util-gfm-footnote@2.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0" } }, "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ=="],
|
||||
|
||||
"mdast-util-gfm-strikethrough": ["mdast-util-gfm-strikethrough@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg=="],
|
||||
|
||||
"mdast-util-gfm-table": ["mdast-util-gfm-table@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "markdown-table": "^3.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg=="],
|
||||
|
||||
"mdast-util-gfm-task-list-item": ["mdast-util-gfm-task-list-item@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ=="],
|
||||
|
||||
"mdast-util-mdx-expression": ["mdast-util-mdx-expression@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ=="],
|
||||
|
||||
"mdast-util-mdx-jsx": ["mdast-util-mdx-jsx@3.2.0", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "parse-entities": "^4.0.0", "stringify-entities": "^4.0.0", "unist-util-stringify-position": "^4.0.0", "vfile-message": "^4.0.0" } }, "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q=="],
|
||||
|
||||
"mdast-util-mdxjs-esm": ["mdast-util-mdxjs-esm@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg=="],
|
||||
|
||||
"mdast-util-phrasing": ["mdast-util-phrasing@4.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "unist-util-is": "^6.0.0" } }, "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w=="],
|
||||
|
||||
"mdast-util-to-hast": ["mdast-util-to-hast@13.2.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@ungap/structured-clone": "^1.0.0", "devlop": "^1.0.0", "micromark-util-sanitize-uri": "^2.0.0", "trim-lines": "^3.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA=="],
|
||||
|
||||
"mdast-util-to-markdown": ["mdast-util-to-markdown@2.1.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "longest-streak": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-string": "^4.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA=="],
|
||||
|
||||
"mdast-util-to-string": ["mdast-util-to-string@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0" } }, "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg=="],
|
||||
|
||||
"memoize-one": ["memoize-one@5.2.1", "", {}, "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q=="],
|
||||
|
||||
"merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
|
||||
|
||||
"micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="],
|
||||
|
||||
"micromark-core-commonmark": ["micromark-core-commonmark@2.0.3", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="],
|
||||
|
||||
"micromark-extension-gfm": ["micromark-extension-gfm@3.0.0", "", { "dependencies": { "micromark-extension-gfm-autolink-literal": "^2.0.0", "micromark-extension-gfm-footnote": "^2.0.0", "micromark-extension-gfm-strikethrough": "^2.0.0", "micromark-extension-gfm-table": "^2.0.0", "micromark-extension-gfm-tagfilter": "^2.0.0", "micromark-extension-gfm-task-list-item": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w=="],
|
||||
|
||||
"micromark-extension-gfm-autolink-literal": ["micromark-extension-gfm-autolink-literal@2.1.0", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw=="],
|
||||
|
||||
"micromark-extension-gfm-footnote": ["micromark-extension-gfm-footnote@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw=="],
|
||||
|
||||
"micromark-extension-gfm-strikethrough": ["micromark-extension-gfm-strikethrough@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw=="],
|
||||
|
||||
"micromark-extension-gfm-table": ["micromark-extension-gfm-table@2.1.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg=="],
|
||||
|
||||
"micromark-extension-gfm-tagfilter": ["micromark-extension-gfm-tagfilter@2.0.0", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg=="],
|
||||
|
||||
"micromark-extension-gfm-task-list-item": ["micromark-extension-gfm-task-list-item@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw=="],
|
||||
|
||||
"micromark-factory-destination": ["micromark-factory-destination@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="],
|
||||
|
||||
"micromark-factory-label": ["micromark-factory-label@2.0.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg=="],
|
||||
|
||||
"micromark-factory-space": ["micromark-factory-space@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg=="],
|
||||
|
||||
"micromark-factory-title": ["micromark-factory-title@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw=="],
|
||||
|
||||
"micromark-factory-whitespace": ["micromark-factory-whitespace@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ=="],
|
||||
|
||||
"micromark-util-character": ["micromark-util-character@2.1.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="],
|
||||
|
||||
"micromark-util-chunked": ["micromark-util-chunked@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="],
|
||||
|
||||
"micromark-util-classify-character": ["micromark-util-classify-character@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q=="],
|
||||
|
||||
"micromark-util-combine-extensions": ["micromark-util-combine-extensions@2.0.1", "", { "dependencies": { "micromark-util-chunked": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg=="],
|
||||
|
||||
"micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@2.0.2", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw=="],
|
||||
|
||||
"micromark-util-decode-string": ["micromark-util-decode-string@2.0.1", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ=="],
|
||||
|
||||
"micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="],
|
||||
|
||||
"micromark-util-html-tag-name": ["micromark-util-html-tag-name@2.0.1", "", {}, "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="],
|
||||
|
||||
"micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q=="],
|
||||
|
||||
"micromark-util-resolve-all": ["micromark-util-resolve-all@2.0.1", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg=="],
|
||||
|
||||
"micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ=="],
|
||||
|
||||
"micromark-util-subtokenize": ["micromark-util-subtokenize@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA=="],
|
||||
|
||||
"micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="],
|
||||
|
||||
"micromark-util-types": ["micromark-util-types@2.0.2", "", {}, "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="],
|
||||
|
||||
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
|
||||
|
||||
"minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
|
||||
@@ -851,6 +1111,8 @@
|
||||
|
||||
"object.values": ["object.values@1.2.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA=="],
|
||||
|
||||
"obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="],
|
||||
|
||||
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
|
||||
|
||||
"optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
|
||||
@@ -865,8 +1127,12 @@
|
||||
|
||||
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
|
||||
|
||||
"parse-entities": ["parse-entities@4.0.2", "", { "dependencies": { "@types/unist": "^2.0.0", "character-entities-legacy": "^3.0.0", "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0", "is-hexadecimal": "^2.0.0" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="],
|
||||
|
||||
"parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="],
|
||||
|
||||
"parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="],
|
||||
|
||||
"path": ["path@0.12.7", "", { "dependencies": { "process": "^0.11.1", "util": "^0.10.3" } }, "sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q=="],
|
||||
|
||||
"path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="],
|
||||
@@ -883,9 +1149,11 @@
|
||||
|
||||
"path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="],
|
||||
|
||||
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
||||
|
||||
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||
|
||||
"picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
|
||||
|
||||
"pify": ["pify@2.3.0", "", {}, "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="],
|
||||
|
||||
@@ -921,6 +1189,8 @@
|
||||
|
||||
"prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
|
||||
|
||||
"property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="],
|
||||
|
||||
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
||||
|
||||
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
|
||||
@@ -941,6 +1211,8 @@
|
||||
|
||||
"react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="],
|
||||
|
||||
"react-markdown": ["react-markdown@10.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "html-url-attributes": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "unified": "^11.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" }, "peerDependencies": { "@types/react": ">=18", "react": ">=18" } }, "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ=="],
|
||||
|
||||
"react-redux": ["react-redux@8.1.3", "", { "dependencies": { "@babel/runtime": "^7.12.1", "@types/hoist-non-react-statics": "^3.3.1", "@types/use-sync-external-store": "^0.0.3", "hoist-non-react-statics": "^3.3.2", "react-is": "^18.0.0", "use-sync-external-store": "^1.0.0" }, "peerDependencies": { "@types/react": "^16.8 || ^17.0 || ^18.0", "@types/react-dom": "^16.8 || ^17.0 || ^18.0", "react": "^16.8 || ^17.0 || ^18.0", "react-dom": "^16.8 || ^17.0 || ^18.0", "react-native": ">=0.59", "redux": "^4 || ^5.0.0-beta.0" }, "optionalPeers": ["@types/react", "@types/react-dom", "react-dom", "react-native", "redux"] }, "sha512-n0ZrutD7DaX/j9VscF+uTALI3oUPa/pO4Z3soOBIjuRn/FzVu6aehhysxZCLi6y7duMf52WNZGMl7CtuK5EnRw=="],
|
||||
|
||||
"react-remove-scroll": ["react-remove-scroll@2.7.1", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA=="],
|
||||
@@ -971,6 +1243,18 @@
|
||||
|
||||
"regexp.prototype.flags": ["regexp.prototype.flags@1.5.4", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", "get-proto": "^1.0.1", "gopd": "^1.2.0", "set-function-name": "^2.0.2" } }, "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA=="],
|
||||
|
||||
"rehype-raw": ["rehype-raw@7.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-raw": "^9.0.0", "vfile": "^6.0.0" } }, "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww=="],
|
||||
|
||||
"rehype-sanitize": ["rehype-sanitize@6.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-sanitize": "^5.0.0" } }, "sha512-CsnhKNsyI8Tub6L4sm5ZFsme4puGfc6pYylvXo1AeqaGbjOYyzNv3qZPwvs0oMJ39eryyeOdmxwUIo94IpEhqg=="],
|
||||
|
||||
"remark-gfm": ["remark-gfm@4.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-gfm": "^3.0.0", "micromark-extension-gfm": "^3.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg=="],
|
||||
|
||||
"remark-parse": ["remark-parse@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "micromark-util-types": "^2.0.0", "unified": "^11.0.0" } }, "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA=="],
|
||||
|
||||
"remark-rehype": ["remark-rehype@11.1.2", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "mdast-util-to-hast": "^13.0.0", "unified": "^11.0.0", "vfile": "^6.0.0" } }, "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw=="],
|
||||
|
||||
"remark-stringify": ["remark-stringify@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-to-markdown": "^2.0.0", "unified": "^11.0.0" } }, "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw=="],
|
||||
|
||||
"require-relative": ["require-relative@0.8.7", "", {}, "sha512-AKGr4qvHiryxRb19m3PsLRGuKVAbJLUD7E6eOaHkfKhwc+vSgVOCY5xNvm9EkolBKTOf0GrQAZKLimOCz81Khg=="],
|
||||
|
||||
"reselect": ["reselect@4.1.8", "", {}, "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ=="],
|
||||
@@ -987,6 +1271,8 @@
|
||||
|
||||
"rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="],
|
||||
|
||||
"rolldown": ["rolldown@1.0.0-rc.11", "", { "dependencies": { "@oxc-project/types": "=0.122.0", "@rolldown/pluginutils": "1.0.0-rc.11" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-rc.11", "@rolldown/binding-darwin-arm64": "1.0.0-rc.11", "@rolldown/binding-darwin-x64": "1.0.0-rc.11", "@rolldown/binding-freebsd-x64": "1.0.0-rc.11", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.11", "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.11", "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.11", "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.11", "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.11", "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.11", "@rolldown/binding-linux-x64-musl": "1.0.0-rc.11", "@rolldown/binding-openharmony-arm64": "1.0.0-rc.11", "@rolldown/binding-wasm32-wasi": "1.0.0-rc.11", "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.11", "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.11" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-NRjoKMusSjfRbSYiH3VSumlkgFe7kYAa3pzVOsVYVFY3zb5d7nS+a3KGQ7hJKXuYWbzJKPVQ9Wxq2UvyK+ENpw=="],
|
||||
|
||||
"rollup": ["rollup@3.29.5", "", { "optionalDependencies": { "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w=="],
|
||||
|
||||
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
|
||||
@@ -1019,14 +1305,22 @@
|
||||
|
||||
"side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
|
||||
|
||||
"siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="],
|
||||
|
||||
"signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
|
||||
|
||||
"slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="],
|
||||
|
||||
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||
|
||||
"space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="],
|
||||
|
||||
"stable-hash-x": ["stable-hash-x@0.2.0", "", {}, "sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ=="],
|
||||
|
||||
"stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="],
|
||||
|
||||
"std-env": ["std-env@4.0.0", "", {}, "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ=="],
|
||||
|
||||
"stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="],
|
||||
|
||||
"string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
|
||||
@@ -1039,6 +1333,8 @@
|
||||
|
||||
"string.prototype.trimstart": ["string.prototype.trimstart@1.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg=="],
|
||||
|
||||
"stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="],
|
||||
|
||||
"strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
@@ -1047,6 +1343,10 @@
|
||||
|
||||
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
|
||||
|
||||
"style-to-js": ["style-to-js@1.1.18", "", { "dependencies": { "style-to-object": "1.0.11" } }, "sha512-JFPn62D4kJaPTnhFUI244MThx+FEGbi+9dw1b9yBBQ+1CZpV7QAT8kUtJ7b7EUNdHajjF/0x8fT+16oLJoojLg=="],
|
||||
|
||||
"style-to-object": ["style-to-object@1.0.11", "", { "dependencies": { "inline-style-parser": "0.2.4" } }, "sha512-5A560JmXr7wDyGLK12Nq/EYS38VkGlglVzkis1JEdbGWSnbQIEhZzTJhzURXN5/8WwwFCs/f/VVcmkTppbXLow=="],
|
||||
|
||||
"sucrase": ["sucrase@3.35.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "glob": "^10.3.10", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA=="],
|
||||
|
||||
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
||||
@@ -1065,10 +1365,20 @@
|
||||
|
||||
"thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="],
|
||||
|
||||
"tinyglobby": ["tinyglobby@0.2.14", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ=="],
|
||||
"tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="],
|
||||
|
||||
"tinyexec": ["tinyexec@1.0.4", "", {}, "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw=="],
|
||||
|
||||
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
|
||||
|
||||
"tinyrainbow": ["tinyrainbow@3.1.0", "", {}, "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw=="],
|
||||
|
||||
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
|
||||
|
||||
"trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="],
|
||||
|
||||
"trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="],
|
||||
|
||||
"true-myth": ["true-myth@4.1.1", "", {}, "sha512-rqy30BSpxPznbbTcAcci90oZ1YR4DqvKcNXNerG5gQBU2v4jk0cygheiul5J6ExIMrgDVuanv/MkGfqZbKrNNg=="],
|
||||
|
||||
"ts-api-utils": ["ts-api-utils@1.4.3", "", { "peerDependencies": { "typescript": ">=4.2.0" } }, "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw=="],
|
||||
@@ -1103,6 +1413,18 @@
|
||||
|
||||
"undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="],
|
||||
|
||||
"unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="],
|
||||
|
||||
"unist-util-is": ["unist-util-is@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g=="],
|
||||
|
||||
"unist-util-position": ["unist-util-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA=="],
|
||||
|
||||
"unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="],
|
||||
|
||||
"unist-util-visit": ["unist-util-visit@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg=="],
|
||||
|
||||
"unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="],
|
||||
|
||||
"unrs-resolver": ["unrs-resolver@1.11.1", "", { "dependencies": { "napi-postinstall": "^0.3.0" }, "optionalDependencies": { "@unrs/resolver-binding-android-arm-eabi": "1.11.1", "@unrs/resolver-binding-android-arm64": "1.11.1", "@unrs/resolver-binding-darwin-arm64": "1.11.1", "@unrs/resolver-binding-darwin-x64": "1.11.1", "@unrs/resolver-binding-freebsd-x64": "1.11.1", "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", "@unrs/resolver-binding-linux-x64-musl": "1.11.1", "@unrs/resolver-binding-wasm32-wasi": "1.11.1", "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" } }, "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg=="],
|
||||
|
||||
"update-browserslist-db": ["update-browserslist-db@1.1.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw=="],
|
||||
@@ -1123,10 +1445,20 @@
|
||||
|
||||
"uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="],
|
||||
|
||||
"vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="],
|
||||
|
||||
"vfile-location": ["vfile-location@5.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile": "^6.0.0" } }, "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg=="],
|
||||
|
||||
"vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="],
|
||||
|
||||
"vite": ["vite@4.5.14", "", { "dependencies": { "esbuild": "^0.18.10", "postcss": "^8.4.27", "rollup": "^3.27.1" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@types/node": ">= 14", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" }, "optionalPeers": ["@types/node", "less", "lightningcss", "sass", "stylus", "sugarss", "terser"], "bin": { "vite": "bin/vite.js" } }, "sha512-+v57oAaoYNnO3hIu5Z/tJRZjq5aHM2zDve9YZ8HngVHbhk66RStobhb1sqPMIPEleV6cNKYK4eGrAbE9Ulbl2g=="],
|
||||
|
||||
"vitest": ["vitest@4.1.1", "", { "dependencies": { "@vitest/expect": "4.1.1", "@vitest/mocker": "4.1.1", "@vitest/pretty-format": "4.1.1", "@vitest/runner": "4.1.1", "@vitest/snapshot": "4.1.1", "@vitest/spy": "4.1.1", "@vitest/utils": "4.1.1", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.0.3", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.1.1", "@vitest/browser-preview": "4.1.1", "@vitest/browser-webdriverio": "4.1.1", "@vitest/ui": "4.1.1", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-yF+o4POL41rpAzj5KVILUxm1GCjKnELvaqmU9TLLUbMfDzuN0UpUR9uaDs+mCtjPe+uYPksXDRLQGGPvj1cTmA=="],
|
||||
|
||||
"vue-eslint-parser": ["vue-eslint-parser@9.4.3", "", { "dependencies": { "debug": "^4.3.4", "eslint-scope": "^7.1.1", "eslint-visitor-keys": "^3.3.0", "espree": "^9.3.1", "esquery": "^1.4.0", "lodash": "^4.17.21", "semver": "^7.3.6" }, "peerDependencies": { "eslint": ">=6.0.0" } }, "sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg=="],
|
||||
|
||||
"web-namespaces": ["web-namespaces@2.0.1", "", {}, "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="],
|
||||
|
||||
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||
|
||||
"which-boxed-primitive": ["which-boxed-primitive@1.1.1", "", { "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" } }, "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA=="],
|
||||
@@ -1137,6 +1469,8 @@
|
||||
|
||||
"which-typed-array": ["which-typed-array@1.1.19", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw=="],
|
||||
|
||||
"why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="],
|
||||
|
||||
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
|
||||
|
||||
"wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
|
||||
@@ -1149,18 +1483,30 @@
|
||||
|
||||
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
||||
|
||||
"zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
|
||||
|
||||
"@isaacs/cliui/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="],
|
||||
|
||||
"@jridgewell/gen-mapping/@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.4", "", {}, "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw=="],
|
||||
|
||||
"@jridgewell/trace-mapping/@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.4", "", {}, "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw=="],
|
||||
|
||||
"@reduxjs/toolkit/immer": ["immer@9.0.21", "", {}, "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA=="],
|
||||
|
||||
"@rolldown/binding-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" } }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="],
|
||||
|
||||
"@typescript-eslint/utils/eslint-scope": ["eslint-scope@5.1.1", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" } }, "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw=="],
|
||||
|
||||
"anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||
|
||||
"chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
||||
|
||||
"cosmiconfig/yaml": ["yaml@1.10.2", "", {}, "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="],
|
||||
|
||||
"eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
|
||||
|
||||
"eslint-import-resolver-typescript/tinyglobby": ["tinyglobby@0.2.14", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ=="],
|
||||
|
||||
"eslint-module-utils/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
|
||||
|
||||
"eslint-plugin-import/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
|
||||
@@ -1175,10 +1521,18 @@
|
||||
|
||||
"has-ansi/ansi-regex": ["ansi-regex@2.1.1", "", {}, "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA=="],
|
||||
|
||||
"hast-util-to-parse5/property-information": ["property-information@6.5.0", "", {}, "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig=="],
|
||||
|
||||
"hoist-non-react-statics/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
|
||||
|
||||
"loglevel-colored-level-prefix/chalk": ["chalk@1.1.3", "", { "dependencies": { "ansi-styles": "^2.2.1", "escape-string-regexp": "^1.0.2", "has-ansi": "^2.0.0", "strip-ansi": "^3.0.0", "supports-color": "^2.0.0" } }, "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A=="],
|
||||
|
||||
"mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
|
||||
|
||||
"micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||
|
||||
"parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="],
|
||||
|
||||
"prettier-eslint/@typescript-eslint/parser": ["@typescript-eslint/parser@6.21.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "6.21.0", "@typescript-eslint/types": "6.21.0", "@typescript-eslint/typescript-estree": "6.21.0", "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^7.0.0 || ^8.0.0" } }, "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ=="],
|
||||
|
||||
"pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="],
|
||||
@@ -1189,20 +1543,24 @@
|
||||
|
||||
"react-rnd/tslib": ["tslib@2.6.2", "", {}, "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="],
|
||||
|
||||
"readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||
|
||||
"rimraf/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
|
||||
|
||||
"rolldown/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.11", "", {}, "sha512-xQO9vbwBecJRv9EUcQ/y0dzSTJgA7Q6UVN7xp6B81+tBGSLVAK03yJ9NkJaUA7JFD91kbjxRSC/mDnmvXzbHoQ=="],
|
||||
|
||||
"string-width/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="],
|
||||
|
||||
"string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
|
||||
"sucrase/commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="],
|
||||
|
||||
"tinyglobby/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
|
||||
|
||||
"tsconfig-paths/json5": ["json5@1.0.2", "", { "dependencies": { "minimist": "^1.2.0" }, "bin": { "json5": "lib/cli.js" } }, "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA=="],
|
||||
|
||||
"tsutils/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="],
|
||||
|
||||
"vitest/vite": ["vite@8.0.2", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.3", "postcss": "^8.5.8", "rolldown": "1.0.0-rc.11", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", "esbuild": "^0.27.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-1gFhNi+bHhRE/qKZOJXACm6tX4bA3Isy9KuKF15AgSRuRazNBOJfdDemPBU16/mpMxApDPrWvZ08DcLPEoRnuA=="],
|
||||
|
||||
"wrap-ansi/ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="],
|
||||
|
||||
"wrap-ansi/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="],
|
||||
@@ -1211,8 +1569,16 @@
|
||||
|
||||
"@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
|
||||
|
||||
"@rolldown/binding-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/core": ["@emnapi/core@1.9.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" } }, "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA=="],
|
||||
|
||||
"@rolldown/binding-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/runtime": ["@emnapi/runtime@1.9.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA=="],
|
||||
|
||||
"@rolldown/binding-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
|
||||
|
||||
"@typescript-eslint/utils/eslint-scope/estraverse": ["estraverse@4.3.0", "", {}, "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw=="],
|
||||
|
||||
"eslint-import-resolver-typescript/tinyglobby/fdir": ["fdir@6.4.6", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w=="],
|
||||
|
||||
"glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
|
||||
|
||||
"loglevel-colored-level-prefix/chalk/ansi-styles": ["ansi-styles@2.2.1", "", {}, "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA=="],
|
||||
@@ -1233,10 +1599,14 @@
|
||||
|
||||
"string-width/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
|
||||
|
||||
"vitest/vite/postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="],
|
||||
|
||||
"wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
|
||||
"wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
|
||||
|
||||
"@rolldown/binding-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg=="],
|
||||
|
||||
"loglevel-colored-level-prefix/chalk/strip-ansi/ansi-regex": ["ansi-regex@2.1.1", "", {}, "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA=="],
|
||||
|
||||
"prettier-eslint/@typescript-eslint/parser/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.3", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg=="],
|
||||
|
||||
11
package.json
11
package.json
@@ -8,7 +8,9 @@
|
||||
"build": "tsc && vite build",
|
||||
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||
"preview": "vite preview",
|
||||
"format": "prettier --write ."
|
||||
"format": "prettier --write .",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
@@ -46,9 +48,13 @@
|
||||
"react-day-picker": "^9.8.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-grid-layout": "^1.5.2",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-rnd": "^10.5.2",
|
||||
"react-window": "^1.8.11",
|
||||
"redux": "^4.2.1",
|
||||
"rehype-raw": "^7.0.0",
|
||||
"rehype-sanitize": "^6.0.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"use-debounce": "^10.0.5",
|
||||
@@ -77,6 +83,7 @@
|
||||
"tailwindcss": "^3.3.2",
|
||||
"ts-prune": "^0.10.3",
|
||||
"typescript": "^4.4.4",
|
||||
"vite": "^4.3.9"
|
||||
"vite": "^4.3.9",
|
||||
"vitest": "^4.1.1"
|
||||
}
|
||||
}
|
||||
BIN
protoc-frontend.exe
LFS
Executable file
BIN
protoc-frontend.exe
LFS
Executable file
Binary file not shown.
17
server/go.mod
Normal file
17
server/go.mod
Normal file
@@ -0,0 +1,17 @@
|
||||
module protoc-frontend-server
|
||||
|
||||
go 1.25.5
|
||||
|
||||
require (
|
||||
github.com/go-chi/chi/v5 v5.2.5 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/richardlehane/mscfb v1.0.6 // indirect
|
||||
github.com/richardlehane/msoleps v1.0.6 // indirect
|
||||
github.com/tiendc/go-deepcopy v1.7.2 // indirect
|
||||
github.com/xuri/efp v0.0.1 // indirect
|
||||
github.com/xuri/excelize/v2 v2.10.1 // indirect
|
||||
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 // indirect
|
||||
golang.org/x/crypto v0.48.0 // indirect
|
||||
golang.org/x/net v0.50.0 // indirect
|
||||
golang.org/x/text v0.34.0 // indirect
|
||||
)
|
||||
22
server/go.sum
Normal file
22
server/go.sum
Normal file
@@ -0,0 +1,22 @@
|
||||
github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug=
|
||||
github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/richardlehane/mscfb v1.0.6 h1:eN3bvvZCp00bs7Zf52bxNwAx5lJDBK1tCuH19qq5aC8=
|
||||
github.com/richardlehane/mscfb v1.0.6/go.mod h1:pe0+IUIc0AHh0+teNzBlJCtSyZdFOGgV4ZK9bsoV+Jo=
|
||||
github.com/richardlehane/msoleps v1.0.6 h1:9BvkpjvD+iUBalUY4esMwv6uBkfOip/Lzvd93jvR9gg=
|
||||
github.com/richardlehane/msoleps v1.0.6/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
|
||||
github.com/tiendc/go-deepcopy v1.7.2 h1:Ut2yYR7W9tWjTQitganoIue4UGxZwCcJy3orjrrIj44=
|
||||
github.com/tiendc/go-deepcopy v1.7.2/go.mod h1:4bKjNC2r7boYOkD2IOuZpYjmlDdzjbpTRyCx+goBCJQ=
|
||||
github.com/xuri/efp v0.0.1 h1:fws5Rv3myXyYni8uwj2qKjVaRP30PdjeYe2Y6FDsCL8=
|
||||
github.com/xuri/efp v0.0.1/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
|
||||
github.com/xuri/excelize/v2 v2.10.1 h1:V62UlqopMqha3kOpnlHy2CcRVw1V8E63jFoWUmMzxN0=
|
||||
github.com/xuri/excelize/v2 v2.10.1/go.mod h1:iG5tARpgaEeIhTqt3/fgXCGoBRt4hNXgCp3tfXKoOIc=
|
||||
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 h1:+C0TIdyyYmzadGaL/HBLbf3WdLgC29pgyhTjAT/0nuE=
|
||||
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
|
||||
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||
95
server/main.go
Normal file
95
server/main.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
//go:embed dist/*
|
||||
var distFS embed.FS
|
||||
|
||||
func main() {
|
||||
port := flag.Int("port", 1111, "port to listen on")
|
||||
apiTarget := flag.String("api", "http://localhost:8000", "backend API URL to proxy /api requests")
|
||||
flag.Parse()
|
||||
|
||||
// Proxy for /api -> backend
|
||||
backendURL, err := url.Parse(*apiTarget)
|
||||
if err != nil {
|
||||
log.Fatalf("invalid api url: %v", err)
|
||||
}
|
||||
|
||||
proxy := httputil.NewSingleHostReverseProxy(backendURL)
|
||||
originalDirector := proxy.Director
|
||||
proxy.Director = func(r *http.Request) {
|
||||
originalDirector(r)
|
||||
r.URL.Path = strings.TrimPrefix(r.URL.Path, "/api")
|
||||
if r.URL.Path == "" {
|
||||
r.URL.Path = "/"
|
||||
}
|
||||
r.Host = backendURL.Host
|
||||
}
|
||||
|
||||
// Static files from embedded dist/
|
||||
distSub, err := fs.Sub(distFS, "dist")
|
||||
if err != nil {
|
||||
log.Fatalf("failed to open embedded dist: %v", err)
|
||||
}
|
||||
fileServer := http.FileServer(http.FS(distSub))
|
||||
|
||||
r := chi.NewRouter()
|
||||
|
||||
// API routes
|
||||
r.Route("/api", func(api chi.Router) {
|
||||
// Protocol generation handled locally (xlsx fill via excelize)
|
||||
api.Post("/protocols/", protocolHandler(*apiTarget))
|
||||
|
||||
// All other /api/* requests proxied to Python backend
|
||||
api.HandleFunc("/*", func(w http.ResponseWriter, r *http.Request) {
|
||||
proxy.ServeHTTP(w, r)
|
||||
})
|
||||
})
|
||||
|
||||
// SPA: serve static files, fallback to index.html
|
||||
r.Get("/*", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
|
||||
path := strings.TrimPrefix(r.URL.Path, "/")
|
||||
if path == "" {
|
||||
path = "index.html"
|
||||
}
|
||||
|
||||
if f, err := distSub.Open(path); err == nil {
|
||||
f.Close()
|
||||
fileServer.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
indexFile, err := fs.ReadFile(distSub, "index.html")
|
||||
if err != nil {
|
||||
http.Error(w, "index.html not found", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
w.Write(indexFile)
|
||||
})
|
||||
|
||||
addr := fmt.Sprintf(":%d", *port)
|
||||
log.Printf("Serving on http://localhost%s", addr)
|
||||
log.Printf("API proxy -> %s", *apiTarget)
|
||||
|
||||
if err := http.ListenAndServe(addr, r); err != nil {
|
||||
log.Fatal(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
107
server/protocol.go
Normal file
107
server/protocol.go
Normal file
@@ -0,0 +1,107 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"github.com/google/uuid"
|
||||
"github.com/xuri/excelize/v2"
|
||||
)
|
||||
|
||||
type CreateProtocolRequest struct {
|
||||
TemplateID string `json:"template_id"`
|
||||
FileID string `json:"file_id"`
|
||||
CellsToUpdate map[string]any `json:"cells_to_update"`
|
||||
PrintArea string `json:"print_area"`
|
||||
}
|
||||
|
||||
func protocolHandler(backendURL string) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
// Parse request body
|
||||
var req CreateProtocolRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, "invalid request body: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Fetch file binary from Python backend
|
||||
fileURL := fmt.Sprintf("%s/files/%s/content", backendURL, req.FileID)
|
||||
resp, err := http.Get(fileURL)
|
||||
if err != nil {
|
||||
http.Error(w, "failed to fetch file: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
http.Error(w, fmt.Sprintf("backend returned %d when fetching file", resp.StatusCode), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
fileBytes, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
http.Error(w, "failed to read file content: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Open xlsx with excelize
|
||||
f, err := excelize.OpenReader(bytes.NewReader(fileBytes))
|
||||
if err != nil {
|
||||
http.Error(w, "failed to open xlsx: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
// Get the first sheet
|
||||
sheetName := f.GetSheetName(0)
|
||||
if sheetName == "" {
|
||||
http.Error(w, "no sheets found in xlsx", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Fill cells
|
||||
for cellAddr, value := range req.CellsToUpdate {
|
||||
if err := setCellValue(f, sheetName, cellAddr, value); err != nil {
|
||||
http.Error(w, fmt.Sprintf("failed to set cell %s: %v", cellAddr, err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Write result to buffer
|
||||
var buf bytes.Buffer
|
||||
if err := f.Write(&buf); err != nil {
|
||||
http.Error(w, "failed to write xlsx: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
protocolID := uuid.New().String()
|
||||
|
||||
w.Header().Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="protocol_%s.xlsx"`, protocolID))
|
||||
w.Header().Set("Content-Length", strconv.Itoa(buf.Len()))
|
||||
w.Write(buf.Bytes())
|
||||
}
|
||||
}
|
||||
|
||||
func setCellValue(f *excelize.File, sheet, cell string, value any) error {
|
||||
switch v := value.(type) {
|
||||
case float64:
|
||||
// JSON numbers are float64; check if it's actually an integer
|
||||
if v == float64(int64(v)) {
|
||||
return f.SetCellInt(sheet, cell, int64(v))
|
||||
}
|
||||
return f.SetCellFloat(sheet, cell, v, -1, 64)
|
||||
case string:
|
||||
return f.SetCellStr(sheet, cell, v)
|
||||
case bool:
|
||||
return f.SetCellBool(sheet, cell, v)
|
||||
case nil:
|
||||
return f.SetCellStr(sheet, cell, "")
|
||||
default:
|
||||
return f.SetCellStr(sheet, cell, fmt.Sprintf("%v", v))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
} from '@/lib/hooks/useToast'
|
||||
import {
|
||||
ElementsCreation,
|
||||
FunctionsWikiPage,
|
||||
ProtocolCreation,
|
||||
StandardsManagementPage,
|
||||
TemplateEditPage,
|
||||
@@ -59,6 +60,10 @@ const App: FC = () => {
|
||||
path="standards-management"
|
||||
element={<StandardsManagementPage />}
|
||||
/>
|
||||
<Route
|
||||
path="functions-wiki"
|
||||
element={<FunctionsWikiPage />}
|
||||
/>
|
||||
</Route>
|
||||
</Routes>
|
||||
</TemplateProvider>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { MergedCell } from '@/type/template'
|
||||
import { CSSProperties } from 'react'
|
||||
import { VariableSizeGrid } from 'react-window'
|
||||
|
||||
export const ROW_COUNT = 120
|
||||
export const ROW_COUNT = 150
|
||||
export const COL_COUNT = 26
|
||||
export const ROW_HEIGHT = 28
|
||||
|
||||
|
||||
375
src/entity/element/model/implementations/NoteElement.tsx
Normal file
375
src/entity/element/model/implementations/NoteElement.tsx
Normal file
@@ -0,0 +1,375 @@
|
||||
import { ElementDefinition } from '@/entity/element/model/interface'
|
||||
import { CellsBadge } from '@/entity/element/ui/cellsBadge'
|
||||
import {
|
||||
Bold,
|
||||
Code,
|
||||
FileText,
|
||||
Heading1,
|
||||
Heading2,
|
||||
Heading3,
|
||||
Italic,
|
||||
Link,
|
||||
List,
|
||||
ListOrdered,
|
||||
Quote,
|
||||
Strikethrough,
|
||||
Table,
|
||||
} from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import ReactMarkdown, { Components } from 'react-markdown'
|
||||
import rehypeRaw from 'rehype-raw'
|
||||
import rehypeSanitize from 'rehype-sanitize'
|
||||
import remarkGfm from 'remark-gfm'
|
||||
import { Button } from 'shared/ui/button'
|
||||
import { Label } from 'shared/ui/label'
|
||||
import { Textarea } from 'shared/ui/textarea'
|
||||
|
||||
interface NoteConfig {
|
||||
content?: string // Основное содержимое markdown
|
||||
targetCells: any[]
|
||||
name?: string
|
||||
id?: string
|
||||
}
|
||||
|
||||
// Компонент панели инструментов
|
||||
const MarkdownToolbar = ({
|
||||
onInsert,
|
||||
disabled = false,
|
||||
}: {
|
||||
onInsert: (text: string) => void
|
||||
disabled?: boolean
|
||||
}) => {
|
||||
const tools = [
|
||||
{ icon: Bold, label: 'Жирный', syntax: '**текст**' },
|
||||
{ icon: Italic, label: 'Курсив', syntax: '*текст*' },
|
||||
{ icon: Strikethrough, label: 'Зачеркнутый', syntax: '~~текст~~' },
|
||||
{ icon: Heading1, label: 'Заголовок 1', syntax: '# Заголовок' },
|
||||
{ icon: Heading2, label: 'Заголовок 2', syntax: '## Заголовок' },
|
||||
{ icon: Heading3, label: 'Заголовок 3', syntax: '### Заголовок' },
|
||||
{ icon: List, label: 'Список', syntax: '- Элемент списка' },
|
||||
{
|
||||
icon: ListOrdered,
|
||||
label: 'Нумерованный список',
|
||||
syntax: '1. Элемент списка',
|
||||
},
|
||||
{ icon: Quote, label: 'Цитата', syntax: '> Цитата' },
|
||||
{ icon: Code, label: 'Код', syntax: '`код`' },
|
||||
{ icon: Link, label: 'Ссылка', syntax: '[текст](url)' },
|
||||
{
|
||||
icon: Table,
|
||||
label: 'Таблица',
|
||||
syntax:
|
||||
'| Колонка 1 | Колонка 2 |\n|-----------|----------|\n| Ячейка 1 | Ячейка 2 |',
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap gap-1 border-b border-gray-200 bg-gray-50 p-2">
|
||||
{tools.map((tool, index) => (
|
||||
<Button
|
||||
key={index}
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => onInsert(tool.syntax)}
|
||||
disabled={disabled}
|
||||
className="h-8 w-8 p-0"
|
||||
title={tool.label}
|
||||
>
|
||||
<tool.icon className="h-3 w-3" />
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Компонент предварительного просмотра markdown
|
||||
const MarkdownPreview = ({ content }: { content: string }) => {
|
||||
return (
|
||||
<div className="prose prose-sm max-w-none">
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkGfm]}
|
||||
rehypePlugins={[rehypeRaw, rehypeSanitize]}
|
||||
components={
|
||||
{
|
||||
// Кастомные стили для компонентов
|
||||
h1: ({ children }: { children: React.ReactNode }) => (
|
||||
<h1 className="mb-2 text-lg font-bold">{children}</h1>
|
||||
),
|
||||
h2: ({ children }: { children: React.ReactNode }) => (
|
||||
<h2 className="mb-2 text-base font-bold">{children}</h2>
|
||||
),
|
||||
h3: ({ children }: { children: React.ReactNode }) => (
|
||||
<h3 className="mb-2 text-sm font-bold">{children}</h3>
|
||||
),
|
||||
p: ({ children }: { children: React.ReactNode }) => (
|
||||
<p className="mb-2 text-sm">{children}</p>
|
||||
),
|
||||
ul: ({ children }: { children: React.ReactNode }) => (
|
||||
<ul className="mb-2 list-inside list-disc text-sm">{children}</ul>
|
||||
),
|
||||
ol: ({ children }: { children: React.ReactNode }) => (
|
||||
<ol className="mb-2 list-inside list-decimal text-sm">
|
||||
{children}
|
||||
</ol>
|
||||
),
|
||||
li: ({ children }: { children: React.ReactNode }) => (
|
||||
<li className="mb-1">{children}</li>
|
||||
),
|
||||
blockquote: ({ children }: { children: React.ReactNode }) => (
|
||||
<blockquote className="mb-2 border-l-4 border-gray-300 pl-4 italic text-gray-600">
|
||||
{children}
|
||||
</blockquote>
|
||||
),
|
||||
code: ({ children }: { children: React.ReactNode }) => (
|
||||
<code className="rounded bg-gray-100 px-1 py-0.5 font-mono text-xs">
|
||||
{children}
|
||||
</code>
|
||||
),
|
||||
pre: ({ children }: { children: React.ReactNode }) => (
|
||||
<pre className="mb-2 overflow-auto rounded bg-gray-100 p-2 font-mono text-xs">
|
||||
{children}
|
||||
</pre>
|
||||
),
|
||||
table: ({ children }: { children: React.ReactNode }) => (
|
||||
<table className="mb-2 w-full border-collapse border border-gray-300 text-xs">
|
||||
{children}
|
||||
</table>
|
||||
),
|
||||
th: ({ children }: { children: React.ReactNode }) => (
|
||||
<th className="border border-gray-300 bg-gray-100 px-2 py-1 font-semibold">
|
||||
{children}
|
||||
</th>
|
||||
),
|
||||
td: ({ children }: { children: React.ReactNode }) => (
|
||||
<td className="border border-gray-300 px-2 py-1">{children}</td>
|
||||
),
|
||||
a: ({
|
||||
children,
|
||||
href,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
href?: string
|
||||
}) => (
|
||||
<a
|
||||
href={href}
|
||||
className="text-blue-600 underline"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
),
|
||||
} as Components
|
||||
}
|
||||
>
|
||||
{content || '*Введите текст для предварительного просмотра...*'}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const noteDefinition: ElementDefinition<NoteConfig, string> = {
|
||||
type: 'note',
|
||||
label: 'Заметка',
|
||||
icon: <FileText className="h-4 w-4" />,
|
||||
version: 1,
|
||||
defaultConfig: {
|
||||
content: '',
|
||||
targetCells: [],
|
||||
name: '',
|
||||
},
|
||||
getInitialValue: config => {
|
||||
return config.content || ''
|
||||
},
|
||||
mapToCells: config => config.targetCells || [],
|
||||
mapToCellValues: (config, value) => {
|
||||
const cells = config.targetCells || []
|
||||
return cells.map(target => ({ target, value: value || '' }))
|
||||
},
|
||||
Editor: ({ config, onChange }) => {
|
||||
const [markdownContent, setMarkdownContent] = useState(config.content || '')
|
||||
|
||||
const handleContentChange = (value: string) => {
|
||||
setMarkdownContent(value)
|
||||
onChange({ ...config, content: value })
|
||||
}
|
||||
|
||||
const insertText = (syntax: string) => {
|
||||
const textarea = document.querySelector(
|
||||
'textarea[data-markdown-editor]'
|
||||
) as HTMLTextAreaElement
|
||||
if (!textarea) return
|
||||
|
||||
const start = textarea.selectionStart
|
||||
const end = textarea.selectionEnd
|
||||
const selectedText = markdownContent.substring(start, end)
|
||||
|
||||
let insertedText = syntax
|
||||
let cursorPosition = start + insertedText.length
|
||||
|
||||
if (selectedText) {
|
||||
// Если есть выделенный текст, оборачиваем его соответствующим синтаксисом
|
||||
if (syntax.includes('**текст**')) {
|
||||
insertedText = `**${selectedText}**`
|
||||
cursorPosition = start + insertedText.length
|
||||
} else if (syntax.includes('*текст*')) {
|
||||
insertedText = `*${selectedText}*`
|
||||
cursorPosition = start + insertedText.length
|
||||
} else if (syntax.includes('~~текст~~')) {
|
||||
insertedText = `~~${selectedText}~~`
|
||||
cursorPosition = start + insertedText.length
|
||||
} else if (syntax.includes('`код`')) {
|
||||
insertedText = `\`${selectedText}\``
|
||||
cursorPosition = start + insertedText.length
|
||||
} else if (syntax.includes('[текст](url)')) {
|
||||
insertedText = `[${selectedText}](url)`
|
||||
cursorPosition = start + selectedText.length + 3 // Курсор на "url"
|
||||
} else if (syntax.includes('# Заголовок')) {
|
||||
insertedText = `# ${selectedText}`
|
||||
cursorPosition = start + insertedText.length
|
||||
} else if (syntax.includes('## Заголовок')) {
|
||||
insertedText = `## ${selectedText}`
|
||||
cursorPosition = start + insertedText.length
|
||||
} else if (syntax.includes('### Заголовок')) {
|
||||
insertedText = `### ${selectedText}`
|
||||
cursorPosition = start + insertedText.length
|
||||
} else if (syntax.includes('> Цитата')) {
|
||||
insertedText = `> ${selectedText}`
|
||||
cursorPosition = start + insertedText.length
|
||||
} else if (syntax.includes('- Элемент списка')) {
|
||||
insertedText = `- ${selectedText}`
|
||||
cursorPosition = start + insertedText.length
|
||||
} else if (syntax.includes('1. Элемент списка')) {
|
||||
insertedText = `1. ${selectedText}`
|
||||
cursorPosition = start + insertedText.length
|
||||
} else {
|
||||
// Для остальных случаев просто заменяем placeholder
|
||||
insertedText = syntax.replace('текст', selectedText)
|
||||
cursorPosition = start + insertedText.length
|
||||
}
|
||||
} else {
|
||||
// Если нет выделенного текста, вставляем шаблон
|
||||
insertedText = syntax
|
||||
|
||||
// Позиционируем курсор внутри шаблона для редактирования
|
||||
if (syntax.includes('**текст**')) {
|
||||
cursorPosition = start + 2 // После **
|
||||
} else if (syntax.includes('*текст*')) {
|
||||
cursorPosition = start + 1 // После *
|
||||
} else if (syntax.includes('~~текст~~')) {
|
||||
cursorPosition = start + 2 // После ~~
|
||||
} else if (syntax.includes('`код`')) {
|
||||
cursorPosition = start + 1 // После `
|
||||
} else if (syntax.includes('[текст](url)')) {
|
||||
cursorPosition = start + 1 // После [
|
||||
} else if (syntax.includes('# Заголовок')) {
|
||||
cursorPosition = start + 2 // После "# "
|
||||
} else if (syntax.includes('## Заголовок')) {
|
||||
cursorPosition = start + 3 // После "## "
|
||||
} else if (syntax.includes('### Заголовок')) {
|
||||
cursorPosition = start + 4 // После "### "
|
||||
} else if (syntax.includes('> Цитата')) {
|
||||
cursorPosition = start + 2 // После "> "
|
||||
} else if (syntax.includes('- Элемент списка')) {
|
||||
cursorPosition = start + 2 // После "- "
|
||||
} else if (syntax.includes('1. Элемент списка')) {
|
||||
cursorPosition = start + 3 // После "1. "
|
||||
} else {
|
||||
cursorPosition = start + insertedText.length
|
||||
}
|
||||
}
|
||||
|
||||
const newValue =
|
||||
markdownContent.substring(0, start) +
|
||||
insertedText +
|
||||
markdownContent.substring(end)
|
||||
handleContentChange(newValue)
|
||||
|
||||
// Возвращаем фокус в textarea
|
||||
setTimeout(() => {
|
||||
textarea.focus()
|
||||
if (!selectedText && syntax.includes('[текст](url)')) {
|
||||
// Для ссылок выделяем "текст"
|
||||
textarea.setSelectionRange(start + 1, start + 6)
|
||||
} else if (
|
||||
!selectedText &&
|
||||
(syntax.includes('**текст**') ||
|
||||
syntax.includes('*текст*') ||
|
||||
syntax.includes('~~текст~~') ||
|
||||
syntax.includes('`код`'))
|
||||
) {
|
||||
// Для форматирования выделяем placeholder
|
||||
const placeholderStart =
|
||||
syntax.indexOf('текст') !== -1
|
||||
? insertedText.indexOf('текст') + start
|
||||
: insertedText.indexOf('код') + start
|
||||
const placeholderLength = syntax.includes('код') ? 3 : 5
|
||||
textarea.setSelectionRange(
|
||||
placeholderStart,
|
||||
placeholderStart + placeholderLength
|
||||
)
|
||||
} else {
|
||||
textarea.setSelectionRange(cursorPosition, cursorPosition)
|
||||
}
|
||||
}, 0)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<Label htmlFor="content" className="text-sm font-medium">
|
||||
Markdown содержимое
|
||||
</Label>
|
||||
<div className="mt-1 overflow-hidden rounded-md border border-gray-200">
|
||||
<MarkdownToolbar onInsert={insertText} />
|
||||
<Textarea
|
||||
data-markdown-editor
|
||||
value={markdownContent}
|
||||
onChange={e => handleContentChange(e.target.value)}
|
||||
placeholder="Введите markdown текст..."
|
||||
className="resize-none rounded-none border-0 font-mono text-sm focus:ring-0"
|
||||
style={{ minHeight: 300 }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
Preview: ({ config }) => {
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
{config.name && (
|
||||
<Label className="text-sm font-medium text-gray-900">
|
||||
{config.name}
|
||||
</Label>
|
||||
)}
|
||||
|
||||
<MarkdownPreview
|
||||
content={config.content || '*Содержимое будет отображено здесь...*'}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
Render: ({ config, value, onChange }) => {
|
||||
return (
|
||||
<div className="relative space-y-2">
|
||||
{/* Бейдж ячеек в правом верхнем углу */}
|
||||
<div className="absolute right-0 top-0 z-10 flex items-center gap-1">
|
||||
<CellsBadge targetCells={config.targetCells} className="" />
|
||||
</div>
|
||||
|
||||
{/* Лейбл поля */}
|
||||
{config.name && (
|
||||
<Label className="text-sm font-medium text-gray-900">
|
||||
{config.name}
|
||||
</Label>
|
||||
)}
|
||||
|
||||
{/* Отображение markdown содержимого */}
|
||||
<MarkdownPreview
|
||||
content={config.content || value || '*Содержимое не задано...*'}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}
|
||||
@@ -9,6 +9,7 @@ interface TextConfig {
|
||||
placeholder?: string
|
||||
required?: boolean
|
||||
showIncrementButton?: boolean // Новый параметр для отображения кнопки +1
|
||||
addZeroWidthSpace?: boolean // Добавляем невидимый символ \u200B к значению
|
||||
targetCells: any[]
|
||||
name?: string
|
||||
id?: string // Добавляем id для совместимости с TemplateElement
|
||||
@@ -51,6 +52,7 @@ export const textDefinition: ElementDefinition<TextConfig, string> = {
|
||||
placeholder: '',
|
||||
required: false,
|
||||
showIncrementButton: false,
|
||||
addZeroWidthSpace: false,
|
||||
targetCells: [],
|
||||
name: '',
|
||||
},
|
||||
@@ -65,7 +67,10 @@ export const textDefinition: ElementDefinition<TextConfig, string> = {
|
||||
mapToCells: config => config.targetCells || [],
|
||||
mapToCellValues: (config, value) => {
|
||||
const cells = config.targetCells || []
|
||||
return cells.map(target => ({ target, value: value || '' }))
|
||||
const cellValue = config.addZeroWidthSpace
|
||||
? `\u200B${value || ''}`
|
||||
: value || ''
|
||||
return cells.map(target => ({ target, value: cellValue }))
|
||||
},
|
||||
Editor: ({ config, onChange }) => (
|
||||
<div className="space-y-4">
|
||||
@@ -84,6 +89,21 @@ export const textDefinition: ElementDefinition<TextConfig, string> = {
|
||||
Показывать кнопку +1
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="addZeroWidthSpace"
|
||||
checked={config.addZeroWidthSpace || false}
|
||||
onCheckedChange={checked =>
|
||||
onChange({ ...config, addZeroWidthSpace: !!checked })
|
||||
}
|
||||
/>
|
||||
<label
|
||||
htmlFor="addZeroWidthSpace"
|
||||
className="text-sm font-medium text-gray-900"
|
||||
>
|
||||
Только текстовое значение
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
Preview: ({ config }) => (
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
useLaboratories,
|
||||
useLaboratoryConditions,
|
||||
} from './hooks'
|
||||
import isEqual from 'lodash.isequal'
|
||||
import {
|
||||
VerificationConditionsConfig,
|
||||
VerificationConditionsValue,
|
||||
@@ -135,20 +136,22 @@ export function VerificationConditionsRender({
|
||||
const hasConditions =
|
||||
dailyCondition && Object.keys(dailyCondition.conditions).length > 0
|
||||
|
||||
// Инициализация данных при загрузке из БД, только если данные еще не заполнены
|
||||
// Автоматически обновляем форму при смене даты или загрузке новых условий из БД
|
||||
useEffect(() => {
|
||||
if (
|
||||
dailyCondition &&
|
||||
hasConditions &&
|
||||
onChange &&
|
||||
(!value?.conditions || Object.keys(value.conditions).length === 0)
|
||||
) {
|
||||
if (!onChange) return
|
||||
|
||||
const nextConditions = dailyCondition?.conditions || {}
|
||||
const currentConditions = value?.conditions || {}
|
||||
const shouldUpdateConditions = !isEqual(currentConditions, nextConditions)
|
||||
const shouldUpdateDate = value?.date !== selectedDate
|
||||
|
||||
if (shouldUpdateConditions || shouldUpdateDate) {
|
||||
onChange({
|
||||
date: selectedDate,
|
||||
conditions: dailyCondition.conditions,
|
||||
conditions: nextConditions,
|
||||
})
|
||||
}
|
||||
}, [dailyCondition, hasConditions, onChange, selectedDate, value?.conditions])
|
||||
}, [dailyCondition, onChange, selectedDate, value?.conditions, value?.date])
|
||||
|
||||
// Синхронизация selectedDate с внешним value.date
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { buttonGroupDefinition } from '@/entity/element/model/implementations/ButtonGroup'
|
||||
import { noteDefinition } from '@/entity/element/model/implementations/NoteElement'
|
||||
import { selectDefinition } from '@/entity/element/model/implementations/SelectElement'
|
||||
import { standardsDefinition } from '@/entity/element/model/implementations/StandardsElement/definition'
|
||||
import { textDefinition } from '@/entity/element/model/implementations/TextElement'
|
||||
@@ -62,6 +63,7 @@ export function getElementDefinition(
|
||||
export const elementDefinitions = {
|
||||
text: textDefinition,
|
||||
select: selectDefinition,
|
||||
note: noteDefinition,
|
||||
standards: standardsDefinition,
|
||||
verification_conditions: verificationConditionsDefinition,
|
||||
button_group: buttonGroupDefinition,
|
||||
@@ -72,6 +74,7 @@ export type ElementType = keyof typeof elementDefinitions
|
||||
export function initializeElementRegistry() {
|
||||
registerElement('text', textDefinition)
|
||||
registerElement('select', selectDefinition)
|
||||
registerElement('note', noteDefinition)
|
||||
registerElement('button_group', buttonGroupDefinition)
|
||||
registerElement('standards', standardsDefinition)
|
||||
registerElement('verification_conditions', verificationConditionsDefinition)
|
||||
|
||||
@@ -89,7 +89,7 @@ export function apiTemplateToTemplate(apiTemplate: ApiTemplate): Template {
|
||||
elements,
|
||||
layoutSettings: getDefaultLayoutSettings(),
|
||||
group_id: apiTemplate.group_id,
|
||||
order: apiTemplate.order, // ВАЖНО: добавляем order!
|
||||
order: apiTemplate.order ?? undefined, // ВАЖНО: добавляем order!
|
||||
createdAt: new Date(apiTemplate.created_at),
|
||||
updatedAt: new Date(apiTemplate.updated_at),
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
apiTemplateWithAttributesToTemplate,
|
||||
copyTemplateApi,
|
||||
CopyTemplateInput,
|
||||
PatchTemplateInput,
|
||||
createTemplateApi,
|
||||
deleteTemplateApi,
|
||||
getTemplateApi,
|
||||
@@ -346,10 +347,12 @@ export const TemplateProvider: React.FC<{ children: ReactNode }> = ({
|
||||
groupId,
|
||||
copyFirstSheetEmpty,
|
||||
}),
|
||||
updateTemplateOrder: async (templateId: string, newOrder: number) =>
|
||||
updateOrderMutation.mutateAsync({ templateId, newOrder }),
|
||||
moveTemplateToGroup: async (templateId: string, groupId: string | null) =>
|
||||
moveToGroupMutation.mutateAsync({ templateId, groupId }),
|
||||
updateTemplateOrder: async (templateId: string, newOrder: number) => {
|
||||
await updateOrderMutation.mutateAsync({ templateId, newOrder })
|
||||
},
|
||||
moveTemplateToGroup: async (templateId: string, groupId: string | null) => {
|
||||
await moveToGroupMutation.mutateAsync({ templateId, groupId })
|
||||
},
|
||||
}),
|
||||
[
|
||||
templates,
|
||||
|
||||
958
src/feature/excelEngine/excelEngine.test.ts
Normal file
958
src/feature/excelEngine/excelEngine.test.ts
Normal file
@@ -0,0 +1,958 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { Workbook, CellUtils } from './excelEngine'
|
||||
|
||||
function createWorkbookWithSheet(name = 'Sheet1') {
|
||||
const wb = new Workbook()
|
||||
const sheet = wb.addSheet(name)
|
||||
return { wb, sheet }
|
||||
}
|
||||
|
||||
describe('Workbook — формулы', () => {
|
||||
// ==================== Базовые значения ====================
|
||||
|
||||
describe('Базовые значения', () => {
|
||||
it('устанавливает и получает число', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 42)
|
||||
expect(wb.getValue('Sheet1!A1')).toBe(42)
|
||||
})
|
||||
|
||||
it('устанавливает и получает строку', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 'hello')
|
||||
expect(wb.getValue('Sheet1!A1')).toBe('hello')
|
||||
})
|
||||
|
||||
it('пустая ячейка возвращает null', () => {
|
||||
const { wb } = createWorkbookWithSheet()
|
||||
expect(wb.getValue('Sheet1!A1')).toBe(null)
|
||||
})
|
||||
})
|
||||
|
||||
// ==================== Простые формулы ====================
|
||||
|
||||
describe('Простые формулы', () => {
|
||||
it('формула с числом', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', '=1+2')
|
||||
expect(wb.getValue('Sheet1!A1')).toBe(3)
|
||||
})
|
||||
|
||||
it('формула со ссылкой на ячейку', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 10)
|
||||
sheet.setCell('A2', '=A1+5')
|
||||
expect(wb.getValue('Sheet1!A2')).toBe(15)
|
||||
})
|
||||
|
||||
it('формула с несколькими ссылками', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 10)
|
||||
sheet.setCell('A2', 20)
|
||||
sheet.setCell('A3', '=A1+A2')
|
||||
expect(wb.getValue('Sheet1!A3')).toBe(30)
|
||||
})
|
||||
})
|
||||
|
||||
// ==================== Функции в формулах ====================
|
||||
|
||||
describe('СУММ в формуле', () => {
|
||||
it('суммирует диапазон', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 1)
|
||||
sheet.setCell('A2', 2)
|
||||
sheet.setCell('A3', 3)
|
||||
sheet.setCell('B1', '=СУММ(A1:A3)')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe(6)
|
||||
})
|
||||
})
|
||||
|
||||
describe('СРЗНАЧ в формуле', () => {
|
||||
it('считает среднее диапазона', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 2)
|
||||
sheet.setCell('A2', 4)
|
||||
sheet.setCell('A3', 6)
|
||||
sheet.setCell('B1', '=СРЗНАЧ(A1:A3)')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe(4)
|
||||
})
|
||||
})
|
||||
|
||||
describe('МАКС в формуле', () => {
|
||||
it('возвращает максимум двух ячеек', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 3)
|
||||
sheet.setCell('A2', 7)
|
||||
sheet.setCell('B1', '=МАКС(A1, A2)')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe(7)
|
||||
})
|
||||
})
|
||||
|
||||
describe('СТЕПЕНЬ в формуле', () => {
|
||||
it('возводит значение ячейки в степень', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 2)
|
||||
sheet.setCell('B1', '=СТЕПЕНЬ(A1, 10)')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe(1024)
|
||||
})
|
||||
})
|
||||
|
||||
describe('КОРЕНЬ в формуле', () => {
|
||||
it('извлекает корень из ячейки', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 25)
|
||||
sheet.setCell('B1', '=КОРЕНЬ(A1)')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe(5)
|
||||
})
|
||||
})
|
||||
|
||||
describe('ABS в формуле', () => {
|
||||
it('возвращает модуль ячейки', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', -15)
|
||||
sheet.setCell('B1', '=ABS(A1)')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe(15)
|
||||
})
|
||||
})
|
||||
|
||||
// ==================== БОЛЬШЕ / МЕНЬШЕ ====================
|
||||
|
||||
describe('БОЛЬШЕ в формуле', () => {
|
||||
it('сравнивает два числа', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 10)
|
||||
sheet.setCell('A2', 5)
|
||||
sheet.setCell('B1', '=БОЛЬШЕ(A1, A2)')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe(true)
|
||||
})
|
||||
|
||||
it('возвращает false при меньшем значении', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 3)
|
||||
sheet.setCell('A2', 5)
|
||||
sheet.setCell('B1', '=БОЛЬШЕ(A1, A2)')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('МЕНЬШЕ в формуле', () => {
|
||||
it('сравнивает два числа', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 3)
|
||||
sheet.setCell('A2', 10)
|
||||
sheet.setCell('B1', '=МЕНЬШЕ(A1, A2)')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
// ==================== ЕСЛИ + БОЛЬШЕ/МЕНЬШЕ ====================
|
||||
|
||||
describe('ЕСЛИ + БОЛЬШЕ/МЕНЬШЕ', () => {
|
||||
it('ЕСЛИ(БОЛЬШЕ(...)) возвращает правильную ветку', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 100)
|
||||
sheet.setCell('B1', '=ЕСЛИ(БОЛЬШЕ(A1, 50), "Много", "Мало")')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe('Много')
|
||||
})
|
||||
|
||||
it('ЕСЛИ(МЕНЬШЕ(...)) возвращает ветку лжи', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 100)
|
||||
sheet.setCell('B1', '=ЕСЛИ(МЕНЬШЕ(A1, 50), "Мало", "Много")')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe('Много')
|
||||
})
|
||||
|
||||
it('ЕСЛИ с И и БОЛЬШЕ', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 50)
|
||||
sheet.setCell('B1', '=ЕСЛИ(И(БОЛЬШЕ(A1, 10), МЕНЬШЕ(A1, 100)), "В диапазоне", "Вне")')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe('В диапазоне')
|
||||
})
|
||||
|
||||
it('ЕСЛИ с ИЛИ', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 5)
|
||||
sheet.setCell('B1', '=ЕСЛИ(ИЛИ(БОЛЬШЕ(A1, 100), МЕНЬШЕ(A1, 10)), "Крайнее", "Норм")')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe('Крайнее')
|
||||
})
|
||||
|
||||
it('вложенный ЕСЛИ', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 50)
|
||||
sheet.setCell('B1', '=ЕСЛИ(БОЛЬШЕ(A1, 100), "Много", ЕСЛИ(МЕНЬШЕ(A1, 10), "Мало", "Норм"))')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe('Норм')
|
||||
})
|
||||
})
|
||||
|
||||
// ==================== ЕСЛИ с операторами сравнения ====================
|
||||
|
||||
describe('ЕСЛИ с операторами = >= <>', () => {
|
||||
it('ЕСЛИ(A1=5, ...) — равенство числу (совпадает)', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 5)
|
||||
sheet.setCell('B1', '=ЕСЛИ(A1=5, "Пять", "Другое")')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe('Пять')
|
||||
})
|
||||
|
||||
it('ЕСЛИ(A1=5, ...) — равенство числу (не совпадает)', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 7)
|
||||
sheet.setCell('B1', '=ЕСЛИ(A1=5, "Пять", "Другое")')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe('Другое')
|
||||
})
|
||||
|
||||
it('ЕСЛИ(A1=B1, ...) — сравнение двух ячеек (равны)', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 10)
|
||||
sheet.setCell('B1', 10)
|
||||
sheet.setCell('C1', '=ЕСЛИ(A1=B1, "Равны", "Не равны")')
|
||||
expect(wb.getValue('Sheet1!C1')).toBe('Равны')
|
||||
})
|
||||
|
||||
it('ЕСЛИ(A1=B1, ...) — сравнение двух ячеек (не равны)', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 10)
|
||||
sheet.setCell('B1', 20)
|
||||
sheet.setCell('C1', '=ЕСЛИ(A1=B1, "Равны", "Не равны")')
|
||||
expect(wb.getValue('Sheet1!C1')).toBe('Не равны')
|
||||
})
|
||||
|
||||
it('ЕСЛИ(A1>=100, ...) — больше или равно (истина)', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 100)
|
||||
sheet.setCell('B1', '=ЕСЛИ(A1>=100, "Сотня+", A1)')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe('Сотня+')
|
||||
})
|
||||
|
||||
it('ЕСЛИ(A1>=100, ...) — больше или равно (ложь)', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 42)
|
||||
sheet.setCell('B1', '=ЕСЛИ(A1>=100, "Сотня+", A1)')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe(42)
|
||||
})
|
||||
|
||||
it('ЕСЛИ(A1>10, ...) — строгое больше', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 15)
|
||||
sheet.setCell('B1', '=ЕСЛИ(A1>10, "Да", "Нет")')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe('Да')
|
||||
})
|
||||
|
||||
it('ЕСЛИ с И и операторами >= <=', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 5)
|
||||
sheet.setCell('B1', '=ЕСЛИ(И(A1>=0, A1<=10), "В диапазоне", "Вне")')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe('В диапазоне')
|
||||
})
|
||||
|
||||
it('ЕСЛИ с И и операторами >= <= — вне диапазона', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 15)
|
||||
sheet.setCell('B1', '=ЕСЛИ(И(A1>=0, A1<=10), "В диапазоне", "Вне")')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe('Вне')
|
||||
})
|
||||
|
||||
it('ЕСЛИ(A1>10, ...) — строгое больше (ложь)', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 5)
|
||||
sheet.setCell('B1', '=ЕСЛИ(A1>10, "Да", "Нет")')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe('Нет')
|
||||
})
|
||||
|
||||
it('ЕСЛИ с БОЛЬШЕ — истина', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('B1', '=ЕСЛИ(БОЛЬШЕ(5, 3), 1, 0)')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe(1)
|
||||
})
|
||||
|
||||
it('ЕСЛИ с БОЛЬШЕ — ложь', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('B1', '=ЕСЛИ(БОЛЬШЕ(1, 3), 1, 0)')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe(0)
|
||||
})
|
||||
|
||||
it('ЕСЛИ с равенством текста в ячейке', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 'Готово')
|
||||
sheet.setCell('B1', '=ЕСЛИ(A1="Готово", "✓", "✗")')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe('✓')
|
||||
})
|
||||
|
||||
it('ЕСЛИ с неравенством текста в ячейке', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 'В работе')
|
||||
sheet.setCell('B1', '=ЕСЛИ(A1="Готово", "✓", "✗")')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe('✗')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Оператор <> (не равно)', () => {
|
||||
it('ЕСЛИ(A1<>0, ...) — не равно (истина)', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 5)
|
||||
sheet.setCell('B1', '=ЕСЛИ(A1<>0, "Не ноль", "Ноль")')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe('Не ноль')
|
||||
})
|
||||
|
||||
it('ЕСЛИ(A1<>0, ...) — не равно (ложь)', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 0)
|
||||
sheet.setCell('B1', '=ЕСЛИ(A1<>0, "Не ноль", "Ноль")')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe('Ноль')
|
||||
})
|
||||
|
||||
it('ЕСЛИ(A1<>B1, ...) — две ячейки не равны', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 10)
|
||||
sheet.setCell('B1', 20)
|
||||
sheet.setCell('C1', '=ЕСЛИ(A1<>B1, "Разные", "Одинаковые")')
|
||||
expect(wb.getValue('Sheet1!C1')).toBe('Разные')
|
||||
})
|
||||
|
||||
it('ЕСЛИ(A1<>B1, ...) — две ячейки равны', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 10)
|
||||
sheet.setCell('B1', 10)
|
||||
sheet.setCell('C1', '=ЕСЛИ(A1<>B1, "Разные", "Одинаковые")')
|
||||
expect(wb.getValue('Sheet1!C1')).toBe('Одинаковые')
|
||||
})
|
||||
|
||||
it('<> внутри строкового литерала не трогается', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 1)
|
||||
sheet.setCell('B1', '=ЕСЛИ(A1=1, "a<>b", "нет")')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe('a<>b')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Операторы внутри строковых литералов не трогаются', () => {
|
||||
it('= внутри строкового литерала остаётся как есть', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 1)
|
||||
sheet.setCell('B1', '=ЕСЛИ(A1=1, "x=y", "нет")')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe('x=y')
|
||||
})
|
||||
})
|
||||
|
||||
// ==================== ЕПУСТО ====================
|
||||
|
||||
describe('ЕПУСТО в формуле', () => {
|
||||
it('true для пустой ячейки', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('B1', '=ЕПУСТО(A1)')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe(true)
|
||||
})
|
||||
|
||||
it('false для заполненной ячейки', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 42)
|
||||
sheet.setCell('B1', '=ЕПУСТО(A1)')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe(false)
|
||||
})
|
||||
|
||||
it('false для ячейки с нулём', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 0)
|
||||
sheet.setCell('B1', '=ЕПУСТО(A1)')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe(false)
|
||||
})
|
||||
|
||||
it('ЕСЛИ(ЕПУСТО(...)) работает', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('B1', '=ЕСЛИ(ЕПУСТО(A1), "Пусто", "Заполнено")')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe('Пусто')
|
||||
})
|
||||
})
|
||||
|
||||
// ==================== Строковые функции в формулах ====================
|
||||
|
||||
describe('Строковые функции в формулах', () => {
|
||||
it('СЦЕПИТЬ', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 'Привет')
|
||||
sheet.setCell('A2', ' мир')
|
||||
sheet.setCell('B1', '=СЦЕПИТЬ(A1, A2)')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe('Привет мир')
|
||||
})
|
||||
|
||||
it('ПРОПИСН', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 'hello')
|
||||
sheet.setCell('B1', '=ПРОПИСН(A1)')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe('HELLO')
|
||||
})
|
||||
|
||||
it('СТРОЧН', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 'HELLO')
|
||||
sheet.setCell('B1', '=СТРОЧН(A1)')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe('hello')
|
||||
})
|
||||
})
|
||||
|
||||
// ==================== Округление в формулах ====================
|
||||
|
||||
describe('Округление в формулах', () => {
|
||||
it('ОКРУГЛ', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 3.456)
|
||||
sheet.setCell('B1', '=ОКРУГЛ(A1, 2)')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe(3.46)
|
||||
})
|
||||
|
||||
it('ЦЕЛОЕ', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 3.9)
|
||||
sheet.setCell('B1', '=ЦЕЛОЕ(A1)')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe(3)
|
||||
})
|
||||
})
|
||||
|
||||
// ==================== Межлистовые ссылки ====================
|
||||
|
||||
describe('Межлистовые ссылки', () => {
|
||||
it('ссылка на другой лист', () => {
|
||||
const wb = new Workbook()
|
||||
const sheetL = wb.addSheet('L')
|
||||
const sheetR = wb.addSheet('R')
|
||||
sheetL.setCell('A1', 100)
|
||||
sheetR.setCell('A1', '=L!A1+50')
|
||||
expect(wb.getValue('R!A1')).toBe(150)
|
||||
})
|
||||
})
|
||||
|
||||
// ==================== Зависимости и пересчёт ====================
|
||||
|
||||
describe('Зависимости и пересчёт', () => {
|
||||
it('пересчёт при изменении зависимой ячейки', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 10)
|
||||
sheet.setCell('B1', '=A1*2')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe(20)
|
||||
|
||||
sheet.setCell('A1', 20)
|
||||
expect(wb.getValue('Sheet1!B1')).toBe(40)
|
||||
})
|
||||
|
||||
it('цепочка зависимостей', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 5)
|
||||
sheet.setCell('A2', '=A1+1')
|
||||
sheet.setCell('A3', '=A2+1')
|
||||
expect(wb.getValue('Sheet1!A3')).toBe(7)
|
||||
|
||||
sheet.setCell('A1', 10)
|
||||
expect(wb.getValue('Sheet1!A3')).toBe(12)
|
||||
})
|
||||
})
|
||||
|
||||
// ==================== Замена функций (регрессия) ====================
|
||||
|
||||
describe('Регрессия: замена функций не ломает кириллические имена', () => {
|
||||
it('ЕСЛИ не должен быть разбит заменой И', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 50)
|
||||
sheet.setCell('B1', '=ЕСЛИ(БОЛЬШЕ(A1, 100), "Много", "Мало")')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe('Мало')
|
||||
})
|
||||
|
||||
it('И внутри ЕСЛИ(И(...)) работает корректно', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 50)
|
||||
sheet.setCell('B1', '=ЕСЛИ(И(БОЛЬШЕ(A1, 10), МЕНЬШЕ(A1, 100)), "Да", "Нет")')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe('Да')
|
||||
})
|
||||
|
||||
it('НЕ не ломает другие функции', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 50)
|
||||
sheet.setCell('B1', '=ЕСЛИ(НЕ(БОЛЬШЕ(A1, 100)), "Не больше", "Больше")')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe('Не больше')
|
||||
})
|
||||
})
|
||||
|
||||
// ==================== Case-insensitive функции ====================
|
||||
|
||||
describe('Функции в нижнем регистре', () => {
|
||||
it('случдр в нижнем регистре', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', '=случдр(1, 10, 2)')
|
||||
const val = wb.getValue('Sheet1!A1') as number
|
||||
expect(val).toBeGreaterThanOrEqual(1)
|
||||
expect(val).toBeLessThanOrEqual(10)
|
||||
})
|
||||
|
||||
it('округл в нижнем регистре', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', '=округл(3.456, 2)')
|
||||
expect(wb.getValue('Sheet1!A1')).toBe(3.46)
|
||||
})
|
||||
|
||||
it('сумм в нижнем регистре', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 1)
|
||||
sheet.setCell('A2', 2)
|
||||
sheet.setCell('A3', 3)
|
||||
sheet.setCell('B1', '=сумм(A1:A3)')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe(6)
|
||||
})
|
||||
|
||||
it('если в нижнем регистре с больше', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 100)
|
||||
sheet.setCell('B1', '=если(больше(A1, 50), "Много", "Мало")')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe('Много')
|
||||
})
|
||||
|
||||
it('смешанный регистр', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 5)
|
||||
sheet.setCell('B1', '=Если(Больше(A1, 3), "да", "нет")')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe('да')
|
||||
})
|
||||
|
||||
it('епусто в нижнем регистре — пустая ячейка', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('B1', '=епусто(A1)')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe(true)
|
||||
})
|
||||
|
||||
it('епусто в нижнем регистре — заполненная ячейка', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 42)
|
||||
sheet.setCell('B1', '=епусто(A1)')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe(false)
|
||||
})
|
||||
|
||||
it('если(епусто(...)) в нижнем регистре', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('B1', '=если(епусто(A1), "Пусто", "Есть")')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe('Пусто')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// ==================== Сценарий протокола ====================
|
||||
|
||||
describe('Сценарий протокола: шаблон → ввод данных → пересчёт → выгрузка', () => {
|
||||
/**
|
||||
* Имитирует реальный flow:
|
||||
* 1. Загружается шаблон с формулами (fromJSON)
|
||||
* 2. Пользователь заполняет форму → значения пишутся в ячейки (setCell)
|
||||
* 3. Движок пересчитывает формулы (recalc)
|
||||
* 4. getAllModifiedCells / toJSON отдают данные для бэкенда
|
||||
*/
|
||||
|
||||
function createProtocolWorkbook() {
|
||||
const wb = new Workbook()
|
||||
wb.addSheet('L') // лист протокола
|
||||
wb.addSheet('R') // лист настроек/расчётов
|
||||
return wb
|
||||
}
|
||||
|
||||
it('загрузка шаблона и пересчёт формул', () => {
|
||||
const wb = createProtocolWorkbook()
|
||||
|
||||
// Шаблон: R!A1 — имя файла, R!A2 — область печати
|
||||
// L!A1 — заголовок, L!B1 — формула суммы
|
||||
wb.fromJSON({
|
||||
L: {
|
||||
A1: 'Протокол испытаний',
|
||||
A2: 10,
|
||||
A3: 20,
|
||||
A4: '=СУММ(A2:A3)',
|
||||
},
|
||||
R: {
|
||||
A1: 'protocol_output.xlsx',
|
||||
A2: 'A1:D20',
|
||||
},
|
||||
})
|
||||
|
||||
expect(wb.getValue('L!A1')).toBe('Протокол испытаний')
|
||||
expect(wb.getValue('L!A4')).toBe(30)
|
||||
expect(wb.getValue('R!A1')).toBe('protocol_output.xlsx')
|
||||
expect(wb.getValue('R!A2')).toBe('A1:D20')
|
||||
})
|
||||
|
||||
it('ввод данных из формы и пересчёт зависимых ячеек', () => {
|
||||
const wb = createProtocolWorkbook()
|
||||
|
||||
wb.fromJSON({
|
||||
L: {
|
||||
A1: '', // ячейка для ввода (пустая в шаблоне)
|
||||
B1: '=ОКРУГЛ(A1 * 1.2, 2)', // формула зависит от A1
|
||||
C1: '=ЕСЛИ(БОЛЬШЕ(B1, 100), "Не соответствует", "Соответствует")',
|
||||
},
|
||||
})
|
||||
|
||||
// Первый recalc после загрузки (как в useMultiSheetEngine.safeRecalc)
|
||||
wb.recalc()
|
||||
|
||||
// Пользователь вводит значение через форму → setCellValue
|
||||
wb.setCell('L!A1', 50)
|
||||
expect(wb.getValue('L!B1')).toBe(60)
|
||||
expect(wb.getValue('L!C1')).toBe('Соответствует')
|
||||
|
||||
// Пользователь меняет значение
|
||||
wb.setCell('L!A1', 100)
|
||||
expect(wb.getValue('L!B1')).toBe(120)
|
||||
expect(wb.getValue('L!C1')).toBe('Не соответствует')
|
||||
})
|
||||
|
||||
it('формулы со ссылками между листами L и R', () => {
|
||||
const wb = createProtocolWorkbook()
|
||||
|
||||
wb.fromJSON({
|
||||
R: {
|
||||
A5: 0.15, // коэффициент из настроек
|
||||
C1: '=случдр(10, 20, 2)', // случайное значение
|
||||
},
|
||||
L: {
|
||||
A1: 100,
|
||||
B1: '=ОКРУГЛ(A1 * R!A5, 2)', // 100 * 0.15 = 15
|
||||
},
|
||||
})
|
||||
|
||||
expect(wb.getValue('L!B1')).toBe(15)
|
||||
|
||||
// recalc чтобы зарегистрировать зависимости
|
||||
wb.recalc()
|
||||
|
||||
// Меняем входное значение
|
||||
wb.setCell('L!A1', 200)
|
||||
expect(wb.getValue('L!B1')).toBe(30)
|
||||
})
|
||||
|
||||
it('ЕСЛИ + БОЛЬШЕ + МЕНЬШЕ в контексте протокола', () => {
|
||||
const wb = createProtocolWorkbook()
|
||||
|
||||
// Типичный сценарий: проверка допусков
|
||||
wb.fromJSON({
|
||||
L: {
|
||||
A1: 'Параметр',
|
||||
A2: 50, // измеренное значение
|
||||
B2: 10, // нижний допуск
|
||||
C2: 100, // верхний допуск
|
||||
D2: '=ЕСЛИ(И(БОЛЬШЕ(A2, B2), МЕНЬШЕ(A2, C2)), "Годен", "Брак")',
|
||||
},
|
||||
})
|
||||
|
||||
expect(wb.getValue('L!D2')).toBe('Годен')
|
||||
|
||||
// Значение вне допуска
|
||||
wb.setCell('L!A2', 5)
|
||||
expect(wb.getValue('L!D2')).toBe('Брак')
|
||||
|
||||
// Значение на верхней границе — БОЛЬШЕ возвращает false при равенстве
|
||||
wb.setCell('L!A2', 100)
|
||||
expect(wb.getValue('L!D2')).toBe('Брак')
|
||||
})
|
||||
|
||||
it('ЕПУСТО в контексте протокола — пропуск пустых строк', () => {
|
||||
const wb = createProtocolWorkbook()
|
||||
|
||||
wb.fromJSON({
|
||||
L: {
|
||||
A1: 42,
|
||||
B1: '=ЕСЛИ(ЕПУСТО(A1), "", ОКРУГЛ(A1 * 1.1, 1))',
|
||||
A2: '', // пустая
|
||||
B2: '=ЕСЛИ(ЕПУСТО(A2), "", ОКРУГЛ(A2 * 1.1, 1))',
|
||||
},
|
||||
})
|
||||
|
||||
expect(wb.getValue('L!B1')).toBe(46.2)
|
||||
expect(wb.getValue('L!B2')).toBe('')
|
||||
})
|
||||
|
||||
it('СТЕПЕНЬ в формулах протокола', () => {
|
||||
const wb = createProtocolWorkbook()
|
||||
|
||||
wb.fromJSON({
|
||||
L: {
|
||||
A1: 3,
|
||||
B1: '=СТЕПЕНЬ(A1, 2)', // 9
|
||||
C1: '=КОРЕНЬ(B1)', // 3
|
||||
},
|
||||
})
|
||||
|
||||
expect(wb.getValue('L!B1')).toBe(9)
|
||||
expect(wb.getValue('L!C1')).toBe(3)
|
||||
})
|
||||
|
||||
it('цепочка пересчёта: A → B → C → D', () => {
|
||||
const wb = createProtocolWorkbook()
|
||||
|
||||
wb.fromJSON({
|
||||
L: {
|
||||
A1: 10,
|
||||
B1: '=A1 * 2', // 20
|
||||
C1: '=B1 + 5', // 25
|
||||
D1: '=ОКРУГЛ(C1 / 3, 2)', // 8.33
|
||||
},
|
||||
})
|
||||
|
||||
expect(wb.getValue('L!D1')).toBe(8.33)
|
||||
|
||||
wb.setCell('L!A1', 100)
|
||||
// 100 → 200 → 205 → 68.33
|
||||
expect(wb.getValue('L!B1')).toBe(200)
|
||||
expect(wb.getValue('L!C1')).toBe(205)
|
||||
expect(wb.getValue('L!D1')).toBe(68.33)
|
||||
})
|
||||
|
||||
it('toJSON возвращает raw-значения (формулы) для бэкенда', () => {
|
||||
const wb = createProtocolWorkbook()
|
||||
|
||||
wb.fromJSON({
|
||||
L: {
|
||||
A1: 10,
|
||||
B1: '=A1 * 2',
|
||||
},
|
||||
})
|
||||
|
||||
// getValue возвращает вычисленное
|
||||
expect(wb.getValue('L!B1')).toBe(20)
|
||||
|
||||
// toJSON возвращает raw (формулу) — это идёт в cells_to_update
|
||||
const json = wb.toJSON()
|
||||
expect(json['L']['A1']).toBe(10)
|
||||
expect(json['L']['B1']).toBe('=A1 * 2')
|
||||
})
|
||||
|
||||
it('getAllModifiedCells: baseline vs текущее состояние', () => {
|
||||
const wb = createProtocolWorkbook()
|
||||
|
||||
// Загружаем шаблон
|
||||
const templateData = {
|
||||
L: {
|
||||
A1: '',
|
||||
B1: '=A1 * 2',
|
||||
},
|
||||
}
|
||||
|
||||
wb.fromJSON(templateData)
|
||||
|
||||
// Сохраняем baseline (как делает setTemplateData в хуке)
|
||||
const baseline = JSON.parse(JSON.stringify(templateData))
|
||||
|
||||
// Пользователь вводит данные
|
||||
wb.setCell('L!A1', 42)
|
||||
|
||||
// Сравниваем текущее с baseline
|
||||
const currentData = wb.toJSON()
|
||||
const modified: Record<string, any> = {}
|
||||
|
||||
for (const cellAddr of Object.keys(currentData['L'] || {})) {
|
||||
const current = String(currentData['L'][cellAddr] ?? '')
|
||||
const original = String(baseline['L']?.[cellAddr] ?? '')
|
||||
if (current !== original) {
|
||||
modified[cellAddr] = currentData['L'][cellAddr]
|
||||
}
|
||||
}
|
||||
|
||||
// A1 изменилась (была '', стала 42)
|
||||
expect(modified['A1']).toBe(42)
|
||||
// B1 не изменилась (формула та же)
|
||||
expect(modified['B1']).toBeUndefined()
|
||||
})
|
||||
|
||||
it('множественные элементы формы → разные ячейки', () => {
|
||||
const wb = createProtocolWorkbook()
|
||||
|
||||
wb.fromJSON({
|
||||
L: {
|
||||
A1: '', // ячейка для ввода
|
||||
B1: '', // ячейка для ввода
|
||||
A2: '', // ячейка для ввода
|
||||
B2: '', // ячейка для ввода
|
||||
// Формулы зависят от ячеек, куда пишутся значения формы
|
||||
C1: '=СЦЕПИТЬ(A1, " ", B1)',
|
||||
C2: '=A2 + B2',
|
||||
},
|
||||
})
|
||||
|
||||
// Первый recalc после загрузки (как в useMultiSheetEngine)
|
||||
wb.recalc()
|
||||
|
||||
// Элемент формы 1 → A1 (текст)
|
||||
wb.setCell('L!A1', 'Иванов')
|
||||
// Элемент формы 2 → B1 (текст)
|
||||
wb.setCell('L!B1', 'И.И.')
|
||||
|
||||
// Элемент формы 3 → A2 (число)
|
||||
wb.setCell('L!A2', 10)
|
||||
// Элемент формы 4 → B2 (число)
|
||||
wb.setCell('L!B2', 20)
|
||||
|
||||
expect(wb.getValue('L!C1')).toBe('Иванов И.И.')
|
||||
expect(wb.getValue('L!C2')).toBe(30)
|
||||
})
|
||||
|
||||
it('формулы в нижнем регистре в шаблоне (как в реальных данных)', () => {
|
||||
const wb = createProtocolWorkbook()
|
||||
|
||||
wb.fromJSON({
|
||||
R: {
|
||||
C7: '=случдр(16, 17.6, 3)',
|
||||
D7: '=если(больше(A1, 100), "Много", "Мало")',
|
||||
},
|
||||
L: {
|
||||
A1: 50,
|
||||
},
|
||||
})
|
||||
|
||||
// случдр должна вернуть число в диапазоне
|
||||
const c7 = wb.getValue('R!C7') as number
|
||||
expect(c7).toBeGreaterThanOrEqual(16)
|
||||
expect(c7).toBeLessThanOrEqual(17.6)
|
||||
|
||||
// если + больше в нижнем регистре
|
||||
expect(wb.getValue('R!D7')).toBe('Мало')
|
||||
})
|
||||
})
|
||||
|
||||
// ==================== CellUtils ====================
|
||||
|
||||
describe('CellUtils', () => {
|
||||
it('indexToColumn', () => {
|
||||
expect(CellUtils.indexToColumn(0)).toBe('A')
|
||||
expect(CellUtils.indexToColumn(25)).toBe('Z')
|
||||
expect(CellUtils.indexToColumn(26)).toBe('AA')
|
||||
})
|
||||
|
||||
it('createCellName с номером колонки', () => {
|
||||
expect(CellUtils.createCellName(0, 1)).toBe('A1')
|
||||
expect(CellUtils.createCellName(2, 5)).toBe('C5')
|
||||
})
|
||||
|
||||
it('createCellName со строкой колонки', () => {
|
||||
expect(CellUtils.createCellName('B', 3)).toBe('B3')
|
||||
})
|
||||
})
|
||||
|
||||
// ==================== Edge Cases ====================
|
||||
|
||||
describe('Edge Cases функций через движок', () => {
|
||||
// 1. ОКРУГЛ с отрицательными разрядами: ОКРУГЛ(1234, -2) = 1200 в Excel
|
||||
describe('ОКРУГЛ с отрицательными разрядами', () => {
|
||||
it('ОКРУГЛ(1234, -2) должно дать 1200', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', '=ОКРУГЛ(1234, -2)')
|
||||
expect(wb.getValue('Sheet1!A1')).toBe(1200)
|
||||
})
|
||||
|
||||
it('ОКРУГЛ(1550, -3) должно дать 2000', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', '=ОКРУГЛ(1550, -3)')
|
||||
expect(wb.getValue('Sheet1!A1')).toBe(2000)
|
||||
})
|
||||
})
|
||||
|
||||
// 2. ЕСЛИ с числом как условие (0 = ложь, не-0 = истина)
|
||||
describe('ЕСЛИ с числовым условием', () => {
|
||||
it('0 как ложь', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 0)
|
||||
sheet.setCell('B1', '=ЕСЛИ(A1, "да", "нет")')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe('нет')
|
||||
})
|
||||
|
||||
it('ненулевое число как истина', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 42)
|
||||
sheet.setCell('B1', '=ЕСЛИ(A1, "да", "нет")')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe('да')
|
||||
})
|
||||
})
|
||||
|
||||
// 3. СЦЕПИТЬ с числами
|
||||
describe('СЦЕПИТЬ с числами', () => {
|
||||
it('число конвертируется в строку', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 42)
|
||||
sheet.setCell('B1', '=СЦЕПИТЬ("итого: ", A1)')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe('итого: 42')
|
||||
})
|
||||
})
|
||||
|
||||
// 4. Несколько = в одной формуле
|
||||
describe('Несколько = в одной формуле', () => {
|
||||
it('И(A1=1, B1=2) — оба true', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 1)
|
||||
sheet.setCell('B1', 2)
|
||||
sheet.setCell('C1', '=ЕСЛИ(И(A1=1, B1=2), "да", "нет")')
|
||||
expect(wb.getValue('Sheet1!C1')).toBe('да')
|
||||
})
|
||||
|
||||
it('И(A1=1, B1=2) — одно false', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 1)
|
||||
sheet.setCell('B1', 99)
|
||||
sheet.setCell('C1', '=ЕСЛИ(И(A1=1, B1=2), "да", "нет")')
|
||||
expect(wb.getValue('Sheet1!C1')).toBe('нет')
|
||||
})
|
||||
})
|
||||
|
||||
// 5. Пустая ячейка в сравнении (движок подставляет 0)
|
||||
describe('Пустая ячейка в сравнении с =', () => {
|
||||
it('пустая ячейка = 0', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
// A1 не установлена — пустая, движок подставит 0
|
||||
sheet.setCell('B1', '=ЕСЛИ(A1=0, "пусто/ноль", "есть значение")')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe('пусто/ноль')
|
||||
})
|
||||
})
|
||||
|
||||
// 6. СТЕПЕНЬ(0, 0) — в Excel = 1
|
||||
describe('СТЕПЕНЬ edge cases', () => {
|
||||
it('СТЕПЕНЬ(0, 0) = 1', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', '=СТЕПЕНЬ(0, 0)')
|
||||
expect(wb.getValue('Sheet1!A1')).toBe(1)
|
||||
})
|
||||
})
|
||||
|
||||
// 7. МАКС — вариадический (как в Excel)
|
||||
describe('МАКС', () => {
|
||||
// Обратная совместимость: 2 аргумента (как было раньше)
|
||||
it('МАКС(3, 7) = 7 — обратная совместимость', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', '=МАКС(3, 7)')
|
||||
expect(wb.getValue('Sheet1!A1')).toBe(7)
|
||||
})
|
||||
|
||||
it('МАКС(10, 5) = 10 — обратная совместимость', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', '=МАКС(10, 5)')
|
||||
expect(wb.getValue('Sheet1!A1')).toBe(10)
|
||||
})
|
||||
|
||||
it('МАКС(-5, 3) = 3 — обратная совместимость с отрицательными', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', '=МАКС(-5, 3)')
|
||||
expect(wb.getValue('Sheet1!A1')).toBe(3)
|
||||
})
|
||||
|
||||
it('МАКС со ссылками на ячейки — обратная совместимость', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', 25)
|
||||
sheet.setCell('A2', 10)
|
||||
sheet.setCell('B1', '=МАКС(A1, A2)')
|
||||
expect(wb.getValue('Sheet1!B1')).toBe(25)
|
||||
})
|
||||
|
||||
// Новое: 3+ аргумента
|
||||
it('МАКС(3, 7, 100) = 100 — вариадический', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', '=МАКС(3, 7, 100)')
|
||||
expect(wb.getValue('Sheet1!A1')).toBe(100)
|
||||
})
|
||||
|
||||
it('МАКС(1, 2, 3, 4, 5) = 5', () => {
|
||||
const { wb, sheet } = createWorkbookWithSheet()
|
||||
sheet.setCell('A1', '=МАКС(1, 2, 3, 4, 5)')
|
||||
expect(wb.getValue('Sheet1!A1')).toBe(5)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -5,11 +5,11 @@ import {
|
||||
} from './functions'
|
||||
|
||||
// Ограничиваем поиск ячеек диапазоном A1-Z120
|
||||
const QUALIFIED_REF_RE = /\b[A-Za-z0-9_]+\b/g
|
||||
const LOCAL_REF_RE = /\b[A-Z]([1-9]|[1-8][0-9]|120)\b/g
|
||||
const RANGE_RE = /\b[A-Z]([1-9]|[1-8][0-9]|120):[A-Z]([1-9]|[1-8][0-9]|120)\b/g
|
||||
const QUALIFIED_REF_RE = /\b[A-Za-z0-9_]+\b/g
|
||||
const LOCAL_REF_RE = /\b[A-Z]([1-9]|[1-8][0-9]|150)\b/g
|
||||
const RANGE_RE = /\b[A-Z]([1-9]|[1-8][0-9]|150):[A-Z]([1-9]|[1-8][0-9]|150)\b/g
|
||||
const QUALIFIED_RANGE_RE =
|
||||
/\b[A-Za-z0-9_]+:[A-Z]([1-9]|[1-8][0-9]|120)\b/g
|
||||
/\b[A-Za-z0-9_]+:[A-Z]([1-9]|[1-8][0-9]|150)\b/g
|
||||
|
||||
// Типы для значений ячеек
|
||||
export type CellValue = string | number | boolean | null | undefined
|
||||
@@ -120,6 +120,26 @@ export class Cell {
|
||||
// Заменяем ссылки на вычисленные значения
|
||||
let code = expr
|
||||
|
||||
// Обрабатываем ЕПУСТО до замены ссылок на значения,
|
||||
// чтобы проверить raw-значение ячейки напрямую
|
||||
code = code.replace(
|
||||
/ЕПУСТО\s*\(\s*(?:([A-Za-z0-9_]+)!)?([A-Za-z](?:[1-9]|[1-8][0-9]|150))\s*\)/gi,
|
||||
(_match, sheetName, cellRef) => {
|
||||
const qualifiedRef = sheetName
|
||||
? `${sheetName}!${cellRef}`
|
||||
: `${this.sheet.name}!${cellRef}`
|
||||
try {
|
||||
const cell = this.sheet.workbook.getCell(qualifiedRef)
|
||||
const raw = cell.raw
|
||||
return raw === null || raw === undefined || raw === ''
|
||||
? 'true'
|
||||
: 'false'
|
||||
} catch {
|
||||
return 'true'
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// ВАЖНО: Сначала заменяем диапазоны ячеек на массивы значений,
|
||||
// затем отдельные ячейки, чтобы избежать конфликтов
|
||||
code = code.replace(QUALIFIED_RANGE_RE, match => {
|
||||
@@ -186,12 +206,18 @@ export class Cell {
|
||||
// Заменяем функции Excel
|
||||
const functions = this.sheet.workbook.getFunctions()
|
||||
|
||||
for (const [funcName] of Object.entries(functions)) {
|
||||
// Сортируем функции по длине имени (от длинных к коротким),
|
||||
// чтобы ЕСЛИ заменилось раньше И, и не было частичных замен
|
||||
const sortedFuncNames = Object.keys(functions).sort(
|
||||
(a, b) => b.length - a.length
|
||||
)
|
||||
|
||||
for (const funcName of sortedFuncNames) {
|
||||
// Экранируем специальные символы в названии функции для регулярного выражения
|
||||
const escapedFuncName = funcName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||
// Для кириллических символов \b может не работать, используем более универсальное решение
|
||||
// Используем юникодные границы: перед функцией не должно быть буквы/цифры (включая кириллицу)
|
||||
const funcRegex = new RegExp(
|
||||
`(?:^|[^\\w])(${escapedFuncName})\\s*\\(`,
|
||||
`(?:^|[^\\wА-Яа-яЁё])(${escapedFuncName})\\s*\\(`,
|
||||
'gi'
|
||||
)
|
||||
const before = code
|
||||
@@ -206,6 +232,21 @@ export class Cell {
|
||||
}
|
||||
}
|
||||
|
||||
// Конвертируем Excel-операторы сравнения в JS-операторы
|
||||
// Сохраняем строковые литералы, чтобы не трогать операторы внутри них
|
||||
const strings: string[] = []
|
||||
code = code.replace(/"[^"]*"/g, m => {
|
||||
strings.push(m)
|
||||
return `__STR${strings.length - 1}__`
|
||||
})
|
||||
// <> → !== (не равно, как в Excel)
|
||||
code = code.replace(/<>/g, '!==')
|
||||
// >= и <= уже валидны в JS, пропускаем
|
||||
// Одиночное = (не ==, !=, >=, <=) → === (равенство вместо присваивания)
|
||||
code = code.replace(/(?<![<>!=])=(?!=)/g, '===')
|
||||
// Восстанавливаем строковые литералы
|
||||
code = code.replace(/__STR(\d+)__/g, (_, i) => strings[Number(i)])
|
||||
|
||||
// Создаём контекст для выполнения
|
||||
const context = {
|
||||
__functions__: functions,
|
||||
|
||||
363
src/feature/excelEngine/functions.test.ts
Normal file
363
src/feature/excelEngine/functions.test.ts
Normal file
@@ -0,0 +1,363 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { DEFAULT_FUNCTIONS } from './functions'
|
||||
|
||||
describe('DEFAULT_FUNCTIONS', () => {
|
||||
// ==================== Математические ====================
|
||||
|
||||
describe('СУММ', () => {
|
||||
it('суммирует числа', () => {
|
||||
expect(DEFAULT_FUNCTIONS.СУММ(1, 2, 3)).toBe(6)
|
||||
})
|
||||
it('суммирует массив', () => {
|
||||
expect(DEFAULT_FUNCTIONS.СУММ([1, 2, 3])).toBe(6)
|
||||
})
|
||||
it('игнорирует не-числа', () => {
|
||||
expect(DEFAULT_FUNCTIONS.СУММ(1, 'abc', 3)).toBe(4)
|
||||
})
|
||||
it('возвращает 0 без аргументов', () => {
|
||||
expect(DEFAULT_FUNCTIONS.СУММ()).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('СРЗНАЧ', () => {
|
||||
it('считает среднее', () => {
|
||||
expect(DEFAULT_FUNCTIONS.СРЗНАЧ(2, 4, 6)).toBe(4)
|
||||
})
|
||||
it('игнорирует не-числа', () => {
|
||||
expect(DEFAULT_FUNCTIONS.СРЗНАЧ(2, 'abc', 4)).toBe(3)
|
||||
})
|
||||
it('возвращает 0 если нет чисел', () => {
|
||||
expect(DEFAULT_FUNCTIONS.СРЗНАЧ('a', 'b')).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('СЧЁТ', () => {
|
||||
it('считает количество чисел', () => {
|
||||
expect(DEFAULT_FUNCTIONS['СЧЁТ'](1, 'a', 2, null, 3)).toBe(3)
|
||||
})
|
||||
it('возвращает 0 без чисел', () => {
|
||||
expect(DEFAULT_FUNCTIONS['СЧЁТ']('a', 'b')).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('МАКС', () => {
|
||||
it('возвращает максимум из двух', () => {
|
||||
expect(DEFAULT_FUNCTIONS.МАКС(3, 7)).toBe(7)
|
||||
})
|
||||
it('возвращает максимум из нескольких', () => {
|
||||
expect(DEFAULT_FUNCTIONS.МАКС(3, 7, 100)).toBe(100)
|
||||
})
|
||||
it('работает с массивом', () => {
|
||||
expect(DEFAULT_FUNCTIONS.МАКС([3, 7, 1])).toBe(7)
|
||||
})
|
||||
it('игнорирует не-числа', () => {
|
||||
expect(DEFAULT_FUNCTIONS.МАКС('a' as any, 1)).toBe(1)
|
||||
})
|
||||
it('игнорирует Infinity', () => {
|
||||
expect(DEFAULT_FUNCTIONS.МАКС(Infinity, 1)).toBe(1)
|
||||
})
|
||||
it('ошибка без валидных чисел', () => {
|
||||
expect(DEFAULT_FUNCTIONS.МАКС('a' as any, 'b' as any)).toBe('#ЗНАЧ!')
|
||||
})
|
||||
})
|
||||
|
||||
describe('МИН', () => {
|
||||
it('возвращает минимум', () => {
|
||||
expect(DEFAULT_FUNCTIONS.МИН(3, 7, 1)).toBe(1)
|
||||
})
|
||||
it('работает с массивом', () => {
|
||||
expect(DEFAULT_FUNCTIONS.МИН([3, 7, 1])).toBe(1)
|
||||
})
|
||||
it('возвращает 0 без чисел', () => {
|
||||
expect(DEFAULT_FUNCTIONS.МИН('a', 'b')).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('ABS', () => {
|
||||
it('возвращает модуль', () => {
|
||||
expect(DEFAULT_FUNCTIONS.ABS(-5)).toBe(5)
|
||||
expect(DEFAULT_FUNCTIONS.ABS(5)).toBe(5)
|
||||
expect(DEFAULT_FUNCTIONS.ABS(0)).toBe(0)
|
||||
})
|
||||
it('ошибка при не-числе', () => {
|
||||
expect(DEFAULT_FUNCTIONS.ABS('abc' as any)).toBe('#ЗНАЧ!')
|
||||
})
|
||||
it('ошибка при Infinity', () => {
|
||||
expect(DEFAULT_FUNCTIONS.ABS(Infinity)).toBe('#ЧИСЛО!')
|
||||
})
|
||||
})
|
||||
|
||||
describe('КОРЕНЬ', () => {
|
||||
it('возвращает корень', () => {
|
||||
expect(DEFAULT_FUNCTIONS['КОРЕНЬ'](9)).toBe(3)
|
||||
expect(DEFAULT_FUNCTIONS['КОРЕНЬ'](0)).toBe(0)
|
||||
})
|
||||
it('ошибка при отрицательном числе', () => {
|
||||
expect(DEFAULT_FUNCTIONS['КОРЕНЬ'](-4)).toBe('#ЧИСЛО!')
|
||||
})
|
||||
it('ошибка при не-числе', () => {
|
||||
expect(DEFAULT_FUNCTIONS['КОРЕНЬ']('abc' as any)).toBe('#ЗНАЧ!')
|
||||
})
|
||||
})
|
||||
|
||||
describe('СТЕПЕНЬ', () => {
|
||||
it('возводит в степень', () => {
|
||||
expect(DEFAULT_FUNCTIONS['СТЕПЕНЬ'](2, 3)).toBe(8)
|
||||
expect(DEFAULT_FUNCTIONS['СТЕПЕНЬ'](5, 0)).toBe(1)
|
||||
expect(DEFAULT_FUNCTIONS['СТЕПЕНЬ'](9, 0.5)).toBe(3)
|
||||
})
|
||||
it('ошибка при не-числе', () => {
|
||||
expect(DEFAULT_FUNCTIONS['СТЕПЕНЬ']('a' as any, 2)).toBe('#ЗНАЧ!')
|
||||
})
|
||||
it('ошибка при Infinity', () => {
|
||||
expect(DEFAULT_FUNCTIONS['СТЕПЕНЬ'](Infinity, 2)).toBe('#ЧИСЛО!')
|
||||
})
|
||||
it('ошибка при переполнении', () => {
|
||||
expect(DEFAULT_FUNCTIONS['СТЕПЕНЬ'](10, 1000)).toBe('#ЧИСЛО!')
|
||||
})
|
||||
})
|
||||
|
||||
// ==================== Сравнение ====================
|
||||
|
||||
describe('БОЛЬШЕ', () => {
|
||||
it('возвращает true когда первое больше', () => {
|
||||
expect(DEFAULT_FUNCTIONS['БОЛЬШЕ'](5, 3)).toBe(true)
|
||||
})
|
||||
it('возвращает false когда первое меньше', () => {
|
||||
expect(DEFAULT_FUNCTIONS['БОЛЬШЕ'](3, 5)).toBe(false)
|
||||
})
|
||||
it('возвращает false при равенстве', () => {
|
||||
expect(DEFAULT_FUNCTIONS['БОЛЬШЕ'](5, 5)).toBe(false)
|
||||
})
|
||||
it('ошибка при не-числах', () => {
|
||||
expect(DEFAULT_FUNCTIONS['БОЛЬШЕ']('a' as any, 1)).toBe('#ЗНАЧ!')
|
||||
})
|
||||
it('ошибка при Infinity', () => {
|
||||
expect(DEFAULT_FUNCTIONS['БОЛЬШЕ'](Infinity, 1)).toBe('#ЧИСЛО!')
|
||||
})
|
||||
})
|
||||
|
||||
describe('МЕНЬШЕ', () => {
|
||||
it('возвращает true когда первое меньше', () => {
|
||||
expect(DEFAULT_FUNCTIONS['МЕНЬШЕ'](3, 5)).toBe(true)
|
||||
})
|
||||
it('возвращает false когда первое больше', () => {
|
||||
expect(DEFAULT_FUNCTIONS['МЕНЬШЕ'](5, 3)).toBe(false)
|
||||
})
|
||||
it('возвращает false при равенстве', () => {
|
||||
expect(DEFAULT_FUNCTIONS['МЕНЬШЕ'](5, 5)).toBe(false)
|
||||
})
|
||||
it('ошибка при не-числах', () => {
|
||||
expect(DEFAULT_FUNCTIONS['МЕНЬШЕ']('a' as any, 1)).toBe('#ЗНАЧ!')
|
||||
})
|
||||
})
|
||||
|
||||
describe('ЕПУСТО', () => {
|
||||
it('true для null', () => {
|
||||
expect(DEFAULT_FUNCTIONS['ЕПУСТО'](null)).toBe(true)
|
||||
})
|
||||
it('true для undefined', () => {
|
||||
expect(DEFAULT_FUNCTIONS['ЕПУСТО'](undefined)).toBe(true)
|
||||
})
|
||||
it('true для пустой строки', () => {
|
||||
expect(DEFAULT_FUNCTIONS['ЕПУСТО']('')).toBe(true)
|
||||
})
|
||||
it('false для числа', () => {
|
||||
expect(DEFAULT_FUNCTIONS['ЕПУСТО'](0)).toBe(false)
|
||||
})
|
||||
it('false для строки', () => {
|
||||
expect(DEFAULT_FUNCTIONS['ЕПУСТО']('текст')).toBe(false)
|
||||
})
|
||||
it('false для false', () => {
|
||||
expect(DEFAULT_FUNCTIONS['ЕПУСТО'](false)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
// ==================== Логические ====================
|
||||
|
||||
describe('ЕСЛИ', () => {
|
||||
it('возвращает значение при истине', () => {
|
||||
expect(DEFAULT_FUNCTIONS['ЕСЛИ'](true, 'да', 'нет')).toBe('да')
|
||||
})
|
||||
it('возвращает значение при лжи', () => {
|
||||
expect(DEFAULT_FUNCTIONS['ЕСЛИ'](false, 'да', 'нет')).toBe('нет')
|
||||
})
|
||||
})
|
||||
|
||||
describe('И', () => {
|
||||
it('true когда все true', () => {
|
||||
expect(DEFAULT_FUNCTIONS['И'](true, true, true)).toBe(true)
|
||||
})
|
||||
it('false когда хоть один false', () => {
|
||||
expect(DEFAULT_FUNCTIONS['И'](true, false, true)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('ИЛИ', () => {
|
||||
it('true когда хоть один true', () => {
|
||||
expect(DEFAULT_FUNCTIONS['ИЛИ'](false, true, false)).toBe(true)
|
||||
})
|
||||
it('false когда все false', () => {
|
||||
expect(DEFAULT_FUNCTIONS['ИЛИ'](false, false, false)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('НЕ', () => {
|
||||
it('инвертирует true', () => {
|
||||
expect(DEFAULT_FUNCTIONS['НЕ'](true)).toBe(false)
|
||||
})
|
||||
it('инвертирует false', () => {
|
||||
expect(DEFAULT_FUNCTIONS['НЕ'](false)).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
// ==================== Строковые ====================
|
||||
|
||||
describe('СЦЕПИТЬ', () => {
|
||||
it('объединяет строки', () => {
|
||||
expect(DEFAULT_FUNCTIONS['СЦЕПИТЬ']('Привет', ' ', 'мир')).toBe(
|
||||
'Привет мир'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('ДЛСТР', () => {
|
||||
it('возвращает длину строки', () => {
|
||||
expect(DEFAULT_FUNCTIONS['ДЛСТР']('hello')).toBe(5)
|
||||
})
|
||||
it('возвращает 0 для пустого', () => {
|
||||
expect(DEFAULT_FUNCTIONS['ДЛСТР']('')).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('ПРОПИСН', () => {
|
||||
it('переводит в верхний регистр', () => {
|
||||
expect(DEFAULT_FUNCTIONS['ПРОПИСН']('hello')).toBe('HELLO')
|
||||
})
|
||||
})
|
||||
|
||||
describe('СТРОЧН', () => {
|
||||
it('переводит в нижний регистр', () => {
|
||||
expect(DEFAULT_FUNCTIONS['СТРОЧН']('HELLO')).toBe('hello')
|
||||
})
|
||||
})
|
||||
|
||||
describe('СЖПРОБЕЛЫ', () => {
|
||||
it('удаляет пробелы по краям', () => {
|
||||
expect(DEFAULT_FUNCTIONS['СЖПРОБЕЛЫ'](' hello ')).toBe('hello')
|
||||
})
|
||||
})
|
||||
|
||||
// ==================== Округление ====================
|
||||
|
||||
describe('ОКРУГЛ', () => {
|
||||
it('округляет до целого', () => {
|
||||
expect(DEFAULT_FUNCTIONS['ОКРУГЛ'](3.7)).toBe(4)
|
||||
expect(DEFAULT_FUNCTIONS['ОКРУГЛ'](3.2)).toBe(3)
|
||||
})
|
||||
it('округляет до разрядов', () => {
|
||||
expect(DEFAULT_FUNCTIONS['ОКРУГЛ'](3.456, 2)).toBe(3.46)
|
||||
})
|
||||
})
|
||||
|
||||
describe('ОКРУГЛВВЕРХ', () => {
|
||||
it('округляет вверх', () => {
|
||||
expect(DEFAULT_FUNCTIONS['ОКРУГЛВВЕРХ'](3.1)).toBe(4)
|
||||
})
|
||||
it('округляет вверх с разрядами', () => {
|
||||
expect(DEFAULT_FUNCTIONS['ОКРУГЛВВЕРХ'](3.411, 1)).toBe(3.5)
|
||||
})
|
||||
})
|
||||
|
||||
describe('ОКРУГЛВНИЗ', () => {
|
||||
it('округляет вниз', () => {
|
||||
expect(DEFAULT_FUNCTIONS['ОКРУГЛВНИЗ'](3.9)).toBe(3)
|
||||
})
|
||||
})
|
||||
|
||||
describe('ОКРВВЕРХ', () => {
|
||||
it('округляет вверх до кратного', () => {
|
||||
expect(DEFAULT_FUNCTIONS['ОКРВВЕРХ'](7, 5)).toBe(10)
|
||||
})
|
||||
it('возвращает 0 при кратном 0', () => {
|
||||
expect(DEFAULT_FUNCTIONS['ОКРВВЕРХ'](7, 0)).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('ОКРВНИЗ', () => {
|
||||
it('округляет вниз до кратного', () => {
|
||||
expect(DEFAULT_FUNCTIONS['ОКРВНИЗ'](7, 5)).toBe(5)
|
||||
})
|
||||
})
|
||||
|
||||
describe('ЦЕЛОЕ', () => {
|
||||
it('отбрасывает дробную часть вниз', () => {
|
||||
expect(DEFAULT_FUNCTIONS['ЦЕЛОЕ'](3.9)).toBe(3)
|
||||
expect(DEFAULT_FUNCTIONS['ЦЕЛОЕ'](-3.1)).toBe(-4)
|
||||
})
|
||||
})
|
||||
|
||||
describe('ОТБР', () => {
|
||||
it('отбрасывает дробную часть', () => {
|
||||
expect(DEFAULT_FUNCTIONS['ОТБР'](3.9)).toBe(3)
|
||||
expect(DEFAULT_FUNCTIONS['ОТБР'](-3.9)).toBe(-3)
|
||||
})
|
||||
it('с разрядами', () => {
|
||||
expect(DEFAULT_FUNCTIONS['ОТБР'](3.456, 2)).toBe(3.45)
|
||||
})
|
||||
})
|
||||
|
||||
describe('ОКРУГЛТ', () => {
|
||||
it('округляет до кратного', () => {
|
||||
expect(DEFAULT_FUNCTIONS['ОКРУГЛТ'](7, 5)).toBe(5)
|
||||
expect(DEFAULT_FUNCTIONS['ОКРУГЛТ'](8, 5)).toBe(10)
|
||||
})
|
||||
it('возвращает 0 при кратном 0', () => {
|
||||
expect(DEFAULT_FUNCTIONS['ОКРУГЛТ'](7, 0)).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
// ==================== Случайные ====================
|
||||
|
||||
describe('СЛУЧ', () => {
|
||||
it('возвращает число от 0 до 1', () => {
|
||||
const result = DEFAULT_FUNCTIONS['СЛУЧ']()
|
||||
expect(result).toBeGreaterThanOrEqual(0)
|
||||
expect(result).toBeLessThan(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('СЛУЧМЕЖДУ', () => {
|
||||
it('возвращает целое число в диапазоне', () => {
|
||||
for (let i = 0; i < 20; i++) {
|
||||
const result = DEFAULT_FUNCTIONS['СЛУЧМЕЖДУ'](1, 10) as number
|
||||
expect(result).toBeGreaterThanOrEqual(1)
|
||||
expect(result).toBeLessThanOrEqual(10)
|
||||
expect(Number.isInteger(result)).toBe(true)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('СЛУЧДР', () => {
|
||||
it('возвращает число в диапазоне', () => {
|
||||
for (let i = 0; i < 20; i++) {
|
||||
const result = DEFAULT_FUNCTIONS['СЛУЧДР'](1, 10, 2) as number
|
||||
expect(result).toBeGreaterThanOrEqual(1)
|
||||
expect(result).toBeLessThanOrEqual(10)
|
||||
}
|
||||
})
|
||||
it('ошибка при не-числах', () => {
|
||||
expect(DEFAULT_FUNCTIONS['СЛУЧДР']('a' as any, 10, 2)).toBe('#ЗНАЧ!')
|
||||
})
|
||||
it('ошибка при Infinity', () => {
|
||||
expect(DEFAULT_FUNCTIONS['СЛУЧДР'](Infinity, 10, 2)).toBe('#ЧИСЛО!')
|
||||
})
|
||||
it('меняет местами если низ > верх', () => {
|
||||
for (let i = 0; i < 20; i++) {
|
||||
const result = DEFAULT_FUNCTIONS['СЛУЧДР'](10, 1, 0) as number
|
||||
expect(result).toBeGreaterThanOrEqual(1)
|
||||
expect(result).toBeLessThanOrEqual(10)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -69,8 +69,9 @@ export const DEFAULT_FUNCTIONS: Record<string, ExcelFunction> = {
|
||||
},
|
||||
МАКС: (...args: any[]) => {
|
||||
const flatArgs = args.flat()
|
||||
const validArgs = flatArgs.filter(val => typeof val === 'number')
|
||||
return validArgs.length > 0 ? Math.max(...validArgs) : 0
|
||||
const validArgs = flatArgs.filter(val => typeof val === 'number' && isFinite(val))
|
||||
if (validArgs.length === 0) return '#ЗНАЧ!'
|
||||
return Math.max(...validArgs)
|
||||
},
|
||||
МИН: (...args: any[]) => {
|
||||
const flatArgs = args.flat()
|
||||
@@ -118,4 +119,89 @@ export const DEFAULT_FUNCTIONS: Record<string, ExcelFunction> = {
|
||||
if (кратное === 0) return 0
|
||||
return Math.round(число / кратное) * кратное
|
||||
},
|
||||
|
||||
// Функция корня с проверками
|
||||
КОРЕНЬ: (число: number) => {
|
||||
// Проверка валидности входного параметра
|
||||
if (typeof число !== 'number') {
|
||||
return '#ЗНАЧ!' // Ошибка значения
|
||||
}
|
||||
|
||||
if (!isFinite(число)) {
|
||||
return '#ЧИСЛО!' // Ошибка числа
|
||||
}
|
||||
|
||||
// Проверка на отрицательное число
|
||||
if (число < 0) {
|
||||
return '#ЧИСЛО!' // Корень из отрицательного числа
|
||||
}
|
||||
|
||||
// Возвращаем квадратный корень
|
||||
return Math.sqrt(число)
|
||||
},
|
||||
|
||||
// Функция возведения в степень
|
||||
СТЕПЕНЬ: (число: number, степень: number) => {
|
||||
if (typeof число !== 'number' || typeof степень !== 'number') {
|
||||
return '#ЗНАЧ!'
|
||||
}
|
||||
|
||||
if (!isFinite(число) || !isFinite(степень)) {
|
||||
return '#ЧИСЛО!'
|
||||
}
|
||||
|
||||
const результат = Math.pow(число, степень)
|
||||
|
||||
if (!isFinite(результат)) {
|
||||
return '#ЧИСЛО!'
|
||||
}
|
||||
|
||||
return результат
|
||||
},
|
||||
|
||||
// Функция сравнения БОЛЬШЕ (>)
|
||||
БОЛЬШЕ: (число1: number, число2: number) => {
|
||||
if (typeof число1 !== 'number' || typeof число2 !== 'number') {
|
||||
return '#ЗНАЧ!'
|
||||
}
|
||||
|
||||
if (!isFinite(число1) || !isFinite(число2)) {
|
||||
return '#ЧИСЛО!'
|
||||
}
|
||||
|
||||
return число1 > число2
|
||||
},
|
||||
|
||||
// Функция сравнения МЕНЬШЕ (<)
|
||||
МЕНЬШЕ: (число1: number, число2: number) => {
|
||||
if (typeof число1 !== 'number' || typeof число2 !== 'number') {
|
||||
return '#ЗНАЧ!'
|
||||
}
|
||||
|
||||
if (!isFinite(число1) || !isFinite(число2)) {
|
||||
return '#ЧИСЛО!'
|
||||
}
|
||||
|
||||
return число1 < число2
|
||||
},
|
||||
|
||||
// Функция проверки на пустоту
|
||||
ЕПУСТО: (значение: CellValue) => {
|
||||
return значение === null || значение === undefined || значение === ''
|
||||
},
|
||||
|
||||
// Функция модуля числа
|
||||
ABS: (число: number) => {
|
||||
// Проверка валидности входного параметра
|
||||
if (typeof число !== 'number') {
|
||||
return '#ЗНАЧ!' // Ошибка значения
|
||||
}
|
||||
|
||||
if (!isFinite(число)) {
|
||||
return '#ЧИСЛО!' // Ошибка числа
|
||||
}
|
||||
|
||||
// Возвращаем абсолютное значение
|
||||
return Math.abs(число)
|
||||
},
|
||||
}
|
||||
|
||||
669
src/page/functions-wiki/Page.tsx
Normal file
669
src/page/functions-wiki/Page.tsx
Normal file
@@ -0,0 +1,669 @@
|
||||
import { ArrowLeft, BookOpen, Calculator, Hash, Search, Sigma, Type } from 'lucide-react'
|
||||
import { FC, useMemo, useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
|
||||
interface FunctionInfo {
|
||||
name: string
|
||||
category: string
|
||||
description: string
|
||||
syntax: string
|
||||
args: { name: string; description: string; optional?: boolean }[]
|
||||
examples: { formula: string; result: string }[]
|
||||
notes?: string
|
||||
}
|
||||
|
||||
const FUNCTION_CATALOG: FunctionInfo[] = [
|
||||
// Случайные числа
|
||||
{
|
||||
name: 'СЛУЧ',
|
||||
category: 'Случайные числа',
|
||||
description: 'Возвращает случайное число от 0 до 1.',
|
||||
syntax: 'СЛУЧ()',
|
||||
args: [],
|
||||
examples: [
|
||||
{ formula: '=СЛУЧ()', result: '0.7231...' },
|
||||
],
|
||||
notes: 'Волатильная функция — пересчитывается при каждом изменении.',
|
||||
},
|
||||
{
|
||||
name: 'СЛУЧМЕЖДУ',
|
||||
category: 'Случайные числа',
|
||||
description: 'Возвращает случайное целое число в заданном диапазоне.',
|
||||
syntax: 'СЛУЧМЕЖДУ(низ,верх)',
|
||||
args: [
|
||||
{ name: 'низ', description: 'Нижняя граница диапазона' },
|
||||
{ name: 'верх', description: 'Верхняя граница диапазона' },
|
||||
],
|
||||
examples: [
|
||||
{ formula: '=СЛУЧМЕЖДУ(1,10)', result: '7' },
|
||||
{ formula: '=СЛУЧМЕЖДУ(100,200)', result: '142' },
|
||||
],
|
||||
notes: 'Волатильная функция.',
|
||||
},
|
||||
{
|
||||
name: 'СЛУЧДР',
|
||||
category: 'Случайные числа',
|
||||
description: 'Возвращает случайное дробное число в заданном диапазоне с указанным количеством знаков после запятой.',
|
||||
syntax: 'СЛУЧДР(низ,верх,знаков)',
|
||||
args: [
|
||||
{ name: 'низ', description: 'Нижняя граница диапазона' },
|
||||
{ name: 'верх', description: 'Верхняя граница диапазона' },
|
||||
{ name: 'знаков', description: 'Количество знаков после запятой (0–15)', optional: true },
|
||||
],
|
||||
examples: [
|
||||
{ formula: '=СЛУЧДР(1,10,2)', result: '4.73' },
|
||||
{ formula: '=СЛУЧДР(0,1,4)', result: '0.5182' },
|
||||
],
|
||||
notes: 'Волатильная функция. Если низ > верх, значения меняются местами автоматически.',
|
||||
},
|
||||
|
||||
// Математические
|
||||
{
|
||||
name: 'СУММ',
|
||||
category: 'Математические',
|
||||
description: 'Суммирует числовые значения. Нечисловые значения игнорируются.',
|
||||
syntax: 'СУММ(число1,число2,...)',
|
||||
args: [
|
||||
{ name: 'число1,число2,...', description: 'Числа или диапазоны ячеек для суммирования' },
|
||||
],
|
||||
examples: [
|
||||
{ formula: '=СУММ(1,2,3)', result: '6' },
|
||||
{ formula: '=СУММ(A1:A5)', result: 'Сумма ячеек A1–A5' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'СРЗНАЧ',
|
||||
category: 'Математические',
|
||||
description: 'Вычисляет среднее арифметическое числовых значений.',
|
||||
syntax: 'СРЗНАЧ(число1,число2,...)',
|
||||
args: [
|
||||
{ name: 'число1,число2,...', description: 'Числа или диапазоны ячеек' },
|
||||
],
|
||||
examples: [
|
||||
{ formula: '=СРЗНАЧ(10,20,30)', result: '20' },
|
||||
{ formula: '=СРЗНАЧ(A1:A3)', result: 'Среднее значений A1–A3' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'СЧЁТ',
|
||||
category: 'Математические',
|
||||
description: 'Подсчитывает количество числовых значений в аргументах.',
|
||||
syntax: 'СЧЁТ(значение1,значение2,...)',
|
||||
args: [
|
||||
{ name: 'значение1,значение2,...', description: 'Значения или диапазоны ячеек' },
|
||||
],
|
||||
examples: [
|
||||
{ formula: '=СЧЁТ(1,"текст",3,5)', result: '3' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'МАКС',
|
||||
category: 'Математические',
|
||||
description: 'Возвращает наибольшее из двух чисел.',
|
||||
syntax: 'МАКС(число1,число2)',
|
||||
args: [
|
||||
{ name: 'число1', description: 'Первое число' },
|
||||
{ name: 'число2', description: 'Второе число' },
|
||||
],
|
||||
examples: [
|
||||
{ formula: '=МАКС(10,25)', result: '25' },
|
||||
{ formula: '=МАКС(-5,3)', result: '3' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'МИН',
|
||||
category: 'Математические',
|
||||
description: 'Возвращает наименьшее числовое значение из аргументов.',
|
||||
syntax: 'МИН(число1,число2,...)',
|
||||
args: [
|
||||
{ name: 'число1,число2,...', description: 'Числа или диапазоны ячеек' },
|
||||
],
|
||||
examples: [
|
||||
{ formula: '=МИН(10,5,20)', result: '5' },
|
||||
{ formula: '=МИН(A1:A3)', result: 'Минимальное из A1–A3' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'ABS',
|
||||
category: 'Математические',
|
||||
description: 'Возвращает абсолютное значение (модуль) числа.',
|
||||
syntax: 'ABS(число)',
|
||||
args: [
|
||||
{ name: 'число', description: 'Число, модуль которого нужно найти' },
|
||||
],
|
||||
examples: [
|
||||
{ formula: '=ABS(-15)', result: '15' },
|
||||
{ formula: '=ABS(7)', result: '7' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'КОРЕНЬ',
|
||||
category: 'Математические',
|
||||
description: 'Возвращает квадратный корень числа.',
|
||||
syntax: 'КОРЕНЬ(число)',
|
||||
args: [
|
||||
{ name: 'число', description: 'Неотрицательное число' },
|
||||
],
|
||||
examples: [
|
||||
{ formula: '=КОРЕНЬ(25)', result: '5' },
|
||||
{ formula: '=КОРЕНЬ(2)', result: '1.4142...' },
|
||||
],
|
||||
notes: 'Возвращает #ЧИСЛО! для отрицательных чисел.',
|
||||
},
|
||||
{
|
||||
name: 'СТЕПЕНЬ',
|
||||
category: 'Математические',
|
||||
description: 'Возводит число в указанную степень.',
|
||||
syntax: 'СТЕПЕНЬ(число,степень)',
|
||||
args: [
|
||||
{ name: 'число', description: 'Основание' },
|
||||
{ name: 'степень', description: 'Показатель степени' },
|
||||
],
|
||||
examples: [
|
||||
{ formula: '=СТЕПЕНЬ(2,10)', result: '1024' },
|
||||
{ formula: '=СТЕПЕНЬ(5,2)', result: '25' },
|
||||
],
|
||||
},
|
||||
|
||||
// Округление
|
||||
{
|
||||
name: 'ОКРУГЛ',
|
||||
category: 'Округление',
|
||||
description: 'Округляет число до указанного количества разрядов.',
|
||||
syntax: 'ОКРУГЛ(число,разряды)',
|
||||
args: [
|
||||
{ name: 'число', description: 'Число для округления' },
|
||||
{ name: 'разряды', description: 'Количество десятичных разрядов', optional: true },
|
||||
],
|
||||
examples: [
|
||||
{ formula: '=ОКРУГЛ(3.456,2)', result: '3.46' },
|
||||
{ formula: '=ОКРУГЛ(3.456,0)', result: '3' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'ОКРУГЛВВЕРХ',
|
||||
category: 'Округление',
|
||||
description: 'Округляет число вверх (от нуля) до указанного количества разрядов.',
|
||||
syntax: 'ОКРУГЛВВЕРХ(число,разряды)',
|
||||
args: [
|
||||
{ name: 'число', description: 'Число для округления' },
|
||||
{ name: 'разряды', description: 'Количество десятичных разрядов', optional: true },
|
||||
],
|
||||
examples: [
|
||||
{ formula: '=ОКРУГЛВВЕРХ(3.21,1)', result: '3.3' },
|
||||
{ formula: '=ОКРУГЛВВЕРХ(3.1,0)', result: '4' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'ОКРУГЛВНИЗ',
|
||||
category: 'Округление',
|
||||
description: 'Округляет число вниз (к нулю) до указанного количества разрядов.',
|
||||
syntax: 'ОКРУГЛВНИЗ(число,разряды)',
|
||||
args: [
|
||||
{ name: 'число', description: 'Число для округления' },
|
||||
{ name: 'разряды', description: 'Количество десятичных разрядов', optional: true },
|
||||
],
|
||||
examples: [
|
||||
{ formula: '=ОКРУГЛВНИЗ(3.89,1)', result: '3.8' },
|
||||
{ formula: '=ОКРУГЛВНИЗ(3.9,0)', result: '3' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'ОКРВВЕРХ',
|
||||
category: 'Округление',
|
||||
description: 'Округляет число вверх до ближайшего кратного.',
|
||||
syntax: 'ОКРВВЕРХ(число,кратное)',
|
||||
args: [
|
||||
{ name: 'число', description: 'Число для округления' },
|
||||
{ name: 'кратное', description: 'Кратное, до которого округляется', optional: true },
|
||||
],
|
||||
examples: [
|
||||
{ formula: '=ОКРВВЕРХ(7,5)', result: '10' },
|
||||
{ formula: '=ОКРВВЕРХ(23,10)', result: '30' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'ОКРВНИЗ',
|
||||
category: 'Округление',
|
||||
description: 'Округляет число вниз до ближайшего кратного.',
|
||||
syntax: 'ОКРВНИЗ(число,кратное)',
|
||||
args: [
|
||||
{ name: 'число', description: 'Число для округления' },
|
||||
{ name: 'кратное', description: 'Кратное, до которого округляется', optional: true },
|
||||
],
|
||||
examples: [
|
||||
{ formula: '=ОКРВНИЗ(7,5)', result: '5' },
|
||||
{ formula: '=ОКРВНИЗ(27,10)', result: '20' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'ЦЕЛОЕ',
|
||||
category: 'Округление',
|
||||
description: 'Округляет число вниз до ближайшего целого.',
|
||||
syntax: 'ЦЕЛОЕ(число)',
|
||||
args: [
|
||||
{ name: 'число', description: 'Число для округления' },
|
||||
],
|
||||
examples: [
|
||||
{ formula: '=ЦЕЛОЕ(7.8)', result: '7' },
|
||||
{ formula: '=ЦЕЛОЕ(-3.2)', result: '-4' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'ОТБР',
|
||||
category: 'Округление',
|
||||
description: 'Отбрасывает дробную часть числа до указанного количества разрядов.',
|
||||
syntax: 'ОТБР(число,разряды)',
|
||||
args: [
|
||||
{ name: 'число', description: 'Число для усечения' },
|
||||
{ name: 'разряды', description: 'Количество десятичных разрядов', optional: true },
|
||||
],
|
||||
examples: [
|
||||
{ formula: '=ОТБР(7.89,1)', result: '7.8' },
|
||||
{ formula: '=ОТБР(-3.7,0)', result: '-3' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'ОКРУГЛТ',
|
||||
category: 'Округление',
|
||||
description: 'Округляет число до ближайшего кратного.',
|
||||
syntax: 'ОКРУГЛТ(число,кратное)',
|
||||
args: [
|
||||
{ name: 'число', description: 'Число для округления' },
|
||||
{ name: 'кратное', description: 'Кратное, до которого округляется' },
|
||||
],
|
||||
examples: [
|
||||
{ formula: '=ОКРУГЛТ(7,3)', result: '6' },
|
||||
{ formula: '=ОКРУГЛТ(10,3)', result: '9' },
|
||||
],
|
||||
},
|
||||
|
||||
// Логические
|
||||
{
|
||||
name: 'ЕСЛИ',
|
||||
category: 'Логические',
|
||||
description: 'Возвращает одно значение, если условие истинно, и другое — если ложно.',
|
||||
syntax: 'ЕСЛИ(условие,истина,ложь)',
|
||||
args: [
|
||||
{ name: 'условие', description: 'Логическое выражение для проверки' },
|
||||
{ name: 'истина', description: 'Значение, если условие истинно' },
|
||||
{ name: 'ложь', description: 'Значение, если условие ложно' },
|
||||
],
|
||||
examples: [
|
||||
{ formula: '=ЕСЛИ(A1>10, "Да", "Нет")', result: '"Да" если A1 > 10' },
|
||||
{ formula: '=ЕСЛИ(A1=5, "Пять", "Другое")', result: '"Пять" если A1 равно 5' },
|
||||
{ formula: '=ЕСЛИ(A1=B1, "Равны", "Не равны")', result: 'Сравнение двух ячеек на равенство' },
|
||||
{ formula: '=ЕСЛИ(A1>=100, "Сотня+", A1)', result: '"Сотня+" если A1 >= 100, иначе значение A1' },
|
||||
{ formula: '=ЕСЛИ(БОЛЬШЕ(5, 3), 1, 0)', result: '1' },
|
||||
{ formula: '=ЕСЛИ(A1="Готово", "✓", "✗")', result: '"✓" если A1 содержит текст "Готово"' },
|
||||
{ formula: '=ЕСЛИ(A1<>0, "Не ноль", "Ноль")', result: '"Не ноль" если A1 не равно 0' },
|
||||
{ formula: '=ЕСЛИ(И(A1>=0, A1<=10), "В диапазоне", "Вне")', result: 'Проверка попадания в диапазон 0–10' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'И',
|
||||
category: 'Логические',
|
||||
description: 'Возвращает ИСТИНА, если все аргументы истинны.',
|
||||
syntax: 'И(условие1,условие2,...)',
|
||||
args: [
|
||||
{ name: 'условие1,условие2,...', description: 'Логические выражения' },
|
||||
],
|
||||
examples: [
|
||||
{ formula: '=И(БОЛЬШЕ(5;3),МЕНЬШЕ(2;4))', result: 'ИСТИНА' },
|
||||
{ formula: '=ЕСЛИ(И(A1>0,A1<100),"ОК","Ошибка")', result: '"ОК" если 0 < A1 < 100' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'ИЛИ',
|
||||
category: 'Логические',
|
||||
description: 'Возвращает ИСТИНА, если хотя бы один аргумент истинен.',
|
||||
syntax: 'ИЛИ(условие1,условие2,...)',
|
||||
args: [
|
||||
{ name: 'условие1,условие2,...', description: 'Логические выражения' },
|
||||
],
|
||||
examples: [
|
||||
{ formula: '=ИЛИ(БОЛЬШЕ(1;5),БОЛЬШЕ(10;5))', result: 'ИСТИНА' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'НЕ',
|
||||
category: 'Логические',
|
||||
description: 'Инвертирует логическое значение.',
|
||||
syntax: 'НЕ(значение)',
|
||||
args: [
|
||||
{ name: 'значение', description: 'Логическое значение для инверсии' },
|
||||
],
|
||||
examples: [
|
||||
{ formula: '=НЕ(ИСТИНА)', result: 'ЛОЖЬ' },
|
||||
{ formula: '=НЕ(БОЛЬШЕ(1;5))', result: 'ИСТИНА' },
|
||||
],
|
||||
},
|
||||
|
||||
// Сравнение
|
||||
{
|
||||
name: 'БОЛЬШЕ',
|
||||
category: 'Сравнение',
|
||||
description: 'Проверяет, больше ли первое число второго.',
|
||||
syntax: 'БОЛЬШЕ(число1,число2)',
|
||||
args: [
|
||||
{ name: 'число1', description: 'Первое число' },
|
||||
{ name: 'число2', description: 'Второе число' },
|
||||
],
|
||||
examples: [
|
||||
{ formula: '=БОЛЬШЕ(10,5)', result: 'ИСТИНА' },
|
||||
{ formula: '=БОЛЬШЕ(3,7)', result: 'ЛОЖЬ' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'МЕНЬШЕ',
|
||||
category: 'Сравнение',
|
||||
description: 'Проверяет, меньше ли первое число второго.',
|
||||
syntax: 'МЕНЬШЕ(число1,число2)',
|
||||
args: [
|
||||
{ name: 'число1', description: 'Первое число' },
|
||||
{ name: 'число2', description: 'Второе число' },
|
||||
],
|
||||
examples: [
|
||||
{ formula: '=МЕНЬШЕ(3,7)', result: 'ИСТИНА' },
|
||||
{ formula: '=МЕНЬШЕ(10,5)', result: 'ЛОЖЬ' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'ЕПУСТО',
|
||||
category: 'Сравнение',
|
||||
description: 'Проверяет, является ли значение пустым (null, undefined или пустая строка).',
|
||||
syntax: 'ЕПУСТО(значение)',
|
||||
args: [
|
||||
{ name: 'значение', description: 'Значение для проверки' },
|
||||
],
|
||||
examples: [
|
||||
{ formula: '=ЕПУСТО(A1)', result: 'ИСТИНА если A1 пуста' },
|
||||
{ formula: '=ЕСЛИ(ЕПУСТО(A1),"Пусто",A1)', result: 'Значение A1 или "Пусто"' },
|
||||
],
|
||||
},
|
||||
|
||||
// Текстовые
|
||||
{
|
||||
name: 'СЦЕПИТЬ',
|
||||
category: 'Текстовые',
|
||||
description: 'Объединяет несколько текстовых строк в одну.',
|
||||
syntax: 'СЦЕПИТЬ(текст1,текст2,...)',
|
||||
args: [
|
||||
{ name: 'текст1,текст2,...', description: 'Строки для объединения' },
|
||||
],
|
||||
examples: [
|
||||
{ formula: '=СЦЕПИТЬ("Привет"," ","мир")', result: '"Привет мир"' },
|
||||
{ formula: '=СЦЕПИТЬ(A1," - ",B1)', result: 'Значения A1 и B1 через дефис' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'ДЛСТР',
|
||||
category: 'Текстовые',
|
||||
description: 'Возвращает длину текстовой строки.',
|
||||
syntax: 'ДЛСТР(текст)',
|
||||
args: [
|
||||
{ name: 'текст', description: 'Строка для подсчёта длины' },
|
||||
],
|
||||
examples: [
|
||||
{ formula: '=ДЛСТР("Привет")', result: '6' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'ПРОПИСН',
|
||||
category: 'Текстовые',
|
||||
description: 'Преобразует текст в верхний регистр.',
|
||||
syntax: 'ПРОПИСН(текст)',
|
||||
args: [
|
||||
{ name: 'текст', description: 'Строка для преобразования' },
|
||||
],
|
||||
examples: [
|
||||
{ formula: '=ПРОПИСН("привет")', result: '"ПРИВЕТ"' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'СТРОЧН',
|
||||
category: 'Текстовые',
|
||||
description: 'Преобразует текст в нижний регистр.',
|
||||
syntax: 'СТРОЧН(текст)',
|
||||
args: [
|
||||
{ name: 'текст', description: 'Строка для преобразования' },
|
||||
],
|
||||
examples: [
|
||||
{ formula: '=СТРОЧН("ПРИВЕТ")', result: '"привет"' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'СЖПРОБЕЛЫ',
|
||||
category: 'Текстовые',
|
||||
description: 'Удаляет начальные и конечные пробелы из текста.',
|
||||
syntax: 'СЖПРОБЕЛЫ(текст)',
|
||||
args: [
|
||||
{ name: 'текст', description: 'Строка для обработки' },
|
||||
],
|
||||
examples: [
|
||||
{ formula: '=СЖПРОБЕЛЫ(" Привет ")', result: '"Привет"' },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const CATEGORY_ICONS: Record<string, FC<{ className?: string }>> = {
|
||||
'Случайные числа': Hash,
|
||||
'Математические': Calculator,
|
||||
'Округление': Sigma,
|
||||
'Логические': BookOpen,
|
||||
'Сравнение': Search,
|
||||
'Текстовые': Type,
|
||||
}
|
||||
|
||||
const CATEGORY_ORDER = [
|
||||
'Математические',
|
||||
'Округление',
|
||||
'Случайные числа',
|
||||
'Логические',
|
||||
'Сравнение',
|
||||
'Текстовые',
|
||||
]
|
||||
|
||||
export const FunctionsWikiPage: FC = () => {
|
||||
const navigate = useNavigate()
|
||||
const [search, setSearch] = useState('')
|
||||
const [activeCategory, setActiveCategory] = useState<string | null>(null)
|
||||
|
||||
const filtered = useMemo(() => {
|
||||
const q = search.toLowerCase().trim()
|
||||
return FUNCTION_CATALOG.filter(fn => {
|
||||
const matchesSearch = !q || fn.name.toLowerCase().includes(q) || fn.description.toLowerCase().includes(q)
|
||||
const matchesCategory = !activeCategory || fn.category === activeCategory
|
||||
return matchesSearch && matchesCategory
|
||||
})
|
||||
}, [search, activeCategory])
|
||||
|
||||
const grouped = useMemo(() => {
|
||||
const map = new Map<string, FunctionInfo[]>()
|
||||
for (const fn of filtered) {
|
||||
const list = map.get(fn.category) || []
|
||||
list.push(fn)
|
||||
map.set(fn.category, list)
|
||||
}
|
||||
return CATEGORY_ORDER
|
||||
.filter(cat => map.has(cat))
|
||||
.map(cat => ({ category: cat, functions: map.get(cat)! }))
|
||||
}, [filtered])
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
{/* Header */}
|
||||
<div className="sticky top-0 z-10 border-b bg-white shadow-sm">
|
||||
<div className="mx-auto max-w-5xl px-4 py-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<button
|
||||
onClick={() => navigate(-1)}
|
||||
className="rounded-md p-1.5 text-gray-500 hover:bg-gray-100 hover:text-gray-700"
|
||||
>
|
||||
<ArrowLeft className="h-5 w-5" />
|
||||
</button>
|
||||
<div>
|
||||
<h1 className="text-lg font-semibold text-gray-900">Справочник функций</h1>
|
||||
<p className="text-sm text-gray-500">
|
||||
{FUNCTION_CATALOG.length} функций в {CATEGORY_ORDER.length} категориях
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mx-auto max-w-5xl px-4 py-6">
|
||||
{/* Operators reference */}
|
||||
<div className="mb-6 rounded-lg border border-blue-200 bg-blue-50 p-4">
|
||||
<h3 className="mb-2 text-sm font-semibold text-blue-800">Операторы сравнения (как в Excel)</h3>
|
||||
<div className="grid grid-cols-2 gap-x-6 gap-y-1 text-sm sm:grid-cols-3">
|
||||
<div><code className="font-medium text-blue-700">=</code> <span className="text-gray-600">равно</span></div>
|
||||
<div><code className="font-medium text-blue-700">{'<>'}</code> <span className="text-gray-600">не равно</span></div>
|
||||
<div><code className="font-medium text-blue-700">{'>'}</code> <span className="text-gray-600">больше</span></div>
|
||||
<div><code className="font-medium text-blue-700">{'<'}</code> <span className="text-gray-600">меньше</span></div>
|
||||
<div><code className="font-medium text-blue-700">{'>='}</code> <span className="text-gray-600">больше или равно</span></div>
|
||||
<div><code className="font-medium text-blue-700">{'<='}</code> <span className="text-gray-600">меньше или равно</span></div>
|
||||
</div>
|
||||
<p className="mt-2 text-xs text-gray-500">Пример: <code className="text-blue-700">=ЕСЛИ(A1{'<>'}0, "Не ноль", "Ноль")</code></p>
|
||||
</div>
|
||||
|
||||
{/* Search & Filters */}
|
||||
<div className="mb-6 flex flex-col gap-3 sm:flex-row sm:items-center">
|
||||
<div className="relative flex-1">
|
||||
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-gray-400" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Поиск функций..."
|
||||
value={search}
|
||||
onChange={e => setSearch(e.target.value)}
|
||||
className="w-full rounded-lg border border-gray-300 bg-white py-2 pl-10 pr-4 text-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
<button
|
||||
onClick={() => setActiveCategory(null)}
|
||||
className={`rounded-full px-3 py-1 text-xs font-medium transition-colors ${
|
||||
!activeCategory
|
||||
? 'bg-blue-100 text-blue-700'
|
||||
: 'bg-gray-100 text-gray-600 hover:bg-gray-200'
|
||||
}`}
|
||||
>
|
||||
Все
|
||||
</button>
|
||||
{CATEGORY_ORDER.map(cat => {
|
||||
const Icon = CATEGORY_ICONS[cat]
|
||||
return (
|
||||
<button
|
||||
key={cat}
|
||||
onClick={() => setActiveCategory(activeCategory === cat ? null : cat)}
|
||||
className={`flex items-center gap-1 rounded-full px-3 py-1 text-xs font-medium transition-colors ${
|
||||
activeCategory === cat
|
||||
? 'bg-blue-100 text-blue-700'
|
||||
: 'bg-gray-100 text-gray-600 hover:bg-gray-200'
|
||||
}`}
|
||||
>
|
||||
{Icon && <Icon className="h-3 w-3" />}
|
||||
{cat}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Results */}
|
||||
{grouped.length === 0 ? (
|
||||
<div className="py-12 text-center text-gray-500">
|
||||
Функции не найдены. Попробуйте изменить запрос.
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-8">
|
||||
{grouped.map(({ category, functions }) => {
|
||||
const Icon = CATEGORY_ICONS[category]
|
||||
return (
|
||||
<section key={category}>
|
||||
<div className="mb-3 flex items-center gap-2">
|
||||
{Icon && <Icon className="h-5 w-5 text-gray-400" />}
|
||||
<h2 className="text-base font-semibold text-gray-800">{category}</h2>
|
||||
<span className="rounded-full bg-gray-100 px-2 py-0.5 text-xs text-gray-500">
|
||||
{functions.length}
|
||||
</span>
|
||||
</div>
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
{functions.map(fn => (
|
||||
<FunctionCard key={fn.name} fn={fn} />
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const FunctionCard: FC<{ fn: FunctionInfo }> = ({ fn }) => {
|
||||
return (
|
||||
<div className="rounded-lg border border-gray-200 bg-white p-4 shadow-sm transition-shadow hover:shadow-md">
|
||||
<div className="mb-2 flex items-start justify-between gap-2">
|
||||
<code className="rounded bg-blue-50 px-2 py-0.5 text-sm font-bold text-blue-700">
|
||||
{fn.name}
|
||||
</code>
|
||||
{fn.notes && (
|
||||
<span className="shrink-0 rounded-full bg-amber-50 px-2 py-0.5 text-[10px] font-medium text-amber-600">
|
||||
{fn.notes.length > 30 ? fn.notes.slice(0, 30) + '...' : fn.notes}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<p className="mb-2 text-sm text-gray-600">{fn.description}</p>
|
||||
|
||||
{/* Syntax */}
|
||||
<div className="mb-2">
|
||||
<div className="mb-1 text-[11px] font-medium uppercase tracking-wide text-gray-400">
|
||||
Синтаксис
|
||||
</div>
|
||||
<code className="block rounded bg-gray-50 px-2 py-1 text-xs text-gray-700">
|
||||
={fn.syntax}
|
||||
</code>
|
||||
</div>
|
||||
|
||||
{/* Args */}
|
||||
{fn.args.length > 0 && (
|
||||
<div className="mb-2">
|
||||
<div className="mb-1 text-[11px] font-medium uppercase tracking-wide text-gray-400">
|
||||
Аргументы
|
||||
</div>
|
||||
<div className="space-y-0.5">
|
||||
{fn.args.map(arg => (
|
||||
<div key={arg.name} className="flex gap-1.5 text-xs">
|
||||
<code className="shrink-0 font-medium text-gray-700">{arg.name}</code>
|
||||
{arg.optional && (
|
||||
<span className="shrink-0 text-[10px] text-gray-400">(необяз.)</span>
|
||||
)}
|
||||
<span className="text-gray-500">— {arg.description}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Examples */}
|
||||
<div>
|
||||
<div className="mb-1 text-[11px] font-medium uppercase tracking-wide text-gray-400">
|
||||
Примеры
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
{fn.examples.map((ex, i) => (
|
||||
<div key={i} className="flex items-center gap-2 text-xs">
|
||||
<code className="rounded bg-green-50 px-1.5 py-0.5 text-green-700">{ex.formula}</code>
|
||||
<span className="text-gray-400">→</span>
|
||||
<span className="text-gray-600">{ex.result}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { FunctionsWikiPage } from './functions-wiki/Page'
|
||||
import { StandardsManagementPage } from './standards-management/Page'
|
||||
import { TemplateEditPage } from './templates/edit/Page'
|
||||
import { ElementsCreation } from './templates/elements/Page'
|
||||
@@ -6,6 +7,7 @@ import { ProtocolCreation } from './templates/protocols/Page'
|
||||
|
||||
export {
|
||||
ElementsCreation,
|
||||
FunctionsWikiPage,
|
||||
ProtocolCreation,
|
||||
StandardsManagementPage,
|
||||
TemplateEditPage,
|
||||
|
||||
@@ -38,6 +38,7 @@ import {
|
||||
import { arrayMove, sortableKeyboardCoordinates } from '@dnd-kit/sortable'
|
||||
import clsx from 'clsx'
|
||||
import {
|
||||
BookOpen,
|
||||
CheckSquare,
|
||||
Copy,
|
||||
Edit3,
|
||||
@@ -49,6 +50,7 @@ import {
|
||||
X,
|
||||
} from 'lucide-react'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { Button } from 'shared/ui/button'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from 'shared/ui/card'
|
||||
import { Checkbox } from 'shared/ui/checkbox'
|
||||
@@ -346,6 +348,7 @@ const TemplateForm: React.FC<TemplateFormProps> = ({
|
||||
}
|
||||
|
||||
const TemplatesPageContent = () => {
|
||||
const navigate = useNavigate()
|
||||
const {
|
||||
templates,
|
||||
deleteTemplate,
|
||||
@@ -935,6 +938,15 @@ const TemplatesPageContent = () => {
|
||||
<SidebarTrigger />
|
||||
<Separator orientation="vertical" className="mr-2 h-4" />
|
||||
<h1 className="text-lg font-semibold">Карточки протоколов</h1>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => navigate('/functions-wiki')}
|
||||
className="ml-2"
|
||||
>
|
||||
<BookOpen className="mr-1 h-4 w-4" />
|
||||
Справочник функций
|
||||
</Button>
|
||||
|
||||
<div className="ml-auto flex items-center gap-2">
|
||||
{/* Кнопка сохранения порядка */}
|
||||
|
||||
@@ -29,6 +29,8 @@ const ProtocolForm: FC<ProtocolFormProps> = ({ template, onSave, onBack }) => {
|
||||
const [showSpreadsheet, setShowSpreadsheet] = useState(false)
|
||||
const [isInitialized, setIsInitialized] = useState(false)
|
||||
const [isSavingProtocol, setIsSavingProtocol] = useState(false)
|
||||
// Track spreadsheet engine readiness so initial form data syncs once it mounts
|
||||
const [isEngineReady, setIsEngineReady] = useState(false)
|
||||
const engineRef = useRef<any>(null)
|
||||
const toast = useToast()
|
||||
const navigate = useNavigate()
|
||||
@@ -104,7 +106,7 @@ const ProtocolForm: FC<ProtocolFormProps> = ({ template, onSave, onBack }) => {
|
||||
) {
|
||||
syncFormDataToEngine()
|
||||
}
|
||||
}, [isInitialized, formData, template.elements])
|
||||
}, [isInitialized, formData, template.elements, isEngineReady])
|
||||
|
||||
const handleFieldChange = (elementId: string, value: any) => {
|
||||
// Обновляем только состояние формы
|
||||
@@ -358,7 +360,17 @@ const ProtocolForm: FC<ProtocolFormProps> = ({ template, onSave, onBack }) => {
|
||||
templateId={template.id}
|
||||
onEngineReady={engine => {
|
||||
engineRef.current = engine
|
||||
// Синхронизация произойдёт автоматически через useEffect
|
||||
if (!isEngineReady) {
|
||||
setIsEngineReady(true)
|
||||
}
|
||||
|
||||
// Immediately push any captured form data into the engine after it appears
|
||||
if (
|
||||
isInitialized &&
|
||||
Object.keys(formData).length > 0
|
||||
) {
|
||||
syncFormDataToEngine()
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -14,16 +14,24 @@
|
||||
"outDir": "./dist/",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"],
|
||||
"shared/*": ["./shared/*"]
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
],
|
||||
"shared/*": [
|
||||
"./shared/*"
|
||||
]
|
||||
},
|
||||
"skipLibCheck": true
|
||||
},
|
||||
|
||||
"ts-node": {
|
||||
"compilerOptions": {
|
||||
"module": "CommonJS"
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules", "dist", "build", "coverage"]
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist",
|
||||
"build",
|
||||
"coverage"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ export default defineConfig({
|
||||
},
|
||||
server: {
|
||||
host: '0.0.0.0',
|
||||
port: 1111,
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:8000',
|
||||
|
||||
Reference in New Issue
Block a user