Compare commits
22 Commits
643aeace27
...
3c27e04aa0
| Author | SHA1 | Date |
|---|---|---|
|
|
3c27e04aa0 | |
|
|
d2154214ba | |
|
|
97312b97f8 | |
|
|
5e2b041f84 | |
|
|
ec97f49919 | |
|
|
4910fff7fb | |
|
|
fc7655c808 | |
|
|
ae2ac9d50f | |
|
|
8932492fd3 | |
|
|
a168e7b648 | |
|
|
1adcb03b93 | |
|
|
b6e737dc76 | |
|
|
2fa6ecc7ef | |
|
|
f744b5711f | |
|
|
2b3d72bb73 | |
|
|
3205eb6925 | |
|
|
d4fcc694a6 | |
|
|
96e11c1b08 | |
|
|
c8d8d92d82 | |
|
|
b96290e778 | |
|
|
bd48e5f6e5 | |
|
|
630116e37c |
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Copilot Instructions for Misskey
|
||||||
|
|
||||||
|
- en-US.yml を編集しないでください。
|
||||||
|
|
@ -54,55 +54,110 @@ jobs:
|
||||||
BASE_MEMORY=$(cat ./artifacts/memory-base.json)
|
BASE_MEMORY=$(cat ./artifacts/memory-base.json)
|
||||||
HEAD_MEMORY=$(cat ./artifacts/memory-head.json)
|
HEAD_MEMORY=$(cat ./artifacts/memory-head.json)
|
||||||
|
|
||||||
BASE_RSS=$(echo "$BASE_MEMORY" | jq -r '.memory.rss // 0')
|
variation() {
|
||||||
HEAD_RSS=$(echo "$HEAD_MEMORY" | jq -r '.memory.rss // 0')
|
calc() {
|
||||||
|
BASE=$(echo "$BASE_MEMORY" | jq -r ".${1}.${2} // 0")
|
||||||
|
HEAD=$(echo "$HEAD_MEMORY" | jq -r ".${1}.${2} // 0")
|
||||||
|
|
||||||
# Calculate difference
|
DIFF=$((HEAD - BASE))
|
||||||
if [ "$BASE_RSS" -gt 0 ] && [ "$HEAD_RSS" -gt 0 ]; then
|
if [ "$BASE" -gt 0 ]; then
|
||||||
DIFF=$((HEAD_RSS - BASE_RSS))
|
DIFF_PERCENT=$(echo "scale=2; ($DIFF * 100) / $BASE" | bc)
|
||||||
DIFF_PERCENT=$(echo "scale=2; ($DIFF * 100) / $BASE_RSS" | bc)
|
else
|
||||||
|
DIFF_PERCENT=0
|
||||||
|
fi
|
||||||
|
|
||||||
# Convert to MB for readability
|
# Convert KB to MB for readability
|
||||||
BASE_MB=$(echo "scale=2; $BASE_RSS / 1048576" | bc)
|
BASE_MB=$(echo "scale=2; $BASE / 1024" | bc)
|
||||||
HEAD_MB=$(echo "scale=2; $HEAD_RSS / 1048576" | bc)
|
HEAD_MB=$(echo "scale=2; $HEAD / 1024" | bc)
|
||||||
DIFF_MB=$(echo "scale=2; $DIFF / 1048576" | bc)
|
DIFF_MB=$(echo "scale=2; $DIFF / 1024" | bc)
|
||||||
|
|
||||||
echo "base_mb=$BASE_MB" >> "$GITHUB_OUTPUT"
|
JSON=$(jq -c -n \
|
||||||
echo "head_mb=$HEAD_MB" >> "$GITHUB_OUTPUT"
|
--argjson base "$BASE_MB" \
|
||||||
echo "diff_mb=$DIFF_MB" >> "$GITHUB_OUTPUT"
|
--argjson head "$HEAD_MB" \
|
||||||
echo "diff_percent=$DIFF_PERCENT" >> "$GITHUB_OUTPUT"
|
--argjson diff "$DIFF_MB" \
|
||||||
echo "has_data=true" >> "$GITHUB_OUTPUT"
|
--argjson diff_percent "$DIFF_PERCENT" \
|
||||||
|
'{base: $base, head: $head, diff: $diff, diff_percent: $diff_percent}')
|
||||||
|
|
||||||
# Determine if this is a significant change (more than 5% increase)
|
echo "$JSON"
|
||||||
if [ "$(echo "$DIFF_PERCENT > 5" | bc)" -eq 1 ]; then
|
}
|
||||||
echo "significant_increase=true" >> "$GITHUB_OUTPUT"
|
|
||||||
else
|
JSON=$(jq -c -n \
|
||||||
echo "significant_increase=false" >> "$GITHUB_OUTPUT"
|
--argjson VmRSS "$(calc $1 VmRSS)" \
|
||||||
fi
|
--argjson VmHWM "$(calc $1 VmHWM)" \
|
||||||
else
|
--argjson VmSize "$(calc $1 VmSize)" \
|
||||||
echo "has_data=false" >> "$GITHUB_OUTPUT"
|
--argjson VmData "$(calc $1 VmData)" \
|
||||||
fi
|
'{VmRSS: $VmRSS, VmHWM: $VmHWM, VmSize: $VmSize, VmData: $VmData}')
|
||||||
|
|
||||||
|
echo "$JSON"
|
||||||
|
}
|
||||||
|
|
||||||
|
JSON=$(jq -c -n \
|
||||||
|
--argjson beforeGc "$(variation beforeGc)" \
|
||||||
|
--argjson afterGc "$(variation afterGc)" \
|
||||||
|
--argjson afterRequest "$(variation afterRequest)" \
|
||||||
|
'{beforeGc: $beforeGc, afterGc: $afterGc, afterRequest: $afterRequest}')
|
||||||
|
|
||||||
|
echo "res=$JSON" >> "$GITHUB_OUTPUT"
|
||||||
- id: build-comment
|
- id: build-comment
|
||||||
name: Build memory comment
|
name: Build memory comment
|
||||||
|
env:
|
||||||
|
RES: ${{ steps.compare.outputs.res }}
|
||||||
run: |
|
run: |
|
||||||
HEADER="## Backend Memory Usage Comparison"
|
HEADER="## Backend memory usage comparison"
|
||||||
FOOTER="[See workflow logs for details](https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID})"
|
FOOTER="[See workflow logs for details](https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID})"
|
||||||
|
|
||||||
echo "$HEADER" > ./output.md
|
echo "$HEADER" > ./output.md
|
||||||
echo >> ./output.md
|
echo >> ./output.md
|
||||||
|
|
||||||
if [ "${{ steps.compare.outputs.has_data }}" == "true" ]; then
|
table() {
|
||||||
echo "| Metric | base | head | Diff |" >> ./output.md
|
echo "| Metric | base (MB) | head (MB) | Diff (MB) | Diff (%) |" >> ./output.md
|
||||||
echo "|--------|------|------|------|" >> ./output.md
|
echo "|--------|------:|------:|------:|------:|" >> ./output.md
|
||||||
echo "| RSS | ${{ steps.compare.outputs.base_mb }} MB | ${{ steps.compare.outputs.head_mb }} MB | ${{ steps.compare.outputs.diff_mb }} MB (${{ steps.compare.outputs.diff_percent }}%) |" >> ./output.md
|
|
||||||
echo >> ./output.md
|
|
||||||
|
|
||||||
if [ "${{ steps.compare.outputs.significant_increase }}" == "true" ]; then
|
line() {
|
||||||
echo "⚠️ **Warning**: Memory usage has increased by more than 5%. Please verify this is not an unintended change." >> ./output.md
|
METRIC=$2
|
||||||
echo >> ./output.md
|
BASE=$(echo "$RES" | jq -r ".${1}.${2}.base")
|
||||||
fi
|
HEAD=$(echo "$RES" | jq -r ".${1}.${2}.head")
|
||||||
else
|
DIFF=$(echo "$RES" | jq -r ".${1}.${2}.diff")
|
||||||
echo "Could not retrieve memory usage data." >> ./output.md
|
DIFF_PERCENT=$(echo "$RES" | jq -r ".${1}.${2}.diff_percent")
|
||||||
|
|
||||||
|
if (( $(echo "$DIFF_PERCENT > 0" | bc -l) )); then
|
||||||
|
DIFF="+$DIFF"
|
||||||
|
DIFF_PERCENT="+$DIFF_PERCENT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# highlight VmRSS
|
||||||
|
if [ "$2" = "VmRSS" ]; then
|
||||||
|
METRIC="**${METRIC}**"
|
||||||
|
BASE="**${BASE}**"
|
||||||
|
HEAD="**${HEAD}**"
|
||||||
|
DIFF="**${DIFF}**"
|
||||||
|
DIFF_PERCENT="**${DIFF_PERCENT}**"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "| ${METRIC} | ${BASE} MB | ${HEAD} MB | ${DIFF} MB | ${DIFF_PERCENT}% |" >> ./output.md
|
||||||
|
}
|
||||||
|
|
||||||
|
line $1 VmRSS
|
||||||
|
line $1 VmHWM
|
||||||
|
line $1 VmSize
|
||||||
|
line $1 VmData
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "### Before GC" >> ./output.md
|
||||||
|
table beforeGc
|
||||||
|
echo >> ./output.md
|
||||||
|
|
||||||
|
echo "### After GC" >> ./output.md
|
||||||
|
table afterGc
|
||||||
|
echo >> ./output.md
|
||||||
|
|
||||||
|
echo "### After Request" >> ./output.md
|
||||||
|
table afterRequest
|
||||||
|
echo >> ./output.md
|
||||||
|
|
||||||
|
# Determine if this is a significant change (more than 5% increase)
|
||||||
|
if [ "$(echo "$RES" | jq -r '.afterGc.VmRSS.diff_percent | tonumber > 5')" = "true" ]; then
|
||||||
|
echo "⚠️ **Warning**: Memory usage has increased by more than 5%. Please verify this is not an unintended change." >> ./output.md
|
||||||
echo >> ./output.md
|
echo >> ./output.md
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@
|
||||||
- Fix: ファイルタブのセンシティブメディアを開く際に確認ダイアログを出す設定が適用されない問題を修正
|
- Fix: ファイルタブのセンシティブメディアを開く際に確認ダイアログを出す設定が適用されない問題を修正
|
||||||
- Fix: 2月29日を誕生日に設定している場合、閏年以外は3月1日を誕生日として扱うように修正
|
- Fix: 2月29日を誕生日に設定している場合、閏年以外は3月1日を誕生日として扱うように修正
|
||||||
- Fix: `Mk:C:container` の `borderWidth` が正しく反映されない問題を修正
|
- Fix: `Mk:C:container` の `borderWidth` が正しく反映されない問題を修正
|
||||||
|
- Fix: mCaptchaが正しく動作しない問題を修正
|
||||||
|
- Fix: 非ログイン時にリバーシの対局が表示されない問題を修正
|
||||||
|
|
||||||
### Server
|
### Server
|
||||||
- Enhance: OAuthのクライアント情報取得(Client Information Discovery)において、IndieWeb Living Standard 11 July 2024で定義されているJSONドキュメント形式に対応しました
|
- Enhance: OAuthのクライアント情報取得(Client Information Discovery)において、IndieWeb Living Standard 11 July 2024で定義されているJSONドキュメント形式に対応しました
|
||||||
|
|
|
||||||
26
package.json
26
package.json
|
|
@ -6,7 +6,7 @@
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/misskey-dev/misskey.git"
|
"url": "https://github.com/misskey-dev/misskey.git"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@10.27.0",
|
"packageManager": "pnpm@10.28.2",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/misskey-js",
|
"packages/misskey-js",
|
||||||
"packages/i18n",
|
"packages/i18n",
|
||||||
|
|
@ -52,10 +52,6 @@
|
||||||
"clean-all": "node scripts/clean-all.mjs",
|
"clean-all": "node scripts/clean-all.mjs",
|
||||||
"cleanall": "pnpm clean-all"
|
"cleanall": "pnpm clean-all"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
|
||||||
"chokidar": "5.0.0",
|
|
||||||
"lodash": "4.17.21"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cssnano": "7.1.2",
|
"cssnano": "7.1.2",
|
||||||
"esbuild": "0.27.2",
|
"esbuild": "0.27.2",
|
||||||
|
|
@ -63,23 +59,23 @@
|
||||||
"ignore-walk": "8.0.0",
|
"ignore-walk": "8.0.0",
|
||||||
"js-yaml": "4.1.1",
|
"js-yaml": "4.1.1",
|
||||||
"postcss": "8.5.6",
|
"postcss": "8.5.6",
|
||||||
"tar": "7.5.2",
|
"tar": "7.5.7",
|
||||||
"terser": "5.44.1"
|
"terser": "5.46.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "9.39.2",
|
"@eslint/js": "9.39.2",
|
||||||
"@misskey-dev/eslint-plugin": "2.2.0",
|
"@misskey-dev/eslint-plugin": "2.2.0",
|
||||||
"@types/js-yaml": "4.0.9",
|
"@types/js-yaml": "4.0.9",
|
||||||
"@types/node": "24.10.4",
|
"@types/node": "24.10.9",
|
||||||
"@typescript-eslint/eslint-plugin": "8.50.1",
|
"@typescript-eslint/eslint-plugin": "8.53.0",
|
||||||
"@typescript-eslint/parser": "8.50.1",
|
"@typescript-eslint/parser": "8.53.0",
|
||||||
"@typescript/native-preview": "7.0.0-dev.20251226.1",
|
"@typescript/native-preview": "7.0.0-dev.20260116.1",
|
||||||
"cross-env": "10.1.0",
|
"cross-env": "10.1.0",
|
||||||
"cypress": "15.8.1",
|
"cypress": "15.9.0",
|
||||||
"eslint": "9.39.2",
|
"eslint": "9.39.2",
|
||||||
"globals": "16.5.0",
|
"globals": "16.5.0",
|
||||||
"ncp": "2.0.0",
|
"ncp": "2.0.0",
|
||||||
"pnpm": "10.27.0",
|
"pnpm": "10.28.2",
|
||||||
"typescript": "5.9.3",
|
"typescript": "5.9.3",
|
||||||
"start-server-and-test": "2.1.3"
|
"start-server-and-test": "2.1.3"
|
||||||
},
|
},
|
||||||
|
|
@ -88,7 +84,9 @@
|
||||||
},
|
},
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"@aiscript-dev/aiscript-languageserver": "-"
|
"@aiscript-dev/aiscript-languageserver": "-",
|
||||||
|
"chokidar": "5.0.0",
|
||||||
|
"lodash": "4.17.23"
|
||||||
},
|
},
|
||||||
"ignoredBuiltDependencies": [
|
"ignoredBuiltDependencies": [
|
||||||
"@sentry-internal/node-cpu-profiler",
|
"@sentry-internal/node-cpu-profiler",
|
||||||
|
|
|
||||||
|
|
@ -41,17 +41,17 @@
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@swc/core-android-arm64": "1.3.11",
|
"@swc/core-android-arm64": "1.3.11",
|
||||||
"@swc/core-darwin-arm64": "1.15.7",
|
"@swc/core-darwin-arm64": "1.15.8",
|
||||||
"@swc/core-darwin-x64": "1.15.7",
|
"@swc/core-darwin-x64": "1.15.8",
|
||||||
"@swc/core-freebsd-x64": "1.3.11",
|
"@swc/core-freebsd-x64": "1.3.11",
|
||||||
"@swc/core-linux-arm-gnueabihf": "1.15.7",
|
"@swc/core-linux-arm-gnueabihf": "1.15.8",
|
||||||
"@swc/core-linux-arm64-gnu": "1.15.7",
|
"@swc/core-linux-arm64-gnu": "1.15.8",
|
||||||
"@swc/core-linux-arm64-musl": "1.15.7",
|
"@swc/core-linux-arm64-musl": "1.15.8",
|
||||||
"@swc/core-linux-x64-gnu": "1.15.7",
|
"@swc/core-linux-x64-gnu": "1.15.8",
|
||||||
"@swc/core-linux-x64-musl": "1.15.7",
|
"@swc/core-linux-x64-musl": "1.15.8",
|
||||||
"@swc/core-win32-arm64-msvc": "1.15.7",
|
"@swc/core-win32-arm64-msvc": "1.15.8",
|
||||||
"@swc/core-win32-ia32-msvc": "1.15.7",
|
"@swc/core-win32-ia32-msvc": "1.15.8",
|
||||||
"@swc/core-win32-x64-msvc": "1.15.7",
|
"@swc/core-win32-x64-msvc": "1.15.8",
|
||||||
"@tensorflow/tfjs": "4.22.0",
|
"@tensorflow/tfjs": "4.22.0",
|
||||||
"@tensorflow/tfjs-node": "4.22.0",
|
"@tensorflow/tfjs-node": "4.22.0",
|
||||||
"bufferutil": "4.1.0",
|
"bufferutil": "4.1.0",
|
||||||
|
|
@ -71,40 +71,39 @@
|
||||||
"utf-8-validate": "6.0.6"
|
"utf-8-validate": "6.0.6"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "3.958.0",
|
"@aws-sdk/client-s3": "3.970.0",
|
||||||
"@aws-sdk/lib-storage": "3.958.0",
|
"@aws-sdk/lib-storage": "3.970.0",
|
||||||
"@discordapp/twemoji": "16.0.1",
|
"@discordapp/twemoji": "16.0.1",
|
||||||
"@fastify/accepts": "5.0.4",
|
"@fastify/accepts": "5.0.4",
|
||||||
"@fastify/cors": "11.2.0",
|
"@fastify/cors": "11.2.0",
|
||||||
"@fastify/express": "4.0.2",
|
"@fastify/express": "4.0.4",
|
||||||
"@fastify/http-proxy": "11.4.1",
|
"@fastify/http-proxy": "11.4.1",
|
||||||
"@fastify/multipart": "9.3.0",
|
"@fastify/multipart": "9.3.0",
|
||||||
"@fastify/static": "8.3.0",
|
"@fastify/static": "8.3.0",
|
||||||
"@kitajs/html": "4.2.11",
|
"@kitajs/html": "4.2.11",
|
||||||
"@misskey-dev/sharp-read-bmp": "1.2.0",
|
"@misskey-dev/sharp-read-bmp": "1.2.0",
|
||||||
"@misskey-dev/summaly": "5.2.5",
|
"@misskey-dev/summaly": "5.2.5",
|
||||||
"@napi-rs/canvas": "0.1.87",
|
"@napi-rs/canvas": "0.1.88",
|
||||||
"@nestjs/common": "11.1.10",
|
"@nestjs/common": "11.1.12",
|
||||||
"@nestjs/core": "11.1.10",
|
"@nestjs/core": "11.1.12",
|
||||||
"@nestjs/testing": "11.1.10",
|
"@nestjs/testing": "11.1.12",
|
||||||
"@peertube/http-signature": "1.7.0",
|
"@peertube/http-signature": "1.7.0",
|
||||||
"@sentry/node": "10.32.1",
|
"@sentry/node": "10.34.0",
|
||||||
"@sentry/profiling-node": "10.32.1",
|
"@sentry/profiling-node": "10.34.0",
|
||||||
"@simplewebauthn/server": "13.2.2",
|
"@simplewebauthn/server": "13.2.2",
|
||||||
"@sinonjs/fake-timers": "15.1.0",
|
"@sinonjs/fake-timers": "15.1.0",
|
||||||
"@smithy/node-http-handler": "4.4.7",
|
"@smithy/node-http-handler": "4.4.8",
|
||||||
"@swc/cli": "0.7.9",
|
"@swc/cli": "0.7.10",
|
||||||
"@swc/core": "1.15.7",
|
"@swc/core": "1.15.8",
|
||||||
"@twemoji/parser": "16.0.0",
|
"@twemoji/parser": "16.0.0",
|
||||||
"@types/redis-info": "3.0.3",
|
|
||||||
"accepts": "1.3.8",
|
"accepts": "1.3.8",
|
||||||
"ajv": "8.17.1",
|
"ajv": "8.17.1",
|
||||||
"archiver": "7.0.1",
|
"archiver": "7.0.1",
|
||||||
"async-mutex": "0.5.0",
|
"async-mutex": "0.5.0",
|
||||||
"bcryptjs": "3.0.3",
|
"bcryptjs": "3.0.3",
|
||||||
"blurhash": "2.0.5",
|
"blurhash": "2.0.5",
|
||||||
"body-parser": "2.2.1",
|
"body-parser": "2.2.2",
|
||||||
"bullmq": "5.66.3",
|
"bullmq": "5.66.5",
|
||||||
"cacheable-lookup": "7.0.0",
|
"cacheable-lookup": "7.0.0",
|
||||||
"chalk": "5.6.2",
|
"chalk": "5.6.2",
|
||||||
"chalk-template": "1.1.2",
|
"chalk-template": "1.1.2",
|
||||||
|
|
@ -113,24 +112,24 @@
|
||||||
"content-disposition": "1.0.1",
|
"content-disposition": "1.0.1",
|
||||||
"date-fns": "4.1.0",
|
"date-fns": "4.1.0",
|
||||||
"deep-email-validator": "0.1.21",
|
"deep-email-validator": "0.1.21",
|
||||||
"fastify": "5.6.2",
|
"fastify": "5.7.1",
|
||||||
"fastify-raw-body": "5.0.0",
|
"fastify-raw-body": "5.0.0",
|
||||||
"feed": "5.1.0",
|
"feed": "5.2.0",
|
||||||
"file-type": "21.2.0",
|
"file-type": "21.3.0",
|
||||||
"fluent-ffmpeg": "2.1.3",
|
"fluent-ffmpeg": "2.1.3",
|
||||||
"form-data": "4.0.5",
|
"form-data": "4.0.5",
|
||||||
"got": "14.6.5",
|
"got": "14.6.6",
|
||||||
"hpagent": "1.2.0",
|
"hpagent": "1.2.0",
|
||||||
"http-link-header": "1.1.3",
|
"http-link-header": "1.1.3",
|
||||||
"i18n": "workspace:*",
|
"i18n": "workspace:*",
|
||||||
"ioredis": "5.8.2",
|
"ioredis": "5.9.2",
|
||||||
"ip-cidr": "4.0.2",
|
"ip-cidr": "4.0.2",
|
||||||
"ipaddr.js": "2.3.0",
|
"ipaddr.js": "2.3.0",
|
||||||
"is-svg": "6.1.0",
|
"is-svg": "6.1.0",
|
||||||
"json5": "2.2.3",
|
"json5": "2.2.3",
|
||||||
"jsonld": "9.0.0",
|
"jsonld": "9.0.0",
|
||||||
"juice": "11.0.3",
|
"juice": "11.1.0",
|
||||||
"meilisearch": "0.54.0",
|
"meilisearch": "0.55.0",
|
||||||
"mfm-js": "0.25.0",
|
"mfm-js": "0.25.0",
|
||||||
"mime-types": "3.0.2",
|
"mime-types": "3.0.2",
|
||||||
"misskey-js": "workspace:*",
|
"misskey-js": "workspace:*",
|
||||||
|
|
@ -139,14 +138,14 @@
|
||||||
"nanoid": "5.1.6",
|
"nanoid": "5.1.6",
|
||||||
"nested-property": "4.0.0",
|
"nested-property": "4.0.0",
|
||||||
"node-fetch": "3.3.2",
|
"node-fetch": "3.3.2",
|
||||||
"node-html-parser": "7.0.1",
|
"node-html-parser": "7.0.2",
|
||||||
"nodemailer": "7.0.12",
|
"nodemailer": "7.0.12",
|
||||||
"nsfwjs": "4.2.0",
|
"nsfwjs": "4.2.0",
|
||||||
"oauth2orize": "1.12.0",
|
"oauth2orize": "1.12.0",
|
||||||
"oauth2orize-pkce": "0.1.2",
|
"oauth2orize-pkce": "0.1.2",
|
||||||
"os-utils": "0.0.14",
|
"os-utils": "0.0.14",
|
||||||
"otpauth": "9.4.1",
|
"otpauth": "9.4.1",
|
||||||
"pg": "8.16.3",
|
"pg": "8.17.1",
|
||||||
"pkce-challenge": "5.0.1",
|
"pkce-challenge": "5.0.1",
|
||||||
"probe-image-size": "7.2.3",
|
"probe-image-size": "7.2.3",
|
||||||
"promise-limit": "2.7.0",
|
"promise-limit": "2.7.0",
|
||||||
|
|
@ -154,7 +153,6 @@
|
||||||
"random-seed": "0.3.0",
|
"random-seed": "0.3.0",
|
||||||
"ratelimiter": "3.4.1",
|
"ratelimiter": "3.4.1",
|
||||||
"re2": "1.23.0",
|
"re2": "1.23.0",
|
||||||
"redis-info": "3.1.0",
|
|
||||||
"reflect-metadata": "0.2.2",
|
"reflect-metadata": "0.2.2",
|
||||||
"rename": "1.0.4",
|
"rename": "1.0.4",
|
||||||
"rss-parser": "3.13.0",
|
"rss-parser": "3.13.0",
|
||||||
|
|
@ -166,7 +164,7 @@
|
||||||
"slacc": "0.0.10",
|
"slacc": "0.0.10",
|
||||||
"strict-event-emitter-types": "2.0.0",
|
"strict-event-emitter-types": "2.0.0",
|
||||||
"stringz": "2.1.0",
|
"stringz": "2.1.0",
|
||||||
"systeminformation": "5.28.1",
|
"systeminformation": "5.30.5",
|
||||||
"tinycolor2": "1.6.0",
|
"tinycolor2": "1.6.0",
|
||||||
"tmp": "0.2.5",
|
"tmp": "0.2.5",
|
||||||
"tsc-alias": "1.8.16",
|
"tsc-alias": "1.8.16",
|
||||||
|
|
@ -174,14 +172,14 @@
|
||||||
"ulid": "3.0.2",
|
"ulid": "3.0.2",
|
||||||
"vary": "1.1.2",
|
"vary": "1.1.2",
|
||||||
"web-push": "3.6.7",
|
"web-push": "3.6.7",
|
||||||
"ws": "8.18.3",
|
"ws": "8.19.0",
|
||||||
"xev": "3.0.2"
|
"xev": "3.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@jest/globals": "29.7.0",
|
"@jest/globals": "29.7.0",
|
||||||
"@kitajs/ts-html-plugin": "4.1.3",
|
"@kitajs/ts-html-plugin": "4.1.3",
|
||||||
"@nestjs/platform-express": "11.1.10",
|
"@nestjs/platform-express": "11.1.12",
|
||||||
"@sentry/vue": "10.32.1",
|
"@sentry/vue": "10.34.0",
|
||||||
"@simplewebauthn/types": "12.0.0",
|
"@simplewebauthn/types": "12.0.0",
|
||||||
"@swc/jest": "0.2.39",
|
"@swc/jest": "0.2.39",
|
||||||
"@types/accepts": "1.3.7",
|
"@types/accepts": "1.3.7",
|
||||||
|
|
@ -195,8 +193,8 @@
|
||||||
"@types/jsonld": "1.5.15",
|
"@types/jsonld": "1.5.15",
|
||||||
"@types/mime-types": "3.0.1",
|
"@types/mime-types": "3.0.1",
|
||||||
"@types/ms": "2.1.0",
|
"@types/ms": "2.1.0",
|
||||||
"@types/node": "24.10.4",
|
"@types/node": "24.10.9",
|
||||||
"@types/nodemailer": "7.0.4",
|
"@types/nodemailer": "7.0.5",
|
||||||
"@types/oauth2orize": "1.11.5",
|
"@types/oauth2orize": "1.11.5",
|
||||||
"@types/oauth2orize-pkce": "0.1.2",
|
"@types/oauth2orize-pkce": "0.1.2",
|
||||||
"@types/pg": "8.16.0",
|
"@types/pg": "8.16.0",
|
||||||
|
|
@ -214,22 +212,22 @@
|
||||||
"@types/vary": "1.1.3",
|
"@types/vary": "1.1.3",
|
||||||
"@types/web-push": "3.6.4",
|
"@types/web-push": "3.6.4",
|
||||||
"@types/ws": "8.18.1",
|
"@types/ws": "8.18.1",
|
||||||
"@typescript-eslint/eslint-plugin": "8.50.1",
|
"@typescript-eslint/eslint-plugin": "8.53.0",
|
||||||
"@typescript-eslint/parser": "8.50.1",
|
"@typescript-eslint/parser": "8.53.0",
|
||||||
"aws-sdk-client-mock": "4.1.0",
|
"aws-sdk-client-mock": "4.1.0",
|
||||||
"cbor": "10.0.11",
|
"cbor": "10.0.11",
|
||||||
"cross-env": "10.1.0",
|
"cross-env": "10.1.0",
|
||||||
"esbuild-plugin-swc": "1.0.1",
|
"esbuild-plugin-swc": "1.0.1",
|
||||||
"eslint-plugin-import": "2.32.0",
|
"eslint-plugin-import": "2.32.0",
|
||||||
"execa": "9.6.1",
|
"execa": "9.6.1",
|
||||||
"fkill": "10.0.1",
|
"fkill": "10.0.3",
|
||||||
"jest": "29.7.0",
|
"jest": "29.7.0",
|
||||||
"jest-mock": "29.7.0",
|
"jest-mock": "29.7.0",
|
||||||
"js-yaml": "4.1.1",
|
"js-yaml": "4.1.1",
|
||||||
"nodemon": "3.1.11",
|
"nodemon": "3.1.11",
|
||||||
"pid-port": "2.0.0",
|
"pid-port": "2.0.1",
|
||||||
"simple-oauth2": "5.1.0",
|
"simple-oauth2": "5.1.0",
|
||||||
"supertest": "7.1.4",
|
"supertest": "7.2.2",
|
||||||
"vite": "7.3.0"
|
"vite": "7.3.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,16 +14,46 @@ import { fork } from 'node:child_process';
|
||||||
import { setTimeout } from 'node:timers/promises';
|
import { setTimeout } from 'node:timers/promises';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import { dirname, join } from 'node:path';
|
import { dirname, join } from 'node:path';
|
||||||
|
import * as http from 'node:http';
|
||||||
|
import * as fs from 'node:fs/promises';
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = dirname(__filename);
|
const __dirname = dirname(__filename);
|
||||||
|
|
||||||
|
const SAMPLE_COUNT = 3; // Number of samples to measure
|
||||||
const STARTUP_TIMEOUT = 120000; // 120 seconds timeout for server startup
|
const STARTUP_TIMEOUT = 120000; // 120 seconds timeout for server startup
|
||||||
const MEMORY_SETTLE_TIME = 10000; // Wait 10 seconds after startup for memory to settle
|
const MEMORY_SETTLE_TIME = 10000; // Wait 10 seconds after startup for memory to settle
|
||||||
|
|
||||||
async function measureMemory() {
|
const keys = {
|
||||||
const startTime = Date.now();
|
VmPeak: 0,
|
||||||
|
VmSize: 0,
|
||||||
|
VmHWM: 0,
|
||||||
|
VmRSS: 0,
|
||||||
|
VmData: 0,
|
||||||
|
VmStk: 0,
|
||||||
|
VmExe: 0,
|
||||||
|
VmLib: 0,
|
||||||
|
VmPTE: 0,
|
||||||
|
VmSwap: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
async function getMemoryUsage(pid) {
|
||||||
|
const status = await fs.readFile(`/proc/${pid}/status`, 'utf-8');
|
||||||
|
|
||||||
|
const result = {};
|
||||||
|
for (const key of Object.keys(keys)) {
|
||||||
|
const match = status.match(new RegExp(`${key}:\\s+(\\d+)\\s+kB`));
|
||||||
|
if (match) {
|
||||||
|
result[key] = parseInt(match[1], 10);
|
||||||
|
} else {
|
||||||
|
throw new Error(`Failed to parse ${key} from /proc/${pid}/status`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function measureMemory() {
|
||||||
// Start the Misskey backend server using fork to enable IPC
|
// Start the Misskey backend server using fork to enable IPC
|
||||||
const serverProcess = fork(join(__dirname, '../built/boot/entry.js'), ['expose-gc'], {
|
const serverProcess = fork(join(__dirname, '../built/boot/entry.js'), ['expose-gc'], {
|
||||||
cwd: join(__dirname, '..'),
|
cwd: join(__dirname, '..'),
|
||||||
|
|
@ -31,9 +61,9 @@ async function measureMemory() {
|
||||||
...process.env,
|
...process.env,
|
||||||
NODE_ENV: 'production',
|
NODE_ENV: 'production',
|
||||||
MK_DISABLE_CLUSTERING: '1',
|
MK_DISABLE_CLUSTERING: '1',
|
||||||
MK_FORCE_GC: '1',
|
|
||||||
},
|
},
|
||||||
stdio: ['pipe', 'pipe', 'pipe', 'ipc'],
|
stdio: ['pipe', 'pipe', 'pipe', 'ipc'],
|
||||||
|
execArgv: [...process.execArgv, '--expose-gc'],
|
||||||
});
|
});
|
||||||
|
|
||||||
let serverReady = false;
|
let serverReady = false;
|
||||||
|
|
@ -59,6 +89,40 @@ async function measureMemory() {
|
||||||
process.stderr.write(`[server error] ${err}\n`);
|
process.stderr.write(`[server error] ${err}\n`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function triggerGc() {
|
||||||
|
const ok = new Promise((resolve) => {
|
||||||
|
serverProcess.once('message', (message) => {
|
||||||
|
if (message === 'gc ok') resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
serverProcess.send('gc');
|
||||||
|
|
||||||
|
await ok;
|
||||||
|
|
||||||
|
await setTimeout(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createRequest() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const req = http.request({
|
||||||
|
host: 'localhost',
|
||||||
|
port: 61812,
|
||||||
|
path: '/api/meta',
|
||||||
|
method: 'POST',
|
||||||
|
}, (res) => {
|
||||||
|
res.on('data', () => { });
|
||||||
|
res.on('end', () => {
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
req.on('error', (err) => {
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
req.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Wait for server to be ready or timeout
|
// Wait for server to be ready or timeout
|
||||||
const startupStartTime = Date.now();
|
const startupStartTime = Date.now();
|
||||||
while (!serverReady) {
|
while (!serverReady) {
|
||||||
|
|
@ -75,46 +139,23 @@ async function measureMemory() {
|
||||||
// Wait for memory to settle
|
// Wait for memory to settle
|
||||||
await setTimeout(MEMORY_SETTLE_TIME);
|
await setTimeout(MEMORY_SETTLE_TIME);
|
||||||
|
|
||||||
// Get memory usage from the server process via /proc
|
|
||||||
const pid = serverProcess.pid;
|
const pid = serverProcess.pid;
|
||||||
let memoryInfo;
|
|
||||||
|
|
||||||
try {
|
const beforeGc = await getMemoryUsage(pid);
|
||||||
const fs = await import('node:fs/promises');
|
|
||||||
|
|
||||||
// Read /proc/[pid]/status for detailed memory info
|
await triggerGc();
|
||||||
const status = await fs.readFile(`/proc/${pid}/status`, 'utf-8');
|
|
||||||
const vmRssMatch = status.match(/VmRSS:\s+(\d+)\s+kB/);
|
|
||||||
const vmDataMatch = status.match(/VmData:\s+(\d+)\s+kB/);
|
|
||||||
const vmSizeMatch = status.match(/VmSize:\s+(\d+)\s+kB/);
|
|
||||||
|
|
||||||
memoryInfo = {
|
const afterGc = await getMemoryUsage(pid);
|
||||||
rss: vmRssMatch ? parseInt(vmRssMatch[1], 10) * 1024 : null,
|
|
||||||
heapUsed: vmDataMatch ? parseInt(vmDataMatch[1], 10) * 1024 : null,
|
|
||||||
vmSize: vmSizeMatch ? parseInt(vmSizeMatch[1], 10) * 1024 : null,
|
|
||||||
};
|
|
||||||
} catch (err) {
|
|
||||||
// Fallback: use ps command
|
|
||||||
process.stderr.write(`Warning: Could not read /proc/${pid}/status: ${err}\n`);
|
|
||||||
|
|
||||||
const { execSync } = await import('node:child_process');
|
// create some http requests to simulate load
|
||||||
try {
|
const REQUEST_COUNT = 10;
|
||||||
const ps = execSync(`ps -o rss= -p ${pid}`, { encoding: 'utf-8' });
|
await Promise.all(
|
||||||
const rssKb = parseInt(ps.trim(), 10);
|
Array.from({ length: REQUEST_COUNT }).map(() => createRequest()),
|
||||||
memoryInfo = {
|
);
|
||||||
rss: rssKb * 1024,
|
|
||||||
heapUsed: null,
|
await triggerGc();
|
||||||
vmSize: null,
|
|
||||||
};
|
const afterRequest = await getMemoryUsage(pid);
|
||||||
} catch {
|
|
||||||
memoryInfo = {
|
|
||||||
rss: null,
|
|
||||||
heapUsed: null,
|
|
||||||
vmSize: null,
|
|
||||||
error: 'Could not measure memory',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop the server
|
// Stop the server
|
||||||
serverProcess.kill('SIGTERM');
|
serverProcess.kill('SIGTERM');
|
||||||
|
|
@ -137,15 +178,51 @@ async function measureMemory() {
|
||||||
|
|
||||||
const result = {
|
const result = {
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
startupTimeMs: startupTime,
|
beforeGc,
|
||||||
memory: memoryInfo,
|
afterGc,
|
||||||
|
afterRequest,
|
||||||
|
};
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
// 直列の方が時間的に分散されて正確そうだから直列でやる
|
||||||
|
const results = [];
|
||||||
|
for (let i = 0; i < SAMPLE_COUNT; i++) {
|
||||||
|
const res = await measureMemory();
|
||||||
|
results.push(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate averages
|
||||||
|
const beforeGc = structuredClone(keys);
|
||||||
|
const afterGc = structuredClone(keys);
|
||||||
|
const afterRequest = structuredClone(keys);
|
||||||
|
for (const res of results) {
|
||||||
|
for (const key of Object.keys(keys)) {
|
||||||
|
beforeGc[key] += res.beforeGc[key];
|
||||||
|
afterGc[key] += res.afterGc[key];
|
||||||
|
afterRequest[key] += res.afterRequest[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const key of Object.keys(keys)) {
|
||||||
|
beforeGc[key] = Math.round(beforeGc[key] / SAMPLE_COUNT);
|
||||||
|
afterGc[key] = Math.round(afterGc[key] / SAMPLE_COUNT);
|
||||||
|
afterRequest[key] = Math.round(afterRequest[key] / SAMPLE_COUNT);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = {
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
beforeGc,
|
||||||
|
afterGc,
|
||||||
|
afterRequest,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Output as JSON to stdout
|
// Output as JSON to stdout
|
||||||
console.log(JSON.stringify(result, null, 2));
|
console.log(JSON.stringify(result, null, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
measureMemory().catch((err) => {
|
main().catch((err) => {
|
||||||
console.error(JSON.stringify({
|
console.error(JSON.stringify({
|
||||||
error: err.message,
|
error: err.message,
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
|
|
|
||||||
|
|
@ -86,9 +86,17 @@ if (!envOption.disableClustering) {
|
||||||
ev.mount();
|
ev.mount();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (envOption.forceGc && global.gc != null) {
|
process.on('message', msg => {
|
||||||
global.gc();
|
if (msg === 'gc') {
|
||||||
}
|
if (global.gc != null) {
|
||||||
|
logger.info('Manual GC triggered');
|
||||||
|
global.gc();
|
||||||
|
if (process.send != null) process.send('gc ok');
|
||||||
|
} else {
|
||||||
|
logger.warn('Manual GC requested but gc is not available. Start the process with --expose-gc to enable this feature.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
readyRef.value = true;
|
readyRef.value = true;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@
|
||||||
import { randomUUID } from 'node:crypto';
|
import { randomUUID } from 'node:crypto';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { MetricsTime, type JobType } from 'bullmq';
|
import { MetricsTime, type JobType } from 'bullmq';
|
||||||
import { parse as parseRedisInfo } from 'redis-info';
|
|
||||||
import type { IActivity } from '@/core/activitypub/type.js';
|
import type { IActivity } from '@/core/activitypub/type.js';
|
||||||
import type { MiDriveFile } from '@/models/DriveFile.js';
|
import type { MiDriveFile } from '@/models/DriveFile.js';
|
||||||
import type { MiWebhook, WebhookEventTypes } from '@/models/Webhook.js';
|
import type { MiWebhook, WebhookEventTypes } from '@/models/Webhook.js';
|
||||||
|
|
@ -86,6 +85,19 @@ const REPEATABLE_SYSTEM_JOB_DEF = [{
|
||||||
pattern: '0 4 * * *',
|
pattern: '0 4 * * *',
|
||||||
}];
|
}];
|
||||||
|
|
||||||
|
function parseRedisInfo(infoText: string): Record<string, string> {
|
||||||
|
const fields = infoText
|
||||||
|
.split('\n')
|
||||||
|
.filter(line => line.length > 0 && !line.startsWith('#'))
|
||||||
|
.map(line => line.trim().split(':'));
|
||||||
|
|
||||||
|
const result: Record<string, string> = {};
|
||||||
|
for (const [key, value] of fields) {
|
||||||
|
result[key] = value;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class QueueService {
|
export class QueueService {
|
||||||
constructor(
|
constructor(
|
||||||
|
|
@ -890,7 +902,7 @@ export class QueueService {
|
||||||
},
|
},
|
||||||
db: {
|
db: {
|
||||||
version: db.redis_version,
|
version: db.redis_version,
|
||||||
mode: db.redis_mode,
|
mode: db.redis_mode as 'cluster' | 'standalone' | 'sentinel',
|
||||||
runId: db.run_id,
|
runId: db.run_id,
|
||||||
processId: db.process_id,
|
processId: db.process_id,
|
||||||
port: parseInt(db.tcp_port),
|
port: parseInt(db.tcp_port),
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ const envOption = {
|
||||||
verbose: false,
|
verbose: false,
|
||||||
withLogTime: false,
|
withLogTime: false,
|
||||||
quiet: false,
|
quiet: false,
|
||||||
forceGc: false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const key of Object.keys(envOption) as (keyof typeof envOption)[]) {
|
for (const key of Object.keys(envOption) as (keyof typeof envOption)[]) {
|
||||||
|
|
|
||||||
|
|
@ -11,15 +11,15 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/estree": "1.0.8",
|
"@types/estree": "1.0.8",
|
||||||
"@types/node": "24.10.4",
|
"@types/node": "24.10.9",
|
||||||
"@typescript-eslint/eslint-plugin": "8.50.1",
|
"@typescript-eslint/eslint-plugin": "8.53.0",
|
||||||
"@typescript-eslint/parser": "8.50.1",
|
"@typescript-eslint/parser": "8.53.0",
|
||||||
"rollup": "4.54.0"
|
"rollup": "4.55.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"i18n": "workspace:*",
|
"i18n": "workspace:*",
|
||||||
"estree-walker": "3.0.3",
|
"estree-walker": "3.0.3",
|
||||||
"magic-string": "0.30.21",
|
"magic-string": "0.30.21",
|
||||||
"vite": "7.3.0"
|
"vite": "7.3.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,12 +25,12 @@
|
||||||
"mfm-js": "0.25.0",
|
"mfm-js": "0.25.0",
|
||||||
"misskey-js": "workspace:*",
|
"misskey-js": "workspace:*",
|
||||||
"punycode.js": "2.3.1",
|
"punycode.js": "2.3.1",
|
||||||
"rollup": "4.54.0",
|
"rollup": "4.55.1",
|
||||||
"sass": "1.97.1",
|
"sass": "1.97.2",
|
||||||
"shiki": "3.20.0",
|
"shiki": "3.21.0",
|
||||||
"tinycolor2": "1.6.0",
|
"tinycolor2": "1.6.0",
|
||||||
"uuid": "13.0.0",
|
"uuid": "13.0.0",
|
||||||
"vite": "7.3.0",
|
"vite": "7.3.1",
|
||||||
"vue": "3.5.26"
|
"vue": "3.5.26"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
@ -39,29 +39,29 @@
|
||||||
"@testing-library/vue": "8.1.0",
|
"@testing-library/vue": "8.1.0",
|
||||||
"@types/estree": "1.0.8",
|
"@types/estree": "1.0.8",
|
||||||
"@types/micromatch": "4.0.10",
|
"@types/micromatch": "4.0.10",
|
||||||
"@types/node": "24.10.4",
|
"@types/node": "24.10.9",
|
||||||
"@types/punycode.js": "npm:@types/punycode@2.1.4",
|
"@types/punycode.js": "npm:@types/punycode@2.1.4",
|
||||||
"@types/tinycolor2": "1.4.6",
|
"@types/tinycolor2": "1.4.6",
|
||||||
"@types/ws": "8.18.1",
|
"@types/ws": "8.18.1",
|
||||||
"@typescript-eslint/eslint-plugin": "8.50.1",
|
"@typescript-eslint/eslint-plugin": "8.53.0",
|
||||||
"@typescript-eslint/parser": "8.50.1",
|
"@typescript-eslint/parser": "8.53.0",
|
||||||
"@vitest/coverage-v8": "4.0.16",
|
"@vitest/coverage-v8": "4.0.17",
|
||||||
"@vue/runtime-core": "3.5.26",
|
"@vue/runtime-core": "3.5.26",
|
||||||
"acorn": "8.15.0",
|
"acorn": "8.15.0",
|
||||||
"cross-env": "10.1.0",
|
"cross-env": "10.1.0",
|
||||||
"eslint-plugin-import": "2.32.0",
|
"eslint-plugin-import": "2.32.0",
|
||||||
"eslint-plugin-vue": "10.6.2",
|
"eslint-plugin-vue": "10.7.0",
|
||||||
"happy-dom": "20.0.11",
|
"happy-dom": "20.3.1",
|
||||||
"intersection-observer": "0.12.2",
|
"intersection-observer": "0.12.2",
|
||||||
"micromatch": "4.0.8",
|
"micromatch": "4.0.8",
|
||||||
"msw": "2.12.6",
|
"msw": "2.12.7",
|
||||||
"nodemon": "3.1.11",
|
"nodemon": "3.1.11",
|
||||||
"prettier": "3.7.4",
|
"prettier": "3.8.0",
|
||||||
"start-server-and-test": "2.1.3",
|
"start-server-and-test": "2.1.3",
|
||||||
"tsx": "4.21.0",
|
"tsx": "4.21.0",
|
||||||
"vite-plugin-turbosnap": "1.0.3",
|
"vite-plugin-turbosnap": "1.0.3",
|
||||||
"vue-component-type-helpers": "3.2.1",
|
"vue-component-type-helpers": "3.2.2",
|
||||||
"vue-eslint-parser": "10.2.0",
|
"vue-eslint-parser": "10.2.0",
|
||||||
"vue-tsc": "3.2.1"
|
"vue-tsc": "3.2.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,11 +21,11 @@
|
||||||
"lint": "pnpm typecheck && pnpm eslint"
|
"lint": "pnpm typecheck && pnpm eslint"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "24.10.4",
|
"@types/node": "24.10.9",
|
||||||
"@typescript-eslint/eslint-plugin": "8.50.1",
|
"@typescript-eslint/eslint-plugin": "8.53.0",
|
||||||
"@typescript-eslint/parser": "8.50.1",
|
"@typescript-eslint/parser": "8.53.0",
|
||||||
"esbuild": "0.27.2",
|
"esbuild": "0.27.2",
|
||||||
"eslint-plugin-vue": "10.6.2",
|
"eslint-plugin-vue": "10.7.0",
|
||||||
"nodemon": "3.1.11",
|
"nodemon": "3.1.11",
|
||||||
"vue-eslint-parser": "10.2.0"
|
"vue-eslint-parser": "10.2.0"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,12 @@
|
||||||
"@analytics/google-analytics": "1.1.0",
|
"@analytics/google-analytics": "1.1.0",
|
||||||
"@discordapp/twemoji": "16.0.1",
|
"@discordapp/twemoji": "16.0.1",
|
||||||
"@github/webauthn-json": "2.1.1",
|
"@github/webauthn-json": "2.1.1",
|
||||||
"@mcaptcha/vanilla-glue": "0.1.0-rc2",
|
"@mcaptcha/core-glue": "0.1.0-alpha-5",
|
||||||
"@misskey-dev/browser-image-resizer": "2024.1.0",
|
"@misskey-dev/browser-image-resizer": "2024.1.0",
|
||||||
"@rollup/plugin-json": "6.1.0",
|
"@rollup/plugin-json": "6.1.0",
|
||||||
"@rollup/plugin-replace": "6.0.3",
|
"@rollup/plugin-replace": "6.0.3",
|
||||||
"@rollup/pluginutils": "5.3.0",
|
"@rollup/pluginutils": "5.3.0",
|
||||||
"@sentry/vue": "10.32.1",
|
"@sentry/vue": "10.34.0",
|
||||||
"@syuilo/aiscript": "1.2.1",
|
"@syuilo/aiscript": "1.2.1",
|
||||||
"@syuilo/aiscript-0-19-0": "npm:@syuilo/aiscript@^0.19.0",
|
"@syuilo/aiscript-0-19-0": "npm:@syuilo/aiscript@^0.19.0",
|
||||||
"@twemoji/parser": "16.0.0",
|
"@twemoji/parser": "16.0.0",
|
||||||
|
|
@ -39,13 +39,13 @@
|
||||||
"chartjs-chart-matrix": "3.0.0",
|
"chartjs-chart-matrix": "3.0.0",
|
||||||
"chartjs-plugin-gradient": "0.6.1",
|
"chartjs-plugin-gradient": "0.6.1",
|
||||||
"chartjs-plugin-zoom": "2.2.0",
|
"chartjs-plugin-zoom": "2.2.0",
|
||||||
"chromatic": "13.3.4",
|
"chromatic": "13.3.5",
|
||||||
"compare-versions": "6.1.1",
|
"compare-versions": "6.1.1",
|
||||||
"cropperjs": "2.1.0",
|
"cropperjs": "2.1.0",
|
||||||
"date-fns": "4.1.0",
|
"date-fns": "4.1.0",
|
||||||
"eventemitter3": "5.0.1",
|
"eventemitter3": "5.0.1",
|
||||||
"execa": "9.6.1",
|
"execa": "9.6.1",
|
||||||
"exifreader": "4.33.1",
|
"exifreader": "4.36.0",
|
||||||
"frontend-shared": "workspace:*",
|
"frontend-shared": "workspace:*",
|
||||||
"i18n": "workspace:*",
|
"i18n": "workspace:*",
|
||||||
"icons-subsetter": "workspace:*",
|
"icons-subsetter": "workspace:*",
|
||||||
|
|
@ -55,7 +55,7 @@
|
||||||
"is-file-animated": "1.0.2",
|
"is-file-animated": "1.0.2",
|
||||||
"json5": "2.2.3",
|
"json5": "2.2.3",
|
||||||
"matter-js": "0.20.0",
|
"matter-js": "0.20.0",
|
||||||
"mediabunny": "1.27.2",
|
"mediabunny": "1.28.0",
|
||||||
"mfm-js": "0.25.0",
|
"mfm-js": "0.25.0",
|
||||||
"misskey-bubble-game": "workspace:*",
|
"misskey-bubble-game": "workspace:*",
|
||||||
"misskey-js": "workspace:*",
|
"misskey-js": "workspace:*",
|
||||||
|
|
@ -64,16 +64,16 @@
|
||||||
"punycode.js": "2.3.1",
|
"punycode.js": "2.3.1",
|
||||||
"qr-code-styling": "1.9.2",
|
"qr-code-styling": "1.9.2",
|
||||||
"qr-scanner": "1.4.2",
|
"qr-scanner": "1.4.2",
|
||||||
"rollup": "4.54.0",
|
"rollup": "4.55.1",
|
||||||
"sanitize-html": "2.17.0",
|
"sanitize-html": "2.17.0",
|
||||||
"sass": "1.97.1",
|
"sass": "1.97.2",
|
||||||
"shiki": "3.20.0",
|
"shiki": "3.21.0",
|
||||||
"textarea-caret": "3.1.0",
|
"textarea-caret": "3.1.0",
|
||||||
"three": "0.182.0",
|
"three": "0.182.0",
|
||||||
"throttle-debounce": "5.0.2",
|
"throttle-debounce": "5.0.2",
|
||||||
"tinycolor2": "1.6.0",
|
"tinycolor2": "1.6.0",
|
||||||
"v-code-diff": "1.13.1",
|
"v-code-diff": "1.13.1",
|
||||||
"vite": "7.3.0",
|
"vite": "7.3.1",
|
||||||
"vue": "3.5.26",
|
"vue": "3.5.26",
|
||||||
"wanakana": "5.3.1"
|
"wanakana": "5.3.1"
|
||||||
},
|
},
|
||||||
|
|
@ -81,7 +81,7 @@
|
||||||
"@misskey-dev/summaly": "5.2.5",
|
"@misskey-dev/summaly": "5.2.5",
|
||||||
"@storybook/addon-essentials": "8.6.15",
|
"@storybook/addon-essentials": "8.6.15",
|
||||||
"@storybook/addon-interactions": "8.6.15",
|
"@storybook/addon-interactions": "8.6.15",
|
||||||
"@storybook/addon-links": "10.1.10",
|
"@storybook/addon-links": "10.1.11",
|
||||||
"@storybook/addon-mdx-gfm": "8.6.15",
|
"@storybook/addon-mdx-gfm": "8.6.15",
|
||||||
"@storybook/addon-storysource": "8.6.15",
|
"@storybook/addon-storysource": "8.6.15",
|
||||||
"@storybook/blocks": "8.6.15",
|
"@storybook/blocks": "8.6.15",
|
||||||
|
|
@ -89,13 +89,13 @@
|
||||||
"@storybook/core-events": "8.6.15",
|
"@storybook/core-events": "8.6.15",
|
||||||
"@storybook/manager-api": "8.6.15",
|
"@storybook/manager-api": "8.6.15",
|
||||||
"@storybook/preview-api": "8.6.15",
|
"@storybook/preview-api": "8.6.15",
|
||||||
"@storybook/react": "10.1.10",
|
"@storybook/react": "10.1.11",
|
||||||
"@storybook/react-vite": "10.1.10",
|
"@storybook/react-vite": "10.1.11",
|
||||||
"@storybook/test": "8.6.15",
|
"@storybook/test": "8.6.15",
|
||||||
"@storybook/theming": "8.6.15",
|
"@storybook/theming": "8.6.15",
|
||||||
"@storybook/types": "8.6.15",
|
"@storybook/types": "8.6.15",
|
||||||
"@storybook/vue3": "10.1.10",
|
"@storybook/vue3": "10.1.11",
|
||||||
"@storybook/vue3-vite": "10.1.10",
|
"@storybook/vue3-vite": "10.1.11",
|
||||||
"@tabler/icons-webfont": "3.35.0",
|
"@tabler/icons-webfont": "3.35.0",
|
||||||
"@testing-library/vue": "8.1.0",
|
"@testing-library/vue": "8.1.0",
|
||||||
"@types/canvas-confetti": "1.9.0",
|
"@types/canvas-confetti": "1.9.0",
|
||||||
|
|
@ -103,46 +103,46 @@
|
||||||
"@types/insert-text-at-cursor": "0.3.2",
|
"@types/insert-text-at-cursor": "0.3.2",
|
||||||
"@types/matter-js": "0.20.2",
|
"@types/matter-js": "0.20.2",
|
||||||
"@types/micromatch": "4.0.10",
|
"@types/micromatch": "4.0.10",
|
||||||
"@types/node": "24.10.4",
|
"@types/node": "24.10.9",
|
||||||
"@types/punycode.js": "npm:@types/punycode@2.1.4",
|
"@types/punycode.js": "npm:@types/punycode@2.1.4",
|
||||||
"@types/sanitize-html": "2.16.0",
|
"@types/sanitize-html": "2.16.0",
|
||||||
"@types/seedrandom": "3.0.8",
|
"@types/seedrandom": "3.0.8",
|
||||||
"@types/textarea-caret": "3.0.4",
|
"@types/textarea-caret": "3.0.4",
|
||||||
"@types/throttle-debounce": "5.0.2",
|
"@types/throttle-debounce": "5.0.2",
|
||||||
"@types/tinycolor2": "1.4.6",
|
"@types/tinycolor2": "1.4.6",
|
||||||
"@typescript-eslint/eslint-plugin": "8.50.1",
|
"@typescript-eslint/eslint-plugin": "8.53.0",
|
||||||
"@typescript-eslint/parser": "8.50.1",
|
"@typescript-eslint/parser": "8.53.0",
|
||||||
"@vitest/coverage-v8": "4.0.16",
|
"@vitest/coverage-v8": "4.0.17",
|
||||||
"@vue/compiler-core": "3.5.26",
|
"@vue/compiler-core": "3.5.26",
|
||||||
"acorn": "8.15.0",
|
"acorn": "8.15.0",
|
||||||
"astring": "1.9.0",
|
"astring": "1.9.0",
|
||||||
"cross-env": "10.1.0",
|
"cross-env": "10.1.0",
|
||||||
"cypress": "15.8.1",
|
"cypress": "15.9.0",
|
||||||
"eslint-plugin-import": "2.32.0",
|
"eslint-plugin-import": "2.32.0",
|
||||||
"eslint-plugin-vue": "10.6.2",
|
"eslint-plugin-vue": "10.7.0",
|
||||||
"estree-walker": "3.0.3",
|
"estree-walker": "3.0.3",
|
||||||
"happy-dom": "20.0.11",
|
"happy-dom": "20.3.1",
|
||||||
"intersection-observer": "0.12.2",
|
"intersection-observer": "0.12.2",
|
||||||
"magic-string": "0.30.21",
|
"magic-string": "0.30.21",
|
||||||
"micromatch": "4.0.8",
|
"micromatch": "4.0.8",
|
||||||
"minimatch": "10.1.1",
|
"minimatch": "10.1.1",
|
||||||
"msw": "2.12.6",
|
"msw": "2.12.7",
|
||||||
"msw-storybook-addon": "2.0.6",
|
"msw-storybook-addon": "2.0.6",
|
||||||
"nodemon": "3.1.11",
|
"nodemon": "3.1.11",
|
||||||
"prettier": "3.7.4",
|
"prettier": "3.8.0",
|
||||||
"react": "19.2.3",
|
"react": "19.2.3",
|
||||||
"react-dom": "19.2.3",
|
"react-dom": "19.2.3",
|
||||||
"seedrandom": "3.0.5",
|
"seedrandom": "3.0.5",
|
||||||
"start-server-and-test": "2.1.3",
|
"start-server-and-test": "2.1.3",
|
||||||
"storybook": "10.1.10",
|
"storybook": "10.1.11",
|
||||||
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
|
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
|
||||||
"tsx": "4.21.0",
|
"tsx": "4.21.0",
|
||||||
"vite-plugin-glsl": "1.5.5",
|
"vite-plugin-glsl": "1.5.5",
|
||||||
"vite-plugin-turbosnap": "1.0.3",
|
"vite-plugin-turbosnap": "1.0.3",
|
||||||
"vitest": "4.0.16",
|
"vitest": "4.0.17",
|
||||||
"vitest-fetch-mock": "0.4.5",
|
"vitest-fetch-mock": "0.4.5",
|
||||||
"vue-component-type-helpers": "3.2.1",
|
"vue-component-type-helpers": "3.2.2",
|
||||||
"vue-eslint-parser": "10.2.0",
|
"vue-eslint-parser": "10.2.0",
|
||||||
"vue-tsc": "3.2.1"
|
"vue-tsc": "3.2.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div>
|
<div>
|
||||||
<span v-if="!available">Loading<MkEllipsis/></span>
|
<span v-if="!available">Loading<MkEllipsis/></span>
|
||||||
<div v-if="props.provider == 'mcaptcha'">
|
<div v-if="props.provider == 'mcaptcha'">
|
||||||
<div id="mcaptcha__widget-container" class="m-captcha-style"></div>
|
<iframe
|
||||||
<div ref="captchaEl"></div>
|
v-if="mCaptchaIframeUrl != null"
|
||||||
|
ref="mCaptchaIframe"
|
||||||
|
:src="mCaptchaIframeUrl"
|
||||||
|
style="border: none; max-width: 320px; width: 100%; height: 100%; max-height: 80px;"
|
||||||
|
></iframe>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="props.provider == 'testcaptcha'" style="background: #eee; border: solid 1px #888; padding: 8px; color: #000; max-width: 320px; display: flex; gap: 10px; align-items: center; box-shadow: 2px 2px 6px #0004; border-radius: 4px;">
|
<div v-if="props.provider == 'testcaptcha'" style="background: #eee; border: solid 1px #888; padding: 8px; color: #000; max-width: 320px; display: flex; gap: 10px; align-items: center; box-shadow: 2px 2px 6px #0004; border-radius: 4px;">
|
||||||
<img src="/client-assets/testcaptcha.png" style="width: 60px; height: 60px; "/>
|
<img src="/client-assets/testcaptcha.png" style="width: 60px; height: 60px; "/>
|
||||||
|
|
@ -26,7 +30,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, useTemplateRef, computed, onMounted, onBeforeUnmount, watch, onUnmounted } from 'vue';
|
import { ref, useTemplateRef, computed, onMounted, onBeforeUnmount, watch, onUnmounted, nextTick } from 'vue';
|
||||||
|
import type Reciever_typeReferenceOnly from '@mcaptcha/core-glue';
|
||||||
import { store } from '@/store.js';
|
import { store } from '@/store.js';
|
||||||
|
|
||||||
// APIs provided by Captcha services
|
// APIs provided by Captcha services
|
||||||
|
|
@ -71,6 +76,19 @@ const available = ref(false);
|
||||||
|
|
||||||
const captchaEl = useTemplateRef('captchaEl');
|
const captchaEl = useTemplateRef('captchaEl');
|
||||||
const captchaWidgetId = ref<string | undefined>(undefined);
|
const captchaWidgetId = ref<string | undefined>(undefined);
|
||||||
|
|
||||||
|
let mCaptchaReciever: Reciever_typeReferenceOnly | null = null;
|
||||||
|
const mCaptchaIframe = useTemplateRef('mCaptchaIframe');
|
||||||
|
const mCaptchaRemoveState = ref(false);
|
||||||
|
const mCaptchaIframeUrl = computed(() => {
|
||||||
|
if (props.provider === 'mcaptcha' && !mCaptchaRemoveState.value && props.instanceUrl && props.sitekey) {
|
||||||
|
const url = new URL('/widget', props.instanceUrl);
|
||||||
|
url.searchParams.set('sitekey', props.sitekey);
|
||||||
|
return url.toString();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
const testcaptchaInput = ref('');
|
const testcaptchaInput = ref('');
|
||||||
const testcaptchaPassed = ref(false);
|
const testcaptchaPassed = ref(false);
|
||||||
|
|
||||||
|
|
@ -129,8 +147,14 @@ function reset() {
|
||||||
if (_DEV_) console.warn(error);
|
if (_DEV_) console.warn(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
testcaptchaPassed.value = false;
|
testcaptchaPassed.value = false;
|
||||||
testcaptchaInput.value = '';
|
testcaptchaInput.value = '';
|
||||||
|
|
||||||
|
if (mCaptchaReciever != null) {
|
||||||
|
mCaptchaReciever.destroy();
|
||||||
|
mCaptchaReciever = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function remove() {
|
function remove() {
|
||||||
|
|
@ -143,6 +167,10 @@ function remove() {
|
||||||
if (_DEV_) console.warn(error);
|
if (_DEV_) console.warn(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (props.provider === 'mcaptcha') {
|
||||||
|
mCaptchaRemoveState.value = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function requestRender() {
|
async function requestRender() {
|
||||||
|
|
@ -160,32 +188,29 @@ async function requestRender() {
|
||||||
'error-callback': () => callback(undefined),
|
'error-callback': () => callback(undefined),
|
||||||
});
|
});
|
||||||
} else if (props.provider === 'mcaptcha' && props.instanceUrl && props.sitekey) {
|
} else if (props.provider === 'mcaptcha' && props.instanceUrl && props.sitekey) {
|
||||||
const { default: Widget } = await import('@mcaptcha/vanilla-glue');
|
const { default: Reciever } = await import('@mcaptcha/core-glue');
|
||||||
new Widget({
|
mCaptchaReciever = new Reciever({
|
||||||
siteKey: {
|
siteKey: {
|
||||||
instanceUrl: new URL(props.instanceUrl),
|
|
||||||
key: props.sitekey,
|
key: props.sitekey,
|
||||||
|
instanceUrl: new URL(props.instanceUrl),
|
||||||
},
|
},
|
||||||
|
}, (token: string) => {
|
||||||
|
callback(token);
|
||||||
});
|
});
|
||||||
|
mCaptchaReciever.listen();
|
||||||
|
mCaptchaRemoveState.value = false;
|
||||||
} else {
|
} else {
|
||||||
window.setTimeout(requestRender, 1);
|
window.setTimeout(requestRender, 50);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearWidget() {
|
function clearWidget() {
|
||||||
if (props.provider === 'mcaptcha') {
|
reset();
|
||||||
const container = window.document.getElementById('mcaptcha__widget-container');
|
remove();
|
||||||
if (container) {
|
|
||||||
container.innerHTML = '';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
reset();
|
|
||||||
remove();
|
|
||||||
|
|
||||||
if (captchaEl.value) {
|
if (captchaEl.value) {
|
||||||
// レンダリング先のコンテナの中身を掃除し、フォームが増殖するのを抑止
|
// レンダリング先のコンテナの中身を掃除し、フォームが増殖するのを抑止
|
||||||
captchaEl.value.innerHTML = '';
|
captchaEl.value.innerHTML = '';
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="[hide ? $style.hidden : $style.visible, (image.isSensitive && prefer.s.highlightSensitiveMedia) && $style.sensitive]" @click="reveal">
|
<div :class="[hide ? $style.hidden : $style.visible, (image.isSensitive && prefer.s.highlightSensitiveMedia) && $style.sensitive]" @click="reveal" @contextmenu.stop="onContextmenu">
|
||||||
<component
|
<component
|
||||||
:is="disableImageLink ? 'div' : 'a'"
|
:is="disableImageLink ? 'div' : 'a'"
|
||||||
v-bind="disableImageLink ? {
|
v-bind="disableImageLink ? {
|
||||||
|
|
@ -123,7 +123,7 @@ watch(() => props.image, (newImage) => {
|
||||||
immediate: true,
|
immediate: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
function showMenu(ev: PointerEvent) {
|
function getMenu() {
|
||||||
const menuItems: MenuItem[] = [];
|
const menuItems: MenuItem[] = [];
|
||||||
|
|
||||||
menuItems.push({
|
menuItems.push({
|
||||||
|
|
@ -188,9 +188,16 @@ function showMenu(ev: PointerEvent) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
os.popupMenu(menuItems, ev.currentTarget ?? ev.target);
|
return menuItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showMenu(ev: PointerEvent) {
|
||||||
|
os.popupMenu(getMenu(), (ev.currentTarget ?? ev.target ?? undefined) as HTMLElement | undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onContextmenu(ev: PointerEvent) {
|
||||||
|
os.contextMenu(getMenu(), ev);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
@click="showFileMenu(item, $event)"
|
@click="showFileMenu(item, $event)"
|
||||||
@keydown.space.enter="showFileMenu(item, $event)"
|
@keydown.space.enter="showFileMenu(item, $event)"
|
||||||
@contextmenu.prevent="showFileMenu(item, $event)"
|
@contextmenu.prevent.stop="showFileMenu(item, $event)"
|
||||||
>
|
>
|
||||||
<!-- pointer-eventsをnoneにしておかないとiOSなどでドラッグしたときに画像の方に判定が持ってかれる -->
|
<!-- pointer-eventsをnoneにしておかないとiOSなどでドラッグしたときに画像の方に判定が持ってかれる -->
|
||||||
<MkDriveFileThumbnail style="pointer-events: none;" :data-id="item.id" :class="$style.thumbnail" :file="item" fit="cover"/>
|
<MkDriveFileThumbnail style="pointer-events: none;" :data-id="item.id" :class="$style.thumbnail" :file="item" fit="cover"/>
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,21 @@ const pullDistance = ref(0);
|
||||||
|
|
||||||
let startScreenY: number | null = null;
|
let startScreenY: number | null = null;
|
||||||
|
|
||||||
|
let moveBySystemCancel: (() => void) | null = null;
|
||||||
|
let moveBySystemRafId: number | null = null;
|
||||||
|
|
||||||
|
const onMouseMove = (event: MouseEvent) => moving(event);
|
||||||
|
const onMouseUp = () => {
|
||||||
|
window.removeEventListener('mousemove', onMouseMove);
|
||||||
|
onPullRelease();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onTouchMove = (event: TouchEvent) => moving(event);
|
||||||
|
const onTouchEnd = () => {
|
||||||
|
window.removeEventListener('touchmove', onTouchMove);
|
||||||
|
onPullRelease();
|
||||||
|
};
|
||||||
|
|
||||||
const rootEl = useTemplateRef('rootEl');
|
const rootEl = useTemplateRef('rootEl');
|
||||||
let scrollEl: HTMLElement | null = null;
|
let scrollEl: HTMLElement | null = null;
|
||||||
|
|
||||||
|
|
@ -97,11 +112,8 @@ function moveStartByMouse(event: MouseEvent) {
|
||||||
startScreenY = getScreenY(event);
|
startScreenY = getScreenY(event);
|
||||||
pullDistance.value = 0;
|
pullDistance.value = 0;
|
||||||
|
|
||||||
window.addEventListener('mousemove', moving, { passive: true });
|
window.addEventListener('mousemove', onMouseMove, { passive: true });
|
||||||
window.addEventListener('mouseup', () => {
|
window.addEventListener('mouseup', onMouseUp, { passive: true, once: true });
|
||||||
window.removeEventListener('mousemove', moving);
|
|
||||||
onPullRelease();
|
|
||||||
}, { passive: true, once: true });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function moveStartByTouch(event: TouchEvent) {
|
function moveStartByTouch(event: TouchEvent) {
|
||||||
|
|
@ -119,34 +131,70 @@ function moveStartByTouch(event: TouchEvent) {
|
||||||
startScreenY = getScreenY(event);
|
startScreenY = getScreenY(event);
|
||||||
pullDistance.value = 0;
|
pullDistance.value = 0;
|
||||||
|
|
||||||
window.addEventListener('touchmove', moving, { passive: true });
|
window.addEventListener('touchmove', onTouchMove, { passive: true });
|
||||||
window.addEventListener('touchend', () => {
|
window.addEventListener('touchend', onTouchEnd, { passive: true, once: true });
|
||||||
window.removeEventListener('touchmove', moving);
|
|
||||||
onPullRelease();
|
|
||||||
}, { passive: true, once: true });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function moveBySystem(to: number): Promise<void> {
|
function moveBySystem(to: number): Promise<void> {
|
||||||
|
if (moveBySystemCancel != null) {
|
||||||
|
moveBySystemCancel();
|
||||||
|
moveBySystemCancel = null;
|
||||||
|
}
|
||||||
|
|
||||||
return new Promise(r => {
|
return new Promise(r => {
|
||||||
const startHeight = pullDistance.value;
|
const startHeight = pullDistance.value;
|
||||||
const overHeight = pullDistance.value - to;
|
const overHeight = startHeight - to;
|
||||||
if (overHeight < 1) {
|
if (Math.abs(overHeight) < 1) {
|
||||||
|
pullDistance.value = to;
|
||||||
r();
|
r();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const startTime = Date.now();
|
|
||||||
let intervalId = window.setInterval(() => {
|
let startTime: DOMHighResTimeStamp | null = null;
|
||||||
const time = Date.now() - startTime;
|
let cancelled = false;
|
||||||
if (time > RELEASE_TRANSITION_DURATION) {
|
moveBySystemCancel = () => {
|
||||||
|
cancelled = true;
|
||||||
|
startTime = null;
|
||||||
|
if (moveBySystemRafId != null) {
|
||||||
|
window.cancelAnimationFrame(moveBySystemRafId);
|
||||||
|
moveBySystemRafId = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const tick = (now: DOMHighResTimeStamp) => {
|
||||||
|
if (cancelled) {
|
||||||
|
r();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (startTime == null) {
|
||||||
|
startTime = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
const time = now - startTime;
|
||||||
|
if (time >= RELEASE_TRANSITION_DURATION) {
|
||||||
pullDistance.value = to;
|
pullDistance.value = to;
|
||||||
window.clearInterval(intervalId);
|
moveBySystemCancel = null;
|
||||||
|
moveBySystemRafId = null;
|
||||||
r();
|
r();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const nextHeight = startHeight - (overHeight / RELEASE_TRANSITION_DURATION) * time;
|
const nextHeight = startHeight - (overHeight / RELEASE_TRANSITION_DURATION) * time;
|
||||||
if (pullDistance.value < nextHeight) return;
|
if (overHeight > 0) {
|
||||||
|
if (pullDistance.value < nextHeight) {
|
||||||
|
moveBySystemRafId = window.requestAnimationFrame(tick);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (pullDistance.value > nextHeight) {
|
||||||
|
moveBySystemRafId = window.requestAnimationFrame(tick);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
pullDistance.value = nextHeight;
|
pullDistance.value = nextHeight;
|
||||||
}, 1);
|
moveBySystemRafId = window.requestAnimationFrame(tick);
|
||||||
|
};
|
||||||
|
|
||||||
|
moveBySystemRafId = window.requestAnimationFrame(tick);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -230,6 +278,14 @@ onMounted(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
|
if (moveBySystemCancel != null) {
|
||||||
|
moveBySystemCancel();
|
||||||
|
moveBySystemCancel = null;
|
||||||
|
}
|
||||||
|
moveBySystemRafId = null;
|
||||||
|
// pull中にwindowへ登録したリスナーが残るのを防ぐ
|
||||||
|
window.removeEventListener('mousemove', onMouseMove);
|
||||||
|
window.removeEventListener('touchmove', onTouchMove);
|
||||||
unlockDownScroll();
|
unlockDownScroll();
|
||||||
if (rootEl.value) rootEl.value.removeEventListener('mousedown', moveStartByMouse);
|
if (rootEl.value) rootEl.value.removeEventListener('mousedown', moveStartByMouse);
|
||||||
if (rootEl.value) rootEl.value.removeEventListener('touchstart', moveStartByTouch);
|
if (rootEl.value) rootEl.value.removeEventListener('touchstart', moveStartByTouch);
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div
|
<div
|
||||||
ref="rootEl"
|
ref="rootEl"
|
||||||
:class="[$style.transitionRoot, { [$style.enableAnimation]: shouldAnimate }]"
|
:class="[$style.transitionRoot, { [$style.enableAnimation]: shouldAnimate }]"
|
||||||
@touchstart.passive="touchStart"
|
@touchstart.passive="moveStartByTouch"
|
||||||
@touchmove.passive="touchMove"
|
@touchmove.passive="moving"
|
||||||
@touchend.passive="touchEnd"
|
@touchend.passive="moveEndByTouch"
|
||||||
|
@touchcancel.passive="moveCancelByTouch"
|
||||||
>
|
>
|
||||||
<Transition
|
<Transition
|
||||||
:class="[$style.transitionChildren, { [$style.swiping]: isSwipingForClass }]"
|
:class="[$style.transitionChildren, { [$style.swiping]: isSwipingForClass }]"
|
||||||
|
|
@ -26,7 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, useTemplateRef, computed, nextTick, watch } from 'vue';
|
import { ref, useTemplateRef, computed, watch, onUnmounted } from 'vue';
|
||||||
import type { Tab } from '@/components/global/MkPageHeader.tabs.vue';
|
import type { Tab } from '@/components/global/MkPageHeader.tabs.vue';
|
||||||
import { isHorizontalSwipeSwiping as isSwiping } from '@/utility/touch.js';
|
import { isHorizontalSwipeSwiping as isSwiping } from '@/utility/touch.js';
|
||||||
import { prefer } from '@/preferences.js';
|
import { prefer } from '@/preferences.js';
|
||||||
|
|
@ -59,11 +60,22 @@ const MAX_SWIPE_DISTANCE = 120;
|
||||||
// スワイプ方向を判定する角度の許容範囲(度数)
|
// スワイプ方向を判定する角度の許容範囲(度数)
|
||||||
const SWIPE_DIRECTION_ANGLE_THRESHOLD = 50;
|
const SWIPE_DIRECTION_ANGLE_THRESHOLD = 50;
|
||||||
|
|
||||||
|
// 指を離したときに元に戻すアニメーションの時間
|
||||||
|
const RELEASE_TRANSITION_DURATION = 200;
|
||||||
|
|
||||||
|
// スワイプ方向ロック判定を始める最小移動量(小さすぎると誤判定しやすい)
|
||||||
|
const DIRECTION_LOCK_START_DISTANCE = 6;
|
||||||
|
|
||||||
// ▲ しきい値 ▲ //
|
// ▲ しきい値 ▲ //
|
||||||
|
|
||||||
let startScreenX: number | null = null;
|
let startScreenX: number | null = null;
|
||||||
let startScreenY: number | null = null;
|
let startScreenY: number | null = null;
|
||||||
|
|
||||||
|
let isTracking = false;
|
||||||
|
let rafId: number | null = null;
|
||||||
|
let pendingPullDistance: number | null = null;
|
||||||
|
let releaseAnimationCancel: (() => void) | null = null;
|
||||||
|
|
||||||
const currentTabIndex = computed(() => props.tabs.findIndex(tab => tab.key === tabModel.value));
|
const currentTabIndex = computed(() => props.tabs.findIndex(tab => tab.key === tabModel.value));
|
||||||
|
|
||||||
const pullDistance = ref(0);
|
const pullDistance = ref(0);
|
||||||
|
|
@ -71,55 +83,173 @@ const isSwipingForClass = ref(false);
|
||||||
let swipeAborted = false;
|
let swipeAborted = false;
|
||||||
let swipeDirectionLocked: 'horizontal' | 'vertical' | null = null;
|
let swipeDirectionLocked: 'horizontal' | 'vertical' | null = null;
|
||||||
|
|
||||||
function touchStart(event: TouchEvent) {
|
function getScreenX(event: TouchEvent): number {
|
||||||
|
return event.touches[0]?.screenX ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getScreenY(event: TouchEvent): number {
|
||||||
|
return event.touches[0]?.screenY ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clamp(value: number, min: number, max: number): number {
|
||||||
|
return Math.min(Math.max(value, min), max);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toEffectiveDistance(rawDistance: number): number {
|
||||||
|
const sign = Math.sign(rawDistance);
|
||||||
|
const abs = Math.abs(rawDistance);
|
||||||
|
if (abs <= MIN_SWIPE_DISTANCE) return 0;
|
||||||
|
return sign * (abs - MIN_SWIPE_DISTANCE);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setPullDistance(nextDistance: number) {
|
||||||
|
pendingPullDistance = nextDistance;
|
||||||
|
if (rafId != null) return;
|
||||||
|
rafId = window.requestAnimationFrame(() => {
|
||||||
|
rafId = null;
|
||||||
|
const next = pendingPullDistance ?? 0;
|
||||||
|
pendingPullDistance = null;
|
||||||
|
// グリッチ抑制: 0.5px未満の更新は捨てる
|
||||||
|
if (Math.abs(next - pullDistance.value) < 0.5) return;
|
||||||
|
pullDistance.value = next;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelMoveBySystem() {
|
||||||
|
if (releaseAnimationCancel != null) {
|
||||||
|
releaseAnimationCancel();
|
||||||
|
releaseAnimationCancel = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
// コンポーネント破棄後にpullDistanceを書き換えないようにする
|
||||||
|
if (rafId != null) {
|
||||||
|
window.cancelAnimationFrame(rafId);
|
||||||
|
rafId = null;
|
||||||
|
}
|
||||||
|
pendingPullDistance = null;
|
||||||
|
cancelMoveBySystem();
|
||||||
|
});
|
||||||
|
|
||||||
|
function moveBySystem(to: number, duration = RELEASE_TRANSITION_DURATION): Promise<void> {
|
||||||
|
cancelMoveBySystem();
|
||||||
|
|
||||||
|
if (!shouldAnimate.value || duration <= 0) {
|
||||||
|
setPullDistance(to);
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const from = pullDistance.value;
|
||||||
|
const delta = to - from;
|
||||||
|
if (Math.abs(delta) < 0.5) {
|
||||||
|
setPullDistance(to);
|
||||||
|
resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DOMHighResTimeStampと合わせるためにDate.now()ではなくperformance.now()を使う
|
||||||
|
let startTime: DOMHighResTimeStamp | null = null;
|
||||||
|
let cancelled = false;
|
||||||
|
releaseAnimationCancel = () => {
|
||||||
|
cancelled = true;
|
||||||
|
startTime = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const tick = (now: DOMHighResTimeStamp) => {
|
||||||
|
if (cancelled) {
|
||||||
|
resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (startTime == null) {
|
||||||
|
startTime = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
const t = Math.min((now - startTime) / duration, 1);
|
||||||
|
// リリース時は軽くイージング(追従中は直接反映)
|
||||||
|
const eased = 1 - Math.pow(1 - t, 3);
|
||||||
|
setPullDistance(from + delta * eased);
|
||||||
|
if (t >= 1) {
|
||||||
|
releaseAnimationCancel = null;
|
||||||
|
resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.requestAnimationFrame(tick);
|
||||||
|
};
|
||||||
|
window.requestAnimationFrame(tick);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetState() {
|
||||||
|
startScreenX = null;
|
||||||
|
startScreenY = null;
|
||||||
|
isTracking = false;
|
||||||
|
swipeDirectionLocked = null;
|
||||||
|
isSwiping.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeContent() {
|
||||||
|
return moveBySystem(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveStartByTouch(event: TouchEvent) {
|
||||||
if (!prefer.r.enableHorizontalSwipe.value) return;
|
if (!prefer.r.enableHorizontalSwipe.value) return;
|
||||||
|
|
||||||
if (event.touches.length !== 1) return;
|
if (event.touches.length !== 1) return;
|
||||||
|
|
||||||
if (hasSomethingToDoWithXSwipe(event.target as HTMLElement)) return;
|
if (hasSomethingToDoWithXSwipe(event.target as HTMLElement)) return;
|
||||||
|
|
||||||
startScreenX = event.touches[0].screenX;
|
cancelMoveBySystem();
|
||||||
startScreenY = event.touches[0].screenY;
|
|
||||||
|
startScreenX = getScreenX(event);
|
||||||
|
startScreenY = getScreenY(event);
|
||||||
|
isTracking = true;
|
||||||
swipeDirectionLocked = null; // スワイプ方向をリセット
|
swipeDirectionLocked = null; // スワイプ方向をリセット
|
||||||
|
swipeAborted = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function touchMove(event: TouchEvent) {
|
function moving(event: TouchEvent) {
|
||||||
if (!prefer.r.enableHorizontalSwipe.value) return;
|
if (!prefer.r.enableHorizontalSwipe.value) return;
|
||||||
|
|
||||||
if (event.touches.length !== 1) return;
|
if (event.touches.length !== 1) return;
|
||||||
|
|
||||||
if (startScreenX == null || startScreenY == null) return;
|
if (startScreenX == null || startScreenY == null) return;
|
||||||
|
if (!isTracking) return;
|
||||||
|
|
||||||
if (swipeAborted) return;
|
if (swipeAborted) return;
|
||||||
|
|
||||||
if (hasSomethingToDoWithXSwipe(event.target as HTMLElement)) return;
|
if (hasSomethingToDoWithXSwipe(event.target as HTMLElement)) return;
|
||||||
|
|
||||||
let distanceX = event.touches[0].screenX - startScreenX;
|
const rawDistanceX = getScreenX(event) - startScreenX;
|
||||||
let distanceY = event.touches[0].screenY - startScreenY;
|
const rawDistanceY = getScreenY(event) - startScreenY;
|
||||||
|
|
||||||
// スワイプ方向をロック
|
// スワイプ方向をロック
|
||||||
if (!swipeDirectionLocked) {
|
if (!swipeDirectionLocked) {
|
||||||
const angle = Math.abs(Math.atan2(distanceY, distanceX) * (180 / Math.PI));
|
const moveDistance = Math.hypot(rawDistanceX, rawDistanceY);
|
||||||
if (angle > 90 - SWIPE_DIRECTION_ANGLE_THRESHOLD && angle < 90 + SWIPE_DIRECTION_ANGLE_THRESHOLD) {
|
if (moveDistance >= DIRECTION_LOCK_START_DISTANCE) {
|
||||||
swipeDirectionLocked = 'vertical';
|
const angle = Math.abs(Math.atan2(rawDistanceY, rawDistanceX) * (180 / Math.PI));
|
||||||
} else {
|
if (angle > 90 - SWIPE_DIRECTION_ANGLE_THRESHOLD && angle < 90 + SWIPE_DIRECTION_ANGLE_THRESHOLD) {
|
||||||
swipeDirectionLocked = 'horizontal';
|
swipeDirectionLocked = 'vertical';
|
||||||
|
} else {
|
||||||
|
swipeDirectionLocked = 'horizontal';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 縦方向のスワイプの場合は中断
|
// 縦方向のスワイプの場合は中断
|
||||||
if (swipeDirectionLocked === 'vertical') {
|
if (swipeDirectionLocked === 'vertical') {
|
||||||
swipeAborted = true;
|
swipeAborted = true;
|
||||||
pullDistance.value = 0;
|
setPullDistance(0);
|
||||||
isSwiping.value = false;
|
resetState();
|
||||||
window.setTimeout(() => {
|
// クラスは即座に落とす(縦スクロールを邪魔しない)
|
||||||
isSwipingForClass.value = false;
|
isSwipingForClass.value = false;
|
||||||
}, 400);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Math.abs(distanceX) < MIN_SWIPE_DISTANCE) return;
|
if (Math.abs(rawDistanceX) < MIN_SWIPE_DISTANCE) return;
|
||||||
if (Math.abs(distanceX) > MAX_SWIPE_DISTANCE) return;
|
|
||||||
|
let distanceX = clamp(toEffectiveDistance(rawDistanceX), -MAX_SWIPE_DISTANCE, MAX_SWIPE_DISTANCE);
|
||||||
|
|
||||||
if (currentTabIndex.value === 0 || props.tabs[currentTabIndex.value - 1].onClick) {
|
if (currentTabIndex.value === 0 || props.tabs[currentTabIndex.value - 1].onClick) {
|
||||||
distanceX = Math.min(distanceX, 0);
|
distanceX = Math.min(distanceX, 0);
|
||||||
|
|
@ -131,32 +261,14 @@ function touchMove(event: TouchEvent) {
|
||||||
|
|
||||||
isSwiping.value = true;
|
isSwiping.value = true;
|
||||||
isSwipingForClass.value = true;
|
isSwipingForClass.value = true;
|
||||||
nextTick(() => {
|
setPullDistance(distanceX);
|
||||||
// グリッチを控えるため、1.5px以上の差がないと更新しない
|
|
||||||
if (Math.abs(distanceX - pullDistance.value) < 1.5) return;
|
|
||||||
pullDistance.value = distanceX;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function touchEnd(event: TouchEvent) {
|
function onSwipeRelease(distance: number) {
|
||||||
if (swipeAborted) {
|
const effectiveDistance = toEffectiveDistance(distance);
|
||||||
swipeAborted = false;
|
const effectiveThreshold = Math.max(SWIPE_DISTANCE_THRESHOLD - MIN_SWIPE_DISTANCE, 0);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!prefer.r.enableHorizontalSwipe.value) return;
|
if (Math.abs(effectiveDistance) > effectiveThreshold) {
|
||||||
|
|
||||||
if (event.touches.length !== 0) return;
|
|
||||||
|
|
||||||
if (startScreenX == null) return;
|
|
||||||
|
|
||||||
if (!isSwiping.value) return;
|
|
||||||
|
|
||||||
if (hasSomethingToDoWithXSwipe(event.target as HTMLElement)) return;
|
|
||||||
|
|
||||||
const distance = event.changedTouches[0].screenX - startScreenX;
|
|
||||||
|
|
||||||
if (Math.abs(distance) > SWIPE_DISTANCE_THRESHOLD) {
|
|
||||||
if (distance > 0) {
|
if (distance > 0) {
|
||||||
if (props.tabs[currentTabIndex.value - 1] && !props.tabs[currentTabIndex.value - 1].onClick) {
|
if (props.tabs[currentTabIndex.value - 1] && !props.tabs[currentTabIndex.value - 1].onClick) {
|
||||||
tabModel.value = props.tabs[currentTabIndex.value - 1].key;
|
tabModel.value = props.tabs[currentTabIndex.value - 1].key;
|
||||||
|
|
@ -169,28 +281,56 @@ function touchEnd(event: TouchEvent) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pullDistance.value = 0;
|
function moveEndByTouch(event: TouchEvent) {
|
||||||
isSwiping.value = false;
|
if (swipeAborted) {
|
||||||
window.setTimeout(() => {
|
swipeAborted = false;
|
||||||
|
resetState();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!prefer.r.enableHorizontalSwipe.value) return;
|
||||||
|
|
||||||
|
if (event.touches.length !== 0) return;
|
||||||
|
|
||||||
|
if (startScreenX == null) return;
|
||||||
|
if (!isTracking) return;
|
||||||
|
|
||||||
|
if (!isSwiping.value) return;
|
||||||
|
|
||||||
|
if (hasSomethingToDoWithXSwipe(event.target as HTMLElement)) return;
|
||||||
|
|
||||||
|
const distance = event.changedTouches[0].screenX - startScreenX;
|
||||||
|
onSwipeRelease(distance);
|
||||||
|
|
||||||
|
resetState();
|
||||||
|
closeContent().finally(() => {
|
||||||
isSwipingForClass.value = false;
|
isSwipingForClass.value = false;
|
||||||
}, 400);
|
});
|
||||||
|
}
|
||||||
|
|
||||||
swipeDirectionLocked = null; // スワイプ方向をリセット
|
function moveCancelByTouch(_event: TouchEvent) {
|
||||||
|
swipeAborted = false;
|
||||||
|
resetState();
|
||||||
|
closeContent().finally(() => {
|
||||||
|
isSwipingForClass.value = false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 横スワイプに関与する可能性のある要素を調べる */
|
/** 横スワイプに関与する可能性のある要素を調べる */
|
||||||
function hasSomethingToDoWithXSwipe(el: HTMLElement) {
|
function hasSomethingToDoWithXSwipe(el: HTMLElement) {
|
||||||
|
// 入力のじゃまになる
|
||||||
if (['INPUT', 'TEXTAREA'].includes(el.tagName)) return true;
|
if (['INPUT', 'TEXTAREA'].includes(el.tagName)) return true;
|
||||||
if (el.isContentEditable) return true;
|
if (el.isContentEditable) return true;
|
||||||
if (el.scrollWidth > el.clientWidth) return true;
|
|
||||||
|
|
||||||
const style = window.getComputedStyle(el);
|
const style = window.getComputedStyle(el);
|
||||||
if (['absolute', 'fixed', 'sticky'].includes(style.position)) return true;
|
// 実際に横スクロールできる要素では横スワイプができないほうがよい
|
||||||
if (['scroll', 'auto'].includes(style.overflowX)) return true;
|
if (['scroll', 'auto'].includes(style.overflowX) && el.scrollWidth > el.clientWidth + 1) return true;
|
||||||
|
// すでに横スワイプを禁止している要素では横スワイプができないほうがよい
|
||||||
if (style.touchAction === 'pan-x') return true;
|
if (style.touchAction === 'pan-x') return true;
|
||||||
|
|
||||||
if (el.parentElement && el.parentElement !== rootEl.value) {
|
if (el.parentElement != null && el.parentElement !== rootEl.value) {
|
||||||
return hasSomethingToDoWithXSwipe(el.parentElement);
|
return hasSomethingToDoWithXSwipe(el.parentElement);
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -246,6 +386,6 @@ watch(tabModel, (newTab, oldTab) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
.swiping {
|
.swiping {
|
||||||
transition: transform .2s ease-out;
|
transition: none;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -151,7 +151,7 @@ import MkButton from '@/components/MkButton.vue';
|
||||||
import MkFolder from '@/components/MkFolder.vue';
|
import MkFolder from '@/components/MkFolder.vue';
|
||||||
import MkSwitch from '@/components/MkSwitch.vue';
|
import MkSwitch from '@/components/MkSwitch.vue';
|
||||||
import { deepClone } from '@/utility/clone.js';
|
import { deepClone } from '@/utility/clone.js';
|
||||||
import { ensureSignin } from '@/i.js';
|
import { $i } from '@/i.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { userPage } from '@/filters/user.js';
|
import { userPage } from '@/filters/user.js';
|
||||||
|
|
@ -160,8 +160,6 @@ import * as os from '@/os.js';
|
||||||
import { confetti } from '@/utility/confetti.js';
|
import { confetti } from '@/utility/confetti.js';
|
||||||
import { genId } from '@/utility/id.js';
|
import { genId } from '@/utility/id.js';
|
||||||
|
|
||||||
const $i = ensureSignin();
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
game: Misskey.entities.ReversiGameDetailed;
|
game: Misskey.entities.ReversiGameDetailed;
|
||||||
connection?: Misskey.IChannelConnection<Misskey.Channels['reversiGame']> | null;
|
connection?: Misskey.IChannelConnection<Misskey.Channels['reversiGame']> | null;
|
||||||
|
|
@ -182,13 +180,13 @@ const engine = shallowRef<Reversi.Game>(Reversi.Serializer.restoreGame({
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const iAmPlayer = computed(() => {
|
const iAmPlayer = computed(() => {
|
||||||
return game.value.user1Id === $i.id || game.value.user2Id === $i.id;
|
return game.value.user1Id === $i?.id || game.value.user2Id === $i?.id;
|
||||||
});
|
});
|
||||||
|
|
||||||
const myColor = computed(() => {
|
const myColor = computed(() => {
|
||||||
if (!iAmPlayer.value) return null;
|
if (!iAmPlayer.value) return null;
|
||||||
if (game.value.user1Id === $i.id && game.value.black === 1) return true;
|
if (game.value.user1Id === $i?.id && game.value.black === 1) return true;
|
||||||
if (game.value.user2Id === $i.id && game.value.black === 2) return true;
|
if (game.value.user2Id === $i?.id && game.value.black === 2) return true;
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -219,7 +217,7 @@ const isMyTurn = computed(() => {
|
||||||
if (!iAmPlayer.value) return false;
|
if (!iAmPlayer.value) return false;
|
||||||
const u = turnUser.value;
|
const u = turnUser.value;
|
||||||
if (u == null) return false;
|
if (u == null) return false;
|
||||||
return u.id === $i.id;
|
return u.id === $i?.id;
|
||||||
});
|
});
|
||||||
|
|
||||||
const cellsStyle = computed(() => {
|
const cellsStyle = computed(() => {
|
||||||
|
|
@ -354,7 +352,7 @@ function onStreamEnded(x: {
|
||||||
}) {
|
}) {
|
||||||
game.value = deepClone(x.game);
|
game.value = deepClone(x.game);
|
||||||
|
|
||||||
if (game.value.winnerId === $i.id) {
|
if (game.value.winnerId === $i?.id) {
|
||||||
confetti({
|
confetti({
|
||||||
duration: 1000 * 3,
|
duration: 1000 * 3,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -116,7 +116,7 @@ import * as Misskey from 'misskey-js';
|
||||||
import * as Reversi from 'misskey-reversi';
|
import * as Reversi from 'misskey-reversi';
|
||||||
import type { MenuItem } from '@/types/menu.js';
|
import type { MenuItem } from '@/types/menu.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { ensureSignin } from '@/i.js';
|
import { $i } from '@/i.js';
|
||||||
import { deepClone } from '@/utility/clone.js';
|
import { deepClone } from '@/utility/clone.js';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import MkRadios from '@/components/MkRadios.vue';
|
import MkRadios from '@/components/MkRadios.vue';
|
||||||
|
|
@ -126,8 +126,6 @@ import * as os from '@/os.js';
|
||||||
import type { MkRadiosOption } from '@/components/MkRadios.vue';
|
import type { MkRadiosOption } from '@/components/MkRadios.vue';
|
||||||
import { useRouter } from '@/router.js';
|
import { useRouter } from '@/router.js';
|
||||||
|
|
||||||
const $i = ensureSignin();
|
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const mapCategories = Array.from(new Set(Object.values(Reversi.maps).map(x => x.category)));
|
const mapCategories = Array.from(new Set(Object.values(Reversi.maps).map(x => x.category)));
|
||||||
|
|
@ -158,13 +156,13 @@ const mapName = computed(() => {
|
||||||
return found ? found.name! : '-Custom-';
|
return found ? found.name! : '-Custom-';
|
||||||
});
|
});
|
||||||
const isReady = computed(() => {
|
const isReady = computed(() => {
|
||||||
if (game.value.user1Id === $i.id && game.value.user1Ready) return true;
|
if (game.value.user1Id === $i?.id && game.value.user1Ready) return true;
|
||||||
if (game.value.user2Id === $i.id && game.value.user2Ready) return true;
|
if (game.value.user2Id === $i?.id && game.value.user2Ready) return true;
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
const isOpReady = computed(() => {
|
const isOpReady = computed(() => {
|
||||||
if (game.value.user1Id !== $i.id && game.value.user1Ready) return true;
|
if (game.value.user1Id !== $i?.id && game.value.user1Ready) return true;
|
||||||
if (game.value.user2Id !== $i.id && game.value.user2Ready) return true;
|
if (game.value.user2Id !== $i?.id && game.value.user2Ready) return true;
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -241,7 +239,7 @@ function updateSettings(key: typeof Misskey.reversiUpdateKeys[number]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function onUpdateSettings<K extends typeof Misskey.reversiUpdateKeys[number]>({ userId, key, value }: { userId: string; key: K; value: Misskey.entities.ReversiGameDetailed[K]; }) {
|
function onUpdateSettings<K extends typeof Misskey.reversiUpdateKeys[number]>({ userId, key, value }: { userId: string; key: K; value: Misskey.entities.ReversiGameDetailed[K]; }) {
|
||||||
if (userId === $i.id) return;
|
if (userId === $i?.id) return;
|
||||||
if (game.value[key] === value) return;
|
if (game.value[key] === value) return;
|
||||||
game.value[key] = value;
|
game.value[key] = value;
|
||||||
if (isReady.value) {
|
if (isReady.value) {
|
||||||
|
|
|
||||||
|
|
@ -17,15 +17,13 @@ import GameBoard from './game.board.vue';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePage } from '@/page.js';
|
||||||
import { useStream } from '@/stream.js';
|
import { useStream } from '@/stream.js';
|
||||||
import { ensureSignin } from '@/i.js';
|
import { $i } from '@/i.js';
|
||||||
import { useRouter } from '@/router.js';
|
import { useRouter } from '@/router.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { url } from '@@/js/config.js';
|
import { url } from '@@/js/config.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { useInterval } from '@@/js/use-interval.js';
|
import { useInterval } from '@@/js/use-interval.js';
|
||||||
|
|
||||||
const $i = ensureSignin();
|
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
|
@ -74,7 +72,7 @@ async function fetchGame() {
|
||||||
connection.value.on('canceled', x => {
|
connection.value.on('canceled', x => {
|
||||||
connection.value?.dispose();
|
connection.value?.dispose();
|
||||||
|
|
||||||
if (x.userId !== $i.id) {
|
if (x.userId !== $i?.id) {
|
||||||
os.alert({
|
os.alert({
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
text: i18n.ts._reversi.gameCanceled,
|
text: i18n.ts._reversi.gameCanceled,
|
||||||
|
|
|
||||||
|
|
@ -29,9 +29,9 @@
|
||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/js-yaml": "4.0.9",
|
"@types/js-yaml": "4.0.9",
|
||||||
"@types/node": "24.10.4",
|
"@types/node": "24.10.9",
|
||||||
"@typescript-eslint/eslint-plugin": "8.50.1",
|
"@typescript-eslint/eslint-plugin": "8.53.0",
|
||||||
"@typescript-eslint/parser": "8.50.1",
|
"@typescript-eslint/parser": "8.53.0",
|
||||||
"chokidar": "5.0.0",
|
"chokidar": "5.0.0",
|
||||||
"esbuild": "0.27.2",
|
"esbuild": "0.27.2",
|
||||||
"execa": "9.6.1",
|
"execa": "9.6.1",
|
||||||
|
|
|
||||||
|
|
@ -11,14 +11,14 @@
|
||||||
"lint": "pnpm typecheck && pnpm eslint"
|
"lint": "pnpm typecheck && pnpm eslint"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "24.10.4",
|
"@types/node": "24.10.9",
|
||||||
"@types/wawoff2": "1.0.2",
|
"@types/wawoff2": "1.0.2",
|
||||||
"@typescript-eslint/eslint-plugin": "8.50.1",
|
"@typescript-eslint/eslint-plugin": "8.53.0",
|
||||||
"@typescript-eslint/parser": "8.50.1"
|
"@typescript-eslint/parser": "8.53.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tabler/icons-webfont": "3.35.0",
|
"@tabler/icons-webfont": "3.35.0",
|
||||||
"harfbuzzjs": "0.4.14",
|
"harfbuzzjs": "0.4.15",
|
||||||
"tsx": "4.21.0",
|
"tsx": "4.21.0",
|
||||||
"wawoff2": "2.0.1"
|
"wawoff2": "2.0.1"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -25,10 +25,10 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/matter-js": "0.20.2",
|
"@types/matter-js": "0.20.2",
|
||||||
"@types/node": "24.10.4",
|
"@types/node": "24.10.9",
|
||||||
"@types/seedrandom": "3.0.8",
|
"@types/seedrandom": "3.0.8",
|
||||||
"@typescript-eslint/eslint-plugin": "8.50.1",
|
"@typescript-eslint/eslint-plugin": "8.53.0",
|
||||||
"@typescript-eslint/parser": "8.50.1",
|
"@typescript-eslint/parser": "8.53.0",
|
||||||
"esbuild": "0.27.2",
|
"esbuild": "0.27.2",
|
||||||
"execa": "9.6.1",
|
"execa": "9.6.1",
|
||||||
"nodemon": "3.1.11"
|
"nodemon": "3.1.11"
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,10 @@
|
||||||
"generate": "tsx src/generator.ts && eslint ./built/**/*.ts --fix"
|
"generate": "tsx src/generator.ts && eslint ./built/**/*.ts --fix"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@readme/openapi-parser": "5.4.0",
|
"@readme/openapi-parser": "5.5.0",
|
||||||
"@types/node": "24.10.4",
|
"@types/node": "24.10.9",
|
||||||
"@typescript-eslint/eslint-plugin": "8.50.1",
|
"@typescript-eslint/eslint-plugin": "8.53.0",
|
||||||
"@typescript-eslint/parser": "8.50.1",
|
"@typescript-eslint/parser": "8.53.0",
|
||||||
"openapi-types": "12.1.3",
|
"openapi-types": "12.1.3",
|
||||||
"openapi-typescript": "7.10.1",
|
"openapi-typescript": "7.10.1",
|
||||||
"ts-case-convert": "2.1.0",
|
"ts-case-convert": "2.1.0",
|
||||||
|
|
|
||||||
|
|
@ -37,17 +37,17 @@
|
||||||
"directory": "packages/misskey-js"
|
"directory": "packages/misskey-js"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@microsoft/api-extractor": "7.55.2",
|
"@microsoft/api-extractor": "7.55.5",
|
||||||
"@types/node": "24.10.4",
|
"@types/node": "24.10.9",
|
||||||
"@typescript-eslint/eslint-plugin": "8.50.1",
|
"@typescript-eslint/eslint-plugin": "8.53.0",
|
||||||
"@typescript-eslint/parser": "8.50.1",
|
"@typescript-eslint/parser": "8.53.0",
|
||||||
"@vitest/coverage-v8": "4.0.16",
|
"@vitest/coverage-v8": "4.0.17",
|
||||||
"esbuild": "0.27.2",
|
"esbuild": "0.27.2",
|
||||||
"execa": "9.6.1",
|
"execa": "9.6.1",
|
||||||
"ncp": "2.0.0",
|
"ncp": "2.0.0",
|
||||||
"nodemon": "3.1.11",
|
"nodemon": "3.1.11",
|
||||||
"tsd": "0.33.0",
|
"tsd": "0.33.0",
|
||||||
"vitest": "4.0.16",
|
"vitest": "4.0.17",
|
||||||
"vitest-websocket-mock": "0.5.0"
|
"vitest-websocket-mock": "0.5.0"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
|
|
||||||
|
|
@ -24,9 +24,9 @@
|
||||||
"lint": "pnpm typecheck && pnpm eslint"
|
"lint": "pnpm typecheck && pnpm eslint"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "24.10.4",
|
"@types/node": "24.10.9",
|
||||||
"@typescript-eslint/eslint-plugin": "8.50.1",
|
"@typescript-eslint/eslint-plugin": "8.53.0",
|
||||||
"@typescript-eslint/parser": "8.50.1",
|
"@typescript-eslint/parser": "8.53.0",
|
||||||
"esbuild": "0.27.2",
|
"esbuild": "0.27.2",
|
||||||
"execa": "9.6.1",
|
"execa": "9.6.1",
|
||||||
"nodemon": "3.1.11"
|
"nodemon": "3.1.11"
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
"misskey-js": "workspace:*"
|
"misskey-js": "workspace:*"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@typescript-eslint/parser": "8.50.1",
|
"@typescript-eslint/parser": "8.53.0",
|
||||||
"@typescript/lib-webworker": "npm:@types/serviceworker@0.0.74",
|
"@typescript/lib-webworker": "npm:@types/serviceworker@0.0.74",
|
||||||
"eslint-plugin-import": "2.32.0",
|
"eslint-plugin-import": "2.32.0",
|
||||||
"nodemon": "3.1.11"
|
"nodemon": "3.1.11"
|
||||||
|
|
|
||||||
4279
pnpm-lock.yaml
4279
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
|
@ -35,4 +35,8 @@ ignorePatchFailures: false
|
||||||
minimumReleaseAge: 10080 # delay 7days to mitigate supply-chain attack
|
minimumReleaseAge: 10080 # delay 7days to mitigate supply-chain attack
|
||||||
minimumReleaseAgeExclude:
|
minimumReleaseAgeExclude:
|
||||||
- '@syuilo/aiscript'
|
- '@syuilo/aiscript'
|
||||||
- systeminformation # 脆弱性対応。そのうち消すこと
|
- '@fastify/express' # 脆弱性対応。そのうち消すこと
|
||||||
|
- 'lodash' # 脆弱性対応。そのうち消すこと
|
||||||
|
- 'tar' # 脆弱性対応。そのうち消すこと
|
||||||
|
# Renovate security update: pnpm@10.28.2
|
||||||
|
- pnpm@10.28.2
|
||||||
|
|
|
||||||
|
|
@ -9,16 +9,16 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/mdast": "4.0.4",
|
"@types/mdast": "4.0.4",
|
||||||
"@types/node": "24.10.4",
|
"@types/node": "24.10.9",
|
||||||
"@vitest/coverage-v8": "4.0.15",
|
"@vitest/coverage-v8": "4.0.17",
|
||||||
"mdast-util-to-string": "4.0.0",
|
"mdast-util-to-string": "4.0.0",
|
||||||
"remark": "15.0.1",
|
"remark": "15.0.1",
|
||||||
"remark-parse": "11.0.0",
|
"remark-parse": "11.0.0",
|
||||||
"typescript": "5.9.3",
|
"typescript": "5.9.3",
|
||||||
"unified": "11.0.5",
|
"unified": "11.0.5",
|
||||||
"vite": "7.3.0",
|
"vite": "7.3.1",
|
||||||
"vite-node": "5.2.0",
|
"vite-node": "5.2.0",
|
||||||
"vitest": "4.0.15"
|
"vitest": "4.0.17"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/helper-string-parser": {
|
"node_modules/@babel/helper-string-parser": {
|
||||||
|
|
@ -524,10 +524,11 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@jridgewell/resolve-uri": {
|
"node_modules/@jridgewell/resolve-uri": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||||
"integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==",
|
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.0.0"
|
"node": ">=6.0.0"
|
||||||
}
|
}
|
||||||
|
|
@ -916,12 +917,11 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "24.10.4",
|
"version": "24.10.9",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.9.tgz",
|
||||||
"integrity": "sha512-vnDVpYPMzs4wunl27jHrfmwojOGKya0xyM3sH+UE5iv5uPS6vX7UIoh6m+vQc5LGBq52HBKPIn/zcSZVzeDEZg==",
|
"integrity": "sha512-ne4A0IpG3+2ETuREInjPNhUGis1SFjv1d5asp8MzEAGtOZeTeHVDOYqOgqfhvseqg/iXty2hjBf1zAOb7RNiNw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~7.16.0"
|
"undici-types": "~7.16.0"
|
||||||
}
|
}
|
||||||
|
|
@ -933,18 +933,17 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@vitest/coverage-v8": {
|
"node_modules/@vitest/coverage-v8": {
|
||||||
"version": "4.0.15",
|
"version": "4.0.17",
|
||||||
"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.15.tgz",
|
"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.17.tgz",
|
||||||
"integrity": "sha512-FUJ+1RkpTFW7rQITdgTi93qOCWJobWhBirEPCeXh2SW2wsTlFxy51apDz5gzG+ZEYt/THvWeNmhdAoS9DTwpCw==",
|
"integrity": "sha512-/6zU2FLGg0jsd+ePZcwHRy3+WpNTBBhDY56P4JTRqUN/Dp6CvOEa9HrikcQ4KfV2b2kAHUFB4dl1SuocWXSFEw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@bcoe/v8-coverage": "^1.0.2",
|
"@bcoe/v8-coverage": "^1.0.2",
|
||||||
"@vitest/utils": "4.0.15",
|
"@vitest/utils": "4.0.17",
|
||||||
"ast-v8-to-istanbul": "^0.3.8",
|
"ast-v8-to-istanbul": "^0.3.10",
|
||||||
"istanbul-lib-coverage": "^3.2.2",
|
"istanbul-lib-coverage": "^3.2.2",
|
||||||
"istanbul-lib-report": "^3.0.1",
|
"istanbul-lib-report": "^3.0.1",
|
||||||
"istanbul-lib-source-maps": "^5.0.6",
|
|
||||||
"istanbul-reports": "^3.2.0",
|
"istanbul-reports": "^3.2.0",
|
||||||
"magicast": "^0.5.1",
|
"magicast": "^0.5.1",
|
||||||
"obug": "^2.1.1",
|
"obug": "^2.1.1",
|
||||||
|
|
@ -955,8 +954,8 @@
|
||||||
"url": "https://opencollective.com/vitest"
|
"url": "https://opencollective.com/vitest"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@vitest/browser": "4.0.15",
|
"@vitest/browser": "4.0.17",
|
||||||
"vitest": "4.0.15"
|
"vitest": "4.0.17"
|
||||||
},
|
},
|
||||||
"peerDependenciesMeta": {
|
"peerDependenciesMeta": {
|
||||||
"@vitest/browser": {
|
"@vitest/browser": {
|
||||||
|
|
@ -965,16 +964,16 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vitest/expect": {
|
"node_modules/@vitest/expect": {
|
||||||
"version": "4.0.15",
|
"version": "4.0.17",
|
||||||
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.15.tgz",
|
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.17.tgz",
|
||||||
"integrity": "sha512-Gfyva9/GxPAWXIWjyGDli9O+waHDC0Q0jaLdFP1qPAUUfo1FEXPXUfUkp3eZA0sSq340vPycSyOlYUeM15Ft1w==",
|
"integrity": "sha512-mEoqP3RqhKlbmUmntNDDCJeTDavDR+fVYkSOw8qRwJFaW/0/5zA9zFeTrHqNtcmwh6j26yMmwx2PqUDPzt5ZAQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@standard-schema/spec": "^1.0.0",
|
"@standard-schema/spec": "^1.0.0",
|
||||||
"@types/chai": "^5.2.2",
|
"@types/chai": "^5.2.2",
|
||||||
"@vitest/spy": "4.0.15",
|
"@vitest/spy": "4.0.17",
|
||||||
"@vitest/utils": "4.0.15",
|
"@vitest/utils": "4.0.17",
|
||||||
"chai": "^6.2.1",
|
"chai": "^6.2.1",
|
||||||
"tinyrainbow": "^3.0.3"
|
"tinyrainbow": "^3.0.3"
|
||||||
},
|
},
|
||||||
|
|
@ -983,13 +982,13 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vitest/mocker": {
|
"node_modules/@vitest/mocker": {
|
||||||
"version": "4.0.15",
|
"version": "4.0.17",
|
||||||
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.15.tgz",
|
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.17.tgz",
|
||||||
"integrity": "sha512-CZ28GLfOEIFkvCFngN8Sfx5h+Se0zN+h4B7yOsPVCcgtiO7t5jt9xQh2E1UkFep+eb9fjyMfuC5gBypwb07fvQ==",
|
"integrity": "sha512-+ZtQhLA3lDh1tI2wxe3yMsGzbp7uuJSWBM1iTIKCbppWTSBN09PUC+L+fyNlQApQoR+Ps8twt2pbSSXg2fQVEQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vitest/spy": "4.0.15",
|
"@vitest/spy": "4.0.17",
|
||||||
"estree-walker": "^3.0.3",
|
"estree-walker": "^3.0.3",
|
||||||
"magic-string": "^0.30.21"
|
"magic-string": "^0.30.21"
|
||||||
},
|
},
|
||||||
|
|
@ -1010,9 +1009,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vitest/pretty-format": {
|
"node_modules/@vitest/pretty-format": {
|
||||||
"version": "4.0.15",
|
"version": "4.0.17",
|
||||||
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.15.tgz",
|
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.17.tgz",
|
||||||
"integrity": "sha512-SWdqR8vEv83WtZcrfLNqlqeQXlQLh2iilO1Wk1gv4eiHKjEzvgHb2OVc3mIPyhZE6F+CtfYjNlDJwP5MN6Km7A==",
|
"integrity": "sha512-Ah3VAYmjcEdHg6+MwFE17qyLqBHZ+ni2ScKCiW2XrlSBV4H3Z7vYfPfz7CWQ33gyu76oc0Ai36+kgLU3rfF4nw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -1023,13 +1022,13 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vitest/runner": {
|
"node_modules/@vitest/runner": {
|
||||||
"version": "4.0.15",
|
"version": "4.0.17",
|
||||||
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.15.tgz",
|
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.17.tgz",
|
||||||
"integrity": "sha512-+A+yMY8dGixUhHmNdPUxOh0la6uVzun86vAbuMT3hIDxMrAOmn5ILBHm8ajrqHE0t8R9T1dGnde1A5DTnmi3qw==",
|
"integrity": "sha512-JmuQyf8aMWoo/LmNFppdpkfRVHJcsgzkbCA+/Bk7VfNH7RE6Ut2qxegeyx2j3ojtJtKIbIGy3h+KxGfYfk28YQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vitest/utils": "4.0.15",
|
"@vitest/utils": "4.0.17",
|
||||||
"pathe": "^2.0.3"
|
"pathe": "^2.0.3"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
|
|
@ -1037,13 +1036,13 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vitest/snapshot": {
|
"node_modules/@vitest/snapshot": {
|
||||||
"version": "4.0.15",
|
"version": "4.0.17",
|
||||||
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.15.tgz",
|
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.17.tgz",
|
||||||
"integrity": "sha512-A7Ob8EdFZJIBjLjeO0DZF4lqR6U7Ydi5/5LIZ0xcI+23lYlsYJAfGn8PrIWTYdZQRNnSRlzhg0zyGu37mVdy5g==",
|
"integrity": "sha512-npPelD7oyL+YQM2gbIYvlavlMVWUfNNGZPcu0aEUQXt7FXTuqhmgiYupPnAanhKvyP6Srs2pIbWo30K0RbDtRQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vitest/pretty-format": "4.0.15",
|
"@vitest/pretty-format": "4.0.17",
|
||||||
"magic-string": "^0.30.21",
|
"magic-string": "^0.30.21",
|
||||||
"pathe": "^2.0.3"
|
"pathe": "^2.0.3"
|
||||||
},
|
},
|
||||||
|
|
@ -1052,9 +1051,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vitest/spy": {
|
"node_modules/@vitest/spy": {
|
||||||
"version": "4.0.15",
|
"version": "4.0.17",
|
||||||
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.15.tgz",
|
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.17.tgz",
|
||||||
"integrity": "sha512-+EIjOJmnY6mIfdXtE/bnozKEvTC4Uczg19yeZ2vtCz5Yyb0QQ31QWVQ8hswJ3Ysx/K2EqaNsVanjr//2+P3FHw==",
|
"integrity": "sha512-I1bQo8QaP6tZlTomQNWKJE6ym4SHf3oLS7ceNjozxxgzavRAgZDc06T7kD8gb9bXKEgcLNt00Z+kZO6KaJ62Ew==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
|
|
@ -1062,13 +1061,13 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vitest/utils": {
|
"node_modules/@vitest/utils": {
|
||||||
"version": "4.0.15",
|
"version": "4.0.17",
|
||||||
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.15.tgz",
|
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.17.tgz",
|
||||||
"integrity": "sha512-HXjPW2w5dxhTD0dLwtYHDnelK3j8sR8cWIaLxr22evTyY6q8pRCjZSmhRWVjBaOVXChQd6AwMzi9pucorXCPZA==",
|
"integrity": "sha512-RG6iy+IzQpa9SB8HAFHJ9Y+pTzI+h8553MrciN9eC6TFBErqrQaTas4vG+MVj8S4uKk8uTT2p0vgZPnTdxd96w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vitest/pretty-format": "4.0.15",
|
"@vitest/pretty-format": "4.0.17",
|
||||||
"tinyrainbow": "^3.0.3"
|
"tinyrainbow": "^3.0.3"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
|
|
@ -1086,9 +1085,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ast-v8-to-istanbul": {
|
"node_modules/ast-v8-to-istanbul": {
|
||||||
"version": "0.3.8",
|
"version": "0.3.10",
|
||||||
"resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.8.tgz",
|
"resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.10.tgz",
|
||||||
"integrity": "sha512-szgSZqUxI5T8mLKvS7WTjF9is+MVbOeLADU73IseOcrqhxr/VAvy6wfoVE39KnKzA7JRhjF5eUagNlHwvZPlKQ==",
|
"integrity": "sha512-p4K7vMz2ZSk3wN8l5o3y2bJAoZXT3VuJI5OLTATY/01CYWumWvwkUw0SqDBnNq6IiTO3qDa1eSQDibAV8g7XOQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -1117,9 +1116,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/chai": {
|
"node_modules/chai": {
|
||||||
"version": "6.2.1",
|
"version": "6.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/chai/-/chai-6.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
|
||||||
"integrity": "sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg==",
|
"integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|
@ -1347,21 +1346,6 @@
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/istanbul-lib-source-maps": {
|
|
||||||
"version": "5.0.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz",
|
|
||||||
"integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "BSD-3-Clause",
|
|
||||||
"dependencies": {
|
|
||||||
"@jridgewell/trace-mapping": "^0.3.23",
|
|
||||||
"debug": "^4.1.1",
|
|
||||||
"istanbul-lib-coverage": "^3.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/istanbul-reports": {
|
"node_modules/istanbul-reports": {
|
||||||
"version": "3.2.0",
|
"version": "3.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz",
|
||||||
|
|
@ -2012,7 +1996,6 @@
|
||||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
|
|
@ -2375,9 +2358,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "7.3.0",
|
"version": "7.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
|
||||||
"integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==",
|
"integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -2473,20 +2456,19 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vitest": {
|
"node_modules/vitest": {
|
||||||
"version": "4.0.15",
|
"version": "4.0.17",
|
||||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.15.tgz",
|
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.17.tgz",
|
||||||
"integrity": "sha512-n1RxDp8UJm6N0IbJLQo+yzLZ2sQCDyl1o0LeugbPWf8+8Fttp29GghsQBjYJVmWq3gBFfe9Hs1spR44vovn2wA==",
|
"integrity": "sha512-FQMeF0DJdWY0iOnbv466n/0BudNdKj1l5jYgl5JVTwjSsZSlqyXFt/9+1sEyhR6CLowbZpV7O1sCHrzBhucKKg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vitest/expect": "4.0.15",
|
"@vitest/expect": "4.0.17",
|
||||||
"@vitest/mocker": "4.0.15",
|
"@vitest/mocker": "4.0.17",
|
||||||
"@vitest/pretty-format": "4.0.15",
|
"@vitest/pretty-format": "4.0.17",
|
||||||
"@vitest/runner": "4.0.15",
|
"@vitest/runner": "4.0.17",
|
||||||
"@vitest/snapshot": "4.0.15",
|
"@vitest/snapshot": "4.0.17",
|
||||||
"@vitest/spy": "4.0.15",
|
"@vitest/spy": "4.0.17",
|
||||||
"@vitest/utils": "4.0.15",
|
"@vitest/utils": "4.0.17",
|
||||||
"es-module-lexer": "^1.7.0",
|
"es-module-lexer": "^1.7.0",
|
||||||
"expect-type": "^1.2.2",
|
"expect-type": "^1.2.2",
|
||||||
"magic-string": "^0.30.21",
|
"magic-string": "^0.30.21",
|
||||||
|
|
@ -2514,10 +2496,10 @@
|
||||||
"@edge-runtime/vm": "*",
|
"@edge-runtime/vm": "*",
|
||||||
"@opentelemetry/api": "^1.9.0",
|
"@opentelemetry/api": "^1.9.0",
|
||||||
"@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
|
"@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
|
||||||
"@vitest/browser-playwright": "4.0.15",
|
"@vitest/browser-playwright": "4.0.17",
|
||||||
"@vitest/browser-preview": "4.0.15",
|
"@vitest/browser-preview": "4.0.17",
|
||||||
"@vitest/browser-webdriverio": "4.0.15",
|
"@vitest/browser-webdriverio": "4.0.17",
|
||||||
"@vitest/ui": "4.0.15",
|
"@vitest/ui": "4.0.17",
|
||||||
"happy-dom": "*",
|
"happy-dom": "*",
|
||||||
"jsdom": "*"
|
"jsdom": "*"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -10,15 +10,15 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/mdast": "4.0.4",
|
"@types/mdast": "4.0.4",
|
||||||
"@types/node": "24.10.4",
|
"@types/node": "24.10.9",
|
||||||
"@vitest/coverage-v8": "4.0.15",
|
"@vitest/coverage-v8": "4.0.17",
|
||||||
"mdast-util-to-string": "4.0.0",
|
"mdast-util-to-string": "4.0.0",
|
||||||
"remark": "15.0.1",
|
"remark": "15.0.1",
|
||||||
"remark-parse": "11.0.0",
|
"remark-parse": "11.0.0",
|
||||||
"typescript": "5.9.3",
|
"typescript": "5.9.3",
|
||||||
"unified": "11.0.5",
|
"unified": "11.0.5",
|
||||||
"vite": "7.3.0",
|
"vite": "7.3.1",
|
||||||
"vite-node": "5.2.0",
|
"vite-node": "5.2.0",
|
||||||
"vitest": "4.0.15"
|
"vitest": "4.0.17"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue